From b57ba0f9d5977c6a049b122bf615bc6560fc20e0 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 20 Dec 2024 11:01:17 -0500 Subject: [PATCH 01/29] create new demos directory structure --- .../adjoint_diff_benchmarking/demo.py | 144 + .../adjoint_diff_benchmarking/metadata.json | 24 + .../adjoint_diff_benchmarking/requirements.in | 4 + demonstrations_v2/ahs_aquila/demo.py | 818 ++++ demonstrations_v2/ahs_aquila/metadata.json | 82 + demonstrations_v2/ahs_aquila/requirements.in | 5 + .../braket-parallel-gradients/demo.py | 608 +++ .../braket-parallel-gradients/metadata.json | 50 + .../braket-parallel-gradients/requirements.in | 5 + .../circuits_as_fourier_series/demo.py | 800 ++++ .../circuits_as_fourier_series/metadata.json | 47 + .../requirements.in | 0 demonstrations_v2/covalent_cloud_gpu/demo.py | 369 ++ .../covalent_cloud_gpu/metadata.json | 56 + .../covalent_cloud_gpu/requirements.in | 5 + demonstrations_v2/ensemble_multi_qpu/demo.py | 578 +++ .../ensemble_multi_qpu/metadata.json | 32 + .../ensemble_multi_qpu/requirements.in | 6 + .../function_fitting_qsp/demo.py | 576 +++ .../function_fitting_qsp/metadata.json | 38 + .../function_fitting_qsp/requirements.in | 4 + demonstrations_v2/gbs/demo.py | 478 +++ demonstrations_v2/gbs/metadata.json | 100 + demonstrations_v2/gbs/requirements.in | 4 + .../getting_started_with_hybrid_jobs/demo.py | 404 ++ .../metadata.json | 37 + .../requirements.in | 4 + demonstrations_v2/gqe_training/demo.py | 705 +++ demonstrations_v2/gqe_training/metadata.json | 77 + .../gqe_training/requirements.in | 6 + .../how_to_use_qiskit1_with_pennylane/demo.py | 430 ++ .../metadata.json | 57 + .../requirements.in | 2 + demonstrations_v2/ibm_pennylane/demo.py | 362 ++ demonstrations_v2/ibm_pennylane/metadata.json | 47 + .../ibm_pennylane/requirements.in | 5 + demonstrations_v2/learning2learn/demo.py | 1181 +++++ .../learning2learn/metadata.json | 39 + .../learning2learn/requirements.in | 5 + .../ml_classical_shadows/demo.py | 859 ++++ .../ml_classical_shadows/metadata.json | 67 + .../ml_classical_shadows/requirements.in | 7 + demonstrations_v2/oqc_pulse/demo.py | 545 +++ demonstrations_v2/oqc_pulse/metadata.json | 73 + demonstrations_v2/oqc_pulse/requirements.in | 6 + demonstrations_v2/plugins_hybrid/demo.py | 449 ++ .../plugins_hybrid/metadata.json | 37 + .../plugins_hybrid/requirements.in | 1 + demonstrations_v2/pytorch_noise/demo.py | 251 ++ demonstrations_v2/pytorch_noise/metadata.json | 39 + .../pytorch_noise/requirements.in | 2 + demonstrations_v2/qnspsa/demo.py | 1040 +++++ demonstrations_v2/qnspsa/metadata.json | 92 + demonstrations_v2/qnspsa/requirements.in | 4 + demonstrations_v2/qonn/demo.py | 474 ++ demonstrations_v2/qonn/metadata.json | 32 + demonstrations_v2/qonn/requirements.in | 2 + demonstrations_v2/qrack/demo.py | 457 ++ demonstrations_v2/qrack/metadata.json | 69 + demonstrations_v2/qrack/requirements.in | 3 + .../qsim_beyond_classical/demo.py | 583 +++ .../qsim_beyond_classical/metadata.json | 94 + .../qsim_beyond_classical/requirements.in | 4 + demonstrations_v2/quantum_neural_net/demo.py | 741 ++++ .../quantum_neural_net/metadata.json | 42 + .../quantum_neural_net/requirements.in | 2 + demonstrations_v2/quantum_volume/demo.py | 890 ++++ .../quantum_volume/metadata.json | 111 + .../quantum_volume/requirements.in | 6 + .../demo.py | 222 + .../metadata.json | 53 + .../requirements.in | 4 + .../demo.py | 239 ++ .../metadata.json | 53 + .../requirements.in | 4 + .../demo.py | 217 + .../metadata.json | 50 + .../requirements.in | 5 + .../demo.py | 212 + .../metadata.json | 51 + .../requirements.in | 3 + demonstrations_v2/tutorial_QGAN/demo.py | 289 ++ demonstrations_v2/tutorial_QGAN/metadata.json | 28 + .../tutorial_QGAN/requirements.in | 3 + .../QUBO/dwave_results_slack.json | 1 + .../QUBO/dwave_results_unbalanced.json | 1 + demonstrations_v2/tutorial_QUBO/demo.py | 741 ++++ demonstrations_v2/tutorial_QUBO/metadata.json | 31 + .../tutorial_QUBO/requirements.in | 8 + .../tutorial_adaptive_circuits/demo.py | 397 ++ .../tutorial_adaptive_circuits/metadata.json | 96 + .../requirements.in | 1 + .../tutorial_adjoint_diff/demo.py | 455 ++ .../tutorial_adjoint_diff/metadata.json | 68 + .../tutorial_adjoint_diff/requirements.in | 4 + .../tutorial_adversarial_attacks_QML/demo.py | 465 ++ .../metadata.json | 85 + .../requirements.in | 3 + demonstrations_v2/tutorial_apply_qsvt/demo.py | 404 ++ .../tutorial_apply_qsvt/metadata.json | 91 + .../tutorial_apply_qsvt/requirements.in | 2 + demonstrations_v2/tutorial_backprop/demo.py | 453 ++ .../tutorial_backprop/metadata.json | 32 + .../tutorial_backprop/requirements.in | 4 + .../barren_gadgets/barren_gadgets.py | 130 + .../barren_gadgets/layered_ansatz.py | 54 + .../tutorial_barren_gadgets/demo.py | 387 ++ .../tutorial_barren_gadgets/metadata.json | 63 + .../tutorial_barren_gadgets/requirements.in | 2 + .../tutorial_barren_plateaus/demo.py | 211 + .../tutorial_barren_plateaus/metadata.json | 62 + .../tutorial_barren_plateaus/requirements.in | 2 + .../tutorial_block_encoding/demo.py | 391 ++ .../tutorial_block_encoding/metadata.json | 67 + .../tutorial_block_encoding/requirements.in | 3 + demonstrations_v2/tutorial_bluequbit/demo.py | 182 + .../tutorial_bluequbit/metadata.json | 59 + .../tutorial_bluequbit/requirements.in | 4 + .../tutorial_chemical_reactions/demo.py | 434 ++ .../tutorial_chemical_reactions/metadata.json | 50 + .../requirements.in | 2 + .../tutorial_circuit_compilation/demo.py | 241 ++ .../metadata.json | 37 + .../requirements.in | 2 + .../demo.py | 568 +++ .../metadata.json | 89 + .../requirements.in | 2 + .../tutorial_classical_kernels/demo.py | 769 ++++ .../tutorial_classical_kernels/metadata.json | 70 + .../requirements.in | 2 + .../tutorial_classical_shadows/demo.py | 715 +++ .../tutorial_classical_shadows/metadata.json | 74 + .../requirements.in | 2 + .../tutorial_classically_boosted_vqe/demo.py | 559 +++ .../metadata.json | 53 + .../requirements.in | 2 + .../demo.py | 514 +++ .../metadata.json | 96 + .../requirements.in | 3 + .../tutorial_coherent_vqls/demo.py | 558 +++ .../tutorial_coherent_vqls/metadata.json | 51 + .../tutorial_coherent_vqls/requirements.in | 2 + .../tutorial_constant_depth_mps_prep/demo.py | 848 ++++ .../metadata.json | 49 + .../requirements.in | 3 + .../tutorial_contextuality/demo.py | 804 ++++ .../tutorial_contextuality/metadata.json | 82 + .../tutorial_contextuality/requirements.in | 7 + .../demo.py | 470 ++ .../metadata.json | 72 + .../requirements.in | 2 + .../tutorial_diffable-mitigation/demo.py | 287 ++ .../metadata.json | 51 + .../requirements.in | 2 + .../tutorial_diffable_shadows/demo.py | 505 +++ .../tutorial_diffable_shadows/metadata.json | 66 + .../tutorial_diffable_shadows/requirements.in | 2 + .../tutorial_differentiable_HF/demo.py | 390 ++ .../tutorial_differentiable_HF/metadata.json | 66 + .../requirements.in | 3 + .../tutorial_doubly_stochastic/demo.py | 404 ++ .../tutorial_doubly_stochastic/metadata.json | 52 + .../requirements.in | 3 + .../tutorial_eqnn_force_field/demo.py | 631 +++ .../tutorial_eqnn_force_field/metadata.json | 108 + .../tutorial_eqnn_force_field/requirements.in | 7 + .../demo.py | 335 ++ .../metadata.json | 57 + .../requirements.in | 4 + .../tutorial_error_mitigation/demo.py | 595 +++ .../error_mitigation/params.npy | Bin 0 -> 208 bytes .../tutorial_error_mitigation/metadata.json | 78 + .../tutorial_error_mitigation/requirements.in | 6 + demonstrations_v2/tutorial_error_prop/demo.py | 245 ++ .../tutorial_error_prop/metadata.json | 48 + .../tutorial_error_prop/requirements.in | 1 + .../demo.py | 858 ++++ .../metadata.json | 48 + .../requirements.in | 2 + demonstrations_v2/tutorial_falqon/demo.py | 475 ++ .../tutorial_falqon/metadata.json | 53 + .../tutorial_falqon/requirements.in | 3 + .../tutorial_fermionic_operators/demo.py | 227 + .../metadata.json | 55 + .../requirements.in | 1 + .../tutorial_gaussian_transformation/demo.py | 169 + .../metadata.json | 42 + .../requirements.in | 4 + .../tutorial_general_parshift/demo.py | 888 ++++ .../tutorial_general_parshift/metadata.json | 90 + .../tutorial_general_parshift/requirements.in | 6 + .../tutorial_geometric_qml/demo.py | 898 ++++ .../tutorial_geometric_qml/metadata.json | 60 + .../tutorial_geometric_qml/requirements.in | 4 + .../tutorial_givens_rotations/demo.py | 495 +++ .../tutorial_givens_rotations/metadata.json | 65 + .../tutorial_givens_rotations/requirements.in | 2 + .../tutorial_grovers_algorithm/demo.py | 392 ++ .../tutorial_grovers_algorithm/metadata.json | 39 + .../requirements.in | 3 + .../demo.py | 253 ++ .../metadata.json | 46 + .../requirements.in | 4 + .../tutorial_haar_measure/demo.py | 812 ++++ .../tutorial_haar_measure/metadata.json | 144 + .../tutorial_haar_measure/requirements.in | 4 + .../tutorial_here_comes_the_sun/demo.py | 573 +++ .../tutorial_here_comes_the_sun/metadata.json | 77 + .../requirements.in | 5 + .../demo.py | 417 ++ .../metadata.json | 71 + .../requirements.in | 2 + .../tutorial_how_to_collect_mcm_stats/demo.py | 227 + .../metadata.json | 50 + .../requirements.in | 2 + .../demo.py | 252 ++ .../metadata.json | 50 + .../requirements.in | 2 + .../demo.py | 217 + .../metadata.json | 42 + .../requirements.in | 4 + .../demo.py | 197 + .../metadata.json | 49 + .../requirements.in | 5 + .../tutorial_how_to_use_noise_models/demo.py | 234 + .../metadata.json | 42 + .../requirements.in | 3 + .../demo.py | 358 ++ .../metadata.json | 65 + .../requirements.in | 1 + .../tutorial_how_to_use_registers/demo.py | 182 + .../metadata.json | 42 + .../requirements.in | 2 + .../demo.py | 654 +++ .../metadata.json | 98 + .../requirements.in | 6 + .../demo.py | 339 ++ .../metadata.json | 41 + .../requirements.in | 3 + .../demo.py | 375 ++ .../metadata.json | 85 + .../requirements.in | 3 + demonstrations_v2/tutorial_intro_qrom/demo.py | 352 ++ .../tutorial_intro_qrom/metadata.json | 81 + .../tutorial_intro_qrom/requirements.in | 3 + demonstrations_v2/tutorial_intro_qsvt/demo.py | 314 ++ .../tutorial_intro_qsvt/metadata.json | 69 + .../tutorial_intro_qsvt/requirements.in | 3 + .../tutorial_isingmodel_PyTorch/demo.py | 251 ++ .../tutorial_isingmodel_PyTorch/metadata.json | 66 + .../requirements.in | 3 + .../tutorial_jax_transformations/demo.py | 308 ++ .../metadata.json | 42 + .../requirements.in | 3 + .../tutorial_kak_theorem/demo.py | 986 +++++ .../tutorial_kak_theorem/metadata.json | 174 + .../tutorial_kak_theorem/requirements.in | 2 + .../tutorial_kernel_based_training/demo.py | 687 +++ .../metadata.json | 33 + .../requirements.in | 5 + .../tutorial_kernels_module/demo.py | 630 +++ .../tutorial_kernels_module/metadata.json | 71 + .../tutorial_kernels_module/requirements.in | 3 + .../tutorial_lcu_blockencoding/demo.py | 277 ++ .../tutorial_lcu_blockencoding/metadata.json | 60 + .../requirements.in | 3 + .../demo.py | 432 ++ .../metadata.json | 67 + .../requirements.in | 4 + .../tutorial_learning_few_data/demo.py | 598 +++ .../tutorial_learning_few_data/metadata.json | 87 + .../requirements.in | 9 + .../demo.py | 539 +++ .../metadata.json | 48 + .../requirements.in | 4 + .../tutorial_learningshallow/demo.py | 465 ++ .../tutorial_learningshallow/metadata.json | 85 + .../tutorial_learningshallow/requirements.in | 7 + demonstrations_v2/tutorial_liealgebra/demo.py | 458 ++ .../tutorial_liealgebra/metadata.json | 115 + .../tutorial_liealgebra/requirements.in | 2 + demonstrations_v2/tutorial_liesim/demo.py | 483 +++ .../tutorial_liesim/metadata.json | 165 + .../tutorial_liesim/requirements.in | 7 + .../tutorial_liesim_extension/demo.py | 536 +++ .../tutorial_liesim_extension/metadata.json | 100 + .../tutorial_liesim_extension/requirements.in | 4 + .../tutorial_local_cost_functions/demo.py | 534 +++ .../metadata.json | 44 + .../requirements.in | 2 + .../tutorial_magic_state_distillation/demo.py | 335 ++ .../metadata.json | 52 + .../requirements.in | 5 + demonstrations_v2/tutorial_mapping/demo.py | 343 ++ .../tutorial_mapping/metadata.json | 51 + .../tutorial_mapping/requirements.in | 1 + demonstrations_v2/tutorial_mbqc/demo.py | 809 ++++ demonstrations_v2/tutorial_mbqc/metadata.json | 245 ++ .../tutorial_mbqc/requirements.in | 4 + .../tutorial_mcm_introduction/demo.py | 469 ++ .../tutorial_mcm_introduction/metadata.json | 52 + .../tutorial_mcm_introduction/requirements.in | 2 + .../tutorial_measurement_optimize/demo.py | 836 ++++ .../metadata.json | 97 + .../requirements.in | 3 + .../tutorial_mitigation_advantage/demo.py | 275 ++ .../metadata.json | 74 + .../requirements.in | 4 + .../tutorial_mol_geo_opt/demo.py | 467 ++ .../tutorial_mol_geo_opt/metadata.json | 106 + .../tutorial_mol_geo_opt/requirements.in | 2 + demonstrations_v2/tutorial_mps/demo.py | 695 +++ demonstrations_v2/tutorial_mps/metadata.json | 101 + .../tutorial_mps/requirements.in | 3 + .../demo.py | 335 ++ .../metadata.json | 37 + .../multiclass_classification/iris.csv | 150 + .../requirements.in | 4 + .../tutorial_neutral_atoms/demo.py | 820 ++++ .../tutorial_neutral_atoms/metadata.json | 135 + .../tutorial_neutral_atoms/requirements.in | 4 + .../demo.py | 543 +++ .../metadata.json | 51 + .../requirements.in | 3 + .../tutorial_noisy_circuits/demo.py | 297 ++ .../tutorial_noisy_circuits/metadata.json | 47 + .../tutorial_noisy_circuits/requirements.in | 4 + demonstrations_v2/tutorial_odegen/demo.py | 369 ++ .../tutorial_odegen/metadata.json | 118 + .../tutorial_odegen/requirements.in | 6 + .../tutorial_optimal_control/demo.py | 843 ++++ .../tutorial_optimal_control/metadata.json | 95 + .../tutorial_optimal_control/requirements.in | 5 + demonstrations_v2/tutorial_pasqal/demo.py | 359 ++ .../tutorial_pasqal/metadata.json | 37 + .../pasqal/Eiffel_tower_data.dat | 126 + .../tutorial_pasqal/requirements.in | 5 + .../tutorial_phase_kickback/demo.py | 183 + .../tutorial_phase_kickback/metadata.json | 38 + .../tutorial_phase_kickback/requirements.in | 2 + demonstrations_v2/tutorial_photonics/demo.py | 963 +++++ .../tutorial_photonics/metadata.json | 142 + .../tutorial_photonics/requirements.in | 4 + .../demo.py | 815 ++++ .../metadata.json | 136 + .../requirements.in | 6 + .../tutorial_pulse_programming101/demo.py | 484 +++ .../metadata.json | 77 + .../requirements.in | 7 + demonstrations_v2/tutorial_qaoa_intro/demo.py | 513 +++ .../tutorial_qaoa_intro/metadata.json | 33 + .../tutorial_qaoa_intro/requirements.in | 5 + .../tutorial_qaoa_maxcut/demo.py | 296 ++ .../tutorial_qaoa_maxcut/metadata.json | 35 + .../tutorial_qaoa_maxcut/requirements.in | 2 + demonstrations_v2/tutorial_qcbm/demo.py | 576 +++ demonstrations_v2/tutorial_qcbm/metadata.json | 98 + .../tutorial_qcbm/requirements.in | 6 + .../tutorial_qchem_external/demo.py | 227 + .../tutorial_qchem_external/metadata.json | 52 + .../tutorial_qchem_external/requirements.in | 3 + demonstrations_v2/tutorial_qft/demo.py | 179 + demonstrations_v2/tutorial_qft/metadata.json | 31 + .../tutorial_qft/requirements.in | 4 + .../tutorial_qft_arithmetics/demo.py | 430 ++ .../tutorial_qft_arithmetics/metadata.json | 44 + .../tutorial_qft_arithmetics/requirements.in | 3 + demonstrations_v2/tutorial_qgrnn/demo.py | 675 +++ .../tutorial_qgrnn/metadata.json | 38 + .../tutorial_qgrnn/requirements.in | 4 + .../demo.py | 401 ++ .../metadata.json | 64 + .../requirements.in | 3 + .../tutorial_qnn_module_tf/demo.py | 232 + .../tutorial_qnn_module_tf/metadata.json | 33 + .../tutorial_qnn_module_tf/requirements.in | 5 + .../tutorial_qnn_module_torch/demo.py | 291 ++ .../tutorial_qnn_module_torch/metadata.json | 33 + .../tutorial_qnn_module_torch/requirements.in | 5 + .../demo.py | 289 ++ .../metadata.json | 83 + .../requirements.in | 6 + demonstrations_v2/tutorial_qpe/demo.py | 292 ++ demonstrations_v2/tutorial_qpe/metadata.json | 60 + .../tutorial_qpe/requirements.in | 3 + .../tutorial_qsvt_hardware/demo.py | 164 + .../tutorial_qsvt_hardware/metadata.json | 43 + .../tutorial_qsvt_hardware/requirements.in | 2 + .../tutorial_quantum_analytic_descent/demo.py | 709 +++ .../metadata.json | 80 + .../requirements.in | 2 + .../tutorial_quantum_chemistry/demo.py | 328 ++ .../tutorial_quantum_chemistry/metadata.json | 87 + .../quantum_chemistry/h2o.xyz | 5 + .../requirements.in | 2 + .../tutorial_quantum_circuit_cutting/demo.py | 889 ++++ .../metadata.json | 73 + .../requirements.in | 3 + .../tutorial_quantum_dropout/demo.py | 700 +++ .../tutorial_quantum_dropout/metadata.json | 68 + .../tutorial_quantum_dropout/requirements.in | 7 + .../tutorial_quantum_gans/demo.py | 599 +++ .../tutorial_quantum_gans/metadata.json | 52 + .../quantum_gans/optdigits.tra | 3823 +++++++++++++++++ .../tutorial_quantum_gans/requirements.in | 6 + .../tutorial_quantum_metrology/demo.py | 359 ++ .../tutorial_quantum_metrology/metadata.json | 44 + .../requirements.in | 3 + .../tutorial_quantum_natural_gradient/demo.py | 496 +++ .../metadata.json | 76 + .../requirements.in | 4 + .../demo.py | 609 +++ .../metadata.json | 64 + .../requirements.in | 4 + .../tutorial_quanvolution/demo.py | 371 ++ .../tutorial_quanvolution/metadata.json | 39 + .../tutorial_quanvolution/requirements.in | 3 + .../tutorial_qubit_rotation/demo.py | 370 ++ .../tutorial_qubit_rotation/metadata.json | 44 + .../tutorial_qubit_rotation/requirements.in | 4 + .../tutorial_qubit_tapering/demo.py | 309 ++ .../tutorial_qubit_tapering/metadata.json | 74 + .../tutorial_qubit_tapering/requirements.in | 1 + .../tutorial_qubitization/demo.py | 167 + .../tutorial_qubitization/metadata.json | 50 + .../tutorial_qubitization/requirements.in | 3 + .../demo.py | 405 ++ .../metadata.json | 48 + .../requirements.in | 1 + .../tutorial_resource_estimation/demo.py | 315 ++ .../metadata.json | 74 + .../requirements.in | 2 + demonstrations_v2/tutorial_rl_pulse/demo.py | 1049 +++++ .../tutorial_rl_pulse/metadata.json | 128 + .../tutorial_rl_pulse/requirements.in | 7 + .../rl_pulse/DemoOG_RLpulse.png | Bin 0 -> 208606 bytes .../rl_pulse/sketch_protocol.png | Bin 0 -> 140921 bytes .../tutorial_rl_pulse/rl_pulse/sketch_rl.png | Bin 0 -> 253837 bytes demonstrations_v2/tutorial_rosalin/demo.py | 663 +++ .../tutorial_rosalin/metadata.json | 86 + .../tutorial_rosalin/requirements.in | 3 + demonstrations_v2/tutorial_rotoselect/demo.py | 467 ++ .../tutorial_rotoselect/metadata.json | 54 + .../tutorial_rotoselect/requirements.in | 2 + demonstrations_v2/tutorial_sc_qubits/demo.py | 876 ++++ .../tutorial_sc_qubits/metadata.json | 117 + .../tutorial_sc_qubits/requirements.in | 3 + .../demo.py | 363 ++ .../metadata.json | 122 + .../requirements.in | 3 + demonstrations_v2/tutorial_spsa/demo.py | 554 +++ demonstrations_v2/tutorial_spsa/metadata.json | 90 + .../tutorial_spsa/requirements.in | 4 + .../tutorial_state_preparation/demo.py | 198 + .../tutorial_state_preparation/metadata.json | 42 + .../requirements.in | 3 + .../demo.py | 416 ++ .../metadata.json | 74 + .../requirements.in | 3 + .../tutorial_teleportation/demo.py | 436 ++ .../tutorial_teleportation/metadata.json | 74 + .../tutorial_teleportation/requirements.in | 2 + .../tutorial_testing_symmetry/demo.py | 465 ++ .../tutorial_testing_symmetry/metadata.json | 49 + .../tutorial_testing_symmetry/requirements.in | 1 + .../tutorial_tn_circuits/demo.py | 418 ++ .../tutorial_tn_circuits/metadata.json | 63 + .../tutorial_tn_circuits/requirements.in | 3 + demonstrations_v2/tutorial_toric_code/demo.py | 1014 +++++ .../tutorial_toric_code/metadata.json | 76 + .../tutorial_toric_code/requirements.in | 3 + .../tutorial_trapped_ions/demo.py | 1102 +++++ .../tutorial_trapped_ions/metadata.json | 182 + .../tutorial_trapped_ions/requirements.in | 4 + .../tutorial_unitary_designs/demo.py | 740 ++++ .../tutorial_unitary_designs/metadata.json | 161 + .../tutorial_unitary_designs/requirements.in | 3 + .../tutorial_univariate_qvr/demo.py | 1266 ++++++ .../tutorial_univariate_qvr/metadata.json | 88 + .../tutorial_univariate_qvr/requirements.in | 4 + .../tutorial_variational_classifier/demo.py | 554 +++ .../metadata.json | 44 + .../requirements.in | 2 + .../data/iris_classes1and2_scaled.txt | 100 + .../data/iris_classes2and3_scaled.txt | 100 + .../data/iris_scaled.txt | 100 + .../variational_classifier/data/moons.txt | 100 + .../data/parity_test.txt | 6 + .../data/parity_train.txt | 10 + .../variational_classifier/data/sine.txt | 50 + demonstrations_v2/tutorial_vqe/demo.py | 292 ++ demonstrations_v2/tutorial_vqe/metadata.json | 80 + .../tutorial_vqe/requirements.in | 5 + demonstrations_v2/tutorial_vqe_qng/demo.py | 475 ++ .../tutorial_vqe_qng/metadata.json | 71 + .../tutorial_vqe_qng/requirements.in | 2 + .../vqe_qng/param_landscape.npy | Bin 0 -> 80128 bytes .../tutorial_vqe_spin_sectors/demo.py | 376 ++ .../tutorial_vqe_spin_sectors/metadata.json | 56 + .../tutorial_vqe_spin_sectors/requirements.in | 1 + demonstrations_v2/tutorial_vqe_vqd/demo.py | 225 + .../tutorial_vqe_vqd/metadata.json | 56 + .../tutorial_vqe_vqd/requirements.in | 5 + demonstrations_v2/tutorial_vqls/demo.py | 530 +++ demonstrations_v2/tutorial_vqls/metadata.json | 45 + .../tutorial_vqls/requirements.in | 2 + demonstrations_v2/tutorial_vqt/demo.py | 572 +++ demonstrations_v2/tutorial_vqt/metadata.json | 44 + .../tutorial_vqt/requirements.in | 6 + .../tutorial_zne_catalyst/demo.py | 312 ++ .../tutorial_zne_catalyst/metadata.json | 84 + .../tutorial_zne_catalyst/requirements.in | 4 + .../tutorial_zx_calculus/demo.py | 867 ++++ .../tutorial_zx_calculus/metadata.json | 142 + .../tutorial_zx_calculus/requirements.in | 3 + demonstrations_v2/vqe_parallel/demo.py | 387 ++ demonstrations_v2/vqe_parallel/metadata.json | 32 + .../vqe_parallel/requirements.in | 3 + .../vqe_parallel/vqe_parallel/RY_params.npy | Bin 0 -> 208 bytes .../vqe_parallel/vqe_parallel/diagram.svg | 893 ++++ .../vqe_parallel/vqe_parallel/h2_0.30.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_0.50.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_0.70.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_0.90.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_1.10.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_1.30.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_1.50.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_1.70.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_1.90.xyz | 4 + .../vqe_parallel/vqe_parallel/h2_2.10.xyz | 4 + .../vqe_parallel/vqe_parallel/vqe_diagram.png | Bin 0 -> 273301 bytes .../vqe_parallel/vqe_parallel.zip | Bin 0 -> 1382 bytes .../vqe_parallel/vqe_parallel_001.png | Bin 0 -> 47704 bytes 533 files changed, 99957 insertions(+) create mode 100644 demonstrations_v2/adjoint_diff_benchmarking/demo.py create mode 100644 demonstrations_v2/adjoint_diff_benchmarking/metadata.json create mode 100644 demonstrations_v2/adjoint_diff_benchmarking/requirements.in create mode 100644 demonstrations_v2/ahs_aquila/demo.py create mode 100644 demonstrations_v2/ahs_aquila/metadata.json create mode 100644 demonstrations_v2/ahs_aquila/requirements.in create mode 100644 demonstrations_v2/braket-parallel-gradients/demo.py create mode 100644 demonstrations_v2/braket-parallel-gradients/metadata.json create mode 100644 demonstrations_v2/braket-parallel-gradients/requirements.in create mode 100644 demonstrations_v2/circuits_as_fourier_series/demo.py create mode 100644 demonstrations_v2/circuits_as_fourier_series/metadata.json create mode 100644 demonstrations_v2/circuits_as_fourier_series/requirements.in create mode 100644 demonstrations_v2/covalent_cloud_gpu/demo.py create mode 100644 demonstrations_v2/covalent_cloud_gpu/metadata.json create mode 100644 demonstrations_v2/covalent_cloud_gpu/requirements.in create mode 100644 demonstrations_v2/ensemble_multi_qpu/demo.py create mode 100644 demonstrations_v2/ensemble_multi_qpu/metadata.json create mode 100644 demonstrations_v2/ensemble_multi_qpu/requirements.in create mode 100644 demonstrations_v2/function_fitting_qsp/demo.py create mode 100644 demonstrations_v2/function_fitting_qsp/metadata.json create mode 100644 demonstrations_v2/function_fitting_qsp/requirements.in create mode 100644 demonstrations_v2/gbs/demo.py create mode 100644 demonstrations_v2/gbs/metadata.json create mode 100644 demonstrations_v2/gbs/requirements.in create mode 100644 demonstrations_v2/getting_started_with_hybrid_jobs/demo.py create mode 100644 demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json create mode 100644 demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in create mode 100644 demonstrations_v2/gqe_training/demo.py create mode 100644 demonstrations_v2/gqe_training/metadata.json create mode 100644 demonstrations_v2/gqe_training/requirements.in create mode 100644 demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py create mode 100644 demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json create mode 100644 demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in create mode 100644 demonstrations_v2/ibm_pennylane/demo.py create mode 100644 demonstrations_v2/ibm_pennylane/metadata.json create mode 100644 demonstrations_v2/ibm_pennylane/requirements.in create mode 100644 demonstrations_v2/learning2learn/demo.py create mode 100644 demonstrations_v2/learning2learn/metadata.json create mode 100644 demonstrations_v2/learning2learn/requirements.in create mode 100644 demonstrations_v2/ml_classical_shadows/demo.py create mode 100644 demonstrations_v2/ml_classical_shadows/metadata.json create mode 100644 demonstrations_v2/ml_classical_shadows/requirements.in create mode 100644 demonstrations_v2/oqc_pulse/demo.py create mode 100644 demonstrations_v2/oqc_pulse/metadata.json create mode 100644 demonstrations_v2/oqc_pulse/requirements.in create mode 100644 demonstrations_v2/plugins_hybrid/demo.py create mode 100644 demonstrations_v2/plugins_hybrid/metadata.json create mode 100644 demonstrations_v2/plugins_hybrid/requirements.in create mode 100644 demonstrations_v2/pytorch_noise/demo.py create mode 100644 demonstrations_v2/pytorch_noise/metadata.json create mode 100644 demonstrations_v2/pytorch_noise/requirements.in create mode 100644 demonstrations_v2/qnspsa/demo.py create mode 100644 demonstrations_v2/qnspsa/metadata.json create mode 100644 demonstrations_v2/qnspsa/requirements.in create mode 100644 demonstrations_v2/qonn/demo.py create mode 100644 demonstrations_v2/qonn/metadata.json create mode 100644 demonstrations_v2/qonn/requirements.in create mode 100644 demonstrations_v2/qrack/demo.py create mode 100644 demonstrations_v2/qrack/metadata.json create mode 100644 demonstrations_v2/qrack/requirements.in create mode 100644 demonstrations_v2/qsim_beyond_classical/demo.py create mode 100644 demonstrations_v2/qsim_beyond_classical/metadata.json create mode 100644 demonstrations_v2/qsim_beyond_classical/requirements.in create mode 100644 demonstrations_v2/quantum_neural_net/demo.py create mode 100644 demonstrations_v2/quantum_neural_net/metadata.json create mode 100644 demonstrations_v2/quantum_neural_net/requirements.in create mode 100644 demonstrations_v2/quantum_volume/demo.py create mode 100644 demonstrations_v2/quantum_volume/metadata.json create mode 100644 demonstrations_v2/quantum_volume/requirements.in create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json create mode 100644 demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in create mode 100644 demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py create mode 100644 demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json create mode 100644 demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in create mode 100644 demonstrations_v2/tutorial_QGAN/demo.py create mode 100644 demonstrations_v2/tutorial_QGAN/metadata.json create mode 100644 demonstrations_v2/tutorial_QGAN/requirements.in create mode 100644 demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_slack.json create mode 100644 demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_unbalanced.json create mode 100644 demonstrations_v2/tutorial_QUBO/demo.py create mode 100644 demonstrations_v2/tutorial_QUBO/metadata.json create mode 100644 demonstrations_v2/tutorial_QUBO/requirements.in create mode 100644 demonstrations_v2/tutorial_adaptive_circuits/demo.py create mode 100644 demonstrations_v2/tutorial_adaptive_circuits/metadata.json create mode 100644 demonstrations_v2/tutorial_adaptive_circuits/requirements.in create mode 100644 demonstrations_v2/tutorial_adjoint_diff/demo.py create mode 100644 demonstrations_v2/tutorial_adjoint_diff/metadata.json create mode 100644 demonstrations_v2/tutorial_adjoint_diff/requirements.in create mode 100644 demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py create mode 100644 demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json create mode 100644 demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in create mode 100644 demonstrations_v2/tutorial_apply_qsvt/demo.py create mode 100644 demonstrations_v2/tutorial_apply_qsvt/metadata.json create mode 100644 demonstrations_v2/tutorial_apply_qsvt/requirements.in create mode 100644 demonstrations_v2/tutorial_backprop/demo.py create mode 100644 demonstrations_v2/tutorial_backprop/metadata.json create mode 100644 demonstrations_v2/tutorial_backprop/requirements.in create mode 100644 demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py create mode 100644 demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py create mode 100644 demonstrations_v2/tutorial_barren_gadgets/demo.py create mode 100644 demonstrations_v2/tutorial_barren_gadgets/metadata.json create mode 100644 demonstrations_v2/tutorial_barren_gadgets/requirements.in create mode 100644 demonstrations_v2/tutorial_barren_plateaus/demo.py create mode 100644 demonstrations_v2/tutorial_barren_plateaus/metadata.json create mode 100644 demonstrations_v2/tutorial_barren_plateaus/requirements.in create mode 100644 demonstrations_v2/tutorial_block_encoding/demo.py create mode 100644 demonstrations_v2/tutorial_block_encoding/metadata.json create mode 100644 demonstrations_v2/tutorial_block_encoding/requirements.in create mode 100644 demonstrations_v2/tutorial_bluequbit/demo.py create mode 100644 demonstrations_v2/tutorial_bluequbit/metadata.json create mode 100644 demonstrations_v2/tutorial_bluequbit/requirements.in create mode 100644 demonstrations_v2/tutorial_chemical_reactions/demo.py create mode 100644 demonstrations_v2/tutorial_chemical_reactions/metadata.json create mode 100644 demonstrations_v2/tutorial_chemical_reactions/requirements.in create mode 100644 demonstrations_v2/tutorial_circuit_compilation/demo.py create mode 100644 demonstrations_v2/tutorial_circuit_compilation/metadata.json create mode 100644 demonstrations_v2/tutorial_circuit_compilation/requirements.in create mode 100644 demonstrations_v2/tutorial_classical_expval_estimation/demo.py create mode 100644 demonstrations_v2/tutorial_classical_expval_estimation/metadata.json create mode 100644 demonstrations_v2/tutorial_classical_expval_estimation/requirements.in create mode 100644 demonstrations_v2/tutorial_classical_kernels/demo.py create mode 100644 demonstrations_v2/tutorial_classical_kernels/metadata.json create mode 100644 demonstrations_v2/tutorial_classical_kernels/requirements.in create mode 100644 demonstrations_v2/tutorial_classical_shadows/demo.py create mode 100644 demonstrations_v2/tutorial_classical_shadows/metadata.json create mode 100644 demonstrations_v2/tutorial_classical_shadows/requirements.in create mode 100644 demonstrations_v2/tutorial_classically_boosted_vqe/demo.py create mode 100644 demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json create mode 100644 demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in create mode 100644 demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py create mode 100644 demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json create mode 100644 demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in create mode 100644 demonstrations_v2/tutorial_coherent_vqls/demo.py create mode 100644 demonstrations_v2/tutorial_coherent_vqls/metadata.json create mode 100644 demonstrations_v2/tutorial_coherent_vqls/requirements.in create mode 100644 demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py create mode 100644 demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json create mode 100644 demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in create mode 100644 demonstrations_v2/tutorial_contextuality/demo.py create mode 100644 demonstrations_v2/tutorial_contextuality/metadata.json create mode 100644 demonstrations_v2/tutorial_contextuality/requirements.in create mode 100644 demonstrations_v2/tutorial_data_reuploading_classifier/demo.py create mode 100644 demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json create mode 100644 demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in create mode 100644 demonstrations_v2/tutorial_diffable-mitigation/demo.py create mode 100644 demonstrations_v2/tutorial_diffable-mitigation/metadata.json create mode 100644 demonstrations_v2/tutorial_diffable-mitigation/requirements.in create mode 100644 demonstrations_v2/tutorial_diffable_shadows/demo.py create mode 100644 demonstrations_v2/tutorial_diffable_shadows/metadata.json create mode 100644 demonstrations_v2/tutorial_diffable_shadows/requirements.in create mode 100644 demonstrations_v2/tutorial_differentiable_HF/demo.py create mode 100644 demonstrations_v2/tutorial_differentiable_HF/metadata.json create mode 100644 demonstrations_v2/tutorial_differentiable_HF/requirements.in create mode 100644 demonstrations_v2/tutorial_doubly_stochastic/demo.py create mode 100644 demonstrations_v2/tutorial_doubly_stochastic/metadata.json create mode 100644 demonstrations_v2/tutorial_doubly_stochastic/requirements.in create mode 100644 demonstrations_v2/tutorial_eqnn_force_field/demo.py create mode 100644 demonstrations_v2/tutorial_eqnn_force_field/metadata.json create mode 100644 demonstrations_v2/tutorial_eqnn_force_field/requirements.in create mode 100644 demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py create mode 100644 demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json create mode 100644 demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in create mode 100644 demonstrations_v2/tutorial_error_mitigation/demo.py create mode 100644 demonstrations_v2/tutorial_error_mitigation/error_mitigation/params.npy create mode 100644 demonstrations_v2/tutorial_error_mitigation/metadata.json create mode 100644 demonstrations_v2/tutorial_error_mitigation/requirements.in create mode 100644 demonstrations_v2/tutorial_error_prop/demo.py create mode 100644 demonstrations_v2/tutorial_error_prop/metadata.json create mode 100644 demonstrations_v2/tutorial_error_prop/requirements.in create mode 100644 demonstrations_v2/tutorial_expressivity_fourier_series/demo.py create mode 100644 demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json create mode 100644 demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in create mode 100644 demonstrations_v2/tutorial_falqon/demo.py create mode 100644 demonstrations_v2/tutorial_falqon/metadata.json create mode 100644 demonstrations_v2/tutorial_falqon/requirements.in create mode 100644 demonstrations_v2/tutorial_fermionic_operators/demo.py create mode 100644 demonstrations_v2/tutorial_fermionic_operators/metadata.json create mode 100644 demonstrations_v2/tutorial_fermionic_operators/requirements.in create mode 100644 demonstrations_v2/tutorial_gaussian_transformation/demo.py create mode 100644 demonstrations_v2/tutorial_gaussian_transformation/metadata.json create mode 100644 demonstrations_v2/tutorial_gaussian_transformation/requirements.in create mode 100644 demonstrations_v2/tutorial_general_parshift/demo.py create mode 100644 demonstrations_v2/tutorial_general_parshift/metadata.json create mode 100644 demonstrations_v2/tutorial_general_parshift/requirements.in create mode 100644 demonstrations_v2/tutorial_geometric_qml/demo.py create mode 100644 demonstrations_v2/tutorial_geometric_qml/metadata.json create mode 100644 demonstrations_v2/tutorial_geometric_qml/requirements.in create mode 100644 demonstrations_v2/tutorial_givens_rotations/demo.py create mode 100644 demonstrations_v2/tutorial_givens_rotations/metadata.json create mode 100644 demonstrations_v2/tutorial_givens_rotations/requirements.in create mode 100644 demonstrations_v2/tutorial_grovers_algorithm/demo.py create mode 100644 demonstrations_v2/tutorial_grovers_algorithm/metadata.json create mode 100644 demonstrations_v2/tutorial_grovers_algorithm/requirements.in create mode 100644 demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py create mode 100644 demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json create mode 100644 demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in create mode 100644 demonstrations_v2/tutorial_haar_measure/demo.py create mode 100644 demonstrations_v2/tutorial_haar_measure/metadata.json create mode 100644 demonstrations_v2/tutorial_haar_measure/requirements.in create mode 100644 demonstrations_v2/tutorial_here_comes_the_sun/demo.py create mode 100644 demonstrations_v2/tutorial_here_comes_the_sun/metadata.json create mode 100644 demonstrations_v2/tutorial_here_comes_the_sun/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_use_noise_models/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in create mode 100644 demonstrations_v2/tutorial_how_to_use_registers/demo.py create mode 100644 demonstrations_v2/tutorial_how_to_use_registers/metadata.json create mode 100644 demonstrations_v2/tutorial_how_to_use_registers/requirements.in create mode 100644 demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py create mode 100644 demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json create mode 100644 demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in create mode 100644 demonstrations_v2/tutorial_initial_state_preparation/demo.py create mode 100644 demonstrations_v2/tutorial_initial_state_preparation/metadata.json create mode 100644 demonstrations_v2/tutorial_initial_state_preparation/requirements.in create mode 100644 demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py create mode 100644 demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json create mode 100644 demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in create mode 100644 demonstrations_v2/tutorial_intro_qrom/demo.py create mode 100644 demonstrations_v2/tutorial_intro_qrom/metadata.json create mode 100644 demonstrations_v2/tutorial_intro_qrom/requirements.in create mode 100644 demonstrations_v2/tutorial_intro_qsvt/demo.py create mode 100644 demonstrations_v2/tutorial_intro_qsvt/metadata.json create mode 100644 demonstrations_v2/tutorial_intro_qsvt/requirements.in create mode 100644 demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py create mode 100644 demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json create mode 100644 demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in create mode 100644 demonstrations_v2/tutorial_jax_transformations/demo.py create mode 100644 demonstrations_v2/tutorial_jax_transformations/metadata.json create mode 100644 demonstrations_v2/tutorial_jax_transformations/requirements.in create mode 100644 demonstrations_v2/tutorial_kak_theorem/demo.py create mode 100644 demonstrations_v2/tutorial_kak_theorem/metadata.json create mode 100644 demonstrations_v2/tutorial_kak_theorem/requirements.in create mode 100644 demonstrations_v2/tutorial_kernel_based_training/demo.py create mode 100644 demonstrations_v2/tutorial_kernel_based_training/metadata.json create mode 100644 demonstrations_v2/tutorial_kernel_based_training/requirements.in create mode 100644 demonstrations_v2/tutorial_kernels_module/demo.py create mode 100644 demonstrations_v2/tutorial_kernels_module/metadata.json create mode 100644 demonstrations_v2/tutorial_kernels_module/requirements.in create mode 100644 demonstrations_v2/tutorial_lcu_blockencoding/demo.py create mode 100644 demonstrations_v2/tutorial_lcu_blockencoding/metadata.json create mode 100644 demonstrations_v2/tutorial_lcu_blockencoding/requirements.in create mode 100644 demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py create mode 100644 demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json create mode 100644 demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in create mode 100644 demonstrations_v2/tutorial_learning_few_data/demo.py create mode 100644 demonstrations_v2/tutorial_learning_few_data/metadata.json create mode 100644 demonstrations_v2/tutorial_learning_few_data/requirements.in create mode 100644 demonstrations_v2/tutorial_learning_from_experiments/demo.py create mode 100644 demonstrations_v2/tutorial_learning_from_experiments/metadata.json create mode 100644 demonstrations_v2/tutorial_learning_from_experiments/requirements.in create mode 100644 demonstrations_v2/tutorial_learningshallow/demo.py create mode 100644 demonstrations_v2/tutorial_learningshallow/metadata.json create mode 100644 demonstrations_v2/tutorial_learningshallow/requirements.in create mode 100644 demonstrations_v2/tutorial_liealgebra/demo.py create mode 100644 demonstrations_v2/tutorial_liealgebra/metadata.json create mode 100644 demonstrations_v2/tutorial_liealgebra/requirements.in create mode 100644 demonstrations_v2/tutorial_liesim/demo.py create mode 100644 demonstrations_v2/tutorial_liesim/metadata.json create mode 100644 demonstrations_v2/tutorial_liesim/requirements.in create mode 100644 demonstrations_v2/tutorial_liesim_extension/demo.py create mode 100644 demonstrations_v2/tutorial_liesim_extension/metadata.json create mode 100644 demonstrations_v2/tutorial_liesim_extension/requirements.in create mode 100644 demonstrations_v2/tutorial_local_cost_functions/demo.py create mode 100644 demonstrations_v2/tutorial_local_cost_functions/metadata.json create mode 100644 demonstrations_v2/tutorial_local_cost_functions/requirements.in create mode 100644 demonstrations_v2/tutorial_magic_state_distillation/demo.py create mode 100644 demonstrations_v2/tutorial_magic_state_distillation/metadata.json create mode 100644 demonstrations_v2/tutorial_magic_state_distillation/requirements.in create mode 100644 demonstrations_v2/tutorial_mapping/demo.py create mode 100644 demonstrations_v2/tutorial_mapping/metadata.json create mode 100644 demonstrations_v2/tutorial_mapping/requirements.in create mode 100644 demonstrations_v2/tutorial_mbqc/demo.py create mode 100644 demonstrations_v2/tutorial_mbqc/metadata.json create mode 100644 demonstrations_v2/tutorial_mbqc/requirements.in create mode 100644 demonstrations_v2/tutorial_mcm_introduction/demo.py create mode 100644 demonstrations_v2/tutorial_mcm_introduction/metadata.json create mode 100644 demonstrations_v2/tutorial_mcm_introduction/requirements.in create mode 100644 demonstrations_v2/tutorial_measurement_optimize/demo.py create mode 100644 demonstrations_v2/tutorial_measurement_optimize/metadata.json create mode 100644 demonstrations_v2/tutorial_measurement_optimize/requirements.in create mode 100644 demonstrations_v2/tutorial_mitigation_advantage/demo.py create mode 100644 demonstrations_v2/tutorial_mitigation_advantage/metadata.json create mode 100644 demonstrations_v2/tutorial_mitigation_advantage/requirements.in create mode 100644 demonstrations_v2/tutorial_mol_geo_opt/demo.py create mode 100644 demonstrations_v2/tutorial_mol_geo_opt/metadata.json create mode 100644 demonstrations_v2/tutorial_mol_geo_opt/requirements.in create mode 100644 demonstrations_v2/tutorial_mps/demo.py create mode 100644 demonstrations_v2/tutorial_mps/metadata.json create mode 100644 demonstrations_v2/tutorial_mps/requirements.in create mode 100644 demonstrations_v2/tutorial_multiclass_classification/demo.py create mode 100644 demonstrations_v2/tutorial_multiclass_classification/metadata.json create mode 100644 demonstrations_v2/tutorial_multiclass_classification/multiclass_classification/iris.csv create mode 100644 demonstrations_v2/tutorial_multiclass_classification/requirements.in create mode 100644 demonstrations_v2/tutorial_neutral_atoms/demo.py create mode 100644 demonstrations_v2/tutorial_neutral_atoms/metadata.json create mode 100644 demonstrations_v2/tutorial_neutral_atoms/requirements.in create mode 100644 demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py create mode 100644 demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json create mode 100644 demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in create mode 100644 demonstrations_v2/tutorial_noisy_circuits/demo.py create mode 100644 demonstrations_v2/tutorial_noisy_circuits/metadata.json create mode 100644 demonstrations_v2/tutorial_noisy_circuits/requirements.in create mode 100644 demonstrations_v2/tutorial_odegen/demo.py create mode 100644 demonstrations_v2/tutorial_odegen/metadata.json create mode 100644 demonstrations_v2/tutorial_odegen/requirements.in create mode 100644 demonstrations_v2/tutorial_optimal_control/demo.py create mode 100644 demonstrations_v2/tutorial_optimal_control/metadata.json create mode 100644 demonstrations_v2/tutorial_optimal_control/requirements.in create mode 100644 demonstrations_v2/tutorial_pasqal/demo.py create mode 100644 demonstrations_v2/tutorial_pasqal/metadata.json create mode 100644 demonstrations_v2/tutorial_pasqal/pasqal/Eiffel_tower_data.dat create mode 100644 demonstrations_v2/tutorial_pasqal/requirements.in create mode 100644 demonstrations_v2/tutorial_phase_kickback/demo.py create mode 100644 demonstrations_v2/tutorial_phase_kickback/metadata.json create mode 100644 demonstrations_v2/tutorial_phase_kickback/requirements.in create mode 100644 demonstrations_v2/tutorial_photonics/demo.py create mode 100644 demonstrations_v2/tutorial_photonics/metadata.json create mode 100644 demonstrations_v2/tutorial_photonics/requirements.in create mode 100644 demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py create mode 100644 demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json create mode 100644 demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in create mode 100644 demonstrations_v2/tutorial_pulse_programming101/demo.py create mode 100644 demonstrations_v2/tutorial_pulse_programming101/metadata.json create mode 100644 demonstrations_v2/tutorial_pulse_programming101/requirements.in create mode 100644 demonstrations_v2/tutorial_qaoa_intro/demo.py create mode 100644 demonstrations_v2/tutorial_qaoa_intro/metadata.json create mode 100644 demonstrations_v2/tutorial_qaoa_intro/requirements.in create mode 100644 demonstrations_v2/tutorial_qaoa_maxcut/demo.py create mode 100644 demonstrations_v2/tutorial_qaoa_maxcut/metadata.json create mode 100644 demonstrations_v2/tutorial_qaoa_maxcut/requirements.in create mode 100644 demonstrations_v2/tutorial_qcbm/demo.py create mode 100644 demonstrations_v2/tutorial_qcbm/metadata.json create mode 100644 demonstrations_v2/tutorial_qcbm/requirements.in create mode 100644 demonstrations_v2/tutorial_qchem_external/demo.py create mode 100644 demonstrations_v2/tutorial_qchem_external/metadata.json create mode 100644 demonstrations_v2/tutorial_qchem_external/requirements.in create mode 100644 demonstrations_v2/tutorial_qft/demo.py create mode 100644 demonstrations_v2/tutorial_qft/metadata.json create mode 100644 demonstrations_v2/tutorial_qft/requirements.in create mode 100644 demonstrations_v2/tutorial_qft_arithmetics/demo.py create mode 100644 demonstrations_v2/tutorial_qft_arithmetics/metadata.json create mode 100644 demonstrations_v2/tutorial_qft_arithmetics/requirements.in create mode 100644 demonstrations_v2/tutorial_qgrnn/demo.py create mode 100644 demonstrations_v2/tutorial_qgrnn/metadata.json create mode 100644 demonstrations_v2/tutorial_qgrnn/requirements.in create mode 100644 demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py create mode 100644 demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json create mode 100644 demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in create mode 100644 demonstrations_v2/tutorial_qnn_module_tf/demo.py create mode 100644 demonstrations_v2/tutorial_qnn_module_tf/metadata.json create mode 100644 demonstrations_v2/tutorial_qnn_module_tf/requirements.in create mode 100644 demonstrations_v2/tutorial_qnn_module_torch/demo.py create mode 100644 demonstrations_v2/tutorial_qnn_module_torch/metadata.json create mode 100644 demonstrations_v2/tutorial_qnn_module_torch/requirements.in create mode 100644 demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py create mode 100644 demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json create mode 100644 demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in create mode 100644 demonstrations_v2/tutorial_qpe/demo.py create mode 100644 demonstrations_v2/tutorial_qpe/metadata.json create mode 100644 demonstrations_v2/tutorial_qpe/requirements.in create mode 100644 demonstrations_v2/tutorial_qsvt_hardware/demo.py create mode 100644 demonstrations_v2/tutorial_qsvt_hardware/metadata.json create mode 100644 demonstrations_v2/tutorial_qsvt_hardware/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_analytic_descent/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_chemistry/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_chemistry/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_chemistry/quantum_chemistry/h2o.xyz create mode 100644 demonstrations_v2/tutorial_quantum_chemistry/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_dropout/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_dropout/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_dropout/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_gans/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_gans/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_gans/quantum_gans/optdigits.tra create mode 100644 demonstrations_v2/tutorial_quantum_gans/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_metrology/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_metrology/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_metrology/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_natural_gradient/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in create mode 100644 demonstrations_v2/tutorial_quantum_transfer_learning/demo.py create mode 100644 demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json create mode 100644 demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in create mode 100644 demonstrations_v2/tutorial_quanvolution/demo.py create mode 100644 demonstrations_v2/tutorial_quanvolution/metadata.json create mode 100644 demonstrations_v2/tutorial_quanvolution/requirements.in create mode 100644 demonstrations_v2/tutorial_qubit_rotation/demo.py create mode 100644 demonstrations_v2/tutorial_qubit_rotation/metadata.json create mode 100644 demonstrations_v2/tutorial_qubit_rotation/requirements.in create mode 100644 demonstrations_v2/tutorial_qubit_tapering/demo.py create mode 100644 demonstrations_v2/tutorial_qubit_tapering/metadata.json create mode 100644 demonstrations_v2/tutorial_qubit_tapering/requirements.in create mode 100644 demonstrations_v2/tutorial_qubitization/demo.py create mode 100644 demonstrations_v2/tutorial_qubitization/metadata.json create mode 100644 demonstrations_v2/tutorial_qubitization/requirements.in create mode 100644 demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py create mode 100644 demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json create mode 100644 demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in create mode 100644 demonstrations_v2/tutorial_resource_estimation/demo.py create mode 100644 demonstrations_v2/tutorial_resource_estimation/metadata.json create mode 100644 demonstrations_v2/tutorial_resource_estimation/requirements.in create mode 100644 demonstrations_v2/tutorial_rl_pulse/demo.py create mode 100644 demonstrations_v2/tutorial_rl_pulse/metadata.json create mode 100644 demonstrations_v2/tutorial_rl_pulse/requirements.in create mode 100644 demonstrations_v2/tutorial_rl_pulse/rl_pulse/DemoOG_RLpulse.png create mode 100644 demonstrations_v2/tutorial_rl_pulse/rl_pulse/sketch_protocol.png create mode 100644 demonstrations_v2/tutorial_rl_pulse/rl_pulse/sketch_rl.png create mode 100644 demonstrations_v2/tutorial_rosalin/demo.py create mode 100644 demonstrations_v2/tutorial_rosalin/metadata.json create mode 100644 demonstrations_v2/tutorial_rosalin/requirements.in create mode 100644 demonstrations_v2/tutorial_rotoselect/demo.py create mode 100644 demonstrations_v2/tutorial_rotoselect/metadata.json create mode 100644 demonstrations_v2/tutorial_rotoselect/requirements.in create mode 100644 demonstrations_v2/tutorial_sc_qubits/demo.py create mode 100644 demonstrations_v2/tutorial_sc_qubits/metadata.json create mode 100644 demonstrations_v2/tutorial_sc_qubits/requirements.in create mode 100644 demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py create mode 100644 demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json create mode 100644 demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in create mode 100644 demonstrations_v2/tutorial_spsa/demo.py create mode 100644 demonstrations_v2/tutorial_spsa/metadata.json create mode 100644 demonstrations_v2/tutorial_spsa/requirements.in create mode 100644 demonstrations_v2/tutorial_state_preparation/demo.py create mode 100644 demonstrations_v2/tutorial_state_preparation/metadata.json create mode 100644 demonstrations_v2/tutorial_state_preparation/requirements.in create mode 100644 demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py create mode 100644 demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json create mode 100644 demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in create mode 100644 demonstrations_v2/tutorial_teleportation/demo.py create mode 100644 demonstrations_v2/tutorial_teleportation/metadata.json create mode 100644 demonstrations_v2/tutorial_teleportation/requirements.in create mode 100644 demonstrations_v2/tutorial_testing_symmetry/demo.py create mode 100644 demonstrations_v2/tutorial_testing_symmetry/metadata.json create mode 100644 demonstrations_v2/tutorial_testing_symmetry/requirements.in create mode 100644 demonstrations_v2/tutorial_tn_circuits/demo.py create mode 100644 demonstrations_v2/tutorial_tn_circuits/metadata.json create mode 100644 demonstrations_v2/tutorial_tn_circuits/requirements.in create mode 100644 demonstrations_v2/tutorial_toric_code/demo.py create mode 100644 demonstrations_v2/tutorial_toric_code/metadata.json create mode 100644 demonstrations_v2/tutorial_toric_code/requirements.in create mode 100644 demonstrations_v2/tutorial_trapped_ions/demo.py create mode 100644 demonstrations_v2/tutorial_trapped_ions/metadata.json create mode 100644 demonstrations_v2/tutorial_trapped_ions/requirements.in create mode 100644 demonstrations_v2/tutorial_unitary_designs/demo.py create mode 100644 demonstrations_v2/tutorial_unitary_designs/metadata.json create mode 100644 demonstrations_v2/tutorial_unitary_designs/requirements.in create mode 100644 demonstrations_v2/tutorial_univariate_qvr/demo.py create mode 100644 demonstrations_v2/tutorial_univariate_qvr/metadata.json create mode 100644 demonstrations_v2/tutorial_univariate_qvr/requirements.in create mode 100644 demonstrations_v2/tutorial_variational_classifier/demo.py create mode 100644 demonstrations_v2/tutorial_variational_classifier/metadata.json create mode 100644 demonstrations_v2/tutorial_variational_classifier/requirements.in create mode 100644 demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes1and2_scaled.txt create mode 100644 demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes2and3_scaled.txt create mode 100644 demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_scaled.txt create mode 100644 demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/moons.txt create mode 100644 demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_test.txt create mode 100644 demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_train.txt create mode 100644 demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/sine.txt create mode 100644 demonstrations_v2/tutorial_vqe/demo.py create mode 100644 demonstrations_v2/tutorial_vqe/metadata.json create mode 100644 demonstrations_v2/tutorial_vqe/requirements.in create mode 100644 demonstrations_v2/tutorial_vqe_qng/demo.py create mode 100644 demonstrations_v2/tutorial_vqe_qng/metadata.json create mode 100644 demonstrations_v2/tutorial_vqe_qng/requirements.in create mode 100644 demonstrations_v2/tutorial_vqe_qng/vqe_qng/param_landscape.npy create mode 100644 demonstrations_v2/tutorial_vqe_spin_sectors/demo.py create mode 100644 demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json create mode 100644 demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in create mode 100644 demonstrations_v2/tutorial_vqe_vqd/demo.py create mode 100644 demonstrations_v2/tutorial_vqe_vqd/metadata.json create mode 100644 demonstrations_v2/tutorial_vqe_vqd/requirements.in create mode 100644 demonstrations_v2/tutorial_vqls/demo.py create mode 100644 demonstrations_v2/tutorial_vqls/metadata.json create mode 100644 demonstrations_v2/tutorial_vqls/requirements.in create mode 100644 demonstrations_v2/tutorial_vqt/demo.py create mode 100644 demonstrations_v2/tutorial_vqt/metadata.json create mode 100644 demonstrations_v2/tutorial_vqt/requirements.in create mode 100644 demonstrations_v2/tutorial_zne_catalyst/demo.py create mode 100644 demonstrations_v2/tutorial_zne_catalyst/metadata.json create mode 100644 demonstrations_v2/tutorial_zne_catalyst/requirements.in create mode 100644 demonstrations_v2/tutorial_zx_calculus/demo.py create mode 100644 demonstrations_v2/tutorial_zx_calculus/metadata.json create mode 100644 demonstrations_v2/tutorial_zx_calculus/requirements.in create mode 100644 demonstrations_v2/vqe_parallel/demo.py create mode 100644 demonstrations_v2/vqe_parallel/metadata.json create mode 100644 demonstrations_v2/vqe_parallel/requirements.in create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/RY_params.npy create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/diagram.svg create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.30.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.50.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.70.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.90.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.10.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.30.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.50.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.70.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.90.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/h2_2.10.xyz create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/vqe_diagram.png create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/vqe_parallel.zip create mode 100644 demonstrations_v2/vqe_parallel/vqe_parallel/vqe_parallel_001.png diff --git a/demonstrations_v2/adjoint_diff_benchmarking/demo.py b/demonstrations_v2/adjoint_diff_benchmarking/demo.py new file mode 100644 index 0000000000..be11d3d52e --- /dev/null +++ b/demonstrations_v2/adjoint_diff_benchmarking/demo.py @@ -0,0 +1,144 @@ +r""" + +.. _adjoint_differentiation_benchmarking: + +Adjoint Differentiation +======================= + +.. meta:: + :property="og:description": Benchmarking file for adjoint diff demonstration. + :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/code.png + + +*Author: Christina Lee — Posted: 23 November 2021. Last updated: 04 July 2024.* + +""" + +############################################################################## +# This page is supplementary material to the +# `Adjoint Differentiation `__ +# demonstration. The below script produces the benchmarking images used. + +import timeit +import matplotlib.pyplot as plt +import pennylane as qml +import jax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +plt.style.use("bmh") + +n_samples = 5 + + +def get_time(qnode, params): + globals_dict = {'grad': jax.grad, 'circuit': qnode, 'params': params} + return timeit.timeit("grad(circuit)(params)", globals=globals_dict, number=n_samples) + + +def wires_scaling(n_wires, n_layers): + key = jax.random.PRNGKey(42) + + t_adjoint = [] + t_ps = [] + t_backprop = [] + + def circuit(params, wires): + qml.StronglyEntanglingLayers(params, wires=range(wires)) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2)) + + for i_wires in n_wires: + dev = qml.device("lightning.qubit", wires=i_wires) + dev_python = qml.device("default.qubit", wires=i_wires) + + circuit_adjoint = qml.QNode(lambda x: circuit(x, wires=i_wires), dev, diff_method="adjoint") + circuit_ps = qml.QNode(lambda x: circuit(x, wires=i_wires), dev, diff_method="parameter-shift") + circuit_backprop = qml.QNode(lambda x: circuit(x, wires=i_wires), dev_python, diff_method="backprop") + + # set up the parameters + param_shape = qml.StronglyEntanglingLayers.shape(n_wires=i_wires, n_layers=n_layers) + params = jax.random.normal(key, param_shape) + + t_adjoint.append(get_time(circuit_adjoint, params)) + t_backprop.append(get_time(circuit_backprop, params)) + t_ps.append(get_time(circuit_ps, params)) + + return t_adjoint, t_backprop, t_ps + + +def layers_scaling(n_wires, n_layers): + key = jax.random.PRNGKey(42) + + dev = qml.device("lightning.qubit", wires=n_wires) + dev_python = qml.device('default.qubit', wires=n_wires) + + t_adjoint = [] + t_ps = [] + t_backprop = [] + + def circuit(params): + qml.StronglyEntanglingLayers(params, wires=range(n_wires)) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2)) + + circuit_adjoint = qml.QNode(circuit, dev, diff_method="adjoint") + circuit_ps = qml.QNode(circuit, dev, diff_method="parameter-shift") + circuit_backprop = qml.QNode(circuit, dev_python, diff_method="backprop") + + for i_layers in n_layers: + # set up the parameters + param_shape = qml.StronglyEntanglingLayers.shape(n_wires=n_wires, n_layers=i_layers) + params = jax.random.normal(key, param_shape) + + t_adjoint.append(get_time(circuit_adjoint, params)) + t_backprop.append(get_time(circuit_backprop, params)) + t_ps.append(get_time(circuit_ps, params)) + + return t_adjoint, t_backprop, t_ps + + +if __name__ == "__main__": + + wires_list = [3, 6, 9, 12, 15] + n_layers = 6 + adjoint_wires, backprop_wires, ps_wires = wires_scaling(wires_list, n_layers) + + layers_list = [3, 9, 15, 21, 27] + n_wires = 12 + adjoint_layers, backprop_layers, ps_layers = layers_scaling(n_wires, layers_list) + + # Generating the graphic + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) + + ax1.plot(wires_list, adjoint_wires, '.-', label="adjoint") + ax1.plot(wires_list, ps_wires, '.-', label="parameter-shift") + ax1.plot(wires_list, backprop_wires, '.-', label="backprop") + + ax1.legend() + + ax1.set_xlabel("Number of wires") + ax1.set_xticks(wires_list) + ax1.set_ylabel("Log Time") + ax1.set_yscale("log") + ax1.set_title("Scaling with wires") + + ax2.plot(layers_list, adjoint_layers, '.-', label="adjoint") + ax2.plot(layers_list, ps_layers, '.-', label="parameter-shift") + ax2.plot(layers_list, backprop_layers, '.-', label="backprop") + + ax2.legend() + + ax2.set_xlabel("Number of layers") + ax2.set_xticks(layers_list) + ax2.set_ylabel("Time") + ax2.set_title("Scaling with Layers") + + plt.savefig("scaling.png") + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/adjoint_diff/scaling.png +# :width: 80% +# :align: center +# +# diff --git a/demonstrations_v2/adjoint_diff_benchmarking/metadata.json b/demonstrations_v2/adjoint_diff_benchmarking/metadata.json new file mode 100644 index 0000000000..f6e61366b7 --- /dev/null +++ b/demonstrations_v2/adjoint_diff_benchmarking/metadata.json @@ -0,0 +1,24 @@ +{ + "title": "Adjoint Differentiation \u2013 Supplementary Material", + "authors": [ + { + "username": "christina" + } + ], + "dateOfPublication": "2021-11-23T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adjoint_differentiation.png" + } + ], + "seoDescription": "Benchmarking file for adjoint diff demonstration.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/adjoint_diff_benchmarking/requirements.in b/demonstrations_v2/adjoint_diff_benchmarking/requirements.in new file mode 100644 index 0000000000..a7e20f9b86 --- /dev/null +++ b/demonstrations_v2/adjoint_diff_benchmarking/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +matplotlib +pennylane diff --git a/demonstrations_v2/ahs_aquila/demo.py b/demonstrations_v2/ahs_aquila/demo.py new file mode 100644 index 0000000000..6731faa973 --- /dev/null +++ b/demonstrations_v2/ahs_aquila/demo.py @@ -0,0 +1,818 @@ +r"""Pulse programming on Rydberg atom hardware +============================================== + +.. meta:: + :property="og:description": Perform measurements on neutral atom hardware through PennyLane + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_pulse_on_hardware.png + +.. related:: + tutorial_pasqal Quantum computation with neutral atoms + tutorial_pulse_programming101 Differentiable pulse programming with qubits in PennyLane + +*Author: Lillian M.A. Frederiksen — Posted: 16 May 2023.* + +Neutral atom hardware is a new innovation in quantum technology that has been gaining traction in +recent years thanks to new developments in optical tweezer technology. One such device, +`QuEra's Aquila `__, is capable of running circuits with up to 256 +physical qubits! The `Aquila device `__ is +now accessible and programmable via pulse programming in PennyLane and the +`PennyLane-Braket SDK plugin `__. +In this demo, we will learn how to define a Hamiltonian for a driven Rydberg atom system in PennyLane, +and use it to first simulate a pulse program on Rydberg atoms, and then upload it and measure +the effect of Rydberg blockade on a hardware device! + +| + +.. figure:: ../_static/demonstration_assets/ahs_aquila/aquila_demo_image.png + :align: center + :width: 70% + :alt: Illustration of robotic hand controlling Rubidium atoms with electromagnetic pulses + :target: javascript:void(0); + +| + + +Pulse programming basics in PennyLane +------------------------------------- + +Pulse programming in PennyLane is a paradigm based on low-level control of electromagnetic driving +pulses. Pulse programs are written directly on the hardware level, skipping the abstraction of +decomposing algorithms into fixed native gate sets. While these abstractions are often used in proposed +error correction schemes to achieve fault tolerance in a universal quantum computer, in noisy and +intermediate-sized quantum computers, they can add unnecessary overhead (and thereby introduce +more noise). + +In quantum computing architectures where qubits are realized through physical systems with discrete +energy levels, transitions from one state to another are driven by electromagnetic fields tuned to +be at or near the relevant energy gap. These electromagnetic fields can vary as a function of time. +The full system Hamiltonian is then a combination of the Hamiltonian describing the state of the +hardware when unperturbed, and a time-dependent drive. + +Pulse control gives some insight into the low-level implementation of more abstract quantum +computations. In most digital quantum architectures, the +native gates of the computer are, at the implementation level, electromagnetic control pulses +that have been finely tuned to perform a particular logical gate. + +This alternative approach requires a different type of control than what you might be used to in +PennyLane, where circuits are generally defined in terms of a series of gates. Specifically, pulse control +is implemented via the functionality provided in the Pennylane :mod:`~pennylane.pulse` module. For +more information on pulse programming in PennyLane, see the +`PennyLane docs `__, or check out the demo +about +`running a ctrl-VQE algorithm with pulse control `__ on the PennyLane `default.qubit` simulator. + + + +The QuEra Aquila device +----------------------- + +The Aquila QPU works with programmable arrays of up to 256 Rubidium-87 atoms (Rb-87), trapped in vacuum by tightly +focused laser beams. These atoms can be arranged in (almost) +`arbitrary user-specified geometries `_ to determine +inter-qubit interactions. On the Aquila device, it is possible to specify 1D and 2D atom arrangements. Atom +positions may be slightly shifted to accommodate hardware limitations, and must obey lattice constraints +for spacing. This will be explored in more detail below. + +Different energy levels of these atoms are used to encode qubits. + +A primary application of pulse control in Rydberg atom systems like Aquila is the implementation of analog +Hamiltonian simulation. This is a technique that aims to investigate the behaviour of some +system of interest using a programmable, controllable device that emulates the target +system. For example, Rydberg atom systems have been used to probe the behaviour +of quantum spin liquids [#Semeghini]_ and antiferromagnetic Ising +models [#Lienhard]_. + +Constructing a pulse program to run on the Aquila hardware is done in two steps, each of which allows us to modify +different parts of the system Hamiltonian: + +1. Define atom positions, which determines qubit connectivity +2. Specify the quantum evolution via the drive parameters + +Let's start with the atom positions and the resulting Hamiltonian term describing inter-qubit interactions. + + +Interaction term and atom arrangement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this treatment of the Hamiltonian, we will assume that we are operating such that we only allow +access to two states; the low and high energy states are referred to as the ground (:math:`| g \rangle`) +and Rydberg (:math:`| r \rangle`) states respectively. + +Qubit interaction in a Rydberg atom system is mediated by a mechanism called Rydberg blockade, which arises +due to van der Waals forces between the atoms. This is described by the interaction Hamiltonian: + +.. math:: \hat{H}_{int} = \sum_{j=1}^{N-1}\sum_{k=j+1}^{N} \frac{C_6}{R^6_{jk}}\hat{n}_j\hat{n}_k + +where :math:`n_j=| r_j \rangle \langle r_j |` is the number operator acting on atom :math:`j,` :math:`R_{jk} = \lvert x_j - x_k \lvert` is the +distance between atoms :math:`j` and :math:`k,` and :math:`C_6` is a fixed value determined by the nature of the ground +and Rydberg states (for Aquila, :math:`5.24 \times 10^{-24} \text{rad m}^6 / \text{s},` referring to the +:math:`| 70S_{1/2} \rangle` state of the Rb-87 atom). + +There are two key things to be aware of in the interaction term. First, the energy contribution of +the interaction between each pair of atoms is only non-zero when both atoms are in the Rydberg state, +so that :math:`\langle \psi | \hat{n}_k \hat{n}_j | \psi \rangle =1.` Second, the energy contribution for each +pair of atoms is inversely proportional to the distance between them. Thus, as we move two atoms closer +together, it becomes increasingly energetically expensive for both to be in the Rydberg state. + +In the Aquila system, we define a *register* (the layout of the atoms) to modify these interactions. + +Below is a conceptual diagram demonstrating this interaction for a pair of atoms. At a distance, a similar +energetic cost is paid to go from 0 to 1 excitation and from 1 to 2 excitations. However, as we move the +atoms into closer proximity, we see a rapidly increasing energy cost to drive to the doubly excited state. + +.. figure:: ../_static/demonstration_assets/ahs_aquila/rydberg_blockade_diagram.png + :align: center + :figwidth: 55% + :width: 95% + :alt: A diagram of the energy levels for the ground, single excitation, and double excitation states + :target: javascript:void(0); + + Energy levels for the ground (:math:`| gg \rangle`), single Rydberg excitation (:math:`| gr \rangle,` :math:`| rg \rangle`), + and double Rydberg excitation (:math:`| rr \rangle`) states + +The modification of the energy levels when atoms are in proximity gives rise to Rydberg blockade, +where atoms that have been driven by a pulse that would, in isolation, leave them in the excited state +instead remain in the ground state due to neighboring atoms being excited. The distance within which two +neighboring atoms are effectively prevented from both being excited is referred to as the *blockade radius* :math:`R_b.` + +This brings us to our discussion of the second part of the Hamiltonian: the drive term. + + +The driven Rydberg Hamiltonian +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The atoms in a Rydberg system can be driven by application of a laser pulse, which can be described by 3 parameters: +amplitude (also called Rabi frequency) :math:`\Omega`, detuning :math:`\Delta,` and phase :math:`\phi.` While in +theory, a drive pulse can be applied to individual atoms, the current control setup for the Aquila hardware only +allows the application of a global drive pulse. + +Let's look at how this plays out in the Hamiltonian describing a global drive targeting the ground +to Rydberg state transition. The driven Hamiltonian of the system is: + +.. math:: \hat{H}_{drive} = \sum_{k=1}^N \frac{\Omega(t)}{2} (e^{i \phi(t)}| g_k \rangle \langle r_k | + e^{-i \phi(t)} | r_k \rangle \langle g_k |) - \Delta(t) \hat{n}_k + +where :math:`| r \rangle` and :math:`| g \rangle` are the Rydberg and ground states. + +Now that we know a bit about the system we will be manipulating, let us look at how to connect to a +real device. + + + +Getting started with Amazon Braket +---------------------------------- + +For this demo, we will integrate PennyLane with Amazon Braket to perform measurements on Rydberg +atom based hardware provided by QuEra. + +In PennyLane, Amazon Braket is accessed through the PennyLane-Braket plugin. The installation +instructions for the plugin can be found `here `__. + +The Pennylane-Braket plugin also comes pre-installed in the Braket managed notebooks +accessible through the `AWS console `__. + +The remote hardware devices available on Amazon Braket can be found +`here `__, along with +information about each system, including which paradigm (gate-based, continuous variable or +analog Hamiltonian simulation) it operates under. Each device has a unique identifier known as an +ARN. In PennyLane, AHS-based Braket devices are accessed through a PennyLane device named +``braket.aws.ahs``, along with specification of the corresponding ARN. + +.. note:: + + To access remote services on Amazon Braket, you must first + `create an account on AWS `__ and also follow the + `setup instructions `__ for accessing Braket from Python. + +Connecting to Aquila +~~~~~~~~~~~~~~~~~~~~ + +Once you are set up with a Braket account, you can access the Aquila device. It is available online +in particular time windows, which can be found `here `__ , though you +can submit tasks to the queue at any time. Note that depending on queue lengths, there can be some +wait time to receive results even during the availability window of the device. + +A simulated version on the Aquila hardware, ``braket.local.ahs``, is also available, and is an +excellent resource for testing out programs before committing to a particular hardware task. It +is important to be aware that some tasks that succeed in simulation will not be able to be sent +to hardware due to physical constraints of the measurement and control setup. Also, +be aware of the hardware specifications and capabilities when planning your pulse program. These +capabilities are accessible at any time from the hardware device; we will demonstrate in more +detail how to access these specifications as we go through this demo. + +.. note:: + + Those cells of this demo that contain the real device ``aquila`` will only run when hardware is online. If you want to run it at + other times to experiment with the concepts, the hardware device can be switched out with the Braket + AHS simulator. When interpreting the section of the demo regarding discretization for hardware, bear in + mind that the simulator does not discretize the functions before upload, so it will not accurately + demonstrate the discretization behaviour. + + +Let us access both the remote hardware device, and a local Rydberg atom simulator from AWS, and then +we can start defining our pulse program. + +""" + +import pennylane as qml + +aquila = qml.device( + "braket.aws.ahs", + device_arn="arn:aws:braket:us-east-1::device/qpu/quera/Aquila", + wires=3, +) + +rydberg_simulator = qml.device("braket.local.ahs", wires=3) + +###################################################################### +# Creating a Rydberg Hamiltonian +# ------------------------------ +# +# To create a pulse program, we first create a :class:`~pennylane.pulse.ParametrizedHamiltonian` that describes +# a Rydberg system and drive we want to implement. Once created, this can be used with the ``default.qubit`` device to +# simulate the system's behaviour directly in PennyLane, as well as with the AWS simulator and hardware services. +# +# +# Atom layout and interaction term +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Recall that placing atoms in close proximity creates a blockade effect, where it is energetically +# favourable for only one atom in each pair to be in the Rydberg state. +# +# Here we define a lattice of 3 atoms, all close enough together that we would expect only one of them +# to be excited at a time. We can see the hardware specifications for the atom lattice via: +# + +# units from the hardware backend are specified in SI units, in this case metres +aquila.hardware_capabilities["lattice"].dict() + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# {'area': {'width': Decimal('0.000075'), 'height': Decimal('0.000076')}, +# 'geometry': {'spacingRadialMin': Decimal('0.000004'), +# 'spacingVerticalMin': Decimal('0.000004'), +# 'positionResolution': Decimal('1E-8'), +# 'numberSitesMax': 256}} +# +# We can see that the atom field has a width of :math:`75 \, \mu m` and a height of :math:`76 \, \mu m.` +# Additionally, we can see that the minimum radial spacing and minimal vertical spacing between two +# atoms are both :math:`4 \, \mu m,` and the resolution for atom placement is :math:`0.1 \, \mu m.` For more +# details accessing and interpreting these specifications, see Amazon Braket’s starter `Aquila example notebook +# `__. +# +# In PennyLane, we will specify these distances in micrometres. Let's set the coordinates to be three +# points on an equilateral triangle with a side length of :math:`5 \, \mu m,` which should be well within +# the blockade radius: +# + +import numpy as np +import matplotlib.pyplot as plt + +a = 5 + +coordinates = [(0, 0), (a, 0), (a / 2, np.sqrt(a**2 - (a / 2) ** 2))] + +print(f"coordinates: {coordinates}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# coordinates: [(0, 0), (5, 0), (2.5, 4.330127018922194)] +# + +plt.scatter([x for x, y in coordinates], [y for x, y in coordinates]) +plt.xlabel("μm") +plt.ylabel("μm") +plt.show() + +############################################################################## +# .. figure:: ../_static/demonstration_assets/ahs_aquila/rydberg_blockade_coordinates.png +# :align: center +# :width: 40% +# :alt: The layout of the 3 atoms defined by `coordinates` +# :target: javascript:void(0); +# +# +# If we want to create a Hamiltonian that we can use in PennyLane to accurately simulate a system, we +# need the correct physical constants; in this case, we need an accurate value of :math:`C_6` to +# calculate the interaction term (different atoms and different sets of energy levels will have different +# physical constants). We can access these via +# + +settings = aquila.settings +settings +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# {'interaction_coeff': 862619.7915580727} +# +# +# PennyLane provides a helper function that creates the relevant Hamiltonian, +# :func:`~pennylane.pulse.rydberg_interaction`. We pass this function the atom coordinates, along with the +# ``settings`` we retrieved above, to create the interaction term for the Hamiltonian: +# +# .. math:: \hat{H} = \sum_{j=1}^{N-1}\sum_{k=j+1}^{N} \frac{C_6}{R^6_{jk}}\hat{n}_j\hat{n}_k +# + +H_interaction = qml.pulse.rydberg_interaction(coordinates, **settings) + +###################################################################### +# Driving field +# ~~~~~~~~~~~~~ +# +# +# The global drive is in relation to the transition between the ground and rydberg states. It is +# defined by 3 components: the *amplitude* (Rabi frequency), the *phase*, and the *detuning*. Let us consider the hardware +# limitations on each of these. We can access the dictionary for hardware specifications for driving the +# Rydberg transition just as we did for the lattice specifications above: +# + +aquila.hardware_capabilities["rydberg"].dict() + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# {'c6Coefficient': Decimal('5.42E-24'), +# 'rydbergGlobal': {'rabiFrequencyRange': (Decimal('0.0'), +# Decimal('15800000.0')), +# 'rabiFrequencyResolution': Decimal('400.0'), +# 'rabiFrequencySlewRateMax': Decimal('400000000000000.0'), +# 'detuningRange': (Decimal('-125000000.0'), Decimal('125000000.0')), +# 'detuningResolution': Decimal('0.2'), +# 'detuningSlewRateMax': Decimal('6000000000000000.0'), +# 'phaseRange': (Decimal('-99.0'), Decimal('99.0')), +# 'phaseResolution': Decimal('5E-7'), +# 'timeResolution': Decimal('1E-9'), +# 'timeDeltaMin': Decimal('5E-8'), +# 'timeMin': Decimal('0.0'), +# 'timeMax': Decimal('0.000004')}, +# 'rydbergLocal': None} +# +# It is important to note that these quantities are in radians per second rather than Hz where relevant, and +# are all in SI units. This means that for amplitude and detuning, we will need to convert from angular +# frequency in rad/s to standard frequency in MHz (the expected input unit in PennyLane) to understand +# the limits on PennyLane inputs. For example, for the largest possible detuning value specified in +# PennyLane should be 19.89 MHz: + + +def angular_SI_to_MHz(angular_SI): + """Converts a value in rad/s or (rad/s)/s into MHz or MHz/s""" + return angular_SI / (2 * np.pi) * 1e-6 + + +angular_SI_to_MHz(125000000.00) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 19.89436788648692 +# +# +# For further details on how to access the specifications and their descriptions for the device, +# including accessing units and more detailed descriptions, see +# `AWS Aquila Notebook 01 `__. +# +# A summary of the general hardware restrictions for Rydberg drive in PennyLane's expected units +# can be seen in the table below. Note that these can be subject to change, and for thoroughness +# it is best practice to confirm these numbers by accessing the device's ``hardware_capabilities`` +# as shown above. +# +# All values in units of frequency (amplitude and detuning) are provided here in the input units +# expected by PennyLane (MHz). For simulations, these numbers will be converted to angular frequency +# (multiplied by :math:`2 \pi`) internally as needed. +# +# Note that when uploaded to hardware, the amplitude and detuning will be piecewise linear functions, +# while phase is piecewise constant. For amplitude and detuning, there is a maximum rate of change for +# the hardware output. +# +# +# .. rst-class:: docstable +# +# +---------------+------------------+------------------+---------------+---------------+-------------------------+ +# | .. centered:: | .. centered:: | .. centered:: | .. centered:: | .. centered:: | .. centered:: | +# | Parameter | Minimum value | Maximum value | Resolution | PWC or PWL | Maximum rate of change | +# +===============+==================+==================+===============+===============+=========================+ +# | Amplitude | 0 MHz | 2.51465 MHz | 6.4e-5 MHz | PWL | 39788735 MHz/s | +# +---------------+------------------+------------------+---------------+---------------+-------------------------+ +# | Phase | -99 rad | +99 rad | 5e-7 rad | PWC | N/A | +# +---------------+------------------+------------------+---------------+---------------+-------------------------+ +# | Detuning | -19.89436788 MHz | +19.89436788 MHz | 3.2e-8 MHz | PWL | 39788735 MHz/s | +# +---------------+------------------+------------------+---------------+---------------+-------------------------+ +# +# For the amplitude, there is an additional restriction that the first and last set-point in the pulse must +# be 0 MHz. The phase has a similar restriction for the first set-point, though the last set-point can take +# any value in the allowed range. There are no special restriction on the start and end points for detuning. +# +# A few additional limitations to be aware of are: +# +# - For hardware upload, the full pulse program must not exceed :math:`4 \, \mu s.` +# - The conversion from PennyLane to hardware upload will place set points every 50ns—consider this +# time resolution when defining pulses. +# +# Each of the 3 parameters can either be constant for the duration of the pulse, or they can be defined by a +# callable, where the callable should respect the above hardware output capabilities at all time points. +# For an initial drive term, let's start by defining a simple pulse with a time-dependent amplitude. +# Phase and detuning will both be set to 0. +# +# For the pulse shape, we'll create a gaussian envelope. Because we also want to run the +# simulation in PennyLane, we need to define the pulse function using ``jax.numpy``. +# The time is expected to be specified in microseconds for the callable. +# + +import jax.numpy as jnp + + +def gaussian_fn(p, t): + return p[0] * jnp.exp(-((t - p[1]) ** 2) / (2 * p[2] ** 2)) + + +# Visualize pulse, time in μs + +max_amplitude = 0.6 +displacement = 0.9 +sigma = 0.3 + +amplitude_params = [max_amplitude, displacement, sigma] + +time = np.linspace(0, 1.75, 176) +y = [gaussian_fn(amplitude_params, t) for t in time] +plt.xlabel("Time [$\mu s$]") +plt.ylabel("Amplitude [MHz]") + +plt.plot(time, y) +plt.show() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/ahs_aquila/gaussian_fn.png +# :align: center +# :width: 50% +# :alt: Plot of the gaussian_fn as a function of time for the above parameters +# :target: javascript:void(0); +# +# +# +# We can then define our drive using via :func:`~pennylane.pulse.rydberg_drive`: +# + +global_drive = qml.pulse.rydberg_drive(amplitude=gaussian_fn, phase=0, detuning=0, wires=[0, 1, 2]) + +###################################################################### +# With only amplitude as non-zero, the overall driven Hamiltonian in this case simplifies to: +# +# .. math:: \hat{H} = \sum_{k=1}^N \frac{\Omega(t)}{2} (| g_k \rangle \langle r_k | + | r_k \rangle \langle g_k |) + \sum_{j=1}^{N-1}\sum_{k=j+1}^{N} \frac{C_6}{R^6_{jk}}\hat{n}_j\hat{n}_k +# +# Now we will use our ``ParametrizedHamiltonian`` terms to run a pulse program! + +###################################################################### +# Simulating in PennyLane to find a pi-pulse +# ------------------------------------------ +# +# A pi-pulse is any pulse calibrated to perform a 180 degree (:math:`\pi` radian) rotation on the +# Bloch Sphere that takes us from the ground state of the un-driven system to the excited state when +# applied. This corresponds to a :math:`\sigma_X = | g \rangle \langle r | + | r \rangle \langle g |` gate in the ground-rydberg basis on each qubit. +# Here we will create one, and observe the effect of applying it with the interaction term +# “turned off”. Ignoring the inter-qubit interactions for now allows us to calibrate a pi-pulse without +# worrying about the effect of Rydberg blockade. +# +# With the interaction term off, each qubit will evolve according to the unitary evolution +# :math:`U = \text{exp}\left(-i \frac{1}{2} \int d\tau \Omega(\tau) \sigma_X \right)` and we construct +# :math:`\Omega(t)` such that :math:`\int d\tau \frac{1}{2} \Omega(\tau) = \frac{\pi}{2},` +# i.e. :math:`U = \exp(-i \frac{\pi}{2} \sigma_X) = -\sigma_X.` +# +# We will implement the pi-pulse using the drive term defined above, and tune the parameters of +# the gaussian envelope to implement the desired pulse. +# +# In the absence of the interaction term, each atom acts as a completely independent system, so +# we don't see any Rydberg blockade. Below, we’ve experimented with the parameters of +# the gaussian pulse envelope via trial-and-error to find settings that result in a pi-pulse: +# + +max_amplitude = 2.0 +displacement = 1.0 +sigma = 0.3 + +amplitude_params = [max_amplitude, displacement, sigma] + +params = [amplitude_params] +ts = [0.0, 1.75] + +default_qubit = qml.device("default.qubit", wires=3, shots=1000) + + +@qml.qnode(default_qubit, interface="jax") +def circuit(parameters): + qml.evolve(global_drive)(parameters, ts) + return qml.counts() + + +circuit(params) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# {'110': 1, '111': 999} +# +# +# Simulating Rydberg blockade in PennyLane +# ---------------------------------------- +# +# To simulate the effect of Rydberg blockade, we create a new circuit that includes both +# the drive and the interaction term. We can run this simulation either in PennyLane, or +# using the local simulator provided by AWS: +# + + +def circuit(params): + qml.evolve(H_interaction + global_drive)(params, ts) + return qml.counts() + + +circuit_qml = qml.QNode(circuit, default_qubit, interface="jax") +circuit_ahs = qml.QNode(circuit, rydberg_simulator) + +print(f"PennyLane simulation: {circuit_qml(params)}") +print(f"AWS local simulation: {circuit_ahs(params)}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# PennyLane simulation: {'000': 76, '001': 286, '010': 300, '100': 338} +# AWS local simulation: {'000': 63, '001': 354, '010': 273, '100': 310} +# +# +# When we apply the pi-pulse, but now to the full Hamiltonian including the interaction term, we +# observe that only one of the three qubits is in the excited state. This is indeed the expected +# effect of Rydberg blockade arising from the interaction term. +# +# +# The blockade radius :math:`R_b` is proportional to the :math:`C_6^{1/6}` value for the transition +# (determining the scale of the coefficient :math:`C_6/R_{jk}^6`). However, it is not determined by the interaction term +# alone-the blockade radius is also inversely proportional to how hard we drive the atoms. The blockade radius can be +# estimated as +# +# .. math:: R_b = (C_6/\sqrt{\Omega^2 + \Delta^2})^{1/6} +# +# Where :math:`\Omega` and :math:`\Delta` describe the amplitude and detuning of the drive, respectively. +# +# .. figure:: ../_static/demonstration_assets/ahs_aquila/rydberg_blockade.png +# :align: center +# :figwidth: 75 % +# :width: 95 % +# :alt: Illustration: three atoms trapped in optical tweezers in their Rydberg state 'clash' with one another +# :target: javascript:void(0); +# +# Rydberg blockade doesn't allow neighboring atoms in close proximity to all be in the excited state. +# +# Rydberg blockade on the QuEra hardware +# -------------------------------------- +# +# Let’s look at how we would move this simple pulse program from local simulations to hardware. +# +# Before uploading to hardware, it’s best to consider whether there are any constraints we need to be +# aware of. Only our amplitude parameter is non-zero, so let’s review the limitations we need to +# respect for defining an amplitude on hardware: +# +# - All values must be within 0 to 2.51465 MHz +# - The output will be linear between set-points, and the rate of change must never exceed 39788735 MHz/s +# - The amplitude sequence must start and end at 0 MHz +# + +times = np.linspace(0, 1.75, 36) +timestep = (times[1] - times[0]) * 1e-6 # Time step in seconds +amplitude = [gaussian_fn(amplitude_params, t) for t in times] + +start_val = amplitude[0] +stop_val = amplitude[-1] +max_val = np.max(amplitude) +max_rate = np.max([(amplitude[i + 1] - amplitude[i]) / timestep for i in range(len(times) - 1)]) + +print(f"start value: {start_val:.3} MHz") +print(f"stop value: {stop_val:.3} MHz") +print(f"maximum value: {max_val:.3} MHz") +print(f"maximum rate of change: {max_rate:.3} MHz/s") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# start value: 0.00773 MHz +# stop value: 0.0879 MHz +# maximum value: 2.0 MHz +# maximum rate of change: 4.01e+06 MHz/s +# +# Our maximum amplitude value and maximum rate of change are well below hardware limits, so the only +# constraint we need to enforce for our pulse program is ensuring the values at timestamps 0 and +# :math:`1.75 \, \mu s` are 0. For this, we can use a convenience function provided in the pulse module, +# :func:`~pennylane.pulse.rect`. We can wrap an existing function with it in order to apply a rectangular window +# within which the pulse has non-zero values. +# +# Note that the function is non-zero outside the window, and the window is defined as including the +# end-points. This means to ensure that 0 and 1.75 return 0, they need to be outside the interval +# defining the window; we’ll use ``windows=[0.01, 1.749]``. Our modified global drive is then: +# + +amp_fn = qml.pulse.rect(gaussian_fn, windows=[0.01, 1.749]) +global_drive = qml.pulse.rydberg_drive(amplitude=amp_fn, phase=0, detuning=0, wires=[0, 1, 2]) + +###################################################################### +# At this point we could skip directly to defining a ``qnode`` using the ``aquila`` device and running our +# pulse program. However, before we do, let’s take a look at how the parameters +# we’ve used to define our pulse program will be converted into hardware upload data. It can be useful +# to examine this to ensure your pulse program looks as you expect it to before paying for a hardware run. +# +# To do this, we create the operator we will be using in our circuit, and pass it to a method on the +# hardware device that creates an AHS program for upload: +# +op = qml.evolve(H_interaction + global_drive)(params, ts) +ahs_program = aquila.create_ahs_program(op) + +###################################################################### +# On a hardware device, the ``create_ahs_program`` method will modify both the register and the pulses +# before upload (this method is called internally when a circuit is run on the ``aquila`` device). +# Float variables are rounded to specific, valid set points, producing a discretized +# version of the input (for example, atom locations in the register lock into grid points). For this +# pulse, we’re interested in the amplitude and the register. +# +# For the register, recall that we defined our coordinates in micrometres as +# ``[(0, 0), (5, 0), (2.5, 4.330127018922194)]``, and that we expect the hardware upload program to be +# in SI units, i.e. micrometres have been converted to metres. We can access the +# ``ahs_program.register.coordinate_list`` to see the :math:`x` and :math:`y` coordinates that will be passed to +# the hardware, and plot them against the coordinates in the register we defined for the Hamiltonian: +# + +ahs_x_coordinates = ahs_program.register.coordinate_list(0) +ahs_y_coordinates = ahs_program.register.coordinate_list(1) + +op_register = op.H.settings.register +op_x_coordinates = [x * 1e-6 for x, _ in op_register] +op_y_coordinates = [y * 1e-6 for _, y in op_register] + +plt.scatter(ahs_x_coordinates, ahs_y_coordinates, label="AHS program") +plt.scatter(op_x_coordinates, op_y_coordinates, marker="x", label="Input register") +plt.xlabel("μm") +plt.ylabel("μm") +plt.legend() +plt.show() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/ahs_aquila/rydberg_blockade_coordinates_discretized.png +# :align: center +# :width: 50% +# :alt: The input coordinates for the atom arrangement, and the shifted uploaded coordinates after discretization +# :target: javascript:void(0); +# +# +# We can see that the final y-coordinate has been shifted ever so slightly when discretizing. We’re happy +# with this very minor change, but in more intricate atom layouts, small adjustments in coordinates could +# have a meaningful impact in executing the program. +# +# Let's also look at the amplitude data. We can access the set-points for hardware upload from the program as +# ``ahs_program.hamiltonian.amplitude.time_series``, which contains both the ``times()`` and +# ``values()`` for set-points. The ``amplitude`` can be switched for ``phase`` or ``detuning`` to +# access other relevant quantities. +# + +# hardware set-points after conversion and discretization +amp_setpoints = ahs_program.hamiltonian.amplitude.time_series + +# values for plotting the function defined in PennyLane for amplitude +input_times = np.linspace(*ts, 1000) +input_amplitudes = [ + qml.pulse.rect(gaussian_fn, windows=[0.01, 1.749])(amplitude_params, _t) for _t in input_times +] + +# plot PL input and hardware setpoints for comparison +fig, (ax1, ax2) = plt.subplots(1, 2) +ax1.plot(input_times, input_amplitudes) +ax1.set_xlabel("Time [$\mu s$]") +ax1.set_ylabel("MHz") +ax1.set_title("gaussian_fn") + +ax2.plot(amp_setpoints.times(), amp_setpoints.values()) +ax2.set_xlabel("Time [s]") +ax2.set_ylabel("rad/s") +ax2.set_title("upload data") + +plt.tight_layout() +plt.show() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/ahs_aquila/gaussian_fn_vs_upload.png +# :align: center +# :width: 50% +# :alt: A plot showing the amplitude function, and the piecewise-linear approximation of it uploaded to hardware +# :target: javascript:void(0); +# +# +# Since we are happy with this, let us send this task to hardware now. If there are any hardware incompatibilities +# with the upload data that we’ve missed, we will be informed immediately. +# Otherwise, the task will be sent to the remote hardware; it will be run when the hardware is online and we +# reach the front of the queue. +# +# To run this without connecting to the hardware, switch the aquila device out with the ``rydberg_simulator`` below. +# Note that running on hardware is a paid service and will incur a fee. + + +# @qml.qnode(rydberg_simulator) +@qml.qnode(aquila) +def circuit(params): + qml.evolve(H_interaction + global_drive)(params, ts) + return qml.counts() + + +circuit(params) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# {'000': 71, '001': 296, '010': 321, '100': 312} +# +# +# We observe the same pattern on the hardware that we can see in simulation—a single +# excitation amongst the three atoms within the blockade distance of one another. +# On hardware, it is possible to scale models beyond what is feasible to simulate; +# while simulation can't handle large numbers of qubits, the Aquila QPU can be initialized +# with up to 256 qubits! +# +# +# +# +# Conclusion +# ---------- +# +# Rydberg atom systems are an interesting and developing field within quantum computing. It is now +# possible to connect to the Aquila QPU via PennyLane and Amazon Braket, and perform measurements on hardware +# with up to 256 qubits. Programs for the Aquila hardware can be defined using PennyLane's +# :mod:`~pennylane.pulse` module, allowing users to define time-dependent control of pulse parameters. +# +# Interfacing with the Aquila hardware provides an opportunity to take a small model of a concept that +# has been tested in simulation, and scale it up to run on up to 256 qubits on hardware. Manipulating +# Rydberg atom systems through pulse-level control has applications in probing new areas of fundamental +# physics—like simulating quantum spin liquids at scales where it is not possible to classically +# simulate the quantum dynamics of the full experimental system! [#Semeghini]_ [#Asthana2022]_ +# +# Here, we have demonstrated a simple, amplitude-only pulse implementing the quintessential behaviour +# of Rydberg atom systems: Rydberg blockade. Introducing phase and detuning to create a more complex +# drive Hamiltonian, and arranging atoms to create specific configurations of inter-atom interaction, +# allows more intricate systems to be studied. +# +# +# References +# ---------- +# +# .. [#Semeghini] +# +# G. Semeghini, H. Levine, A. Keesling, S. Ebadi, T.T. Wang, D. Bluvstein, R. Verresen, H. Pichler, +# M. Kalinowski, R. Samajdar, A. Omran, S. Sachdev, A. Vishwanath, M. Greiner, V. Vuletic, M.D. Lukin +# "Probing topological spin liquids on a programmable quantum simulator" +# `arxiv.2104.04119 `__, 2021. +# +# .. [#Lienhard] +# +# V. Lienhard, S. de Léséleuc, D. Barredo, T. Lahaye, A. Browaeys, M. Schuler, L.-P. Henry, A.M. Läuchli +# "Observing the Space- and Time-Dependent Growth of Correlations in Dynamically Tuned Synthetic Ising +# Models with Antiferromagnetic Interactions" +# `arxiv.2104.04119 `__, 2018. +# +# .. [#BraketDevGuide] +# +# Amazon Web Services: Amazon Braket +# "Hello AHS: Run your first Analog Hamiltonian Simulation" +# `AWS Developer Guide `__ +# +# .. [#Asthana2022] +# +# Alexander Keesling, Eric Kessler, and Peter Komar +# "AWS Quantum Technologies Blog: Realizing quantum spin liquid phase on an analog Hamiltonian Rydberg simulator" +# `Amazon Quantum Computing Blog `__, 2021. + +############################################################################## diff --git a/demonstrations_v2/ahs_aquila/metadata.json b/demonstrations_v2/ahs_aquila/metadata.json new file mode 100644 index 0000000000..498d348c9b --- /dev/null +++ b/demonstrations_v2/ahs_aquila/metadata.json @@ -0,0 +1,82 @@ +{ + "title": "Pulse programming on Rydberg atom hardware", + "authors": [ + { + "username": "lillian_frederiksen" + } + ], + "dateOfPublication": "2023-05-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Devices and Performance", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/ahs_aquila/thumbnail_tutorial_pulse_on_hardware.png" + } + ], + "seoDescription": "Perform measurements on neutral atom hardware through PennyLane", + "doi": "", + "references": [ + { + "id": "Semeghini", + "type": "article", + "title": "Probing topological spin liquids on a programmable quantum simulator", + "authors": "G. Semeghini, H. Levine, A. Keesling, S. Ebadi, T.T. Wang, D. Bluvstein, R. Verresen, H. Pichler, M. Kalinowski, R. Samajdar, A. Omran, S. Sachdev, A. Vishwanath, M. Greiner, V. Vuletic, M.D. Lukin", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2104.04119" + }, + { + "id": "Lienhard", + "type": "article", + "title": "Observing the Space- and Time-Dependent Growth of Correlations in Dynamically Tuned Synthetic Ising Models with Antiferromagnetic Interactions", + "authors": "V. Lienhard, S. de L\u00e9s\u00e9leuc, D. Barredo, T. Lahaye, A. Browaeys, M. Schuler, L.-P. Henry, A.M. L\u00e4uchli", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1711.01185" + }, + { + "id": "BraketDevGuide", + "type": "webpage", + "title": "Hello AHS: Run your first Analog Hamiltonian Simulation", + "authors": "Amazon Web Services: Amazon Braket", + "journal": "", + "url": "https://docs.aws.amazon.com/braket/latest/developerguide/braket-get-started-hello-ahs.html" + }, + { + "id": "Asthana2022", + "type": "article", + "title": "AWS Quantum Technologies Blog: Realizing quantum spin liquid phase on an analog Hamiltonian Rydberg simulator", + "authors": "Alexander Keesling, Eric Kessler, and Peter Komar", + "year": "2021", + "journal": "", + "url": "https://aws.amazon.com/blogs/quantum-computing/realizing-quantum-spin-liquid-phase-on-an-analog-hamiltonian-rydberg-simulator/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/analog_hamiltonian_simulation/06_Analog_Hamiltonian_simulation_with_PennyLane.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/ahs_aquila/requirements.in b/demonstrations_v2/ahs_aquila/requirements.in new file mode 100644 index 0000000000..bb242ed39b --- /dev/null +++ b/demonstrations_v2/ahs_aquila/requirements.in @@ -0,0 +1,5 @@ +jax +jaxlib +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/braket-parallel-gradients/demo.py b/demonstrations_v2/braket-parallel-gradients/demo.py new file mode 100644 index 0000000000..d436675e3e --- /dev/null +++ b/demonstrations_v2/braket-parallel-gradients/demo.py @@ -0,0 +1,608 @@ +""" +Computing gradients in parallel with Amazon Braket +================================================== + +.. meta:: + :property="og:description": Parallelize gradient calculations with Amazon Braket + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pl-braket.png + +.. related:: + + tutorial_qaoa_intro Intro to QAOA + vqe_parallel VQE with parallel QPUs with Rigetti + + +*Authors: Tom Bromley and Maria Schuld — Posted: 08 December 2020. Last updated: 30 September 2021.* + +PennyLane integrates with `Amazon Braket `__ to enable quantum +machine learning and optimization on high-performance simulators and quantum processing +units (QPUs) through a range of `providers `__. + +In PennyLane, Amazon Braket is accessed through the +`PennyLane-Braket `__ plugin. The +plugin can be installed using + +.. code-block:: bash + + pip install amazon-braket-pennylane-plugin + +A central feature of Amazon Braket is that its remote simulator can execute multiple circuits +in parallel. This capability can be harnessed in PennyLane during circuit training, +which requires lots of variations of a circuit to be executed. Hence, the PennyLane-Braket plugin +provides a method for scalable optimization of large circuits with many parameters. This tutorial +will explain the importance of this feature, allow you to benchmark it yourself, and explore its +use for solving a scaled-up graph problem with QAOA. + +.. figure:: ../_static/remote-multi-job-simulator.png + :align: center + :scale: 75% + :alt: PennyLane can leverage Braket for parallelized gradient calculations + :target: javascript:void(0); + +Why is training circuits so expensive? +-------------------------------------- + +Quantum-classical hybrid optimization of quantum circuits is the workhorse algorithm of near-term +quantum computing. It is not only fundamental for training variational quantum circuits, but also +more broadly for applications like quantum chemistry and quantum machine learning. Today's most +powerful optimization algorithms rely on the efficient computation of gradients—which tell us how +to adapt parameters a little bit at a time to improve the algorithm. + +Calculating the gradient involves multiple device executions: for each +trainable parameter we must execute our circuit on the device typically +:doc:`more than once `. Reasonable applications involve many +trainable parameters (just think of a classical neural net with millions of tunable weights). The +result is a huge number of device executions for each optimization step. + +.. figure:: ../_static/grad-circuits.png + :align: center + :scale: 75% + :alt: Calculating the gradient requires multiple circuit executions + :target: javascript:void(0); + +In the standard ``default.qubit`` device, gradients are calculated in PennyLane through +sequential device executions—in other words, all these circuits have to wait in the same queue +until they can be evaluated. This approach is simpler, but quickly becomes slow as we scale the +number of parameters. Moreover, as the number of qubits, or "width", of the circuit is scaled, +each device execution will slow down and eventually become a noticeable bottleneck. In +short—**the future of training quantum circuits relies on high-performance remote simulators and +hardware devices that are highly parallelized**. + +Fortunately, the PennyLane-Braket plugin provides a solution for scalable quantum circuit training +by giving access to the Amazon Braket simulator known as +`SV1 `__. +SV1 is a high-performance state vector simulator that is +designed with parallel execution in mind. Together with PennyLane, we can use SV1 to run in +parallel all the circuits needed to compute a gradient! + +Accessing devices on Amazon Braket +---------------------------------- + +The remote simulator and quantum hardware devices available on Amazon Braket can be found +`here `__. Each +device has a unique identifier known as an +`ARN `__. In PennyLane, +all remote Braket devices are accessed through a single PennyLane device named ``braket.aws.qubit``, +along with specification of the corresponding ARN. + +.. note:: + + To access remote services on Amazon Braket, you must first create an account on AWS and also + follow the `setup instructions + `__ for accessing Braket from + Python. + +Let's load the SV1 simulator in PennyLane with 25 qubits by specifying the device ARN. +""" + +device_arn = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" + +############################################################################## +# SV1 can now be loaded with the standard PennyLane :func:`~.pennylane.device`: + +import pennylane as qml +from pennylane import numpy as np + +n_wires = 25 + +dev_remote = qml.device( + "braket.aws.qubit", + device_arn=device_arn, + wires=n_wires, + parallel=True, +) + +############################################################################## +# Note the ``parallel=True`` argument. This setting allows us to unlock the power of parallel +# execution on SV1 for gradient calculations. We'll also load ``default.qubit`` for comparison. + +dev_local = qml.device("default.qubit", wires=n_wires) + +############################################################################## +# Note that a local Braket device ``braket.local.qubit`` is also available. See the +# `documentation `__ for more details. +# +# Benchmarking circuit evaluation +# ------------------------------- +# +# We will now compare the execution time for the remote Braket SV1 device and ``default.qubit``. Our +# first step is to create a simple circuit: + + +def circuit(params): + for i in range(n_wires): + qml.RX(params[i], wires=i) + for i in range(n_wires): + qml.CNOT(wires=[i, (i + 1) % n_wires]) + + # Measure all qubits to make sure all's good with Braket + observables = [qml.PauliZ(n_wires - 1)] + [qml.Identity(i) for i in range(n_wires - 1)] + return qml.expval(qml.prod(*observables)) + + +############################################################################## +# +# .. figure:: ../_static/circuit.png +# :align: center +# :scale: 75% +# :alt: A simple circuit used for benchmarking +# :target: javascript:void(0); +# +# In this circuit, each of the 25 qubits has a controllable rotation. A final block of two-qubit +# CNOT gates is added to entangle the qubits. Overall, this circuit has 25 trainable parameters. +# Although not particularly relevant for practical problems, we can use this circuit as a testbed +# for our comparison. +# +# The next step is to convert the above circuit into a PennyLane :func:`~.pennylane.QNode`. + +qnode_remote = qml.QNode(circuit, dev_remote) +qnode_local = qml.QNode(circuit, dev_local) + +############################################################################## +# .. note:: +# The above uses :func:`~.pennylane.QNode` to convert the circuit. In other tutorials, +# you may have seen the :func:`~.pennylane.qnode` decorator being used. These approaches are +# interchangeable, but we use :func:`~.pennylane.QNode` here because it allows us to pair the +# same circuit to different devices. +# +# .. warning:: +# Running the contents of this tutorial will result in simulation fees charged to your +# AWS account. We recommend monitoring your usage on the AWS dashboard. +# +# Let's now compare the execution time between the two devices: + +import time + +params = np.random.random(n_wires) + +t_0_remote = time.time() +qnode_remote(params) +t_1_remote = time.time() + +t_0_local = time.time() +qnode_local(params) +t_1_local = time.time() + +print("Execution time on remote device (seconds):", t_1_remote - t_0_remote) +print("Execution time on local device (seconds):", t_1_local - t_0_local) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Execution time on remote device (seconds): 7.571744918823242 +# Execution time on local device (seconds): 27.32159185409546 +# +# Nice! These timings highlight the advantage of using the Amazon Braket SV1 device for simulations +# with large qubit numbers. In general, simulation times scale exponentially with the number of +# qubits, but SV1 is highly optimized and running on AWS remote servers. This allows SV1 to +# outperform ``default.qubit`` in this 25-qubit example. The time you see in practice for the +# remote device will also depend on factors such as your distance to AWS servers. +# +# .. note:: +# Given these timings, why would anyone want to use ``default.qubit``? You should consider +# using local devices when your circuit has few qubits. In this regime, the latency +# times of communicating the circuit to a remote server dominate over simulation times, +# allowing local simulators to be faster. +# +# Benchmarking gradient calculations +# ---------------------------------- +# +# Now let us compare the gradient-calculation times between the two devices. Remember that when +# loading the remote device, we set ``parallel=True``. This allows the multiple device executions +# required during gradient calculations to be performed in parallel, so we expect the +# remote device to be much faster. +# +# First, consider the remote device: + +d_qnode_remote = qml.grad(qnode_remote) + +t_0_remote_grad = time.time() +d_qnode_remote(params) +t_1_remote_grad = time.time() + +print( + "Gradient calculation time on remote device (seconds):", + t_1_remote_grad - t_0_remote_grad, +) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Gradient calculation time on remote device (seconds): 6.4775872230529785 +# +# Now, the local device: +# +# .. warning:: +# Evaluating the gradient with ``default.qubit`` will take a long time, consider +# commenting-out the following lines unless you are happy to wait. + +d_qnode_local = qml.grad(qnode_local) + +t_0_local_grad = time.time() +d_qnode_local(params) +t_1_local_grad = time.time() + +print( + "Gradient calculation time on local device (seconds):", + t_1_local_grad - t_0_local_grad, +) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Gradient calculation time on local device (seconds): 181.5902488231659 +# +# Wow, the local device needs around 3 minutes! Compare this to less around 6 seconds spent +# calculating the gradient on SV1. This provides a powerful lesson in parallelization. +# +# What if we had run on SV1 with ``parallel=False``? It would have taken around 3 minutes—still +# faster than a local device, but much slower than running SV1 in parallel. +# +# Scaling up QAOA for larger graphs +# --------------------------------- +# +# The quantum approximate optimization algorithm (QAOA) is a candidate algorithm for near-term +# quantum hardware that can find approximate solutions to combinatorial optimization +# problems such as graph-based problems. We have seen in the main +# :doc:`QAOA tutorial` how QAOA successfully solves the minimum vertex +# cover problem on a four-node graph. +# +# Here, let's be ambitious and try to solve the maximum cut problem on a twenty-node graph! In +# maximum cut, the objective is to partition the graph's nodes into two groups so that the number +# of edges crossed or 'cut' by the partition is maximized (see the diagram below). This problem is +# NP-hard, so we expect it to be tough as we increase the number of graph nodes. +# +# .. figure:: ../_static/max-cut.png +# :align: center +# :scale: 100% +# :alt: The maximum cut problem +# :target: javascript:void(0); +# +# Let's first set the graph: + +import networkx as nx + +nodes = n_wires = 20 +edges = 60 +seed = 1967 + +g = nx.gnm_random_graph(nodes, edges, seed=seed) +positions = nx.spring_layout(g, seed=seed) + +nx.draw(g, with_labels=True, pos=positions) + +############################################################################## +# .. figure:: ../_static/20_node_graph.png +# :align: center +# :scale: 100% +# :target: javascript:void(0); +# +# We will use the remote SV1 device to help us optimize our QAOA circuit as quickly as possible. +# First, the device is loaded again for 20 qubits + +dev = qml.device( + "braket.aws.qubit", + device_arn=device_arn, + wires=n_wires, + parallel=True, + max_parallel=20, + poll_timeout_seconds=30, +) + +############################################################################## +# Note the specification of ``max_parallel=20``. This means that up to ``20`` circuits will be +# executed in parallel on SV1 (the default value is ``10``). +# +# .. warning:: +# Increasing the maximum number of parallel executions can result in a greater rate of +# spending on simulation fees on Amazon Braket. The value must also be set bearing in mind your +# service +# `quota `__. +# +# The QAOA problem can then be set up following the standard pattern, as discussed in detail in +# the :doc:`QAOA tutorial`. + +cost_h, mixer_h = qml.qaoa.maxcut(g) +n_layers = 2 + + +def qaoa_layer(gamma, alpha): + qml.qaoa.cost_layer(gamma, cost_h) + qml.qaoa.mixer_layer(alpha, mixer_h) + + +def circuit(params, **kwargs): + for i in range(n_wires): # Prepare an equal superposition over all qubits + qml.Hadamard(wires=i) + + qml.layer(qaoa_layer, n_layers, params[0], params[1]) + return qml.expval(cost_h) + + +cost_function = qml.QNode(circuit, dev) +optimizer = qml.AdagradOptimizer(stepsize=0.01) + +############################################################################## +# We're now set up to train the circuit! Note, if you are training this circuit yourself, you may +# want to increase the number of iterations in the optimization loop and also investigate changing +# the number of QAOA layers. +# +# .. warning:: +# The following lines are computationally intensive. Remember that running it will result in +# simulation fees charged to your AWS account. We recommend monitoring your usage on the AWS +# dashboard. + +import time + +np.random.seed(1967) +params = 0.01 * np.random.uniform(size=[2, n_layers], requires_grad=True) +iterations = 10 + +for i in range(iterations): + t0 = time.time() + + params, cost_before = optimizer.step_and_cost(cost_function, params) + + t1 = time.time() + + if i == 0: + print("Initial cost:", cost_before) + else: + print(f"Cost at step {i}:", cost_before) + + print(f"Completed iteration {i + 1}") + print(f"Time to complete iteration: {t1 - t0} seconds") + +print(f"Cost at step {iterations}:", cost_function(params)) + +np.save("params.npy", params) +print("Parameters saved to params.npy") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Initial cost: -29.98570234095953 +# Completed iteration 1 +# Time to complete iteration: 11.028863906860352 seconds +# Cost at step 1: -29.995107300982195 +# Completed iteration 2 +# Time to complete iteration: 10.132628917694092 seconds +# Cost at step 2: -29.99981056829394 +# Completed iteration 3 +# Time to complete iteration: 9.985284090042114 seconds +# Cost at step 3: -30.00233965117029 +# Completed iteration 4 +# Time to complete iteration: 10.059210062026978 seconds +# Cost at step 4: -30.01206799710033 +# Completed iteration 5 +# Time to complete iteration: 9.966963052749634 seconds +# Cost at step 5: -30.048670540725286 +# Completed iteration 6 +# Time to complete iteration: 11.216133832931519 seconds +# Cost at step 6: -30.13240170461342 +# Completed iteration 7 +# Time to complete iteration: 10.09446096420288 seconds +# Cost at step 7: -30.25935979157505 +# Completed iteration 8 +# Time to complete iteration: 10.020287990570068 seconds +# Cost at step 8: -30.41499293575487 +# Completed iteration 9 +# Time to complete iteration: 10.345153093338013 seconds +# Cost at step 9: -30.587353834845057 +# Completed iteration 10 +# Time to complete iteration: 10.07306981086731 seconds +# Cost at step 10: -30.76855713404487 +# Parameters saved to params.npy +# +# This example shows us that a 20-qubit QAOA problem can be trained within around 1-2 minutes per +# iteration by using parallel executions on the Amazon Braket SV1 device to speed up gradient +# calculations. If this problem were run on ``default.qubit`` without parallelization, we would +# expect for training to take much longer. +# +# The results of this optimization can be investigated by saving the parameters +# :download:`here ` to your working directory. See if you can +# analyze the performance of this optimized circuit following a similar strategy to the +# :doc:`QAOA tutorial`. Did we find a large graph cut? +# +# Large-scale experiments with Hybrid Jobs +# ---------------------------------- +# We have seen how we can use PennyLane on Braket to solve graph optimization problems with QAOA. +# However, we have only used Amazon Braket for the quantum simulations, leaving the classical +# Python code to run on our laptop. +# This is great for getting started with small experiments, but for large-scale algorithms running +# them on a laptop is impractical due to potential crashes, restarts or even hardware limitations +# such as limited memory. With Amazon Braket Hybrid Jobs, you can send your entire algorithm, both +# classical and quantum parts, to run on AWS while you get a coffee! +# +# With a single line of code, we'll see how to scale from PennyLane simulators on your laptop to +# run full-scale experiments on AWS that leverage both powerful classical compute and quantum +# devices. For more details on hybrid jobs see +# :doc:`getting started with hybrid jobs `. +# +# .. warning:: +# The following demo is only compatible with Python version 3.10. +# +# In the following code, we run the same QAOA optimization as before, but this time on an +# `Amazon EC2 ml.c5.xlarge instance `__ +# instead of our laptop. We specify this with the `instance_config` for our hybrid job. +# A complete set of options is available in the `Braket documentation +# `__. +# + +from braket.jobs import InstanceConfig, hybrid_job +from braket.jobs.metrics import log_metric +from braket.tracking import Tracker + +# choose a large instance type for our experiment +large_instance = InstanceConfig(instanceType="ml.c5.xlarge") + + +@hybrid_job(device="local:pennylane/lightning.qubit", instance_config=large_instance) +def qaoa_training(n_iterations, n_layers=2): + braket_tasks_cost = Tracker().start() # track Braket quantum tasks costs + + # declare PennyLane device + dev = qml.device("lightning.qubit", wires=n_wires) + + @qml.qnode(dev) + def cost_function(params, **kwargs): + for i in range(n_wires): # Prepare an equal superposition over all qubits + qml.Hadamard(wires=i) + qml.layer(qaoa_layer, n_layers, params[0], params[1]) + return qml.expval(cost_h) + + params = 0.01 * np.random.uniform(size=[2, n_layers]) + optimizer = qml.AdagradOptimizer(stepsize=0.01) + + # run the classical-quantum iterations + for i in range(n_iterations): + params, cost_before = optimizer.step_and_cost(cost_function, params) + + log_metric(metric_name="cost", value=cost_before, iteration_number=i) + + return { + "parameters": params, + "final_cost": cost_function(params), + "braket_tasks_cost": braket_tasks_cost.qpu_tasks_cost() + + braket_tasks_cost.simulator_tasks_cost(), + } + + +############################################################################## +# Now we create a hybrid job by calling the function as usual. This returns an ``AwsQuantumJob`` +# object that contains the device ARN, region, and job name. The hybrid job will start running +# immediately since we are not using a QPU in this example. +# +# .. warning:: +# Running the the following cell will result charges to your AWS account based on Amazon EC2 +# pricing. +# + +job = qaoa_training(n_iterations=20, n_layers=2) +print(job) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# AwsQuantumJob('arn':'arn:aws:braket:::job/qaoa-training-1695044583') + +###################################################################### +# The hybrid job automatically captures the function arguments as hyperparameters. +# In this case, we set ``n_iterations = 20`` and ``n_layers = 2`` as the hyperparameters. +# +# We can check the status with: +# + +job.state() + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# 'QUEUED' + +############################################################################## +# The hybrid job will be scheduled to run and will appear in the "QUEUED" state. +# If the target is a QPU, the job will be queued with other jobs. +# If the target device is not a QPU, the hybrid job should start immediately. +# Note that since the algorithm code is run in a containerized environment, it takes approximately 1 +# minute to start running your algorithm. +# +# After the hybrid job is completed, we can get the results with ``job.result()``. For this example, +# it should take approximately 6 minutes. +# + +job.result() + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# {'parameters': [[-0.2615367426048209, -0.46218141633156967], +# [0.3853200394389563, 0.2402391372931216]], +# 'final_cost': -37.76176577995996, +# 'braket_tasks_cost': 0} + + +############################################################################## +# The results included the three values from the return statement of our function. +# Additionally, we can retrieve the metrics recorded during the training with: +# + +metrics = job.metrics() + +############################################################################## +# Now that we have the metrics, we can plot the convergence of the loss function. +# We use the pandas library to load the metrics into a DataFrame, and plot the loss as a function of +# iteration number. + +import pandas as pd +import matplotlib.pyplot as plt + +plt.style.use("pennylane.drawer.plot") + +df = pd.DataFrame(metrics) + +df.sort_values(by=["iteration_number"]).plot(x="iteration_number", y="cost") + + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/braket/qaoa_training.png +# :align: center +# :scale: 75% +# :alt: Convergence of cost function for QAOA training. +# :target: javascript:void(0); + +############################################################################## +# Great! The loss function gets lower with each iteration, reaching a value of approximately -37.8. +# +# Conclusion +# ---------------------------------- +# In this tutorial, we found the solution to a MaxCut problem using QAOA. We used Amazon Braket +# on-demand simulators to efficiently parallelize the parameter-shift rule for gradients. We also +# demonstrated how to scale-up experiments with your own simulator, such as PennyLane Lightning, by +# running on Amazon Braket Hybrid Jobs. In both approaches, we perform the intensive calculations on +# the cloud instead of our laptop. +# +# For more examples of how to use hybrid jobs, see the +# `Amazon Braket examples GitHub `__. +# + +############################################################################## diff --git a/demonstrations_v2/braket-parallel-gradients/metadata.json b/demonstrations_v2/braket-parallel-gradients/metadata.json new file mode 100644 index 0000000000..a7a102bba3 --- /dev/null +++ b/demonstrations_v2/braket-parallel-gradients/metadata.json @@ -0,0 +1,50 @@ +{ + "title": "Computing gradients in parallel with Amazon Braket", + "authors": [ + { + "username": "trbromley" + }, + { + "username": "mariaschuld" + }, + { + "username": "mbeach" + } + ], + "dateOfPublication": "2020-12-08T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_computing_gradients_amazon_braket.png" + } + ], + "seoDescription": "Parallelize gradient calculations with Amazon Braket", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "vqe_parallel", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/1_Parallelized_optimization_of_quantum_circuits/1_Parallelized_optimization_of_quantum_circuits.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/braket-parallel-gradients/requirements.in b/demonstrations_v2/braket-parallel-gradients/requirements.in new file mode 100644 index 0000000000..c516a74f3e --- /dev/null +++ b/demonstrations_v2/braket-parallel-gradients/requirements.in @@ -0,0 +1,5 @@ +amazon-braket-sdk +matplotlib +networkx +pandas +pennylane diff --git a/demonstrations_v2/circuits_as_fourier_series/demo.py b/demonstrations_v2/circuits_as_fourier_series/demo.py new file mode 100644 index 0000000000..4f6f60f3aa --- /dev/null +++ b/demonstrations_v2/circuits_as_fourier_series/demo.py @@ -0,0 +1,800 @@ +r""" +Circuits as Fourier series +========================== + +.. raw:: html + + + +.. meta:: + :property="og:description": Learn interactively how we can view circuits as Fourier series. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_Fourier.png + +In this demo, we're going to give an interactive, code-free introduction to the idea +of quantum circuits as Fourier series. We'll also discuss one of the main applications, called the +parameter-shift rule. Concepts will be embodied in visualizations you can play with! +We'll assume some familiarity with the basics of quantum circuits. +The last part of the demonstration is more advanced, and intended to give researchers a +different way to think of the material. But beginners are also welcome! + +Single-qubit gates +-------------------------- + +Consider a single-qubit Hermitian operator :math:`G.` It acts on a two-dimensional +space, and therefore has two eigenvalues: :math:`\kappa \pm \gamma,` +where :math:`\kappa` is the average and :math:`2\gamma` the difference between them. If +we exponentiate :math:`G`, with a parameter :math:`\theta,` we obtain a unitary gate: + +.. math:: + + U(\theta) = e^{i\theta G}. + +Up to an overall phase of :math:`e^{i\theta\kappa},` which we can ignore because it is +not measurable, the unitary :math:`U` has eigenvalues :math:`e^ +{\pm i \theta\gamma}`. In the eigenbasis (i.e., the basis of eigenvectors +of :math:`G,` or equivalently :math:`U`), it is diagonal. We will draw this +diagonal matrix as a magenta box: + +.. raw:: html + + +
+
+
+
+
+ +We will work on the eigenbasis of :math:`U` from now on. This means that a column +vector :math:`[1, 0]^T` is the eigenvector associated with :math:`-\gamma,` and +:math:`[0, 1]^T` is associated with :math:`+\gamma:` + +.. tip:: + + Click the image to toggle between eigenvectors. + +.. raw:: html + +
+
+
+
+
+ +We've written the matrix as a box to suggest a different way to think of it: +a *gate* in a quantum circuit. Instead of column vectors, we can use +bra-ket notation, with basis states :math:`\vert0\rangle = [1, 0]^T` and +:math:`\vert 1\rangle = [0, 1]^T.` We will also add some horizontal lines through the +gate to suggest that the states are "piped" through and pick up the +corresponding phase. + +.. tip:: + + Click to toggle between basis states :math:`\vert 0\rangle` + and :math:`\vert 1\rangle.` While the mouse is in the magenta + box, its vertical position controls :math:`\gamma,` with the top of + the box corresponding to high frequencies, and the bottom of the box + to low frequencies. + +.. raw:: html + +
+
+
+
+
+ +Frequency components +----------------------------- + +You may wonder why we have introduced the parameter :math:`\theta.` +The idea is that, if we have :math:`U(\theta)` in our circuit, we can +treat :math:`\theta` as a parameter we can tune to improve the results of +the circuit for, say, approximating a state of interest. +Viewed as functions of this tunable parameter :math:`\theta,` the purples +phases are *exponentials* of frequency :math:`\omega = \pm \gamma.` +Below, we plot the real and imaginary parts of these frequencies. + +.. tip:: + + Click to toggle between basis states :math:`\vert 0\rangle` + and :math:`\vert 1\rangle.` While the mouse is in the magenta + box, its vertical position controls :math:`\gamma.` + +.. raw:: html + +
+
+
+
+
+ +Usually, we start a wire in the :math:`\vert 0\rangle` basis state. We +can "split" this into a combination of :math:`\vert 0\rangle` +and :math:`\vert1\rangle` by applying a gate :math:`W.` We picture :math:`W` +as a blue gate below. The state +:math:`\vert\psi(\theta)\rangle =U(\theta)W\vert 0\rangle` will then be a superposition of +:math:`e^{-i\theta\gamma}\vert0\rangle` and +:math:`e^{+i\theta\gamma}\vert1\rangle.` Again, viewed as a function of +:math:`\theta,` it has both frequency components, storing them in the +coefficient of the corresponding eigenstate. + +.. tip:: + + Vertical mouse position in the blue box controls the relative weight + of :math:`\vert0\rangle` and :math:`\vert 1\rangle.` Hovering towards the + top places the most weight on :math:`\vert0\rangle,` and towards the bottom + on :math:`\vert1\rangle.` + Clicking the magenta box toggles between them, with vertical mouse position controlling :math:`\gamma.` + +.. raw:: html + +
+
+
+
+
+ +Once we've prepared a state :math:`\vert\psi(\theta)\rangle,` we can +measure it with some Hermitian operator :math:`M.` In the context of variational circuits, the result of +a measurement can be used to optimize the parameter :math:`\theta.` +To this end, let's define the expectation value as a function of +:math:`\theta,` + +.. math:: + + f(\theta) = \langle \psi(\theta)\vert M \vert\psi(\theta)\rangle. + +We represent the measurement :math:`M` as a yellow box, sandwiched between a +circuit on the left preparing the ket :math:`\vert\psi(\theta)\rangle` and +an adjoint circuit preparing the bra :math:`\langle \psi(\theta)\vert.` + +.. note:: + + Note that taking the adjoint swaps :math:`\pm\gamma,` and the order of elements + in the circuit is inverted compared to the expression for + :math:`f(\theta).` + +We can expand the bra and ket in terms of frequency components +:math:`e^{\pm i\theta\gamma},` so :math:`f(\theta)` will be a sum of products of +these terms. We show this below. + +.. tip:: + + Click on magenta boxes to toggle between frequency + components contributing to :math:`f(\theta).` Vertical position in the + yellow box controls the constant terms + :math:`\langle 0 \vert M \vert0\rangle` and + :math:`\langle 1 \vert M \vert 1\rangle.` + + +.. raw:: html + +
+
+
+
+
+ +To recap what we've learned so far: the ket :math:`\vert\psi +(\theta)\rangle` is a linear combination of the terms :math:`e^ +{-i\theta\gamma}\vert0\rangle` and :math:`e^ +{+i\theta\gamma}\vert1\rangle`. If we measure this state, the expectation +value :math:`f(\theta) = \langle\psi(\theta)\vert M\vert\psi +(\theta)\rangle` is a linear combination of products of the frequency +terms :math:`e^{\pm i\theta\gamma}.` More formally, we can write + +.. math:: + + f(\theta) = c_{(-2)} e^{-i2\gamma\theta} + c_{(0)} + c_{(+2)} + e^{+i2\gamma\theta} + +for some coefficients :math:`c_{(-2)}, c_{(0)}, c_{(+2)}.` General expressions of +this form—sums of exponential terms with evenly spaced frequencies—are +called *Fourier series*. This turns out to be a useful way to look at +parameterized circuits! + +Larger circuits +------------------------- + +We can embed this structure, with a single occurrence of :math:`U +(\theta)`, into a larger circuit. Instead of a linear combination +of :math:`\vert0\rangle` and :math:`\vert 1\rangle,` it will be a linear +combination of the form + +.. math:: + + \alpha_0 \vert 0\rangle \otimes \vert \psi_0\rangle + \alpha_1 \vert + 1\rangle \otimes\vert \psi_1\rangle, + +for some states :math:`\vert \psi_0\rangle` and :math:`\vert \psi_1\rangle` on +the rest of the circuit, up to a reordering of wires. (This follows by +factorizing with respect to the tensor product.) + + +After applying :math:`U(\theta),` the overall state will become + +.. math:: + + e^{-i\theta\gamma}\alpha_0 \vert 0\rangle \otimes\vert \psi_0\rangle + e^{+i\theta\gamma}\alpha_1 \vert 1\rangle \otimes\vert \psi_1\rangle. + +Whatever subsequent gates we apply, as long as there is only one occurrence +of :math:`U(\theta),` this expansion in frequencies :math:`e^ +{\pm i\theta\gamma}` is the same. We illustrate how a single copy of :math:`U +(\theta)` acts on the larger circuit below. + +.. raw:: html + +
+
+
+
+
+ +If there are multiple copies of :math:`U(\theta)` in the circuit, we simply +get more frequencies. Each box will multiply existing coefficients +by :math:`e^{-i\gamma\theta}` or :math:`e^{+i\gamma\theta},` depending on the +state it encounters. This will iteratively build up a state of the following +form: + +.. math:: + + \vert \psi(\theta)\rangle = \alpha_{0\cdots 00}e^{-in\gamma\theta}\vert + \psi_{0\cdots 00}\rangle + \alpha_{0\cdots 01}e^{-i(n-2)\gamma\theta}\vert + \psi_{0\cdots 01}\rangle + \cdots + \alpha_{1\cdots 11}e^{+in\gamma\theta}\vert + \psi_{1\cdots 11}\rangle, + +where the first term corresponds to choosing :math:`e^{-i\gamma \theta}` in +each box, and the last term :math:`e^{+i\gamma \theta}` in each box. Note +that, for intermediate frequencies, many different choices yield the same +final result! We illustrate for :math:`n = 2` below, where the total state at +the end of the circuit is + +.. math:: + + \vert \psi(\theta)\rangle = \alpha_{00}e^{-i2\theta\gamma} \vert \psi_{00}\rangle + \alpha_{01}e^{i0\gamma}\vert + \psi_{01}\rangle + \alpha_{10}e^{i0\gamma}\vert \psi_{10}\rangle + \alpha_{1}e^{+i2\theta\gamma} \vert \psi_{11}\rangle. + +Here, there are two ways to obtain a frequency of zero. + +.. tip:: + + Click on the magenta boxes terms to choose + :math:`e^{\pm i\gamma\theta}` in each box. The final frequency is shown on + the right. Although we are placing the boxes in parallel, the result is the + same for series. + +.. raw:: html + +
+
+
+
+
+ + +As usual, we can form the expectation value of a measurement +:math:`f(\theta) = \langle \psi(\theta)\vert M \vert \psi +(\theta)\rangle`. This will be a linear combination of overlaps of the +states above. For instance, when :math:`n =2,` it will be built from +overlaps of the states (including the associated phases) + +.. math:: + + e^{-i2\theta\gamma}\vert \psi_{00}\rangle, \quad e^{i0\gamma}\vert \psi_{01}\rangle, \quad e^{i0\gamma}\vert + \psi_{10}\rangle, \quad e^{+i2\theta\gamma}\vert \psi_{11}\rangle. + +Thus, the expectation is a Fourier series, with terms arising from +all the different ways of combining these frequencies. For general :math:`n,` this is + +.. math:: + + f(\theta) = c_{(-2n)}e^{-2in\gamma\theta} + + \cdots + c_{(0)} + \cdots + + c_{(2n)}e^{2in\gamma\theta}. + +Below, we illustrate for :math:`n = 2,` where the Fourier series consists +of five terms: + +.. math:: + + f(\theta) = c_{(-4)}e^{-4i\gamma\theta} + c_{(-2)}e^{-2i\gamma\theta} + + c_{(0)} + c_{(+2)}e^{+2i\gamma\theta} +c_{(+4)}e^{+4i\gamma\theta}. + + +.. tip:: + + Click on the magenta boxes terms to choose + :math:`e^{\pm i\gamma\theta}` in each box, contributing to the final + frequency. This frequency is shown below the circuit. + +.. raw:: html + +
+
+
+
+
+ +Coefficient vectors +---------------------------- + +So far, we've focused on the :math:`\theta`-dependent "pure frequency" terms :math:`e^{i\omega\theta\gamma}` appearing in the Fourier series. +However, the *coefficients* :math:`c_w` also have an important role +to play. +It turns out that, for a given set of frequencies in the Fourier +series, the coefficients *uniquely* characterize :math:`f(\theta).` +We'll redo the :math:`n = 2` example but highlight the coefficients instead; +these depend on the structure of the circuit and the choice of +measurement. + +.. tip:: + + Click on the magenta boxes terms to choose + :math:`e^{\pm i\gamma\theta}` in each box, contributing to the final + frequency. The coefficient of this final frequency is shown below the circuit. + +.. raw:: html + +
+
+
+
+
+ +The set of Fourier sums of fixed degree form a vector space, and the pure +frequencies :math:`e^{i\omega \theta\gamma}` are a *basis*. We can assemble +these coefficients into a column vector, +:math:`\vec{c}_f,` which contains the same information as the +function :math:`f(\theta):` + +.. math:: + + f(\theta) = c_{(-2n)} e^{-i2n\theta\gamma} + \cdots + c_{(+2n)} e^{+i2n\theta\gamma} \quad + \Leftrightarrow \quad \vec{c}_f = + \begin{bmatrix} + c_{(-2n)} \\ + \vdots \\ + c_{(2n)} + \end{bmatrix}. + +If an operation +on the function :math:`f(\theta)` preserves the structure of the function, +i.e., it only modifies the coefficients, then we can think of it as +an operation on vectors instead! Our first example is +differentiation. This simply pulls down a constant term from the +exponent of each pure frequency: + +.. math:: + + f'(\theta) = (-i2n\gamma)c_{-2n} e^{-i2n\theta\gamma} + \cdots + + (+i2n\gamma)c_{+2n} e^{+i2n\theta\gamma}. + +In terms of the coefficient vectors, however, it is just a diagonal +matrix :math:`D:` + +.. math:: + \vec{c}_{f'} = + \begin{bmatrix} + (-i2n\gamma)c_{-2n} \\ + \vdots \\ + (+i2n\gamma)c_{2n} + \end{bmatrix} = + \begin{bmatrix} + -i2n\gamma & & \\ + & \ddots & \\ + && +i2n\gamma + \end{bmatrix} + \begin{bmatrix} + c_{-2n} \\ + \vdots \\ c_{2n} + \end{bmatrix} = D\vec{c}. + + +.. tip:: + + The derivative of the expectation + :math:`f(\theta)` as a coefficient vector. Click on the magenta boxes to + choose terms contributing to the final frequency. + +.. raw:: html + +
+
+
+
+
+ +Another particularly simple operation on :math:`f(\theta)` is to +shift the parameter :math:`\theta` by some constant amount :math:`s,` giving a new +function :math:`f_s(\theta) = f(\theta + s),` also called a +*parameter shift*. From index laws, this adds an exponential +factor to each coefficient: + +.. math:: + + f(\theta + s) = c_{(-2n)} e^{-i2n(\theta + s)\gamma} + \cdots + + c_{(+2n)} e^{+i2n(\theta + s)\gamma} = e^{-i2ns\gamma}c_{(-2n)} e^{-i2n\theta\gamma} + \cdots + + e^{+i2ns\gamma}c_{(+2n)} e^{+i2n\theta\gamma}. + +Once again, this can be viewed as a diagonal matrix :math:`T_s` +acting on the coefficient vector: + +.. math:: + + \vec{c}_{f_s} = + \begin{bmatrix} + e^{-i2ns\gamma}c_{-2n} \\ + \vdots \\ + e^{+i2ns\gamma}c_{2n} + \end{bmatrix} = + \begin{bmatrix} + e^{-i2ns\gamma} & & \\ + & \ddots & \\ + && e^{+i2ns\gamma} + \end{bmatrix} + \begin{bmatrix} + c_{-2n} \\ + \vdots \\ c_{2n} + \end{bmatrix} = T_s\vec{c}. + +.. tip:: + + The parameter shifted expectation + :math:`f(\theta + s)` as a coefficient vector. Click on the magenta boxes to + choose terms contributing to the final frequency. + +.. raw:: html + +
+
+
+
+
+ +The two-term parameter-shift rule +----------------------------------------- + +Our original motivation for introducing :math:`\theta` was to *optimize* the +measurement result :math:`f(\theta).` If we can differentiate :math:`f +(\theta)`, we can use tools from classical machine learning such as *gradient +descent*. The problem is that circuits are black boxes; all we can do is set +some parameters, pull a lever, and out pops a measurement outcome. It's a bit +like a toaster. How do you differentiate a toaster? + +.. tip:: + + Click the measurement button for toasty measurement outcomes. + +.. raw:: html + +
+
+
+
+
+ +Luckily, the magic of Fourier series and coefficient vectors come to the +rescue. The basic idea is to write the differentiation matrix :math:`D` as a +linear combination of shift matrices :math:`T_s.` Although we can't +differentiate directly, we _can_ change parameters! Let's illustrate with the +simple example of :math:`n = 1.` In this case, the matrices are + +.. math:: + + D = + \begin{bmatrix}-2i\gamma && \\ & 0 & \\ & & +2i\gamma \end + {bmatrix}, \quad T_s = \begin{bmatrix}e^{-2i\gamma s} && \\ &1& \\ && e^ + {+2i\gamma s} \end{bmatrix}. + + +It's easy to check that for any :math:`s,` + +.. math:: + + D = \frac{2\gamma}{\sin(\gamma s)}(T_s - T_{-s}). + +Translating back into statements about functions, we learn that + +.. math:: + + f'(\theta) = \frac{2\gamma}{\sin(\gamma s)}[f(\theta + s) - f + (\theta - s)]. + + +This is called *two-term parameter-shift rule*. + +.. tip:: + + Click anywhere for gratuitous toast. + +.. raw:: html + +
+
+
+
+
+ +The two-term rule has a simple geometric interpretation. Changing +:math:`\theta` takes us around a circle of fixed radius +:math:`r = \vert c_{\pm2}\vert` at speed :math:`\gamma/2\pi.` + +.. note:: + + Note that parameter shifts add phases and don't change :math:`\vert c_ + {(\pm 2)}\vert`. The radius is given by either, since the reality of + measurement outcomes implies :math:`c_{(+2)} = \overline{c_{(-2)}}.` + +The derivative :math:`f'(\theta)` is a tangent vector of +length :math:`r\gamma.` We can choose Cartesian coordinates where this +tangent vector is vertical, with components + +.. math:: + + f'(\theta) = (0, r\gamma). + +The parameter shifts :math:`f(\theta \pm s),` on the other hand, have +components + +.. math:: + + f(\theta \pm s) = (r \cos(\gamma s), \pm r\sin(\gamma s)). + +It follows immediately that + +.. math:: + + f'(\theta) = \frac{2\gamma}{\sin(\gamma s)} [f(\theta + s) - f(\theta - s)]. + +We picture the geometry below. On the right, tangent to the circle, +is :math:`f'` as a coefficient vector in the :math:`c_{\pm2}` plane. We +display :math:`f(\theta + s)` and :math:`-f(\theta - s)` as light magenta +lines. Their vector sum :math:`\Delta f` is a dark magenta line. + +.. tip:: + + Horizontal mouse position controls :math:`s.` + +.. raw:: html + +
+
+
+
+
+ +The general parameter-shift rule +----------------------------------------- + +For :math:`n > 1,` parameter shift rules don't have a simple geometric +interpretation. The problem is that each pair of coefficients +:math:`c_{\pm \omega}` is associated with a circle governing two components of +the coefficient vector. To find the derivative, we need to understand each +pair of components separately, but parameter shifts *simultaneously* wind us +around all the circles, at different speeds! Geometrically speaking, +putting all these circles together gives a +*higher-dimensional donut*, which parameter shifts wind us +around. This sounds complicated! + +It also looks complicated, as we illustrate for :math:`n = 3` below. We +set :math:`\gamma = 1` so that, on the circle in the :math:`c_{\pm +\omega}` plane, we execute :math:`\omega` revolutions for a single cycle +of :math:`s.` + +.. tip:: + + Horizontal mouse position controls :math:`s.` For instance, if we place it in + the middle of interval, :math:`s = \pi.` This translates into an angle + :math:`\pm \pi\gamma` on the circle labelled :math:`c_{(\pm \gamma)}.` + +.. raw:: html + +
+
+
+
+
+ + +Perhaps surprisingly, the coefficient vector perspective and a few tricks let +us derive the general parameter-shift rule straighforwardly. We start with +the observation that :math:`f'(\theta)` has a coefficient vector +with :math:`2n` nonzero components, since the constant term always vanishes. +Thus, :math:`2n` linearly independent parameter shifts :math:`f +(\theta + s_k)` should be sufficient to reconstruct the derivative, with + +.. math:: + + f'(\theta) = \sum_{k=1}^{2n} \beta_k f(\theta + s_k) \tag{1} + +for some coefficients :math:`\beta_k.` The problem is how to find the shifts +and coefficients! We can invoke linear algebra to find coefficients, but only +once we choose shifts, and it's not obvious how to get them to be +independent. We can see the problem for :math:`n = 3,` where we choose six +random shifts. Are they independent or not? It seems hard to tell. Is there a +better approach? + +.. tip:: + + Horizontal mouse position controls :math:`s,` + which is now "quantized" with random shifts. + +.. raw:: html + +
+
+
+
+
+ +Thankfully, there is! We can solve two problems at once by introducing +an *inner product*, i.e., a way to find the scalar overlap of two vectors. +This will let us identify orthogonal and hence independent +shifts :math:`s_k.` Since they are orthogonal, we can also easily determine +the coefficients :math:`\beta_k.` The idea is straightforward: since the +matrices of interest are diagonal, + +.. math:: + + D = \mbox{diag}(-2in\gamma, \ldots, +2in\gamma), \quad T_s = \mbox{diag} + (e^{-2in\gamma s}, \ldots, e^{+2in\gamma s}), + +we can just pluck out the vector of diagonal entries and define a complex +inner product in the usual way. Technically, this is the `Frobenius inner +product `__ for +matrices: + +.. math:: + + \langle A, B\rangle = \mbox{Tr}[A^\dagger B]. + +Consider two shifts :math:`s, t \in [0, 2\pi/\gamma),` and +define :math:`\omega = e^{2\pi i\gamma(t-s)}.` The inner product of diagonal +shift matrices :math:`T_s, T_t` is + +.. math:: + + \langle T_s, T_t\rangle = \sum_{j=-n}^n \omega^j = \omega^{-n}\sum_{j=0}^ + {2n} \omega^j= \frac{\omega^{-n}(1 - \omega^{2n+1})}{1 - \omega} \tag{2} + +using the `geometric series `__. Before moving on, let's visualize what these inner +products look like for :math:`n = 3.` The expression (2) is +a sum of phases, which we can add top-to-tail on the complex plane. We've +added a big :math:`\mathbb{C}` to distinguish this from other planes we've +been looking at. + + +.. tip:: + + We display the inner product :math:`\langle T_s, T_t\rangle` below. + Horizontal mouse position controls :math:`s.` The choice of :math:`t` + is "quantized" to the random shifts from above; click to the left to set + it. The phases summed are light magenta, and the total is dark magenta. + +.. raw:: html + +
+
+
+
+
+ +As expected, our random shifts are not orthogonal. But with (2) in hand, it's +easy to choose orthogonal vectors! We simply select our shifts :math:`s_j` so +that the numerator of (2) vanishes: + +.. math:: + + \omega^{2n + 1} = e^{2\pi i \gamma(2n+1)(s_k - s_j)} = 1. + +This occurs if :math:`\gamma(2n + 1)(s_k - s_j)` is always an integer. A +natural choice is + +.. math:: + + s_k = \frac{k}{\gamma(2n+1)}, + +for :math:`k = 1, \ldots, 2n.` In this case, we have the orthogonality +relation + +.. math:: + + \langle T_{s_k}, T_{s_k}\rangle = (2n+1)\delta_{jk}. + +Thus, spacing shifts equally around the :math:`s` circle gives us an +orthogonal set of shifts. We picture these equally spaced shifts, and check +visually they are orthogonal, for :math:`n=3` below. Select any of the +equally spaced points, and you can see that its inner product with another of +the equally spaced points vanishes. + +.. tip:: + + Horizontal mouse position controls :math:`s.` + +.. raw:: html + +
+
+
+
+
+ +Orthogonality makes finding the coefficients :math:`\beta_k` easy: we simply +take the inner product between :math:`T_{s_k}` and the left-hand side +of (1), expressed in terms of the differentiation +matrix :math:`D.` This gives + +.. math:: + + \begin{align*} + \beta_k & = \frac{2i\gamma}{2n+1}\sum_{j=-n}^n j \omega_k^j + \end{align*} + +for :math:`\omega_k = e^{-2\pi i k/(2n+1)}.` This looks tricky, but we can +start with a geometric series, *differentiate* with respect +to :math:`\omega`, and multiply by :math:`\omega_k,` so we get what we want: + +.. math:: + + \omega_k \partial_{\omega_k} \sum_{j=-n}^n \omega_k^j = \sum_ + {j=-n}^n j\omega_k^j. + +We already computed the geometric series in +(2). Plugging that back in, differentiating, and using the +fact that :math:`\omega_k^{2n+1} = 1,` we finally get + +.. math:: + + \begin{align*} + \beta_k & = + \frac{2i\gamma}{2n+1}\cdot\frac{\omega_k^{-n}(2n+1)(\omega_k - + 1)}{(\omega_k - 1)^2} = \frac{2 i\gamma \omega_k^{-n}}{\omega_k-1}. + \end{align*} + + +Putting these together with our shifts, we have our general parameter-shift +rule: + +.. math:: + + f'(\theta) = \sum_{k=1}^{2n}\frac{2 i\gamma \omega_k^{-n}} + {\omega_k-1}f\left(\theta + \frac{k}{\gamma(2n+1)}\right). + + +The approach outlined here only works when the frequencies in the +problem are evenly spaced. However, there are ways to generalize +further. Even without orthogonality, we can find +independent shifts and solve the linear algebra problem +(1) for the coefficients. Alternatively, we can use +randomization to obtain shifts that are orthogonal on average, leading to +the *stochastic parameter-shift rule*. + +Conclusion +----------------------------------------- + +We've seen that, by viewing a parameterized gate as an opportunity to +choose eigenvalues, we naturally expand the expectations of the circuit +as a Fourier series, that is, a sum of frequency terms, corresponding to +the eigenvalues we chose along the way, with coefficients depending on the +circuit. By thinking about the structure of coefficients themselves, we +derived parameter-shift rules, allowing us to evaluate the derivative +of a circuit as a linear combination of its expectation value at different +parameter values. This is a key part of the machinery of performing +machine learning with quantum circuits, since we cannot perform the +derivatives directly. + +If you'd like to learn more, there are many papers on the topic. Two particularly clear references: + +- `General parameter-shift rules for quantum gradients `__ (2021). David Wierichs, Josh Izaac, Cody Wang, Cedric Yen-Yu Lin. +- `The effect of data encoding on the expressive power of variational quantum machine learning models `__ (2020). Maria Schuld, Ryan Sweke, Johannes Jakob Meyer. + +Handily, both references have an associated PennyLane demo! + +- `Quantum models as Fourier series `__ (2020). Maria Schuld, Ryan Sweke, Johannes Jakob Meyer. +- `Generalized parameter-shift rules `__ (2021). David Wierichs. + +Finally, PennyLane is jam-packed with tools for analyzing circuits as Fourier series. +Check out the documentation on the `Fourier module `__ to learn more! +""" + +############################################################################## diff --git a/demonstrations_v2/circuits_as_fourier_series/metadata.json b/demonstrations_v2/circuits_as_fourier_series/metadata.json new file mode 100644 index 0000000000..111a70eecf --- /dev/null +++ b/demonstrations_v2/circuits_as_fourier_series/metadata.json @@ -0,0 +1,47 @@ +{ + "title": "Circuits as Fourier series", + "authors": [ + { + "username": "hapax" + } + ], + "dateOfPublication": "2023-09-11T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/circuits_as_fourier_series/thumbnail_circuits_as_fourier_series.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_circuits_as_fourier_series.png" + } + ], + "seoDescription": "Learn with this interactive, code-free introduction to the idea of quantum circuits as Fourier series.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_stochastic_parameter_shift", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/circuits_as_fourier_series/requirements.in b/demonstrations_v2/circuits_as_fourier_series/requirements.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/demonstrations_v2/covalent_cloud_gpu/demo.py b/demonstrations_v2/covalent_cloud_gpu/demo.py new file mode 100644 index 0000000000..4846c01342 --- /dev/null +++ b/demonstrations_v2/covalent_cloud_gpu/demo.py @@ -0,0 +1,369 @@ +r"""Running GPU-accelerated quantum circuit simulations on Covalent Cloud using PennyLane +=================================================================== +""" + +############################################################################## +# +# In this tutorial, we’ll demonstrate how to run GPU-accelerated quantum circuit simulations +# on `Covalent Cloud `__ using PennyLane. We will focus on a specific example around +# quantum support vector machines (QSVMs) to demonstrate how easy it is to run GPU-accelerated quantum circuit simulations on Covalent Cloud. +# +# QSVMs are essentially `traditional +# SVMs `__ that rely on `embedding +# kernels `__ evaluated on +# a quantum computer—a.k.a. `quantum embedding +# kernel `__. +# These kernels provide a unique (and perhaps classically intractable) means of measuring pairwise +# similarity. +# +# Using GPUs to simulate quantum computers is worthwhile when qubit capacity and/or fidelity +# requirements are not met by the available quantum hardware. While QSVMs are relatively tolerant to +# noise (an important reason for their current popularity), evaluating kernels on *real* quantum +# hardware is not always practical nor necessary. +# + +############################################################################## +# Implementation +# -------------- +# +# Let’s start by importing the required packages. +# + +import covalent as ct +import covalent_cloud as cc +import matplotlib.pyplot as plt +import pennylane as qml +from matplotlib.colors import ListedColormap +from pennylane import numpy as np +from sklearn.datasets import make_blobs +from sklearn.inspection import DecisionBoundaryDisplay +from sklearn.model_selection import train_test_split +from sklearn.svm import SVC + +# cc.save_api_key("YOUR_API_KEY") + +############################################################################## +# Covalent Cloud allows us to create `reusable execution +# environments `__, as shown +# below. This environment represents a typical setup for running Pennylane on NVIDIA GPUs. +# + +cc.create_env( + name="pennylane-gpu", # identifier for referring to this environment + conda={ + "channels": ["conda-forge"], + "dependencies": ["cudatoolkit>=11.8"], + }, + pip=[ + "cuquantum==23.10.0", + "matplotlib==3.8.2", + "Pennylane==0.34.0", + "PennyLane-Lightning[GPU]==0.34.0", + "scikit-learn==1.3.1", + "torch==2.1.2", + ], + wait=True, +) + +############################################################################## +# .. rst-class :: sphx-glr-script-out +# +# .. code-block: none +# +# Waiting for environment pennylane-gpu to be ready.................. +# Name: pennylane-gpu +# Status: READY +# Estimated Time: 1002 seconds +# Notes: +# pip was added to the dependencies. +# Python version 3.10 was added to the dependencies. +# Environment file contains: +# ========================== +# channels: +# - conda-forge +# dependencies: +# - python=3.10 +# - pip +# - cudatoolkit>=11.8 +# - pip: +# - cuquantum==23.10.0 +# - matplotlib==3.8.2 +# - Pennylane==0.34.0 +# - PennyLane-Lightning[GPU]==0.34.0 +# - scikit-learn==1.3.1 +# - torch==2.1.2 +# - covalent-cloud +# name: pennylane-gpu +# + +############################################################################## +# Next, we’ll define our resource specifications by creating some +# `executors `__ +# for this workflow. Both executors will run tasks in our new environment, named ``”pennylane-gpu”``. +# + +cpu_executor = cc.CloudExecutor( # for lightweight non-quantum tasks + env="pennylane-gpu", + num_cpus=2, + memory="2GB", +) +gpu_executor = cc.CloudExecutor( # for GPU-powered circuit simulations + env="pennylane-gpu", num_cpus=4, memory="12GB", num_gpus=1, gpu_type="v100" +) + +############################################################################## +# On to the algorithm! +# +# Here’s a function returns a simple quantum kernel based on Pennylane’s `IQP +# Embedding `__ template. +# We’ll use it as-is inside our workflow. +# + +QML_DEVICE = "lightning.qubit" + + +def get_kernel_circuit(n_wires): + + @qml.qnode(qml.device(QML_DEVICE, wires=n_wires, shots=None)) + def circuit(x1, x2): + qml.IQPEmbedding(x1, wires=range(n_wires), n_repeats=4) + qml.adjoint(qml.IQPEmbedding)(x2, wires=range(n_wires), n_repeats=4) + return qml.probs(wires=range(n_wires)) + + return lambda x1, x2: circuit(x1, x2)[0] # |0..0> state probability + + +############################################################################## +# Next, each function destined for remote execution is decorated with ``@ct.electron``, with an +# `executor `__ +# specified therein. Only tasks that evaluate the simulated quantum kernel should require +# ``gpu_executor``. For example, we don’t need GPUs to generate our input data: +# + + +@ct.electron(executor=cpu_executor) # lightweight non-quantum task +def get_split_data(n_samples=18, test_size=0.2): + centers = [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)] + X, y = make_blobs(n_samples, centers=centers, cluster_std=0.25, shuffle=False) + # rescale labels to be -1, 1 + mapping = {0: -1, 1: 1, 2: -1, 3: 1, 4: -1, 5: 1, 6: -1, 7: 1, 8: -1} + y = np.array([mapping[i] for i in y]) + X = X.astype(np.float32) + y = y.astype(int) + + # X_train, X_test, y_train, y_test + return train_test_split(X, y, test_size=test_size, random_state=3) + + +############################################################################## +# Classifying with the SVM, on the other hand, requires :math:`O(n^2)` kernel evaluations, where +# :math:`n` is the dataset size. Accordingly, we’ll use GPUs (i.e. ``gpu_executor``) to speed up this +# process. +# + +DISP_SETTINGS = { + "grid_resolution": 50, + "response_method": "predict", + "alpha": 0.5, + "cmap": plt.cm.RdBu, +} + + +@ct.electron(executor=gpu_executor) +def classify_with_qsvm(Xtr, Xte, ytr, yte): + kernel = get_kernel_circuit(n_wires=Xtr.shape[1]) + + kernel_matrix_fn = lambda X, Z: qml.kernels.kernel_matrix(X, Z, kernel) + svc = SVC(kernel=kernel_matrix_fn).fit(Xtr, ytr) + + # train/test accuracy + accuracy_tr = svc.score(Xtr, ytr) + accuracy_te = svc.score(Xte, yte) + + # decision boundary + cm_bright = ListedColormap(["#FF0000", "#0000FF"]) + disp = DecisionBoundaryDisplay.from_estimator(svc, Xte, **DISP_SETTINGS) + disp.ax_.scatter(Xtr[:, 0], Xtr[:, 1], c=ytr, cmap=cm_bright) + disp.ax_.scatter(Xte[:, 0], Xte[:, 1], c=yte, cmap=cm_bright, marker="$\u25EF$") + + return accuracy_tr, accuracy_te, disp + + +############################################################################## +# Putting it all together, we can define a QSVM training and testing workflow. This special function +# gets decorated with ``@ct.lattice``. +# + + +@ct.lattice(workflow_executor=cpu_executor, executor=cpu_executor) +def run_qsvm(n_samples, test_size): + Xtr, Xte, ytr, yte = get_split_data(n_samples, test_size) + return classify_with_qsvm(Xtr, Xte, ytr, yte) + + +############################################################################## +# Now, to dispatch ``run_qsvm`` to Covalent Cloud, we call it after wrapping with ``ct.dispatch``, as +# usual. +# + +dispatch_id = cc.dispatch(run_qsvm)(n_samples=64, test_size=0.2) +print("Dispatch ID:", dispatch_id) + +############################################################################## +# .. rst-class :: sphx-glr-script-out +# +# .. code-block: none +# +# Dispatch ID: 0b5d3a08-fe9c-4dc2-910b-0be6eb925663 + +############################################################################## +# Here’s what we get when we query and display the results. +# + +result = cc.get_result(dispatch_id, wait=True) +result.result.load() + +train_acc, test_acc, decision_boundary_figure = result.result.value +print(f"Train accuracy: {train_acc * 100:.1f}%") +print(f"Test accuracy: {test_acc * 100:.1f}%") + +decision_boundary_figure + +############################################################################## +# .. rst-class :: sphx-glr-script-out +# +# .. code-block: none +# +# Train accuracy: 64.7% +# Test accuracy: 76.9% +# +# .. figure:: ../_static/demonstration_assets/covalent_cloud_gpu/covalent_cloud_gpu_19_1.png +# :align: center +# :width: 90% + +############################################################################## +# Conclusion +# ---------- +# +# In this tutorial, we demonstrated how to run quantum circuit simulations on GPUs via Covalent Cloud. +# We used PennyLane to define a simple quantum kernel, and then trained and tested a QSVM on a +# 2-dimensional dataset. To make the most of this tutorial, try experimenting with different +# datasets/kernels or increasing the dataset dimension, to gain a greater advantage from GPU +# acceleration. +# +# The cost of running this workflow is approximately \$0.27. The full code is available below, and you can try it yourself with the *Run on Covalent Cloud* button in the side menu on the right. +# + +############################################################################## +# Full Code +# --------- +# + +############################################################################## +# .. code:: python +# +# import covalent as ct +# import covalent_cloud as cc +# import matplotlib.pyplot as plt +# import pennylane as qml +# from matplotlib.colors import ListedColormap +# from pennylane import numpy as np +# from sklearn.datasets import make_blobs +# from sklearn.inspection import DecisionBoundaryDisplay +# from sklearn.model_selection import train_test_split +# from sklearn.svm import SVC +# +# cc.save_api_key("API_KEY") +# +# cc.create_env( +# name="pennylane-gpu", # identifier for referring to this environment +# conda={ +# "channels": ["conda-forge"], +# "dependencies": ["cudatoolkit>=11.8"], +# }, pip=[ +# "cuquantum==23.10.0", +# "matplotlib==3.8.2", +# "Pennylane==0.34.0", +# "PennyLane-Lightning[GPU]==0.34.0", +# "scikit-learn==1.3.1", +# "torch==2.1.2", +# ], +# wait=True +# ) +# +# cpu_executor = cc.CloudExecutor( # for lightweight non-quantum tasks +# env="pennylane-gpu", +# num_cpus=2, +# memory="2GB", +# ) +# gpu_executor = cc.CloudExecutor( # for GPU-powered circuit simulations +# env="pennylane-gpu", +# num_cpus=4, +# memory="12GB", +# num_gpus=1, +# gpu_type="v100" +# ) +# +# QML_DEVICE = "lightning.gpu" +# +# def get_kernel_circuit(n_wires): +# +# @qml.qnode(qml.device(QML_DEVICE, wires=n_wires, shots=None)) +# def circuit(x1, x2): +# qml.IQPEmbedding(x1, wires=range(n_wires), n_repeats=4) +# qml.adjoint(qml.IQPEmbedding)(x2, wires=range(n_wires), n_repeats=4) +# return qml.probs(wires=range(n_wires)) +# +# return lambda x1, x2: circuit(x1, x2)[0] # |0..0> state probability +# +# @ct.electron(executor=cpu_executor) # lightweight non-quantum task +# def get_split_data(n_samples=18, test_size=0.2): +# centers = [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)] +# X, y = make_blobs(n_samples, centers=centers, cluster_std=0.25, shuffle=False) +# # rescale labels to be -1, 1 +# mapping = {0: -1, 1: 1, 2: -1, 3: 1, 4: -1, 5: 1, 6: -1, 7: 1, 8: -1} +# y = np.array([mapping[i] for i in y]) +# X = X.astype(np.float32) +# y = y.astype(int) +# +# # X_train, X_test, y_train, y_test +# return train_test_split(X, y, test_size=test_size, random_state=3) +# +# DISP_SETTINGS = {"grid_resolution": 50, "response_method": "predict", "alpha": 0.5, "cmap": plt.cm.RdBu} +# +# @ct.electron(executor=gpu_executor) +# def classify_with_qsvm(Xtr, Xte, ytr, yte): +# kernel = get_kernel_circuit(n_wires=Xtr.shape[1]) +# +# kernel_matrix_fn = lambda X, Z: qml.kernels.kernel_matrix(X, Z, kernel) +# svc = SVC(kernel=kernel_matrix_fn).fit(Xtr, ytr) +# +# # train/test accuracy +# accuracy_tr = svc.score(Xtr, ytr) +# accuracy_te = svc.score(Xte, yte) +# +# # decision boundary +# cm_bright = ListedColormap(["#FF0000", "#0000FF"]) +# disp = DecisionBoundaryDisplay.from_estimator(svc, Xte, **DISP_SETTINGS) +# disp.ax_.scatter(Xtr[:, 0], Xtr[:, 1], c=ytr, cmap=cm_bright) +# disp.ax_.scatter(Xte[:, 0], Xte[:, 1], c=yte, cmap=cm_bright, marker="$\u25EF$") +# +# return accuracy_tr, accuracy_te, disp +# +# @ct.lattice(workflow_executor=cpu_executor, executor=cpu_executor) +# def run_qsvm(n_samples, test_size): +# Xtr, Xte, ytr, yte = get_split_data(n_samples, test_size) +# return classify_with_qsvm(Xtr, Xte, ytr, yte) +# +# dispatch_id = cc.dispatch(run_qsvm)(n_samples=64, test_size=0.2) +# print("Dispatch ID:", dispatch_id) +# +# result = cc.get_result(dispatch_id, wait=True) +# result.result.load() +# +# train_acc, test_acc, decision_boundary_figure = result.result.value +# print(f"Train accuracy: {train_acc * 100:.1f}%") +# print(f"Test accuracy: {test_acc * 100:.1f}%") +# + +############################################################################## diff --git a/demonstrations_v2/covalent_cloud_gpu/metadata.json b/demonstrations_v2/covalent_cloud_gpu/metadata.json new file mode 100644 index 0000000000..3b079b3760 --- /dev/null +++ b/demonstrations_v2/covalent_cloud_gpu/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "Running GPU-accelerated quantum circuit simulations on Covalent Cloud using PennyLane", + "authors": [ + { + "username": "skradha" + }, + { + "username": "aghukasyan" + } + ], + "dateOfPublication": "2024-05-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/covalent_cloud_gpu/thumbnail_covalent_cloud_gpu.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_covalent_cloud_gpu.png" + } + ], + "seoDescription": "Explore the process of running GPU-accelerated quantum circuit simulations on Covalent Cloud using PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_univariate_qvr", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "covalent", + "link": "https://docs.covalent.xyz/docs/cloud/tutorials-cloud/pennylane_tutorial/", + "logo": "/_static/hardware_logos/covalent.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/covalent_cloud_gpu/requirements.in b/demonstrations_v2/covalent_cloud_gpu/requirements.in new file mode 100644 index 0000000000..5e2d3500be --- /dev/null +++ b/demonstrations_v2/covalent_cloud_gpu/requirements.in @@ -0,0 +1,5 @@ +covalent==0.227.0rc0 +covalent_cloud +matplotlib +pennylane +scikit-learn diff --git a/demonstrations_v2/ensemble_multi_qpu/demo.py b/demonstrations_v2/ensemble_multi_qpu/demo.py new file mode 100644 index 0000000000..d72b843c17 --- /dev/null +++ b/demonstrations_v2/ensemble_multi_qpu/demo.py @@ -0,0 +1,578 @@ +r""" +Ensemble classification with Rigetti and Qiskit devices +======================================================= + +.. meta:: + :property="og:description": We demonstrate how two QPUs can be + combined in parallel to help solve a machine learning classification problem, + using PyTorch and PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/ensemble_diagram.png + +.. related + + tutorial_variational_classifier Variational classifier + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 13 December 2021.* + +This tutorial outlines how two QPUs can be combined in parallel to help solve a machine learning +classification problem. + +.. warning:: + This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin. + It is compatible with ``qiskit==0.46`` and ``pennylane-qiskit==0.35.1``. Older versions of + Qiskit and the Pennylane-Qiskit plugin should not be installed in environments with an + existing installation of Qiskit 1.0 or above. + +We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to +simulate another. Each QPU makes an independent prediction, and an ensemble model is +formed by choosing the prediction of the most confident QPU. The iris dataset is used in this +tutorial, consisting of three classes of iris flower. Using a pre-trained model and the PyTorch +interface, we'll see that ensembling allows the QPUs to specialize towards +different classes. + +Let's begin by importing the prerequisite libraries: +""" + +from collections import Counter + +import dask +import matplotlib.pyplot as plt +import numpy as np +import pennylane as qml +import sklearn.datasets +import sklearn.decomposition +import torch +from matplotlib.lines import Line2D +from matplotlib.patches import Patch + +############################################################################## +# This tutorial requires the ``pennylane-rigetti`` and ``pennylane-qiskit`` packages, which can be +# installed by following the instructions `here `__. We also +# make use of the `PyTorch interface `_, which can be installed from `here +# `__. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# Load data +# --------- +# +# The next step is to load the iris dataset. + +n_features = 2 +n_classes = 3 +n_samples = 150 + +data = sklearn.datasets.load_iris() +x = data["data"] +y = data["target"] + +############################################################################## +# We shuffle the data and then embed the four features into a two-dimensional space for ease of +# plotting later on. The first two principal components of the data are used. + +np.random.seed(1967) + +data_order = np.random.permutation(np.arange(n_samples)) +x, y = x[data_order], y[data_order] + +pca = sklearn.decomposition.PCA(n_components=n_features) +pca.fit(x) +x = pca.transform(x) + +############################################################################## +# We will be encoding these two features into quantum circuits using :class:`~.pennylane.RX` +# rotations, and hence renormalize our features to be between :math:`[-\pi, \pi].` + + +x_min = np.min(x, axis=0) +x_max = np.max(x, axis=0) + +x = 2 * np.pi * (x - x_min) / (x_max - x_min) - np.pi + +############################################################################## +# The data is split between a training and a test set. This tutorial uses a model that is +# pre-trained on the training set. + + +split = 125 + +x_train = x[:split] +x_test = x[split:] +y_train = y[:split] +y_test = y[split:] + +############################################################################## +# Finally, let's take a quick look at our data: + + +colours = ["#ec6f86", "#4573e7", "#ad61ed"] + + +def plot_points(x_train, y_train, x_test, y_test): + c_train = [] + c_test = [] + + for y in y_train: + c_train.append(colours[y]) + + for y in y_test: + c_test.append(colours[y]) + + plt.scatter(x_train[:, 0], x_train[:, 1], c=c_train) + plt.scatter(x_test[:, 0], x_test[:, 1], c=c_test, marker="x") + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch(facecolor=colours[0], edgecolor=c_transparent, label="Class 0"), + Patch(facecolor=colours[1], edgecolor=c_transparent, label="Class 1"), + Patch(facecolor=colours[2], edgecolor=c_transparent, label="Class 2"), + Line2D([0], [0], marker="o", color=c_transparent, label="Train", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker="x", color=c_transparent, label="Test", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +plot_points(x_train, y_train, x_test, y_test) +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# This plot shows us that class 0 points can be nicely separated, but that there is an overlap +# between points from classes 1 and 2. +# +# Define model +# ------------ +# +# Our model is summarized in the figure below. We use two 4-qubit devices: ``4q-qvm`` +# from the pennyLane-rigetti plugin and ``qiskit.aer`` from the PennyLane-Qiskit plugin. +# +# Data is input using :class:`~.pennylane.RX` rotations and then a different circuit is enacted +# for each device with a unique set of trainable parameters. The output of both circuits is a +# :class:`~.pennylane.PauliZ` measurement on three of the qubits. This is then fed through a +# softmax function, resulting in two 3-dimensional probability vectors corresponding to the 3 +# classes. +# +# Finally, the ensemble model chooses the QPU which is most confident about its prediction +# (i.e., the class with the highest overall probability over all QPUs) and uses that to make a +# prediction. +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_diagram.png +# :width: 80% +# :align: center +# +# Quantum nodes +# ^^^^^^^^^^^^^ +# +# We begin by defining the two quantum devices and the circuits to be run on them. + +n_wires = 4 + +dev0 = qml.device("rigetti.qvm", device="4q-qvm") +dev1 = qml.device("qiskit.aer", wires=4) +devs = [dev0, dev1] + +############################################################################## +# .. note:: +# If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` +# and specify the hardware device to run on. Users with access to the IBM Q Experience can +# swap ``qiskit.aer`` for ``qiskit.ibmq`` and specify their chosen backend (see `here +# `__). +# +# +# The circuits for both QPUs are shown in the figure below: +# +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/diagram_circuits.png +# :width: 80% +# :align: center + + +def circuit0(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[1, 0, i], wires=i) + + qml.CZ(wires=[1, 0]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[3, 0]) + + for i in range(n_wires): + qml.Rot(*params[1, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +def circuit1(params, x=None): + for i in range(n_wires): + qml.RX(x[i % n_features], wires=i) + qml.Rot(*params[0, 0, i], wires=i) + + qml.CZ(wires=[0, 1]) + qml.CZ(wires=[1, 2]) + qml.CZ(wires=[1, 3]) + + for i in range(n_wires): + qml.Rot(*params[0, 1, i], wires=i) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + +############################################################################## +# We finally combine the two devices into a :class:`~.pennylane.QNode` list: + + +qnodes = [ + qml.QNode(circuit0, dev0), + qml.QNode(circuit1, dev1), +] + +############################################################################## +# Postprocessing into a prediction +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``predict_point`` function below allows us to find the ensemble prediction, as well as keeping +# track of the individual predictions from each QPU. +# +# We include a ``parallel`` keyword argument for evaluating the :class:`~.pennylane.QNode` list +# in a parallel asynchronous manner. This feature requires the ``dask`` library, which can be +# installed using ``pip install "dask[delayed]"``. When ``parallel=True``, we are able to make +# predictions faster because we do not need to wait for one QPU to output before running on the +# other. + +def decision(softmax): + return int(torch.argmax(softmax)) + + +def predict_point(params, x_point=None, parallel=True): + if parallel: + results = tuple(dask.delayed(q)(params, x=torch.from_numpy(x_point)) for q in qnodes) + results = torch.tensor(dask.compute(*results, scheduler="threads")) + else: + results = tuple(q(params, x=x_point) for q in qnodes) + results = torch.tensor(results) + softmax = torch.nn.functional.softmax(results, dim=1) + choice = torch.where(softmax == torch.max(softmax))[0][0] + chosen_softmax = softmax[choice] + return decision(chosen_softmax), decision(softmax[0]), decision(softmax[1]), int(choice) + + +############################################################################## +# Next, let's define a function to make a predictions over multiple data points. + + +def predict(params, x=None, parallel=True): + predictions_ensemble = [] + predictions_0 = [] + predictions_1 = [] + choices = [] + + for i, x_point in enumerate(x): + if i % 10 == 0 and i > 0: + print("Completed up to iteration {}".format(i)) + results = predict_point(params, x_point=x_point, parallel=parallel) + predictions_ensemble.append(results[0]) + predictions_0.append(results[1]) + predictions_1.append(results[2]) + choices.append(results[3]) + + return predictions_ensemble, predictions_0, predictions_1, choices + + +############################################################################## +# Make predictions +# ---------------- +# +# To test our model, we first load a pre-trained set of parameters which can also be downloaded +# by clicking :download:`here <../_static/demonstration_assets/ensemble_multi_qpu/params.npy>`. + + +params = np.load("../_static/demonstration_assets/ensemble_multi_qpu/params.npy") + +############################################################################## +# We can then make predictions for the training and test datasets. + + +print("Predicting on training dataset") +p_train, p_train_0, p_train_1, choices_train = predict(params, x=x_train) +print("Predicting on test dataset") +p_test, p_test_0, p_test_1, choices_test = predict(params, x=x_test) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Predicting on training dataset +# Completed up to iteration 10 +# Completed up to iteration 20 +# Completed up to iteration 30 +# Completed up to iteration 40 +# Completed up to iteration 50 +# Completed up to iteration 60 +# Completed up to iteration 70 +# Completed up to iteration 80 +# Completed up to iteration 90 +# Completed up to iteration 100 +# Completed up to iteration 110 +# Completed up to iteration 120 +# Predicting on test dataset +# Completed up to iteration 10 +# Completed up to iteration 20 + +############################################################################## +# Analyze performance +# ------------------- +# +# The last thing to do is test how well the model performs. We begin by looking at the accuracy. +# +# Accuracy +# ^^^^^^^^ + + +def accuracy(predictions, actuals): + count = 0 + + for i in range(len(predictions)): + if predictions[i] == actuals[i]: + count += 1 + + accuracy = count / (len(predictions)) + return accuracy + +############################################################################## + +print("Training accuracy (ensemble): {}".format(accuracy(p_train, y_train))) +print("Training accuracy (QPU0): {}".format(accuracy(p_train_0, y_train))) +print("Training accuracy (QPU1): {}".format(accuracy(p_train_1, y_train))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Training accuracy (ensemble): 0.824 +# Training accuracy (QPU0): 0.648 +# Training accuracy (QPU1): 0.296 + +############################################################################## + +print("Test accuracy (ensemble): {}".format(accuracy(p_test, y_test))) +print("Test accuracy (QPU0): {}".format(accuracy(p_test_0, y_test))) +print("Test accuracy (QPU1): {}".format(accuracy(p_test_1, y_test))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Test accuracy (ensemble): 0.72 +# Test accuracy (QPU0): 0.56 +# Test accuracy (QPU1): 0.24 + +############################################################################## +# These numbers tell us a few things: +# +# - On both training and test datasets, the ensemble model outperforms the predictions from each +# QPU. This provides a nice example of how QPUs can be used in parallel to gain a performance +# advantage. +# +# - The accuracy of QPU0 is much higher than the accuracy of QPU1. This does not mean that one +# device is intrinsically better than the other. In fact, another set of parameters can lead to +# QPU1 becoming more accurate. We will see in the next section that the difference in accuracy +# is due to specialization of each QPU, which leads to overall better performance of the +# ensemble model. +# +# - The test accuracy is lower than the training accuracy. Here our focus is on analyzing the +# performance of the ensemble model, rather than minimizing the generalization error. +# +# Choice of QPU +# ^^^^^^^^^^^^^ +# +# Is there a link between the class of a datapoint and the QPU chosen to make the prediction in +# the ensemble model? Let's investigate. + + +# Combine choices_train and choices_test to simplify analysis +choices = np.append(choices_train, choices_test) +print("Choices: {}".format(choices)) +print("Choices counts: {}".format(Counter(choices))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Choices: [0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 1 0 0 0 1 0 0 1 0 1 0 0 0 +# 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 +# 0 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 +# 1 0 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 +# 0 0] +# Choices counts: Counter({0: 110, 1: 40}) + +############################################################################## +# The following lines keep track of choices and corresponding predictions in the ensemble model. + + +predictions = np.append(p_train, p_test) +choice_vs_prediction = np.array([(choices[i], predictions[i]) for i in range(n_samples)]) + +############################################################################## +# We can hence find the predictions each QPU was responsible for. + + +choices_vs_prediction_0 = choice_vs_prediction[choice_vs_prediction[:, 0] == 0] +choices_vs_prediction_1 = choice_vs_prediction[choice_vs_prediction[:, 0] == 1] +predictions_0 = choices_vs_prediction_0[:, 1] +predictions_1 = choices_vs_prediction_1[:, 1] + + +expl = "When QPU{} was chosen by the ensemble, it made the following distribution of " \ + "predictions:\n{}" +print(expl.format("0", Counter(predictions_0))) +print("\n" + expl.format("1", Counter(predictions_1))) +print("\nDistribution of classes in iris dataset: {}".format(Counter(y))) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# When QPU0 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({0: 55, 2: 55}) +# +# When QPU1 was chosen by the ensemble, it made the following distribution of predictions: +# Counter({1: 37, 0: 3}) +# +# Distribution of classes in iris dataset: Counter({0: 50, 2: 50, 1: 50}) + +############################################################################## +# These results show us that QPU0 specializes to making predictions on classes 0 and 2, +# while QPU1 specializes to class 1. +# +# Visualization +# ^^^^^^^^^^^^^ +# +# We conclude by visualizing the correct and incorrect predictions on the dataset. The following +# function plots correctly predicted points in green and incorrectly predicted points in red. + + +colours_prediction = {"correct": "#83b5b9", "incorrect": "#f98d91"} +markers = ["o", "v", "d"] + + +def plot_points_prediction(x, y, p, title): + c = {0: [], 1: [], 2: []} + x_ = {0: [], 1: [], 2: []} + + for i in range(n_samples): + x_[y[i]].append(x[i]) + if p[i] == y[i]: + c[y[i]].append(colours_prediction["correct"]) + else: + c[y[i]].append(colours_prediction["incorrect"]) + + for i in range(n_classes): + x_class = np.array(x_[i]) + plt.scatter(x_class[:, 0], x_class[:, 1], c=c[i], marker=markers[i]) + + plt.xlabel("Feature 1", fontsize=16) + plt.ylabel("Feature 2", fontsize=16) + plt.title("Predictions from {} model".format(title)) + + ax = plt.gca() + ax.set_aspect(1) + + c_transparent = "#00000000" + + custom_lines = [ + Patch( + facecolor=colours_prediction["correct"], + edgecolor=c_transparent, label="Correct" + ), + Patch( + facecolor=colours_prediction["incorrect"], + edgecolor=c_transparent, label="Incorrect" + ), + Line2D([0], [0], marker=markers[0], color=c_transparent, label="Class 0", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[1], color=c_transparent, label="Class 1", + markerfacecolor="black", markersize=10), + Line2D([0], [0], marker=markers[2], color=c_transparent, label="Class 2", + markerfacecolor="black", markersize=10), + ] + + ax.legend(handles=custom_lines, bbox_to_anchor=(1.0, 0.75)) + + +############################################################################## +# We can again compare the ensemble model with the individual models from each QPU. + + +plot_points_prediction(x, y, predictions, "ensemble") # ensemble +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_002.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_0, p_test_0), "QPU0") # QPU 0 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_003.png +# :width: 80% +# :align: center +# + +############################################################################## + +plot_points_prediction(x, y, np.append(p_train_1, p_test_1), "QPU1") # QPU 1 +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ensemble_multi_qpu/ensemble_multi_qpu_004.png +# :width: 80% +# :align: center +# + +############################################################################## +# These plots reinforce the specialization of the two QPUs. QPU1 concentrates on doing a good job +# at predicting class 1, while QPU0 is focused on classes 0 and 2. By combining together, +# the resultant ensemble performs better. +# +# This tutorial shows how QPUs can work in parallel to realize a performance advantage. Check out +# our :doc:`vqe_parallel` tutorial to see how multiple QPUs can be +# evaluated asynchronously to speed up calculating the potential energy surface of molecular +# hydrogen! + +############################################################################## diff --git a/demonstrations_v2/ensemble_multi_qpu/metadata.json b/demonstrations_v2/ensemble_multi_qpu/metadata.json new file mode 100644 index 0000000000..2765d91fa8 --- /dev/null +++ b/demonstrations_v2/ensemble_multi_qpu/metadata.json @@ -0,0 +1,32 @@ +{ + "title": "Ensemble classification with Rigetti and Qiskit devices", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-02-14T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_ensemble_classification_rigetti_qiskit.png" + } + ], + "seoDescription": "We demonstrate how two QPUs can be combined in parallel to help solve a machine learning classification problem, using PyTorch and PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/ensemble_multi_qpu/requirements.in b/demonstrations_v2/ensemble_multi_qpu/requirements.in new file mode 100644 index 0000000000..d65986eab4 --- /dev/null +++ b/demonstrations_v2/ensemble_multi_qpu/requirements.in @@ -0,0 +1,6 @@ +dask +matplotlib +numpy +pennylane +scikit-learn +torch diff --git a/demonstrations_v2/function_fitting_qsp/demo.py b/demonstrations_v2/function_fitting_qsp/demo.py new file mode 100644 index 0000000000..c973108860 --- /dev/null +++ b/demonstrations_v2/function_fitting_qsp/demo.py @@ -0,0 +1,576 @@ +r""" + +.. function_fitting_qsp: + +Function Fitting using Quantum Signal Processing +====================================================== + +.. meta:: + :property="og:description": Learn how to create polynomial approximations to functions + using Quantum Signal Processing (QSP). + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/function_fitting_qsp/cover.png + +*Author: Jay Soni — Posted: 24 May 2022. Last updated: 17 April 2023.* + +Introduction +~~~~~~~~~~~~~ + +This demo is inspired by the paper `‘A Grand Unification of Quantum +Algorithms’ `__. This +paper is centered around the Quantum Singular Value Transform (QSVT) +protocol and how it provides a single framework to generalize some of +the most famous quantum algorithms like Shor’s factoring algorithm, Grover search, +and more. + +The QSVT is a method to apply polynomial transformations to the singular +values of *any matrix*. This is powerful +because from polynomial transformations we can generate arbitrary function +transformations using Taylor approximations. The QSVT protocol is an +extension of the more constrained Quantum Signal Processing (QSP) +protocol which presents a method for polynomial transformation of matrix +entries in a single-qubit unitary operator. The QSVT protocol is sophisticated, +but the idea at its core +is quite simple. By studying QSP, we get a relatively simpler path to explore +this idea at the foundation of QSVT. + +In this demo, we explore the QSP protocol and how it can be used +for curve fitting. We show how you can fit polynomials, as illustrated in +the animation below. + +.. figure:: ../_static/demonstration_assets/function_fitting_qsp/trained_poly.gif + :align: center + :width: 50% + +This is a powerful tool that will ultimately allow us +to approximate any function on the interval :math:`[-1, 1]` that +satisfies certain constraints. Before we can dive into function fitting, +let’s develop some intuition. Consider the following single-qubit operator +parameterized by :math:`a \in [-1, 1]:` + +.. math:: \hat{W}(a) = \begin{bmatrix} a & i\sqrt{1 - a^{2}} \\ i\sqrt{1 - a^{2}} & a \end{bmatrix}. + +:math:`\hat{W}(a)` is called the *signal rotation operator* (SRO). Using +this operator, we can construct another operator which we call +*signal processing operator* (SPO), + +.. math:: \hat{U}_{sp} = \hat{R}_{z}(\phi_{0}) \prod_{k=1}^{d} \hat{W}(a) \hat{R}_{z}(\phi_{k}). + +The SPO is parameterized by a vector +:math:`\vec{\phi} \in \mathbb{R}^{d+1},` where :math:`d` is a free +parameter which represents the number of repeated applications of +:math:`\hat{W}(a).` + +The SPO :math:`\hat{U}_{sp}` alternates between applying the SRO :math:`\hat{W}(a)` +and parameterized rotations around the z-axis. Let’s see what happens when we try to compute the +expectation value :math:`\langle 0|\hat{U}_{sp}|0\rangle` for the particular +case where :math:`d = 2` and :math:`\vec{\phi} = (0, 0, 0)` : + +.. math:: + + + \begin{align*} + \langle 0 |\hat{U}_{sp}|0\rangle &= \langle 0 | \ \hat{R}_{z}(0) \prod_{k=1}^{2} \hat{W}(a) \hat{R}_{z}(0) \ |0\rangle \\ + \langle 0 |\hat{U}_{sp}|0\rangle &= \langle 0 | \hat{W}(a)^{2} |0\rangle \\ + \end{align*} + +.. math:: + + + \langle 0 |\hat{U}_{sp}|0\rangle = \langle 0 | \begin{bmatrix} a & i\sqrt{1 - a^{2}} \\ i\sqrt{1 - a^{2}} & a \end{bmatrix} \ \circ \ \begin{bmatrix} a & i\sqrt{1 - a^{2}} \\ i\sqrt{1 - a^{2}} & a \end{bmatrix} |0\rangle + +.. math:: + + + \langle 0|\hat{U}_{sp}|0\rangle = \langle 0| \begin{bmatrix} 2a^{2} - 1 & 2ai\sqrt{1 - a^{2}} \\ 2ai\sqrt{1 - a^{2}} & 2a^{2} - 1 \end{bmatrix} |0\rangle + +.. math:: \langle 0|\hat{U}_{sp}|0\rangle = 2a^{2} - 1 + +Notice that this quantity is a polynomial in :math:`a.` Equivalently, +suppose we wanted to create a map :math:`S: a \to 2a^2 - 1.` +This expectation value would give us the means to perform such a mapping. +This may seem oddly specific at first, but it turns out that +this process can be generalized for generating a mapping +:math:`S: a \to \text{poly}(a).` The following theorem shows us how: + +Theorem: Quantum Signal Processing +$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + +Given a vector :math:`\vec{\phi} \in \mathbb{R}^{d+1},` there exist +complex polynomials :math:`P(a)` and :math:`Q(a)` such that the SPO, +:math:`\hat{U}_{sp},` can be expressed in matrix form as: + +.. math:: \hat{U}_{sp} = \hat{R}_{z}(\phi_{0}) \prod_{k=1}^{d} \hat{W}(a) \hat{R}_{z}(\phi_{k}), + +.. math:: + + \hat{U}_{sp} = \begin{bmatrix} P(a) & iQ(a)\sqrt{1 - a^{2}} \\ iQ^{*}(a)\sqrt{1 - a^{2}} & P^{*}(a) \end{bmatrix}, + +where :math:`a \in [-1, 1]` and the polynomials :math:`P(a),` +:math:`Q(a)` satisfy the following constraints: + +- :math:`deg(P) \leq d \ ` and :math:`deg(Q) \leq d - 1,` +- :math:`P` has parity :math:`d` mod 2 and :math:`Q` has parity, + :math:`d - 1` mod 2 +- :math:`|P|^{2} + (1 - a^{2})|Q|^{2} = 1.` + + +The third condition is actually quite restrictive because if we substitute :math:`a = \pm 1,` +we get the result :math:`|P^{2}(\pm 1)| = 1.` Thus it restricts the polynomial to be +pinned to :math:`\pm 1` at the end points of the domain, :math:`a = \pm 1.` This condition +can be relaxed to :math:`|P^{2}(a)| \leq 1` by expressing the signal processing operator +in the Hadamard basis, i.e., :math:`\langle + |\hat{U}_{sp}(\vec{\phi};a)|+\rangle`). This is equivalent to +redefining :math:`P(a)` such that: + +.. math:: P^{'}(a) = \text{Re}(P(a)) + i\text{Re}(Q(a))\sqrt{1 - a^{2}} + +*This is the convention we follow in this demo.* + +""" + +###################################################################### +# Let's Plot some Polynomials +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now we put this theorem to the test! In this section we construct +# the SRO :math:`\hat{W}(a),` and then use PennyLane to define the SPO. +# To test the theorem we will randomly generate parameters +# :math:`\vec{\phi}` and plot the expectation value +# :math:`\langle + |\hat{U}_{sp}(\vec{\phi};a)|+\rangle` for +# :math:`a \in [-1, 1].` +# + +###################################################################### +# Next, we introduce a function called ``rotation_mat(a)``, +# which will construct the SRO matrix. We can also make a helper function +# (``generate_many_sro(a_vals)``) which, given an array of possible values +# for ‘:math:`a`’, will generate an array of :math:`\hat{W}(a)` associated +# with each element. We use Pytorch to construct this array as it will later +# be used as input when training our function fitting model. +# + + +import torch + + +def rotation_mat(a): + """Given a fixed value 'a', compute the signal rotation matrix W(a). + (requires -1 <= 'a' <= 1) + """ + diag = a + off_diag = (1 - a**2) ** (1 / 2) * 1j + W = [[diag, off_diag], [off_diag, diag]] + + return W + + +def generate_many_sro(a_vals): + """Given a tensor of possible 'a' vals, return a tensor of W(a)""" + w_array = [] + for a in a_vals: + w = rotation_mat(a) + w_array.append(w) + + return torch.tensor(w_array, dtype=torch.complex64, requires_grad=False) + + +###################################################################### +# Now having access to the matrix elements of the SRO, we can leverage +# PennyLane to define a quantum function that will compute the SPO. +# Recall we are measuring in the Hadamard basis to +# relax the third condition of the theorem. We +# accomplish this by sandwiching the SPO between two Hadamard gates to +# account for this change of basis. +# + + +import pennylane as qml + + +def QSP_circ(phi, W): + """This circuit applies the SPO. The components in the matrix + representation of the final unitary are polynomials! + """ + qml.Hadamard(wires=0) # set initial state |+> + for angle in phi[:-1]: + qml.RZ(angle, wires=0) + qml.QubitUnitary(W, wires=0) + + qml.RZ(phi[-1], wires=0) # final rotation + qml.Hadamard(wires=0) # change of basis |+> , |-> + return + + +###################################################################### +# Finally, we randomly generate the vector :math:`\vec{\phi}` and plot the +# expectation value :math:`\langle +|\hat{U}_{sp}|+\rangle` as a function of +# :math:`a`. In this case we choose :math:`d = 5.` +# We expect to observe the following: +# +# - Since :math:`d` is odd, we expect all of the +# polynomials we plot to have odd symmetry +# - Since :math:`d = 5,` we expect none of the polynomials will have +# terms ~ :math:`O(a^6)` or higher +# - All of the polynomials are bounded by :math:`\pm1` +# + +import math +import matplotlib.pyplot as plt + +d = 5 +a_vals = torch.linspace(-1, 1, 50) +w_mats = generate_many_sro(a_vals) + +gen = torch.Generator() +gen.manual_seed(444422) # set random seed for reproducibility + +for i in range(5): + phi = torch.rand(d + 1, generator=gen) * 2 * torch.tensor([math.pi], requires_grad=False) + matrix_func = qml.matrix(QSP_circ, wire_order=[0]) + y_vals = [matrix_func(phi, w)[0, 0].real for w in w_mats] + + plt.plot(a_vals, y_vals, label=f"poly #{i}") + +plt.vlines(0.0, -1.0, 1.0, color="black") +plt.hlines(0.0, -1.0, 1.0, color="black") +plt.legend(loc=1) +plt.show() + + +###################################################################### +# .. figure:: ../_static/demonstration_assets/function_fitting_qsp/random_poly.png +# :align: center +# :width: 50% +# + + +###################################################################### +# Exactly as predicted, all of these conditions are met! +# +# - All curves have odd symmetry +# - Qualitatively, the plots look similar to polynomials of low degree +# - Each plot does not exceed :math:`\pm1` ! +# +# Function Fitting with Quantum Signal Processing +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Another observation we can make about this theorem is the fact that it +# holds true in both directions: If we have two polynomials :math:`P(a)` +# and :math:`Q(a)` that satisfy the conditions of the theorem, then there +# exists a :math:`\vec{\phi}` for which we can construct a signal +# processing operator which maps :math:`a \to P(a).` +# +# In this section we try to answer the question: +# +# **Can we learn the parameter values of** :math:`\vec{\phi}` **to transform +# our signal processing operator polynomial to fit a given function?** +# +# In order to answer this question, we leverage the power of machine learning. +# In this demo we assume you are familiar with some concepts from quantum +# machine learning, for a refresher checkout this `blog post on QML +# `__. +# We begin by building a machine learning model using Pytorch. The ``__init__()`` +# method handles the +# random initialization of our parameter vector :math:`\vec{\phi}.` The +# ``forward()`` method takes an array of signal rotation matrices +# :math:`\hat{W}(a)` for varying :math:`a,` and produces the +# predicted :math:`y` values. +# +# Next we leverage the PennyLane function `qml.matrix() +# `__, +# which accepts our quantum function (it can also accept quantum tapes and +# QNodes) and returns its unitary matrix representation. We are interested +# in the real value of the top left entry, this corresponds to +# :math:`P(a).` +# + +torch_pi = torch.Tensor([math.pi]) + + +class QSP_Func_Fit(torch.nn.Module): + def __init__(self, degree, num_vals, random_seed=None): + """Given the degree and number of samples, this method randomly + initializes the parameter vector (randomness can be set by random_seed) + """ + super().__init__() + if random_seed is None: + self.phi = torch_pi * torch.rand(degree + 1, requires_grad=True) + + else: + gen = torch.Generator() + gen.manual_seed(random_seed) + self.phi = torch_pi * torch.rand(degree + 1, requires_grad=True, generator=gen) + + self.phi = torch.nn.Parameter(self.phi) + self.num_phi = degree + 1 + self.num_vals = num_vals + + def forward(self, omega_mats): + """PennyLane forward implementation""" + y_pred = [] + generate_qsp_mat = qml.matrix(QSP_circ, wire_order=[0]) + + for w in omega_mats: + u_qsp = generate_qsp_mat(self.phi, w) + P_a = u_qsp[0, 0] # Taking the (0,0) entry of the matrix corresponds to <0|U|0> + y_pred.append(P_a.real) + + return torch.stack(y_pred, 0) + + +###################################################################### +# Next we create a ``Model_Runner`` class to handle running the +# optimization, storing the results, and providing plotting functionality: +# + + +class Model_Runner: + def __init__(self, model, degree, num_samples, x_vals, process_x_vals, y_true): + """Given a model and a series of model specific arguments, store everything in + internal attributes. + """ + self.model = model + self.degree = degree + self.num_samples = num_samples + + self.x_vals = x_vals + self.inp = process_x_vals(x_vals) + self.y_true = y_true + + def execute( + self, random_seed=13_02_1967, max_shots=25000, verbose=True + ): # easter egg: oddly specific seed? + """Run the optimization protocol on the model using Mean Square Error as a loss + function and using stochastic gradient descent as the optimizer. + """ + model = self.model(degree=self.degree, num_vals=self.num_samples, random_seed=random_seed) + + criterion = torch.nn.MSELoss(reduction="sum") + optimizer = torch.optim.SGD(model.parameters(), lr=1e-5) + + t = 0 + loss_val = 1.0 + + while (t <= max_shots) and (loss_val > 0.5): + + self.y_pred = model(self.inp) + + if t == 1: + self.init_y_pred = self.y_pred + + # Compute and print loss + loss = criterion(self.y_pred, self.y_true) + loss_val = loss.item() + + if (t % 1000 == 0) and verbose: + print(f"---- iter: {t}, loss: {round(loss_val, 4)} -----") + + # Perform a backward pass and update weights. + optimizer.zero_grad() + loss.backward() + optimizer.step() + + t += 1 + + self.model_params = model.phi + + def plot_result(self, show=True): + """Plot the results""" + plt.plot(self.x_vals, self.y_true.tolist(), "--b", label="target func") + plt.plot(self.x_vals, self.y_pred.tolist(), ".g", label="optim params") + plt.plot(self.x_vals, self.init_y_pred.tolist(), ".r", label="init params") + plt.legend(loc=1) + + if show: + plt.show() + + +###################################################################### +# Now that we have a model, lets first attempt to fit a polynomial. We +# expect this to perform well when the target polynomial also obeys the +# symmetry and degree constraints that our quantum signal processing +# polynomial does. To do this, we defined a function ``custom_poly(x)`` +# which implements the target polynomial. In this case, we (arbitrarily) +# choose the target polynomial: +# +# .. math:: y = 4x^{5} - 5x^{3} + x +# +# Lets see how well we can fit this polynomial! +# +# +# .. note:: +# Depending on the initial parameters, training can take +# anywhere from 10 - 30 mins +# + +import numpy as np + +d = 9 # dim(phi) = d + 1, +num_samples = 50 + + +def custom_poly(x): + """A custom polynomial of degree <= d and parity d % 2""" + return torch.tensor(4 * x**5 - 5 * x**3 + x, requires_grad=False, dtype=torch.float) + + +a_vals = np.linspace(-1, 1, num_samples) +y_true = custom_poly(a_vals) + +qsp_model_runner = Model_Runner(QSP_Func_Fit, d, num_samples, a_vals, generate_many_sro, y_true) + +qsp_model_runner.execute() +qsp_model_runner.plot_result() + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# ---- iter: 0, loss: 13.5938 ----- +# ---- iter: 1000, loss: 11.8809 ----- +# ---- iter: 2000, loss: 10.229 ----- +# ---- iter: 3000, loss: 8.6693 ----- +# ---- iter: 4000, loss: 7.2557 ----- +# ---- iter: 5000, loss: 6.0084 ----- +# ---- iter: 6000, loss: 4.9197 ----- +# ---- iter: 7000, loss: 3.9801 ----- +# ---- iter: 8000, loss: 3.1857 ----- +# ---- iter: 9000, loss: 2.5312 ----- +# ---- iter: 10000, loss: 2.0045 ----- +# ---- iter: 11000, loss: 1.5873 ----- +# ---- iter: 12000, loss: 1.2594 ----- +# ---- iter: 13000, loss: 1.0021 ----- +# ---- iter: 14000, loss: 0.7997 ----- +# ---- iter: 15000, loss: 0.6397 ----- +# ---- iter: 16000, loss: 0.5127 ----- +# +# .. figure:: ../_static/demonstration_assets/function_fitting_qsp/trained_poly.png +# :align: center +# :width: 50% +# + + +###################################################################### +# We were able to fit that polynomial quite well! +# Lets try something more challenging: fitting a +# non-polynomial function. One thing to keep in mind is the symmetry and +# bounds constraints on our polynomials. If our target function does not +# satisfy them as well, then we cannot hope to generate a good polynomial +# fit, regardless of how long we train for. +# +# A good non-polynomial candidate to fit to, that obeys our constraints, +# is the step function. Let’s try it! +# + +d = 9 # dim(phi) = d + 1, +num_samples = 50 + + +def step_func(x): + """A step function (odd parity) which maps all values <= 0 to -1 + and all values > 0 to +1. + """ + res = [-1.0 if x_i <= 0 else 1.0 for x_i in x] + return torch.tensor(res, requires_grad=False, dtype=torch.float) + + +a_vals = np.linspace(-1, 1, num_samples) +y_true = step_func(a_vals) + +qsp_model_runner = Model_Runner(QSP_Func_Fit, d, num_samples, a_vals, generate_many_sro, y_true) + +qsp_model_runner.execute() +qsp_model_runner.plot_result() + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# ---- iter: 0, loss: 33.8345 ----- +# ---- iter: 1000, loss: 19.0937 ----- +# ---- iter: 2000, loss: 11.6557 ----- +# ---- iter: 3000, loss: 8.2853 ----- +# ---- iter: 4000, loss: 6.6824 ----- +# ---- iter: 5000, loss: 5.8523 ----- +# ---- iter: 6000, loss: 5.3855 ----- +# ---- iter: 7000, loss: 5.1036 ----- +# ---- iter: 8000, loss: 4.9227 ----- +# ---- iter: 9000, loss: 4.8004 ----- +# ---- iter: 10000, loss: 4.7138 ----- +# ---- iter: 11000, loss: 4.6502 ----- +# ---- iter: 12000, loss: 4.6018 ----- +# ---- iter: 13000, loss: 4.5638 ----- +# ---- iter: 14000, loss: 4.5333 ----- +# ---- iter: 15000, loss: 4.5082 ----- +# ---- iter: 16000, loss: 4.4872 ----- +# ---- iter: 17000, loss: 4.4693 ----- +# ---- iter: 18000, loss: 4.4537 ----- +# ---- iter: 19000, loss: 4.4401 ----- +# ---- iter: 20000, loss: 4.4281 ----- +# ---- iter: 21000, loss: 4.4174 ----- +# ---- iter: 22000, loss: 4.4078 ----- +# ---- iter: 23000, loss: 4.3991 ----- +# ---- iter: 24000, loss: 4.3912 ----- +# ---- iter: 25000, loss: 4.3839 ----- +# +# .. figure:: ../_static/demonstration_assets/function_fitting_qsp/trained_step.png +# :align: center +# :width: 50% +# + + +###################################################################### +# Conclusion +# ~~~~~~~~~~~~~~ +# +# In this demo, we explored the Quantum Signal Processing theorem, which +# is a method to perform polynomial transformations on the entries of the +# SRO :math:`\hat{W}(a).` This polynomial transformation +# arises from the repeated application of :math:`\hat{W}(a)` and the +# parameterized Z-axis rotations :math:`e^{i \phi \hat{Z}}.` Note, the +# SRO is itself a transformation, in this case a rotation around the +# X-axis by :math:`\theta = -2 \cos^{-1}(a),` which rotates our basis. +# Thus the underlying principal of quantum signal processing is that we +# can generate polynomial transformations through parameterized rotations +# along a principal axis followed by change of basis transformations +# which re-orients this axis. +# +# This is the same principal at the heart of QSVT. In this case the subspace +# in which we apply our parameterized rotations is defined by the singular +# vectors, the change of basis transformation takes us between these +# subspaces and this allows us to apply polynomial transformations on the +# singular values of our matrix of interest. +# +# We also showed that one could use a simple gradient descent model to +# train a parameter vector :math:`\vec{\phi}` to generate reasonably good +# polynomial approximations of arbitrary functions (provided the function +# satisfied the same constraints). This isn't the only way to compute the +# optimal values. It turns out there exist *efficient* algorithms for +# explicitly computing the optimal values for :math:`\vec{\phi}` +# known as "Remez-type exchange algorithms" for analytic function fitting. If +# you want to explore other approaches to function fitting, checkout this +# `demo `__ +# where we use a photonic neural network for function fitting. +# +# + +###################################################################### +# .. figure:: ../_static/demonstration_assets/function_fitting_qsp/trained_step.gif +# :align: center +# :width: 50% +# + +###################################################################### +# References +# ~~~~~~~~~~~~~~ +# +# [1]: *John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang. “A +# Grand Unification of Quantum Algorithms”* `PRX Quantum 2, +# 040203 `__\ *, 2021.* +# +# diff --git a/demonstrations_v2/function_fitting_qsp/metadata.json b/demonstrations_v2/function_fitting_qsp/metadata.json new file mode 100644 index 0000000000..1c2e5c2863 --- /dev/null +++ b/demonstrations_v2/function_fitting_qsp/metadata.json @@ -0,0 +1,38 @@ +{ + "title": "Function Fitting using Quantum Signal Processing", + "authors": [ + { + "username": "Jay" + } + ], + "dateOfPublication": "2022-05-24T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_function_fitting_quantum_signal_processing.png" + } + ], + "seoDescription": "Learn how to create polynomial approximations to functions using Quantum Signal Processing (QSP).", + "doi": "", + "references": [ + { + "id": "Martyn2021", + "type": "article", + "title": "A Grand Unification of Quantum Algorithms", + "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang", + "year": "2021", + "journal": "PRX Quantum 2, 040203", + "doi": "10.48550/arXiv.2105.02859" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2105.02859" + ], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/function_fitting_qsp/requirements.in b/demonstrations_v2/function_fitting_qsp/requirements.in new file mode 100644 index 0000000000..d8d1b51485 --- /dev/null +++ b/demonstrations_v2/function_fitting_qsp/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +torch diff --git a/demonstrations_v2/gbs/demo.py b/demonstrations_v2/gbs/demo.py new file mode 100644 index 0000000000..1da37f6ed5 --- /dev/null +++ b/demonstrations_v2/gbs/demo.py @@ -0,0 +1,478 @@ +r""" +.. role:: html(raw) + :format: html + +Quantum advantage with Gaussian Boson Sampling +============================================== + +.. meta:: + :property="og:description": Using light to perform tasks beyond the reach of classical computers. + + :property="og:image": https://pennylane.ai/qml/_static/demo_thumbnails/opengraph_demo_thumbnails/gbs_expt2.png + +.. related:: + + tutorial_gaussian_transformation Gaussian transformation + qsim_beyond_classical Beyond classical computing with qsim + qonn Optimizing a quantum optical neural network + tutorial_photonics Photonic quantum computers + +*Authors: Josh Izaac and Nathan Killoran — Posted: 04 December 2020. Last updated: 04 December 2020.* + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +On the journey to large-scale fault-tolerant quantum computers, one of the first major +milestones is to demonstrate a quantum device carrying out tasks that are beyond the reach of +any classical algorithm. The launch of Xanadu's Borealis device marked an important milestone +within the quantum computing community, wherein our very own quantum computational advantage +experiment using quantum photonics was demonstrated in our `Nature paper `__. +Among other quantum advantage achievements are the Google Quantum team's experiment as can be seen in their paper +`Quantum supremacy using a programmable superconducting processor `__ [#Arute2019]_, +and the experiment from the team led by Chao-Yang Lu and Jian-Wei as can be seen in their paper +`Quantum computational advantage using photons `__ +[#Zhong2020]_. + +While Google's experiment performed the task of :doc:`random circuit sampling ` +using a superconducting processor, both Chao-Yang Lu and Jian-Wei's team and Xanadu leveraged the +quantum properties of light to tackle a task called +`Gaussian Boson Sampling `__ (GBS). + +This tutorial will walk you through the basic elements of GBS, motivate why it is +classically challenging, and show you how to explore GBS using PennyLane and the photonic +quantum devices accessible via the +`PennyLane-Strawberry Fields plugin `__. If you are +interested in possible applications of GBS, or want to access programmable GBS hardware +via the cloud, check out the +`Strawberry Fields website `__ for more details. + +| + +.. image:: /_static/demonstration_assets/gbs/gbs_expt2.png + :align: center + :width: 80% + :target: javascript:void(0); + +.. figure:: /_static/demonstration_assets/gbs/gbs_expt1.png + :align: center + :width: 80% + :target: javascript:void(0); + + *Illustration of the experimental setup used by Zhong et al. in Quantum computational advantage + using photons* [#Zhong2020]_. + +The origins of GBS +------------------ + +Let's first explain the name. `Boson `__ refers to bosonic +matter, which, along with fermions, makes up one of the two elementary classes of particles. +The most prevalent bosonic system in our everyday lives is light, which is made of particles +called photons. Another famous example, though much harder to find, is the Higgs boson. +The distinguishing characteristic of bosons is that they follow "Bose-Einstein statistics", +which very loosely means that the particles like to bunch together (contrast this to fermionic +matter like electrons, which must follow the Pauli Exclusion Principle and keep apart). + +This property can be observed in simple interference experiments such as the +`Hong-Ou Mandel setup `__. +If two single photons are interfered on a balanced beamsplitter, they will both emerge at +the same output port—there is zero probability that they will emerge at separate outputs. +This is a simple but notable quantum property of light; if electrons were brought +together in a similar experiement, they would always appear at separate output ports. + +Gaussian Boson Sampling [#hamilton2017]_ is, in fact, a member of a larger family of +"Boson Sampling" algorithms, +stemming back to the initial proposal of Aaronson and Arkhipov [#aaronson2013]_ in 2013. +Boson Sampling is quantum interferometry writ large. Aaronson and Arkhipov's original proposal +was to inject many single photons into distinct input ports of a large interferometer, then +measure which output ports they appear at. The natural interference properties of bosons +means that photons will appear at the output ports in very unique and specific ways. Boson +Sampling was not proposed with any kind of practical real-world use-case in mind. Like +the random circuit sampling, it's just a quantum system being its best self. With sufficient +size and quality, it is strongly believed to be hard for a classical computer to simulate this efficiently. + +Finally, the "Gaussian" in GBS refers to the fact that we modify the original Boson Sampling +proposal slightly: instead of injecting single photons—which are hard to jointly create in the +size and quality needed to demonstrate Boson Sampling conclusively—we instead use states of +light that are experimentally less demanding (though still challenging!). +These states of light are called Gaussian states, +because they bear strong connections to the +`Gaussian (or Normal) distribution `__ +from statistics. In practice, we use a particular Gaussian state called a +`squeezed state `__ for the inputs, +since these are arguably the most non-classical of Gaussian states. + + +.. note:: While computationally hard to simulate, Boson Sampling devices, on their own, + are not capable of universal quantum computing. However, in combination with other + components, GBS is a key building block for a + universal device [#Bourassa2020]_. + + +Coding a GBS algorithm +---------------------- + +The researchers in [#Zhong2020]_ experimentally demonstrate a GBS device by preparing 50 +squeezed states and injecting them into a 100-mode interferometer. In this demo, +in order to keep things classically simulable, we will stick to a much simpler setting +consisting of 4 squeezed states injected into a 4-mode interferometer. At a high level, +an interferometer on :math:`N` modes can be represented using an :math:`N\times N` unitary +matrix :math:`U.` When decomposed into a quantum optical circuit, the interferometer will +be made up of beamsplitters and phase shifters. + +.. image:: /_static/demonstration_assets/gbs/gbs_circuit2.png + :align: center + :width: 90% + :target: javascript:void(0); + +.. raw:: html + +
+ +Simulating this circuit using PennyLane is easy; we can simply read off the gates from left +to right, and convert it into a QNode. +""" + +import numpy as np + +# set the random seed +np.random.seed(42) + +# import PennyLane +import pennylane as qml + +###################################################################### +# We must define the unitary matrix we would like to embed in the circuit. +# We will use SciPy to generate a Haar-random unitary: + +from scipy.stats import unitary_group + +# define the linear interferometer +U = unitary_group.rvs(4) +print(U) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.23648826-0.48221431j 0.06829648+0.04447898j 0.51150074-0.09529866j +# 0.55205719-0.35974699j] +# [-0.11148167+0.69780321j -0.24943828+0.08410701j 0.46705929-0.43192981j +# 0.16220654-0.01817602j] +# [-0.22351926-0.25918352j 0.24364996-0.05375623j -0.09259829-0.53810588j +# 0.27267708+0.66941977j] +# [ 0.11519953-0.28596729j -0.90164923-0.22099186j -0.09627758-0.13105595j +# -0.0200152 +0.12766128j]] +# +# We can now use this to construct the circuit, choosing a compatible +# device. For the simulation, we can use the Strawberry Fields +# Gaussian backend. This backend is perfectly suited for simulation of GBS, +# as the initial states are Gaussian, and all gates transform Gaussian states to other +# Gaussian states. + +n_wires = 4 +cutoff = 10 + +dev = qml.device("strawberryfields.gaussian", wires=n_wires, cutoff_dim=cutoff) + + +@qml.qnode(dev) +def gbs_circuit(): + # prepare the input squeezed states + for i in range(n_wires): + qml.Squeezing(1.0, 0.0, wires=i) + + # linear interferometer + qml.InterferometerUnitary(U, wires=range(n_wires)) + return qml.probs(wires=range(n_wires)) + + +###################################################################### +# A couple of things to note in this particular example: +# +# 1. To prepare the input single mode squeezed vacuum state :math:`|re^{i\phi}\rangle,` +# where :math:`r = 1` and :math:`\phi=0,` we +# apply a squeezing gate (:class:`~pennylane.Squeezing`) to each of the wires (initially in +# the vacuum state). +# +# 2. Next we apply the linear interferometer to all four wires using +# :class:`~pennylane.Interferometer` and the unitary matrix ``U``. This operator +# decomposes the unitary matrix representing the linear interferometer into single-mode +# rotation gates (:class:`~pennylane.PhaseShift`) and two-mode beamsplitters +# (:class:`~pennylane.Beamsplitter`). After applying the interferometer, we will denote the +# output state by :math:`|\psi'\rangle.` +# +# 3. GBS takes place physically in an infinite-dimensional Hilbert space, +# which is not practical for simulation. We need to set an upper limit on the maximum +# number of photons we can detect. This is the +# ``cutoff`` value we defined above; we will only be considering detection events +# containing 0 to 9 photons per mode. +# +# We can now execute the QNode, and extract the resulting probability distribution: + +probs = gbs_circuit().reshape([cutoff] * n_wires) +print(probs.shape) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# (10, 10, 10, 10) +# +###################################################################### +# For example, element ``[1,2,0,1]`` represents the probability of +# detecting 1 photon on wire +# ``0`` and wire ``3``, and 2 photons at wire ``1``, i.e., the value +# +# .. math:: \text{prob}(1,2,0,1) = \left|\langle{1,2,0,1} \mid \psi'\rangle \right|^2. +# +# Let's extract and view the probabilities of measuring various Fock states. + +# Fock states to measure at output +measure_states = [(0, 0, 0, 0), (1, 1, 0, 0), (0, 1, 0, 1), (1, 1, 1, 1), (2, 0, 0, 0)] + +# extract the probabilities of calculating several +# different Fock states at the output, and print them out +for i in measure_states: + print(f"|{''.join(str(j) for j in i)}>: {probs[i]}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# |0000>: 0.17637844761413496 +# |1100>: 0.03473293649420282 +# |0101>: 0.011870900427255589 +# |1111>: 0.005957399165336106 +# |2000>: 0.02957384308320549 +# +###################################################################### +# The GBS Distribution +# -------------------- +# +# Hamilton et al. [#hamilton2017]_ showed that the probability of +# measuring a final state containing only 0 or 1 photons per mode is given by +# +# .. math:: +# +# \left|\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\rangle\right|^2 = +# \frac{\left|\text{Haf}[(U(\bigoplus_i\mathrm{tanh}(r_i))U^T)]_{st}\right|^2}{\prod_{i=1}^N \cosh(r_i)} +# +# i.e., the sampled single-photon probability distribution is proportional to the **hafnian** of a +# submatrix of :math:`U(\bigoplus_i\mathrm{tanh}(r_i))U^T.` +# +# .. note:: +# +# The hafnian of a matrix is defined by +# +# .. math:: \text{Haf}(A) = \sum_{\sigma \in \text{PMP}_{2N}}\prod_{i=1}^N A_{\sigma(2i-1)\sigma(2i)}, +# +# where :math:`\text{PMP}_{2N}` is the set of all perfect matching permutations of :math:`2N` elements. In graph theory, the +# hafnian calculates the number of perfect `matchings +# `_ in a graph with +# adjacency matrix :math:`A.` +# +# Compare this to the permanent, which calculates the number of perfect matchings on a *bipartite* +# graph. Notably, the permanent appears in vanilla Boson Sampling in a similar way +# that the hafnian appears in GBS. +# The hafnian turns out to be a generalization of the permanent, with the relationship +# +# .. math:: +# +# \text{Per(A)} = \text{Haf}\left(\left[\begin{matrix} +# 0&A\\ A^T&0 +# \end{matrix}\right]\right). +# +# As any algorithm that could calculate (or even approximate) the hafnian could also calculate the +# permanent—a `#P-hard problem `__---it follows that +# calculating or approximating the hafnian must also be a classically hard problem. This lies behind +# the classical hardness of GBS. +# +# In this demo, we will use the same squeezing parameter, :math:`z=r,` for +# all input states; this allows us to simplify this equation. To start with, the hafnian expression +# simply becomes :math:`\text{Haf}[(UU^T\mathrm{tanh}(r))]_{st},` removing the need for the direct sum. +# +# Thus, we have +# +# .. math:: +# +# \left|\left\langle{n_1,n_2,\dots,n_N}\middle|{\psi'}\right\rangle\right|^2 = +# \frac{\left|\text{Haf}[(UU^T\tanh(r))]_{st}\right|^2}{n_1!n_2!\cdots n_N!\cosh^N(r)}. +# +# Now that we have the theoretical formulas, as well as the probabilities from our simulated GBS +# QNode, we can compare the two and see whether they agree. +# +# In order to calculate the probability of different GBS events classically, we need a +# method for calculating the hafnian. +# For this, we will use `The Walrus +# `_ library (which is installed as a dependency of the +# PennyLane-SF plugin): + +from thewalrus import hafnian as haf + +###################################################################### +# Now, for the right-hand side numerator, we first calculate the submatrix +# :math:`A = [(UU^T\mathrm{tanh}(r))]_{st}:` + +A = np.dot(U, U.T) * np.tanh(1) + +###################################################################### +# In GBS, we determine the submatrix by taking the +# rows and columns corresponding to the measured Fock state. For example, to calculate the submatrix +# in the case of the output measurement :math:`\left|{1,1,0,0}\right\rangle,` +# we have + +print(A[:, [0, 1]][[0, 1]]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 0.19343159-0.54582922j 0.43418269-0.09169615j] +# [ 0.43418269-0.09169615j -0.27554025-0.46222197j]] +# +###################################################################### +# i.e., we consider only the rows and columns where a photon was detected, which gives us +# the submatrix corresponding to indices :math:`0` and :math:`1.` + +###################################################################### +# Comparing to simulation +# ----------------------- +# +# Now that we have a method for calculating the hafnian, let's compare the output to that provided by +# the PennyLane QNode. +# +# **Measuring** :math:`|0,0,0,0\rangle` **at the output** +# +# This corresponds to the hafnian of an *empty* matrix, which is simply 1: + +print(1 / np.cosh(1) ** 4) +print(probs[0, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.1763784476141347 +# 0.17637844761413496 +# +###################################################################### +# **Measuring** :math:`|1,1,0,0\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 1]][[0, 1]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.03473293649420271 +# 0.03473293649420282 +# +###################################################################### +# **Measuring** :math:`|0,1,0,1\rangle` **at the output** + +A = (np.dot(U, U.T) * np.tanh(1))[:, [1, 3]][[1, 3]] +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[0, 1, 0, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.011870900427255558 +# 0.011870900427255589 +# +###################################################################### +# **Measuring** :math:`|1,1,1,1\rangle` **at the output** +# +# This corresponds to the hafnian of the full matrix :math:`A=UU^T\mathrm{tanh}(r):` + +A = np.dot(U, U.T) * np.tanh(1) +print(np.abs(haf(A)) ** 2 / np.cosh(1) ** 4) +print(probs[1, 1, 1, 1]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.005957399165336081 +# 0.005957399165336106 +# +###################################################################### +# **Measuring** :math:`|2,0,0,0\rangle` **at the output** +# +# Since we have two photons in mode ``q[0]``, we take two copies of the +# first row and first column, making sure to divide by :math:`2!:` + +A = (np.dot(U, U.T) * np.tanh(1))[:, [0, 0]][[0, 0]] +print(np.abs(haf(A)) ** 2 / (2 * np.cosh(1) ** 4)) +print(probs[2, 0, 0, 0]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.029573843083205383 +# 0.02957384308320549 +# +# The PennyLane simulation results agree (with almost negligible numerical error) to the +# expected result from the Gaussian boson sampling equation! +# +# This demo provides an entry-level walkthrough to the ideas behind GBS, +# providing you with the basic code needed for exploring the ideas behind +# the photonic quantum advantage paper. Try changing the number of modes, +# the number of injected squeezed states, or the cutoff dimension, and +# see how each of these affect the classical computation time. If you're +# interested in learning more about GBS, or about photonic quantum +# computing in general, the +# `Strawberry Fields website `__ is a great resource. +# +# References +# ---------- +# +# .. [#Arute2019] +# +# Arute, F., Arya, K., Babbush, R., et al. "Quantum supremacy using a programmable +# superconducting processor" +# `Nature 574, 505-510 (2019) `__. +# +# .. [#Zhong2020] +# +# Zhong, H.-S., Wang, H., Deng, Y.-H., et al. (2020). Quantum computational advantage using photons. Science, 10.1126/science.abe8770. +# +# .. [#hamilton2017] +# +# Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, +# and Igor Jex. Gaussian boson sampling. Physical Review Letters, 119:170501, Oct 2017. +# arXiv:1612.01199, doi:10.1103/PhysRevLett.119.170501. +# +# .. [#aaronson2013] +# +# Scott Aaronson and Alex Arkhipov. The computational complexity of linear optics. Theory of +# Computing, 9(1):143–252, 2013. doi:10.4086/toc.2013.v009a004. +# +# .. [#Bourassa2020] +# +# Bourassa, J. E., Alexander, R. N., Vasmer, et al. (2020). Blueprint for a scalable +# photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. +# +# diff --git a/demonstrations_v2/gbs/metadata.json b/demonstrations_v2/gbs/metadata.json new file mode 100644 index 0000000000..39f5110ae9 --- /dev/null +++ b/demonstrations_v2/gbs/metadata.json @@ -0,0 +1,100 @@ +{ + "title": "Quantum advantage with Gaussian Boson Sampling", + "authors": [ + { + "username": "josh" + }, + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-12-04T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_advantage_guassian_boson_sampling.png" + } + ], + "seoDescription": "Using light to perform tasks beyond the reach of classical computers.", + "doi": "", + "references": [ + { + "id": "Arute2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Arute, F., Arya, K., Babbush, R., et al.", + "year": "2019", + "journal": "Nature", + "url": "https://doi.org/10.1038/s41586-019-1666-5" + }, + { + "id": "Zhong2020", + "type": "article", + "title": "Quantum computational advantage using photons", + "authors": "Zhong, H.-S., Wang, H., Deng, Y.-H., et al.", + "year": "2020", + "journal": "Science", + "doi": "10.1126/science.abe8770", + "url": "" + }, + { + "id": "hamilton2017", + "type": "article", + "title": "Gaussian boson sampling", + "authors": "Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, and Igor Jex", + "year": "2017", + "journal": "Physical Review Letters", + "doi": "10.1103/PhysRevLett.119.170501", + "url": "" + }, + { + "id": "aaronson2013", + "type": "article", + "title": "The computational complexity of linear optics", + "authors": "Scott Aaronson and Alex Arkhipov", + "year": "2013", + "journal": "Theory of Computing", + "doi": "10.4086/toc.2013.v009a004", + "url": "" + }, + { + "id": "Bourassa2020", + "type": "article", + "title": "Blueprint for a scalable photonic fault-tolerant quantum computer", + "authors": "Bourassa, J. E., Alexander, R. N., Vasmer, et al.", + "year": "2020", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_gaussian_transformation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qsim_beyond_classical", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qonn", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/gbs/requirements.in b/demonstrations_v2/gbs/requirements.in new file mode 100644 index 0000000000..3df1b86098 --- /dev/null +++ b/demonstrations_v2/gbs/requirements.in @@ -0,0 +1,4 @@ +numpy +pennylane +scipy +thewalrus diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py b/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py new file mode 100644 index 0000000000..5777735d78 --- /dev/null +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py @@ -0,0 +1,404 @@ +r""" + +Getting started with Amazon Braket Hybrid Jobs +======================================= + + +This tutorial provides an introduction to running hybrid quantum-classical algorithms using +PennyLane on `Amazon Braket `__. With Amazon Braket, you gain access to both real quantum devices and +scalable classical compute, enabling you to push the boundaries of your algorithm. + +In this tutorial, we'll walk through how to create your first hybrid quantum-classical algorithms on AWS. +With a single-line-of-code, we'll see how to scale from PennyLane simulators on your laptop to running full-scale experiments on AWS that leverage both powerful classical compute and quantum devices. +You'll gain understanding of the hybrid jobs queue, including QPU priority queuing, and learn how to scale classical resources for resource-intensive tasks. +We hope these tools will empower you to start experimenting today with hybrid quantum algorithms! + +.. figure:: /_static/demonstration_assets/getting_started_with_hybrid_jobs/socialthumbnail_large_getting_started_with_hybrid_jobs.png + :align: center + :width: 65% + :target: javascript:void(0); + + +Amazon Braket Hybrid Jobs +------------------------- + +Amazon Braket Hybrid Jobs offers a way for you to run hybrid quantum-classical algorithms that +require both classical resources and quantum processing units (QPUs). Hybrid Jobs is designed to +spin up the requested classical compute, run your algorithm, and release the instances after +completion so you only pay for what you use. This workflow is ideal for long-running iterative +algorithms involving both classical and quantum resources. Simply package up your code into a single +function, create a hybrid job with a single line of code, and Braket will schedule it to run as soon +as possible without interruption. + +Hybrid jobs have a separate queue from quantum tasks, so once your algorithm starts running, it will +not be interrupted by variations in the quantum task queue. This helps your long-running algorithms +run efficiently and predictably. Any quantum tasks created from a running hybrid job will be run +before any other quantum tasks in the queue. This is particularly beneficial for iterative hybrid +algorithms where subsequent tasks depend on the outcomes of prior quantum tasks. Examples of such +algorithms include the Quantum Approximate Optimization Algorithm (QAOA), Variational Quantum +Eigensolver (VQE), or Quantum Machine Learning (QML). You can also monitor your algorithm's progress in near-real +time, enabling you to keep track of costs, budget, or custom metrics such as training loss or +expectation values. + +Importantly, on specific QPUs, running your algorithm in Hybrid Jobs benefits from `parametric compilation `__. +This reduces the overhead associated with the computationally expensive compilation step by compiling a circuit only once and not for every iteration in your hybrid algorithm. +This dramatically reduces the total runtime for many variational algorithms. +For long-running hybrid jobs, Braket automatically uses the updated calibration data from the hardware provider when compiling your circuit to ensure the highest quality results. + +Getting started with PennyLane +------------------------------ + +Let’s setup an algorithm that makes use of both classical and quantum resources. We adapt the :doc:`PennyLane qubit rotation tutorial`. + +.. warning:: + + The following demo is only compatible with Python version 3.10. + +""" + +###################################################################### +# First, we define a quantum simulator to run the algorithm on. In this example, we will use the Braket +# local simulator before moving onto a QPU. +# + +import pennylane as qml +from pennylane import numpy as np + + +device = qml.device("braket.local.qubit", wires=1) + +###################################################################### +# Now we define a circuit with two rotation gates and measure the expectation value in the +# :math:`Z`-basis. +# + + +@qml.qnode(device) +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=0) + return qml.expval(qml.PauliZ(0)) + + +###################################################################### +# Finally, we create a classical-quantum loop that uses gradient descent to minimize the expectation value. +# +# We add the ``log_metric`` function from Braket to record the training progress (see `metrics +# documentation `__). +# When running on AWS, ``log_metric`` records the metrics in `Amazon CloudWatch `__, which is accessible +# through the Braket console page or the Braket SDK. When running locally on your laptop, +# ``log_metric`` prints the iteration numbers. +# + +from braket.jobs.metrics import log_metric + + +def qubit_rotation(num_steps=10, stepsize=0.5): + opt = qml.GradientDescentOptimizer(stepsize=stepsize) + params = np.array([0.5, 0.75]) + + for i in range(num_steps): + # update the circuit parameters + params = opt.step(circuit, params) + expval = circuit(params) + + log_metric(metric_name="expval", iteration_number=i, value=expval) + + return params + + +###################################################################### +# To run the entire algorithm, we call the qubit rotation function to see that it runs correctly. +# + +qubit_rotation(5, stepsize=0.5) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Metrics - timestamp=1695043649.822114; expval=0.38894534132396147; iteration_number=0; +# Metrics - timestamp=1695043649.8757634; expval=0.12290715413453956; iteration_number=1; +# Metrics - timestamp=1695043649.9254549; expval=-0.09181374013482171; iteration_number=2; +# Metrics - timestamp=1695043649.973501; expval=-0.2936094099948542; iteration_number=3; +# Metrics - timestamp=1695043650.020348; expval=-0.5344079938678078; iteration_number=4; +# [0.6767967215302757, 2.3260934173312653] + +###################################################################### +# Great! We see the expectation value change with each iteration number and the final parameters were +# returned as a list. Now, instead of running on our laptop, let’s submit this same function to be run +# on AWS cloud. +# + +###################################################################### +# Running as a hybrid job +# ----------------------- +# +# To run our algorithm for a long time, we can run it asynchronously with Amazon Braket Hybrid Jobs, +# which fully manages the classical infrastructure so you can focus on the algorithm. For example, you +# can train a larger circuit, or increase the number of iterations. Note that each hybrid job has +# at least a one minute startup time since it creates a containerized environment on Amazon EC2. So +# for very short workloads, such as a single circuit or a batch of circuits, it may suffice for you to +# use quantum tasks. +# +# We now show how you can go from running your local Python function to running it as a hybrid job. +# Note that only Python 3.10 is supported by default. For custom environments, you can opt to hybrid +# job scripts, or a custom container from Amazon Elastic Container Registry (ECR) (see `containers +# documentation `__). +# +# The first step to creating a hybrid job is to annotate which function you want to run with the +# ``@hybrid_job`` decorator. Then you create the job by invoking the function as you would for normal +# Python functions. However, the decorated function returns the hybrid job handle rather than the +# result of the function. To retrieve the results after it has been completed, use ``job.result()``. +# +# The required device argument in the ``@hybrid_job`` decorator specifies the QPU that the hybrid job +# will have priority access to. +# The device string you give is accessible in the hybrid job instance as the environment variable ``AMZN_BRAKET_DEVICE_ARN``. +# +# When using on-demand simulators or `embedded simulators `__, +# you may provide the device argument as string of the form: ``"local:/"`` or simply ``None``. +# For example, you may set ``"local:pennylane/lightning.qubit"`` for the `PennyLane lightning simulator `__. +# +# In the following code, we annotate the ``qubit_rotation`` function from above. It is redefined inside the +# `hybrid_job` context as the job only has access to local variables and functions. +# + +from braket.jobs import hybrid_job + + +@hybrid_job(device="local:pennylane/lightning.qubit") +def qubit_rotation_hybrid_job(num_steps=1, stepsize=0.5): + device = qml.device("lightning.qubit", wires=1) + + @qml.qnode(device) + def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=0) + return qml.expval(qml.PauliZ(0)) + + opt = qml.GradientDescentOptimizer(stepsize=stepsize) + params = np.array([0.5, 0.75]) + + for i in range(num_steps): + # update the circuit parameters + params = opt.step(circuit, params) + expval = circuit(params) + + log_metric(metric_name="expval", iteration_number=i, value=expval) + + return params + + +###################################################################### +# Now we create a hybrid job by calling the function as usual. This returns an ``AwsQuantumJob`` object +# that contains the device ARN, region, and job name. +# + +job = qubit_rotation_hybrid_job(num_steps=10, stepsize=0.5) +print(job) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# AwsQuantumJob('arn':'arn:aws:braket:::job/qubit-rotation-hybrid-job-1695044583') + +###################################################################### +# The hybrid job automatically captures the function arguments as hyperparameters. +# In this case, we set ``num_steps = 10`` and ``stepsize = 0.5`` as the hyperparameters. +# +# We can check the status with: +# + +job.state() + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# 'QUEUED' + +###################################################################### +# Once the hybrid job starts, it will change the status to ``RUNNING``. We can also check the hybrid +# job status in the Braket console. +# +# After the hybrid job completes, we can get the results with ``job.result()``. For this example, it +# should take approximately 2 minutes. +# + +job.result() + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# {'result': [0.036420360224358496, 3.1008192940506736]} + +###################################################################### +# Any objects in the return statement will be returned in the hybrid job results. Note that the objects +# returned by the function must be a tuple with each element being serializable. +# serializable. +# +# Additionally, we can plot the metrics recorded during the algorithm. Below we show the expectation +# value decreases with each iteration as expected. +# + +import pandas as pd +import matplotlib.pyplot as plt +from braket.jobs.metrics_data.definitions import MetricType + +df = pd.DataFrame(job.metrics(metric_type=MetricType.ITERATION_NUMBER)) +df.sort_values(by=["iteration_number"], inplace=True) + +plt.plot(df["iteration_number"], df["expval"], "-o", color="orange") +plt.xlabel("iteration number") +plt.ylabel("expectation value") +plt.title("Simulator results") + +plt.show() + +###################################################################### +# +# .. figure:: /_static/demonstration_assets/getting_started_with_hybrid_jobs/simulator.png +# :align: center +# :width: 75% +# :alt: Expectation value per iteration number on QPU. +# :target: javascript:void(0); + + +###################################################################### +# Running on a QPU with priority +# ---------------- +# +# The next step is to see how well the qubit rotation works on a real QPU. We +# create a hybrid job with the Rigetti device as the priority QPU. We also increase the number of +# steps to 10. +# +# Using hybrid jobs for iterative algorithms is very beneficial because you retain priority access to the +# target QPU. So once your quantum tasks are created in the hybrid job, they run ahead of other tasks +# waiting in the *quantum task queue*. This means your +# algorithm will not be interrupted by other quantum tasks, so it will run more efficiently and +# predictably. Quantum tasks submitted as part of a hybrid job have priority, and are aggregated in the *priority task queue*. +# +# Hybrid jobs have their own *hybrid jobs queue* so that only a single +# hybrid job can run on a QPU at a time. +# Each QPU has its own hybrid jobs queue. Note that this is a different queue from the quantum tasks. For a single quantum circuit, or a batch of circuits, it’s +# recommended to create quantum tasks instead of hybrid jobs. +# For more information on quantum tasks and hybrid jobs queue see the `Amazon Braket documentation `__. +# +# To get QPU priority, you must ensure that the device ARN used within the function matches that +# specified in the decorator. For convenience, you can use the helper function ``get_device_arn()`` to +# automatically capture the device ARN declared in ``@hybrid_job``. +# +# .. note:: +# Only hybrid jobs running on AWS receive priority. Hybrid jobs running `locally `__, +# or with a mismatched device ARN do not get priority task queueing. +# +# In the previous example, we declared the local simulator outside the decorated function scope. +# However, for AWS devices such as QPUs or on-demand simulators, the device must be declared within the function scope. +# +# .. note:: +# AWS devices must be declared within the body of the decorated function. +# + +from braket.devices import Devices + +device_arn = Devices.Rigetti.AspenM3 + + +@hybrid_job(device=device_arn) # set priority QPU +def qpu_qubit_rotation_hybrid_job(num_steps=10, stepsize=0.5): + # AWS devices must be declared within the decorated function. + device = qml.device( + "braket.aws.qubit", + device_arn=device_arn.value, # Make sure the device ARN matches the hybrid job device ARN + wires=2, + shots=1_000, + ) + + @qml.qnode(device) + def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=0) + return qml.expval(qml.PauliZ(0)) + + opt = qml.GradientDescentOptimizer(stepsize=stepsize) + params = np.array([0.5, 0.75]) + + for i in range(num_steps): + # update the circuit parameters + params = opt.step(circuit, params) + expval = circuit(params) + + log_metric(metric_name="expval", iteration_number=i, value=expval) + + return params + + +###################################################################### +# To get a sense of how long we will wait before the hybrid job runs, we can check the hybrid job +# queue depth with ``AwsDevice(device_arn).queue_depth().jobs``. We can also check if the device is +# currently available with ``AwsDevice(device_arn).is_available``. +# +# When there are no other hybrid jobs in the queue ahead of you, and the device is available, the +# hybrid job will start running. +# +# .. warning:: +# +# Running the following cell will only run once the QPU is available. This may take a long +# time and will result in usage fees charged to your AWS account. Only run the cell if you +# are comfortable with the potential wait-time and costs. We recommend monitoring the Billing & +# Cost Management Dashboard on the AWS console. +# + +qpu_job = qpu_qubit_rotation_hybrid_job(num_steps=10, stepsize=0.5) +print(qpu_job) +qpu_job.result() + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# AwsQuantumJob('arn':'arn:aws:braket:::job/qpu-qubit-rotation-hybrid-job-1695044576') +# {'result': [0.034321330234338991, 3.058282990501767]} + + +###################################################################### +# Next, we plot the expectation value per iteration number below. We see that on a real QPU, the data +# is not as smooth as the simulator, but the minimum still is detected correctly! +# + +df = pd.DataFrame(qpu_job.metrics(metric_type=MetricType.ITERATION_NUMBER)) +df.sort_values(by=["iteration_number"], inplace=True) + +plt.plot(df["iteration_number"], df["expval"], "-o", color="teal") +plt.xlabel("iteration number") +plt.ylabel("expectation value") +plt.title("QPU results") +plt.show() + +###################################################################### +# +# .. figure:: /_static/demonstration_assets/getting_started_with_hybrid_jobs/qpu.png +# :align: center +# :width: 75% +# :alt: Expectation value per iteration number on QPU. +# :target: javascript:void(0); + + +###################################################################### +# Conclusion +# ------------ +# +# In this tutorial, we showed how to migrate from local Python functions to running algorithms on simulators and QPUs on Amazon Braket. +# We adapted the simple example of rotating a qubit using gradient descent, running this on both a local simulator and a real QPU. +# Using Amazon Braket Hybrid Jobs allowed us to run algorithms asynchronously, scale classical compute using AWS, and obtain priority access to the selected QPU for the duration of our algorithm. +# + + +############################################################################## diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json b/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json new file mode 100644 index 0000000000..faa27af773 --- /dev/null +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json @@ -0,0 +1,37 @@ +{ + "title": "Getting started with the Amazon Braket Hybrid Jobs", + "authors": [ + { + "username": "mbeach" + } + ], + "dateOfPublication": "2023-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/getting_started_with_hybrid_jobs/thumbnail_getting_started_with_hybrid_jobs.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_getting_started_with_hybrid_jobs.png" + } + ], + "seoDescription": "Getting started with the Amazon Braket Hybrid Jobs", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/tree/main/examples/hybrid_jobs", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in b/demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in new file mode 100644 index 0000000000..baffb20148 --- /dev/null +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in @@ -0,0 +1,4 @@ +amazon-braket-sdk +matplotlib +pandas +pennylane diff --git a/demonstrations_v2/gqe_training/demo.py b/demonstrations_v2/gqe_training/demo.py new file mode 100644 index 0000000000..6d0b112912 --- /dev/null +++ b/demonstrations_v2/gqe_training/demo.py @@ -0,0 +1,705 @@ +r"""Generative quantum eigensolver (GQE) training using PennyLane data +=================================================== + +We will be demonstrating and evaluating a novel algorithm proposed by Nakaji et al. in their paper +`The generative quantum eigensolver (GQE) and its application for ground state search `__ +that employs a classical generative model of quantum circuits for the purpose of ground-state +energy estimation of any molecular Hamiltonian [#nakaji2024]_. +It has been proposed as a scalable alternative to the `variational quantum eigensolver (VQE) `__ approach, +where the quantum state is represented as a quantum circuit with tunable parameters which are then optimized during training in order to arrive at a +state minimizing the corresponding energy :math:`E.` Instead, in GQE, the structure of the quantum circuit is given by a trained generative model. + +We will primarily focus on offline training on a fixed training dataset -- thanks to the molecular data +available in `PennyLane Datasets `__. +By the end of the demo, we will show that the model gradually provides a better estimate for the energies and, in turn, +can sample energies close to the ground state energy calculated by PennyLane. + +This demo is organized as follows. Firstly, we compare the GQE and VQE algorithms, and afterwards, we dive deeper in +describing GPT-QE, i.e., the GQE algorithm which uses a GPT model, and its training. Next, we generate the training dataset for our GPT model using PennyLane, +and give details on our model architecture and training implementation. After that, we +evaluate the model throughout its training and discuss its performance in estimating the ground +state. And lastly, we discuss the results, potential ways optimizing the code, and its extension into the +"online training" phase. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_generative_quantum_eigensolver.png + :align: centeravailable + :width: 70% + :target: javascript:void(0) + +""" + +###################################################################### +# GQE vs. VQE +# ----------- +# Despite the relative success of VQE, there are some issues regarding its *trainability* for large problem +# instances [#nakaji2024]_. This shortcoming makes it less competitive against the performance of +# classical machine learning (ML) algorithms for large problems. To bypass this, the GQE algorithm was +# proposed. Specifically, GQE uses a classical generative model where quantum circuits are sampled as a +# sequence of unitaries from a given operator pool. This generative model is then trained so that it learns to +# predict quantum circuits that evolves an initial state to the states better approximating the ground state. +# +# The main difference between the two approaches is where the tunable parameters are embedded. +# That is, it is the classical GQE model that is being optimized as opposed to the variable +# quantum circuit of VQE. Potentially then, the :doc:`barren plateau ` landscape of +# VQE and the quantum gradient evaluation of large circuits will be sidestepped by GQE, thus becoming more amenable +# for larger problems. +# + + +############################################################################## +#.. figure:: ../_static/demonstration_assets/gqe_training/paper_gqe_vqe.png +# :align: center +# :width: 90% +# +# Figure 1: Diagrams of GQE and VQE from Fig. 1 in [#nakaji2024]_ + +###################################################################### +# GPT-QE background +# -------------------- +# +# In particular, the model architecture used by Nakaji et al. [#nakaji2024]_ for GQE was a generative pre-trained +# transformer (GPT) [#radford2019]_, [#vaswani2017]_. This choice is then reflected in the name GPT-QE. +# As language models, GPTs are successful in generating +# sequences of words that closely resemble human natural language. This performance is +# harnessed for quantum chemistry by constructing quantum states :math:`\rho` as a sequence of unitary operators +# which are, in turn, represented by quantum circuits. That is, we let :math:`\rho = U\rho_0 U^{\dagger}` +# for some fixed initial state :math:`\rho_0` and the aforementioned sequence is :math:`U = U_{j_N}U_{j_{N-1}}\cdots U_{j_1}.` +# The GPT model samples a sequence of integers :math:`j_1, j_2, ..., j_N` indexing a pool +# of operators :math:`U_j` generated using molecular data from `PennyLane Molecules `__. We interpret these +# integers as tokens and the pool as the vocabulary in the parlance for language models. +# The goal of training is then to minimize the corresponding energy +# :math:`E = \mbox{Tr}(\hat{H}\rho),` where :math:`\hat{H}` is the Hamiltonian of the molecule in +# question. +# +# Each token :math:`j_i` is sampled from the distribution :math:`\exp(-\beta w_{j_i}),` where +# :math:`w_{j_i}` is the unnormalized log probability (or logit) returned by the GPT model for the token :math:`j_i` +# and :math:`\beta` is an inverse temperature representing a trade-off parameter between exploration and exploitation. We then +# observe that the probability of sampling a state through the method described above is +# proportional to :math:`\exp(-\beta w_{\mbox{sum}}),` where :math:`w_{\mbox{sum}} = \sum_{i=1}^N w_{j_i}` +# and the probability for the corresponding energy is :math:`\exp(-\beta E).` We thus have a constraint +# for the total logit to be equal to the energy of the corresponding state: :math:`w_{\mbox{sum}} = E,` which +# can be imposed by training GPT-QE to minimize the loss function :math:`C = (w_{\mbox{sum}} - E)^2.` +# With this constraint satisfied, GPT-QE would then be sampling states of smaller energies with increasing +# likelihood. +# +# More concretely, we summarize the *pre-*training loop in Figure 2. This is called +# pre-training because it involves learning from a fixed dataset first before transitioning to the "real" training +# which utilizes the data generated by the model itself. In this demo, we will call the pre-training as offline +# training since the GPT model does not receive feedback from the sequences it samples, and online +# training if otherwise. + +############################################################################## +#.. figure:: ../_static/demonstration_assets/gqe_training/gqe_training_diagram.png +# :align: center +# :width: 90% +# +# Figure 2: Overview for offline training of GPT-QE + +###################################################################### +# Dataset construction via PennyLane +# ------------------------------------- +# +# Firstly, let us construct the static dataset we will use for offline training. We choose +# to generate our own dataset in order to illustrate the sequences and energies more concretely. +# Our dataset will be made from random sequences of tokens, which we recall corresponds to indices +# of a vocabulary of unitary operators. We then define an energy function in PennyLane to calculate +# the energy of a state corresponding to a token sequence. Applying the aforementioned function, +# we would then have a dataset of token sequences and energies for the GPT model offline training. + +###################################################################### +# Loading molecular information +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# For simplicity, let us consider the `hydrogen molecule `__ +# and load the corresponding dataset from PennyLane. +# Recall that we would need a vocabulary of operators :math:`U_j`, an initial state :math:`\rho_0,` and +# the Hamiltonian :math:`\hat{H}` for a hydrogen molecule. We also get the ground state energy for later comparison +# with the results. +# +# Specifically, the unitary operators :math:`U_j` are time evolution operators as prescribed +# in [#nakaji2024]_. The non-identity operators are generated in PennyLane using :class:`~.pennylane.SingleExcitation` +# and :class:`~.pennylane.DoubleExcitation` which then depend on the number of electrons and orbitals +# of the molecule. +# + +import numpy as np +import pennylane as qml + +def generate_molecule_data(molecules="H2"): + datasets = qml.data.load("qchem", molname=molecules) + + # Get the time set T + op_times = np.sort(np.array([-2**k for k in range(1, 5)] + [2**k for k in range(1, 5)]) / 160) + + # Build operator set P for each molecule + molecule_data = dict() + for dataset in datasets: + molecule = dataset.molecule + num_electrons, num_qubits = molecule.n_electrons, 2 * molecule.n_orbitals + singles, doubles = qml.qchem.excitations(num_electrons, num_qubits) + double_excs = [qml.DoubleExcitation(time, wires=double) for double in doubles for time in op_times] + single_excs = [qml.SingleExcitation(time, wires=single) for single in singles for time in op_times] + identity_ops = [qml.exp(qml.I(range(num_qubits)), 1j*time) for time in op_times] # For Identity + operator_pool = double_excs + single_excs + identity_ops + molecule_data[dataset.molname] = { + "op_pool": np.array(operator_pool), + "num_qubits": num_qubits, + "hf_state": dataset.hf_state, + "hamiltonian": dataset.hamiltonian, + "expected_ground_state_E": dataset.fci_energy + } + return molecule_data + +molecule_data = generate_molecule_data("H2") +h2_data = molecule_data["H2"] +op_pool = h2_data["op_pool"] +num_qubits = h2_data["num_qubits"] +init_state = h2_data["hf_state"] +hamiltonian = h2_data["hamiltonian"] +grd_E = h2_data["expected_ground_state_E"] +op_pool_size = len(op_pool) + +###################################################################### +# Defining the energy function +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# In PennyLane, we define the energy function :math:`E = \mbox{Tr}(\hat{H}U_{j_N}\cdots U_{j_1}\rho_0 U_{j_1}^{\dagger}\cdots U_{j_N}^{\dagger})` +# corresponding to Eq. 1 of [#nakaji2024]_. Here, ``energy_circuit`` takes in the operator sequence :math:`U_{j_1}, U_{j_2}, ..., U_{j_N}` +# and returns the energy of the corresponding quantum state. +# +# As a slight extension, we can also calculate the energies for each subsequence of +# operators to help with the training of the model. That is, for a sequence of three operators +# :math:`U_{j_1}, U_{j_2}, U_{j_3}` we compute the energies for :math:`U_{j_1}` and :math:`U_{j_1}, U_{j_2}` instead of just +# the full sequence of three operators, which was described in [#nakaji2024]_. This can be done simply in PennyLane, using +# :class:`~.pennylane.Snapshot` as shown below. +# + +dev = qml.device("default.qubit", wires=num_qubits) + +@qml.qnode(dev) +def energy_circuit(gqe_ops): + # Computes Eq. 1 from Nakaji et al. based on the selected unitary operators + qml.BasisState(init_state, wires=range(num_qubits)) # Initial state <-- Hartree Fock state + for op in gqe_ops: + qml.Snapshot(measurement=qml.expval(hamiltonian)) + qml.apply(op) # Applies each of the unitary operators + return qml.expval(hamiltonian) + +energy_circuit = qml.snapshots(energy_circuit) + +def get_subsequence_energies(op_seq): + # Collates the energies of each subsequence for a batch of sequences + energies = [] + for ops in op_seq: + es = energy_circuit(ops) + energies.append( + [es[k].item() for k in list(range(1, len(ops))) + ["execution_results"]] + ) + return np.array(energies) + +###################################################################### +# Token sequence generation with corresponding energies +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# With these ingredients, we can now construct a dataset containing sequences of tokens and their energies. +# Since we cannot feed the operators directly to the GPT model, we would need to tokenize +# them. The indices of ``op_pool`` seems to be a good candidate, but we instead choose the tokens to be +# the ``op_pool`` indices shifted by 1. This is so that we can define a special token ``0`` that tells +# the GPT model where the sequence starts. +# +# We generate a ``train_size`` number of random operator sequences of length ``seq_len`` for our +# purposes and calculate their energies (and their subsequences). +# + +# Generate sequence of indices of operators in vocab +train_size = 1024 +seq_len = 4 +train_op_pool_inds = np.random.randint(op_pool_size, size=(train_size, seq_len)) + +# Corresponding sequence of operators +train_op_seq = op_pool[train_op_pool_inds] + +# Corresponding tokens with special starting tokens +train_token_seq = np.concatenate([ + np.zeros(shape=(train_size, 1), dtype=int), # starting token is 0 + train_op_pool_inds + 1 # shift operator inds by one +], axis=1) + +# Calculate the energies for each subsequence in the training set +train_sub_seq_en = get_subsequence_energies(train_op_seq) + +###################################################################### +# GPT-QE offline training +# -------------------------- +# Having setup our training dataset, we can start implementing our offline training loop as +# illustrated in Figure 2. We outline our implementation below. + +###################################################################### +# GPT model implementation details +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The GPT model we will use in this demo is mostly implemented in the `nanoGPT repo +# `__ (a reimplementation of the `OpenAI GPT-2 `__) as the +# `class `__ +# ``GPT`` with the model hyperparameters stored in the +# `dataclass `__ +# ``GPTConfig``. Namely, we will use 12 attention layers, 12 attention heads, and 768 embedding dimensions, +# which are equal to those described in [#nakaji2024]_. +# We can import from the nanoGPT repo directly by running the ``curl`` command (commented out below) in a Jupyter Notebook. +# Since nanoGPT is trained as a language model, its loss function and sampling method are +# defined differently. We then define the subclass ``GPTQE`` below to override some nanoGPT methods in order to make it more +# suitable for our case. +# + +# !curl -O https://raw.githubusercontent.com/karpathy/nanoGPT/master/model.py +from model import GPT, GPTConfig +import torch +from torch.nn import functional as F + +class GPTQE(GPT): + def forward(self, idx): + device = idx.device + b, t = idx.size() + pos = torch.arange(0, t, dtype=torch.long, device=device) # shape (t) + + # forward the GPT model itself + tok_emb = self.transformer.wte(idx) # token embeddings of shape (b, t, n_embd) + pos_emb = self.transformer.wpe(pos) # position embeddings of shape (t, n_embd) + x = self.transformer.drop(tok_emb + pos_emb) + for block in self.transformer.h: + x = block(x) + x = self.transformer.ln_f(x) + logits = self.lm_head(x) + return logits + + def calculate_loss(self, tokens, energies): + current_tokens, next_tokens = tokens[:, :-1], tokens[:, 1:] + # calculate the logits for the next possible tokens in the sequence + logits = self(current_tokens) + # get the logit for the actual next token in the sequence + next_token_mask = torch.nn.functional.one_hot( + next_tokens, num_classes=self.config.vocab_size + ) + next_token_logits = (logits * next_token_mask).sum(axis=2) + # calculate the cumulative logits for each subsequence + cumsum_logits = torch.cumsum(next_token_logits, dim=1) + # match cumulative logits to subsequence energies + loss = torch.mean(torch.square(cumsum_logits - energies)) + return loss + + @torch.no_grad() + def generate(self, n_sequences, max_new_tokens, temperature=1.0, device="cpu"): + idx = torch.zeros(size=(n_sequences, 1), dtype=int, device=device) + total_logits = torch.zeros(size=(n_sequences, 1), device=device) + for _ in range(max_new_tokens): + # if the sequence context is growing too long we must crop it at block_size + idx_cond = idx if idx.size(1) <= self.config.block_size else idx[:, -self.config.block_size:] + # forward the model to get the logits for the index in the sequence + logits = self(idx_cond) + # pluck the logits at the final step + logits = logits[:, -1, :] + # set the logit of the first token so that its probability will be zero + logits[:, 0] = float("inf") + # apply softmax to convert logits to (normalized) probabilities and scale by desired temperature + probs = F.softmax(-logits / temperature, dim=-1) + # sample from the distribution + idx_next = torch.multinomial(probs, num_samples=1) + # # Accumulate logits + total_logits += torch.gather(logits, index=idx_next, dim=1) + # append sampled index to the running sequence and continue + idx = torch.cat((idx, idx_next), dim=1) + return idx, total_logits + +###################################################################### +# However, it is important to note that the loss function ``calculate_loss``, that we defined is different from the one +# described in [#nakaji2024]_ which is :math:`(\exp(-w_{\mbox{sum}}) - \exp(-E))^2.` As described +# beforehand, we instead directly compute the mean squared error between :math:`w_{\mbox{sum}}` and :math:`E.` +# Since the exponential function is one-to-one, both loss functions would then impose the same minimum. +# Using the error between exponentials may even +# introduce numerical instabilities in the training since the loss would be taking differences of potentially large numbers. +# In addition to this change from [#nakaji2024]_, we also use the error between the cumulative sum of logits and the corresponding energy +# for each subsequence instead of just the error between total logits and the energy of an entire sequence. +# This addition will give more training data to the model and should help with logit matching the intermediate tokens. +# +# Since it was not explicitly shown in [#nakaji2024]_, another possible deviation we made is with the logit calculation during offline training. +# It seems that the logits were accumulated by looping through the sequential generation of tokens. +# In order to fill in the blanks, we implement the logit calculation in a manner that we think is efficient. +# Namely, we directly pass the fixed training sequences to the model and retrieve the relevant logits from it. +# This can be done because we are using a causal mask for the attention blocks so that the logits of the earlier tokens in the sequence are +# not affected by tokens in the later part of the sequence. Thus, having the same effect of the sequential token generation. +# +# We initialize our GPT model below and we see that it has around 85 million parameters. When saved, the model is ``324.25 MB`` in size. + +tokens = torch.from_numpy(train_token_seq).to("cuda") +energies = torch.from_numpy(train_sub_seq_en).to("cuda") + +gpt = GPTQE(GPTConfig( + vocab_size=op_pool_size + 1, + block_size=seq_len, + dropout=0.2, + bias=False +)).to("cuda") +opt = gpt.configure_optimizers( + weight_decay=0.01, learning_rate=5e-5, betas=(0.9, 0.999), device_type="cuda" +) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# number of parameters: 84.98M +# num decayed parameter tensors: 50, with 84,963,072 parameters +# num non-decayed parameter tensors: 25, with 19,200 parameters + +###################################################################### +# GPT offline training +# ~~~~~~~~~~~~~~~~~~~~ +# +# We now implement a training loop for our GPT model. This can be framed as a straightforward +# supervised learning problem. We sketch the steps for each training iteration/epoch below: +# +# 1. Shuffle the training set and split it into ``n_batches`` minibatches +# 2. For each minibatch, calculate the average loss, the gradients, and take an optimizer step +# 3. For each n-th iteration (n is 500 here), evaluate the GPT model: +# +# - Generate a batch of sequences and the predicted energies (total logits). Note that these are not necessarily same sequences as in the training set. +# +# - Calculate the true energies using PennyLane +# +# - Calculate the mean absolute error as a metric to track the learning progress and save the GPT model everytime the metric gets better +# + +# %%time +n_batches = 8 +train_inds = np.arange(train_size) + +losses = [] +pred_Es_t = [] +true_Es_t = [] +current_mae = 10000 +gpt.train() +for i in range(10000): + # Shuffle batches of the training set + np.random.shuffle(train_inds) + token_batches = torch.tensor_split(tokens[train_inds], n_batches) + energy_batches = torch.tensor_split(energies[train_inds], n_batches) + + # SGD on random minibatches + loss_record = 0 + for token_batch, energy_batch in zip(token_batches, energy_batches): + opt.zero_grad() + loss = gpt.calculate_loss(token_batch, energy_batch) + loss.backward() + opt.step() + loss_record += loss.item() / n_batches + losses.append(loss_record) + + if (i+1) % 500 == 0: + # For GPT evaluation + gpt.eval() + gen_token_seq, pred_Es = gpt.generate( + n_sequences=100, + max_new_tokens=seq_len, + temperature=0.001, # Use a low temperature to emphasize the difference in logits + device="cuda" + ) + pred_Es = pred_Es.cpu().numpy() + + gen_inds = (gen_token_seq[:, 1:] - 1).cpu().numpy() + gen_op_seq = op_pool[gen_inds] + true_Es = get_subsequence_energies(gen_op_seq)[:, -1].reshape(-1, 1) + + mae = np.mean(np.abs(pred_Es - true_Es)) + ave_E = np.mean(true_Es) + + pred_Es_t.append(pred_Es) + true_Es_t.append(true_Es) + + print(f"Iteration: {i+1}, Loss: {losses[-1]}, MAE: {mae}, Ave E: {ave_E}") + + if mae < current_mae: + current_mae = mae + torch.save(gpt, f"./seq_len={seq_len}/gqe.pt") + print("Saved model!") + + gpt.train() + +pred_Es_t = np.concatenate(pred_Es_t, axis=1) +true_Es_t = np.concatenate(true_Es_t, axis=1) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Iteration: 500, Loss: 0.004496691238049528, MAE: 0.13945468622863236, Ave E: -1.1161227981406456 +# Saved model! +# Iteration: 1000, Loss: 0.001162520404255374, MAE: 0.11792013497926974, Ave E: -1.116178063434579 +# Saved model! +# Iteration: 1500, Loss: 0.0006311882560414964, MAE: 0.08421050347067748, Ave E: -1.1304435666682537 +# Saved model! +# Iteration: 2000, Loss: 0.0002220232025956396, MAE: 0.03313205549288038, Ave E: -1.13411711385679 +# Saved model! +# Iteration: 2500, Loss: 9.021296506465553e-05, MAE: 0.03720317687198404, Ave E: -1.1360217383940532 +# Iteration: 3000, Loss: 0.00011929328764308375, MAE: 0.010246824522607662, Ave E: -1.1355033629645301 +# Saved model! +# Iteration: 3500, Loss: 4.015137835017087e-05, MAE: 0.008332604993116905, Ave E: -1.1362737218253494 +# Saved model! +# Iteration: 4000, Loss: 0.00025425587370956726, MAE: 0.03346923599957368, Ave E: -1.13442109812976 +# Iteration: 4500, Loss: 4.590269966149363e-05, MAE: 0.0086580669691949, Ave E: -1.1344678899103924 +# Iteration: 5000, Loss: 2.7407370499136962e-05, MAE: 0.006680762382889203, Ave E: -1.136412143925528 +# Saved model! +# Iteration: 5500, Loss: 3.778071550021417e-05, MAE: 0.014272903220676704, Ave E: -1.1362969016861684 +# Iteration: 6000, Loss: 2.2792776141250974e-05, MAE: 0.007428675818214263, Ave E: -1.1367647064449693 +# Iteration: 6500, Loss: 1.9002385742602413e-05, MAE: 0.004431537870071902, Ave E: -1.135880723613281 +# Saved model! +# Iteration: 7000, Loss: 1.5268728079291623e-05, MAE: 0.002464256235883442, Ave E: -1.1356989137037925 +# Saved model! +# Iteration: 7500, Loss: 1.1030378864566936e-05, MAE: 0.007000517223791054, Ave E: -1.1360445255294285 +# Iteration: 8000, Loss: 7.638036884241474e-06, MAE: 0.0044611951680048586, Ave E: -1.1352658877947734 +# Iteration: 8500, Loss: 1.616690860258467e-05, MAE: 0.004094392133172753, Ave E: -1.1356437076129735 +# Iteration: 9000, Loss: 7.37882245331426e-06, MAE: 0.004240113290004896, Ave E: -1.1358971131175264 +# Iteration: 9500, Loss: 1.004411104422562e-05, MAE: 0.010631562300185794, Ave E: -1.1368761600775912 +# Iteration: 10000, Loss: 1.809862392776087e-05, MAE: 0.01987725166307399, Ave E: -1.1345492765523346 +# CPU times: user 2h 12min 24s, sys: 8.18 s, total: 2h 12min 32s +# Wall time: 2h 12min 32s + +###################################################################### +# With a preliminary look at the training logs above, we see that the offline training took 2 h and 12 min for 10,000 +# training iterations. The code execution time was measured by including the ``%%time`` magic command before the code block. +# We also note that the mean absolute error between the predicted and true energies for the +# generated sequences quickly became smaller during the earlier parts of the training but fluctuates more +# later on. As mentioned earlier, a model version is saved each time we get better performance. The best model version is then +# saved at the 7,000th iteration, where the mean absolute error is at its lowest. The other quantities we observe are the +# average true energies of the generated sequences. It is then promising to see that throughout training, this is +# close to the ground state energy ``grd_E=-1.1372633205048763 Ha``. +# + +###################################################################### +# GPT-QE results +# ----------------- +# Having finished the offline training, let's take a look at some of our results. + +###################################################################### +# Training loss curve +# ~~~~~~~~~~~~~~~~~~~ +# One of the first things we can look at is the training loss curve. Recall that for our case, the +# loss is the mean squared error between the cumulative sum of logits (predicted subsequence energies) and their +# corresponding true subsequence energies. So reducing this quantity allows the model to be better at giving +# correct energies and in turn, correctly sample sequences with lower energies. Since the raw loss values +# become very small, we instead plot the loss in log-scale below to magnify the loss trend. +# + +import holoviews as hv +import hvplot.pandas +import pandas as pd + +hvplot.extension('matplotlib') + +losses = pd.read_csv("./seq_len=4/trial7/losses.csv")["0"] +loss_fig = losses.hvplot( + title="Training loss progress", ylabel="loss", xlabel="Training epochs", logy=True +).opts(fig_size=600, fontscale=2, aspect=1.2) +loss_fig + +############################################################################## +#.. figure:: ../_static/demonstration_assets/gqe_training/gqe_training_loss.png +# :align: center +# :width: 100% +# +# Figure 3: The subsequence loss for each training iteration + +############################################################################## +# We see in Figure 3 that the loss +# continues to decrease until around the 4,000th iteration. There, the model was erroraneous but is quick +# to recover as training continues. This may signal that the GPT model started focusing on learning something +# erroraneous too quickly. So, more regularization noise (like ``dropout``) may be needed to help avoid this. + +###################################################################### +# Evaluation progress +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We now track the performance of the GPT model throughout its training in Figure 4 below. As mentioned +# before, after every 500th iteration, we let the model generate a batch of sequences. Alongside, we +# also return the total logits (predicted energies) used in the sequence generation. In Figure 4, +# the average predicted energies correspond to the red markers and the distribution of predicted +# energies is represented by the red area. Once we have the generated sequences, we can also let PennyLane calculate the true sequence +# energies. Similarly then, the blue markers are the average true energies and the blue +# area represents the true energy distribution. +# + +df_true = pd.read_csv("./seq_len=4/trial7/true_Es_t.csv").iloc[:, 1:] +df_pred = pd.read_csv("./seq_len=4/trial7/pred_Es_t.csv").iloc[:, 1:] + +df_true.columns = df_true.columns.astype(int) +df_pred.columns = df_pred.columns.astype(int) + +df_trues_stats = pd.concat([df_true.mean(axis=0), df_true.min(axis=0), df_true.max(axis=0)], axis=1).reset_index() +df_trues_stats.columns = ["Training Iterations", "Ave True E", "Min True E", "Max True E"] + +df_preds_stats = pd.concat([df_pred.mean(axis=0), df_pred.min(axis=0), df_pred.max(axis=0)], axis=1).reset_index() +df_preds_stats.columns = ["Training Iterations", "Ave Pred E", "Min Pred E", "Max Pred E"] + +fig = ( + df_trues_stats.hvplot.scatter(x="Training Iterations", y="Ave True E", label="Mean True Energies") * + df_trues_stats.hvplot.line(x="Training Iterations", y="Ave True E", alpha=0.5, linewidth=1) * + df_trues_stats.hvplot.area(x="Training Iterations", y="Min True E", y2="Max True E", alpha=0.1) +) * ( + df_preds_stats.hvplot.scatter(x="Training Iterations", y="Ave Pred E", label="Mean Predicted Energies") * + df_preds_stats.hvplot.line(x="Training Iterations", y="Ave Pred E", alpha=0.5, linewidth=1) * + df_preds_stats.hvplot.area(x="Training Iterations", y="Min Pred E", y2="Max Pred E", alpha=0.1) +) +fig = fig * hv.Curve([[0, grd_E], [10000, grd_E]], label="Ground State Energy").opts(color="k", alpha=0.4, linestyle="dashed") +fig = fig.opts(ylabel="Sequence Energies", title="GQE Evaluations", fig_size=600, fontscale=2) +fig + +############################################################################## +#.. figure:: ../_static/demonstration_assets/gqe_training/gqe_performance.png +# :align: center +# :width: 100% +# +# Figure 4: True and predicted energies for sequences generated by the GPT model for each 500th training iteration + +############################################################################## +# We now see that the energies predicted by the model improve in accuracy, aligning more closely with the true +# energies during training. The increase in accuracy then allows the model to correctly sample states +# with lower energies. This is supported in Figure 4 where the sampled true energies +# get closer to the ground state energy (the dashed line). +# +# Note that at around the 4,000th iteration, the predicted energies are very far from the true energies. +# This makes sense considering our observation in Figure 3. Also note that at the 7,000th +# iteration, the averages of the predicted and true energies are the closest and even their respective +# spreads seem to have good overlap. This is when the best performing model version was saved, as seen in the training logs. +# For later iterations however, the predicted energies no longer improved. +# This may indicate that the GPT model has started overfitting on the training dataset in the later iterations. +# That is, the model became great at predicting the correct energies for the training set (as observed by the decreasing +# loss in Figure 3) but not great at generalizing on those outside the training set (like the sequences that the model +# generated on its own). One solution to avoid overfitting could then be online training. This is so that +# the GPT model is not restricted on a fixed dataset on which to overfit. +# + +###################################################################### +# Sequence generation comparison +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Here, we compare some statistics of the true energies corresponding to sequences generated +# by a "random" model, the latest version of the model after all the training iterations, and the best model version +# saved based on the mean absolute error between the true and predicted energies during training +# (for our case, this is the model saved at the 7,000th iteration). +# Note that we consider the training set to be generated by a random model since the token +# sequences are just sampled uniformly. +# + +# Latest model +gen_token_seq_, _ = gpt.generate( + n_sequences=1024, + max_new_tokens=seq_len, + temperature=0.001, + device="cuda" +) +gen_inds_ = (gen_token_seq_[:, 1:] - 1).cpu().numpy() +gen_op_seq_ = op_pool[gen_inds_] +true_Es_ = get_subsequence_energies(gen_op_seq_)[:, -1].reshape(-1, 1) + +# Best model +loaded = torch.load("./seq_len=4/trial7/gqe.pt") +loaded_token_seq_, _ = loaded.generate( + n_sequences=1024, + max_new_tokens=seq_len, + temperature=0.001, + device="cuda" +) +loaded_inds_ = (loaded_token_seq_[:, 1:] - 1).cpu().numpy() +loaded_op_seq_ = op_pool[loaded_inds_] +loaded_true_Es_ = get_subsequence_energies(loaded_op_seq_)[:, -1].reshape(-1, 1) + +# Summary table +df_compare_Es = pd.DataFrame({ + "Source": ["Random", "Latest Model", "Best Model"], + "Aves": [train_sub_seq_en[:, -1].mean(), true_Es_.mean(), loaded_true_Es_.mean()], + "Mins": [train_sub_seq_en[:, -1].min(), true_Es_.min(), loaded_true_Es_.min()], + "Maxs": [train_sub_seq_en[:, -1].max(), true_Es_.max(), loaded_true_Es_.max()], + "Mins_error": [ + abs(train_sub_seq_en[:, -1].min() - grd_E), + abs(true_Es_.min() - grd_E), + abs(loaded_true_Es_.min() - grd_E), + ], +}) +df_compare_Es + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Source Aves Mins Maxs Mins_error +# 0 Random -1.114531 -1.136982 -1.027878 2.811117e-04 +# 1 Latest Model -1.132200 -1.137038 -1.125118 2.253048e-04 +# 2 Best Model -1.135560 -1.137263 -1.125118 7.712067e-07 + +############################################################################## +# We observe that the minimum energy corresponding to the random training set is already close +# to the ground state energy ``grd_E=-1.1372633205048763 Ha`` with an error of around ``2.81e-04 Ha``. But we notice that the maximum energy +# is relatively far so the random sequences give a wider spread of energies. +# +# The energies of the generated sequences from the latest and the best GPT model versions however have a narrower spread and so, +# the average and the minimum energies are very close to ``grd_E``, closer than those in the random +# training set. Namely, around a ``2.25e-04 Ha`` error for the minimum energy generated by the latest model version +# and a ``7.71e-07 Ha`` error for the best model version. It is then very interesting that +# a well-trained GPT model can generate sequences that are better than those in the training set, even though every +# sequence the model has seen just came from that set. That is, the model was able to generalize. +# +# Between the two GPT model versions, we see that the latest version is worse than the best version. The minimum energy +# error for the latest version has the same order of magnitude as the corresponding one for the random training set. +# Contrast this with the minimum energy error for the best version, which is 3 orders of magnitude smaller. +# This behavior is supported by our observation in Figure 4 where the performance of the model after +# the 7,000th iteration worsened. That is, the predicted energies started to deviate further from the true energies +# which in turn caused the states being sampled from these predicted energies to be different from the intended +# lower energy states. +# + +###################################################################### +# Conclusion +# ------------- +# +# In this demo, we see that GPT-QE is a viable alternative in estimating the ground-state energy +# of a hydrogen molecule. The best underlying GPT model version can generate a state whose energy is +# only around ``7.71e-07 Ha`` away from ground state energy, which is well below the chemical accuracy. Additionally, since the GPT model being optimized +# is completely detached from the quantum simulations, gradients across a potentially large quantum circuit +# don't need to be computed, for example. Thus, the machinery of classical ML can be harnessed without +# worrying too much about the quantum algorithm side of the problem. +# +# The reader can also experiment with other molecules from PennyLane and tweak several hyperparameters +# of the GPT model (like ``dropout``, and ``n_layer``) and include standard ML callbacks to its training +# (like an early stopping mechanism and a learning rate schedule). The code itself is also open to further +# optimization. For instance, using `PennyLane Lightning `__ +# to evaluate the energies faster. An online training loop can also be implemented similarly to our offline +# version, the reader would just need to sample sequences from the current GPT model instead of a fixed +# dataset for each training iteration. To facilitate exploration and exploitation, one would also define +# a schedule for the inverse temperature. That is, initially letting the GPT model sample more randomly through +# a high temperature, then gradually decreasing so that the GPT model focuses more on the higher probability +# states (low energies). +# + +###################################################################### +# Reference +# --------- +# +# .. [#nakaji2024] +# +# Kouhei Nakaji *et al.*, "The generative quantum eigensolver (GQE) and its application for ground state search". +# `arXiv:2401.09253 (2024) `__ +# +# .. [#radford2019] +# +# Alec Radford *et al.*, "Language Models are Unsupervised Multitask Learners". +# OpenAI blog, 1(8):9 (2019) +# +# .. [#vaswani2017] +# +# Ashish Vaswani *et al.*, "Attention is All you Need". +# Advances in Neural Information Processing Systems, 30 (2017) +# + +###################################################################### diff --git a/demonstrations_v2/gqe_training/metadata.json b/demonstrations_v2/gqe_training/metadata.json new file mode 100644 index 0000000000..a20b54000a --- /dev/null +++ b/demonstrations_v2/gqe_training/metadata.json @@ -0,0 +1,77 @@ +{ + "title": "Generative quantum eigensolver training using PennyLane data", + "authors": [ + { + "username": "Joseph" + }, + { + "username": "zy_n" + } + ], + "dateOfPublication": "2024-09-20T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Quantum Chemistry", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generative_quantum_eigensolver.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_generative_quantum_eigensolver.png" + } + ], + "seoDescription": "Learn how you can train a small GPT model using the generative quantum eigensolver (GQE) technique and PennyLane data.", + "doi": "", + "references": [ + { + "id": "nakaji2024", + "type": "article", + "title": "The generative quantum eigensolver (GQE) and its application for ground state search", + "authors": "K. Nakaji, L. B. Kristensen, J. A. Campos-Gonzalez-Angulo, M. G. Vakili, H. Huang, M. Bagherimehrab, C. Gorgulla, F. Wong, A. McCaskey, J. S. Kim, T. Nguyen, P. Rao, A. Aspuru-Guzik", + "year": "2024", + "journal": "", + "url": "https://arxiv.org/abs/2401.09253" + }, + { + "id": "radford2019", + "type": "article", + "title": "Language Models are Unsupervised Multitask Learners", + "authors": "A. Radford, J. Wu, R. Child, D. Luan, D. Amodei, I. Sutskever", + "year": "2019", + "journal": "OpenAI blog", + "url": "https://openai.com/index/better-language-models/" + }, + { + "id": "vaswani2017", + "type": "article", + "title": "Attention is All you Need", + "authors": "A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, L. Kaiser, I. Polosukhin", + "year": "2017", + "journal": "Advances in Neural Information Processing Systems", + "url": "https://papers.nips.cc/paper_files/paper/2017/hash/3f5ee243547dee91fbd053c1c4a845aa-Abstract.html" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2401.09253" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + } + ], + "hardware": [] +} \ No newline at end of file diff --git a/demonstrations_v2/gqe_training/requirements.in b/demonstrations_v2/gqe_training/requirements.in new file mode 100644 index 0000000000..5cef66d488 --- /dev/null +++ b/demonstrations_v2/gqe_training/requirements.in @@ -0,0 +1,6 @@ +holoviews +hvplot +numpy +pandas +pennylane +torch diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py new file mode 100644 index 0000000000..26c699a067 --- /dev/null +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py @@ -0,0 +1,430 @@ +r"""How to use Qiskit 1.0 with PennyLane +==================================== + +The PennyLane-Qiskit plugin enables you to integrate your existing Qiskit code and run circuits on IBM +devices with PennyLane, encompassing two real-world scenarios: (1) working in PennyLane from the start +and executing your work on an IBM device and (2) converting your existing Qiskit code to PennyLane and +executing that on *any* device, including IBM devices, :doc:`Amazon Braket ` +— you name it! + +With the `first stable release of Qiskit `__ in February 2024, we subsequently shipped some +excellent features and upgrades with the PennyLane-Qiskit plugin, allowing +anyone familiar with +Qiskit to jump into the PennyLane ecosystem and land on both feet. +In this demo, we want to show you how easy these features are to use, letting you make that +aforementioned jump like it’s nothing 😌. +""" + +###################################################################### +# .. note :: +# +# To follow along, we recommend installing the PennyLane-Qiskit plugin in a separate virtual environment. +# +# .. code-block:: none +# +# pip install -U pennylane-qiskit +# +# This will install PennyLane, the plugin, and the latest Qiskit 1.0 version that the plugin supports. +# If you use an environment that contains a pre-1.0 version of Qiskit, upgrading to Qiskit 1.0 could +# cause issues. In that case, we recommend following `Qiskit's upgrade instructions `__. +# + +###################################################################### +# Coding in PennyLane, executing on IBM Quantum devices +# ----------------------------------------------------- +# +# If you want to distill how a PennyLane plugin works down to one thing, it’s all in the provided devices! In +# PennyLane, you just :doc:`create your circuit (a quantum function) ` and decorate it with +# the QNode decorator :func:`@qml.qnode(dev) `, where ``dev`` is (one of) the plugin’s device(s). +# +# In PennyLane and its plugins, +# `devices `__ are called upon by their short name, and can be loaded via the :func:`~pennylane.device` function: +# +# .. code-block:: +# +# qml.device("shortname", *device_options) +# +# If you’ve +# seen PennyLane code before, you’ve probably seen ``"default.qubit"`` or ``"lightning.qubit"`` as +# short names for our Python and C++ statevector simulators, respectively. +# +# In the PennyLane-Qiskit plugin, there are `many IBM +# devices `__ you can use, but there are +# two heavy hitters for Qiskit 1.0: +# +# - ``"qiskit.basicsim"``: uses the Qiskit ``BasicSimulator`` backend from the ``basic_provider`` +# module in Qiskit 1.0. +# +# - ``"qiskit.remote"``: lets you run PennyLane code on any Qiskit device, where you can choose between +# different backends — either simulators tailor-made to emulate the real hardware, or the real +# hardware itself. +# +# +# If you want to use any of these devices in PennyLane, simply put those short names into +# ``qml.device`` and any quantum function decorated with ``@qml.qnode(dev)`` will execute on the +# corresponding device. +# + +###################################################################### +# To show how easy this is, let’s say we have this simple circuit in Qiskit 1.0. +# + +import qiskit +from qiskit import QuantumCircuit +from qiskit.providers.basic_provider import BasicSimulator + +qc = QuantumCircuit(2, 1) + +qc.h(0) +qc.cx(0, 1) +qc.measure(1, 0) + +backend = BasicSimulator() +counts = backend.run(qc).result().get_counts() + +print(counts) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# {'0': 523, '1': 501} +# + +###################################################################### +# In PennyLane, we can execute the exact same circuit on the exact same device and backend like so: +# + +import pennylane as qml + +dev = qml.device("qiskit.basicsim", wires=2, shots=1024) + +@qml.qnode(dev) +def circuit(): + qml.Hadamard(0) + qml.CNOT([0, 1]) + return qml.counts(wires=1) + +print(circuit()) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# {'0': tensor(474, requires_grad=True), '1': tensor(550, requires_grad=True)} +# + +###################################################################### +# Magic! With one line of code, you can work inside PennyLane and ship the execution off to your +# favourite IBM device. It’s exactly like using Qiskit 1.0, but you interact with PennyLane instead. +# + +###################################################################### +# Converting Qiskit 1.0 code to PennyLane +# --------------------------------------- +# +# This is probably what a lot of the audience is wondering: “Can I combine my existing work in Qiskit +# with PennyLane?” *YES.* And don’t worry, you don’t need to import a ton of things or use a bunch of +# functions — you only need to know *two* functions: +# +# - :func:`~pennylane.from_qiskit`: converts an entire Qiskit ``QuantumCircuit`` — the whole thing — into a +# PennyLane quantum function. It will faithfully convert Qiskit-side measurements (even mid-circuit +# measurements), or you can append Pennylane-side measurements directly to it. +# +# - :func:`~pennylane.from_qiskit_op`: converts a ``SparsePauliOp`` in Qiskit 1.0 to the equivalent operator +# in PennyLane. +# +# +# Both of these functions give you all the functionality you need to access PennyLane’s features and +# user interface starting from the side of Qiskit 1.0. Let’s look at an example where both of these +# functions are used. +# + +###################################################################### +# Let’s say you’ve created the following Qiskit code that prepares a modified GHZ state for an +# arbitrary amount of qubits and measures several expectation values of ``SparsePauliOp`` operators. +# + +from qiskit.quantum_info import SparsePauliOp + +n = 5 + +def qiskit_GHZ_circuit(n): + qc = QuantumCircuit(n) + qc.h(0) + for i in range(n - 1): + qc.cx(i, i + 1) + qc.rx(0.1967, i) + return qc + +qc = qiskit_GHZ_circuit(n) + +operator_strings = ["I" * i + "ZZ" + "I" * (n - 2 - i) for i in range(n - 1)] +operators = [SparsePauliOp(operator_string) for operator_string in operator_strings] +print(operators) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# [SparsePauliOp(['ZZIII'], +# coeffs=[1.+0.j]), SparsePauliOp(['IZZII'], +# coeffs=[1.+0.j]), SparsePauliOp(['IIZZI'], +# coeffs=[1.+0.j]), SparsePauliOp(['IIIZZ'], +# coeffs=[1.+0.j])] +# + +###################################################################### +# With the circuit and operators defined, we can create a +# `StatevectorEstimator `__ +# primitive in Qiskit to execute the circuit and calculate expectation values. +# + +from qiskit.primitives import StatevectorEstimator + +estimator = StatevectorEstimator() +job_estimator = estimator.run([(qc, operators)]) +result_estimator = job_estimator.result()[0].data.evs + +print(result_estimator) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# [0.98071685 0.96180554 0.96180554 0.96180554] +# + +###################################################################### +# To convert this work into PennyLane, let’s start with the Qiskit-side ``SparsePauliOp`` operators +# and converting them to PennyLane objects with ``qml.from_qiskit_op``. +# + +pl_operators = [qml.from_qiskit_op(qiskit_op) for qiskit_op in operators] +print(pl_operators) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# [Z(3) @ Z(4), Z(2) @ Z(3), Z(1) @ Z(2), Z(0) @ Z(1)] +# + +###################################################################### +# .. note :: +# +# PennyLane wires are enumerated from left to right, while the Qiskit convention is to +# enumerate from right to left. This means a ``SparsePauliOp`` term defined by the string “XYZ” in Qiskit +# applies ``Z`` on wire ``0``, ``Y`` on wire ``1``, and ``X`` on wire ``2``. For more details, see +# the `String `__ representation +# section of the Qiskit documentation for the ``Pauli`` class. +# + +###################################################################### +# Next, we show how to convert the Qiskit ``QuantumCircuit``, ``qc``, to PennyLane with ``qml.from_qiskit``. We +# can append the measurements — expectation values (:func:`~pennylane.expval`) of ``pl_operators`` — with the +# ``measurements`` keyword argument, which accepts a list of PennyLane measurements. +# + +measurements = [qml.expval(op) for op in pl_operators] # expectation values + +qc = qiskit_GHZ_circuit(n) +pl_qfunc = qml.from_qiskit(qc, measurements=measurements) + +###################################################################### +# The last thing to do is make ``pl_func`` a QNode. We can’t decorate ``pl_qfunc`` with +# ``@qml.qnode``, but we can equivalently wrap it with ``qml.QNode`` and supply the device. +# + +pl_circuit = qml.QNode(pl_qfunc, device=qml.device("lightning.qubit", wires=n)) +pl_circuit() + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# [0.9807168489852623, 0.961805537883582, 0.961805537883582, 0.961805537883582] +# + +###################################################################### +# What’s really useful about being able to append measurements to the end of a circuit with +# :func:`~pennylane.from_qiskit` is being able to measure something that isn’t available in Qiskit 1.0 but is +# available in PennyLane, like the :doc:`classical shadow ` measurement protocol, for example. In PennyLane, +# you can measure this with :func:`~pennylane.classical_shadow`. +# + +measurements = [qml.classical_shadow(wires=range(n))] +pl_qfunc = qml.from_qiskit(qc, measurements=measurements) + +pl_circuit = qml.QNode(pl_qfunc, device=qml.device("default.qubit", wires=n)) +pl_circuit(shots=5) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# [array([[[0, 0, 0, 0, 1], +# [1, 1, 1, 0, 1], +# [0, 1, 0, 0, 0], +# [1, 1, 0, 0, 0], +# [0, 0, 0, 0, 0]], +# +# [[0, 0, 2, 2, 1], +# [2, 0, 2, 0, 1], +# [1, 0, 2, 0, 2], +# [1, 0, 1, 1, 1], +# [1, 0, 2, 0, 2]]], dtype=int8)] +# + +###################################################################### +# And that’s it! Now you have a copy of your work in PennyLane, where you can access +# fully-differentiable and hardware-agnostic quantum programming — the entire quantum programming +# ecosystem is at your disposal 💪. +# + +###################################################################### +# A real-world example +# -------------------- +# +# One of the things you’ll almost certainly encounter in the wild is a cost function to optimize with +# tunable parameters belonging to a circuit — it’s common 😉! PennyLane is a great option for these +# problems because of its end-to-end differentiability and long list of `optimization methods `_ you can +# leverage. So, maybe you have a home-cooked :doc:`variational circuit ` written in Qiskit and you want to +# access PennyLane’s seamless differentiability — yep, the PennyLane-Qiskit plugin has you covered +# here, too 🙌. +# +# To keep things simple, let’s use the following Qiskit circuit as our variational circuit. +# + +from qiskit.circuit import ParameterVector, Parameter + +n = 3 + +angles1 = ParameterVector("phis", n - 1) +angle2 = Parameter("theta") + +qc = QuantumCircuit(3) +qc.rx(angles1[0], [0]) +qc.ry(angles1[1], [1]) +qc.ry(angle2, [2]) + +qc.draw("mpl") + +###################################################################### +# .. rst-class:: image-no-text-wrap +# +# .. figure:: ../_static/demonstration_assets/how_to_use_qiskit_1_with_pennylane/qiskit_parameterized_circuit.png +# :align: center +# :width: 30% +# + +###################################################################### +# This circuit contains two sets of differentiable parameters: ``phis`` (length 2) and ``theta`` +# (scalar). +# +# If we give this Qiskit circuit to ``qml.from_qiskit``, we get a quantum function that can +# subsequently be called within a circuit — it’s as if the gates and operations contained within it +# get transferred over to our new QNode. +# + +import pennylane.numpy as np + +pl_qfunc = qml.from_qiskit(qc) + +dev = qml.device("lightning.qubit", wires=n) + +@qml.qnode(dev) +def differentiable_circuit(phis, theta): + pl_qfunc(phis, theta) + return [qml.expval(qml.Z(i)) for i in range(n)] + +phis = np.array([0.6, 0.7]) +theta = np.array([0.19]) + +print(differentiable_circuit(phis, theta)) +print(qml.draw_mpl(differentiable_circuit)(phis, theta)) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# [0.8253356149096783, 0.7648421872844883, 0.9820042351172701] +# + +###################################################################### +# .. rst-class:: image-no-text-wrap +# +# .. figure:: ../_static/demonstration_assets/how_to_use_qiskit_1_with_pennylane/pl_real_world_example.png +# :align: center +# :width: 45% +# + +###################################################################### +# You’ll notice, too, that ``pl_func`` has the call signature that you would expect: +# ``pl_qfunc(phis, theta)``, just like the name we gave them when we defined them in Qiskit +# (``ParameterVector("phis", n-1)`` and ``Parameter("theta")``). +# +# With the circuit being immediately differentiable to PennyLane, let’s define a dummy cost function +# that will sum the output of ``differentiable_circuit``. +# + +def cost(phis, theta): + return np.sum(differentiable_circuit(phis, theta)) + +###################################################################### +# Now we’re in business and can optimize! You can use any suitable optimizer in PennyLane that you +# like, and by calling its ``step_and_cost`` method you can access the updated parameters and cost +# function value after each optimization step. +# + +opt = qml.AdamOptimizer(0.1) + +for i in range(100): + (phis, theta), new_loss = opt.step_and_cost(cost, phis, theta) + +print(f"Optimized parameters: phis = {phis}, theta = {theta}") +print(f"Optimized cost function value: {new_loss}") + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Optimized parameters: phis = [3.12829384 3.12823583], theta = [3.1310224] +# Optimized cost function val: -2.999796472821245 +# + +###################################################################### +# As we expect, the minimum value that our cost function can take is :math:`-3` when all angles of +# rotation are :math:`\pi` (all qubits are rotated into the :math:`\vert 1 \rangle` state). Of course, +# this is just a toy example of an easy optimization problem. But, you can apply this process to a +# larger, more complicated circuit ansatze without worries. +# + +###################################################################### +# Further resources +# ----------------- +# +# There’s so much more to learn about what’s possible in PennyLane, and if you’re coming from Qiskit +# you’re in good hands! The `PennyLany-Qiskit plugin `__ is your personal chaperone to the PennyLane +# ecosystem. You can dive deeper into what’s possible with the PennyLane-Qiskit plugin by visiting the +# plugin homepage and, in the +# mean time, if you have any questions about the plugin, PennyLane, or even Qiskit, drop a question on +# our `Discussion Forum `__ and we’ll promptly respond. +# +# Now that you’ve used PennyLane, every road in the wonderful world of quantum programming SDKs is +# open with no set speed limits 🏎️. Explore our website to see +# the latest and greatest `PennyLane features `__, `Demos `__ and our `blog posts `__, and follow us on +# `LinkedIn `__ or `X (formerly +# Twitter) `__ to stay updated! +# + +###################################################################### diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json new file mode 100644 index 0000000000..1fa88996ca --- /dev/null +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json @@ -0,0 +1,57 @@ +{ + "title": "How to use Qiskit 1.0 with PennyLane", + "authors": [ + { + "username": "isaacdevlugt" + } + ], + "dateOfPublication": "2024-07-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_HowToUseQiskit1WithPL.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_HowToUseQiskit1WithPL.png" + } + ], + "seoDescription": "Learn how to get started using Qiskit 1.0 with PennyLane", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_guide_to_pennylane_knowing_qiskit", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qnn_module_torch", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in new file mode 100644 index 0000000000..3666a853e5 --- /dev/null +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in @@ -0,0 +1,2 @@ +pennylane +qiskit diff --git a/demonstrations_v2/ibm_pennylane/demo.py b/demonstrations_v2/ibm_pennylane/demo.py new file mode 100644 index 0000000000..85f2a8c93b --- /dev/null +++ b/demonstrations_v2/ibm_pennylane/demo.py @@ -0,0 +1,362 @@ +r""" +Using PennyLane with IBM's quantum devices and Qiskit +=================================== + +.. meta:: + :property="og:description": Learn how to use IBM devices with Pennylane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_ibm_pennylane.png + +.. related:: + + quantum_volume Quantum Volume + tutorial_vqe A brief overview of VQE + +*Authors: Kaur Kristjuhan, Clara Ferreira Cores, Mark Nicholas Jones; Molecular Quantum Solutions (MQS) — Posted: 20 June 2023. Last updated: 8 Aug 2024.* + +.. warning:: + This demo includes some outdated features and may not work as intended. + It can still be used as a guideline, but please consult the `PennyLane-Qiskit plugin documentation `__ + to get the most up-to-date information on the features and usage of this plugin. + +Bigger and better quantum computers are built every year. Instead of waiting for the perfect quantum computer to be +released, we can already try out the best hardware that exists today. Experimenting on cutting-edge devices helps us +understand the technology and improve the way we develop quantum software. PennyLane is a fantastic tool for prototyping +quantum algorithms of all kinds, while IBM provides access to the newest and most powerful superconducting quantum devices +available today. Let's combine the two! + +In this tutorial, we'll show you how to use PennyLane to interface with IBM's quantum computing +platform. We will learn how to: + +* discover what kind of devices IBM offers; +* connect to IBM devices through PennyLane's device class; +* use Qiskit Runtime to run hybrid algorithms; +* compare different devices to improve our quantum algorithms. +""" + +############################################################################## +# Using IBM devices +# ----------------- +# IBM offers access to a variety of devices, both classical simulators and real quantum hardware. +# By default, these devices are not included in PennyLane, but after installing the +# `PennyLane-Qiskit plugin `__ with the command ``pip install pennylane-qiskit``, +# they can be used just like any other device offered in PennyLane! +# Currently, there are three devices available — ``Aer``, ``BasicSim`` and ``Remote`` — that can be initialized +# as follows: +import pennylane as qml +from qiskit_aer import AerSimulator + +qubits = 4 +dev_aer = qml.device("qiskit.aer", wires=qubits) +dev_basicsim = qml.device("qiskit.basicsim", wires=qubits) +try: + dev_remote = qml.device("qiskit.remote", wires=qubits, backend=AerSimulator()) +except Exception as e: + print(e) + +############################################################################## +# The last device (``qiskit.remote``) can cause an error if we don't provide a valid account +# token through Qiskit. The Remote device is used to access quantum hardware, so it also requires +# an IBM Quantum account, which can be specified using an identifying token. You can find your +# token by creating or logging into your `IBM Quantum account `__. +# Be careful not to publish code that reveals your token to other people! One way to avoid this +# is by saving your token in a `PennyLane configuration file `__. +# To specify which machine or computational framework these devices actually connect to, we can +# use the ``backend`` argument. + +dev_aer = qml.device("qiskit.aer", wires=qubits) + +############################################################################## +# For the Aer device, different quantum computers can be used by changing the backend to the name +# of the specific simulator method. To see which backends exist, we can call the +# ``capabilities`` function: + +from qiskit_aer import Aer + +print(Aer.backends()) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# ['aer_simulator', 'aer_simulator_statevector', 'aer_simulator_density_matrix', +# 'aer_simulator_stabilizer', 'aer_simulator_matrix_product_state', +# 'aer_simulator_extended_stabilizer', 'aer_simulator_unitary', 'aer_simulator_superop', +# 'qasm_simulator', 'statevector_simulator', 'unitary_simulator', 'pulse_simulator'] + +############################################################################## +# You can find even more details about these devices directly from the IBM Quantum platform. You can +# find information about the size, topology, quantum volume and noise profile of all the devices +# that they have available. Currently, the smallest device has 5 qubits and the largest has 127. On +# the IBM Quantum platform you can also check which devices are free to use and whether any of them +# are temporarily unavailable. You can even check your active jobs and estimated time in the queue +# for any programs you execute. + +############################################################################## +# Qiskit Runtime +# --------------- +# Qiskit Runtime is a quantum computing service provided by IBM intended to make hybrid algorithms +# more efficient to execute. Hybrid algorithms are algorithms where a classical computer and +# quantum computer work together. This often involves the classical algorithm iteratively +# optimizing the quantum circuit, which the quantum computer repeatedly runs. +# +# One such example is the VQE algorithm, which can be used to calculate the ground state energy of +# molecules. It contains an optimization loop, which repeatedly requests the device to run a +# parameterized quantum circuit. Because the optimization algorithm changes the values of the +# parameters, the circuit requested is different each iteration. Also, the change is dependent on +# the results of the previous circuit, which means that there needs to be constant communication +# back and forth between the quantum computer and the classical computer in charge of the +# optimization. +# +# The solution that Qiskit Runtime provides is placing a classical computer in close physical +# proximity of the quantum computer. The user uploads a job to the classical computer, which runs +# the entire hybrid algorithm together with the quantum hardware, with no intermediate user input. +# This automates the iterative process, which otherwise requires time and resources for +# communication between the user and the hardware provider. + +############################################################################## +# Using Qiskit Runtime +# -------------------- +# The PennyLane-Qiskit plugin includes some tools to help create a Qiskit Runtime job. Since using +# Qiskit Runtime only makes sense when using real quantum hardware, we must again specify our IBM +# Quantum account details to run these jobs. +# +# First, we set up our problem as usual, and then retrieve a program ID from IBM, which gives us a +# place to upload our job: + +from pennylane import numpy as pnp +from qiskit_ibm_runtime import QiskitRuntimeService + +import pennylane as qml + +# Obtaining the Hamiltonian for H2 from PennyLane QChem dataset +[dataset] = qml.data.load("qchem", molname="H2", bondlength=0.742, basis="STO-3G") +H = dataset.hamiltonian +qubits = 4 + +service = QiskitRuntimeService() +# Gets a 127 qubit device from IBM +backend = service.least_busy(n_qubits=127, simulator=False, operational=True) + +try: + # Although we only need 4 qubits, our device has 127 qubits, therefore we initialize with wires=127 + dev = qml.device("qiskit.remote", wires=127, backend=backend) +except Exception as e: + print(e) + +############################################################################## +# Next, we specify our quantum circuit. Although there are many circuits to choose from, it is +# important to know that before a circuit is executed on hardware, it undergoes a transpilation +# step, which converts your circuit into a different, but equivalent, circuit. The purpose of this +# step is to ensure that only operations that are native to the quantum computer are used. With +# parameterized gates, however, this may cause some unexpected behavior, such as the emergence of +# more parameters when the transpiler attempts to decompose a complicated gate, such as +# :class:`~pennylane.AllSinglesDoubles`. These types of issues will likely be fixed in the future, but, when in doubt, +# it is preferable to use simpler gates where possible. We will use a simple four-qubit circuit +# with one parameter that is designed specifically for the :math:`H_2` molecule: + + +def four_qubit_ansatz(theta): + # initial state 1100: + qml.PauliX(wires=0) + qml.PauliX(wires=1) + + # change of basis + qml.RX(pnp.pi / 2, wires=0) + qml.Hadamard(wires=1) + qml.Hadamard(wires=2) + qml.Hadamard(wires=3) + + qml.CNOT(wires=[3, 2]) + qml.CNOT(wires=[2, 1]) + qml.CNOT(wires=[1, 0]) + + qml.RZ(theta, wires=0) + + qml.CNOT(wires=[1, 0]) + qml.CNOT(wires=[2, 1]) + qml.CNOT(wires=[3, 2]) + + # invert change of basis + qml.RX(-pnp.pi / 2, wires=0) + qml.Hadamard(wires=1) + qml.Hadamard(wires=2) + qml.Hadamard(wires=3) + + +############################################################################## +# Finally, we can run our example VQE algorithm. In order to query the quantum computer iteratively +# we need to initialize a `Qiskit Session `__, which +# allows multiple jobs from a single algorithm to be ran sequentially without interruptions. We +# also need to provide our VQE algorithm an optimizer. In this case, we will be using the :class:`~.pennylane.GradientDescentOptimizer`. + +from pennylane_qiskit import qiskit_session + + +@qml.qnode(dev) +def cost_fn(theta): + four_qubit_ansatz(theta) + return qml.expval(H) + + +max_iterations = 40 +theta = pnp.array(0.0, requires_grad=True) +opt = qml.GradientDescentOptimizer(stepsize=0.4) +energies = [] + +with qiskit_session(dev) as session: + for n in range(max_iterations): + theta, prev_energy = opt.step_and_cost(cost_fn, theta) + energies.append(prev_energy) + +###################################################################### +# .. note :: +# +# This may take a long time depending on how busy the hardware is. Depending on the tier of +# your IBM plan, your session may also be interrupted before the optimization can finish. +# Luckily, the rest of the workflow demonstrates how to run VQE with a simulator instead, which +# has no such restrictions at all. + +############################################################################## +# Benchmarking +# ~~~~~~~~~~~~ +# One of the reasons why we even want to have access to these various devices and backends is so +# that we can benchmark the capabilities of the algorithms that we develop. Some simulators are +# particularly good with certain types of circuits, whereas other simulators are more general and +# may provide resources for simulating noise which mimics the kind of errors that real quantum +# hardware produces. Switching between your devices helps you learn more about your algorithm and +# can potentially provide guidance on how to make it better. For example, we can compare the +# performance of the default PennyLane simulator to the Qiskit ``'aer_simulator'`` by running the same +# VQE algorithm on both. The difference between these two devices is that the ``'aer_simulator'`` uses a +# finite number of shots to estimate the energy in each iteration, rather than performing an exact +# calculation using the information hidden in the vector representation of the quantum state. + +dev1 = qml.device("default.qubit", wires=4) +shots = 8000 +dev2 = qml.device("qiskit.aer", wires=4, shots=shots) + + +@qml.qnode(dev1) +def cost_fn_1(theta): + four_qubit_ansatz(theta) + return qml.expval(H) + + +@qml.qnode(dev2) +def cost_fn_2(theta): + four_qubit_ansatz(theta) + return qml.expval(H) + + +# we can also use the qnode to draw the circuit +import matplotlib.pyplot as plt + +qml.draw_mpl(cost_fn_1, decimals=2)(theta=1.0) +plt.show() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/ibm_pennylane/figure_1.png +# :align: center +# :width: 80% +# :alt: Circuit +# :target: javascript:void(0); + +stepsize = 0.4 +max_iterations = 40 +opt = qml.GradientDescentOptimizer(stepsize=stepsize) +theta_1 = pnp.array(0.0, requires_grad=True) +theta_2 = pnp.array(0.0, requires_grad=True) +energies_1 = [] +energies_2 = [] +for n in range(max_iterations): + theta_1, prev_energy_1 = opt.step_and_cost(cost_fn_1, theta_1) + theta_2, prev_energy_2 = opt.step_and_cost(cost_fn_2, theta_2) + print(prev_energy_1, prev_energy_2) + energies_1.append(prev_energy_1) + energies_2.append(prev_energy_2) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# -1.1173489211359304 -1.1190829868933736 +# -1.1279998277357466 -1.1288232086760344 +# -1.1326490062948753 -1.1301129826377698 +# -1.1346624646306156 -1.1359551977827906 +# -1.135531456349987 -1.1361153521202156 +# -1.1359059478903804 -1.1365135907218555 +# -1.1360672311675288 -1.1378046643806679 +# -1.1361366722397177 -1.135703178347981 +# -1.1361665667682972 -1.1357895389865689 +# -1.1361794357654167 -1.1369628601568447 +# -1.1361849754890518 -1.1365783784951322 +# -1.1361873601539711 -1.1367306582741445 +# -1.1361883866679017 -1.1358320382653255 +# -1.1361888285450743 -1.1357663570027223 +# -1.1361890187570935 -1.135670418637738 +# -1.1361891006364095 -1.1369084485357166 +# -1.1361891358824536 -1.139272401360956 +# -1.1361891510545838 -1.137130432389924 +# -1.136189157585629 -1.1377776180459274 +# -1.1361891603970047 -1.1358917536737867 +# -1.1361891616071986 -1.1370070425290821 +# -1.1361891621281424 -1.135792429417887 +# -1.13618916235239 -1.1350561467266231 +# -1.13618916244892 -1.1366759212135573 +# -1.1361891624904732 -1.1351253597692734 +# -1.13618916250836 -1.1362073324228987 +# -1.13618916251606 -1.1366017151897079 +# -1.136189162519374 -1.1362493563165617 +# -1.136189162520801 -1.1378309783921152 +# -1.1361891625214149 -1.1350975937163135 +# -1.1361891625216796 -1.1372437534918245 +# -1.136189162521793 -1.1361363466968788 +# -1.1361891625218425 -1.136401712436813 +# -1.1361891625218634 -1.1346185510801001 +# -1.136189162521872 -1.1351658522378076 +# -1.1361891625218763 -1.1350958264741222 +# -1.1361891625218783 -1.135516284054897 +# -1.136189162521879 -1.137538330500378 +# -1.1361891625218792 -1.1359072863719688 +# -1.1361891625218794 -1.1369053955536899 + + +############################################################################## +# We can clearly see the difference between the two devices when we plot the energies over each +# iteration: + +plt.plot(energies_1, color="r", label="default.qubit") +plt.plot(energies_2, color="b", label="qiskit.aer") + +# min energy = min eigenvalue +min_energy = min(qml.eigvals(H)) +z = [min_energy] * max_iterations + +plt.plot(z, "--", color="k", label="Exact answer") +plt.xlabel("VQE iterations") +plt.ylabel("Energy (Ha)") +plt.legend() +plt.show() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/ibm_pennylane/figure_2.png +# :align: center +# :width: 80% +# :alt: Iterations +# :target: javascript:void(0); + +############################################################################## +# The device with the finite number of shots is unable to converge to the right answer because it +# is limited by the precision of the result in each iteration. This is an effect that will +# certainly appear in real quantum devices too, and it can be instructive to study this effect +# independently of all the other limitations on real devices, such as decoherence, limited +# topology and readout errors. +# +# This tutorial has demonstrated how and why to use quantum computing hardware provided by IBM using PennyLane. To read +# more about the details and possibilities of the PennyLane-Qiskit plugin, `read the documentation `__. +# diff --git a/demonstrations_v2/ibm_pennylane/metadata.json b/demonstrations_v2/ibm_pennylane/metadata.json new file mode 100644 index 0000000000..32b1483537 --- /dev/null +++ b/demonstrations_v2/ibm_pennylane/metadata.json @@ -0,0 +1,47 @@ +{ + "title": "Using PennyLane with IBM's quantum devices and Qiskit", + "authors": [ + { + "username": "kkristjuhan" + }, + { + "username": "cfcores" + }, + { + "username": "mnjones" + } + ], + "dateOfPublication": "2023-06-20T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/ibm_pennylane/thumbnail_tutorial_ibm_pennylane.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_ibm_pennylane.png" + } + ], + "seoDescription": "Learn how to use IBM devices with PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/ibm_pennylane/requirements.in b/demonstrations_v2/ibm_pennylane/requirements.in new file mode 100644 index 0000000000..7d0a480805 --- /dev/null +++ b/demonstrations_v2/ibm_pennylane/requirements.in @@ -0,0 +1,5 @@ +matplotlib +pennylane +pennylane_qiskit +qiskit_aer +qiskit_ibm_runtime diff --git a/demonstrations_v2/learning2learn/demo.py b/demonstrations_v2/learning2learn/demo.py new file mode 100644 index 0000000000..eb948e2b54 --- /dev/null +++ b/demonstrations_v2/learning2learn/demo.py @@ -0,0 +1,1181 @@ +r""" +Learning to learn with quantum neural networks +============================================== + +.. meta:: + :property="og:description": Use a classical recurrent neural network to initilize the parameters of a variational quatum algorithm. + :property="og:image": ../_static/demonstration_assets/learning2learn/thumbnail.png + +.. related:: + + tutorial_qaoa_intro Intro to QAOA + tutorial_qaoa_maxcut QAOA for MaxCut problem + +*Author: Stefano Mangini — Posted: 02 March 2021. Last updated: 15 September 2021.* + + +In this demo we recreate the architecture proposed +in *Learning to learn with quantum neural networks via +classical neural networks* [#l2l]_, using **PennyLane** and **TensorFlow**. +We use classical recurrent neural networks to assist +the optimization of variational quantum algorithms. + +We start with a brief theoretical overview explaining the problem +and the setup used to solve it. After that, we deep dive into the +code to build a fully functioning model, ready to be further developed +or customized for your own needs. Without further ado, let’s begin! + + +Problem: Optimization of Variational Quantum Algorithms +------------------------------------------------------- + +Recently, a big effort by the quantum computing community has been +devoted to the study of variational quantum algorithms (VQAs) +which leverage quantum circuits with fixed shape and tunable +parameters. The idea is similar to +classical neural networks, where the weights of the network are +optimized during training. Similarly, once the shape of the variational quantum +circuit is chosen — something that is very difficult and sensitive to +the particular task at hand — its tunable parameters are optimized +iteratively by minimizing a cost (or loss) function, which measures how +good the quantum algorithm is performing (see [#vqas]_ for a +thorough overview on VQAs). + +A major challenge for VQAs relates to the optimization of tunable +parameters, which was shown to be a very hard task [#barren]_, [#vqas]_ . +Parameter initialization plays a key role in this scenario, +since initializing the parameters in the proximity of an optimal +solution leads to faster convergence and better results. Thus, a good initialization +strategy is crucial to promote the convergence of local optimizers to +local extrema and to select reasonably good local minima. By local +optimizer, we mean a procedure that moves from one solution to another +by small (local) changes in parameter space. These are opposed to global +search methods, which take into account large sections of +parameter space to propose a new solution. + +One such strategy could come from the classical machine learning +literature. + +Solution: Classical Recurrent Neural Networks +------------------------------------------------------------------ + +By building on results from the *meta-learning* literature in machine learning, +authors in [#l2l]_ propose to use a Recurrent Neural Network (RNN) +as a black-box controller to optimize the parameters of +variational quantum algorithms, as shown in the figure below. The cost +function used is the expectation value :math:`\langle H \rangle_{\boldsymbol{\theta}} = \langle \psi_{\boldsymbol{\theta}} | H | \psi_{\boldsymbol{\theta}}\rangle` +of a Hamiltonian :math:`H` with respect to the parametrized state +:math:`|\psi_\boldsymbol{\theta}\rangle` evolved by applying the variational quantum circuit to the zero state :math:`|00\cdots0\rangle.` + +.. figure:: ../_static/demonstration_assets/learning2learn/HybridLSTM.png + :align: center + :width: 100% + +Given parameters :math:`\boldsymbol{\theta}_{t-1}` of the variational quantum circuit, +the cost function :math:`y_{t-1},` and the hidden state of the +classical network :math:`\boldsymbol{h}_{t-1}` at the previous time step, the +recurrent neural network proposes a new +guess for the parameters :math:`\boldsymbol{\theta}_t,` which are +then fed into the quantum computer to evaluate the +cost function :math:`y_t.` By repeating this cycle a few times, and +by training the weights of the recurrent neural network to minimize +the loss function :math:`y_t,` a good initialization heuristic is +found for the parameters :math:`\boldsymbol{\theta}` of the variational +quantum circuit. + +At a given iteration, the RNN receives as input the previous cost +function :math:`y_t` evaluated on the quantum computer, where +:math:`y_t` is the estimate of :math:`\langle H\rangle_{t},` as well as +the parameters :math:`\boldsymbol{\theta}_t` for which the variational +circuit was evaluated. The RNN at this time step also receives +information stored in its internal hidden state from the previous time +step :math:`\boldsymbol{h}_t.` The RNN itself has trainable parameters :math:`\phi,` +and hence it applies the parametrized mapping: + +.. math:: \boldsymbol{h}_{t+1}, \boldsymbol{\theta}_{t+1} = \text{RNN}_{\phi}(\boldsymbol{h}_{t}, \boldsymbol{\theta}_{t}, y_{t}), + +which generates a new suggestion for the variational parameters as well +as a new internal state. +Upon training the weights :math:`\phi,` the RNN +eventually learns a good heuristic to suggest optimal parameters for the +quantum circuit. + +Thus, by training on a dataset of graphs, the RNN can subsequently be used to +provide suggestions for starting points on new graphs! We are not directly optimizing the variational parameters of +the quantum circuit, but instead, we let the RNN figure out how to do that. +In this sense, we are learning (training the RNN) how to learn (how to optimize a variational quantum circuit). + + +**VQAs in focus: QAOA for MaxCut** + +There are multiple VQAs for which this hybrid training routine could +be used, some of them directly analyzed in [#l2l]_. In the +following, we focus on one such example, the +Quantum Approximate Optimization Algorithm (QAOA) for solving +the MaxCut problem [#maxcut]_. Thus, referring to the picture above, +the shape of the variational circuit is the one dictated by the QAOA +ansatz, and such a quantum circuit is used to evaluate the cost +Hamiltonian :math:`H` of the MaxCut problem. +You can check out a great tutorial on +[how to use QAOA for solving graph problems](https://pennylane.ai/qml/demos/tutorial_qaoa_intro.html). + +.. note:: + Running the tutorial (excluding the Appendix) requires approx. ~13m. + +""" + +###################################################################### +# **Importing the required packages** +# +# +# During this tutorial, we will use +# **PennyLane** for executing quantum circuits and for integrating +# seamlessly with **TensorFlow**, which will be used for creating the RNN. +# + +# Quantum Machine Learning +import pennylane as qml +from pennylane import qaoa + +# Classical Machine Learning +import tensorflow as tf + +tf.get_logger().setLevel("ERROR") + +# Generation of graphs +import networkx as nx + +# Standard Python libraries +import numpy as np +import matplotlib.pyplot as plt +import random + +# Fix the seed for reproducibility, which affects all random functions in this demo +random.seed(42) +np.random.seed(42) +tf.random.set_seed(42) + + +###################################################################### +# Generation of training data: graphs +# ----------------------------------- +# +# The first step is to gather or +# create a good dataset that will be used to train the model +# and test its performance. In our case, we are analyzing MaxCut, +# which deals with the problem of finding a good binary partition +# of nodes in a graph such that the number of edges *cut* by such a +# separation is maximized. We start by generating some +# random graphs :math:`G_{n,p}` where: +# +# * :math:`n` is the number of nodes in each graph, +# * :math:`p` is the probability of having an edge between two nodes. +# + + +def generate_graphs(n_graphs, n_nodes, p_edge): + """Generate a list containing random graphs generated by Networkx.""" + + datapoints = [] + for _ in range(n_graphs): + random_graph = nx.gnp_random_graph(n_nodes, p=p_edge) + datapoints.append(random_graph) + return datapoints + + +###################################################################### +# An example of a random graph generated using the function +# ``generate_graphs`` just defined: +# + +# Define parameters of the graphs +n_graphs = 20 +n_nodes = 7 +p_edge = 3.0 / n_nodes +graphs = generate_graphs(n_graphs, n_nodes, p_edge) + +nx.draw(graphs[0]) + +###################################################################### +# .. figure:: ../_static/demonstration_assets/learning2learn/rendered_Graph0.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# + +###################################################################### +# Variational Quantum Circuit: QAOA +# --------------------------------- +# +# Now that we have a dataset, we move on by creating the QAOA quantum +# circuits using PennyLane’s built-in sub-packages. In particular, using +# PennyLane’s ``qaoa`` module, we will able to create fully functioning +# quantum circuits for the MaxCut problem, with very few lines of code. +# + + +def qaoa_from_graph(graph, n_layers=1): + """Uses QAOA to create a cost Hamiltonian for the MaxCut problem.""" + + # Number of qubits (wires) equal to the number of nodes in the graph + wires = range(len(graph.nodes)) + + # Define the structure of the cost and mixer subcircuits for the MaxCut problem + cost_h, mixer_h = qaoa.maxcut(graph) + + # Defines a layer of the QAOA ansatz from the cost and mixer Hamiltonians + def qaoa_layer(gamma, alpha): + qaoa.cost_layer(gamma, cost_h) + qaoa.mixer_layer(alpha, mixer_h) + + # Creates the actual quantum circuit for the QAOA algorithm + def circuit(params, **kwargs): + for w in wires: + qml.Hadamard(wires=w) + qml.layer(qaoa_layer, n_layers, params[0], params[1]) + return qml.expval(cost_h) + + # Evaluates the cost Hamiltonian + def hamiltonian(params, **kwargs): + """Evaluate the cost Hamiltonian, given the angles and the graph.""" + + dev = qml.device("default.qubit", wires=len(graph.nodes)) + + # This qnode evaluates the expectation value of the cost hamiltonian operator + cost = qml.QNode(circuit, dev, diff_method="backprop", interface="tf") + + return cost(params) + + return hamiltonian + + +###################################################################### +# Before continuing, let’s see how to use these functions. +# + +# Create an instance of a QAOA circuit given a graph. +cost = qaoa_from_graph(graph=graphs[0], n_layers=1) + +# Since we use only one layer in QAOA, params have the shape 1 x 2, +# in the form [[alpha, gamma]]. +x = tf.Variable([[0.5], [0.5]], dtype=tf.float32) + +# Evaluate th QAOA instance just created with some angles. +print(cost(x)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# tf.Tensor(-3.193267957255582, shape=(), dtype=float64) +# + + +###################################################################### +# Recurrent Neural Network: LSTM +# ------------------------------ +# +# So far, we have defined the machinery which lets us build the QAOA +# algorithm for solving the MaxCut problem. +# Now we wish to implement the Recurrent Neural Network architecture +# explained previously. As proposed in the original +# paper, we will build a custom model of a Long-Short Term +# Memory (LSTM) network, capable of handling the hybrid data passing between +# classical and quantum procedures. For this task, we will use ``Keras`` +# and ``TensorFlow``. +# + + +###################################################################### +# First of all, let’s define the elemental building block of the model, +# an LSTM cell (see `TensorFlow +# documentation `__ +# for further details). +# + +# Set the number of layers in the QAOA ansatz. +# The higher the better in terms of performance, but it also gets more +# computationally expensive. For simplicity, we stick to the single layer case. +n_layers = 1 + +# Define a single LSTM cell. +# The cell has two units per layer since each layer in the QAOA ansatz +# makes use of two parameters. +cell = tf.keras.layers.LSTMCell(2 * n_layers) + + +###################################################################### +# Using the ``qaoa_from_graph`` function, we create a list +# ``graph_cost_list`` containing the cost functions of a set of graphs. +# You can see this as a preprocessing step of the data. +# + +# We create the QAOA MaxCut cost functions of some graphs +graph_cost_list = [qaoa_from_graph(g) for g in graphs] + + +###################################################################### +# At this stage, we seek to reproduce the recurrent behavior depicted in +# the picture above, outlining the functioning of an RNN as a black-box +# optimizer. We do so by defining two functions: +# +# * ``rnn_iteration``: accounts for the computations happening on a single time step in the figure. +# It performs the calculation inside the CPU and evaluates the quantum circuit on the QPU to obtain +# the loss function for the current parameters. +# +# * ``recurrent_loop``: as the name suggests, it accounts for the creation of the recurrent loop +# of the model. In particular, it makes consecutive calls to the ``rnn_iteration`` function, +# where the outputs of a previous call are fed as inputs of the next call. +# + + +def rnn_iteration(inputs, graph_cost, n_layers=1): + """Perform a single time step in the computational graph of the custom RNN.""" + + # Unpack the input list containing the previous cost, parameters, + # and hidden states (denoted as 'h' and 'c'). + prev_cost = inputs[0] + prev_params = inputs[1] + prev_h = inputs[2] + prev_c = inputs[3] + + # Concatenate the previous parameters and previous cost to create new input + new_input = tf.keras.layers.concatenate([prev_cost, prev_params]) + + # Call the LSTM cell, which outputs new values for the parameters along + # with new internal states h and c + new_params, [new_h, new_c] = cell(new_input, states=[prev_h, prev_c]) + + # Reshape the parameters to correctly match those expected by PennyLane + _params = tf.reshape(new_params, shape=(2, n_layers)) + + # Evaluate the cost using new angles + _cost = graph_cost(_params) + + # Reshape to be consistent with other tensors + new_cost = tf.reshape(tf.cast(_cost, dtype=tf.float32), shape=(1, 1)) + + return [new_cost, new_params, new_h, new_c] + + +def recurrent_loop(graph_cost, n_layers=1, intermediate_steps=False): + """Creates the recurrent loop for the Recurrent Neural Network.""" + + # Initialize starting all inputs (cost, parameters, hidden states) as zeros. + initial_cost = tf.zeros(shape=(1, 1)) + initial_params = tf.zeros(shape=(1, 2 * n_layers)) + initial_h = tf.zeros(shape=(1, 2 * n_layers)) + initial_c = tf.zeros(shape=(1, 2 * n_layers)) + + # We perform five consecutive calls to 'rnn_iteration', thus creating the + # recurrent loop. More iterations lead to better results, at the cost of + # more computationally intensive simulations. + out0 = rnn_iteration( + [initial_cost, initial_params, initial_h, initial_c], graph_cost + ) + out1 = rnn_iteration(out0, graph_cost) + out2 = rnn_iteration(out1, graph_cost) + out3 = rnn_iteration(out2, graph_cost) + out4 = rnn_iteration(out3, graph_cost) + + # This cost function takes into account the cost from all iterations, + # but using different weights. + loss = tf.keras.layers.average( + [0.1 * out0[0], 0.2 * out1[0], 0.3 * out2[0], 0.4 * out3[0], 0.5 * out4[0]] + ) + + if intermediate_steps: + return [out0[1], out1[1], out2[1], out3[1], out4[1], loss] + else: + return loss + + +###################################################################### +# **The cost function** +# +# +# A key part in the ``recurrent_loop`` function is given by the +# definition of the variable ``loss``. In order to drive the learning +# procedure of the weights in the LSTM cell, a cost function is needed. +# While in the original paper the authors suggest using a measure called +# *observed improvement*, for simplicity here we use an easier cost +# function :math:`\cal{L}(\phi)` defined as: +# +# .. math:: \cal{L}(\phi) = {\bf w} \cdot {\bf y}_t(\phi), +# +# where :math:`{\bf y}_t(\phi) = (y_1, \cdots, y_5)` contains the +# Hamiltonian cost functions from all iterations, and :math:`{\bf w}` are +# just some coefficients weighting the different steps in the recurrent +# loop. In this case, we used :math:`{\bf w}=\frac{1}{5} (0.1, 0.2, 0.3, 0.4, 0.5),` +# to give more importance to the last steps rather than the initial steps. +# Intuitively in this way the RNN is more free (low coefficient) to +# explore a larger portion of parameter space during the first steps of +# optimization, while it is constrained (high coefficient) to select an +# optimal solution towards the end of the procedure. Note that one could +# also use just the final cost function from the last iteration to drive +# the training procedure of the RNN. However, using values also from +# intermediate steps allows for a smoother suggestion routine, since even +# non-optimal parameter suggestions from early steps are penalized using +# :math:`\cal{L}(\phi).` +# + + +###################################################################### +# **Training** +# +# +# Now all the cards are on the table and we just need to prepare a +# training routine and then run it! +# +# First of all, let’s wrap a single gradient descent step inside a custom +# function ``train_step``. +# + + +def train_step(graph_cost): + """Single optimization step in the training procedure.""" + + with tf.GradientTape() as tape: + # Evaluates the cost function + loss = recurrent_loop(graph_cost) + + # Evaluates gradients, cell is the LSTM cell defined previously + grads = tape.gradient(loss, cell.trainable_weights) + + # Apply gradients and update the weights of the LSTM cell + opt.apply_gradients(zip(grads, cell.trainable_weights)) + return loss + + +###################################################################### +# We are now ready to start the training. In particular, we will perform a +# stochastic gradient descent in the parameter space of the weights of the +# LSTM cell. For each graph in the training set, we evaluate gradients and +# update the weights accordingly. Then, we repeat this procedure for +# multiple times (epochs). +# +# .. note:: +# Be careful when using bigger datasets or training for larger +# epochs, this may take a while to execute. +# + +# Select an optimizer +opt = tf.keras.optimizers.Adam(learning_rate=0.1) + +# Set the number of training epochs +epochs = 5 + +for epoch in range(epochs): + print(f"Epoch {epoch+1}") + total_loss = np.array([]) + for i, graph_cost in enumerate(graph_cost_list): + loss = train_step(graph_cost) + total_loss = np.append(total_loss, loss.numpy()) + # Log every 5 batches. + if i % 5 == 0: + print(f" > Graph {i+1}/{len(graph_cost_list)} - Loss: {loss[0][0]}") + print(f" >> Mean Loss during epoch: {np.mean(total_loss)}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Epoch 1 +# > Graph 1/20 - Loss: -1.6641689538955688 +# > Graph 6/20 - Loss: -1.4186843633651733 +# > Graph 11/20 - Loss: -1.3757232427597046 +# > Graph 16/20 - Loss: -1.294339656829834 +# >> Mean Loss during epoch: -1.7352586269378663 +# Epoch 2 +# > Graph 1/20 - Loss: -2.119091749191284 +# > Graph 6/20 - Loss: -1.4789190292358398 +# > Graph 11/20 - Loss: -1.3779840469360352 +# > Graph 16/20 - Loss: -1.2963457107543945 +# >> Mean Loss during epoch: -1.8252217948436738 +# Epoch 3 +# > Graph 1/20 - Loss: -2.1322619915008545 +# > Graph 6/20 - Loss: -1.459418535232544 +# > Graph 11/20 - Loss: -1.390620470046997 +# > Graph 16/20 - Loss: -1.3165746927261353 +# >> Mean Loss during epoch: -1.8328069806098939 +# Epoch 4 +# > Graph 1/20 - Loss: -2.1432175636291504 +# > Graph 6/20 - Loss: -1.476362943649292 +# > Graph 11/20 - Loss: -1.3938289880752563 +# > Graph 16/20 - Loss: -1.3140206336975098 +# >> Mean Loss during epoch: -1.8369774043560028 +# Epoch 5 +# > Graph 1/20 - Loss: -2.1429405212402344 +# > Graph 6/20 - Loss: -1.477513074874878 +# > Graph 11/20 - Loss: -1.3909202814102173 +# > Graph 16/20 - Loss: -1.315887689590454 +# >> Mean Loss during epoch: -1.8371947884559632 +# + + +###################################################################### +# As you can see, the Loss for each graph keeps decreasing across epochs, +# indicating that the training routine is working correctly. +# + + +###################################################################### +# Results +# -------------------- +# +# Let’s see how to use the optimized RNN as an initializer for the angles +# in the QAOA algorithm. +# +# First, we pick a new graph, not present in the training dataset: +# + +new_graph = nx.gnp_random_graph(7, p=3 / 7) +new_cost = qaoa_from_graph(new_graph) + +nx.draw(new_graph) + +###################################################################### +# .. figure:: ../_static/demonstration_assets/learning2learn/rendered_Graph1.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# + + +###################################################################### +# Then we apply the trained RNN to this new graph, saving intermediate +# results coming from all the recurrent iterations in the network. +# + +# Apply the RNN (be sure that training was performed) +res = recurrent_loop(new_cost, intermediate_steps=True) + +# Extract all angle suggestions +start_zeros = tf.zeros(shape=(2 * n_layers, 1)) +guess_0 = res[0] +guess_1 = res[1] +guess_2 = res[2] +guess_3 = res[3] +guess_4 = res[4] +final_loss = res[5] + +# Wrap them into a list +guesses = [start_zeros, guess_0, guess_1, guess_2, guess_3, guess_4] + +# Losses from the hybrid LSTM model +lstm_losses = [new_cost(tf.reshape(guess, shape=(2, n_layers))) for guess in guesses] + + +###################################################################### +# **Plot of the loss function** +# +# +# We can plot these losses to see how well the RNN proposes new guesses for +# the parameters. +# + +fig, ax = plt.subplots() + +plt.plot(lstm_losses, color="blue", lw=3, ls="-.", label="LSTM") + +plt.grid(ls="--", lw=2, alpha=0.25) +plt.ylabel("Cost function", fontsize=12) +plt.xlabel("Iteration", fontsize=12) +plt.legend() +ax.set_xticks([0, 5, 10, 15, 20]) +plt.show() + +###################################################################### +# .. figure:: ../_static/demonstration_assets/learning2learn/rendered_LossLSTM.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# +# That’s remarkable! The RNN learned to propose new parameters such that +# the MaxCut cost is minimized very rapidly: in just a few iterations the +# loss reaches a minimum. Actually, it takes just a single step for the LSTM +# to find a very good minimum. In fact, due to the recurrent loop, the loss +# in each time step is directly dependent on the previous ones, with the first +# iteration thus having a lot of influence on the loss function defined above. +# Changing the loss function, for example giving less importance to initial +# steps and just focusing on the last one, leads to different optimization +# behaviors, but with the same final results. +# + + +###################################################################### +# **Comparison with standard Stochastic Gradient Descent (SGD)** +# +# How well does this method compare with +# standard optimization techniques, for example, leveraging Stochastic +# Gradient Descent (SGD) to optimize the parameters in the QAOA? +# +# Let’s check it out. +# + +# Parameters are randomly initialized +x = tf.Variable(np.random.rand(2, 1)) + +# We set the optimizer to be a Stochastic Gradient Descent +opt = tf.keras.optimizers.SGD(learning_rate=0.01) +step = 15 + +# Training process +steps = [] +sdg_losses = [] +for _ in range(step): + with tf.GradientTape() as tape: + loss = new_cost(x) + + steps.append(x) + sdg_losses.append(loss) + + gradients = tape.gradient(loss, [x]) + opt.apply_gradients(zip(gradients, [x])) + print(f"Step {_+1} - Loss = {loss}") + +print(f"Final cost function: {new_cost(x).numpy()}\nOptimized angles: {x.numpy()}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Step 1 - Loss = -4.1700805 +# Step 2 - Loss = -4.67503588 +# Step 3 - Loss = -5.09949909 +# Step 4 - Loss = -5.40388533 +# Step 5 - Loss = -5.59529203 +# Step 6 - Loss = -5.70495197 +# Step 7 - Loss = -5.7642561 +# Step 8 - Loss = -5.79533198 +# Step 9 - Loss = -5.81138752 +# Step 10 - Loss = -5.81966529 +# Step 11 - Loss = -5.82396722 +# Step 12 - Loss = -5.82624537 +# Step 13 - Loss = -5.82749126 +# Step 14 - Loss = -5.82820626 +# Step 15 - Loss = -5.82864379 +# Final cost function: -5.828932361904984 +# Optimized angles: [[ 0.5865477 ] +# [-0.3228858]] +# + +fig, ax = plt.subplots() + +plt.plot(sdg_losses, color="orange", lw=3, label="SGD") + +plt.plot(lstm_losses, color="blue", lw=3, ls="-.", label="LSTM") + +plt.grid(ls="--", lw=2, alpha=0.25) +plt.legend() +plt.ylabel("Cost function", fontsize=12) +plt.xlabel("Iteration", fontsize=12) +ax.set_xticks([0, 5, 10, 15, 20]) +plt.show() + +###################################################################### +# .. figure:: ../_static/demonstration_assets/learning2learn/rendered_LossConfrontation.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# + + +###################################################################### +# *Hurray!* 🎉🎉 +# +# As is clear from the picture, the RNN reaches a better minimum in +# fewer iterations than the standard SGD. +# Thus, as the authors suggest, the trained RNN can +# be used for a few iterations at the start of the training procedure to +# initialize the parameters of the quantum circuit close to an optimal +# solution. Then, a standard optimizer like the SGD can be used to +# fine-tune the proposed parameters and reach even better solutions. +# While on this small scale example the benefits of using an LSTM to +# initialize parameters may seem modest, on more complicated instances +# and problems it can make a big difference, since, on random +# initialization of the parameters, standard local optimizer may +# encounter problems finding a good minimization direction (for further +# details, see [#l2l]_, [#vqas]_). +# + + +###################################################################### +# Final remarks +# ----------------- +# +# In this demo, we saw how to use a recurrent neural network +# as a black-box optimizer to initialize the parameters in +# a variational quantum circuit close to an optimal solution. +# We connected MaxCut QAOA quantum circuits in PennyLane +# with an LSTM built with TensorFlow, and we used a custom hybrid training +# routine to optimize the whole network. +# +# Such architecture proved itself to be a good candidate for the +# initialization problem of Variational Quantum Algorithms, since it +# reaches good optimal solutions in very few iterations. Besides, the +# architecture is quite general since the same machinery can be used for +# graphs having a generic number of nodes (see "Generalization Performances" +# in the Appendix). +# +# **What’s next?** +# +# +# But the story does not end here. There are multiple ways this work could +# be improved. Here are a few: +# +# * Use the proposed architecture for VQAs other than QAOA for MaxCut. +# You can check the paper [#l2l]_ to get some inspiration. +# * Scale up the simulation, using bigger graphs and longer recurrent +# loops. +# * While working correctly, the training routine is quite basic and it +# could be improved for example by implementing batch learning or a +# stopping criterion. Also, one could implement the +# *observed improvement* loss function, as used in the original paper +# [#l2l]_. +# * Depending on the problem, you may wish to transform the functions +# ``rnn_iteration`` and ``recurrent_loop`` to actual ``Keras Layers`` +# and ``Models``. This way, by compiling the model before the training +# takes place, ``TensorFlow`` can create the computational graph of the +# model and train more efficiently. You can find +# some ideas below to start working on it. +# +# If you're interested, in the Appendix below you can find some more details +# and insights about this model. Go check it out! +# +# If you have any doubt, or wish to discuss about the project don’t +# hesitate to contact me, I’ll be very happy to help you as much as I can +# 😁 +# +# Have a great quantum day! +# + + +###################################################################### +# References +# ---------- +# +# .. [#l2l] +# +# Verdon G., Broughton M., McClean J. R., Sung K. J., Babbush R., +# Jiang Z., Neven H. and Mohseni M. "Learning to learn with quantum neural networks via classical neural +# networks", `arXiv:1907.05415 `__ (2019). +# +# .. [#vqas] +# +# Cerezo M., Arrasmith A., Babbush R., Benjamin S. C., Endo S., +# Fujii K., McClean J. R., Mitarai K., Yuan X., Cincio L. and Coles P. +# J. "Variational Quantum Algorithms", `arXiv:2012.09265 `__ (2020). +# +# .. [#barren] +# +# McClean J.R., Boixo S., Smelyanskiy V.N. et al. +# "Barren plateaus in quantum neural network training landscapes", +# `Nat Commun 9, 4812 `__ (2018). +# +# .. [#maxcut] +# +# MaxCut problem: `https://pennylane.ai/qml/demos/tutorial_qaoa_maxcut/ `__. +# +# +# +# + + +###################################################################### +# Appendix +# ----------------- +# +# In this appendix you can find further details about the Learning to Learn approach +# introduced in this tutorial. +# +# Generalization performances +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# A very interesting feature of this model, is that it can be +# straightforwardly applied to graphs having a different number of nodes. In +# fact, until now our analysis focused only on graphs with the same number +# of nodes for ease of explanation, and there is no actual restriction in +# this respect. The same machinery works fine for any graph, since the +# number of QAOA parameters are only dependent on the number of layers in +# the ansatz, and not on the number of qubits (equal to the number of +# nodes in the graph) in the quantum circuit. +# +# Thus, we might want to challenge our model to learn a good +# initialization heuristic for a non-specific graph, with an arbitrary +# number of nodes. For this purpose, let’s create a training dataset +# containing graphs with a different number of nodes :math:`n,` taken in +# the interval :math:`n \in [7,9]` (that is, our dataset now contains +# graphs having either 7, 8 and 9 nodes). +# + +cell = tf.keras.layers.LSTMCell(2 * n_layers) + +g7 = generate_graphs(5, 7, 3 / 7) +g8 = generate_graphs(5, 8, 3 / 7) +g9 = generate_graphs(5, 9, 3 / 7) + +gs = g7 + g8 + g9 +gs_cost_list = [qaoa_from_graph(g) for g in gs] + +# Shuffle the dataset +import random + +random.seed(1234) +random.shuffle(gs_cost_list) + + +###################################################################### +# So far, we have created an equally balanced dataset that contains graphs with +# a different number of nodes. We now use this dataset to train the LSTM. +# + +# Select an optimizer +opt = tf.keras.optimizers.Adam(learning_rate=0.1) + +# Set the number of training epochs +epochs = 3 + +for epoch in range(epochs): + print(f"Epoch {epoch+1}") + total_loss = np.array([]) + for i, graph_cost in enumerate(gs_cost_list): + loss = train_step(graph_cost) + total_loss = np.append(total_loss, loss.numpy()) + # Log every 5 batches. + if i % 5 == 0: + print(f" > Graph {i+1}/{len(gs_cost_list)} - Loss: {loss}") + print(f" >> Mean Loss during epoch: {np.mean(total_loss)}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Epoch 1 +# > Graph 1/15 - Loss: [[-1.4876363]] +# > Graph 6/15 - Loss: [[-1.8590403]] +# > Graph 11/15 - Loss: [[-1.7644017]] +# >> Mean Loss during epoch: -1.9704322338104248 +# Epoch 2 +# > Graph 1/15 - Loss: [[-1.8650053]] +# > Graph 6/15 - Loss: [[-1.9578737]] +# > Graph 11/15 - Loss: [[-1.8377447]] +# >> Mean Loss during epoch: -2.092947308222453 +# Epoch 3 +# > Graph 1/15 - Loss: [[-1.9009062]] +# > Graph 6/15 - Loss: [[-1.9726204]] +# > Graph 11/15 - Loss: [[-1.8668792]] +# >> Mean Loss during epoch: -2.1162660201390584 +# + + +###################################################################### +# Let’s check if this hybrid model eventually learned a good heuristic to +# propose new updates for the parameters in the QAOA ansatz of the MaxCut +# problem. +# +# For this reason, we consider a new graph. In particular, we can take a +# graph with 10 nodes, which is something that the recurrent network has +# not seen before. +# + +new_graph = nx.gnp_random_graph(10, p=3 / 7) +new_cost = qaoa_from_graph(new_graph) + +nx.draw(new_graph) + +###################################################################### +# .. figure:: ../_static/demonstration_assets/learning2learn/rendered_Graph10.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# + + +###################################################################### +# We call the trained recurrent LSTM on this graph, saving not only the +# last, but all intermediate guesses for the parameters. +# + +res = recurrent_loop(new_cost, intermediate_steps=True) + +# Extract all angle suggestions +start_zeros = tf.zeros(shape=(2 * n_layers, 1)) +guess_0 = res[0] +guess_1 = res[1] +guess_2 = res[2] +guess_3 = res[3] +guess_4 = res[4] +final_loss = res[5] + +# Wrap them into a list +guesses = [start_zeros, guess_0, guess_1, guess_2, guess_3, guess_4] + +# Losses from the hybrid LSTM model +lstm_losses = [new_cost(tf.reshape(guess, shape=(2, n_layers))) for guess in guesses] + +fig, ax = plt.subplots() + +plt.plot(lstm_losses, color="blue", lw=3, ls="-.", label="LSTM") + +plt.grid(ls="--", lw=2, alpha=0.25) +plt.legend() +plt.ylabel("Cost function", fontsize=12) +plt.xlabel("Iteration", fontsize=12) +ax.set_xticks([0, 5, 10, 15, 20]) +plt.show() + +###################################################################### +# .. figure:: ../_static/demonstration_assets/learning2learn/rendered_LossGeneralization.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# + + +###################################################################### +# Again, we can confirm that the custom optimizer based on the LSTM quickly reaches a good +# value of the loss function, and also achieve good generalization performances, since +# it is able to initialize parameters also for graphs not present in the training set. +# +# .. note:: +# To get the optimized weights of the LSTM use: ``optimized_weights = cell.get_weights()``. +# To set initial weights for the LSTM cell, use instead: ``cell.set_weights(optimized_weights)``. +# +# +# Loss landscape in parameter space +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# It may be interesting to plot the path suggested by the RNN in the space +# of the parameters. Note that this is possible only if one layer is used +# in the QAOA ansatz since in this case only two angles are needed and +# they can be plotted on a 2D plane. Of course, if more layers are used, +# you can always select a pair of them to reproduce a similar plot. +# +# .. note:: +# This cell takes approx. ~1m to run with an 11 by 11 grid +# + +# Evaluate the cost function on a grid in parameter space +dx = dy = np.linspace(-1.0, 1.0, 11) +dz = np.array([new_cost([[xx], [yy]]).numpy() for yy in dy for xx in dx]) +Z = dz.reshape((11, 11)) + +# Plot cost landscape +plt.contourf(dx, dy, Z) +plt.colorbar() + +# Extract optimizer steps +params_x = [0.0] + [res[i].numpy()[0, 0] for i in range(len(res[:-1]))] +params_y = [0.0] + [res[i].numpy()[0, 1] for i in range(len(res[:-1]))] + +# Plot steps +plt.plot(params_x, params_y, linestyle="--", color="red", marker="x") + +plt.yticks(np.linspace(-1, 1, 5)) +plt.xticks(np.linspace(-1, 1, 5)) +plt.xlabel(r"$\alpha$", fontsize=12) +plt.ylabel(r"$\gamma$", fontsize=12) +plt.title("Loss Landscape", fontsize=12) +plt.show() + +###################################################################### +# .. figure:: ../_static/demonstration_assets/learning2learn/rendered_LossLandscape.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# +# +# +# +# Ideas for creating a Keras Layer and Keras Model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Definition of a ``Keras Layer`` containing a single pass through the +# LSTM and the Quantum Circuit. That’s equivalent to the function +# ``rnn_iteration`` from before. +# + + +class QRNN(tf.keras.layers.Layer): + def __init__(self, p=1, graph=None): + super(QRNN, self).__init__() + # p is the number of layers in the QAOA ansatz + self.cell = tf.keras.layers.LSTMCell(2 * p) + self.expectation = qaoa_from_graph(graph, n_layers=p) + self.qaoa_p = p + + def call(self, inputs): + prev_cost = inputs[0] + prev_params = inputs[1] + prev_h = inputs[2] + prev_c = inputs[3] + + # Concatenate the previous parameters and previous cost to create new input + new_input = tf.keras.layers.concatenate([prev_cost, prev_params]) + + # New parameters obtained by the LSTM cell, along with new internal states h and c + new_params, [new_h, new_c] = self.cell(new_input, states=[prev_h, prev_c]) + + # This part is used to feed the parameters to the PennyLane function + _params = tf.reshape(new_params, shape=(2, self.qaoa_p)) + + # Cost evaluation, and reshaping to be consistent with other Keras tensors + new_cost = tf.reshape( + tf.cast(self.expectation(_params), dtype=tf.float32), shape=(1, 1) + ) + + return [new_cost, new_params, new_h, new_c] + + +###################################################################### +# Code for creating an actual ``Keras Model`` starting from the previous +# layer definition. +# + +_graph = nx.gnp_random_graph(7, p=3 / 7) + +# Instantiate the LSTM cells +rnn0 = QRNN(graph=_graph) + +# Create some input layers to feed the data +inp_cost = tf.keras.layers.Input(shape=(1,)) +inp_params = tf.keras.layers.Input(shape=(2,)) +inp_h = tf.keras.layers.Input(shape=(2,)) +inp_c = tf.keras.layers.Input(shape=(2,)) + +# Manually creating the recurrent loops. In this case just three iterations are used. +out0 = rnn0([inp_cost, inp_params, inp_h, inp_c]) +out1 = rnn0(out0) +out2 = rnn0(out1) + +# Definition of a loss function driving the training of the LSTM +loss = tf.keras.layers.average([0.15 * out0[0], 0.35 * out1[0], 0.5 * out2[0]]) + +# Definition of a Keras Model +model = tf.keras.Model( + inputs=[inp_cost, inp_params, inp_h, inp_c], + outputs=[out0[1], out1[1], out2[1], loss], +) + +model.summary() + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Model: "functional_1" +# __________________________________________________________________________________________________ +# Layer (type) Output Shape Param # Connected to +# ================================================================================================== +# input_1 (InputLayer) [(None, 1)] 0 +# __________________________________________________________________________________________________ +# input_2 (InputLayer) [(None, 2)] 0 +# __________________________________________________________________________________________________ +# input_3 (InputLayer) [(None, 2)] 0 +# __________________________________________________________________________________________________ +# input_4 (InputLayer) [(None, 2)] 0 +# __________________________________________________________________________________________________ +# qrnn (QRNN) [(1, 1), 48 input_1[0][0] +# (None, 2), input_2[0][0] +# (None, 2), input_3[0][0] +# (None, 2)] input_4[0][0] +# qrnn[0][0] +# qrnn[0][1] +# qrnn[0][2] +# qrnn[0][3] +# qrnn[1][0] +# qrnn[1][1] +# qrnn[1][2] +# qrnn[1][3] +# __________________________________________________________________________________________________ +# tf.math.multiply (TFOpLambda) (1, 1) 0 qrnn[0][0] +# __________________________________________________________________________________________________ +# tf.math.multiply_1 (TFOpLambda) (1, 1) 0 qrnn[1][0] +# __________________________________________________________________________________________________ +# tf.math.multiply_2 (TFOpLambda) (1, 1) 0 qrnn[2][0] +# __________________________________________________________________________________________________ +# average_147 (Average) (1, 1) 0 tf.math.multiply[0][0] +# tf.math.multiply_1[0][0] +# tf.math.multiply_2[0][0] +# ================================================================================================== +# Total params: 48 +# Trainable params: 48 +# Non-trainable params: 0 +# + +###################################################################### +# A basic training routine for the ``Keras Model`` just created: +# + +p = 1 + +inp_costA = tf.zeros(shape=(1, 1)) +inp_paramsA = tf.zeros(shape=(1, 2 * p)) +inp_hA = tf.zeros(shape=(1, 2 * p)) +inp_cA = tf.zeros(shape=(1, 2 * p)) + +inputs = [inp_costA, inp_paramsA, inp_hA, inp_cA] + +opt = tf.keras.optimizers.Adam(learning_rate=0.01) +step = 5 + +for _ in range(step): + with tf.GradientTape() as tape: + pred = model(inputs) + loss = pred[3] + + gradients = tape.gradient(loss, model.trainable_variables) + opt.apply_gradients(zip(gradients, model.trainable_variables)) + print( + f"Step {_+1} - Loss = {loss} - Cost = {qaoa_from_graph(_graph, n_layers=p)(np.reshape(pred[2].numpy(),(2, p)))}" + ) + +print("Final Loss:", loss.numpy()) +print("Final Outs:") +for t, s in zip(pred, ["out0", "out1", "out2", "Loss"]): + print(f" >{s}: {t.numpy()}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Step 1 - Loss = [[-1.5563084]] - Cost = -4.762684301954701 +# Step 2 - Loss = [[-1.5649065]] - Cost = -4.799981173473755 +# Step 3 - Loss = [[-1.5741502]] - Cost = -4.840036354736862 +# Step 4 - Loss = [[-1.5841404]] - Cost = -4.883246647056216 +# Step 5 - Loss = [[-1.5948243]] - Cost = -4.929228976649736 +# Final Loss: [[-1.5948243]] +# Final Outs: +# >out0: [[-0.01041588 0.01016874]] +# >out1: [[-0.04530389 0.38148248]] +# >out2: [[-0.10258182 0.4134117 ]] +# >Loss: [[-1.5948243]] +# + +###################################################################### +# .. note:: +# This code works only for a single graph at a time, since a graph was +# needed to create the ``QRNN`` ``Keras Layer`` named ``rnn0``. Thus, in +# order to actually train the RNN network for multiple graphs, the above +# training routine must be modified. Otherwise, you could find a way to +# define the model to accept as input a whole dataset of graphs, and not +# just a single one. Still, this might prove particularly hard, since +# TensorFlow deals with tensors, and is not able to directly manage +# other data structures, like graphs or functions taking graphs as +# input, like ``qaoa_from_graph``. +# +# diff --git a/demonstrations_v2/learning2learn/metadata.json b/demonstrations_v2/learning2learn/metadata.json new file mode 100644 index 0000000000..d81a901fc4 --- /dev/null +++ b/demonstrations_v2/learning2learn/metadata.json @@ -0,0 +1,39 @@ +{ + "title": "Learning to learn with quantum neural networks", + "authors": [ + { + "username": "smangini" + } + ], + "dateOfPublication": "2021-03-02T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_learning_to_learn_quantum_neural.png" + } + ], + "seoDescription": "Use a classical recurrent neural network to initilize the parameters of a variational quatum algorithm.", + "doi": "", + "references": [], + "basedOnPapers": [ + "10.48550/arXiv.1907.05415" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/learning2learn/requirements.in b/demonstrations_v2/learning2learn/requirements.in new file mode 100644 index 0000000000..0139ee4a32 --- /dev/null +++ b/demonstrations_v2/learning2learn/requirements.in @@ -0,0 +1,5 @@ +matplotlib +networkx +numpy +pennylane +tensorflow diff --git a/demonstrations_v2/ml_classical_shadows/demo.py b/demonstrations_v2/ml_classical_shadows/demo.py new file mode 100644 index 0000000000..76d7b7932b --- /dev/null +++ b/demonstrations_v2/ml_classical_shadows/demo.py @@ -0,0 +1,859 @@ +r""" +Machine learning for quantum many-body problems +============================================================== + +Storing and processing a complete description of an :math:`n`-qubit quantum mechanical +system is challenging because the amount of memory required generally scales exponentially +with the number of qubits. The quantum community has recently addressed this challenge by using +the :doc:`classical shadow ` formalism, which allows us to build more +concise classical descriptions of quantum states using randomized single-qubit measurements. +It was argued in Ref. [#preskill]_ that combining classical shadows with classical machine learning +enables using learning models that efficiently predict properties of the quantum systems, such as +the expectation value of a Hamiltonian, correlation functions, and entanglement entropies. + +.. figure:: /_static/demonstration_assets/ml_classical_shadows/class_shadow_ml.png + :align: center + :width: 80 % + :alt: Combining ML with Classical Shadow + + Combining machine learning and classical shadows + + +In this demo, we describe one of the ideas presented in Ref. [#preskill]_ for using classical +shadow formalism and machine learning to predict the ground-state properties of the +2D antiferromagnetic Heisenberg model. We begin by learning how to build the Heisenberg model, +calculate its ground-state properties, and compute its classical shadow. Finally, we demonstrate +how to use :doc:`kernel-based learning models ` to predict ground-state +properties from the learned classical shadows. So let's get started! + +.. note:: + This demo is compatible with the latest version of PennyLane and ``neural-tangents==0.6.5``. + The latter is required for building the kernel for the infinite network used in training. + + +Building the 2D Heisenberg model +--------------------------------- + +We define a two-dimensional antiferromagnetic `Heisenberg model +`__ as a square +lattice, where a spin-1/2 particle occupies each site. The antiferromagnetic +nature and the overall physics of this model depend on the couplings +:math:`J_{ij}` present between the spins, as reflected in the Hamiltonian +associated with the model: + +.. math:: H = \sum_{i < j} J_{ij}(X_i X_j + Y_i Y_j + Z_i Z_j) . + +Here, we consider the family of Hamiltonians where all the couplings :math:`J_{ij}` +are sampled uniformly from [0, 2]. We build a coupling matrix :math:`J` by providing +the number of rows :math:`N_r` and columns :math:`N_c` present in the square lattice. +The dimensions of this matrix are :math:`N_s \times N_s,` where :math:`N_s = N_r \times N_c` +is the total number of spin particles present in the model. + + +""" + +import itertools as it +import numpy as np + +def build_coupling_mats(num_mats, num_rows, num_cols): + num_spins = num_rows * num_cols + coupling_mats = np.zeros((num_mats, num_spins, num_spins)) + coup_terms = np.random.RandomState(24).uniform(0, 2, + size=(num_mats, 2 * num_rows * num_cols - num_rows - num_cols)) + # populate edges to build the grid lattice + edges = [(si, sj) for (si, sj) in it.combinations(range(num_spins), 2) + if sj % num_cols and sj - si == 1 or sj - si == num_cols] + for itr in range(num_mats): + for ((i, j), term) in zip(edges, coup_terms[itr]): + coupling_mats[itr][i][j] = coupling_mats[itr][j][i] = term + return coupling_mats + + +###################################################################### +# For this demo, we study a model with four spins arranged on the nodes of +# a square lattice. We require four qubits for simulating this model; +# one qubit for each spin. We start by building a coupling matrix ``J_mat`` +# using our previously defined function. +# + +Nr, Nc = 2, 2 +num_qubits = Nr * Nc # Ns +J_mat = build_coupling_mats(1, Nr, Nc)[0] + + +###################################################################### +# We can now visualize the model instance by representing the coupling matrix as a +# ``networkx`` graph: + +import matplotlib.pyplot as plt +import networkx as nx + +G = nx.from_numpy_array(J_mat, create_using=nx.DiGraph) +pos = {i: (i % Nc, -(i // Nc)) for i in G.nodes()} +edge_labels = {(x, y): np.round(J_mat[x, y], 2) for x, y in G.edges()} +weights = [x + 1.5 for x in list(nx.get_edge_attributes(G, "weight").values())] + +plt.figure(figsize=(4, 4)) +nx.draw( + G, pos, node_color="lightblue", with_labels=True, + node_size=600, width=weights, edge_color="firebrick", +) +nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=edge_labels) +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ml_classical_shadows/spin_lattice.png +# :width: 40% +# :align: center +# :alt: Spin model on square lattice + + +###################################################################### +# We then use the same coupling matrix ``J_mat`` to obtain the Hamiltonian +# :math:`H` for the model we have instantiated above. +# + +import pennylane as qml + +def Hamiltonian(J_mat): + coeffs, ops = [], [] + ns = J_mat.shape[0] + for i, j in it.combinations(range(ns), r=2): + coeff = J_mat[i, j] + if coeff: + for op in [qml.PauliX, qml.PauliY, qml.PauliZ]: + coeffs.append(coeff) + ops.append(op(i) @ op(j)) + H = qml.Hamiltonian(coeffs, ops) + return H + +print(f"Hamiltonian =\n{Hamiltonian(J_mat)}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Hamiltonian = +# 1.920034606671837 * (X(0) @ X(1)) + 1.920034606671837 * (Y(0) @ Y(1)) + 1.920034606671837 * (Z(0) @ Z(1)) + 1.399024099899152 * (X(0) @ X(2)) + 1.399024099899152 * (Y(0) @ Y(2)) + 1.399024099899152 * (Z(0) @ Z(2)) + 1.9997345852477584 * (X(1) @ X(3)) + 1.9997345852477584 * (Y(1) @ Y(3)) + 1.9997345852477584 * (Z(1) @ Z(3)) + 0.44013459956570355 * (X(2) @ X(3)) + 0.44013459956570355 * (Y(2) @ Y(3)) + 0.44013459956570355 * (Z(2) @ Z(3)) + + +###################################################################### +# For the Heisenberg model, a property of interest is usually the two-body +# correlation function :math:`C_{ij},` which for a pair of spins :math:`i` +# and :math:`j` is defined as the following operator: +# +# .. math:: \hat{C}_{ij} = \frac{1}{3} (X_i X_j + Y_iY_j + Z_iZ_j). + +def corr_function(i, j): + ops = [] + for op in [qml.PauliX, qml.PauliY, qml.PauliZ]: + if i != j: + ops.append(op(i) @ op(j)) + else: + ops.append(qml.Identity(i)) + return ops + +###################################################################### +# +# The expectation value of each such operator :math:`\hat{C}_{ij}` with respect to +# the ground state :math:`|\psi_{0}\rangle` of the model can be used to build +# the correlation matrix :math:`C:` +# +# .. math:: {C}_{ij} = \langle \hat{C}_{ij} \rangle = \frac{1}{3} \langle \psi_{0} | X_i X_j + Y_iY_j + Z_iZ_j | \psi_{0} \rangle . +# + +###################################################################### +# Hence, to build :math:`C` for the model, we need to calculate its +# ground state :math:`|\psi_{0}\rangle.` We do this by diagonalizing +# the Hamiltonian for the model. Then, we obtain the eigenvector corresponding +# to the smallest eigenvalue. +# + +import scipy as sp + +ham = Hamiltonian(J_mat) +eigvals, eigvecs = sp.sparse.linalg.eigs(ham.sparse_matrix()) +psi0 = eigvecs[:, np.argmin(eigvals)] + + +###################################################################### +# We then build a circuit that initializes the qubits into the ground +# state and measures the expectation value of the provided set of observables. +# + +dev_exact = qml.device("default.qubit", wires=num_qubits) # for exact simulation + +def circuit(psi, observables): + psi = psi / np.linalg.norm(psi) # normalize the state + qml.StatePrep(psi, wires=range(num_qubits)) + return [qml.expval(o) for o in observables] + +circuit_exact = qml.QNode(circuit, dev_exact) + + +###################################################################### +# Finally, we execute this circuit to obtain the exact correlation matrix +# :math:`C.` We compute the correlation operators :math:`\hat{C}_{ij}` and +# their expectation values with respect to the ground state :math:`|\psi_0\rangle.` +# + +coups = list(it.product(range(num_qubits), repeat=2)) +corrs = [corr_function(i, j) for i, j in coups] + +def build_exact_corrmat(coups, corrs, circuit, psi): + corr_mat_exact = np.zeros((num_qubits, num_qubits)) + for idx, (i, j) in enumerate(coups): + corr = corrs[idx] + if i == j: + corr_mat_exact[i][j] = 1.0 + else: + corr_mat_exact[i][j] = ( + np.sum(np.array([circuit(psi, observables=[o]) for o in corr]).T) / 3 + ) + corr_mat_exact[j][i] = corr_mat_exact[i][j] + return corr_mat_exact + +expval_exact = build_exact_corrmat(coups, corrs, circuit_exact, psi0) + +######################################################################### +# Once built, we can visualize the correlation matrix: +# + +fig, ax = plt.subplots(1, 1, figsize=(4, 4)) +im = ax.imshow(expval_exact, cmap=plt.get_cmap("RdBu"), vmin=-1, vmax=1) +ax.xaxis.set_ticks(range(num_qubits)) +ax.yaxis.set_ticks(range(num_qubits)) +ax.xaxis.set_tick_params(labelsize=14) +ax.yaxis.set_tick_params(labelsize=14) +ax.set_title("Exact Correlation Matrix", fontsize=14) + +bar = fig.colorbar(im, pad=0.05, shrink=0.80 ) +bar.set_label(r"$C_{ij}$", fontsize=14, rotation=0) +bar.ax.tick_params(labelsize=14) +plt.show() + +############################################################################## +# .. figure:: /_static/demonstration_assets/ml_classical_shadows/exact_correlation_matrix.png +# :width: 55% +# :align: center +# :alt: Exact correlation matrix + + +###################################################################### +# Constructing classical shadows +# ------------------------------ +# + + +###################################################################### +# Now that we have built the Heisenberg model, the next step is to construct +# a :doc:`classical shadow ` representation for its ground state. To construct an +# approximate classical representation of an :math:`n`-qubit quantum state :math:`\rho,` +# we perform randomized single-qubit measurements on :math:`T`-copies of +# :math:`\rho.` Each measurement is chosen randomly among the Pauli bases +# :math:`X`, :math:`Y,` or :math:`Z` to yield random :math:`n` pure product +# states :math:`|s_i\rangle` for each copy: +# +# .. math:: |s_{i}^{(t)}\rangle \in \{|0\rangle, |1\rangle, |+\rangle, |-\rangle, |i+\rangle, |i-\rangle\}. +# +# .. math:: S_T(\rho) = \big\{|s_{i}^{(t)}\rangle: i\in\{1,\ldots, n\},\ t\in\{1,\ldots, T\} \big\}. +# +# Each of the :math:`|s_i^{(t)}\rangle` provides us with a snapshot of the +# state :math:`\rho,` and the :math:`nT` measurements yield the complete +# set :math:`S_{T},` which requires just :math:`3nT` bits to be stored +# in classical memory. This is discussed in further detail in our previous +# demo about :doc:`classical shadows `. +# + +###################################################################### +# .. figure:: /_static/demonstration_assets/ml_classical_shadows/class_shadow_prep.png +# :align: center +# :width: 100 % +# :alt: Preparing Classical Shadows +# + + +###################################################################### +# To prepare a classical shadow for the ground state of the Heisenberg +# model, we simply reuse the circuit template used above and reconstruct +# a ``QNode`` utilizing a device that performs single-shot measurements. +# + +dev_oshot = qml.device("default.qubit", wires=num_qubits, shots=1) +circuit_oshot = qml.QNode(circuit, dev_oshot) + + +###################################################################### +# Now, we define a function to build the classical shadow for the quantum +# state prepared by a given :math:`n`-qubit circuit using :math:`T`-copies +# of randomized Pauli basis measurements +# + + +def gen_class_shadow(circ_template, circuit_params, num_shadows, num_qubits): + # prepare the complete set of available Pauli operators + unitary_ops = [qml.PauliX, qml.PauliY, qml.PauliZ] + # sample random Pauli measurements uniformly + unitary_ensmb = np.random.randint(0, 3, size=(num_shadows, num_qubits), dtype=int) + + outcomes = np.zeros((num_shadows, num_qubits)) + for ns in range(num_shadows): + # for each snapshot, extract the Pauli basis measurement to be performed + meas_obs = [unitary_ops[unitary_ensmb[ns, i]](i) for i in range(num_qubits)] + # perform single shot randomized Pauli measuremnt for each qubit + outcomes[ns, :] = circ_template(circuit_params, observables=meas_obs) + + return outcomes, unitary_ensmb + +outcomes, basis = gen_class_shadow(circuit_oshot, psi0, 100, num_qubits) +print("First five measurement outcomes =\n", outcomes[:5]) +print("First five measurement bases =\n", basis[:5]) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# First five measurement outcomes = +# [[-1. 1. -1. 1.] +# [ 1. 1. -1. -1.] +# [-1. 1. 1. -1.] +# [-1. 1. -1. -1.] +# [ 1. 1. -1. 1.]] +# First five measurement bases = +# [[2 2 1 0] +# [1 2 0 2] +# [2 1 1 0] +# [0 0 1 0] +# [2 0 2 1]] + + +###################################################################### +# Furthermore, :math:`S_{T}` can be used to construct an approximation +# of the underlying :math:`n`-qubit state :math:`\rho` by averaging over :math:`\sigma_t:` +# +# .. math:: \sigma_T(\rho) = \frac{1}{T} \sum_{1}^{T} \big(3|s_{1}^{(t)}\rangle\langle s_1^{(t)}| - \mathbb{I}\big)\otimes \ldots \otimes \big(3|s_{n}^{(t)}\rangle\langle s_n^{(t)}| - \mathbb{I}\big). +# + + +def snapshot_state(meas_list, obs_list): + # undo the rotations done for performing Pauli measurements in the specific basis + rotations = [ + qml.matrix(qml.Hadamard(wires=0)), # X-basis + qml.matrix(qml.Hadamard(wires=0)) @ qml.matrix(qml.adjoint(qml.S(wires=0))), # Y-basis + qml.matrix(qml.Identity(wires=0)), # Z-basis + ] + + # reconstruct snapshot from local Pauli measurements + rho_snapshot = [1] + for meas_out, basis in zip(meas_list, obs_list): + # preparing state |s_i><0| for 1 and |1><1| for -1 + state = np.array([[1, 0], [0, 0]]) if meas_out == 1 else np.array([[0, 0], [0, 1]]) + local_rho = 3 * (rotations[basis].conj().T @ state @ rotations[basis]) - np.eye(2) + rho_snapshot = np.kron(rho_snapshot, local_rho) + + return rho_snapshot + +def shadow_state_reconst(shadow): + num_snapshots, num_qubits = shadow[0].shape + meas_lists, obs_lists = shadow + + # Reconstruct the quantum state from its classical shadow + shadow_rho = np.zeros((2 ** num_qubits, 2 ** num_qubits), dtype=complex) + for i in range(num_snapshots): + shadow_rho += snapshot_state(meas_lists[i], obs_lists[i]) + + return shadow_rho / num_snapshots + + +###################################################################### +# To see how well the reconstruction works for different values of :math:`T,` +# we look at the `fidelity `_ +# of the actual quantum state with respect to the reconstructed quantum state from +# the classical shadow with :math:`T` copies. On average, as the number of copies +# :math:`T` is increased, the reconstruction becomes more effective with average +# higher fidelity values (orange) and lower variance (blue). Eventually, in the +# limit :math:`T\rightarrow\infty,` the reconstruction will be exact. +# +# .. figure:: /_static/demonstration_assets/ml_classical_shadows/fidel_snapshot.png +# :align: center +# :width: 80 % +# :alt: Fidelity of reconstructed ground state with different shadow sizes :math:`T` +# +# Fidelity of the reconstructed ground state with different shadow sizes :math:`T` +# + + +###################################################################### +# The reconstructed quantum state :math:`\sigma_T` can also be used to +# evaluate expectation values :math:`\text{Tr}(O\sigma_T)` for some localized +# observable :math:`O = \bigotimes_{i}^{n} P_i,` where :math:`P_i \in \{I, X, Y, Z\}.` +# However, as shown above, :math:`\sigma_T` would be only an approximation of +# :math:`\rho` for finite values of :math:`T.` Therefore, to estimate +# :math:`\langle O \rangle` robustly, we use the median of means +# estimation. For this purpose, we split up the :math:`T` shadows into +# :math:`K` equally-sized groups and evaluate the median of the mean +# value of :math:`\langle O \rangle` for each of these groups. +# + + +def estimate_shadow_obs(shadow, observable, k=10): + shadow_size = shadow[0].shape[0] + + # convert Pennylane observables to indices + map_name_to_int = {"PauliX": 0, "PauliY": 1, "PauliZ": 2} + if isinstance(observable, (qml.PauliX, qml.PauliY, qml.PauliZ)): + target_obs = np.array([map_name_to_int[observable.name]]) + target_locs = np.array([observable.wires[0]]) + else: + target_obs = np.array([map_name_to_int[o.name] for o in observable.obs]) + target_locs = np.array([o.wires[0] for o in observable.obs]) + + # perform median of means to return the result + means = [] + meas_list, obs_lists = shadow + for i in range(0, shadow_size, shadow_size // k): + meas_list_k, obs_lists_k = ( + meas_list[i : i + shadow_size // k], + obs_lists[i : i + shadow_size // k], + ) + indices = np.all(obs_lists_k[:, target_locs] == target_obs, axis=1) + if sum(indices): + means.append( + np.sum(np.prod(meas_list_k[indices][:, target_locs], axis=1)) / sum(indices) + ) + else: + means.append(0) + + return np.median(means) + + +###################################################################### +# Now we estimate the correlation matrix :math:`C^{\prime}` from the +# classical shadow approximation of the ground state. +# + +coups = list(it.product(range(num_qubits), repeat=2)) +corrs = [corr_function(i, j) for i, j in coups] +qbobs = [qob for qobs in corrs for qob in qobs] + +def build_estim_corrmat(coups, corrs, num_obs, shadow): + k = int(2 * np.log(2 * num_obs)) # group size + corr_mat_estim = np.zeros((num_qubits, num_qubits)) + for idx, (i, j) in enumerate(coups): + corr = corrs[idx] + if i == j: + corr_mat_estim[i][j] = 1.0 + else: + corr_mat_estim[i][j] = ( + np.sum(np.array([estimate_shadow_obs(shadow, o, k=k+1) for o in corr])) / 3 + ) + corr_mat_estim[j][i] = corr_mat_estim[i][j] + return corr_mat_estim + +shadow = gen_class_shadow(circuit_oshot, psi0, 1000, num_qubits) +expval_estmt = build_estim_corrmat(coups, corrs, len(qbobs), shadow) + +######################################################################### +# This time, let us visualize the deviation observed between the exact correlation +# matrix (:math:`C`) and the estimated correlation matrix (:math:`C^{\prime}`) +# to assess the effectiveness of classical shadow formalism. +# + +fig, ax = plt.subplots(1, 1, figsize=(4.2, 4)) +im = ax.imshow(expval_exact-expval_estmt, cmap=plt.get_cmap("RdBu"), vmin=-1, vmax=1) +ax.xaxis.set_ticks(range(num_qubits)) +ax.yaxis.set_ticks(range(num_qubits)) +ax.xaxis.set_tick_params(labelsize=14) +ax.yaxis.set_tick_params(labelsize=14) +ax.set_title("Error in estimating the\ncorrelation matrix", fontsize=14) + +bar = fig.colorbar(im, pad=0.05, shrink=0.80) +bar.set_label(r"$\Delta C_{ij}$", fontsize=14, rotation=0) +bar.ax.tick_params(labelsize=14) +plt.show() + +###################################################################### +# .. figure:: /_static/demonstration_assets/ml_classical_shadows/error_correlation_matrix.png +# :align: center +# :width: 55 % +# :alt: Error in estimating correlation matrix +# + + +###################################################################### +# Training classical machine learning models +# ------------------------------------------ +# + + +###################################################################### +# There are multiple ways in which we can combine classical shadows and +# machine learning. This could include training a model to learn +# the classical representation of quantum systems based on some system +# parameter, estimating a property from such learned classical representations, +# or a combination of both. In our case, we consider the problem of using +# :doc:`kernel-based models ` to learn the ground-state representation of the +# Heisenberg model Hamiltonian :math:`H(x_l)` from the coupling vector :math:`x_l,` +# where :math:`x_l = [J_{i,j} \text{ for } i < j].` The goal is to predict the +# correlation functions :math:`C_{ij}:` +# +# .. math:: \big\{x_l \rightarrow \sigma_T(\rho(x_l)) \rightarrow \text{Tr}(\hat{C}_{ij} \sigma_T(\rho(x_l))) \big\}_{l=1}^{N}. +# +# Here, we consider the following kernel-based machine learning model [#neurtangkernel]_: +# +# .. math:: \hat{\sigma}_{N} (x) = \sum_{l=1}^{N} \kappa(x, x_l)\sigma_T (x_l) = \sum_{l=1}^{N} \left(\sum_{l^{\prime}=1}^{N} k(x, x_{l^{\prime}})(K+\lambda I)^{-1}_{l, l^{\prime}} \sigma_T(x_l) \right), +# +# where :math:`\lambda > 0` is a regularization parameter in cases when +# :math:`K` is not invertible, :math:`\sigma_T(x_l)` denotes the classical +# representation of the ground state :math:`\rho(x_l)` of the Heisenberg +# model constructed using :math:`T` randomized Pauli measurements, and +# :math:`K_{ij}=k(x_i, x_j)` is the kernel matrix with +# :math:`k(x, x^{\prime})` as the kernel function. +# +# Similarly, estimating an expectation value on the predicted ground state +# :math:`\sigma_T(x_l)` using the trained model can then be done by +# evaluating: +# +# .. math:: \text{Tr}(\hat{O} \hat{\sigma}_{N} (x)) = \sum_{l=1}^{N} \kappa(x, x_l)\text{Tr}(O\sigma_T (x_l)). +# +# We train the classical kernel-based models using :math:`N = 70` +# randomly chosen values of the coupling matrices :math:`J.` +# + +# imports for ML methods and techniques +from sklearn.model_selection import train_test_split, cross_val_score +from sklearn import svm +from sklearn.kernel_ridge import KernelRidge + +###################################################################### +# First, to build the dataset, we use the function ``build_dataset`` that +# takes as input the size of the dataset (``num_points``), the topology of +# the lattice (``Nr`` and ``Nc``), and the number of randomized +# Pauli measurements (:math:`T`) for the construction of classical shadows. +# The ``X_data`` is the set of coupling vectors that are defined as a +# stripped version of the coupling matrix :math:`J,` where only non-duplicate +# and non-zero :math:`J_{ij}` are considered. The ``y_exact`` and +# ``y_clean`` are the set of correlation vectors, i.e., the flattened +# correlation matrix :math:`C,` computed with respect to the ground-state +# obtained from exact diagonalization and classical shadow representation +# (with :math:`T=500`), respectively. +# + + +def build_dataset(num_points, Nr, Nc, T=500): + + num_qubits = Nr * Nc + X, y_exact, y_estim = [], [], [] + coupling_mats = build_coupling_mats(num_points, Nr, Nc) + + for coupling_mat in coupling_mats: + ham = Hamiltonian(coupling_mat) + eigvals, eigvecs = sp.sparse.linalg.eigs(ham.sparse_matrix()) + psi = eigvecs[:, np.argmin(eigvals)] + shadow = gen_class_shadow(circuit_oshot, psi, T, num_qubits) + + coups = list(it.product(range(num_qubits), repeat=2)) + corrs = [corr_function(i, j) for i, j in coups] + qbobs = [x for sublist in corrs for x in sublist] + + expval_exact = build_exact_corrmat(coups, corrs, circuit_exact, psi) + expval_estim = build_estim_corrmat(coups, corrs, len(qbobs), shadow) + + coupling_vec = [] + for coup in coupling_mat.reshape(1, -1)[0]: + if coup and coup not in coupling_vec: + coupling_vec.append(coup) + coupling_vec = np.array(coupling_vec) / np.linalg.norm(coupling_vec) + + X.append(coupling_vec) + y_exact.append(expval_exact.reshape(1, -1)[0]) + y_estim.append(expval_estim.reshape(1, -1)[0]) + + return np.array(X), np.array(y_exact), np.array(y_estim) + +X, y_exact, y_estim = build_dataset(100, Nr, Nc, 500) +X_data, y_data = X, y_estim +X_data.shape, y_data.shape, y_exact.shape + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# ((100, 4), (100, 16), (100, 16)) + + +###################################################################### +# Now that our dataset is ready, we can shift our focus to the ML models. +# Here, we use two different Kernel functions: (i) Gaussian Kernel and +# (ii) Neural Tangent Kernel. For both of them, we consider the +# regularization parameter :math:`\lambda` from the following set of values: +# +# .. math:: \lambda = \left\{ 0.0025, 0.0125, 0.025, 0.05, 0.125, 0.25, 0.5, 1.0, 5.0, 10.0 \right\}. +# +# Next, we define the kernel functions :math:`k(x, x^{\prime})` for each +# of the mentioned kernels: + +###################################################################### +# .. math:: k(x, x^{\prime}) = e^{-\gamma||x - x^{\prime}||^{2}_{2}}. \tag{Gaussian Kernel} +# +# For the Gaussian kernel, the hyperparameter +# :math:`\gamma = N^{2}/\sum_{i=1}^{N} \sum_{j=1}^{N} ||x_i-x_j||^{2}_{2} > 0` +# is chosen to be the inverse of the average Euclidean distance +# :math:`x_i` and :math:`x_j.` The kernel is implemented using the +# radial-basis function (rbf) kernel in the ``sklearn`` library. +# + +###################################################################### +# .. math:: k(x, x^{\prime}) = k^{\text{NTK}}(x, x^{\prime}). \tag{Neural Tangent Kernel} +# +# The neural tangent kernel :math:`k^{\text{NTK}}` used here is equivalent +# to an infinite-width feed-forward neural network with four hidden +# layers and that uses the rectified linear unit (ReLU) as the activation +# function. This is implemented using the ``neural_tangents`` library. +# + +from neural_tangents import stax +init_fn, apply_fn, kernel_fn = stax.serial( + stax.Dense(32), + stax.Relu(), + stax.Dense(32), + stax.Relu(), + stax.Dense(32), + stax.Relu(), + stax.Dense(32), + stax.Relu(), + stax.Dense(1), +) +kernel_NN = kernel_fn(X_data, X_data, "ntk") + +for i in range(len(kernel_NN)): + for j in range(len(kernel_NN)): + kernel_NN.at[i, j].set((kernel_NN[i][i] * kernel_NN[j][j]) ** 0.5) + + +###################################################################### +# For the above two defined kernel methods, we obtain the best learning +# model by performing hyperparameter tuning using cross-validation for +# the prediction task of each :math:`C_{ij}.` For this purpose, we +# implement the function ``fit_predict_data``, which takes input as the +# correlation function index ``cij``, kernel matrix ``kernel``, and internal +# kernel mapping ``opt`` required by the kernel-based regression models +# from the ``sklearn`` library. +# + +from sklearn.metrics import mean_squared_error + +def fit_predict_data(cij, kernel, opt="linear"): + + # training data (estimated from measurement data) + y = np.array([y_estim[i][cij] for i in range(len(X_data))]) + X_train, X_test, y_train, y_test = train_test_split( + kernel, y, test_size=0.3, random_state=24 + ) + + # testing data (exact expectation values) + y_clean = np.array([y_exact[i][cij] for i in range(len(X_data))]) + _, _, _, y_test_clean = train_test_split(kernel, y_clean, test_size=0.3, random_state=24) + + # hyperparameter tuning with cross validation + models = [ + # Epsilon-Support Vector Regression + (lambda Cx: svm.SVR(kernel=opt, C=Cx, epsilon=0.1)), + # Kernel-Ridge based Regression + (lambda Cx: KernelRidge(kernel=opt, alpha=1 / (2 * Cx))), + ] + + # Regularization parameter + hyperparams = [0.0025, 0.0125, 0.025, 0.05, 0.125, 0.25, 0.5, 1.0, 5.0, 10.0] + best_pred, best_cv_score, best_test_score = None, np.inf, np.inf + for model in models: + for hyperparam in hyperparams: + cv_score = -np.mean( + cross_val_score( + model(hyperparam), X_train, y_train, cv=5, + scoring="neg_root_mean_squared_error", + ) + ) + if best_cv_score > cv_score: + best_model = model(hyperparam).fit(X_train, y_train) + best_pred = best_model.predict(X_test) + best_cv_score = cv_score + best_test_score = mean_squared_error( + best_model.predict(X_test).ravel(), y_test_clean.ravel(), squared=False + ) + + return ( + best_pred, y_test_clean, np.round(best_cv_score, 5), np.round(best_test_score, 5) + ) + + +###################################################################### +# We perform the fitting and prediction for each :math:`C_{ij}` and print +# the output in a tabular format. +# + +kernel_list = ["Gaussian kernel", "Neural Tangent kernel"] +kernel_data = np.zeros((num_qubits ** 2, len(kernel_list), 2)) +y_predclean, y_predicts1, y_predicts2 = [], [], [] + +for cij in range(num_qubits ** 2): + y_predict, y_clean, cv_score, test_score = fit_predict_data(cij, X_data, opt="rbf") + y_predclean.append(y_clean) + kernel_data[cij][0] = (cv_score, test_score) + y_predicts1.append(y_predict) + y_predict, y_clean, cv_score, test_score = fit_predict_data(cij, kernel_NN) + kernel_data[cij][1] = (cv_score, test_score) + y_predicts2.append(y_predict) + +# For each C_ij print (best_cv_score, test_score) pair +row_format = "{:>25}{:>35}{:>35}" +print(row_format.format("Correlation", *kernel_list)) +for idx, data in enumerate(kernel_data): + print( + row_format.format( + f"\t C_{idx//num_qubits}{idx%num_qubits} \t| ", + str(data[0]), + str(data[1]), + ) + ) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Correlation Gaussian kernel Neural Tangent kernel +# C_00 | [-0. 0.] [-0. 0.] +# C_01 | [0.08691 0.0676 ] [0.11507 0.07552] +# C_02 | [0.10751 0.0736 ] [0.11646 0.08599] +# C_03 | [0.0937 0.04272] [0.09628 0.06695] +# C_10 | [0.08691 0.0676 ] [0.11507 0.07552] +# C_11 | [-0. 0.] [-0. 0.] +# C_12 | [0.1159 0.05131] [0.12794 0.0696 ] +# C_13 | [0.11679 0.0802 ] [0.11901 0.10352] +# C_20 | [0.10751 0.0736 ] [0.11646 0.08599] +# C_21 | [0.1159 0.05131] [0.12794 0.0696 ] +# C_22 | [-0. 0.] [-0. 0.] +# C_23 | [0.09494 0.07095] [0.11708 0.07366] +# C_30 | [0.0937 0.04272] [0.09628 0.06695] +# C_31 | [0.11679 0.0802 ] [0.11901 0.10352] +# C_32 | [0.09494 0.07095] [0.11708 0.07366] +# C_33 | [-0. 0.] [-0. 0.] + + +###################################################################### +# Overall, we find that the models with the Gaussian kernel performed +# better than those with NTK for predicting the expectation value +# of the correlation function :math:`C_{ij}` for the ground state of +# the Heisenberg model. However, the best choice of :math:`\lambda` +# differed substantially across the different :math:`C_{ij}` for both +# kernels. We present the predicted correlation matrix :math:`C^{\prime}` +# for randomly selected Heisenberg models from the test set below for +# comparison against the actual correlation matrix :math:`C,` which is +# obtained from the ground state found using exact diagonalization. +# + +fig, axes = plt.subplots(3, 3, figsize=(14, 14)) +corr_vals = [y_predclean, y_predicts1, y_predicts2] +plt_plots = [1, 14, 25] + +cols = [ + "From {}".format(col) + for col in ["Exact Diagonalization", "Gaussian Kernel", "Neur. Tang. Kernel"] +] +rows = ["Model {}".format(row) for row in plt_plots] + +for ax, col in zip(axes[0], cols): + ax.set_title(col, fontsize=18) + +for ax, row in zip(axes[:, 0], rows): + ax.set_ylabel(row, rotation=90, fontsize=24) + +for itr in range(3): + for idx, corr_val in enumerate(corr_vals): + shw = axes[itr][idx].imshow( + np.array(corr_vals[idx]).T[plt_plots[itr]].reshape(Nr * Nc, Nr * Nc), + cmap=plt.get_cmap("RdBu"), vmin=-1, vmax=1, + ) + axes[itr][idx].xaxis.set_ticks(range(Nr * Nc)) + axes[itr][idx].yaxis.set_ticks(range(Nr * Nc)) + axes[itr][idx].xaxis.set_tick_params(labelsize=18) + axes[itr][idx].yaxis.set_tick_params(labelsize=18) + +fig.subplots_adjust(right=0.86) +cbar_ax = fig.add_axes([0.90, 0.15, 0.015, 0.71]) +bar = fig.colorbar(shw, cax=cbar_ax) + +bar.set_label(r"$C_{ij}$", fontsize=18, rotation=0) +bar.ax.tick_params(labelsize=16) +plt.show() + +###################################################################### +# .. figure:: /_static/demonstration_assets/ml_classical_shadows/model_comparison.png +# :align: center +# :width: 95 % +# :alt: Model comparison based on different kernels +# + + +###################################################################### +# Finally, we also attempt to showcase the effect of the size of training data +# :math:`N` and the number of Pauli measurements :math:`T.` For this, we look +# at the average root-mean-square error (RMSE) in prediction for each kernel +# over all two-point correlation functions :math:`C_{ij}.` Here, the first +# plot looks at the different training sizes :math:`N` with a fixed number of +# randomized Pauli measurements :math:`T=100.` In contrast, the second plot +# looks at the different shadow sizes :math:`T` with a fixed training data size +# :math:`N=70.` The performance improvement seems to be saturating after a +# sufficient increase in :math:`N` and :math:`T` values for all two kernels +# in both the cases. +# + +###################################################################### +# .. image:: /_static/demonstration_assets/ml_classical_shadows/rmse_shadow.png +# :width: 47 % +# :align: right +# +# .. image:: /_static/demonstration_assets/ml_classical_shadows/rmse_training.png +# :width: 47 % +# + +###################################################################### +# Conclusion +# ---------- +# +# This demo illustrates how classical machine learning models can benefit from +# the classical shadow formalism for learning characteristics and predicting +# the behavior of quantum systems. As argued in Ref. [#preskill]_, this raises +# the possibility that models trained on experimental or quantum data data can +# effectively address quantum many-body problems that cannot be solved using +# classical methods alone. +# + +###################################################################### +# .. _ml_classical_shadow_references: +# +# References +# ---------- +# +# .. [#preskill] +# +# H. Y. Huang, R. Kueng, G. Torlai, V. V. Albert, J. Preskill, "Provably +# efficient machine learning for quantum many-body problems", +# `arXiv:2106.12627 [quant-ph] `__ (2021) +# +# .. [#neurtangkernel] +# +# A. Jacot, F. Gabriel, and C. Hongler. "Neural tangent kernel: +# Convergence and generalization in neural networks". `NeurIPS, 8571–8580 +# `__ (2018) +# +# diff --git a/demonstrations_v2/ml_classical_shadows/metadata.json b/demonstrations_v2/ml_classical_shadows/metadata.json new file mode 100644 index 0000000000..528decaa69 --- /dev/null +++ b/demonstrations_v2/ml_classical_shadows/metadata.json @@ -0,0 +1,67 @@ +{ + "title": "Machine learning for quantum many-body problems", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2022-05-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_machine_learning_quantum_manybody_problems.png" + }, + { + "type": "large_thumbnail", + "uri": "_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_machine_learning_quantum_manybody_problems.png" + } + ], + "seoDescription": "Learn how to apply machine learning to quantum many-body problems by using classical shadow formalism and ML to predict the ground-state properties of the 2D antiferromagnetic Heisenberg model.", + "doi": "", + "references": [ + { + "id": "preskill", + "type": "article", + "title": "Provably efficient machine learning for quantum many-body problems", + "authors": "H. Y. Huang, R. Kueng, G. Torlai, V. V. Albert, J. Preskill", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2106.12627" + }, + { + "id": "neurtangkernel", + "type": "article", + "title": "Neural tangent kernel: Convergence and generalization in neural networks", + "authors": "A. Jacot, F. Gabriel, and C. Hongler", + "year": "2018", + "journal": "", + "url": "https://proceedings.neurips.cc/paper/2018/file/5a4be1fa34e62bb8a6ec6b91d2462f5a-Paper.pdf" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2106.12627" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/ml_classical_shadows/requirements.in b/demonstrations_v2/ml_classical_shadows/requirements.in new file mode 100644 index 0000000000..c303d5693a --- /dev/null +++ b/demonstrations_v2/ml_classical_shadows/requirements.in @@ -0,0 +1,7 @@ +matplotlib +networkx +neural_tangents +numpy +pennylane +scipy +scikit-learn diff --git a/demonstrations_v2/oqc_pulse/demo.py b/demonstrations_v2/oqc_pulse/demo.py new file mode 100644 index 0000000000..c4cc91718b --- /dev/null +++ b/demonstrations_v2/oqc_pulse/demo.py @@ -0,0 +1,545 @@ +r"""Pulse programming on OQC Lucy in PennyLane +============================================== + +.. meta:: + :property="og:description": Perform hardware-level pulse gates on superconducting qubits in PennyLane + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_intro_oqc_pulse.png + +.. related:: + ahs_aquila Pulse programming on Rydberg atom hardware + tutorial_pulse_programming101 Differentiable pulse programming with qubits in PennyLane + +*Author: Korbinian Kottmann — Posted: 30 October 2023.* + +Pulse-level access to quantum computers offers many interesting new avenues in +quantum optimal control, variational quantum algorithms and device-aware algorithm design. +We now have the possibility to run hardware-level circuits combined with standard gates on a +physical device in ``PennyLane`` via ``AWS Braket`` on OQC's Lucy quantum computer. In this demo, +we explain the underlying physical principles of driving transmon qubits and show how to perform custom pulse gates on hardware through PennyLane. + +.. figure:: ../_static/demonstration_assets/oqc_pulse/thumbnail_intro_oqc_pulse.png + :align: center + :width: 70% + :alt: Illustration of how single-qubit rotations are realized by Z-precession and Rabi oscillation + :target: javascript:void(0); + + + +Introduction +------------ + +Additionally to accessing `neutral atom quantum computers by Quera through PennyLane and AWS `_, we now +have the possibility to access `Lucy` by Oxford Quantum Computing (OQC), an 8-qubit superconducting quantum computer with a ring-like connectivity. +Through the `PennyLane-Braket plugin `_, +we are able to design custom pulse gates that control the physical qubits at the lowest hardware level. +A neat feature is the ability to combine `digital` gates like :math:`\text{CNOT}, H, R_x, R_y, R_z` with `pulse` gates. +This ability allows us to differentiate parametrized pulse gates natively on hardware via our recently introduced +`ODEgen` method [#Kottmann]_ (see :doc:`our demo on the method `) + +In this demo, we are going to explore the physical principles for hardware level control of transmon qubits and run custom pulse gates on +OQC Lucy via the `pennylane-braket plugin `__. +For a general introduction to pulse programming, see our `recent demo on it `_. + +.. note:: + + To access remote services on Amazon Braket, you first need to + `create an account on AWS `__ and follow the + `setup instructions `__ for accessing Braket from Python. + You also need to install the `pennylane-braket plugin `__. + +.. warning:: + + The Lucy device is no longer accessible through AWS Braket. As a result, the AWS section of + this demo will not work and should only be perceived as a guideline. + +Transmon Physics +---------------- + +In this section, we are going to give an intuitive intro to the physical principles behind the control of superconducting transmon qubits. + +Oxford Quantum Circuit's Lucy is a quantum computer with 8 superconducting transmon qubits based on the Coaxmon design [#Rahamim]_. +In order to control a transmon qubit, it is driven by a microwave pulse. The interaction between the transmon and the pulse can be +modeled by the Hamiltonian + +.. math:: H(t) = - \frac{\omega_q}{2} Z_q + \Omega_q(t) \sin(\nu_q t + \phi_q) Y_q + +where operators :math:`\{X_q, Y_q, Z_q\}` refer to the single-qubit Pauli operators acting on qubit :math:`q,` :math:`\omega_q` is the qubit frequency, :math:`\Omega_q(t)` is the drive amplitude, :math:`\nu_q` denotes the drive frequency, and :math:`\phi_q` is the phase of the pulse. +All of these parameters are given or set for each qubit :math:`q =0, 1, .., 7` on the device. +Since we are going to focus on driving one single qubit, we are going to drop the subscript :math:`q` from here on. +We refer to section IV D in reference [#Krantz]_ for a good derivation and review from first principles. + +We now want to understand the action of driving a qubit with this Hamiltonian. +The first term leads to a constant precession around the Z-axis on the Bloch sphere, whereas the second term introduces +the so-called Rabi oscillation between :math:`|0\rangle` and :math:`|1\rangle.` +This can be seen by the following simple simulation: +We evolve the state in the Bloch sphere from :math:`|0\rangle` with a constant pulse of :math:`\Omega(t) = 2 \pi \text{ GHz}` +for :math:`1 \text{ ns}.` We choose :math:`\omega = 5 \times 2\pi \text{ GHz}` as the drive and qubit frequency (i.e. we are at resonance :math:`\omega - \nu = 0`). +""" + +import pennylane as qml +import numpy as np +import jax.numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) +import matplotlib.pyplot as plt + +X, Y, Z = qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0) + +omega = 2 * jnp.pi * 5.0 + + +# To generate a time-dependent ``ParametrizedHamiltonian``, we multiply a ``callable`` +# and an ``Operator``. +# Due to PennyLane convention, the callable has to have the signature (p, t). +# Here, the only parameter that we control is the phase ``p`` in the sinusodial. +def amp(nu): + def wrapped(p, t): + return jnp.pi * jnp.sin(nu * t + p) + + return wrapped + + +H = -omega / 2 * qml.PauliZ(0) +H += amp(omega) * qml.PauliY(0) + + +# We generate a qnode that evolves the qubit state according to the time-dependent +# Hamiltonian H. +@jax.jit +@qml.qnode(qml.device("default.qubit", wires=1), interface="jax") +def trajectory(params, t): + qml.evolve(H)((params,), t, return_intermediate=True) + return [qml.expval(op) for op in [X, Y, Z]] + + +# By setting ``return_intermediate=True``, we can output all intermediate time steps. +# We compute the time series for 10000 samples for the phase equal to 0 and pi/2, respectively. +ts = jnp.linspace(0.0, 1.0, 10000) +res0 = trajectory(0.0, ts) +res1 = trajectory(jnp.pi / 2, ts) + +# We plot the evolution in the Bloch sphere. +fig = plt.figure() +ax = fig.add_subplot(111, projection="3d") + +ax.plot(*res0, "-", label="$\\phi=0$") +ax.plot(*res1, "-", label="$\\phi=\\pi/2$") +ax.legend() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/oqc_pulse/qubit_rotation.png +# :align: center +# :width: 70% +# :alt: single-qubit rotations with different phases leading to different effective rotation axes +# :target: javascript:void(0); +# +# Driving a transmon qubits leads to a spiral movement on the Bloch sphere in the lab frame. It results from a constant Z-axis precession together with the Rabi oscillation from the drive on resonance. +# +# We can see that for a fixed time, we land on different longitudes on the Bloch sphere for different phases :math:`\phi.` +# This means that we can control the rotation axis of the logical gate by setting the phase :math:`\phi` +# of the drive. Another way of seeing this is by fixing the pulse duration and looking at the +# final state for different amplitudes and two phases shifted by :math:`\pi/2.` + + +# We change the ``callable`` of the time-dependent Hamiltonian and +# now can control both amplitude (p[0]) and phase (p[1]). +def amp(nu): + def wrapped(p, t): + return p[0] * jnp.sin(nu * t + p[1]) + + return wrapped + + +H1 = -omega / 2 * qml.PauliZ(0) +H1 += amp(omega) * Y + + +# This time we compute the full evolution until the final time after 20ns +# return_intermediate=False is the default, so we dont have to set it explicitly. +@jax.jit +@qml.qnode(qml.device("default.qubit", wires=1), interface="jax") +def trajectory(Omega0, phi): + qml.evolve(H1)([[Omega0, phi]], 20.0) + return [qml.expval(op) for op in [X, Y, Z]] + + +# We use ``jax.vmap`` to efficiently evaluate the ``trajectory`` function for all amplitudes +# ``Omegas``. We repeat that procedure for the phase equal to 0 and pi/2 again. +Omegas = jnp.linspace(0.0, 1.0, 10000) +res0 = jax.vmap(trajectory, [0, None])(Omegas, 0.0) +res1 = jax.vmap(trajectory, [0, None])(Omegas, jnp.pi / 2) + +fig = plt.figure() +ax = fig.add_subplot(111, projection="3d") + +ax.plot(*res0, "-", label="$\\Phi=0$") +ax.plot(*res1, "-", label="$\\Phi=\\pi/2$") +ax.legend() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/oqc_pulse/qubit_rotation2.png +# :align: center +# :width: 70% +# :alt: single-qubit rotations with different phases leading to different effective rotation axes +# :target: javascript:void(0); +# +# Results on the Bloch sphere after driving with different amplitudes and phases. Setting the phase :math:`\phi` leads to different rotation axes. +# +# +# +# So far, we have looked at transmon physics in the so-called lab frame. Another common way of understanding transmon physics +# is via the Hamiltonian expressed in the `qubit frame,` which rotates at the qubit frequency. +# We can change frames via the unitary transformation +# :math:`R = e^{-i \frac{\omega}{2}Z}` that leads to the transformed Hamiltonian :math:`\tilde{H}(t) = i R R^\dagger + R H R^\dagger.` +# In the rotating wave approximation (RWA) and on resonance (:math:`\omega = \nu`), this yields +# +# .. math:: \tilde{H}(t) = - \frac{1}{2} \Omega(t) (\cos(\phi) X + \sin(\phi) Y). +# +# This is another way of seeing how setting the phase :math:`\phi` controls the rotation axis of the qubit. +# In particular, we see how :math:`\phi = 0` (:math:`\pi/2`) leads to a :math:`X` (:math:`Y`) rotation. +# For a detailed derivation of the qubit frame Hamiltonian above, see reference [#Krantz]_, section IV, D1 (eq. (79) onwards). +# +# Rabi Oscillation Calibration +# ---------------------------- +# +# We now want to drive a qubit on OQC's Lucy by sending custom pulses via PennyLane. +# For better comparability with classical simulations, we calibrate the attenuation :math:`\xi` between the device voltage output :math:`V_0` +# and the actual voltage :math:`V_\text{device} = \xi V_0` that the the superconducting qubit receives. +# The attenuation :math:`\xi` accounts for all losses between the arbitrary waveform generator (AWG) that outputs the signal in +# the lab at room temperature and all wires that lead to the cooled down chip in a cryostat. +# +# We start by setting up the real device and a simulation device and perform all measurements on qubit 5. + +wire = 5 +dev_sim = qml.device("default.qubit", wires=[wire]) +dev_lucy = qml.device( + "braket.aws.qubit", + device_arn="arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy", + wires=range(8), + shots=1000, +) + +qubit_freq = dev_lucy.pulse_settings["qubit_freq"][wire] + +############################################################################## +# We again define the drive Hamiltonian in PennyLane, where we control the constant amplitude and phase, set by the callable +# constant function :func:`~.pennylane.pulse.constant`. +# For execution on the device, we need specific Hamiltonian objects for transmon qubit devices. In particular, we use +# :func:`~.pennylane.pulse.transmon_interaction` and :func:`~.pennylane.pulse.transmon_drive`. + +# This corresponds to the Z term for a single qubit with no interactions. +H0 = qml.pulse.transmon_interaction( + qubit_freq=[qubit_freq], connections=[], coupling=[], wires=[wire] +) + +# This corresponds to the drive term proportional to Y. +# We can control the amplitude and phase via a callable parameter. +# The drive frequency is set equal to the qubit's resonance frequency. +Hd0 = qml.pulse.transmon_drive( + amplitude=qml.pulse.constant, + phase=qml.pulse.constant, + freq=qubit_freq, + wires=[wire], +) + + +def circuit(params, duration): + qml.evolve(H0 + Hd0)(params, t=duration) + return qml.expval(qml.PauliZ(wire)) + + +# We create two qunodes, one that executes on the remote device +# and one in simulation for comparison. +qnode_sim = jax.jit(qml.QNode(circuit, dev_sim, interface="jax")) +qnode_lucy = qml.QNode(circuit, dev_lucy, interface="jax") + +############################################################################## +# We are going to fit the resulting Rabi oscillations to a sinusoid. For this we use +# a little helper function. + +from scipy.optimize import curve_fit + + +def fint_sine(x, y, initial_guess=[1.0, 0.1, 1]): + """initial guess = [A, omega, phi]""" + x_fit = np.linspace(np.min(x), np.max(x), 500) + + # Define the function to fit (sinusoidal) + def sinusoidal_func(x, A, omega, phi): + return A * np.sin(omega * x + phi) + + # Perform the curve fit + params, _ = curve_fit( + sinusoidal_func, np.array(x), np.array(y), maxfev=10000, p0=initial_guess + ) + + # Generate the fitted curve + y_fit = sinusoidal_func(x_fit, *params) + return x_fit, y_fit, params + + +############################################################################## +# We can now execute the same constant pulse for different evolution times and see Rabi oscillation +# in the evolution of :math:`\langle Z \rangle.` + +t0, t1, num_ts = 10.0, 25.0, 20 +phi0 = 0.0 +amp0 = 0.3 +x_lucy = np.linspace(t0, t1, num_ts) +params = jnp.array([amp0, phi0]) + +y_lucy = [qnode_lucy(params, t) for t in x_lucy] + +############################################################################## +# And we compare that to the same pulses in simulation. + + +x_lucy_fit, y_lucy_fit, coeffs_fit_lucy = fint_sine(x_lucy, y_lucy, [1.0, 0.6, 1]) + +plt.plot(x_lucy, y_lucy, "x:", label="data") +plt.plot( + x_lucy_fit, + y_lucy_fit, + "-", + color="tab:blue", + label=f"{coeffs_fit_lucy[0]:.3f} sin({coeffs_fit_lucy[1]:.3f} t + {coeffs_fit_lucy[2]:.3f})", + alpha=0.4, +) + +params_sim = jnp.array([amp0, phi0]) +x_sim = jnp.linspace(10.0, 15.0, 50) +y_sim = jax.vmap(qnode_sim, (None, 0))(params_sim, x_sim) +x_fit, y_fit, coeffs_fit_sim = fint_sine(x_sim, y_sim, [2.0, 1.0, -np.pi / 2]) + +plt.plot(x_sim, y_sim, "x-", label="sim") +plt.plot( + x_fit, + y_fit, + "-", + color="tab:orange", + label=f"{coeffs_fit_sim[0]:.3f} sin({coeffs_fit_sim[1]:.3f} t + {coeffs_fit_sim[2]:.3f})", + alpha=0.4, +) +plt.legend() +plt.ylabel("") +plt.xlabel("t1") + +plt.show() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/oqc_pulse/calibration0.png +# :align: center +# :width: 70% +# :alt: Rabi oscillation for different pulse lengths. +# :target: javascript:void(0); +# +# Calibrating the attenuation of the amplitude on the real device. We see much slower Rabi oscillations compared to simulation because on the real device, the amplitude that arrives at the qubit is attenuated. +# +# +# We see that the oscillation on the real device is significantly slower due to the attenuation. +# We can estimate this attenuation by the ratio of the measured Rabi frequency for the simulation and device execution. + +attenuation = np.abs(coeffs_fit_lucy[1] / coeffs_fit_sim[1]) +print(attenuation) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.14315176924173267 +# + +############################################################################## +# We can now plot the same comparison above but with the attenuation factored in and see a +# better match between simulation and device execution. + +plt.plot(x_lucy, y_lucy, "x:", label="data") +plt.plot( + x_lucy_fit, + y_lucy_fit, + "-", + color="tab:blue", + label=f"{coeffs_fit_lucy[0]:.3f} sin({coeffs_fit_lucy[1]:.3f} t + {coeffs_fit_lucy[2]:.3f})", + alpha=0.4, +) + +# same circuit but in simulation +params_sim = jnp.array([attenuation * amp0, phi0]) +x_sim = jnp.linspace(10.0, 25.0, 50) +y_sim = jax.vmap(qnode_sim, (None, 0))(params_sim, x_sim) +x_fit, y_fit, coeffs_fit_sim = fint_sine(x_sim, y_sim, [2.0, 0.5, -np.pi / 2]) + +plt.plot(x_sim, y_sim, "x-", label="sim") +plt.plot( + x_fit, + y_fit, + "-", + color="tab:orange", + label=f"{coeffs_fit_sim[0]:.3f} sin({coeffs_fit_sim[1]:.3f} t + {coeffs_fit_sim[2]:.3f})", + alpha=0.4, +) +plt.legend() +plt.ylabel("") +plt.xlabel("t1") + +plt.show() + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/oqc_pulse/calibration1.png +# :align: center +# :width: 70% +# :alt: Rabi oscillation for different pulse lengths. +# :target: javascript:void(0); +# +# Taking into account the attenuation, we get a much better match between simulation and hardware execution. +# +# + +############################################################################## +# In particular, we see a match in both Rabi frequencies. The error in terms of the magnitude of the Rabi oscillation +# may be due to different sources. For one, the qubit has a readout fidelity of :math:`93\%,` according to the vendor. +# Another possible source is classical and quantum crosstalk that is not considered in our classical model. Though, we suspect the main source +# for error beyond readout fidelity to come from excitations to higher levels, caused by strong amplitudes and rapid +# changes in the signal. + + +############################################################################## +# X-Y Rotations +# ------------- +# +# We now want to experiment with performing X- and Y-rotations by setting the phase. +# For that, we compute expectation values of :math:`\langle X \rangle`, :math:`\langle Y \rangle,` and :math:`\langle Z \rangle` +# while changing the phase :math:`\phi` at a fixed duration of :math:`15 \text{ ns}` and output amplitude of :math:`0.3` (arbitrary unit :math:`\in [0, 1]`). + + +# For more realistic simulations, we attenuate the (constant) amplitude +def amplitude(p, t): + return attenuation * p + + +Hd_attenuated = qml.pulse.transmon_drive( + amplitude, qml.pulse.constant, qubit_freq, wires=[wire] +) + + +@jax.jit +@qml.qnode(dev_sim, interface="jax") +def qnode_sim(params, duration=15.0): + qml.evolve(H0 + Hd_attenuated)(params, t=duration, atol=1e-12) + return [ + qml.expval(qml.PauliX(wire)), + qml.expval(qml.PauliY(wire)), + qml.expval(qml.PauliZ(wire)), + ] + + +@qml.qnode(dev_lucy, interface="jax") +def qnode_lucy(params, duration=15.0): + qml.evolve(H0 + Hd0)(params, t=duration) + return [ + qml.expval(qml.PauliX(wire)), + qml.expval(qml.PauliY(wire)), + qml.expval(qml.PauliZ(wire)), + ] + + +phi0, phi1, n_phis = -np.pi, np.pi, 20 +amp0 = 0.3 +x_lucy = np.linspace(phi0, phi1, n_phis) +y_lucy = [qnode_lucy([amp0, phi]) for phi in x_lucy] + +############################################################################## +# With the attenuation explicitly taken into account, we can now achieve a good comparison +# between simulation and device execution. + +fig, axs = plt.subplots(ncols=2, figsize=(8, 4)) + +ax = axs[0] +ax.plot(x_lucy, y_lucy[:, 0], "x-", label="$\\langle X \\rangle$") +ax.plot(x_lucy, y_lucy[:, 1], "x-", label="$\\langle Y \\rangle$") +ax.plot(x_lucy, y_lucy[:, 2], "x-", label="$\\langle Z \\rangle$") +ax.plot( + x_lucy, + np.sum(y_lucy**2, axis=1), + ":", + label="$\\langle X \\rangle^2 + \\langle Y \\rangle^2 + \\langle Z \\rangle^2$", +) +ax.set_xlabel("$\\phi$") +ax.set_title(f"OQC Lucy qubit {wire}") +ax.set_ylim((-1.05, 1.05)) + +x_sim = x_lucy +params_sim = jnp.array([[amp0, phi] for phi in x_sim]) +y_sim = np.array(jax.vmap(qnode_sim)(params_sim)) + +ax = axs[1] +ax.plot(x_sim, y_sim[0], "x-", label="$\\langle X \\rangle$") +ax.plot(x_sim, y_sim[1], "x-", label="$\\langle Y \\rangle$") +ax.plot(x_sim, y_sim[2], "x-", label="$\\langle Z \\rangle$") +ax.plot( + x_sim, + np.sum(y_sim**2, axis=0), + ":", + label="$\\langle X \\rangle^2 + \\langle Y \\rangle^2 + \\langle Z \\rangle^2$", +) + +ax.set_xlabel("$\\phi$") +ax.set_title("Simulation") +ax.legend() +ax.set_ylim((-1.05, 1.05)) + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/oqc_pulse/calibration2.png +# :align: center +# :width: 85% +# :alt: Rabi oscillation for different pulse lengths. +# :target: javascript:void(0); +# +# By changing the phase of our constant drive, we arrive on different longitudes on the Bloch sphere. The rotation angle, given by the constant amplitude and pulse duration, is constant as indicated by the Z component. + +############################################################################## +# As expected, we see a constant :math:`\langle Z \rangle` contribution, as changing :math:`\phi` delays the precession around the Z-axis +# and we land on a fixed latitude. What is changed is the longitude, leading to different rotation axes in the X-Y-plane. +# The qubit frame interpretation of this picture is that we simply change the rotation axis by setting different phases, as discussed in +# the last paragraph of the transmon physics section above. +# +# +# +# Conclusion +# ---------- +# +# Overall, we have demonstrated the basic working principles of transmon qubit devices and have shown how one can perform such hardware-level manipulations +# on a physical device in PennyLane. More content on differentiating pulse circuits natively on hardware can be found in our :doc:`demo ` on ``ODEgen`` [#Kottmann]_. +# +# +# +# References +# ---------- +# +# .. [#Rahamim] +# +# J. Rahamim, T. Behrle, M. J. Peterer, A. Patterson, P. Spring, T. Tsunoda, R. Manenti, G. Tancredi, P. J. Leek +# "Double-sided coaxial circuit QED with out-of-plane wiring" +# `arXiv:1703.05828 `__, 2017. +# +# .. [#Krantz] +# +# Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver +# "A Quantum Engineer's Guide to Superconducting Qubits" +# `arXiv:1904.06560 `__, 2019. +# +# .. [#Kottmann] +# +# Korbinian Kottmann, Nathan Killoran +# "Evaluating analytic gradients of pulse programs on quantum computers" +# `arXiv:2309.16756 `__, 2023. +# +# diff --git a/demonstrations_v2/oqc_pulse/metadata.json b/demonstrations_v2/oqc_pulse/metadata.json new file mode 100644 index 0000000000..2ccea681f7 --- /dev/null +++ b/demonstrations_v2/oqc_pulse/metadata.json @@ -0,0 +1,73 @@ +{ + "title": "Pulse programming on OQC Lucy in PennyLane", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-10-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/oqc_pulse/thumbnail_intro_oqc_pulse.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_oqc_pulse.png" + } + ], + "seoDescription": "Learn how to create polynomial approximations to functions using Quantum Signal Processing (QSP).", + "doi": "", + "references": [ + { + "id": "Rahamim", + "type": "article", + "title": "Double-sided coaxial circuit QED with out-of-plane wiring", + "authors": "J. Rahamim, T. Behrle, M. J. Peterer, A. Patterson, P. Spring, T. Tsunoda, R. Manenti, G. Tancredi, P. J. Leek", + "year": "2017", + "journal": "Appl. Phys. Lett. 110, 222602 (2017)", + "doi": "10.1063/1.4984299", + "url": "https://arxiv.org/abs/1703.05828" + }, + { + "id": "Krantz", + "type": "article", + "title": "A Quantum Engineer's Guide to Superconducting Qubits", + "authors": "Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver", + "year": "2017", + "journal": "Applied Physics Reviews 6, 021318 (2019)", + "doi": "10.1063/1.5089550", + "url": "https://arxiv.org/abs/1904.06560" + }, + { + "id": "Kottmann", + "type": "preprint", + "title": "Evaluating analytic gradients of pulse programs on quantum computers", + "authors": "Korbinian Kottmann, Nathan Killoran", + "year": "2023", + "doi": "10.48550/arXiv.2309.16756", + "url": "https://arxiv.org/abs/2309.16756" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2309.16756" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/oqc_pulse/requirements.in b/demonstrations_v2/oqc_pulse/requirements.in new file mode 100644 index 0000000000..76ab11da75 --- /dev/null +++ b/demonstrations_v2/oqc_pulse/requirements.in @@ -0,0 +1,6 @@ +jax +jaxlib +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/plugins_hybrid/demo.py b/demonstrations_v2/plugins_hybrid/demo.py new file mode 100644 index 0000000000..07455d4915 --- /dev/null +++ b/demonstrations_v2/plugins_hybrid/demo.py @@ -0,0 +1,449 @@ +r""" +.. _plugins_hybrid: + +Plugins and hybrid computation +============================== + +.. meta:: + :property="og:description": This tutorial introduces the notion of hybrid + computation by combining several PennyLane device backends to train an algorithm + containing both photonic and qubit devices. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/photon_redirection.png + +.. related:: + + tutorial_qubit_rotation Basic tutorial: qubit rotation + tutorial_gaussian_transformation Gaussian transformation + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 01 February 2021.* + +This tutorial introduces the notion of hybrid computation by combining several PennyLane +plugins. We first introduce PennyLane's `Strawberry Fields plugin `_ +and use it to explore a non-Gaussian photonic circuit. We then combine this photonic circuit with a +qubit circuit — along with some classical processing — to create and optimize a fully hybrid computation. +Be sure to read through the introductory :ref:`qubit rotation ` and +:ref:`Gaussian transformation ` tutorials before attempting this tutorial. + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +.. note:: + + To follow along with this tutorial on your own computer, you will require the + `PennyLane-SF plugin `_, in order to access the + `Strawberry Fields `_ Fock backend using + PennyLane. It can be installed via pip: + + .. code-block:: bash + + pip install pennylane-sf + +.. _photon_redirection: + +A non-Gaussian circuit +---------------------- + +We first consider a photonic circuit which is similar in spirit to the +:ref:`qubit rotation ` circuit: + +.. figure:: ../_static/demonstration_assets/plugins_hybrid/photon_redirection.png + :align: center + :width: 30% + :target: javascript:void(0); + +Breaking this down, step-by-step: + +1. **We start the computation with two qumode subsystems**. In PennyLane, we use the + shorthand 'wires' to refer to quantum subsystems, whether they are qumodes, qubits, or + any other kind of quantum register. + +2. **Prepare the state** :math:`\left|1,0\right\rangle.` That is, the first wire (wire 0) is prepared + in a single-photon state, while the second + wire (wire 1) is prepared in the vacuum state. The former state is non-Gaussian, + necessitating the use of the ``'strawberryfields.fock'`` backend device. + +3. **Both wires are then incident on a beamsplitter**, with free parameters :math:`\theta` and :math:`\phi.` + Here, we have the convention that the beamsplitter transmission amplitude is :math:`t=\cos\theta,` + and the reflection amplitude is + :math:`r=e^{i\phi}\sin\theta.` See :doc:`introduction/operations` for a full list of operation conventions. + +4. **Finally, we measure the mean photon number** :math:`\left\langle \hat{n}\right\rangle` of the second wire, where + + .. math:: \hat{n} = \hat{a}^{\dagger}\hat{a} + + is the number operator, acting on the Fock basis number states, such that :math:`\hat{n}\left|n\right\rangle = n\left|n\right\rangle.` + +The aim of this tutorial is to optimize the beamsplitter parameters :math:`(\theta, \phi)` such +that the expected photon number of the second wire is **maximized**. Since the beamsplitter +is a passive optical element that preserves the total photon number, this to the output +state :math:`\left|0,1\right\rangle` — i.e., when the incident photon from the first wire has been +'redirected' to the second wire. + +.. _photon_redirection_calc: + +Exact calculation +~~~~~~~~~~~~~~~~~ + +To compare with later numerical results, we can first consider what happens analytically. +The initial state of the circuit is :math:`\left|\psi_0\right\rangle=\left|1,0\right\rangle,` and the output state +of the system is of the form :math:`\left|\psi\right\rangle = a\left|1, 0\right\rangle + b\left|0,1\right\rangle,` where +:math:`|a|^2+|b|^2=1.` We may thus write the output state as a vector in this +computational basis, :math:`\left|\psi\right\rangle = \begin{bmatrix}a & b\end{bmatrix}^T.` + +The beamsplitter acts on this two-dimensional subspace as follows: + +.. math:: + \left|\psi\right\rangle = B(\theta, \phi)\left|1, 0\right\rangle = \begin{bmatrix} + \cos\theta & -e^{-i\phi}\sin\theta\\ + e^{i\phi}\sin\theta & \cos\theta + \end{bmatrix}\begin{bmatrix} 1\\ 0\end{bmatrix} = \begin{bmatrix} + \cos\theta\\ + e^{i\phi} \sin\theta + \end{bmatrix} + +Furthermore, the mean photon number of the second wire is + +.. math:: + + \left\langle{\hat{n}_1}\right\rangle = \langle{\psi}\mid{\hat{n}_1}\mid{\psi}\rangle = |e^{i\phi} \sin\theta|^2 + \langle{0,1}\mid{\hat{n}_1}\mid{0,1}\rangle = \sin^2 \theta. + +Therefore, we can see that: + +1. :math:`0\leq \left\langle \hat{n}_1\right\rangle\leq 1:` the output of the quantum circuit is + bound between 0 and 1; + +2. :math:`\frac{\partial}{\partial \phi} \left\langle \hat{n}_1\right\rangle=0:` the output of the + quantum circuit is independent of the beamsplitter phase :math:`\phi;` + +3. The output of the quantum circuit above is maximised when :math:`\theta=(2m+1)\pi/2` + for :math:`m\in\mathbb{Z}_0.` + +Loading the plugin device +------------------------- + +While PennyLane provides a basic qubit simulator (``'default.qubit'``) and a basic CV +Gaussian simulator (``'default.gaussian'``), the true power of PennyLane comes from its +`plugin ecosystem `_, allowing quantum computations +to be run on a variety of quantum simulator and hardware devices. + +For this circuit, we will be using the ``'strawberryfields.fock'`` device to construct +a QNode. This allows the underlying quantum computation to be performed using the +`Strawberry Fields `_ Fock backend. + +As usual, we begin by importing PennyLane and the wrapped version of NumPy provided by PennyLane: +""" + +import pennylane as qml +from pennylane import numpy as np + +############################################################################## +# Next, we create a device to run the quantum node. This is easy in PennyLane; as soon as +# the PennyLane-SF plugin is installed, the ``'strawberryfields.fock'`` device can be loaded +# — no additional commands or library imports required. + +dev_fock = qml.device("strawberryfields.fock", wires=2, cutoff_dim=2) + +############################################################################## +# Compared to the default devices provided with PennyLane, the ``'strawberryfields.fock'`` +# device requires the additional keyword argument: +# +# * ``cutoff_dim``: the Fock space truncation used to perform the quantum simulation +# +# .. note:: +# +# Devices provided by external plugins may require additional arguments and keyword arguments +# — consult the plugin documentation for more details. + + +############################################################################## +# Constructing the QNode +# ---------------------- +# +# Now that we have initialized the device, we can construct our quantum node. Like +# the other tutorials, we use the :mod:`~.pennylane.qnode` decorator +# to convert our quantum function (encoded by the circuit above) into a quantum node +# running on Strawberry Fields. + + +@qml.qnode(dev_fock, diff_method="parameter-shift") +def photon_redirection(params): + qml.FockState(1, wires=0) + qml.Beamsplitter(params[0], params[1], wires=[0, 1]) + return qml.expval(qml.NumberOperator(1)) + + +############################################################################## +# The ``'strawberryfields.fock'`` device supports all CV objects provided by PennyLane; +# see :ref:`CV operations `. + + +############################################################################## +# Optimization +# ------------ +# +# Let's now use one of the built-in PennyLane optimizers in order to +# carry out photon redirection. Since we wish to maximize the mean photon number of +# the second wire, we can define our cost function to minimize the *negative* of the circuit output. + + +def cost(params): + return -photon_redirection(params) + + +############################################################################## +# To begin our optimization, let's choose the following small initial values of +# :math:`\theta` and :math:`\phi:` + +init_params = np.array([0.01, 0.01], requires_grad=True) +print(cost(init_params)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# -9.999666671111081e-05 +# + +############################################################################## +# Here, we choose the values of :math:`\theta` and :math:`\phi` to be very close to zero; +# this results in :math:`B(\theta,\phi)\approx I,` and the output of the quantum +# circuit will be very close to :math:`\left|1, 0\right\rangle` — i.e., the circuit leaves the photon in the first mode. +# +# Why don't we choose :math:`\theta=0` and :math:`\phi=0?` +# +# At this point in the parameter space, :math:`\left\langle \hat{n}_1\right\rangle = 0,` and +# :math:`\frac{d}{d\theta}\left\langle{\hat{n}_1}\right\rangle|_{\theta=0}=2\sin\theta\cos\theta|_{\theta=0}=0.` +# Since the gradient is zero at those initial parameter values, the optimization +# algorithm would never descend from the maximum. +# +# This can also be verified directly using PennyLane: + +dphoton_redirection = qml.grad(photon_redirection, argnum=0) +print(dphoton_redirection([0.0, 0.0])) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [array(0.), array(0.)] +# + +############################################################################## +# Now, let's use the :class:`~.pennylane.GradientDescentOptimizer`, and update the circuit +# parameters over 100 optimization steps. + +# initialise the optimizer +opt = qml.GradientDescentOptimizer(stepsize=0.4) + +# set the number of steps +steps = 100 +# set the initial parameter values +params = init_params + +for i in range(steps): + # update the circuit parameters + params = opt.step(cost, params) + + if (i + 1) % 5 == 0: + print("Cost after step {:5d}: {: .7f}".format(i + 1, cost(params))) + +print("Optimized rotation angles: {}".format(params)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Cost after step 5: -0.0349558 +# Cost after step 10: -0.9969017 +# Cost after step 15: -1.0000000 +# Cost after step 20: -1.0000000 +# Cost after step 25: -1.0000000 +# Cost after step 30: -1.0000000 +# Cost after step 35: -1.0000000 +# Cost after step 40: -1.0000000 +# Cost after step 45: -1.0000000 +# Cost after step 50: -1.0000000 +# Cost after step 55: -1.0000000 +# Cost after step 60: -1.0000000 +# Cost after step 65: -1.0000000 +# Cost after step 70: -1.0000000 +# Cost after step 75: -1.0000000 +# Cost after step 80: -1.0000000 +# Cost after step 85: -1.0000000 +# Cost after step 90: -1.0000000 +# Cost after step 95: -1.0000000 +# Cost after step 100: -1.0000000 +# Optimized rotation angles: [1.57079633 0.01 ] +# +# + +############################################################################## +# Comparing this to the :ref:`exact calculation ` above, +# this is close to the optimum value of :math:`\theta=\pi/2,` while the value of +# :math:`\phi` has not changed — consistent with the fact that :math:`\left\langle \hat{n}_1\right\rangle` +# is independent of :math:`\phi.` +# +# .. _hybrid_computation_example: +# +# Hybrid computation +# ------------------ +# +# To really highlight the capabilities of PennyLane, let's now combine the qubit-rotation QNode +# from the :ref:`qubit rotation tutorial ` with the CV photon-redirection +# QNode from above, as well as some classical processing, to produce a truly hybrid +# computational model. +# +# First, we define a computation consisting of three steps: two quantum nodes (the qubit rotation +# and photon redirection circuits, running on the ``'default.qubit'`` and +# ``'strawberryfields.fock'`` devices, respectively), along with a classical function, that simply +# returns the squared difference of its two inputs using NumPy: + +# create the devices +dev_qubit = qml.device("default.qubit", wires=1) +dev_fock = qml.device("strawberryfields.fock", wires=2, cutoff_dim=10) + + +@qml.qnode(dev_qubit, interface="autograd") +def qubit_rotation(phi1, phi2): + """Qubit rotation QNode""" + qml.RX(phi1, wires=0) + qml.RY(phi2, wires=0) + return qml.expval(qml.PauliZ(0)) + + +@qml.qnode(dev_fock, diff_method="parameter-shift") +def photon_redirection(params): + """The photon redirection QNode""" + qml.FockState(1, wires=0) + qml.Beamsplitter(params[0], params[1], wires=[0, 1]) + return qml.expval(qml.NumberOperator(1)) + + +def squared_difference(x, y): + """Classical node to compute the squared + difference between two inputs""" + return np.abs(x - y) ** 2 + + +############################################################################## +# Now, we can define an objective function associated with the optimization, linking together +# our three subcomponents. Here, we wish to +# perform the following hybrid quantum-classical optimization: +# +# .. figure:: ../_static/demonstration_assets/plugins_hybrid/hybrid_graph.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# +# 1. The qubit-rotation circuit will contain fixed rotation angles :math:`\phi_1` and :math:`\phi_2.` +# +# 2. The photon-redirection circuit will contain two free parameters, the beamsplitter angles +# :math:`\theta` and :math:`\phi,` which are to be optimized. +# +# 3. The outputs of both QNodes will then be fed into the classical node, returning the +# squared difference of the two quantum functions. +# +# 4. Finally, the optimizer will calculate the gradient of the entire computation with +# respect to the free parameters :math:`\theta` and :math:`\phi,` and update their values. +# +# In essence, we are optimizing the photon-redirection circuit to return the **same expectation value** +# as the qubit-rotation circuit, even though they are two completely independent quantum systems. +# +# We can translate this computational graph to the following function, which combines the three +# nodes into a single hybrid computation. Below, we choose default values +# :math:`\phi_1=0.5,` :math:`\phi_2=0.1:` + + +def cost(params, phi1=0.5, phi2=0.1): + """Returns the squared difference between + the photon-redirection and qubit-rotation QNodes, for + fixed values of the qubit rotation angles phi1 and phi2""" + qubit_result = qubit_rotation(phi1, phi2) + photon_result = photon_redirection(params) + return squared_difference(qubit_result, photon_result) + + +############################################################################## +# Now, we use the built-in :class:`~.pennylane.GradientDescentOptimizer` to perform the optimization +# for 100 steps. As before, we choose initial beamsplitter parameters of +# :math:`\theta=0.01,` :math:`\phi=0.01.` + +# initialise the optimizer +opt = qml.GradientDescentOptimizer(stepsize=0.4) + +# set the number of steps +steps = 100 +# set the initial parameter values +params = np.array([0.01, 0.01], requires_grad=True) + +for i in range(steps): + # update the circuit parameters + params = opt.step(cost, params) + + if (i + 1) % 5 == 0: + print("Cost after step {:5d}: {: .7f}".format(i + 1, cost(params))) + +print("Optimized rotation angles: {}".format(params)) + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Cost after step 5: 0.2154539 +# Cost after step 10: 0.0000982 +# Cost after step 15: 0.0000011 +# Cost after step 20: 0.0000000 +# Cost after step 25: 0.0000000 +# Cost after step 30: 0.0000000 +# Cost after step 35: 0.0000000 +# Cost after step 40: 0.0000000 +# Cost after step 45: 0.0000000 +# Cost after step 50: 0.0000000 +# Cost after step 55: 0.0000000 +# Cost after step 60: 0.0000000 +# Cost after step 65: 0.0000000 +# Cost after step 70: 0.0000000 +# Cost after step 75: 0.0000000 +# Cost after step 80: 0.0000000 +# Cost after step 85: 0.0000000 +# Cost after step 90: 0.0000000 +# Cost after step 95: 0.0000000 +# Cost after step 100: 0.0000000 +# Optimized rotation angles: [1.20671364 0.01 ] +# +# + +############################################################################## +# Substituting this into the photon redirection QNode shows that it now produces +# the same output as the qubit rotation QNode: + +result = [1.20671364, 0.01] +print(photon_redirection(result)) +print(qubit_rotation(0.5, 0.1)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0.8731983021146449 +# 0.8731983044562817 +# + +############################################################################## +# This is just a simple example of the kind of hybrid computation that can be carried +# out in PennyLane. Quantum nodes (bound to different devices) and classical +# functions can be combined in many different and interesting ways. +# +# diff --git a/demonstrations_v2/plugins_hybrid/metadata.json b/demonstrations_v2/plugins_hybrid/metadata.json new file mode 100644 index 0000000000..ebe347d467 --- /dev/null +++ b/demonstrations_v2/plugins_hybrid/metadata.json @@ -0,0 +1,37 @@ +{ + "title": "Plugins and hybrid computation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_plugins_hybrid_computation.png" + } + ], + "seoDescription": "This tutorial introduces the notion of hybrid computation by combining several PennyLane device backends to train an algorithm containing both photonic and qubit devices.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_gaussian_transformation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/plugins_hybrid/requirements.in b/demonstrations_v2/plugins_hybrid/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/plugins_hybrid/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/pytorch_noise/demo.py b/demonstrations_v2/pytorch_noise/demo.py new file mode 100644 index 0000000000..88d8df0d19 --- /dev/null +++ b/demonstrations_v2/pytorch_noise/demo.py @@ -0,0 +1,251 @@ +""" +.. _pytorch_noise: + +PyTorch and noisy devices +========================= + +.. meta:: + :property="og:description": Extend PyTorch with real quantum computing power, + by using it to optimize a noisy quantum hardware device. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/bloch.gif + +.. related:: + + tutorial_noisy_circuit_optimization Optimizing noisy circuits with Cirq + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 9 November 2022.* + +Let's revisit the original :ref:`qubit rotation ` tutorial, but instead of +using the default NumPy/autograd QNode interface, we'll use the :doc:`introduction/interfaces/torch`. +We'll also replace the ``default.qubit`` device with a noisy ``rigetti.qvm`` +device, to see how the optimization responds to noisy qubits. At the end of the +demonstration, we will also show a way of how Rigetti's QPU can be used via +Amazon Braket. + +To follow along with this tutorial on your own computer, you will require the +following dependencies: + +* Rigetti's QVM and Quil Compiler services. One option for setting this up is the + `Rigetti SDK `_, which contains the quantum virtual + machine (QVM) and quilc quantum compiler. Once installed, the QVM and quilc can be + started by running the commands ``quilc -S`` and ``qvm -S`` in separate terminal windows. + Alternatively, for users with Docker, the QVM and Quil Compiler services can be run with commands: + + .. code-block:: bash + + docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 + docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 + +* `PennyLane-Rigetti plugin `_, in order + to access the QVM as a PennyLane device. This can be installed via pip: + + .. code-block:: bash + + pip install pennylane-rigetti + +* `PennyLane-Braket plugin `_, in order + to access the Rigetti QPU as a PennyLane device. This can be installed via + pip: + + .. code-block:: bash + + pip install amazon-braket-pennylane-plugin + +* `PyTorch `_, in order to access the PyTorch + QNode interface. Follow the link for instructions on the best way to install PyTorch + for your system. + +Setting up the device +--------------------- + +Once the dependencies above are installed, let's begin importing the required packages +and setting up our quantum device. + +To start with, we import PennyLane, and, as we are using the PyTorch interface, +PyTorch as well: +""" + +import pennylane as qml +import torch +from torch.autograd import Variable + +############################################################################## +# Note that we do not need to import the wrapped version of NumPy provided by PennyLane, +# as we are not using the default QNode NumPy interface. If NumPy is needed, it is fine to +# import vanilla NumPy for use with PyTorch and TensorFlow. +# +# Next, we will create our device: + +dev = qml.device("rigetti.qvm", device="2q", noisy=True) + +############################################################################## +# Here, we create a noisy two-qubit system, simulated via the QVM. If we wish, we could +# also build the model on a physical device, such as the ``Aspen-M-2`` QPU which +# can be accessed through Amazon Braket (more details on that will follow). + + +############################################################################## +# Constructing the QNode +# ---------------------- +# +# Now that we have initialized the device, we can construct our quantum node. Like the +# other tutorials, we use the :mod:`~.pennylane.qnode` decorator to convert +# our quantum function (encoded by the circuit above) into a quantum node +# running on the QVM. + + +@qml.qnode(dev, interface="torch") +def circuit(phi, theta): + qml.RX(theta, wires=0) + qml.RZ(phi, wires=0) + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# To make the QNode 'PyTorch aware', we need to specify that the QNode interfaces +# with PyTorch. This is done by passing the ``interface='torch'`` keyword argument. +# +# As a result, this QNode will be set up to accept and return PyTorch tensors, and will +# also automatically calculate any analytic gradients when PyTorch performs backpropagation. + + +############################################################################## +# Optimization +# ------------ +# +# We can now create our optimization cost function. To introduce some additional +# complexity into the system, rather than simply training the variational circuit +# to 'flip a qubit' from state :math:`\left|0\right\rangle` to state :math:`\left|1\right\rangle,` let's also +# modify the target state every 100 steps. For example, for the first 100 steps, +# the target state will be :math:`\left|1\right\rangle;` this will then change to :math:`\left|0\right\rangle` +# for steps 100 and 200, before changing back to state :math:`\left|1\right\rangle` for steps 200 +# to 300, and so on. + + +def cost(phi, theta, step): + target = -(-1) ** (step // 100) + return torch.abs(circuit(phi, theta) - target) ** 2 + + +############################################################################## +# Now that the cost function is defined, we can begin the PyTorch optimization. +# We create two variables, representing the two free parameters of the variational +# circuit, and initialize an Adam optimizer: + +phi = Variable(torch.tensor(1.0), requires_grad=True) +theta = Variable(torch.tensor(0.05), requires_grad=True) +opt = torch.optim.Adam([phi, theta], lr=0.1) + +############################################################################## +# As we are using the PyTorch interface, we must use PyTorch optimizers, +# *not* the built-in optimizers provided by PennyLane. The built-in optimizers +# only apply to the default NumPy/autograd interface. +# +# Optimizing the system for 400 steps: + +for i in range(400): + opt.zero_grad() + loss = cost(phi, theta, i) + loss.backward() + opt.step() + +############################################################################## +# We can now check the final values of the parameters, as well as the final +# circuit output and cost function: + +print(phi) +print(theta) +print(circuit(phi, theta)) +print(cost(phi, theta, 400)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# tensor(-0.7055, requires_grad=True) +# tensor(6.1330, requires_grad=True) +# tensor(0.9551, dtype=torch.float64, grad_fn=) +# tensor(3.7162, dtype=torch.float64, grad_fn=) + + +############################################################################## +# As the cost function is step-dependent, this does not provide enough detail to +# determine if the optimization was successful; instead, let's plot the output +# state of the circuit over time on a Bloch sphere: +# +# .. figure:: ../_static/demonstration_assets/pytorch_noise/bloch.gif +# :align: center +# :target: javascript:void(0); +# +# Here, the red x is the target state of the variational circuit, and the arrow is +# the variational circuit output state. As the target state changes, the circuit +# learns to produce the new target state! + + +############################################################################## +# Hybrid GPU-QPU optimization +# --------------------------- +# +# As PyTorch natively supports GPU-accelerated classical processing, and Amazon +# Braket provides quantum hardware access in the form of QPUs, we can run the above code +# as a hybrid GPU-QPU optimization with very little modification. +# +# Note that to run the following script, you will need access to Rigetti's QPU. +# To connect to a QPU, we can use Amazon Braket. For a dedicated demonstration +# on using Amazon Braket, see our tutorial on +# `Computing gradients in parallel with Amazon Braket `_. + +import pennylane as qml +import torch +from torch.autograd import Variable + +my_bucket = "amazon-braket-Your-Bucket-Name" # the name of the bucket +my_prefix = "Your-Folder-Name" # the name of the folder in the bucket +s3_folder = (my_bucket, my_prefix) + +device_arn = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" + +qpu = qml.device( + "braket.aws.qubit", + device_arn=device_arn, + wires=32, + s3_destination_folder=s3_folder, +) + +# Note: swap dev to qpu here to use the QPU +# Warning: check the pricing of Aspen-M-3 on Braket to make +# sure you are aware of the costs associated with running the +# optimization below. +@qml.qnode(dev, interface="torch") +def circuit(phi, theta): + qml.RX(theta, wires=0) + qml.RZ(phi, wires=0) + return qml.expval(qml.PauliZ(0)) + + +def cost(phi, theta, step): + target = -(-1) ** (step // 100) + return torch.abs(circuit(phi, theta) - target) ** 2 + + +phi = Variable(torch.tensor(1.0, device="cuda"), requires_grad=True) +theta = Variable(torch.tensor(0.05, device="cuda"), requires_grad=True) +opt = torch.optim.Adam([phi, theta], lr=0.1) + +for i in range(400): + opt.zero_grad() + loss = cost(phi, theta, i) + loss.backward() + opt.step() + +############################################################################## +# When using a classical interface that supports GPUs, the QNode will automatically +# copy any tensor arguments to the CPU, before applying them on the specified quantum +# device. Once done, it will return a tensor containing the QNode result, and +# automatically copy it back to the GPU for any further classical processing. +# +# .. note:: For more details on the PyTorch interface, see :doc:`introduction/interfaces/torch`. +# +# diff --git a/demonstrations_v2/pytorch_noise/metadata.json b/demonstrations_v2/pytorch_noise/metadata.json new file mode 100644 index 0000000000..98429cab62 --- /dev/null +++ b/demonstrations_v2/pytorch_noise/metadata.json @@ -0,0 +1,39 @@ +{ + "title": "PyTorch and noisy devices", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_PyTorch_noisy_devices.png" + } + ], + "seoDescription": "Extend PyTorch with real quantum computing power, by using it to optimize a noisy quantum hardware device.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/4_Simulation_of_noisy_quantum_circuits_on_Amazon_Braket_with_PennyLane/4_Simulation_of_noisy_quantum_circuits_on_Amazon_Braket_with_PennyLane.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/pytorch_noise/requirements.in b/demonstrations_v2/pytorch_noise/requirements.in new file mode 100644 index 0000000000..eb64412b26 --- /dev/null +++ b/demonstrations_v2/pytorch_noise/requirements.in @@ -0,0 +1,2 @@ +pennylane +torch diff --git a/demonstrations_v2/qnspsa/demo.py b/demonstrations_v2/qnspsa/demo.py new file mode 100644 index 0000000000..944d85ff83 --- /dev/null +++ b/demonstrations_v2/qnspsa/demo.py @@ -0,0 +1,1040 @@ +r""" +Quantum natural SPSA optimizer +============================== + +.. meta:: + :property="og:description": Introduction to the Quantum natural SPSA optimizer, which reduces the number of quantum measurements in the optimization. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qnspsa_cover.png + +.. related:: + + tutorial_spsa Simultaneous perturbation stochastic approximation (SPSA) optimizer + +*Author: Yiheng Duan — Posted: 18 July 2022. Last updated: 05 September 2022.* + +In this tutorial, we show how we can implement the +`quantum natural simultaneous perturbation stochastic approximation (QN-SPSA) optimizer +`__ +from Gacon et al. [#Gacon2021]_ using PennyLane. + +Variational quantum algorithms (VQAs) are in close analogy to their counterparts +in classical machine learning. They both build a closed optimization loop and utilize an +optimizer to iterate on the parameters. However, out-of-the-box classical gradient-based +optimizers, such as gradient descent, are often unsatisfying for VQAs, as quantum measurements +are notoriously expensive and gradient measurements for quantum circuits scale poorly +with the system size. + +In [#Gacon2021]_, Gacon et al. propose QN-SPSA, which is tailored for quantum +algorithms. In each optimization step, QN-SPSA executes only 2 quantum circuits to +estimate the gradient, and another 4 for the Fubini-Study metric tensor, independent of the +problem size. This preferred scaling makes it a promising candidate for optimization tasks +for noisy intermediate-scale quantum (NISQ) devices. + +""" + +###################################################################### +# Introduction +# ------------ +# +# In quantum machine learning (QML) and variational quantum algorithms +# (VQA), an optimizer does the following two tasks: +# +# - It estimates the gradient of the cost function or other relevant +# metrics at the current step. +# - Based on the metrics, it decides the parameters for the next iteration +# to reduce the cost. +# +# A simple example of such an optimizer is the vanilla gradient descent +# (GD), whose update rule is written as: +# +# .. math:: +# +# \mathbf{x}^{(t + 1)} = \mathbf{x}^{(t)} - \eta \nabla f(\mathbf{x}^{(t)}) \label{eq:vanilla}\tag{1}, +# +# where :math:`f(\mathbf{x})` is the loss function with input parameter +# :math:`\mathbf{x},` while :math:`\eta` is the learning rate. The superscript +# :math:`t` stands for the :math:`t`-th iteration step in the optimization. +# Here the gradient :math:`\nabla f` is estimated dimension by dimension, +# requiring :math:`O(d)` quantum measurements (:math:`d` being the +# dimension of the parameter space). As quantum measurements are +# expensive, this scaling makes GD impractical for complicated high-dimensional +# circuits. +# +# To address this unsatisfying scaling, the :doc:`simultaneous perturbation +# stochastic approximation (SPSA) optimizer ` +# replaces this dimensionwise gradient estimation with a stochastic one [#SPSA]_. +# In SPSA, a random direction :math:`\mathbf{h} \in \mathcal{U}(\{-1, 1\}^d)` +# in the parameter space is sampled, where :math:`\mathcal{U}(\{-1, 1\}^d)` is a +# :math:`d`-dimensional discrete uniform distribution. The gradient +# component along this sampled direction is then measured with a finite difference +# approach, with a perturbation step size :math:`\epsilon:` +# +# .. math:: +# +# |{\nabla}_{\mathbf{h}}f(\mathbf{x})| \equiv +# \mathbf{h}\cdot {\nabla}f(\mathbf{x}) \simeq \frac{1}{2\epsilon}\big(f(\mathbf{x} + \epsilon \mathbf{h}) - f(\mathbf{x} - \epsilon \mathbf{h})\big)\label{eq:finite_diff}\tag{2}. +# +# A stochastic gradient estimator +# :math:`\widehat{\boldsymbol{\nabla}}f(\mathbf{x}, \mathbf{h})_{SPSA}` is +# then constructed: +# +# .. math:: \widehat{\nabla f}(\mathbf{x}, \mathbf{h})_{SPSA} = | {\nabla}_{\mathbf{h}}f(\mathbf{x})|\mathbf{h}\label{eq:spsaGrad}\tag{3}. +# +# With the estimator, SPSA gives the following update rule: +# +# .. math:: \mathbf{x}^{(t + 1)} = \mathbf{x}^{(t)} - \eta \widehat{\nabla f}(\mathbf{x}^{(t)}, \mathbf{h}^{(t)})_{SPSA} \label{eq:spsa}\tag{4}, +# +# where :math:`\mathbf{h}^{(t)}` is sampled at each step. Although this +# stochastic approach cannot provide a stepwise unbiased gradient +# estimation, SPSA is proved to be especially effective when accumulated +# over multiple optimization steps. +# +# On the other hand, :doc:`quantum natural gradient descent (QNG) +# ` [#Stokes2020]_ +# is a variant of gradient descent. It introduces the Fubini-Study metric tensor +# :math:`\boldsymbol{g}` into the optimization to account for the +# structure of the non-Euclidean parameter space [#FS]_. The +# :math:`d \times d` metric tensor is defined as +# +# .. math:: \boldsymbol{g}_{ij}(\mathbf{x}) = -\frac{1}{2} \frac{\partial}{\partial \mathbf{x}_i} \frac{\partial}{\partial \mathbf{x}_j} F(\mathbf{x}', \mathbf{x})\biggr\rvert_{\mathbf{x}'=\mathbf{x}},\label{eq:fsTensor}\tag{5} +# +# where +# :math:`F(\mathbf{x}', \mathbf{x}) = \bigr\rvert\langle \phi(\mathbf{x}') | \phi(\mathbf{x}) \rangle \bigr\rvert ^ 2,` +# and :math:`\phi(\mathbf{x})` is the parameterized ansatz with input +# :math:`\mathbf{x}.` With the metric tensor, the update rule is rewritten +# as: +# +# .. math:: \mathbf{x}^{(t + 1)} = \mathbf{x}^{(t)} - \eta \boldsymbol{g}^{-1}(\mathbf{x}^{(t)}) \nabla f(\mathbf{x}^{(t)}) \label{eq:qn}\tag{6}. +# +# While the introduction of the metric tensor helps to find better minima +# and allows for faster convergence [#Stokes2020]_ [#Yamamoto2019]_, +# the algorithm is not as scalable due to the number of measurements +# required to estimate :math:`\boldsymbol{g}.` +# +# QN-SPSA manages to combine the merits of QNG and SPSA by estimating +# both the gradient and the metric tensor stochastically. The gradient is +# estimated in the same fashion as the SPSA algorithm, while the +# Fubini-Study metric is computed by a second-order process with another +# two stochastic perturbations: +# +# .. math:: \widehat{\boldsymbol{g}}(\mathbf{x}, \mathbf{h}_1, \mathbf{h}_2)_{SPSA} = \frac{\delta F }{8 \epsilon^2}\Big(\mathbf{h}_1 \mathbf{h}_2^\intercal + \mathbf{h}_2 \mathbf{h}_1^\intercal\Big) \label{eq:fs_qnspsa}\tag{7}, +# +# where +# +# .. math:: \delta F = F(\mathbf{x, \mathbf{x} + \epsilon \mathbf{h}_1} + \epsilon \mathbf{h}_2) - F (\mathbf{x, \mathbf{x} + \epsilon \mathbf{h}_1}) - F(\mathbf{x, \mathbf{x} - \epsilon \mathbf{h}_1} + \epsilon \mathbf{h}_2) + F(\mathbf{x, \mathbf{x} - \epsilon \mathbf{h}_1})\label{eq:deltaf}\tag{8}, +# +# and :math:`\mathbf{h}_1, \mathbf{h}_2 \in \mathcal{U}(\{-1, 1\}^d)` are +# two randomly sampled directions. +# +# With equation (7), QN-SPSA provides the update rule +# +# .. math:: \mathbf{x}^{(t + 1)} = \mathbf{x}^{(t)} - \eta \widehat{\boldsymbol{g}}^{-1}(\mathbf{x}^{(t)}, \mathbf{h}_1^{(t)}, \mathbf{h}_2^{(t)})_{SPSA} \widehat{\nabla f}(\mathbf{x}^{(t)}, \mathbf{h}^{(t)})_{SPSA} \label{eq:qnspsa}\tag{9}. +# +# In each optimization step :math:`t,` one will need to randomly sample 3 +# perturbation directions +# :math:`\mathbf{h}^{(t)}, \mathbf{h}_1^{(t)}, \mathbf{h}_2^{(t)}.` Equation (9) +# is then applied to compute the parameters for the :math:`(t + 1)`-th +# step accordingly. This :math:`O(1)` update rule fits into NISQ devices +# well. +# +# Numerical stability +# ------------------- +# +# The QN-SPSA update rule given in equation (9) is highly stochastic and may +# not behave well numerically. In practice, a few tricks are applied to +# ensure the method’s numerical stability [#Gacon2021]_: +# +# Averaging on the Fubini-Study metric tensor +# A running average is taken on the metric tensor estimated from equation (7) +# at each step :math:`t:` +# +# .. math:: \bar{\boldsymbol{g}}^{(t)}(\mathbf{x}) = \frac{1}{t + 1} \Big(\sum_{i=1}^{t}\widehat{\boldsymbol{g}}(\mathbf{x}, \mathbf{h}_1^{(i)}, \mathbf{h}_2^{(i)})_{SPSA} + \boldsymbol{g}^{(0)}\Big)\label{eq:tensorRunningAvg}\tag{10} , +# +# where the initial guess :math:`\boldsymbol{g}^{(0)}` is set to be the +# identity matrix. +# +# Fubini-Study metric tensor regularization +# To ensure the positive semidefiniteness of the metric tensor near +# a minimum, the running average in equation (10) is regularized: +# +# .. math:: \bar{\boldsymbol{g}}^{(t)}_{reg}(\mathbf{x}) = \sqrt{\bar{\boldsymbol{g}}^{(t)}(\mathbf{x}) \bar{\boldsymbol{g}}^{(t)}(\mathbf{x})} + \beta \mathbb{I}\label{eq:tensor_reg}\tag{11}, +# +# where :math:`\beta` is the regularization coefficient. We can consider :math:`\beta` +# as a hyperparameter and choose a suitable value by trial and error. If +# :math:`\beta` is too small, it cannot protect the positive semidefiniteness +# of :math:`\bar{\boldsymbol{g}}_{reg}.` If :math:`\beta` is too large, it will wipe out +# the information from the Fubini-Study metric tensor, reducing QN-SPSA to the first +# order SPSA. +# +# With equation (11), the QN-SPSA update rule we implement in code reads +# +# .. math:: \mathbf{x}^{(t + 1)} = \mathbf{x}^{(t)} - \eta (\bar{\boldsymbol{g}}^{(t)}_{reg})^{-1}(\mathbf{x}^{(t)}) \widehat{\nabla f}(\mathbf{x}^{(t)}, \mathbf{h}^{(t)})_{SPSA} \label{eq:qnspsa_reg}\tag{12}. +# +# Blocking condition on the parameter update +# A blocking condition is applied onto the parameter update. The optimizer +# only accepts updates that lead to a loss value no larger than the one +# before update, plus a tolerance. Reference [#Spall1997]_ suggests choosing a tolerance +# that is twice the standard deviation of the loss. +# +# Implementation +# -------------- +# +# We are now going to implement the QN-SPSA optimizer with all the tricks for numerical stability +# included, and test it with a toy optimization problem. +# +# Let’s first set up the toy example to optimize. We use a `QAOA max cut +# problem `__ as our testing ground. +# + +# initialize a graph for the max cut problem +import networkx as nx +import pennylane as qml +from pennylane import qaoa + +nodes = n_qubits = 4 +edges = 4 +seed = 121 + +g = nx.gnm_random_graph(nodes, edges, seed=seed) +cost_h, mixer_h = qaoa.maxcut(g) +depth = 2 +# define device to be the PennyLane lightning local simulator +dev = qml.device("lightning.qubit", wires=n_qubits, shots=1000) + + +def qaoa_layer(gamma, alpha): + qaoa.cost_layer(gamma, cost_h) + qaoa.mixer_layer(alpha, mixer_h) + + +def qaoa_circuit(params, n_qubits, depth): + # initialize all qubits into +X eigenstate. + for w in range(n_qubits): + qml.Hadamard(wires=w) + gammas = params[0] + alphas = params[1] + # stack building blocks for depth times. + qml.layer(qaoa_layer, depth, gammas, alphas) + + +# define ansatz and loss function +@qml.qnode(dev) +def cost_function(params): + qaoa_circuit(params, n_qubits, depth) + return qml.expval(cost_h) + + +###################################################################### +# Let’s confirm this circuit works. We generate a set of random parameters +# as input, and check if the QNode ``cost_function`` for the circuit can be +# executed. +# + +from pennylane import numpy as np + +# initialize a random parameter tensor with shape (2, depth), scaled +# to [-pi, pi) +params_curr = 2 * np.pi * (np.random.rand(2, depth) - 0.5) +print("Input parameter shape:", params_curr.shape) +print("Loss value:", cost_function(params_curr)) + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Input parameter shape: (2, 2) +# Loss value: -1.5099999999999998 +# +# With the problem set up, we will for now focus on implementing a +# single-step update of the QN-SPSA. Given the current parameters +# ``params_curr``, we would like to compute the parameters for the next +# step ``params_next``. We first define a few necessary +# hyperparameters and global variables. +# + +import random + +# step index +k = 1 + +# random seed for sampling the perturbation directions +seed = 1 +random.seed(seed) + +# perturbation size for the finite difference calculation +finite_diff_step = 1e-2 + +# regularization coefficient for the metric tensor +regularization = 1e-3 + +# learning rate +lr = 1e-2 + +# initialize the metric tensor to be an identity matrix +params_number = params_curr.size +metric_tensor = np.identity(params_number) + + +###################################################################### +# As both the gradient estimator and the metric tensor estimator involve +# getting random perturbation directions, we first implement a sampling +# function that we call ``get_perturbation_direction``. The function takes +# the input parameter to the circuit ansatz, and returns a direction tensor +# of the same shape. The direction tensor is sampled from a discrete uniform +# distribution :math:`\mathcal{U}(\{-1, 1\}^d)` using ``random.choices`.` +# + + +def get_perturbation_direction(params): + param_number = len(params) if isinstance(params, list) else params.size + sample_list = random.choices([-1, 1], k=param_number) + direction = np.array(sample_list).reshape(params.shape) + return direction + + +print(get_perturbation_direction(params_curr)) + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[-1 1] +# [ 1 -1]] +# +# With this function at our disposal, we implement the gradient estimator ``get_grad`` +# following equation (2): +# + + +def get_grad(params_curr): + grad_dir = get_perturbation_direction(params_curr) + # apply the perturbation + params_forward = params_curr + finite_diff_step * grad_dir + params_backward = params_curr - finite_diff_step * grad_dir + # measured stochastic gradient + loss_forward = cost_function(params_forward) + loss_backward = cost_function(params_backward) + grad = (loss_forward - loss_backward) / (2 * finite_diff_step) * grad_dir + return grad + + +grad = get_grad(params_curr) +print("Estimated SPSA gradient:\n", grad) + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Estimated SPSA gradient: +# [[-3.05 -3.05] +# [ 3.05 3.05]] +# +# To estimate the raw stochastic metric tensor +# :math:`\widehat{\boldsymbol{g}}(\mathbf{x}, \mathbf{h}_1, \mathbf{h}_2)_{SPSA}` +# from equation (7), we will first need to measure the state overlap +# :math:`F(\mathbf{x}_1, \mathbf{x}_2) = \bigr\rvert\langle \phi(\mathbf{x}_1) | \phi(\mathbf{x}_2) \rangle \bigr\rvert ^ 2.` +# We denote the unitary transformation forming the ansatz with :math:`U;` +# that is, +# :math:`\rvert\phi(\mathbf{x})\rangle = U(\mathbf{x}) \rvert0\rangle.` +# Applying the adjoint operation :math:`U^{\dagger}(\mathbf{x}_2)` on to +# the ansatz state :math:`\rvert\phi(\mathbf{x}_1)\rangle` followed with a +# measurement in the computational basis then does the trick. The state +# overlap equals the probability of a :math:`\rvert00...0\rangle` +# measurement outcome. Note that this circuit measuring the state overlap +# doubles the circuit depth of the ansatz, and therefore has longer +# execution time and experiences more accumulated noise from the device. +# The function ``get_state_overlap`` returns a state overlap value between +# 0 (minimum overlap) and 1 (perfect overlap). +# + +from copy import copy + + +def get_operations(qnode, params): + qnode.construct([params], {}) + return qnode.tape.operations + + +def get_overlap_tape(qnode, params1, params2): + op_forward = get_operations(qnode, params1) + op_inv = get_operations(qnode, params2) + + with qml.tape.QuantumTape() as tape: + for op in op_forward: + qml.apply(op) + for op in reversed(op_inv): + qml.adjoint(copy(op)) + qml.probs(wires=qnode.tape.wires.labels) + return tape + + +def get_state_overlap(tape): + return qml.execute([tape], dev, None)[0][0] + + +###################################################################### +# Let’s do a quick sanity check on the state overlap calculation. From the following +# cell, we can see that the overlap of a state with itself is 1, while the number +# for two states from random inputs can vary between 0 and 1. This means ``get_state_overlap`` +# function works! +# + +tape = get_overlap_tape(cost_function, params_curr, params_curr) +print("Perfect overlap: ", get_state_overlap(tape)) + +tape = get_overlap_tape(cost_function, params_curr, 2 * np.pi * (np.random.rand(2, depth) - 0.5)) +print("Random state overlap: ", get_state_overlap(tape)) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Perfect overlap: 1.0 +# Random state overlap: 0.599 +# +# Now that we have confirmed our implementation of the state overlap, we can +# proceed to compute the raw stochastic metric tensor +# :math:`\widehat{\boldsymbol{g}}(\mathbf{x}, \mathbf{h}_1, \mathbf{h}_2)_{SPSA}.` +# With the function ``get_raw_tensor_metric``, we sample two perturbations with +# ``get_perturbation_direction`` independently and estimate the raw metric +# tensor with equations (8) and (7). +# + + +def get_raw_tensor_metric(params_curr): + dir1 = get_perturbation_direction(params_curr) + dir2 = get_perturbation_direction(params_curr) + perturb1 = dir1 * finite_diff_step + perturb2 = dir2 * finite_diff_step + dir_vec1 = dir1.reshape(-1) + dir_vec2 = dir2.reshape(-1) + tapes = [ + get_overlap_tape(cost_function, params_curr, params_curr + perturb1 + perturb2), + get_overlap_tape(cost_function, params_curr, params_curr + perturb1), + get_overlap_tape(cost_function, params_curr, params_curr - perturb1 + perturb2), + get_overlap_tape(cost_function, params_curr, params_curr - perturb1), + ] + + tensor_finite_diff = ( + get_state_overlap(tapes[0]) + - get_state_overlap(tapes[1]) + - get_state_overlap(tapes[2]) + + get_state_overlap(tapes[3]) + ) + + metric_tensor_raw = ( + -(np.tensordot(dir_vec1, dir_vec2, axes=0) + np.tensordot(dir_vec2, dir_vec1, axes=0)) + * tensor_finite_diff + / (8 * finite_diff_step * finite_diff_step) + ) + return metric_tensor_raw + + +metric_tensor_raw = get_raw_tensor_metric(params_curr) +print("Raw estimated metric tensor:\n", metric_tensor_raw) + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Raw estimated metric tensor: +# [[ 2.5 0. -2.5 2.5] +# [ 0. -2.5 0. 0. ] +# [-2.5 0. 2.5 -2.5] +# [ 2.5 0. -2.5 2.5]] +# +# Now, let's apply the running average in equation (10), and the regularization in equation (11): +# + +from scipy.linalg import sqrtm + +metric_tensor_avg = 1 / (k + 1) * metric_tensor_raw + k / (k + 1) * metric_tensor +tensor_reg = np.real(sqrtm(np.matmul(metric_tensor_avg, metric_tensor_avg))) +# update metric tensor +metric_tensor = ((tensor_reg + regularization * np.identity(metric_tensor.shape[0]))) / ( + 1 + regularization +) +# update step index +k += 1 +print("Updated metric tensor after the step:\n", metric_tensor) + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Updated metric tensor after the step: +# [[ 1.74925075 0. -1.24875125 1.24875125] +# [ 0. 0.75024975 0. 0. ] +# [-1.24875125 0. 1.74925075 -1.24875125] +# [ 1.24875125 0. -1.24875125 1.74925075]] +# +# Equation (12) requires computing the inverse of the metric tensor. A +# numerically more stable approach is to solve the equivalent linear +# equation for :math:`\mathbf{x}^{(t + 1)}:` +# +# .. math:: \bar{\boldsymbol{g}}^{(t)}_{reg}(\mathbf{x}^{(t)})\big( \mathbf{x}^{(t)} - \mathbf{x}^{(t + 1)}\big) = \eta \widehat{\nabla f}(\mathbf{x}^{(t)}, \mathbf{h}^{(t)})_{SPSA} \label{eq:lin_solver}\tag{13}. +# + + +def get_next_params(params, gradient): + grad_vec, params_vec = gradient.reshape(-1), params.reshape(-1) + new_params_vec = np.linalg.solve( + metric_tensor, + (-lr * grad_vec + np.matmul(metric_tensor, params_vec)), + ) + return new_params_vec.reshape(params.shape) + + +params_next = get_next_params(params_curr, grad) +print("Next parameters:\n", params_next) + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Next parameters: +# [[-1.03117138 -0.54824992] +# [-2.03318839 0.80331292]] +# +# Now, it is the time to apply the blocking condition. Let’s first try the +# proposal in [#Spall1997]_ to use twice the sample standard deviation of the loss +# at the current step as the tolerance. To collect such a sample, we need to +# repeat the QNode execution for the loss ``cost_function`` for, say, 10 times. +# The straightforward implementation goes as follows: +# + +loss_next = cost_function(params_next) + +repeats = 10 +loss_curr_list = np.zeros(repeats) +for i in range(repeats): + loss_curr_list[i] = cost_function(params_curr) + +tol = 2 * loss_curr_list.std() +loss_curr = loss_curr_list.mean() + +# block updates that lead to significant increase +# of the loss value +if loss_curr + tol < loss_next: + params_next = params_curr +print("Next parameters after blocking:\n", params_next) + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Next parameters after blocking: +# [[-1.03117138 -0.54824992] +# [-2.03318839 0.80331292]] +# +# As quantum measurements are generally expensive, computing the tolerance +# this way adds significant overhead to the QN-SPSA optimizer. To be +# specific, in each step of the optimization, QN-SPSA only requires +# executing 2 circuits for the gradient, and 4 for the metric tensor. Yet +# in the approach above, there are an additional 10 (from the repeat number) + 1 +# circuits required to apply the blocking. +# +# To address this issue, we propose to define the tolerance as the +# standard deviation of the loss values of the past :math:`N` steps +# instead, where :math:`N` is a hyperparameter we choose. +# The intuition here is that when the optimizer is working in a +# fast-descending regime, the blocking condition is unlikely to be +# triggered, as new loss values are often smaller than the previous ones. +# On the other hand, when the optimizer is working in a rather flat energy +# landscape, losses from the past :math:`N` steps could be very similar to +# the current loss value. In this regime, the tolerance defined from both +# approaches should be close. +# +# The implementation of this new tolerance is shown below: +# + +# define number of steps to track +history_length = 5 +# track the past losses in an array +last_n_steps = np.zeros(history_length) + + +# stepwise update +loss_curr = cost_function(params_curr) +loss_next = cost_function(params_next) + +# k has been updated above +ind = (k - 2) % history_length +last_n_steps[ind] = loss_curr + +tol = 2 * last_n_steps.std() if k > history_length else 2 * last_n_steps[: k - 1].std() + +if loss_curr + tol < loss_next: + params_next = params_curr + + +###################################################################### +# The efficacy of this new tolerance definition is confirmed by +# reproducing the experiment on QN-SPSA in Fig. 1(b) from reference [#Gacon2021]_. In the +# following figure, we show the performance of the optimizer with the two +# tolerance definitions for an 11-qubit system. The shaded areas are the +# profiles of 25 trials of the experiment. One can confirm the +# past-:math:`N`-step (:math:`N=5` for the plot) standard deviation works +# just as well. With the new choice of the tolerance, for each step, the +# QN-SPSA will only need to execute 2 (for gradient) + 4 (for metric tensor) + +# 2 (for the current and the next-step loss) = 8 circuits. In practice, we measure +# a 50% reduction in the stepwise optimization time. +# +# The test is done with `Amazon Braket Hybrid Jobs `__, as it is a handy tool to +# scale up experiments systematically. We will show how to do that towards +# the end of the tutorial. +# +# .. figure:: ../_static/demonstration_assets/qnspsa/qnspsa_new_tol.png +# :align: center +# :width: 80% +# +# Similarly, with Hybrid Jobs, we can confirm that blocking is +# necessary for this second-order SPSA optimizer, though it does not make +# much difference for SPSA. Here, the envelope of the QN-SPSA curves +# without blocking is not plotted since it is too noisy to visualize. SPSA +# is implemented by replacing the metric tensor with an identity +# matrix. +# +# .. figure:: ../_static/demonstration_assets/qnspsa/qnspsa_blocking.png +# :align: center +# :width: 80% +# +# Efficiency improvement +# ---------------------- +# +# Let’s do a deep dive on how to further improve the execution efficiency +# of the code. In the code example above, we compute gradient, metric +# tensor, and the loss values through individual calls on the +# ``QNode.__call__()`` function (in this example, ``cost_function()``). In +# a handwavy argument, each ``QNode.__call__()`` does the following two +# things: (1) it constructs a tape with the given parameters, and (2) +# calls ``qml.execute()`` to execute the single tape. +# +# However, in this use case, the better practice is to group the tapes and +# call one ``qml.execute()`` on all the tapes. This practice utilizes the +# batch execution feature from PennyLane, and has a few potential +# advantages. Some simulators provide parallelization support, so that the +# grouped tapes can be executed simutaneously. As an example, utilizing +# the `task +# batching `__ +# feature from the Braket SV1 simulator, we are able to reduce the +# optimization time by 75% for large circuits. For quantum hardware, +# sending tapes in batches could also enable further efficiency +# improvement in circuit compilation. +# +# With this rewriting, the complete optimizer class is provided in the +# following cell. +# + +import random +import pennylane as qml +from pennylane import numpy as np +from scipy.linalg import sqrtm +import warnings + + +class QNSPSA: + """Quantum natural SPSA optimizer. Refer to https://quantum-journal.org/papers/q-2021-10-20-567/ + for a detailed description of the methodology. When disable_metric_tensor + is set to be True, the metric tensor estimation is disabled, and QNSPSA is + reduced to be a SPSA optimizer. + + Args: + stepsize (float): The learn rate. + regularization (float): Regularitzation term to the Fubini-Study + metric tensor for numerical stability. + finite_diff_step (float): step size to compute the finite difference + gradient and the Fubini-Study metric tensor. + resamplings (int): The number of samples to average for each parameter + update. + blocking (boolean): When set to be True, the optimizer only accepts + updates that lead to a loss value no larger than the loss value + before update, plus a tolerance. The tolerance is set with the + parameter history_length. + history_length (int): When blocking is True, the tolerance is set to be + the average of the cost values in the last history_length steps. + disable_metric_tensor (boolean): When set to be True, the optimizer is + reduced to be a (1st-order) SPSA optimizer. + seed (int): Seed for the random sampling. + """ + + def __init__( + self, + stepsize=1e-3, + regularization=1e-3, + finite_diff_step=1e-2, + resamplings=1, + blocking=True, + history_length=5, + disable_metric_tensor=False, + seed=None, + ): + self.stepsize = stepsize + self.reg = regularization + self.finite_diff_step = finite_diff_step + self.metric_tensor = None + self.k = 1 + self.resamplings = resamplings + self.blocking = blocking + self.last_n_steps = np.zeros(history_length) + self.history_length = history_length + self.disable_metric_tensor = disable_metric_tensor + random.seed(seed) + return + + def step(self, cost, params): + """Update trainable arguments with one step of the optimizer. + + .. warning:: + When blocking is set to be True, use step_and_cost instead, as loss + measurements are required for the updates for the case. + + Args: + cost (qml.QNode): the QNode wrapper for the objective function for + optimization + params (np.array): Parameter before update. + + Returns: + np.array: The new variable values after step-wise update. + """ + if self.blocking: + warnings.warn( + "step_and_cost() instead of step() is called when " + "blocking is turned on, as the step-wise loss value " + "is required by the algorithm.", + stacklevel=2, + ) + return self.step_and_cost(cost, params)[0] + + if self.disable_metric_tensor: + return self.__step_core_first_order(cost, params) + return self.__step_core(cost, params) + + def step_and_cost(self, cost, params): + """Update trainable parameters with one step of the optimizer and return + the corresponding objective function value after the step. + + Args: + cost (qml.QNode): the QNode wrapper for the objective function for + optimization + params (np.array): Parameter before update. + + Returns: + tuple[np.array, float]: the updated parameter and the objective + function output before the step. + """ + params_next = ( + self.__step_core_first_order(cost, params) + if self.disable_metric_tensor + else self.__step_core(cost, params) + ) + + if not self.blocking: + loss_curr = cost(params) + return params_next, loss_curr + params_next, loss_curr = self.__apply_blocking(cost, params, params_next) + return params_next, loss_curr + + def __step_core(self, cost, params): + # Core function that returns the next parameters before applying blocking. + grad_avg = np.zeros(params.shape) + tensor_avg = np.zeros((params.size, params.size)) + for i in range(self.resamplings): + grad_tapes, grad_dir = self.__get_spsa_grad_tapes(cost, params) + metric_tapes, tensor_dirs = self.__get_tensor_tapes(cost, params) + raw_results = qml.execute(grad_tapes + metric_tapes, cost.device, None) + grad = self.__post_process_grad(raw_results[:2], grad_dir) + metric_tensor = self.__post_process_tensor(raw_results[2:], tensor_dirs) + grad_avg = grad_avg * i / (i + 1) + grad / (i + 1) + tensor_avg = tensor_avg * i / (i + 1) + metric_tensor / (i + 1) + + self.__update_tensor(tensor_avg) + return self.__get_next_params(params, grad_avg) + + def __step_core_first_order(self, cost, params): + # Reduced core function that returns the next parameters with SPSA rule. + # Blocking not applied. + grad_avg = np.zeros(params.shape) + for i in range(self.resamplings): + grad_tapes, grad_dir = self.__get_spsa_grad_tapes(cost, params) + raw_results = qml.execute(grad_tapes, cost.device, None) + grad = self.__post_process_grad(raw_results, grad_dir) + grad_avg = grad_avg * i / (i + 1) + grad / (i + 1) + return params - self.stepsize * grad_avg + + def __post_process_grad(self, grad_raw_results, grad_dir): + # With the quantum measurement results from the 2 gradient tapes, + # compute the estimated gradient. Returned gradient is a tensor + # of the same shape with the input parameter tensor. + loss_forward, loss_backward = grad_raw_results + grad = (loss_forward - loss_backward) / (2 * self.finite_diff_step) * grad_dir + return grad + + def __post_process_tensor(self, tensor_raw_results, tensor_dirs): + # With the quantum measurement results from the 4 metric tensor tapes, + # compute the estimated raw metric tensor. Returned raw metric tensor + # is a tensor of shape (d x d), d being the dimension of the input parameter + # to the ansatz. + tensor_finite_diff = ( + tensor_raw_results[0][0] + - tensor_raw_results[1][0] + - tensor_raw_results[2][0] + + tensor_raw_results[3][0] + ) + metric_tensor = ( + -( + np.tensordot(tensor_dirs[0], tensor_dirs[1], axes=0) + + np.tensordot(tensor_dirs[1], tensor_dirs[0], axes=0) + ) + * tensor_finite_diff + / (8 * self.finite_diff_step * self.finite_diff_step) + ) + return metric_tensor + + def __get_next_params(self, params, gradient): + grad_vec, params_vec = gradient.reshape(-1), params.reshape(-1) + new_params_vec = np.linalg.solve( + self.metric_tensor, + (-self.stepsize * grad_vec + np.matmul(self.metric_tensor, params_vec)), + ) + return new_params_vec.reshape(params.shape) + + def __get_perturbation_direction(self, params): + param_number = len(params) if isinstance(params, list) else params.size + sample_list = random.choices([-1, 1], k=param_number) + direction = np.array(sample_list).reshape(params.shape) + return direction + + def __get_spsa_grad_tapes(self, cost, params): + # Returns the 2 tapes along with the sampled direction that will be + # used to estimate the gradient per optimization step. The sampled + # direction is of the shape of the input parameter. + direction = self.__get_perturbation_direction(params) + cost.construct([params + self.finite_diff_step * direction], {}) + tape_forward = cost.tape.copy(copy_operations=True) + cost.construct([params - self.finite_diff_step * direction], {}) + tape_backward = cost.tape.copy(copy_operations=True) + return [tape_forward, tape_backward], direction + + def __update_tensor(self, tensor_raw): + tensor_avg = self.__get_tensor_moving_avg(tensor_raw) + tensor_regularized = self.__regularize_tensor(tensor_avg) + self.metric_tensor = tensor_regularized + self.k += 1 + + def __get_tensor_tapes(self, cost, params): + # Returns the 4 tapes along with the 2 sampled directions that will be + # used to estimate the raw metric tensor per optimization step. The sampled + # directions are 1d vectors of the length of the input parameter dimension. + dir1 = self.__get_perturbation_direction(params) + dir2 = self.__get_perturbation_direction(params) + perturb1 = dir1 * self.finite_diff_step + perturb2 = dir2 * self.finite_diff_step + dir_vecs = dir1.reshape(-1), dir2.reshape(-1) + + tapes = [ + self.__get_overlap_tape(cost, params, params + perturb1 + perturb2), + self.__get_overlap_tape(cost, params, params + perturb1), + self.__get_overlap_tape(cost, params, params - perturb1 + perturb2), + self.__get_overlap_tape(cost, params, params - perturb1), + ] + return tapes, dir_vecs + + def __get_overlap_tape(self, cost, params1, params2): + op_forward = self.__get_operations(cost, params1) + op_inv = self.__get_operations(cost, params2) + + with qml.tape.QuantumTape() as tape: + for op in op_forward: + qml.apply(op) + for op in reversed(op_inv): + qml.adjoint(copy(op)) + qml.probs(wires=cost.tape.wires.labels) + return tape + + def __get_operations(self, cost, params): + # Given a QNode, returns the list of operations before the measurement. + cost.construct([params], {}) + return cost.tape.operations + + def __get_tensor_moving_avg(self, metric_tensor): + # For numerical stability: averaging on the Fubini-Study metric tensor. + if self.metric_tensor is None: + self.metric_tensor = np.identity(metric_tensor.shape[0]) + return self.k / (self.k + 1) * self.metric_tensor + 1 / (self.k + 1) * metric_tensor + + def __regularize_tensor(self, metric_tensor): + # For numerical stability: Fubini-Study metric tensor regularization. + tensor_reg = np.real(sqrtm(np.matmul(metric_tensor, metric_tensor))) + return (tensor_reg + self.reg * np.identity(metric_tensor.shape[0])) / (1 + self.reg) + + def __apply_blocking(self, cost, params_curr, params_next): + # For numerical stability: apply the blocking condition on the parameter update. + cost.construct([params_curr], {}) + tape_loss_curr = cost.tape.copy(copy_operations=True) + cost.construct([params_next], {}) + tape_loss_next = cost.tape.copy(copy_operations=True) + + loss_curr, loss_next = qml.execute([tape_loss_curr, tape_loss_next], cost.device, None) + # self.k has been updated earlier. + ind = (self.k - 2) % self.history_length + self.last_n_steps[ind] = loss_curr + + tol = ( + 2 * self.last_n_steps.std() + if self.k > self.history_length + else 2 * self.last_n_steps[: self.k - 1].std() + ) + + if loss_curr + tol < loss_next: + params_next = params_curr + return params_next, loss_curr + + +###################################################################### +# Let’s see how it performs on our QAOA example: +# + +opt = QNSPSA(stepsize=5e-2) +params_init = 2 * np.pi * (np.random.rand(2, depth) - 0.5) +params = params_init +for i in range(300): + params, loss = opt.step_and_cost(cost_function, params) + if i % 40 == 0: + print(f"Step {i}: cost = {loss:.4f}") + + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Step 0: cost = -2.0730 +# Step 40: cost = -2.5390 +# Step 80: cost = -2.7240 +# Step 120: cost = -2.6760 +# Step 160: cost = -2.7540 +# Step 200: cost = -2.7060 +# Step 240: cost = -2.8110 +# Step 280: cost = -2.7930 +# +# The optimizer performs reasonably well: the loss drops over optimization +# steps and converges finally. We then reproduce the benchmarking test +# between the gradient descent, quantum natural gradient descent, SPSA and +# QN-SPSA in Fig. 1(b) of reference [#Gacon2021]_ with the following job. You +# can find a more detailed version of the example in this +# `notebook `__, +# with its dependencies in the `source_scripts` +# `folder `__. +# +# .. note:: +# In order for the remainder of this demo to work, you will need to have done 3 things: +# +# #. Copied the `source_scripts` folder (linked above) to your working directory +# #. Authenticated with AWS locally +# #. Granted yourself the appropriate permissions as described in this `AWS Braket setup document `__ + +from braket.aws import AwsSession, AwsQuantumJob +from braket.jobs.config import InstanceConfig +from braket.jobs.image_uris import Framework, retrieve_image + +region_name = AwsSession().region +image_uri = retrieve_image(Framework.BASE, region_name) + +n_qubits = 11 + +hyperparameters = { + "n_qubits": n_qubits, + "n_layers": 4, + "shots": 8192, + "max_iter": 600, + "learn_rate": 1e-2, + "spsa_repeats": 25, +} + +job_name = f"ref-paper-benchmark-qubit-{n_qubits}-job" +instance_config = InstanceConfig(instanceType="ml.m5.large", volumeSizeInGb=30, instanceCount=1) + +job = AwsQuantumJob.create( + device="local:pennylane/lightning.qubit", + source_module="source_scripts", + entry_point="source_scripts.benchmark_ref_paper_converge_speed", + job_name=job_name, + hyperparameters=hyperparameters, + instance_config=instance_config, + image_uri=image_uri, + wait_until_complete=False, +) + + +###################################################################### +# Visualizing the job results, we get the following plot. The results are +# well aligned with the observations from Gacon et al. [#Gacon2021]_. The +# stepwise optimization times for GD, QNG, SPSA and QN-SPSA are measured to +# be 0.43s, 0.75s, 0.03s and 0.20s. In this example, the average behavior of +# SPSA matches the one from GD. QNG performs the best among the 4 candidates, +# but it requires the most circuit executions and shots per step. In +# particular, for QPUs, this is a severe disadvantage of this method. +# From the more shot-frugal options, QN-SPSA demonstrates the fastest +# convergence and best final accuracy, making it a promising candidate +# for variational algorithms. +# +# .. figure:: ../_static/demonstration_assets/qnspsa/qnspsa_braket.png +# :align: center +# :width: 80% +# +# To sum up, in this tutorial, we showed step-by-step how we can implement +# the QN-SPSA optimizer with PennyLane, along with a few tricks to further +# improve the optimizer’s performance. We also demonstrated how one can +# scale up the benchmarking experiments with Amazon Braket Hybrid Jobs. +# +# References +# ---------- +# +# .. [#Gacon2021] Gacon, J., Zoufal, C., Carleo, G., & Woerner, S. (2021). +# *Simultaneous perturbation stochastic approximation of the quantum +# fisher information*. +# `Quantum, 5, 567 `__. +# +# .. [#SPSA] Simultaneous perturbation stochastic approximation (2022). +# Wikipedia. +# https://en.wikipedia.org/wiki/Simultaneous_perturbation_stochastic_approximation +# +# .. [#Stokes2020] Stokes, J., Izaac, J., Killoran, N., & Carleo, G. (2020). *Quantum +# natural gradient*. `Quantum, 4, 269 `__. +# +# .. [#FS] Fubini–Study metric (2022). Wikipedia. +# https://en.wikipedia.org/wiki/Fubini%E2%80%93Study_metric +# +# .. [#Yamamoto2019] Yamamoto, N. (2019). *On the natural gradient for variational +# quantum eigensolver*. `arXiv preprint arXiv:1909.05074 `__. +# +# .. [#Spall1997] Spall, J. C. (1997). *Accelerated second-order stochastic +# optimization using only function measurements*. `In Proceedings of the +# 36th IEEE Conference on Decision and Control (Vol. 2, pp. 1417-1424). +# IEEE `__. +# diff --git a/demonstrations_v2/qnspsa/metadata.json b/demonstrations_v2/qnspsa/metadata.json new file mode 100644 index 0000000000..a7e70f05ba --- /dev/null +++ b/demonstrations_v2/qnspsa/metadata.json @@ -0,0 +1,92 @@ +{ + "title": "Quantum natural SPSA optimizer", + "authors": [ + { + "username": "yduan" + } + ], + "dateOfPublication": "2022-07-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_natural_SPSA_optimizer.png" + } + ], + "seoDescription": "Introduction to the Quantum natural SPSA optimizer, which reduces the number of quantum measurements in the optimization.", + "doi": "", + "references": [ + { + "id": "Gacon2021", + "type": "article", + "title": "Simultaneous perturbation stochastic approximation of the quantum fisher information", + "authors": "Gacon, J., Zoufal, C., Carleo, G., & Woerner, S.", + "year": "2021", + "journal": "Quantum", + "doi": "10.22331/q-2021-10-20-567", + "url": "https://quantum-journal.org/papers/q-2021-10-20-567/" + }, + { + "id": "SPSA", + "type": "webpage", + "title": "Simultaneous perturbation stochastic approximation", + "year": "2022", + "url": "https://en.wikipedia.org/wiki/Simultaneous_perturbation_stochastic_approximation" + }, + { + "id": "Stokes2020", + "type": "article", + "title": "Quantum natural gradient", + "authors": "Stokes, J., Izaac, J., Killoran, N., & Carleo, G.", + "year": "2020", + "journal": "Quantum", + "doi": "10.22331/q-2020-05-25-269", + "url": "https://quantum-journal.org/papers/q-2020-05-25-269/" + }, + { + "id": "FS", + "type": "webpage", + "title": "Fubini\u2013Study metric", + "year": "2022", + "url": "https://en.wikipedia.org/wiki/Fubini%E2%80%93Study_metric" + }, + { + "id": "Yamamoto2019", + "type": "article", + "title": "On the natural gradient for variational quantum eigensolver", + "authors": "Yamamoto, N.", + "year": "2019", + "journal": "", + "doi": "10.48550/arXiv.1909.05074", + "url": "https://arxiv.org/abs/1909.05074" + }, + { + "id": "Spall1997", + "type": "article", + "title": "Accelerated second-order stochastic optimization using only function measurements", + "authors": "Spall, J. C.", + "year": "1997", + "journal": "In Proceedings of the 36th IEEE Conference on Decision and Control", + "volume": "2", + "pages": "1417-1424", + "doi": "10.1109/CDC.1997.657661", + "url": "https://ieeexplore.ieee.org/document/657661" + } + ], + "basedOnPapers": [ + "10.22331/q-2021-10-20-567" + ], + "referencedByPapers": [], + "relatedContent": [], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/6_QNSPSA_optimizer_with_embedded_simulator/qnspsa_with_embedded_simulator.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/qnspsa/requirements.in b/demonstrations_v2/qnspsa/requirements.in new file mode 100644 index 0000000000..5be38fb006 --- /dev/null +++ b/demonstrations_v2/qnspsa/requirements.in @@ -0,0 +1,4 @@ +amazon-braket-sdk +networkx +pennylane +scipy diff --git a/demonstrations_v2/qonn/demo.py b/demonstrations_v2/qonn/demo.py new file mode 100644 index 0000000000..fffebde61d --- /dev/null +++ b/demonstrations_v2/qonn/demo.py @@ -0,0 +1,474 @@ +""" +.. _quantum_optical_neural_network: + +Optimizing a quantum optical neural network +=========================================== + +.. meta:: + :property="og:description": Optimizing a quantum optical neural network using PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qonn_thumbnail.png + +.. related:: + + quantum_neural_net Function fitting with a photonic quantum neural network + +*Author: Theodor Isacsson — Posted: 05 August 2020. Last updated: 08 March 2022.* + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +This tutorial is based on a paper from `Steinbrecher et al. (2019) +`__ which explores a Quantum Optical Neural +Network (QONN) based on Fock states. Similar to the continuous-variable :doc:`quantum neural network +` (CV QNN) model described by `Killoran et al. (2018) +`__, the QONN +attempts to apply neural networks and deep learning theory to the quantum case, using quantum data +as well as a quantum hardware-based architecture. + +We will focus on constructing a QONN as described in Steinbrecher et al. and training it to work as +a basic CNOT gate using a "dual-rail" state encoding. This tutorial also provides a working example +of how to use third-party optimization libraries with PennyLane; in this case, `NLopt +`__ will be used. + +""" + +###################################################################### +# .. figure:: ../_static/demonstration_assets/qonn/qonn_thumbnail.png +# :width: 100% +# :align: center +# +# A quantum optical neural network using the Reck encoding (green) +# with a Kerr non-linear layer (red) +# + +###################################################################### +# Background +# ---------- +# +# The QONN is an optical architecture consisting of layers of linear +# unitaries, using the encoding described in `Reck et +# al. (1994) `__, and Kerr +# non-linearities applied on all involved optical modes. This setup can be +# constructed using arrays of beamsplitters and programmable phase shifts +# along with some form of Kerr non-linear material. +# +# By constructing a cost function based on the input-output relationship +# of the QONN, using the programmable phase-shift variables as +# optimization parameters, it can be trained to both act as an arbitrary +# quantum gate or to be able to generalize on previously unseen data. This +# is very similar to classical neural networks, and many classical machine +# learning task can in fact also be solved by these types of quantum deep +# neural networks. +# + + +###################################################################### +# Code and simulations +# -------------------- +# +# The first thing we need to do is to import PennyLane, NumPy, as well as an +# optimizer. Here we use a wrapped version of NumPy supplied by PennyLane +# which uses Autograd to wrap essential functions to support automatic +# differentiation. +# +# There are many optimizers to choose from. We could either use an +# optimizer from the ``pennylane.optimize`` module or we could use a +# third-party optimizer. In this case we will use the Nlopt library +# which has several fast implementations of both gradient-free and +# gradient-based optimizers. +# + +import pennylane as qml +from pennylane import numpy as np + +import nlopt + + +###################################################################### +# We create a Strawberry Fields simulator device with as many quantum modes +# (or wires) as we want our quantum-optical neural network to have. Four +# modes are used for this demonstration, due to the use of a dual-rail encoding. The +# cutoff dimension is set to the same value as the number of wires (a +# lower cutoff value will cause loss of information, while a higher value +# might use unnecessary resources without any improvement). + +dev = qml.device("strawberryfields.fock", wires=4, cutoff_dim=4) + +###################################################################### +# +# .. note:: +# +# You will need to have `Strawberry Fields `__ as well as the +# `Strawberry Fields plugin `__ for PennyLane +# installed for this tutorial to work. +# + +###################################################################### +# Creating the QONN +# ~~~~~~~~~~~~~~~~~ +# +# Create a layer function which defines one layer of the QONN, consisting of a linear +# `interferometer +# `__ +# (i.e., an array of beamsplitters and phase shifts) and a non-linear Kerr interaction layer. Both +# the interferometer and the non-linear layer are applied to all modes. The triangular mesh scheme, +# described in `Reck et al. (1994) `__ is chosen here +# due to its use in the paper from Steinbrecher et al., although any other interferometer scheme +# should work equally well. Some might even be slightly faster than the one we use here. +# + +def layer(theta, phi, wires): + M = len(wires) + phi_nonlinear = np.pi / 2 + + qml.Interferometer( + theta, phi, np.zeros(M), wires=wires, mesh="triangular", + ) + + for i in wires: + qml.Kerr(phi_nonlinear, wires=i) + + +###################################################################### +# Next, we define the full QONN by building each layer one-by-one and then +# measuring the mean photon number of each mode. The parameters to be +# optimized are all contained in ``var``, where each element in ``var`` is +# a list of parameters ``theta`` and ``phi`` for a specific layer. +# + +@qml.qnode(dev) +def quantum_neural_net(var, x): + wires = list(range(len(x))) + + # Encode input x into a sequence of quantum fock states + for i in wires: + qml.FockState(x[i], wires=i) + + # "layer" subcircuits + for i, v in enumerate(var): + layer(v[: len(v) // 2], v[len(v) // 2 :], wires) + + return [qml.expval(qml.NumberOperator(w)) for w in wires] + + +###################################################################### +# Defining the cost function +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# A helper function is needed to calculate the normalized square loss of +# two vectors. The square loss function returns a value between 0 and 1, +# where 0 means that ``labels`` and ``predictions`` are equal and 1 means +# that the vectors are fully orthogonal. +# + +def square_loss(labels, predictions): + term = 0 + for l, p in zip(labels, predictions): + lnorm = l / np.linalg.norm(l) + pnorm = p / np.linalg.norm(p) + + term = term + np.abs(np.dot(lnorm, pnorm.T)) ** 2 + + return 1 - term / len(labels) + + +###################################################################### +# Finally, we define the cost function to be used during optimization. It +# collects the outputs from the QONN (``predictions``) for each input +# (``data_inputs``) and then calculates the square loss between the +# predictions and the true outputs (``labels``). +# + +def cost(var, data_input, labels): + predictions = np.array([quantum_neural_net(var, x) for x in data_input]) + sl = square_loss(labels, predictions) + + return sl + + +###################################################################### +# Optimizing for the CNOT gate +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# For this tutorial we will train the network to function as a CNOT gate. +# That is, it should transform the input states in the following way: +# +# .. figure:: ../_static/demonstration_assets/qonn/cnot.png +# :width: 30% +# :align: center +# +# | +# +# We need to choose the inputs ``X`` and the corresponding labels ``Y``. They are +# defined using the dual-rail encoding, meaning that :math:`|0\rangle = [1, 0]` +# (as a vector in the Fock basis of a single mode), and +# :math:`|1\rangle = [0, 1].` So a CNOT transformation of :math:`|1\rangle|0\rangle = |10\rangle = [0, 1, 1, 0]` +# would give :math:`|11\rangle = [0, 1, 0, 1].` +# +# Furthermore, we want to make sure that the gradient isn't calculated with regards +# to the inputs or the labels. We can do this by marking them with `requires_grad=False`. +# + +# Define the CNOT input-output states (dual-rail encoding) and initialize +# them as non-differentiable. + +X = np.array([[1, 0, 1, 0], + [1, 0, 0, 1], + [0, 1, 1, 0], + [0, 1, 0, 1]], requires_grad=False) + +Y = np.array([[1, 0, 1, 0], + [1, 0, 0, 1], + [0, 1, 0, 1], + [0, 1, 1, 0]], requires_grad=False) + + +###################################################################### +# At this stage we could play around with other input-output +# combinations; just keep in mind that the input states should contain the +# same total number of photons as the output, since we want to use +# the dual-rail encoding. Also, since the QONN will +# act upon the states as a unitary operator, there must be a bijection +# between the inputs and the outputs, i.e., two different inputs must have +# two different outputs, and vice versa. +# + + +###################################################################### +# +# .. note:: +# Other example gates we could use include the dual-rail encoded SWAP gate, +# +# .. code:: python +# +# X = np.array([[1, 0, 1, 0], +# [1, 0, 0, 1], +# [0, 1, 1, 0], +# [0, 1, 0, 1]]) +# +# Y = np.array([[1, 0, 1, 0], +# [0, 1, 1, 0], +# [0, 0, 0, 1], +# [0, 1, 0, 1]]) +# +# the single-rail encoded SWAP gate (remember to change the number of +# modes to 2 in the device initialization above), +# +# .. code:: python +# +# X = np.array([[0, 1], [1, 0]]) +# Y = np.array([[1, 0], [0, 1]]) +# +# or the single 6-photon GHZ state (which needs 6 modes, and thus might be +# very heavy on both memory and CPU): +# +# .. code:: python +# +# X = np.array([1, 0, 1, 0, 1, 0]) +# Y = (np.array([1, 0, 1, 0, 1, 0]) + np.array([1, 0, 1, 0, 1, 0])) / 2 +# + + +###################################################################### +# Now, we must set the number of layers to use and then calculate the +# corresponding number of initial parameter values, initializing them with +# a random value between :math:`-2\pi` and :math:`2\pi.` For the CNOT gate two layers is +# enough, although for more complex optimization tasks, many more layers +# might be needed. Generally, the more layers there are, the richer the +# representational capabilities of the neural network, and the better it +# will be at finding a good fit. +# +# The number of variables corresponds to the number of transmittivity +# angles :math:`\theta` and phase angles :math:`\phi,` while the Kerr +# non-linearity is set to a fixed strength. +# + +num_layers = 2 +M = len(X[0]) +num_variables_per_layer = M * (M - 1) + +rng = np.random.default_rng(seed=1234) +var_init = (4 * rng.random(size=(num_layers, num_variables_per_layer), requires_grad=True) - 2) * np.pi +print(var_init) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# [[ 5.99038594 -1.50550479 5.31866903 -2.99466132 -2.27329341 -4.79920711 +# -3.24506046 -2.2803699 5.83179179 -2.97006415 -0.74133893 1.38067731] +# [ 4.56939998 4.5711137 2.1976234 2.00904031 2.96261861 -3.48398028 +# -4.12093786 4.65477183 -5.52746064 2.30830291 2.15184041 1.3950931 ]] + +###################################################################### +# The NLopt library is used for optimizing the QONN. For using +# gradient-based methods the cost function must be wrapped so that NLopt +# can access its gradients. This is done by calculating the gradient using +# autograd and then saving it in the ``grad[:]`` variable inside of the +# optimization function. The variables are flattened to conform to the +# requirements of both NLopt and the above-defined cost function. +# + +cost_grad = qml.grad(cost) + +print_every = 1 + +# Wrap the cost so that NLopt can use it for gradient-based optimizations +evals = 0 +def cost_wrapper(var, grad=[]): + global evals + evals += 1 + + if grad.size > 0: + # Get the gradient for `var` by first "unflattening" it + var = var.reshape((num_layers, num_variables_per_layer)) + var = np.array(var, requires_grad=True) + var_grad = cost_grad(var, X, Y) + grad[:] = var_grad.flatten() + cost_val = cost(var.reshape((num_layers, num_variables_per_layer)), X, Y) + + if evals % print_every == 0: + print(f"Iter: {evals:4d} Cost: {cost_val:.4e}") + + return float(cost_val) + + +# Choose an algorithm +opt_algorithm = nlopt.LD_LBFGS # Gradient-based +# opt_algorithm = nlopt.LN_BOBYQA # Gradient-free + +opt = nlopt.opt(opt_algorithm, num_layers*num_variables_per_layer) + +opt.set_min_objective(cost_wrapper) + +opt.set_lower_bounds(-2*np.pi * np.ones(num_layers*num_variables_per_layer)) +opt.set_upper_bounds(2*np.pi * np.ones(num_layers*num_variables_per_layer)) + +var = opt.optimize(var_init.flatten()) +var = var.reshape(var_init.shape) + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Iter: 1 Cost: 5.2344e-01 +# Iter: 2 Cost: 4.6269e-01 +# Iter: 3 Cost: 3.3963e-01 +# Iter: 4 Cost: 3.0214e-01 +# Iter: 5 Cost: 2.7352e-01 +# Iter: 6 Cost: 1.9481e-01 +# Iter: 7 Cost: 2.6425e-01 +# Iter: 8 Cost: 8.8005e-02 +# Iter: 9 Cost: 1.3520e-01 +# Iter: 10 Cost: 6.9529e-02 +# Iter: 11 Cost: 2.2332e-02 +# Iter: 12 Cost: 5.4051e-03 +# Iter: 13 Cost: 1.7288e-03 +# Iter: 14 Cost: 5.7472e-04 +# Iter: 15 Cost: 2.1946e-04 +# Iter: 16 Cost: 8.5438e-05 +# Iter: 17 Cost: 3.9276e-05 +# Iter: 18 Cost: 1.8697e-05 +# Iter: 19 Cost: 8.7004e-06 +# Iter: 20 Cost: 3.7786e-06 +# Iter: 21 Cost: 1.5192e-06 +# Iter: 22 Cost: 7.0577e-07 +# Iter: 23 Cost: 3.1065e-07 +# Iter: 24 Cost: 1.4212e-07 +# Iter: 25 Cost: 6.3160e-08 +# Iter: 26 Cost: 2.5086e-08 +# Iter: 27 Cost: 1.2039e-08 +# Iter: 28 Cost: 4.6965e-09 +# Iter: 29 Cost: 1.6962e-09 +# Iter: 30 Cost: 6.1205e-10 +# Iter: 31 Cost: 2.4764e-10 +# Iter: 32 Cost: 1.2485e-10 +# Iter: 33 Cost: 8.3915e-11 +# Iter: 34 Cost: 6.1669e-11 +# Iter: 35 Cost: 5.1633e-11 +# Iter: 36 Cost: 4.8152e-11 +# Iter: 37 Cost: 3.9745e-11 +# Iter: 38 Cost: 3.2651e-11 +# Iter: 39 Cost: 1.9693e-11 + + +###################################################################### +# .. note:: +# +# It’s also possible to use any of PennyLane’s built-in +# gradient-based optimizers: +# +# .. code:: python +# +# from pennylane.optimize import AdamOptimizer +# +# opt = AdamOptimizer(0.01, beta1=0.9, beta2=0.999) +# +# var = var_init +# for it in range(200): +# var = opt.step(lambda v: cost(v, X, Y), var) +# +# if (it+1) % 20 == 0: +# print(f"Iter: {it+1:5d} | Cost: {cost(var, X, Y):0.7f} ") +# + + +###################################################################### +# Finally, we print the results. +# + +print(f"The optimized parameters (layers, parameters):\n {var}\n") + +Y_pred = np.array([quantum_neural_net(var, x) for x in X]) +for i, x in enumerate(X): + print(f"{x} --> {Y_pred[i].round(2)}, should be {Y[i]}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# The optimized parameters (layers, parameters): +# [[ 5.59646472 -0.76686269 6.28318531 -3.2286718 -1.61696115 -4.79794955 +# -3.44889052 -2.68088816 5.65397191 -2.81207159 -0.59737994 1.39431044] +# [ 4.71056381 5.24800052 3.14152765 3.13959016 2.78451845 -3.92895253 +# -4.38654718 4.65891554 -5.34964081 2.607051 2.40425267 1.39415476]] +# +# [1 0 1 0] --> [1. 0. 1. 0.], should be [1 0 1 0] +# [1 0 0 1] --> [1. 0. 0. 1.], should be [1 0 0 1] +# [0 1 1 0] --> [0. 1. 0. 1.], should be [0 1 0 1] +# [0 1 0 1] --> [0. 1. 1. 0.], should be [0 1 1 0] + +############################################################################## +# We can also print the circuit to see how the final network looks. + +print(qml.draw(quantum_neural_net)(var_init, X[0])) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0: ──|1⟩──────────────────────────────────╭BS(5.32,5.83)────R(0.00)──────────Kerr(1.57)──── +# 1: ──|0⟩─────────────────╭BS(-1.51,-2.28)─╰BS(5.32,5.83)───╭BS(-2.27,-0.74)──R(0.00)─────── +# 2: ──|1⟩─╭BS(5.99,-3.25)─╰BS(-1.51,-2.28)─╭BS(-2.99,-2.97)─╰BS(-2.27,-0.74)─╭BS(-4.80,1.38) +# 3: ──|0⟩─╰BS(5.99,-3.25)──────────────────╰BS(-2.99,-2.97)──────────────────╰BS(-4.80,1.38) +# +# ─────────────────────────────────────────────────────────╭BS(2.20,-5.53)──R(0.00)────── +# ───Kerr(1.57)─────────────────────────────╭BS(4.57,4.65)─╰BS(2.20,-5.53)─╭BS(2.96,2.15) +# ───R(0.00)─────Kerr(1.57)─╭BS(4.57,-4.12)─╰BS(4.57,4.65)─╭BS(2.01,2.31)──╰BS(2.96,2.15) +# ───R(0.00)─────Kerr(1.57)─╰BS(4.57,-4.12)────────────────╰BS(2.01,2.31)──────────────── +# +# ───Kerr(1.57)─────────────────────────────┤ +# ───R(0.00)─────────Kerr(1.57)─────────────┤ +# ──╭BS(-3.48,1.40)──R(0.00)─────Kerr(1.57)─┤ +# ──╰BS(-3.48,1.40)──R(0.00)─────Kerr(1.57)─┤ +# +# diff --git a/demonstrations_v2/qonn/metadata.json b/demonstrations_v2/qonn/metadata.json new file mode 100644 index 0000000000..c0a8c149e6 --- /dev/null +++ b/demonstrations_v2/qonn/metadata.json @@ -0,0 +1,32 @@ +{ + "title": "Optimizing a quantum optical neural network", + "authors": [ + { + "username": "tisacsson" + } + ], + "dateOfPublication": "2020-08-05T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimizing_quantum_optical_neural_network.png" + } + ], + "seoDescription": "Optimizing a quantum optical neural network using PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "quantum_neural_net", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/qonn/requirements.in b/demonstrations_v2/qonn/requirements.in new file mode 100644 index 0000000000..56e807ad3a --- /dev/null +++ b/demonstrations_v2/qonn/requirements.in @@ -0,0 +1,2 @@ +nlopt +pennylane diff --git a/demonstrations_v2/qrack/demo.py b/demonstrations_v2/qrack/demo.py new file mode 100644 index 0000000000..0f489b75ee --- /dev/null +++ b/demonstrations_v2/qrack/demo.py @@ -0,0 +1,457 @@ +r""" +QJIT compilation with Qrack and Catalyst +======================================== + +`Qrack `__ is a GPU-accelerated quantum +computer simulator with many novel optimizations, and `PyQrack +`__ is its Python wrapper, written in +pure (``ctypes``) Python language standard. Founded in 2017 by Dan Strano and +Benn Bollay, Qrack's vision was always to provide the best possible (classical) +quantum computer emulator, targeting the use case of running +industrially-relevant quantum workloads without recourse to genuine quantum +computer hardware. + +In this tutorial you will learn how to use Qrack with +PennyLane and quantum just-in-time (QJIT) compilation via +`Catalyst +`__ --- enabling +your hybrid quantum-classical Python program to be compiled and executed +with Qrack with significant performance boosts. + +You'll learn certain suggested cases of use where Qrack might particularly excel +at delivering lightning-fast performance or minimizing required memory resources +— for example, special cases of the quantum or discrete :doc:`Fourier transform +`, circuits with predominantly :doc:`Clifford +` or classical preambles, +circuits with :doc:`mid-circuit measurement +`, and high-width circuits with +low-complexity representations in terms of a QBDD (quantum binary decision +diagram). However, Qrack is a general-purpose simulator, so you might +employ it for all their applications and still see parity with or improvement +over available device back ends. + +.. figure:: ../_static/demonstration_assets/qrack/qrack_catalyst_integration_shelf.png + :align: center + :width: 90% + :target: javascript:void(0); + +How Qrack works +--------------- + +When developing `Qrack `__, we wanted to +provide the emulator as open source, free of charge, agnostic to any specific +GPU or hardware accelerator provider, backwards compatible to serve those with +very limited classical computer hardware resources, but (nonetheless) capable of +scaling to supercomputer systems, as secure and free of external dependencies as +possible, and under the reasonably permissive LGPL license, with bindings and +wrappers for third-party libraries provided under even more permissive licenses +like MIT and Apache 2.0. Our hope was that the global floor of minimal access to +cost-competitive quantum workload throughput would never be lower than the +capabilities of Qrack. + +When simulating quantum subroutines of varying qubit widths, Qrack will +transparently, automatically, and dynamically transition between GPU-based and +CPU-based simulation techniques for maximal execution speed, to respond to +situations when qubit registers might be too narrow to benefit from the large +parallel processing element count of a GPU (up to maybe roughly 20 qubits, +depending upon the classical hardware platform). Qrack also offers so-called +hybrid stabilizer simulation (with fallback to universal simulation) and +near-Clifford simulation with a greatly reduced memory footprint on Clifford +gate sets with the inclusion of the `RZ` variational Pauli Z-axis rotation gate. +(For more information, see the `QCE'23 report `__ [#QCEReport]_ by the Qrack and `Unitary Fund `__ teams.) + +Particularly for systems that don't rely on GPU acceleration, Qrack offers a +quantum binary decision diagram (QBDD) simulation algorithm option that might +significantly reduce the memory footprint or execution complexity for circuits +with low entanglement, as judged by the complexity of a QBDD to represent the +state. (Qrack's implementation of QBDD is entirely original source code, but it +is based on reports like `this one `__ +[#Wille]_.) Qrack also offers approximation options aimed at trading off minimal +fidelity reduction for maximum reduction in simulation complexity (as opposed to +models of physical noise), including methods based on the Schmidt decomposition +rounding parameter (SDRP) [#QCEReport]_ and the near-Clifford rounding parameter +(NCRP). + +The Qrack simulator doesn't fit neatly into a single canonical category of +quantum computer simulation algorithm: it optionally and by default leverages +elements of state vector simulation, tensor network simulation, stabilizer and +near-Clifford simulation, and QBDD simulation, often all at once, while it +introduces some novel algorithmic tricks for the Schmidt decomposition of +state vectors in a manner similar to matrix product state (MPS) simulation. + +Demonstrating Qrack with the quantum Fourier transform +------------------------------------------------------ + +The :doc:`quantum Fourier transform (QFT) ` is a building-block +subroutine of many other quantum algorithms. Qrack exhibits unique capability +for many cases of the QFT algorithm, and its worst-case performance is +competitive with other popular quantum computer simulators [#QCEReport]_. In this +section, you'll be presented with examples of Qrack's uniquely optimal +performance on the QFT. + +In the case of a *trivial* computational basis eigenstate input, Qrack can +simulate basically any QFT width. Below, we pick a random eigenstate +initialization and perform the QFT across a width of 60 qubits, with Catalyst's `qjit `__. +""" + +import pennylane as qml +from pennylane import numpy as np +from catalyst import qjit + +import matplotlib.pyplot as plt + +import random + +qubits = 60 +dev = qml.device("qrack.simulator", qubits, shots=8) + +@qjit +@qml.qnode(dev) +def circuit(): + for i in range(qubits): + if random.uniform(0, 1) < 0.5: + qml.X(wires=[i]) + qml.QFT(wires=range(qubits)) + return qml.sample(wires=range(qubits)) + +def counts_from_samples(samples): + counts = {} + for sample in samples: + s = 0 + + for bit in sample: + s = (s << 1) | bit + + s = str(s) + + if s in counts: + counts[s] = counts[s] + 1 + else: + counts[s] = 1 + + return counts + +counts = counts_from_samples(circuit()) + +plt.bar(counts.keys(), counts.values()) +plt.title(f"QFT on {qubits} Qubits with Random Eigenstate Init. (8 samples)") +plt.xlabel("|x⟩") +plt.ylabel("counts") +plt.show() + +############################################################################## +# .. figure:: ../_static/demonstration_assets/qrack/fig1.png +# :align: center +# :width: 90% +# :target: javascript:void(0); + +############################################################################## +# In this image we have represented only 8 measurement samples so we can visualize the result more easily. +# +# This becomes harder if we request a non-trivial initialization. In general, Qrack will use +# Schmidt decomposition techniques to try to break up circuits into separable subsystems of +# qubits to simulate semi-independently, combining them just-in-time (JIT) with Kronecker +# products when they need to interact, according the used circuit definition. +# +# The circuit becomes much harder for Qrack if we randomly initialize the input qubits +# with Haar-random `U3 gates `__, which you can see below, +# but the performance is still significantly better than the worst case (of +# `GHZ state `__ initialization). + +qubits = 12 +dev = qml.device("qrack.simulator", qubits, shots=8) + +@qjit +@qml.qnode(dev) +def circuit(): + for i in range(qubits): + th = random.uniform(0, np.pi) + ph = random.uniform(0, np.pi) + dl = random.uniform(0, np.pi) + qml.U3(th, ph, dl, wires=[i]) + qml.QFT(wires=range(qubits)) + return qml.sample(wires=range(qubits)) + +counts = counts_from_samples(circuit()) + +plt.bar(counts.keys(), counts.values()) +plt.title(f"QFT on {qubits} Qubits with Random U3 Init. (8 samples)") +plt.xlabel("|x⟩") +plt.ylabel("counts") +plt.show() + +############################################################################## +# .. figure:: ../_static/demonstration_assets/qrack/fig2.png +# :align: center +# :width: 90% +# :target: javascript:void(0); + +############################################################################## +# Alternate simulation algorithms (QBDD and near-Clifford) +# -------------------------------------------------------- +# By default, Qrack relies on a combination of state vector simulation, "hybrid" stabilizer and +# near-Clifford simulation, and Schmidt decomposition optimization techniques. Alternatively, we could use +# pure stabilizer simulation or QBDD simulation if the circuit is at all amenable to optimization in this way. +# +# To demonstrate this, we prepare a 60-qubit GHZ state, which would commonly be intractable in the case of state vector simulation. + +qubits = 60 +dev = qml.device( + "qrack.simulator", + qubits, + shots=8, + isBinaryDecisionTree=False, + isStabilizerHybrid=True, + isSchmidtDecompose=False, +) + +@qjit +@qml.qnode(dev) +def circuit(): + qml.Hadamard(0) + for i in range(1, qubits): + qml.CNOT(wires=[i - 1, i]) + return qml.sample(wires=range(qubits)) + +counts = counts_from_samples(circuit()) + +plt.bar(counts.keys(), counts.values()) +plt.title(f"{qubits}-Qubit GHZ preparation (8 samples)") +plt.xlabel("|x⟩") +plt.ylabel("counts") +plt.show() + +############################################################################## +# .. figure:: ../_static/demonstration_assets/qrack/fig3.png +# :align: center +# :width: 90% +# :target: javascript:void(0); + +############################################################################## +# As you can see, Qrack was able to construct the 60-qubit GHZ state (without +# exceeding memory limitations), and the probability is peaked at bit strings of all 0 and all 1. +# +# It's trivial for Qrack to perform large GHZ state preparations with "hybrid" stabilizer +# or near-Clifford simulation if Schmidt decomposition is deactivated. +# QBDD cannot be accelerated by GPU, so its application might be limited, but it is parallel +# over CPU processing elements, hence it might be particularly well-suited for systems with no GPU at all. +# Qrack's default simulation methods will likely still outperform QBDD on BQP-complete problems +# like random circuit sampling or quantum volume certification. +# + +qubits = 24 +dev = qml.device( + "qrack.simulator", qubits, shots=8, isBinaryDecisionTree=True, isStabilizerHybrid=False +) + +@qjit +@qml.qnode(dev) +def circuit(): + qml.Hadamard(0) + for i in range(1, qubits): + qml.CNOT(wires=[i - 1, i]) + return qml.sample(wires=range(qubits)) + +counts = counts_from_samples(circuit()) + +plt.bar(counts.keys(), counts.values()) +plt.title(f"{qubits}-Qubit GHZ preparation (8 samples)") +plt.xlabel("|x⟩") +plt.ylabel("counts") +plt.show() + +############################################################################## +# .. figure:: ../_static/demonstration_assets/qrack/fig4.png +# :align: center +# :width: 90% +# :target: javascript:void(0); + +############################################################################## +# If your gate set is restricted to Clifford with general :class:`~pennylane.RZ` gates +# (being mindful of the fact that compilers like Catalyst might optimize such a gate set basis into different gates), +# the time complexity for measurement samples becomes doubly exponential with near-Clifford simulation, +# but the space complexity is almost exactly that of stabilizer simulation for the logical qubits plus +# an ancillary qubit per (non-optimized) :class:`~.pennylane.RZ` gate, scaling like the square of the +# sum of the count of the logical and ancillary qubits put together. +# +# Comparing performance +# --------------------- +# We've already seen that the Qrack device back end can do some tasks that most other simulators, or +# basically any other simulator, simply can't do, like 60-qubit-wide special cases of the QFT or GHZ state +# preparation with a Clifford or universal (QBDD) simulation algorithm, for example. However, +# in the worst case for circuit complexity, Qrack will tend perform similarly to state vector simulation. +# +# How does the performance of Qrack compare with other simulators' on a non-trivial problem, +# like the U3 initialization we used above for the `QFT algorithm <#demonstrating-qrack-with-the-quantum-fourier-transform>`_? + +import time + +def bench(n, results): + for device in ["qrack.simulator", "lightning.qubit"]: + dev = qml.device(device, n, shots=1) + + @qjit + @qml.qnode(dev) + def circuit(): + for i in range(n): + th = random.uniform(0, np.pi) + ph = random.uniform(0, np.pi) + dl = random.uniform(0, np.pi) + qml.U3(th, ph, dl, wires=[i]) + qml.QFT(wires=range(n)) + return qml.sample(wires=range(n)) + + start_ns = time.perf_counter_ns() + circuit() + results[ + f"Qrack ({n} qb)" if device == "qrack.simulator" else f"Lightning ({n} qb)" + ] = time.perf_counter_ns() - start_ns + + return results + +results = {} +results = bench(6, results) +results = bench(12, results) +results = bench(18, results) + +bar_colors = ["purple", "yellow", "purple", "yellow"] +plt.bar(results.keys(), results.values(), color=bar_colors) +plt.title("Performance comparison, QFT with U3 initialization (1 sample apiece)") +plt.xlabel("|x⟩") +plt.ylabel("Nanoseconds") +plt.show() + +############################################################################## +# .. figure:: ../_static/demonstration_assets/qrack/fig5.png +# :align: center +# :width: 90% +# :target: javascript:void(0); + +############################################################################## +# Benchmarks will differ somewhat when running this code on your local machine, for example, +# but we tend to see that Qrack manages to demonstrate good performance compared to the +# `Lightning simulators `__ on this task case. +# (Note that this initialization case isn't specifically the hardest case of the QFT for Qrack; +# that's probably rather a GHZ state input.) +# +# Similarly, we can use quantum just-in-time (QJIT) compilation from PennyLane's +# `Catalyst `__, for both Qrack and +# `Lightning `__. How does Qrack with QJIT compare to Qrack without it? + +def bench(n, results): + dev = qml.device("qrack.simulator", n, shots=1) + + @qjit + @qml.qnode(dev) + def circuit(): + for i in range(n): + th = random.uniform(0, np.pi) + ph = random.uniform(0, np.pi) + dl = random.uniform(0, np.pi) + qml.U3(th, ph, dl, wires=[i]) + qml.QFT(wires=range(n)) + return qml.sample(wires=range(n)) + + start_ns = time.perf_counter_ns() + circuit() + results[f"QJIT Qrack ({n} qb)"] = time.perf_counter_ns() - start_ns + + @qml.qnode(dev) + def circuit(): + for i in range(n): + th = random.uniform(0, np.pi) + ph = random.uniform(0, np.pi) + dl = random.uniform(0, np.pi) + qml.U3(th, ph, dl, wires=[i]) + qml.QFT(wires=range(n)) + return qml.sample(wires=range(n)) + + start_ns = time.perf_counter_ns() + circuit() + results[f"PyQrack ({n} qb)"] = time.perf_counter_ns() - start_ns + + return results + +# Make sure OpenCL has been initalized in PyQrack: +bench(6, results) + +results = {} +results = bench(6, results) +results = bench(12, results) +results = bench(18, results) + +bar_colors = ["purple", "yellow", "purple", "yellow"] +plt.bar(results.keys(), results.values(), color=bar_colors) +plt.title("Performance comparison, QFT with U3 initialization (1 sample apiece)") +plt.xlabel("|x⟩") +plt.ylabel("Nanoseconds") +plt.show() + +############################################################################## +# .. figure:: ../_static/demonstration_assets/qrack/fig6.png +# :align: center +# :width: 90% +# :target: javascript:void(0); + +############################################################################## +# Again, your mileage may vary somewhat, depending on your local system, but Qrack tends +# to be significantly faster with Catalyst's QJIT than without! +# +# As a basic test of validity, if we compare the inner product between both simulator +# state vector outputs on some QFT case, do they agree? + +def validate(n): + results = [] + for device in ["qrack.simulator", "lightning.qubit"]: + dev = qml.device(device, n, shots=None) + + @qjit + @qml.qnode(dev) + def circuit(): + qml.Hadamard(0) + for i in range(1, n): + qml.CNOT(wires=[i - 1, i]) + qml.QFT(wires=range(n)) + return qml.state() + + start_ns = time.perf_counter_ns() + results.append(circuit()) + + return np.abs(sum([np.conj(x) * y for x, y in zip(results[0], results[1])])) + +print("Qrack cross entropy with Lightning:", validate(12), "out of 1.0") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# .. code-block:: none +# +# Qrack cross entropy with Lightning: 0.9999997797266185 out of 1.0 + +############################################################################## +# Conclusion +# ---------- +# In this tutorial, we've demonstrated the basics of using the `Qrack `__ +# simulator back end and showed examples of special cases on which Qrack's novel optimizations can lead +# to huge increases in performance or maximum achievable qubit widths. Remember the Qrack device back +# end for PennyLane if you'd like to leverage GPU acceleration but don't want to complicate your +# choice of devices or device initialization, to handle a mixture of wide and narrow qubit registers in your subroutines. + +############################################################################## +# +# References +# ---------- +# +# .. [#QCEReport] +# +# Daniel Strano, Benn Bollay, Aryan Blaauw, Nathan Shammah, William J. Zeng, Andrea Mari +# "Exact and approximate simulation of large quantum circuits on a single GPU" +# `arXiv:2304.14969 `__, 2023. +# +# .. [#Wille] +# +# Robert Wille, Stefan Hillmich, Lukas Burgholzer +# "Decision Diagrams for Quantum Computing" +# `arXiv:2302.04687 `__, 2023. + +############################################################################## diff --git a/demonstrations_v2/qrack/metadata.json b/demonstrations_v2/qrack/metadata.json new file mode 100644 index 0000000000..a2e87f9832 --- /dev/null +++ b/demonstrations_v2/qrack/metadata.json @@ -0,0 +1,69 @@ +{ + "title": "QJIT compilation with Qrack and Catalyst", + "authors": [ + { + "username": "dstrano" + } + ], + "dateOfPublication": "2024-07-10T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qrack_catalyst_integration.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qrack_catalyst_integration.png" + } + ], + "seoDescription": "Quantum JIT compilation (QJIT) using the Qrack device for PennyLane and Catalyst, with GPU-acceleration and novel optimization.", + "doi": "", + "references": [ + { + "id": "QECReport", + "type": "preprint", + "title": "Exact and approximate simulation of large quantum circuits on a single GPU", + "authors": "Daniel Strano, Benn Bollay, Aryan Blaauw, Nathan Shammah, William J. Zeng, Andrea Mari", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2304.14969", + "url": "https://arxiv.org/abs/2304.14969" + }, + { + "id": "Wille", + "type": "preprint", + "title": "Decision Diagrams for Quantum Computing", + "authors": "Robert Wille, Stefan Hillmich, Lukas Burgholzer", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2302.04687", + "url": "https://arxiv.org/abs/2302.04687" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_clifford_circuit_simulations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mcm_introduction", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/qrack/requirements.in b/demonstrations_v2/qrack/requirements.in new file mode 100644 index 0000000000..d08f6bd335 --- /dev/null +++ b/demonstrations_v2/qrack/requirements.in @@ -0,0 +1,3 @@ +pennylane-catalyst +matplotlib +pennylane diff --git a/demonstrations_v2/qsim_beyond_classical/demo.py b/demonstrations_v2/qsim_beyond_classical/demo.py new file mode 100644 index 0000000000..ea93f3fb99 --- /dev/null +++ b/demonstrations_v2/qsim_beyond_classical/demo.py @@ -0,0 +1,583 @@ +""" + +.. _qsim_beyond_classical: + +Beyond classical computing with qsim +==================================== + +.. meta:: + :property="og:description": Use Google's qsim simulator to explore the barriers between quantum and classical computing, and recreate their benchmarks and circuits. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sycamore.png + +.. related:: + + tutorial_quantum_metrology Variationally optimizing measurement protocols + tutorial_noisy_circuit_optimization Optimizing noisy circuits with Cirq + quantum_volume Quantum volume + +*Author: Theodor Isacsson — Posted: 30 November 2020. Last updated: 10 September 2021.* + +.. figure:: ../_static/demonstration_assets/qsim_beyond_classical/qc.png + :align: right + :height: 300pt + +In the paper `Quantum supremacy using a programmable superconducting +processor `__, the +Google AI Quantum team and collaborators showed that the Sycamore quantum processor could +complete a task that would take a classical computer potentially thousands +of years. They faced their quantum chip off against JEWEL—one of the +world's most powerful supercomputers—using a classical statevector simulator called +`qsim `__. The main idea behind this +showdown was to prove that a quantum device could solve a specific task +that no classical method could do in a reasonable amount of time. + +For the face-off, a pseudo-random quantum circuit was constructed by alternating +single-qubit and two-qubit gates in a specific, semi-random pattern. This +procedure gives a random unitary transformation which is +compatible with the Sycamore hardware. The circuit output is +measured many times, producing a set of sampled bitstrings. +The more qubits there are, and the deeper the circuit is, the more difficult +it becomes to simulate and sample this +bitstring distribution classically. By comparing run-times for the +classical simulations and the Sycamore chip +on smaller circuits, and then extrapolating classical run-times for larger +circuits, the team concluded that simulating larger circuits on Sycamore +was intractable classically—i.e., the Sycamore chips had demonstrated +what is called "quantum supremacy". + +In this demonstration, we will walk you through how their random quantum +circuits were constructed, how the performance was measured via +cross-entropy benchmarks, and provide reusable examples of their classical +simulations. + +.. note:: + + We will be using PennyLane along with the aformentioned + ``qsim`` simulator running via our `PennyLane-Cirq plugin + `__. To use the ``qsim`` + device you also need to install ``qsimcirq``, which is the Python module + interfacing the ``qsim`` simulator with Cirq. +""" + + +###################################################################### +# Preparations +# ------------ +# +# As always, we begin by importing the necessary modules. We will use +# PennyLane, along with some PennyLane-Cirq specific operations, as well as +# Cirq. +# + +import pennylane as qml +from pennylane_cirq import ops + +import cirq +import numpy as np + + +###################################################################### +# To start, we need to define the qubit grid that we will use for mimicking +# Google's Sycamore chip, although we will only use 12 qubits instead of +# the 54 that the actual chip has. This is so that you can run +# this demo without having access to a supercomputer! +# +# We define the 12 qubits in a rectangular grid, setting the coordinates for +# each qubit following the paper's suplementary dataset [#Martinis2020]_. We also create +# a mapping between the wire number and the Cirq qubit to more easily reference +# specific qubits later. Feel free to play around with different grids and +# number of qubits. Just keep in mind that the grid needs to stay +# connected. You could, for example, remove the final row (last four qubits +# in the list) to simulate an 8-qubit system. +# + +qubits = sorted([ + cirq.GridQubit(3, 3), + cirq.GridQubit(3, 4), + cirq.GridQubit(3, 5), + cirq.GridQubit(3, 6), + cirq.GridQubit(4, 3), + cirq.GridQubit(4, 4), + cirq.GridQubit(4, 5), + cirq.GridQubit(4, 6), + cirq.GridQubit(5, 3), + cirq.GridQubit(5, 4), + cirq.GridQubit(5, 5), + cirq.GridQubit(5, 6), +]) + +wires = len(qubits) + +# create a mapping between wire number and Cirq qubit +qb2wire = {i: j for i, j in zip(qubits, range(wires))} + + +###################################################################### +# Now let's create the ``qsim`` device, available via the Cirq plugin, making +# use of the ``wires`` and ``qubits`` keywords that we defined above. +# First, we need to define the number of 'shots' per circuit instance to +# be used—where the number of shots simply corresponds to the number +# of times that the circuit is sampled. This will also be needed later when +# calculating the cross-entropy benchmarking fidelity. The more shots, the +# more accurate the results will be. 500,000 shots will be used here—the same +# number of samples used in the paper—but feel free to +# change this (depending on your own computational restrictions). +# + +shots = 500000 +dev = qml.device('cirq.qsim', wires=wires, qubits=qubits, shots=shots) + + +###################################################################### +# The next step would be to prepare the necessary gates. Some of these +# gates are not natively supported in PennyLane, but are accessible +# through the Cirq plugin. We can define the remaining gates by hand. +# +# For the single-qubit gates we need the :math:`\sqrt{X}` and +# :math:`\sqrt{Y}` gates, which can be written as :math:`RX(\pi/2)` and +# :math:`RY(\pi/2)` respectively, as well as the :math:`\sqrt{W}` gate, +# where :math:`W = \frac{X + Y}{2}.` The latter is easiest defined by its +# unitary matrix +# +# .. math:: +# +# \frac{1}{\sqrt{2}} +# \begin{bmatrix} +# 1 & \sqrt{i} \\ +# \sqrt{-i} & 1 \\ +# \end{bmatrix}. +# +# The :math:`\sqrt{X}` gate is already implemented in PennyLane, while the +# two other gates can be implemented as follows: + +sqrtYgate = lambda wires: qml.RY(np.pi / 2, wires=wires) + +sqrtWgate = lambda wires: qml.QubitUnitary( + np.array([[1, -np.sqrt(1j)], + [np.sqrt(-1j), 1]]) / np.sqrt(2), wires=wires +) + +single_qubit_gates = [qml.SX, sqrtYgate, sqrtWgate] + + +###################################################################### +# For the two-qubit gates we need the iSWAP gate +# +# .. math:: +# +# \begin{bmatrix} +# 1 & 0 & 0 & 0 \\ +# 0 & 0 & i & 0 \\ +# 0 & i & 0 & 0 \\ +# 0 & 0 & 0 & 1 +# \end{bmatrix}, +# +# as well as the CPhase gate +# +# .. math:: +# +# \begin{bmatrix} +# 1 & 0 & 0 & 0 \\ +# 0 & 1 & 0 & 0 \\ +# 0 & 0 & 1 & 0 \\ +# 0 & 0 & 0 & e^{-i\phi} +# \end{bmatrix}, +# +# both accessible via the Cirq plugin. +# + + +###################################################################### +# Assembling the circuit +# ---------------------- +# +# Here comes one of the tricky parts. To decide which qubits the +# two-qubit gates should be applied to, we have to look at how they are +# connected to each other. In an alternating pattern, each pair of +# neighbouring qubits gets labeled with a letter A-D, where A and B +# correspond to all horizontally neighbouring qubits (in a row), and C and +# D to the vertically neighbouring qubits (in a column). This is depicted +# in the figure below, where you can also see how the single-qubit gates +# are applied, as well as the cycles, each consisting of a layer of +# single-qubit gates and a pair of two-qubit gates. Note that each coloured +# two-qubit gate represented in the image is implemented as the two +# consecutive gates iSWAP and CPhase in this demo. +# +# .. figure:: ../_static/demonstration_assets/qsim_beyond_classical/supremacy_circuit.png +# :align: center +# :width: 90% +# +# **Image taken from Arute, F., Arya, K., Babbush, R. et al.** [#Arute2019]_ +# +# The logic below iterates through all connections and returns a +# dictionary, ``gate_order``, where the keys are the connection labels +# between different qubits and the values are lists of all neighbouring +# qubit pairs. We will use this dictionary inside the circuit to iterate +# through the different pairs and apply the two two-qubit gates that we +# just defined above. The way we iterate through the dictionary will depend +# on a gate sequence defined in the next section. +# + +from itertools import combinations + +gate_order = {"A":[], "B":[], "C":[], "D":[]} +for i, j in combinations(qubits, 2): + wire_1 = qb2wire[i] + wire_2 = qb2wire[j] + if i in j.neighbors(): + if i.row == j.row and i.col % 2 == 0: + gate_order["A"].append((wire_1, wire_2)) + elif i.row == j.row and j.col % 2 == 0: + gate_order["B"].append((wire_1, wire_2)) + elif i.col == j.col and i.row % 2 == 0: + gate_order["C"].append((wire_1, wire_2)) + elif i.col == j.col and j.row % 2 == 0: + gate_order["D"].append((wire_1, wire_2)) + + +###################################################################### +# At this point we can define the gate sequence, which is the order the +# two-qubit gates are applied to the different qubit pairs. For example, +# ``["A", "B"]`` would mean that the two-qubit gates are first applied to +# all qubits connected with label A, and then, during the next full cycle, +# the two-qubit gates are applied to all qubits connected with label B. +# This would then correspond to a 2-cycle run (or a circuit with a depth of +# 2). +# +# While we can define any patterns we'd like, the two gate sequences below +# are the ones that are used in the paper. The shorter one is +# used for their classically verifiable benchmarking. The slightly +# longer sequence, which is much harder to simulate classically, is used +# for estimating the cross-entropy fidelity in what they call the "supremacy +# regime". We will use the shorter gate sequence for the following +# demonstration; feel free to play around with other combinations. +# + +m = 14 # number of cycles + +gate_sequence_longer = np.resize(["A", "B", "C", "D", "C", "D", "A", "B"], m) +gate_sequence = np.resize(["A", "B", "C", "D"], m) + + +###################################################################### +# The single-qubit gates are randomly selected and applied to each qubit in +# the circuit, while avoiding the same gate being applied to the same wire +# twice in a row. We do this by creating a helper function ``generate_single_qubit_gate_list()`` that +# specifies the order in which the single-qubit +# gates should be applied. We can use this list within the +# circuit to know which gate to apply when. +# + +def generate_single_qubit_gate_list(): + # create the first list by randomly selecting indices + # from single_qubit_gates + g = [list(np.random.choice(range(len(single_qubit_gates)), size=wires))] + + for cycle in range(len(gate_sequence)): + g.append([]) + for w in range(wires): + # check which gate was applied to the wire previously + one_gate_removed = list(range(len(single_qubit_gates))) + bool_list = np.array(one_gate_removed) == g[cycle][w] + + # and remove it from the choices of gates to be applied + pop_idx = np.where(bool_list)[0][0] + one_gate_removed.pop(pop_idx) + g[cycle + 1].append(np.random.choice(one_gate_removed)) + return g + + +###################################################################### +# Finally, we can define the circuit itself and create a QNode that we will +# use for circuit evaluation with the ``qsim`` device. The two-qubit gates +# are applied to the qubits connected by A, B, C, or D as defined above. +# The circuit ends with a half-cycle, consisting of only a layer of +# single-qubit gates. +# +# From the QNode, we need both the probabilities of the measurement +# results, as well as raw samples. To facilitate this, we add a keyword +# argument to our circuit allowing us to switch between the two returns. We +# take samples from the computational basis state using all wires, which will return bitstrings of +# values :math:`0` and :math:`1,` corresponding to the states +# :math:`\left|0\right>` and :math:`\left|1\right>.` +# + +@qml.qnode(dev) +def circuit(seed=42, return_probs=False): + np.random.seed(seed) + gate_idx = generate_single_qubit_gate_list() + + # m full cycles - single-qubit gates & two-qubit gate + for i, gs in enumerate(gate_sequence): + for w in range(wires): + single_qubit_gates[gate_idx[i][w]](wires=w) + + for qb_1, qb_2 in gate_order[gs]: + qml.ISWAP(wires=(qb_1, qb_2)) + qml.CPhase(-np.pi/6, wires=(qb_1, qb_2)) + + # one half-cycle - single-qubit gates only + for w in range(wires): + single_qubit_gates[gate_idx[-1][w]](wires=w) + + if return_probs: + return qml.probs(wires=range(wires)) + else: + return qml.sample() + +###################################################################### +# The cross-entropy benchmarking fidelity +# --------------------------------------- +# +# The performance metric that is used in the experiment, and the one that we +# will use in this demo, is called the linear cross-entropy benchmarking +# fidelity. It's defined as +# +# .. math:: +# +# F_{XEB} = 2^{n}\left - 1, +# +# where :math:`n` is the number of qubits, :math:`P(x_i)` is the +# probability of bitstring :math:`x_i` computed for the ideal quantum +# circuit, and the average is over the observed bitstrings. +# +# The idea behind using this fidelity is that it will be close to 1 for +# samples obtained from random quantum circuits, such as the one we defined +# above, and close to zero for a uniform probability distribution, which +# can be effectively sampled from classically. Sampling a bitstring from a +# random quantum circuit would follow the distribution +# +# .. math:: +# +# Pr(p) = (N - 1)(1- p)^{N-2}, +# +# where :math:`N = 2^n` is the number of possible bitstrings [#Boixo2018]_. +# This distribution is approximated well by the Porter-Thomas distribution, +# given by :math:`Pr(p) = Ne^{-Np},` a characteristic property of chaotic quantum +# systems. From this we can then calculate the expectation value +# :math:`\left` as follows: +# +# .. math:: +# +# \left = \int_0^1 p^2 N (N-1)(1-p)^{N-2}dp = \frac{2}{N+1}, +# +# which leads to the theoretical fidelity +# +# .. math:: +# +# F_{XEB} = 2^{n}\left - 1 = \frac{2N}{N+1} - 1. +# +# We implement this fidelity as the function below, where ``samples`` is a +# list of sampled bitstrings, and ``probs`` is a list with corresponding +# sampling probabilities for the same noiseless circuit. +# + +def fidelity_xeb(samples, probs): + sampled_probs = [] + for bitstring in samples: + # convert each bitstring into an integer + bitstring_idx = int(bitstring, 2) + + # retrieve the corresponding probability for the bitstring + sampled_probs.append(probs[bitstring_idx]) + + return 2 ** len(samples[0]) * np.mean(sampled_probs) - 1 + + +###################################################################### +# We set a random seed and use it to calculate the probability for all the +# possible bitstrings. It is then possible to sample from exactly the same +# circuit by using the same seed. Before calculating the cross-entropy +# benchmarking fidelity, the Pauli-Z samples need to be converted into +# their correponding bitstrings, since we need the samples to be in the +# computational basis. +# +# .. note:: +# +# Every time the previously defined circuit is run using the ``qsim`` device, ``qsimcirq`` +# will print a warning message because the circuit has no intermediate measurements. +# More information about this warning can be found in the `Measurement sampling +# section of the qsimcirq guide `__. +# + +seed = np.random.randint(0, 42424242) +probs = circuit(seed=seed, return_probs=True) +circuit_samples = circuit(seed=seed) + +# get bitstrings from the samples +bitstring_samples = [] +for sam in circuit_samples: + bitstring_samples.append("".join(str(bs) for bs in sam)) + +f_circuit = fidelity_xeb(bitstring_samples, probs) + +###################################################################### +# Similarly, we can sample random bitstrings from a uniform probability +# distribution by generating all basis states, along with their +# corresponding bitstrings, and sample directly from them using NumPy. +# + +basis_states = dev.generate_basis_states(wires) +random_integers = np.random.randint(0, len(basis_states), size=shots) +bitstring_samples = [] +for i in random_integers: + bitstring_samples.append("".join(str(bs) for bs in basis_states[i])) + +f_uniform = fidelity_xeb(bitstring_samples, probs) + +###################################################################### +# Finally, let's compare the two different values. Sampling from the +# circuit's probability distribution should give a fidelity close to 1, +# while sampling from a uniform distribution should give a fidelity +# close to 0. +# +# .. note:: +# +# The cross-entropy benchmarking fidelity may output +# values that are negative or that are larger than 1, for any finite +# number of samples. This is due to the random nature of the sampling. +# For an infinite amount of samples, or circuit runs, the observed +# values will tend towards the theoretical ones, and will then always +# lie in the 0-to-1 interval. +# +print("Circuit's distribution:", f"{f_circuit:.7f}".rjust(12)) +print("Uniform distribution:", f"{f_uniform:.7f}".rjust(14)) + +###################################################################### +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Circuit's distribution: 1.0398803 +# Uniform distribution: 0.0013487 +# + +###################################################################### +# To show that the fidelity from the circuit sampling actually tends +# towards the theoretical value calculated above we can run several +# different random circuits, calculate their respective cross-entropy +# benchmarking fidelities and then calculate the mean fidelity of all the +# runs. The more evaluations we do, the closer to the theoretical value we +# should get. +# +# In the experiment, they typically calculate each of their +# presented fidelities over ten circuit instances, which only differ +# in the choices of single-qubit gates. In this demo, we use even more +# instances to demonstrate a value closer to the theoretically obtained +# one. +# +# .. note:: +# +# The following mean fidelity calculations can be interesting to play +# around with. You can change the qubit grid at the top of this demo +# using, e.g., 8 or 4 qubits; change the number of shots used; as well +# as the number of circuit evaluations below. Running the following code +# snippet, the mean fidelity should still tend towards the theoretical +# value (which will be lower for fewer qubits). +# + +N = 2 ** wires +theoretical_value = 2 * N / (N + 1) - 1 + +print("Theoretical:", f"{theoretical_value:.7f}".rjust(24)) + +f_circuit = [] +num_of_evaluations = 100 +for i in range(num_of_evaluations): + seed = np.random.randint(0, 42424242) + + probs = circuit(seed=seed, return_probs=True) + samples = circuit(seed=seed) + + bitstring_samples = [] + for sam in samples: + bitstring_samples.append("".join(str(bs) for bs in sam)) + + f_circuit.append(fidelity_xeb(bitstring_samples, probs)) + print(f"\r{i + 1:4d} / {num_of_evaluations:4d}{' ':17}{np.mean(f_circuit):.7f}", end="") +print("\rObserved:", f"{np.mean(f_circuit):.7f}".rjust(27)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Theoretical: 0.9995118 +# Observed: 0.9999512 +# + +###################################################################### +# Classical hardness +# ------------------ +# +# Why are we calculating this specific fidelity, and what does it actually +# mean if we get a cross-entropy benchmarking fidelity close to 1? This is +# an important question, containing one of the main arguments behind why +# this experiment is used to demonstrate "quantum supremacy". +# +# Much is due to the Porter-Thompson probability distribution that the +# random quantum circuits follow, which is hard to simulate classically. +# On the other hand, a quantum device, running a circuit as the one +# constructed above, should be able to sample from such a distribution +# without much overhead. Thus, by showing that a quantum device can produce +# a high enough fidelity value for a large enough circuit, "quantum +# supremacy" can be claimed. This is exactly what Google's experiment +# has done. +# +# There's still one issue that hasn't been touched on yet: the addition of +# noise in quantum hardware. Simply put, this noise will lower the +# cross-entropy benchmarking fidelity—the larger the +# circuit, the more noise there will be, and thus the lower the fidelity, with the +# fidelity approaching 0 as the noise increases. +# By calculating the specific single-qubit, two-qubit, and readout errors +# of the Sycamore chip, and using them to simulate a noisy circuit, the Google +# AI quantum team was able to compare the run-times with the output from +# their actual hardware device. This way, they managed to show that a +# significant speedup could be gained from using a quantum computer, and +# thus proclaimed "quantum supremacy" (see Fig. 4 in [#Arute2019]_). +# +# .. note:: +# +# For more reading on this, the original paper [#Arute2019]_ is highly +# recommended (along with the suplementary information [#Arute2019sup]_ if you want +# to dive deeper into the math and physics of the experiment). The blog +# post in [#Sohaib2019]_, along with the accompanying GitHub repo, also provides +# a nice introduction to the cross-entropy benchmarking fidelity, and +# includes calculations highlighting the effects of added noise models. +# + +###################################################################### +# References +# ---------- +# +# .. [#Arute2019] +# +# Arute, F., Arya, K., Babbush, R. et al. "Quantum supremacy using a programmable +# superconducting processor" +# `Nature 574, 505-510 (2019) `__. +# +# .. [#Arute2019sup] +# +# Arute, F., Arya, K., Babbush, R. et al. Supplementary information for "Quantum +# supremacy using a programmable superconducting processor" +# `arXiv:1910.11333 (2019) `__ +# +# .. [#Martinis2020] +# +# Martinis, John M. et al. (2020), `Quantum supremacy using a programmable +# superconducting processor, Dryad, Dataset `__ +# +# .. [#Boixo2018] +# +# Boixo, S., Isakov, S.V., Smelyanskiy, V.N. et al. Characterizing quantum supremacy +# in near-term devices. +# `Nature Phys 14, 595-600 (2018) `__ +# +# .. [#Sohaib2019] +# +# Sohaib, Alam M. and Zeng, W., `Unpacking the Quantum Supremacy Benchmark with Python +# `__ +# +# diff --git a/demonstrations_v2/qsim_beyond_classical/metadata.json b/demonstrations_v2/qsim_beyond_classical/metadata.json new file mode 100644 index 0000000000..d3960e5d20 --- /dev/null +++ b/demonstrations_v2/qsim_beyond_classical/metadata.json @@ -0,0 +1,94 @@ +{ + "title": "Beyond classical computing with qsim", + "authors": [ + { + "username": "tisacsson" + } + ], + "dateOfPublication": "2020-11-30T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_beyond_classical_computing_QSM.png" + } + ], + "seoDescription": "Use Google's qsim simulator to explore the barriers between quantum and classical computing, and recreate their benchmarks and circuits.", + "doi": "", + "references": [ + { + "id": "Arute2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Arute, F., Arya, K., Babbush, R. et al.", + "year": "2019", + "journal": "Nature", + "doi": "10.1038/s41586-019-1666-5", + "url": "" + }, + { + "id": "Arute2019sup", + "type": "article", + "title": "Supplementary information for \"Quantum supremacy using a programmable superconducting processor\"", + "authors": "Arute, F., Arya, K., Babbush, R. et al.", + "year": "2019", + "journal": "", + "doi": "10.48550/arXiv.1910.11333", + "url": "https://arxiv.org/abs/1910.11333" + }, + { + "id": "Martinis2020", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Martinis, John M. et al.", + "year": "2020", + "journal": "Dryad", + "doi": "10.5061/dryad.k6t1rj8", + "url": "" + }, + { + "id": "Boixo2018", + "type": "article", + "title": "Characterizing quantum supremacy in near-term devices.", + "authors": "Boixo, S., Isakov, S.V., Smelyanskiy, V.N. et al.", + "year": "2018", + "journal": "Nature Phys", + "doi": "10.1038/s41567-018-0124-x", + "url": "" + }, + { + "id": "Sohaib2019", + "type": "article", + "title": "Unpacking the Quantum Supremacy Benchmark with Python", + "authors": "Sohaib, Alam M. and Zeng, W.", + "year": "2019", + "url": "https://medium.com/@sohaib.alam/unpacking-the-quantum-supremacy-benchmark-with-python-67a46709d" + } + ], + "basedOnPapers": [ + "10.1038/s41586-019-1666-5" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_metrology", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/qsim_beyond_classical/requirements.in b/demonstrations_v2/qsim_beyond_classical/requirements.in new file mode 100644 index 0000000000..4bf640990a --- /dev/null +++ b/demonstrations_v2/qsim_beyond_classical/requirements.in @@ -0,0 +1,4 @@ +cirq +numpy +pennylane +pennylane_cirq diff --git a/demonstrations_v2/quantum_neural_net/demo.py b/demonstrations_v2/quantum_neural_net/demo.py new file mode 100644 index 0000000000..07e39c352e --- /dev/null +++ b/demonstrations_v2/quantum_neural_net/demo.py @@ -0,0 +1,741 @@ +""" +.. role:: html(raw) + :format: html + +.. _quantum_neural_net: + +Function fitting with a photonic quantum neural network +======================================================= + +.. meta:: + :property="og:description": Fit to noisy data with a variational quantum circuit. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qnn_output_28_0.png + +.. related:: + + qonn Optimizing a quantum optical neural network + pytorch_noise PyTorch and noisy devices + tutorial_noisy_circuit_optimization Optimizing noisy circuits with Cirq + +*Author: Maria Schuld — Posted: 11 October 2019. Last updated: 25 January 2021.* + +.. warning:: + This demo is only compatible with PennyLane version ``0.29`` or below. + +In this example we show how a variational circuit can be used to learn a +fit for a one-dimensional function when being trained with noisy samples +from that function. + +The variational circuit we use is the continuous-variable quantum neural +network model described in `Killoran et al. +(2018) `__. + +Imports +~~~~~~~ + +We import PennyLane, the wrapped version of NumPy provided by PennyLane, +and an optimizer. +""" + +import pennylane as qml +from pennylane import numpy as np +from pennylane.optimize import AdamOptimizer + +############################################################################## +# The device we use is the Strawberry Fields simulator, this time with +# only one quantum mode (or ``wire``). You will need to have the +# Strawberry Fields plugin for PennyLane installed. + +dev = qml.device("strawberryfields.fock", wires=1, cutoff_dim=10) + +############################################################################## +# Quantum node +# ~~~~~~~~~~~~ +# +# For a single quantum mode, each layer of the variational circuit is +# defined as: + + +def layer(v): + # Matrix multiplication of input layer + qml.Rotation(v[0], wires=0) + qml.Squeezing(v[1], 0.0, wires=0) + qml.Rotation(v[2], wires=0) + + # Bias + qml.Displacement(v[3], 0.0, wires=0) + + # Element-wise nonlinear transformation + qml.Kerr(v[4], wires=0) + + +############################################################################## +# The variational circuit in the quantum node first encodes the input into +# the displacement of the mode, and then executes the layers. The output +# is the expectation of the x-quadrature. + + +@qml.qnode(dev) +def quantum_neural_net(var, x): + # Encode input x into quantum state + qml.Displacement(x, 0.0, wires=0) + + # "layer" subcircuits + for v in var: + layer(v) + + return qml.expval(qml.QuadX(0)) + + +############################################################################## +# Objective +# ~~~~~~~~~ +# +# As an objective we take the square loss between target labels and model +# predictions. + + +def square_loss(labels, predictions): + loss = 0 + for l, p in zip(labels, predictions): + loss = loss + (l - p) ** 2 + + loss = loss / len(labels) + return loss + + +############################################################################## +# In the cost function, we compute the outputs from the variational +# circuit. Function fitting is a regression problem, and we interpret the +# expectations from the quantum node as predictions (i.e., without +# applying postprocessing such as thresholding). + + +def cost(var, features, labels): + preds = [quantum_neural_net(var, x) for x in features] + return square_loss(labels, preds) + + +############################################################################## +# Optimization +# ~~~~~~~~~~~~ +# +# We load noisy data samples of a sine function from the external file ``sine.txt`` +# (:html:`download the file here`). + +data = np.loadtxt("sine.txt") +X = np.array(data[:, 0], requires_grad=False) +Y = np.array(data[:, 1], requires_grad=False) + +############################################################################## +# Before training a model, let's examine the data. +# +# *Note: For the next cell to work you need the matplotlib library.* + +import matplotlib.pyplot as plt + +plt.figure() +plt.scatter(X, Y) +plt.xlabel("x", fontsize=18) +plt.ylabel("f(x)", fontsize=18) +plt.tick_params(axis="both", which="major", labelsize=16) +plt.tick_params(axis="both", which="minor", labelsize=16) +plt.show() + +############################################################################## +# .. image:: ../_static/demonstration_assets/quantum_neural_net/qnn_output_20_0.png +# +# The network’s weights (called ``var`` here) are initialized with values +# sampled from a normal distribution. We use 4 layers; performance has +# been found to plateau at around 6 layers. + +np.random.seed(0) +num_layers = 4 +var_init = 0.05 * np.random.randn(num_layers, 5, requires_grad=True) +print(var_init) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# array([[ 0.08820262, 0.02000786, 0.0489369 , 0.11204466, 0.0933779 ], +# [-0.04886389, 0.04750442, -0.00756786, -0.00516094, 0.02052993], +# [ 0.00720218, 0.07271368, 0.03805189, 0.00608375, 0.02219316], +# [ 0.01668372, 0.07470395, -0.01025791, 0.01565339, -0.04270479]]) +# +# Using the Adam optimizer, we update the weights for 500 steps (this +# takes some time). More steps will lead to a better fit. + +opt = AdamOptimizer(0.01, beta1=0.9, beta2=0.999) + +var = var_init +for it in range(500): + (var, _, _), _cost = opt.step_and_cost(cost, var, X, Y) + print("Iter: {:5d} | Cost: {:0.7f} ".format(it, _cost)) + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Iter: 0 | Cost: 0.3006065 +# Iter: 1 | Cost: 0.2689702 +# Iter: 2 | Cost: 0.2472125 +# Iter: 3 | Cost: 0.2300139 +# Iter: 4 | Cost: 0.2157100 +# Iter: 5 | Cost: 0.2035455 +# Iter: 6 | Cost: 0.1931103 +# Iter: 7 | Cost: 0.1841536 +# Iter: 8 | Cost: 0.1765061 +# Iter: 9 | Cost: 0.1700410 +# Iter: 10 | Cost: 0.1646527 +# Iter: 11 | Cost: 0.1602444 +# Iter: 12 | Cost: 0.1567201 +# Iter: 13 | Cost: 0.1539806 +# Iter: 14 | Cost: 0.1519220 +# Iter: 15 | Cost: 0.1504356 +# Iter: 16 | Cost: 0.1494099 +# Iter: 17 | Cost: 0.1487330 +# Iter: 18 | Cost: 0.1482962 +# Iter: 19 | Cost: 0.1479980 +# Iter: 20 | Cost: 0.1477470 +# Iter: 21 | Cost: 0.1474655 +# Iter: 22 | Cost: 0.1470914 +# Iter: 23 | Cost: 0.1465799 +# Iter: 24 | Cost: 0.1459034 +# Iter: 25 | Cost: 0.1450506 +# Iter: 26 | Cost: 0.1440251 +# Iter: 27 | Cost: 0.1428427 +# Iter: 28 | Cost: 0.1415282 +# Iter: 29 | Cost: 0.1401125 +# Iter: 30 | Cost: 0.1386296 +# Iter: 31 | Cost: 0.1371132 +# Iter: 32 | Cost: 0.1355946 +# Iter: 33 | Cost: 0.1341006 +# Iter: 34 | Cost: 0.1326526 +# Iter: 35 | Cost: 0.1312654 +# Iter: 36 | Cost: 0.1299478 +# Iter: 37 | Cost: 0.1287022 +# Iter: 38 | Cost: 0.1275259 +# Iter: 39 | Cost: 0.1264120 +# Iter: 40 | Cost: 0.1253502 +# Iter: 41 | Cost: 0.1243284 +# Iter: 42 | Cost: 0.1233333 +# Iter: 43 | Cost: 0.1223521 +# Iter: 44 | Cost: 0.1213726 +# Iter: 45 | Cost: 0.1203843 +# Iter: 46 | Cost: 0.1193790 +# Iter: 47 | Cost: 0.1183506 +# Iter: 48 | Cost: 0.1172959 +# Iter: 49 | Cost: 0.1162138 +# Iter: 50 | Cost: 0.1151057 +# Iter: 51 | Cost: 0.1139748 +# Iter: 52 | Cost: 0.1128259 +# Iter: 53 | Cost: 0.1116647 +# Iter: 54 | Cost: 0.1104972 +# Iter: 55 | Cost: 0.1093295 +# Iter: 56 | Cost: 0.1081673 +# Iter: 57 | Cost: 0.1070151 +# Iter: 58 | Cost: 0.1058764 +# Iter: 59 | Cost: 0.1047533 +# Iter: 60 | Cost: 0.1036464 +# Iter: 61 | Cost: 0.1025554 +# Iter: 62 | Cost: 0.1014787 +# Iter: 63 | Cost: 0.1004141 +# Iter: 64 | Cost: 0.0993591 +# Iter: 65 | Cost: 0.0983111 +# Iter: 66 | Cost: 0.0972679 +# Iter: 67 | Cost: 0.0962278 +# Iter: 68 | Cost: 0.0951896 +# Iter: 69 | Cost: 0.0941534 +# Iter: 70 | Cost: 0.0931195 +# Iter: 71 | Cost: 0.0920891 +# Iter: 72 | Cost: 0.0910638 +# Iter: 73 | Cost: 0.0900453 +# Iter: 74 | Cost: 0.0890357 +# Iter: 75 | Cost: 0.0880366 +# Iter: 76 | Cost: 0.0870493 +# Iter: 77 | Cost: 0.0860751 +# Iter: 78 | Cost: 0.0851144 +# Iter: 79 | Cost: 0.0841675 +# Iter: 80 | Cost: 0.0832342 +# Iter: 81 | Cost: 0.0823143 +# Iter: 82 | Cost: 0.0814072 +# Iter: 83 | Cost: 0.0805125 +# Iter: 84 | Cost: 0.0796296 +# Iter: 85 | Cost: 0.0787583 +# Iter: 86 | Cost: 0.0778983 +# Iter: 87 | Cost: 0.0770497 +# Iter: 88 | Cost: 0.0762127 +# Iter: 89 | Cost: 0.0753874 +# Iter: 90 | Cost: 0.0745742 +# Iter: 91 | Cost: 0.0737733 +# Iter: 92 | Cost: 0.0729849 +# Iter: 93 | Cost: 0.0722092 +# Iter: 94 | Cost: 0.0714462 +# Iter: 95 | Cost: 0.0706958 +# Iter: 96 | Cost: 0.0699578 +# Iter: 97 | Cost: 0.0692319 +# Iter: 98 | Cost: 0.0685177 +# Iter: 99 | Cost: 0.0678151 +# Iter: 100 | Cost: 0.0671236 +# Iter: 101 | Cost: 0.0664430 +# Iter: 102 | Cost: 0.0657732 +# Iter: 103 | Cost: 0.0651139 +# Iter: 104 | Cost: 0.0644650 +# Iter: 105 | Cost: 0.0638264 +# Iter: 106 | Cost: 0.0631981 +# Iter: 107 | Cost: 0.0625800 +# Iter: 108 | Cost: 0.0619719 +# Iter: 109 | Cost: 0.0613737 +# Iter: 110 | Cost: 0.0607853 +# Iter: 111 | Cost: 0.0602064 +# Iter: 112 | Cost: 0.0596368 +# Iter: 113 | Cost: 0.0590764 +# Iter: 114 | Cost: 0.0585249 +# Iter: 115 | Cost: 0.0579820 +# Iter: 116 | Cost: 0.0574476 +# Iter: 117 | Cost: 0.0569214 +# Iter: 118 | Cost: 0.0564033 +# Iter: 119 | Cost: 0.0558932 +# Iter: 120 | Cost: 0.0553908 +# Iter: 121 | Cost: 0.0548960 +# Iter: 122 | Cost: 0.0544086 +# Iter: 123 | Cost: 0.0539286 +# Iter: 124 | Cost: 0.0534557 +# Iter: 125 | Cost: 0.0529897 +# Iter: 126 | Cost: 0.0525306 +# Iter: 127 | Cost: 0.0520781 +# Iter: 128 | Cost: 0.0516320 +# Iter: 129 | Cost: 0.0511923 +# Iter: 130 | Cost: 0.0507587 +# Iter: 131 | Cost: 0.0503311 +# Iter: 132 | Cost: 0.0499094 +# Iter: 133 | Cost: 0.0494934 +# Iter: 134 | Cost: 0.0490830 +# Iter: 135 | Cost: 0.0486781 +# Iter: 136 | Cost: 0.0482785 +# Iter: 137 | Cost: 0.0478842 +# Iter: 138 | Cost: 0.0474949 +# Iter: 139 | Cost: 0.0471107 +# Iter: 140 | Cost: 0.0467313 +# Iter: 141 | Cost: 0.0463567 +# Iter: 142 | Cost: 0.0459868 +# Iter: 143 | Cost: 0.0456214 +# Iter: 144 | Cost: 0.0452604 +# Iter: 145 | Cost: 0.0449038 +# Iter: 146 | Cost: 0.0445514 +# Iter: 147 | Cost: 0.0442032 +# Iter: 148 | Cost: 0.0438590 +# Iter: 149 | Cost: 0.0435188 +# Iter: 150 | Cost: 0.0431825 +# Iter: 151 | Cost: 0.0428499 +# Iter: 152 | Cost: 0.0425211 +# Iter: 153 | Cost: 0.0421960 +# Iter: 154 | Cost: 0.0418744 +# Iter: 155 | Cost: 0.0415563 +# Iter: 156 | Cost: 0.0412416 +# Iter: 157 | Cost: 0.0409302 +# Iter: 158 | Cost: 0.0406222 +# Iter: 159 | Cost: 0.0403173 +# Iter: 160 | Cost: 0.0400156 +# Iter: 161 | Cost: 0.0397169 +# Iter: 162 | Cost: 0.0394213 +# Iter: 163 | Cost: 0.0391286 +# Iter: 164 | Cost: 0.0388389 +# Iter: 165 | Cost: 0.0385520 +# Iter: 166 | Cost: 0.0382679 +# Iter: 167 | Cost: 0.0379866 +# Iter: 168 | Cost: 0.0377079 +# Iter: 169 | Cost: 0.0374319 +# Iter: 170 | Cost: 0.0371585 +# Iter: 171 | Cost: 0.0368877 +# Iter: 172 | Cost: 0.0366194 +# Iter: 173 | Cost: 0.0363535 +# Iter: 174 | Cost: 0.0360901 +# Iter: 175 | Cost: 0.0358291 +# Iter: 176 | Cost: 0.0355704 +# Iter: 177 | Cost: 0.0353140 +# Iter: 178 | Cost: 0.0350599 +# Iter: 179 | Cost: 0.0348081 +# Iter: 180 | Cost: 0.0345585 +# Iter: 181 | Cost: 0.0343110 +# Iter: 182 | Cost: 0.0340658 +# Iter: 183 | Cost: 0.0338226 +# Iter: 184 | Cost: 0.0335815 +# Iter: 185 | Cost: 0.0333425 +# Iter: 186 | Cost: 0.0331056 +# Iter: 187 | Cost: 0.0328706 +# Iter: 188 | Cost: 0.0326377 +# Iter: 189 | Cost: 0.0324067 +# Iter: 190 | Cost: 0.0321777 +# Iter: 191 | Cost: 0.0319506 +# Iter: 192 | Cost: 0.0317255 +# Iter: 193 | Cost: 0.0315022 +# Iter: 194 | Cost: 0.0312808 +# Iter: 195 | Cost: 0.0310613 +# Iter: 196 | Cost: 0.0308436 +# Iter: 197 | Cost: 0.0306278 +# Iter: 198 | Cost: 0.0304138 +# Iter: 199 | Cost: 0.0302016 +# Iter: 200 | Cost: 0.0299912 +# Iter: 201 | Cost: 0.0297826 +# Iter: 202 | Cost: 0.0295757 +# Iter: 203 | Cost: 0.0293707 +# Iter: 204 | Cost: 0.0291674 +# Iter: 205 | Cost: 0.0289659 +# Iter: 206 | Cost: 0.0287661 +# Iter: 207 | Cost: 0.0285681 +# Iter: 208 | Cost: 0.0283718 +# Iter: 209 | Cost: 0.0281772 +# Iter: 210 | Cost: 0.0279844 +# Iter: 211 | Cost: 0.0277933 +# Iter: 212 | Cost: 0.0276039 +# Iter: 213 | Cost: 0.0274163 +# Iter: 214 | Cost: 0.0272304 +# Iter: 215 | Cost: 0.0270461 +# Iter: 216 | Cost: 0.0268636 +# Iter: 217 | Cost: 0.0266829 +# Iter: 218 | Cost: 0.0265038 +# Iter: 219 | Cost: 0.0263264 +# Iter: 220 | Cost: 0.0261508 +# Iter: 221 | Cost: 0.0259768 +# Iter: 222 | Cost: 0.0258046 +# Iter: 223 | Cost: 0.0256341 +# Iter: 224 | Cost: 0.0254652 +# Iter: 225 | Cost: 0.0252981 +# Iter: 226 | Cost: 0.0251327 +# Iter: 227 | Cost: 0.0249690 +# Iter: 228 | Cost: 0.0248070 +# Iter: 229 | Cost: 0.0246467 +# Iter: 230 | Cost: 0.0244881 +# Iter: 231 | Cost: 0.0243312 +# Iter: 232 | Cost: 0.0241760 +# Iter: 233 | Cost: 0.0240225 +# Iter: 234 | Cost: 0.0238707 +# Iter: 235 | Cost: 0.0237206 +# Iter: 236 | Cost: 0.0235721 +# Iter: 237 | Cost: 0.0234254 +# Iter: 238 | Cost: 0.0232803 +# Iter: 239 | Cost: 0.0231369 +# Iter: 240 | Cost: 0.0229952 +# Iter: 241 | Cost: 0.0228552 +# Iter: 242 | Cost: 0.0227168 +# Iter: 243 | Cost: 0.0225801 +# Iter: 244 | Cost: 0.0224450 +# Iter: 245 | Cost: 0.0223116 +# Iter: 246 | Cost: 0.0221798 +# Iter: 247 | Cost: 0.0220496 +# Iter: 248 | Cost: 0.0219211 +# Iter: 249 | Cost: 0.0217942 +# Iter: 250 | Cost: 0.0216688 +# Iter: 251 | Cost: 0.0215451 +# Iter: 252 | Cost: 0.0214230 +# Iter: 253 | Cost: 0.0213024 +# Iter: 254 | Cost: 0.0211835 +# Iter: 255 | Cost: 0.0210660 +# Iter: 256 | Cost: 0.0209502 +# Iter: 257 | Cost: 0.0208358 +# Iter: 258 | Cost: 0.0207230 +# Iter: 259 | Cost: 0.0206117 +# Iter: 260 | Cost: 0.0205019 +# Iter: 261 | Cost: 0.0203936 +# Iter: 262 | Cost: 0.0202867 +# Iter: 263 | Cost: 0.0201813 +# Iter: 264 | Cost: 0.0200773 +# Iter: 265 | Cost: 0.0199748 +# Iter: 266 | Cost: 0.0198737 +# Iter: 267 | Cost: 0.0197740 +# Iter: 268 | Cost: 0.0196757 +# Iter: 269 | Cost: 0.0195787 +# Iter: 270 | Cost: 0.0194831 +# Iter: 271 | Cost: 0.0193889 +# Iter: 272 | Cost: 0.0192959 +# Iter: 273 | Cost: 0.0192043 +# Iter: 274 | Cost: 0.0191140 +# Iter: 275 | Cost: 0.0190249 +# Iter: 276 | Cost: 0.0189371 +# Iter: 277 | Cost: 0.0188505 +# Iter: 278 | Cost: 0.0187651 +# Iter: 279 | Cost: 0.0186810 +# Iter: 280 | Cost: 0.0185980 +# Iter: 281 | Cost: 0.0185163 +# Iter: 282 | Cost: 0.0184356 +# Iter: 283 | Cost: 0.0183561 +# Iter: 284 | Cost: 0.0182777 +# Iter: 285 | Cost: 0.0182004 +# Iter: 286 | Cost: 0.0181242 +# Iter: 287 | Cost: 0.0180491 +# Iter: 288 | Cost: 0.0179750 +# Iter: 289 | Cost: 0.0179020 +# Iter: 290 | Cost: 0.0178299 +# Iter: 291 | Cost: 0.0177589 +# Iter: 292 | Cost: 0.0176888 +# Iter: 293 | Cost: 0.0176197 +# Iter: 294 | Cost: 0.0175515 +# Iter: 295 | Cost: 0.0174843 +# Iter: 296 | Cost: 0.0174180 +# Iter: 297 | Cost: 0.0173525 +# Iter: 298 | Cost: 0.0172880 +# Iter: 299 | Cost: 0.0172243 +# Iter: 300 | Cost: 0.0171614 +# Iter: 301 | Cost: 0.0170994 +# Iter: 302 | Cost: 0.0170382 +# Iter: 303 | Cost: 0.0169777 +# Iter: 304 | Cost: 0.0169181 +# Iter: 305 | Cost: 0.0168592 +# Iter: 306 | Cost: 0.0168010 +# Iter: 307 | Cost: 0.0167436 +# Iter: 308 | Cost: 0.0166869 +# Iter: 309 | Cost: 0.0166309 +# Iter: 310 | Cost: 0.0165756 +# Iter: 311 | Cost: 0.0165209 +# Iter: 312 | Cost: 0.0164669 +# Iter: 313 | Cost: 0.0164136 +# Iter: 314 | Cost: 0.0163608 +# Iter: 315 | Cost: 0.0163087 +# Iter: 316 | Cost: 0.0162572 +# Iter: 317 | Cost: 0.0162063 +# Iter: 318 | Cost: 0.0161559 +# Iter: 319 | Cost: 0.0161061 +# Iter: 320 | Cost: 0.0160568 +# Iter: 321 | Cost: 0.0160080 +# Iter: 322 | Cost: 0.0159598 +# Iter: 323 | Cost: 0.0159121 +# Iter: 324 | Cost: 0.0158649 +# Iter: 325 | Cost: 0.0158181 +# Iter: 326 | Cost: 0.0157719 +# Iter: 327 | Cost: 0.0157260 +# Iter: 328 | Cost: 0.0156807 +# Iter: 329 | Cost: 0.0156357 +# Iter: 330 | Cost: 0.0155912 +# Iter: 331 | Cost: 0.0155471 +# Iter: 332 | Cost: 0.0155034 +# Iter: 333 | Cost: 0.0154601 +# Iter: 334 | Cost: 0.0154172 +# Iter: 335 | Cost: 0.0153747 +# Iter: 336 | Cost: 0.0153325 +# Iter: 337 | Cost: 0.0152907 +# Iter: 338 | Cost: 0.0152492 +# Iter: 339 | Cost: 0.0152081 +# Iter: 340 | Cost: 0.0151673 +# Iter: 341 | Cost: 0.0151269 +# Iter: 342 | Cost: 0.0150867 +# Iter: 343 | Cost: 0.0150469 +# Iter: 344 | Cost: 0.0150073 +# Iter: 345 | Cost: 0.0149681 +# Iter: 346 | Cost: 0.0149291 +# Iter: 347 | Cost: 0.0148905 +# Iter: 348 | Cost: 0.0148521 +# Iter: 349 | Cost: 0.0148140 +# Iter: 350 | Cost: 0.0147761 +# Iter: 351 | Cost: 0.0147385 +# Iter: 352 | Cost: 0.0147012 +# Iter: 353 | Cost: 0.0146641 +# Iter: 354 | Cost: 0.0146273 +# Iter: 355 | Cost: 0.0145907 +# Iter: 356 | Cost: 0.0145543 +# Iter: 357 | Cost: 0.0145182 +# Iter: 358 | Cost: 0.0144824 +# Iter: 359 | Cost: 0.0144467 +# Iter: 360 | Cost: 0.0144113 +# Iter: 361 | Cost: 0.0143762 +# Iter: 362 | Cost: 0.0143412 +# Iter: 363 | Cost: 0.0143065 +# Iter: 364 | Cost: 0.0142720 +# Iter: 365 | Cost: 0.0142378 +# Iter: 366 | Cost: 0.0142037 +# Iter: 367 | Cost: 0.0141699 +# Iter: 368 | Cost: 0.0141363 +# Iter: 369 | Cost: 0.0141030 +# Iter: 370 | Cost: 0.0140699 +# Iter: 371 | Cost: 0.0140370 +# Iter: 372 | Cost: 0.0140043 +# Iter: 373 | Cost: 0.0139719 +# Iter: 374 | Cost: 0.0139397 +# Iter: 375 | Cost: 0.0139077 +# Iter: 376 | Cost: 0.0138760 +# Iter: 377 | Cost: 0.0138445 +# Iter: 378 | Cost: 0.0138132 +# Iter: 379 | Cost: 0.0137822 +# Iter: 380 | Cost: 0.0137515 +# Iter: 381 | Cost: 0.0137210 +# Iter: 382 | Cost: 0.0136907 +# Iter: 383 | Cost: 0.0136607 +# Iter: 384 | Cost: 0.0136310 +# Iter: 385 | Cost: 0.0136015 +# Iter: 386 | Cost: 0.0135723 +# Iter: 387 | Cost: 0.0135433 +# Iter: 388 | Cost: 0.0135146 +# Iter: 389 | Cost: 0.0134863 +# Iter: 390 | Cost: 0.0134581 +# Iter: 391 | Cost: 0.0134303 +# Iter: 392 | Cost: 0.0134027 +# Iter: 393 | Cost: 0.0133755 +# Iter: 394 | Cost: 0.0133485 +# Iter: 395 | Cost: 0.0133218 +# Iter: 396 | Cost: 0.0132954 +# Iter: 397 | Cost: 0.0132694 +# Iter: 398 | Cost: 0.0132436 +# Iter: 399 | Cost: 0.0132181 +# Iter: 400 | Cost: 0.0131929 +# Iter: 401 | Cost: 0.0131681 +# Iter: 402 | Cost: 0.0131435 +# Iter: 403 | Cost: 0.0131193 +# Iter: 404 | Cost: 0.0130953 +# Iter: 405 | Cost: 0.0130717 +# Iter: 406 | Cost: 0.0130484 +# Iter: 407 | Cost: 0.0130254 +# Iter: 408 | Cost: 0.0130028 +# Iter: 409 | Cost: 0.0129804 +# Iter: 410 | Cost: 0.0129584 +# Iter: 411 | Cost: 0.0129367 +# Iter: 412 | Cost: 0.0129153 +# Iter: 413 | Cost: 0.0128942 +# Iter: 414 | Cost: 0.0128735 +# Iter: 415 | Cost: 0.0128530 +# Iter: 416 | Cost: 0.0128329 +# Iter: 417 | Cost: 0.0128131 +# Iter: 418 | Cost: 0.0127935 +# Iter: 419 | Cost: 0.0127743 +# Iter: 420 | Cost: 0.0127554 +# Iter: 421 | Cost: 0.0127368 +# Iter: 422 | Cost: 0.0127185 +# Iter: 423 | Cost: 0.0127006 +# Iter: 424 | Cost: 0.0126829 +# Iter: 425 | Cost: 0.0126655 +# Iter: 426 | Cost: 0.0126483 +# Iter: 427 | Cost: 0.0126315 +# Iter: 428 | Cost: 0.0126150 +# Iter: 429 | Cost: 0.0125987 +# Iter: 430 | Cost: 0.0125827 +# Iter: 431 | Cost: 0.0125670 +# Iter: 432 | Cost: 0.0125516 +# Iter: 433 | Cost: 0.0125364 +# Iter: 434 | Cost: 0.0125215 +# Iter: 435 | Cost: 0.0125068 +# Iter: 436 | Cost: 0.0124924 +# Iter: 437 | Cost: 0.0124782 +# Iter: 438 | Cost: 0.0124643 +# Iter: 439 | Cost: 0.0124507 +# Iter: 440 | Cost: 0.0124372 +# Iter: 441 | Cost: 0.0124240 +# Iter: 442 | Cost: 0.0124110 +# Iter: 443 | Cost: 0.0123983 +# Iter: 444 | Cost: 0.0123857 +# Iter: 445 | Cost: 0.0123734 +# Iter: 446 | Cost: 0.0123613 +# Iter: 447 | Cost: 0.0123494 +# Iter: 448 | Cost: 0.0123377 +# Iter: 449 | Cost: 0.0123262 +# Iter: 450 | Cost: 0.0123149 +# Iter: 451 | Cost: 0.0123038 +# Iter: 452 | Cost: 0.0122929 +# Iter: 453 | Cost: 0.0122821 +# Iter: 454 | Cost: 0.0122715 +# Iter: 455 | Cost: 0.0122611 +# Iter: 456 | Cost: 0.0122509 +# Iter: 457 | Cost: 0.0122409 +# Iter: 458 | Cost: 0.0122310 +# Iter: 459 | Cost: 0.0122212 +# Iter: 460 | Cost: 0.0122116 +# Iter: 461 | Cost: 0.0122022 +# Iter: 462 | Cost: 0.0121929 +# Iter: 463 | Cost: 0.0121838 +# Iter: 464 | Cost: 0.0121748 +# Iter: 465 | Cost: 0.0121660 +# Iter: 466 | Cost: 0.0121572 +# Iter: 467 | Cost: 0.0121487 +# Iter: 468 | Cost: 0.0121402 +# Iter: 469 | Cost: 0.0121319 +# Iter: 470 | Cost: 0.0121237 +# Iter: 471 | Cost: 0.0121156 +# Iter: 472 | Cost: 0.0121076 +# Iter: 473 | Cost: 0.0120998 +# Iter: 474 | Cost: 0.0120921 +# Iter: 475 | Cost: 0.0120844 +# Iter: 476 | Cost: 0.0120769 +# Iter: 477 | Cost: 0.0120695 +# Iter: 478 | Cost: 0.0120622 +# Iter: 479 | Cost: 0.0120550 +# Iter: 480 | Cost: 0.0120479 +# Iter: 481 | Cost: 0.0120409 +# Iter: 482 | Cost: 0.0120340 +# Iter: 483 | Cost: 0.0120272 +# Iter: 484 | Cost: 0.0120205 +# Iter: 485 | Cost: 0.0120138 +# Iter: 486 | Cost: 0.0120073 +# Iter: 487 | Cost: 0.0120008 +# Iter: 488 | Cost: 0.0119944 +# Iter: 489 | Cost: 0.0119881 +# Iter: 490 | Cost: 0.0119819 +# Iter: 491 | Cost: 0.0119758 +# Iter: 492 | Cost: 0.0119697 +# Iter: 493 | Cost: 0.0119637 +# Iter: 494 | Cost: 0.0119578 +# Iter: 495 | Cost: 0.0119520 +# Iter: 496 | Cost: 0.0119462 +# Iter: 497 | Cost: 0.0119405 +# Iter: 498 | Cost: 0.0119349 +# Iter: 499 | Cost: 0.0119293 +# +# +# Finally, we collect the predictions of the trained model for 50 values +# in the range :math:`[-1,1]:` + +x_pred = np.linspace(-1, 1, 50) +predictions = [quantum_neural_net(var, x_) for x_ in x_pred] + +############################################################################## +# and plot the shape of the function that the model has “learned” from +# the noisy data (green dots). + +plt.figure() +plt.scatter(X, Y) +plt.scatter(x_pred, predictions, color="green") +plt.xlabel("x") +plt.ylabel("f(x)") +plt.tick_params(axis="both", which="major") +plt.tick_params(axis="both", which="minor") +plt.show() + +############################################################################## +# .. image:: ../_static/demonstration_assets/quantum_neural_net/qnn_output_28_0.png +# +# The model has learned to smooth the noisy data. +# +# In fact, we can use PennyLane to look at typical functions that the +# model produces without being trained at all. The shape of these +# functions varies significantly with the variance hyperparameter for the +# weight initialization. +# +# Setting this hyperparameter to a small value produces almost linear +# functions, since all quantum gates in the variational circuit +# approximately perform the identity transformation in that case. Larger +# values produce smoothly oscillating functions with a period that depends +# on the number of layers used (generically, the more layers, the smaller +# the period). + +variance = 1.0 + +plt.figure() +x_pred = np.linspace(-2, 2, 50) +for i in range(7): + rnd_var = variance * np.random.randn(num_layers, 7) + predictions = [quantum_neural_net(rnd_var, x_) for x_ in x_pred] + plt.plot(x_pred, predictions, color="black") +plt.xlabel("x") +plt.ylabel("f(x)") +plt.tick_params(axis="both", which="major") +plt.tick_params(axis="both", which="minor") +plt.show() + +############################################################################## +# .. image:: ../_static/demonstration_assets/quantum_neural_net/qnn_output_30_0.png + +############################################################################## diff --git a/demonstrations_v2/quantum_neural_net/metadata.json b/demonstrations_v2/quantum_neural_net/metadata.json new file mode 100644 index 0000000000..47ac6cb861 --- /dev/null +++ b/demonstrations_v2/quantum_neural_net/metadata.json @@ -0,0 +1,42 @@ +{ + "title": "Function fitting with a photonic quantum neural network", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_function_fitting_photonic_quantum_neural.png" + } + ], + "seoDescription": "Fit to noisy data with a variational quantum circuit.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "qonn", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/quantum_neural_net/requirements.in b/demonstrations_v2/quantum_neural_net/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/quantum_neural_net/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/quantum_volume/demo.py b/demonstrations_v2/quantum_volume/demo.py new file mode 100644 index 0000000000..7dc9e43f48 --- /dev/null +++ b/demonstrations_v2/quantum_volume/demo.py @@ -0,0 +1,890 @@ +r""".. _quantum_volume: + +Quantum volume +============== + +.. meta:: + :property="og:description": Learn about quantum volume, and how to + compute it. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/quantum_volume_thumbnail.png + +.. related:: + + qsim_beyond_classical Beyond classical computing with qsim + +*Author: Olivia Di Matteo — Posted: 15 December 2020. Last updated: 15 April 2021.* + +.. warning:: + The data in this demo was originally generated using system calibration data from the ``ibmq_lima`` device, + which has since been retired. While the code now uses a fake version of the Lima device, the noise and calibration + may have shifted since the data was generated. + +Twice per year, a project called the TOP500 [#top500]_ releases a list of the +500 most powerful supercomputing systems in the world. However, there is a large +amount of variation in how supercomputers are built. They may run different +operating systems and have varying amounts of memory. `Some +`_ use 48-core processors, +while `others `_ use processors +with up to 260 cores. The speed of processors will differ, and they may be +connected in different ways. We can't rank them by simply counting the number of +processors! + +In order to make a fair comparison, we need benchmarking standards that give us +a holistic view of their performance. To that end, the TOP500 rankings are based +on something called the LINPACK benchmark [#linpack]_. The task of the +supercomputers is to solve a dense system of linear equations, and the metric of +interest is the rate at which they perform `floating-point operations (FLOPS) +`__. Today's top machines reach speeds well +into the regime of hundreds of petaFLOPS! While a single number certainly +cannot tell the whole story, it still gives us insight into the quality of the +machines, and provides a standard so we can compare them. + +A similar problem is emerging with quantum computers: we can't judge quantum +computers on the number of qubits alone. Present-day devices have a number of +limitations, an important one being gate error rates. Typically +the qubits on a chip are not all connected to each other, so it may not be +possible to perform operations on arbitrary pairs of them. + +Considering this, can we tell if a machine with 20 noisy qubits is better +than one with 5 very high-quality qubits? Or if a machine with 8 fully-connected +qubits is better than one with 16 qubits of comparable error rate, but arranged in +a square lattice? How can we make comparisons between different +types of qubits? + +.. figure:: ../_static/demonstration_assets/quantum_volume/qubit_graph_variety.svg + :align: center + :width: 50% + + .. + + Which of these qubit hardware graphs is the best? + +To compare across all these facets, researchers have proposed a metric called +"quantum volume" [#cross]_. Roughly, the quantum volume is a measure of the +effective number of qubits a processor has. It is calculated by determining the +largest number of qubits on which it can reliably run circuits of a prescribed +type. You can think of it loosely as a quantum analogue of the LINPACK +benchmark. Different quantum computers are tasked with solving the same problem, +and the success will be a function of many properties: error rates, qubit +connectivity, even the quality of the software stack. A single +number won't tell us everything about a quantum computer, but it does establish +a framework for comparing them. + +After working through this tutorial, you'll be able to define quantum volume, +explain the problem on which it's based, and run the protocol to compute it! + +""" + + +############################################################################## +# +# Designing a benchmark for quantum computers +# ------------------------------------------- +# +# There are many different properties of a quantum computer +# that contribute to the successful execution of a computation. Therefore, we +# must be very explicit about what exactly we are benchmarking, and what is our +# measure of success. In general, to set up a benchmark for a quantum computer +# we need to decide on a number of things [#robin]_: +# +# 1. A family of circuits with a well-defined structure and variable size +# 2. A set of rules detailing how the circuits can be compiled +# 3. A measure of success for individual circuits +# 4. A measure of success for the family of circuits +# 5. (Optional) An experimental design specifying how the circuits are to be run +# +# We'll work through this list in order to see how the protocol for computing +# quantum volume fits within this framework. +# +# The circuits +# ~~~~~~~~~~~~ +# +# Quantum volume relates +# to the largest *square* circuit that a quantum processor can run reliably. This benchmark +# uses *random* square circuits with a very particular form: +# +# .. figure:: ../_static/demonstration_assets/quantum_volume/model_circuit_cross.png +# :align: center +# :width: 60% +# +# .. +# +# A schematic of the random circuit structure used in the quantum volume protocol. +# Image source: [#cross]_. +# +# Specifically, the circuits consist of :math:`d` sequential layers acting on +# :math:`d` qubits. Each layer consists of two parts: a random permutation of +# the qubits, followed by Haar-random SU(4) operations performed on neighbouring +# pairs of qubits. (When the number of qubits is odd, the bottom-most qubit is +# idle while the SU(4) operations run on the pairs. However, it will still be +# incorporated by way of the permutations.) These circuits satisfy the criteria +# in item 1 — they have well-defined structure, and it is clear how they can be +# scaled to different sizes. +# +# As for the compilation rules of item 2, to compute quantum volume we're +# allowed to do essentially anything we'd like to the circuits in order to +# improve them. This includes optimization, hardware-aware considerations such +# as qubit placement and routing, and even resynthesis by finding unitaries that +# are close to the target, but easier to implement on the hardware [#cross]_. +# +# Both the circuit structure and the compilation highlight how quantum volume is +# about more than just the number of qubits. The error rates will affect the +# achievable depth, and the qubit connectivity contributes through the layers of +# permutations because a very well-connected processor will be able to implement +# these in fewer steps than a less-connected one. Even the quality of the +# software and the compiler plays a role here: higher-quality compilers will +# produce circuits that fit better on the target devices, and will thus produce +# higher quality results. +# +# The measures of success +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now that we have our circuits, we have to define the quantities that will +# indicate how well we're able to run them. For that, we need a problem +# to solve. The problem used for computing quantum volume is called the *heavy output +# generation problem*. It has roots in the proposals for demonstrating quantum +# advantage [#aaronson]_. Many such proposals make use of the properties of +# various random quantum circuit families, as the distribution of the +# measurement outcomes may not be easy to sample using classical +# techniques. +# +# A distribution that is theorized to fulfill this property is the distribution +# of *heavy* output bit strings. Heavy bit strings are those whose outcome +# probabilities are above the median of the distribution. For example, suppose +# we run a two-qubit circuit and find that the measurement probabilities for +# the output states are as follows: + +measurement_probs = {"00": 0.558, "01": 0.182, "10": 0.234, "11": 0.026} + +############################################################################## +# +# The median of this probability distribution is: + +import numpy as np +prob_array = np.fromiter(measurement_probs.values(), dtype=np.float64) +print(f"Median = {np.median(prob_array):.3f}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Median = 0.208 +# + +############################################################################## +# +# This means that the heavy bit strings are '00' and '10', because these are +# the two probabilities above the median. If we were to run this circuit, the +# probability of obtaining one of the heavy outputs is: + +heavy_output_prob = np.sum(prob_array[prob_array > np.median(prob_array)]) +print(f"Heavy output probability = {heavy_output_prob}") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Heavy output probability = 0.792 +# + +############################################################################## +# +# Each circuit in a circuit family has its own heavy output probability. If our +# quantum computer is of high quality, then we should expect to see heavy +# outputs quite often across all the circuits. On the other hand, if it's of +# poor quality and everything is totally decohered, we will end up with output +# probabilities that are roughly all the same, as noise will reduce the +# probabilities to the uniform distribution. +# +# The heavy output generation problem quantifies this — for our family of +# random circuits, do we obtain heavy outputs at least 2/3 of the time on +# average? Furthermore, do we obtain this with high confidence? This is the +# basis for quantum volume. Looking back at the criteria for our benchmarks, for +# item 3 the measure of success for each circuit is how often we obtain heavy +# outputs when we run the circuit and take a measurement. For item 4 the +# measure of success for the whole family is whether or not the mean of these +# probabilities is greater than 2/3 with high confidence. +# +# On a related note, it is important to determine what heavy output probability +# we should *expect* to see on average. The intuition for how this can be +# calculated is as follows [#aaronson]_, [#cmu]_. Suppose that our random +# square circuits scramble things up enough so that the effective operation +# looks like a Haar-random unitary :math:`U.` Since in the circuits we are +# applying :math:`U` to the all-zero ket, the measurement outcome probabilities +# will be the moduli squared of the entries in the first column of :math:`U.` +# +# Now if :math:`U` is Haar-random, we can say something about the form of these +# entries. In particular, they are complex numbers for which both the real and +# imaginary parts are normally distributed with mean 0 and variance +# :math:`1/2^m,` where :math:`m` is the number of qubits. Taking the modulus +# squared of such numbers and making a histogram yields a distribution +# of probabilities with the form :math:`\hbox{Pr}(p) \sim 2^m e^{-2^m p}.` This +# is also known as the *Porter-Thomas distribution*. +# +# By looking at the form of the underlying probability distribution, the +# exponential distribution :math:`\hbox{Pr}(x) = e^{-x},` we can calculate some +# properties of the heavy output probabilities. First, we can integrate the exponential +# distribution to find that the median sits at :math:`\ln 2.` We can further +# compute the expectation value of obtaining something greater than the median +# by integrating :math:`x e^{-x}` from :math:`\ln 2` to :math:`\infty` to obtain +# :math:`(1 + \ln 2)/2.` This is the expected heavy output probability! +# Numerically it is around 0.85, as we will observe later in our results. +# +# +# The benchmark +# ~~~~~~~~~~~~~ +# +# Now that we have our circuits and our measures of success, we're ready to +# define the quantum volume. +# +# +# .. admonition:: Definition +# :class: defn +# +# The quantum volume :math:`V_Q` of an :math:`n`-qubit processor is defined as [#cross]_ +# +# .. math:: +# \log_2(V_Q) = \hbox{argmax}_m \min (m, d(m)) +# +# where :math:`m \leq n` is a number of qubits, and :math:`d(m)` is the number of +# qubits in the largest square circuits for which we can reliably sample +# heavy outputs with probability greater than 2/3. +# +# To see this more concretely, suppose we have a 20-qubit device and find that +# we get heavy outputs reliably for up to depth-4 circuits on any set of 4 +# qubits, then the quantum volume is :math:`\log_2 V_Q = 4.` Quantum volume is +# incremental, as shown below — we gradually work our way up to larger +# circuits, until we find something we can't do. Very loosely, quantum volume +# is like an effective number of qubits. Even if we have those 20 qubits, only +# groups of up to 4 of them work well enough together to sample from +# distributions that would be considered hard. +# +# .. figure:: ../_static/demonstration_assets/quantum_volume/qv_square_circuits.svg +# :align: center +# :width: 75% +# +# .. +# +# This quantum computer has :math:`\log_2 V_Q = 4,` as the 4-qubit square +# circuits are the largest ones it can run successfully. +# +# +# The maximum achieved quantum volume has been doubling at an increasing rate. In +# late 2020, the most recent announcements have been :math:`\log_2 V_Q = 6` on +# IBM's 27-qubit superconducting device `ibmq_montreal` [#qv64]_, and +# :math:`\log_2 V_Q = 7` on a Honeywell trapped-ion qubit processor +# [#honeywell]_. A device with an expected quantum volume of :math:`\log_2 V_Q +# = 22` has also been announced by IonQ [#ionq]_, though benchmarking results +# have not yet been published. +# +# .. note:: +# +# In many sources, the quantum volume of processors is reported as +# :math:`V_Q` explicitly, rather than :math:`\log_2 V_Q` as is the +# convention in this demo. As such, IonQ's processor has the potential for a +# quantum volume of :math:`2^{22} > 4000000.` Here we use the :math:`\log` +# because it is more straightforward to understand that they have 22 +# high-quality, well-connected qubits than to extract this at first glance from the +# explicit value of the volume. +# + + +############################################################################## +# +# Computing the quantum volume +# ---------------------------- +# +# Equipped with our definition of quantum volume, it's time to compute it +# ourselves! We'll use the `PennyLane-Qiskit +# `_ plugin to compute the +# volume of a simulated version of one of the IBM processors, since their properties are easily +# accessible through this plugin. +# +# +# Loosely, the protocol for quantum volume consists of three steps: +# +# 1. Construct random square circuits of increasing size +# +# 2. Run those circuits on both a simulator and on a noisy hardware device +# +# 3. Perform a statistical analysis of the results to determine what size +# circuits the device can run reliably +# +# +# The largest reliable size will become the :math:`m` in the expression for +# quantum volume. +# +# +# Step 1: construct random square circuits +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Recall that the structure of the circuits above is alternating layers of +# permutations and random SU(4) operations on pairs of qubits. Let's implement +# the generation of such circuits in PennyLane. +# +# First we write a function that randomly permutes qubits. We'll do this by +# using numpy to generate a permutation, and then apply it with the built-in +# :func:`~.pennylane.Permute` subroutine. + +import pennylane as qml + +# Object for random number generation from numpy +rng = np.random.default_rng() + +def permute_qubits(num_qubits): + # A random permutation + perm_order = list(rng.permutation(num_qubits)) + qml.Permute(perm_order, wires=list(range(num_qubits))) + + +############################################################################## +# +# Next, we need to apply SU(4) gates to pairs of qubits. PennyLane doesn't have +# built-in functionality to generate these random matrices, however its cousin +# `Strawberry Fields `_ does! We will use the +# ``random_interferometer`` method, which can generate unitary matrices uniformly +# at random. This function actually generates elements of U(4), but they are +# essentially equivalent up to a global phase. + +from strawberryfields.utils import random_interferometer + +def apply_random_su4_layer(num_qubits): + for qubit_idx in range(0, num_qubits, 2): + if qubit_idx < num_qubits - 1: + rand_haar_su4 = random_interferometer(N=4) + qml.QubitUnitary(rand_haar_su4, wires=[qubit_idx, qubit_idx + 1]) + + +############################################################################## +# +# Next, let's write a layering method to put the two together — this is just +# for convenience and to highlight the fact that these two methods together +# make up one layer of the circuit depth. +# + + +def qv_circuit_layer(num_qubits): + permute_qubits(num_qubits) + apply_random_su4_layer(num_qubits) + + +############################################################################## +# +# Let's take a look! We'll set up an ideal device with 5 qubits, and generate a +# circuit with 3 qubits. In this demo, we'll work explicitly with `quantum tapes +# `__ since they +# are not immediately tied to a device. This will be convenient later when we +# need to run the same random circuit on two devices independently. + +num_qubits = 5 +dev_ideal = qml.device("lightning.qubit", shots=None, wires=num_qubits) + +m = 3 # number of qubits + +with qml.tape.QuantumTape() as tape: + qml.layer(qv_circuit_layer, m, num_qubits=m) + +expanded_tape = tape.expand(stop_at=lambda op: isinstance(op, qml.QubitUnitary)) +print(qml.drawer.tape_text(expanded_tape, wire_order=dev_ideal.wires, show_all_wires=True, show_matrices=True)) + + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# 0: ─╭SWAP───────╭U(M0)─╭SWAP───────╭U(M1)─╭U(M2)─┤ +# 1: ─│─────╭SWAP─╰U(M0)─╰SWAP─╭SWAP─╰U(M1)─╰U(M2)─┤ +# 2: ─╰SWAP─╰SWAP──────────────╰SWAP───────────────┤ +# 3: ──────────────────────────────────────────────┤ +# 4: ──────────────────────────────────────────────┤ +# M0 = +# [[ 0.22234537+0.12795769j 0.24613682-0.34470179j 0.58179809-0.36478045j +# -0.16337007-0.50650086j] +# [-0.08840637-0.42456216j -0.01961572+0.35189839j 0.4214659 +0.31514336j +# -0.63733039+0.06764003j] +# [ 0.28919627-0.23577761j -0.11249786-0.67687982j 0.22914826+0.37205064j +# 0.12755164+0.42749545j] +# [ 0.59999195+0.49689511j -0.29294024+0.37382355j 0.23724315-0.06544043j +# -0.039832 +0.3246437j ]] +# M1 = +# [[ 0.11583153-0.3628563j 0.55797708+0.48315028j -0.22400838-0.264741j +# -0.34856401+0.26149824j] +# [-0.04549494-0.25884483j 0.00258749-0.20351027j -0.26326583-0.70408962j +# 0.33442905-0.46109931j] +# [-0.46824254-0.14274112j -0.00491681+0.61278881j -0.02506472+0.26582603j +# 0.54135395-0.14312156j] +# [ 0.73672445-0.05881259j 0.19534118+0.01057264j -0.29145879+0.398047j +# 0.33955583-0.23837031j]] +# M2 = +# [[-0.33352575+0.21982221j -0.29128941-0.51347253j 0.63543764-0.11913356j +# 0.27186717+0.00704727j] +# [-0.22330473+0.02289549j 0.1997405 -0.47316218j -0.23040621-0.14078015j +# -0.47922028-0.61909121j] +# [-0.00705247+0.82724695j 0.52220719+0.02527864j -0.05490671-0.04899343j +# 0.03167901+0.18935341j] +# [ 0.23396138-0.22566431j 0.32400589+0.09694607j 0.54666955-0.45261179j +# -0.48177768+0.2101061j ]] + + +############################################################################## +# +# The first thing to note is that the last two qubits are never used in the +# operations, since the quantum volume circuits are square. Another important +# point is that this circuit with 3 layers actually has depth much greater than +# 3, since each layer has both SWAPs and SU(4) operations that are further +# decomposed into elementary gates when run on the actual processor. +# +# One last thing we'll need before running our circuits is the machinery to +# determine the heavy outputs. This is quite an interesting aspect of the +# protocol — we're required to compute the heavy outputs classically in order +# to get the results! As a consequence, it will only be possible to calculate +# quantum volume for processors up to a certain point before they become too +# large. +# +# That said, classical simulators are always improving, and can simulate +# circuits with numbers of qubits well into the double digits (though they may +# need a supercomputer to do so). Furthermore, the designers of the protocol +# don't expect this to be an issue until gate error rates decrease below +# :math:`\approx 10^{-4},` after which we may need to make adjustments to remove +# the classical simulation, or even consider new volume metrics [#cross]_. +# +# The heavy outputs can be retrieved from a classically-obtained probability +# distribution as follows: +# + +def heavy_output_set(m, probs): + # Compute heavy outputs of an m-qubit circuit with measurement outcome + # probabilities given by probs, which is an array with the probabilities + # ordered as '000', '001', ... '111'. + + # Sort the probabilities so that those above the median are in the second half + probs_ascending_order = np.argsort(probs) + sorted_probs = probs[probs_ascending_order] + + # Heavy outputs are the bit strings above the median + heavy_outputs = [ + # Convert integer indices to m-bit binary strings + format(x, f"#0{m+2}b")[2:] for x in list(probs_ascending_order[2 ** (m - 1) :]) + ] + + # Probability of a heavy output + prob_heavy_output = np.sum(sorted_probs[2 ** (m - 1) :]) + + return heavy_outputs, prob_heavy_output + + +############################################################################## +# +# As an example, let's compute the heavy outputs and probability for our circuit +# above. +# + +# Adds a measurement of the first m qubits to the previous circuit +with tape: + qml.probs(wires=range(m)) + +# Run the circuit, compute heavy outputs, and print results +output_probs = qml.execute([tape], dev_ideal, None) # returns a list of result ! +output_probs = output_probs[0].reshape(2 ** m, ) +heavy_outputs, prob_heavy_output = heavy_output_set(m, output_probs) + +print("State\tProbability") +for idx, prob in enumerate(output_probs): + bit_string = format(idx, f"#05b")[2:] + print(f"{bit_string}\t{prob:.4f}") + +print(f"\nMedian is {np.median(output_probs):.4f}") +print(f"Probability of a heavy output is {prob_heavy_output:.4f}") +print(f"Heavy outputs are {heavy_outputs}") + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# State Probability +# 000 0.0559 +# 001 0.3687 +# 010 0.0326 +# 011 0.0179 +# 100 0.0550 +# 101 0.3590 +# 110 0.1103 +# 111 0.0005 +# +# Median is 0.0554 +# Probability of a heavy output is 0.8939 +# Heavy outputs are ['000', '110', '101', '001'] +# + +############################################################################## +# +# Step 2: run the circuits +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now it's time to run the protocol. First, let's set up our hardware +# device. We'll use a simulated version of the 5-qubit IBM Lima as an example +# — the reported quantum volume according to IBM is :math:`V_Q=8,` so we +# endeavour to reproduce that here. This means that we should be able to run our +# square circuits reliably on up to :math:`\log_2 V_Q =3` qubits. +# +# .. note:: +# +# In order to access the IBM Q backend, users must have an IBM Q account +# configured. This can be done by running: +# +# .. code-block:: python3 +# +# from qiskit_ibm_provider import IBMProvider +# IBMProvider.save_account('MY_API_TOKEN') +# +# A token can be generated by logging into your IBM Q account `here `_ . +# +# .. note:: +# +# Users can get a list of available IBM Q backends by importing IBM Q, +# specifying their provider and then calling: ``provider.backends()`` +# + + +############################################################################## +# +# First, we can take a look at the arrangement of the qubits on the processor +# by plotting its hardware graph. + +from rustworkx.visualization import mpl_draw +from qiskit_ibm_runtime.fake_provider import FakeLimaV2 + +mpl_draw(FakeLimaV2().coupling_map.graph) + + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/quantum_volume/lima.svg +# :align: center +# :width: 75% +# + +############################################################################## +# +# This hardware graph is not fully connected, so the quantum compiler will have +# to make some adjustments when non-connected qubits need to interact. +# +# To actually perform the simulations, we'll need to access a copy of the +# Lima noise model. Again, we won't be running on Lima directly --- +# we'll set up a local device to simulate its behaviour. +# + +dev_noisy = qml.device("qiskit.remote", wires=5, shots=1000, backend=FakeLimaV2()) + +############################################################################## +# +# As a final point, since we are allowed to do as much optimization as we like, +# let's put the compiler to work. The compiler will perform a number of +# optimizations to simplify our circuit. We'll also specify some high-quality +# qubit placement and routing techniques [#sabre]_ in order to fit the circuits +# on the hardware graph in the best way possible. + +dev_noisy.set_transpile_args( + **{ + "optimization_level": 3, + "coupling_map": FakeLimaV2().coupling_map, + "layout_method": "sabre", + "routing_method": "sabre", + } +) + + +############################################################################## +# +# Let's run the protocol. We'll start with the smallest circuits on 2 +# qubits, and make our way up to 5. At each :math:`m,` we'll look at 200 randomly +# generated circuits. +# + +min_m = 2 +max_m = 5 +num_ms = (max_m - min_m) + 1 + +num_trials = 200 + +# To store the results +probs_ideal = np.zeros((num_ms, num_trials)) +probs_noisy = np.zeros((num_ms, num_trials)) + +for m in range(min_m, max_m + 1): + for trial in range(num_trials): + + # Simulate the circuit analytically + with qml.tape.QuantumTape() as tape: + qml.layer(qv_circuit_layer, m, num_qubits=m) + qml.probs(wires=range(m)) + + output_probs = qml.execute([tape], dev_ideal, None) + output_probs = output_probs[0].reshape(2 ** m, ) + heavy_outputs, prob_heavy_output = heavy_output_set(m, output_probs) + + # Execute circuit on the noisy device + qml.execute([tape], dev_noisy, None) + + # Get the output bit strings; flip ordering of qubits to match PennyLane + counts = dev_noisy._current_job.result().get_counts() + reordered_counts = {x[::-1]: counts[x] for x in counts.keys()} + + device_heavy_outputs = np.sum( + [ + reordered_counts[x] if x[:m] in heavy_outputs else 0 + for x in reordered_counts.keys() + ] + ) + fraction_device_heavy_output = device_heavy_outputs / dev_noisy.shots + + probs_ideal[m - min_m, trial] = prob_heavy_output + probs_noisy[m - min_m, trial] = fraction_device_heavy_output + +############################################################################## +# +# Step 3: perform a statistical analysis +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Having run our experiments, we can now get to the heart of the quantum volume +# protocol: what *is* the largest square circuit that our processor can run? +# Let's first check out the means and see how much higher they are than 2/3. +# + +probs_mean_ideal = np.mean(probs_ideal, axis=1) +probs_mean_noisy = np.mean(probs_noisy, axis=1) + +print(f"Ideal mean probabilities:") +for idx, prob in enumerate(probs_mean_ideal): + print(f"m = {idx + min_m}: {prob:.6f} {'above' if prob > 2/3 else 'below'} threshold.") + +print(f"\nDevice mean probabilities:") +for idx, prob in enumerate(probs_mean_noisy): + print(f"m = {idx + min_m}: {prob:.6f} {'above' if prob > 2/3 else 'below'} threshold.") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Ideal mean probabilities: +# m = 2: 0.801480 above threshold. +# m = 3: 0.853320 above threshold. +# m = 4: 0.832995 above threshold. +# m = 5: 0.858370 above threshold. +# +# Device mean probabilities: +# m = 2: 0.765920 above threshold. +# m = 3: 0.773985 above threshold. +# m = 4: 0.674380 above threshold. +# m = 5: 0.639500 below threshold. + +############################################################################## +# +# We see that the ideal probabilities are well over 2/3. In fact, they're quite +# close to the expected value of :math:`(1 + \ln 2)/2,` which we recall from +# above is :math:`\approx 0.85.` For this experiment, we see that the device +# probabilities are also above the threshold (except one). But it isn't enough +# that just the mean of the heavy output probabilities is greater than 2/3. Since we're +# dealing with randomness, we also want to ensure these results were not just a +# fluke! To be confident, we also want to be above 2/3 within 2 standard +# deviations :math:`(\sigma)` of the mean. This is referred to as a 97.5% +# confidence interval (since roughly 97.5% of a normal distribution sits within +# :math:`2\sigma` of the mean.) +# +# At this point, we're going to do some statistical sorcery and make some +# assumptions about our distributions. Whether or not a circuit is successful +# (in the sense that it produces heavy outputs more the 2/3 of the time) is a +# binary outcome. When we sample many circuits, it is almost like we are +# sampling from a *binomial distribution* where the outcome probability is +# equivalent to the heavy output probability. In the limit of a large number of +# samples (in this case 200 circuits), a binomial distribution starts to look +# like a normal distribution. If we make this approximation, we can compute the standard +# deviation and use it to make our confidence interval. With the normal +# approximation, the standard deviation is +# +# .. math:: +# +# \sigma = \sqrt{\frac{p_h(1 - p_h)}{N}}, +# +# where :math:`p_h` is the average heavy output probability, and :math:`N` is +# the number of circuits. +# + +stds_ideal = np.sqrt(probs_mean_ideal * (1 - probs_mean_ideal) / num_trials) +stds_noisy = np.sqrt(probs_mean_noisy * (1 - probs_mean_noisy) / num_trials) + +############################################################################## +# +# Now that we have our standard deviations, let's see if our means are at least +# :math:`2\sigma` away from the threshold! +# + +from matplotlib import pyplot as plt +fig, ax = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(9, 6)) +ax = ax.ravel() + +for m in range(min_m - 2, max_m + 1 - 2): + ax[m].hist(probs_noisy[m, :]) + ax[m].set_title(f"m = {m + min_m}", fontsize=16) + ax[m].set_xlabel("Heavy output probability", fontsize=14) + ax[m].set_ylabel("Occurrences", fontsize=14) + ax[m].axvline(x=2.0 / 3, color="black", label="2/3") + ax[m].axvline(x=probs_mean_noisy[m], color="red", label="Mean") + ax[m].axvline( + x=(probs_mean_noisy[m] - 2 * stds_noisy[m]), + color="red", + linestyle="dashed", + label="2σ", + ) + +fig.suptitle("Heavy output distributions for (simulated) Lima QPU", fontsize=18) +plt.legend(fontsize=14) +plt.tight_layout() +plt.show() + + +############################################################################## +# +# .. figure:: ../_static/demonstration_assets/quantum_volume/lima_heavy_output_distributions.svg +# :align: center +# :width: 90% +# + + +############################################################################## +# +# Let's verify this numerically: +# + +two_sigma_below = probs_mean_noisy - 2 * stds_noisy + +for idx, prob in enumerate(two_sigma_below): + print(f"m = {idx + min_m}: {prob:.6f} {'above' if prob > 2/3 else 'below'} threshold.") + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# m = 2: 0.706039 above threshold. +# m = 3: 0.714836 above threshold. +# m = 4: 0.608109 below threshold. +# m = 5: 0.571597 below threshold. +# + +############################################################################## +# +# We see that we are :math:`2\sigma` above the threshold only for :math:`m=2,` +# and :math:`m=3.` Thus, we find that the quantum volume of our simulated Lima is +# :math:`\log_2 V_Q = 3`, or :math:`V_Q = 8,` as expected. +# +# This framework and code will allow you to calculate the quantum volume of many +# different processors. Try it yourself! What happens if we don't specify a +# large amount of compiler optimization? How does the volume compare across +# different hardware devices? You can even build your own device configurations +# and noise models to explore the extent to which different factors affect the +# volume. +# +# Concluding thoughts +# ------------------- +# +# Quantum volume is a metric used for comparing the quality of different quantum +# computers. By determining the largest square random circuits a processor can +# run reliably, it provides a measure of the effective number of qubits a +# processor has. Furthermore, it goes beyond just gauging quality by a number of +# qubits — it incorporates many different aspects of a device such as its +# compiler, qubit connectivity, and gate error rates. +# +# However, as with any benchmark, it is not without limitations. A key one +# already discussed is that the heavy output generation problem requires us to +# simulate circuits classically in addition to running them on a device. While +# this is perhaps not an issue now, it will surely become one in the future. The +# number of qubits continues to increase and error rates are getting lower, +# both of which imply that our square circuits will be growing in both width and +# depth as time goes on. Eventually they will reach a point where they are no +# longer classical simulable, and we will have to design new benchmarks. +# +# Another limitation is that the protocol only looks at one type of circuit, +# i.e., square circuits. It might be the case that a processor has very few +# qubits, but also very low error rates. For example, what if a processor with 5 +# qubits can run circuits with up to 20 layers? Quantum volume would limit us to +# :math:`\log_2 V_Q = 5` and the high quality of those qubits is not reflected +# in this. To that end, a more general *volumetric benchmark* framework was +# proposed that includes not only square circuits, but also rectangular circuits +# [#robin]_. Investigating very deep circuits on few qubits (and very shallow +# circuits on many qubits) will give us a broader overview of a processor's +# quality. Furthermore, the flexibility of the framework of [#robin]_ will +# surely inspire us to create new types of benchmarks. Having a variety of +# benchmarks calculated in different ways is beneficial and gives us a broader +# view of the performance of quantum computers. +# +# +# .. _quantum_volume_references: +# +# References +# ---------- +# +# .. [#top500] +# +# ``__ +# +# .. [#linpack] +# +# ``__ +# +# .. [#cross] +# +# Cross, A. W., Bishop, L. S., Sheldon, S., Nation, P. D., & Gambetta, J. M., +# Validating quantum computers using randomized model circuits, `Physical +# Review A, 100(3), (2019). `__ +# +# .. [#robin] +# +# Blume-Kohout, R., & Young, K. C., A volumetric framework for quantum +# computer benchmarks, `Quantum, 4, 362 (2020). +# `__ +# +# .. [#aaronson] +# +# Aaronson, S., & Chen, L., Complexity-theoretic foundations of quantum supremacy experiments. +# `arXiv 1612.05903 quant-ph `__ +# +# .. [#cmu] +# +# O'Donnell, R. CMU course: Quantum Computation and Quantum Information 2018. +# `Lecture 25 `__ +# +# .. [#qv64] +# +# Jurcevic et al. Demonstration of quantum volume 64 on a superconducting quantum computing system. +# `arXiv 2008.08571 quant-ph `__ +# +# .. [#honeywell] +# +# ``__ +# +# .. [#ionq] +# +# ``__ +# +# .. [#sabre] +# +# Li, G., Ding, Y., & Xie, Y., Tackling the qubit mapping problem for +# nisq-era quantum devices, `In Proceedings of the Twenty-Fourth +# International Conference on Architectural Support for Programming Languages +# and Operating Systems (pp. 1001–1014) +# (2019). `__ New York, NY, +# USA: Association for Computing Machinery. diff --git a/demonstrations_v2/quantum_volume/metadata.json b/demonstrations_v2/quantum_volume/metadata.json new file mode 100644 index 0000000000..fbe4d73114 --- /dev/null +++ b/demonstrations_v2/quantum_volume/metadata.json @@ -0,0 +1,111 @@ +{ + "title": "Quantum volume", + "authors": [ + { + "username": "glassnotes" + } + ], + "dateOfPublication": "2020-12-15T00:00:00+00:00", + "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_volume.png" + } + ], + "seoDescription": "Learn about quantum volume, and how to compute it.", + "doi": "", + "references": [ + { + "id": "top500", + "type": "website", + "title": "Top500", + "url": "https://www.top500.org/" + }, + { + "id": "linpack", + "type": "webpage", + "title": "The Linpack Benchmark", + "url": "https://www.top500.org/project/linpack/" + }, + { + "id": "cross", + "type": "article", + "title": "Validating quantum computers using randomized model circuits", + "authors": "Cross, A. W., Bishop, L. S., Sheldon, S., Nation, P. D., & Gambetta, J. M.", + "year": "2019", + "journal": "Physical Review A", + "doi": "10.1103/physreva.100.032328" + }, + { + "id": "robin", + "type": "article", + "title": "A volumetric framework for quantum computer benchmarks", + "authors": "Blume-Kohout, R., & Young, K. C.", + "year": "2020", + "journal": "Quantum", + "doi": "10.22331/q-2020-11-15-362" + }, + { + "id": "aaronson", + "type": "article", + "title": "Complexity-theoretic foundations of quantum supremacy experiments.", + "authors": "Aaronson, S., & Chen, L.", + "doi": "10.48550/arXiv.1612.05903", + "url": "https://arxiv.org/abs/1612.05903" + }, + { + "id": "cmu", + "type": "other", + "title": "CMU course: Quantum Computation and Quantum Information 2018.", + "authors": "O'Donnell, R.", + "url": "https://www.cs.cmu.edu/~odonnell/quantum18/lecture25.pdf" + }, + { + "id": "qv64", + "type": "article", + "title": "Demonstration of quantum volume 64 on a superconducting quantum computing system.", + "authors": "Jurcevic et al.", + "doi": "10.48550/arXiv.2008.08571", + "url": "https://arxiv.org/abs/2008.08571" + }, + { + "id": "honeywell", + "type": "article", + "title": "Achieving Quantum Volume 128 On The Honeywell Quantum Computer", + "year": "2020", + "url": "https://www.honeywell.com/en-us/newsroom/news/2020/09/achieving-quantum-volume-128-on-the-honeywell-quantum-computer" + }, + { + "id": "ionq", + "type": "article", + "title": "IonQ Unveils World's Most Powerful Quantum Computer", + "year": "2020", + "url": "https://www.prnewswire.com/news-releases/ionq-unveils-worlds-most-powerful-quantum-computer-301143782.html" + }, + { + "id": "sabre", + "type": "article", + "title": "Tackling the qubit mapping problem for nisq-era quantum devices", + "authors": "Li, G., Ding, Y., & Xie, Y.", + "year": "2019", + "proceedings": "In Proceedings of the Twenty-Fourth International Conference on Architectural Support for Programming Languages and Operating Systems", + "pages": "1001\u20131014", + "url": "https://dl.acm.org/doi/10.1145/3297858.3304023" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "qsim_beyond_classical", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/quantum_volume/requirements.in b/demonstrations_v2/quantum_volume/requirements.in new file mode 100644 index 0000000000..1fae978d3a --- /dev/null +++ b/demonstrations_v2/quantum_volume/requirements.in @@ -0,0 +1,6 @@ +matplotlib +numpy +pennylane +qiskit_ibm_runtime +rustworkx +strawberryfields diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py new file mode 100644 index 0000000000..975e5dd03a --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py @@ -0,0 +1,222 @@ +r"""How to optimize a QML model using JAX and JAXopt +===================================================================== + +Once you have set up a quantum machine learning model, data to train with, and +cost function to minimize as an objective, the next step is to **perform the optimization**. That is, +setting up a classical optimization loop to find a minimal value of your cost function. + +In this example, we’ll show you how to use `JAX `__, an +autodifferentiable machine learning framework, and `JAXopt `__, a suite +of JAX-compatible gradient-based optimizers, to optimize a PennyLane quantum machine learning model. + +.. figure:: ../_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_JAXopt/socialthumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png + :align: center + :width: 50% + +""" + +###################################################################### +# Set up your model, data, and cost +# --------------------------------- +# + +###################################################################### +# Here, we will create a simple QML model for our optimization. In particular: +# +# - We will embed our data through a series of rotation gates. +# - We will then have an ansatz of trainable rotation gates with parameters ``weights``; it is these +# values we will train to minimize our cost function. +# - We will train the QML model on ``data``, a ``(5, 4)`` array, and optimize the model to match +# target predictions given by ``target``. +# + +import pennylane as qml +import jax +from jax import numpy as jnp +import jaxopt + +jax.config.update("jax_platform_name", "cpu") + +n_wires = 5 +data = jnp.sin(jnp.mgrid[-2:2:0.2].reshape(n_wires, -1)) ** 3 +targets = jnp.array([-0.2, 0.4, 0.35, 0.2]) + +dev = qml.device("default.qubit", wires=n_wires) + +@qml.qnode(dev) +def circuit(data, weights): + """Quantum circuit ansatz""" + + # data embedding + for i in range(n_wires): + # data[i] will be of shape (4,); we are + # taking advantage of operation vectorization here + qml.RY(data[i], wires=i) + + # trainable ansatz + for i in range(n_wires): + qml.RX(weights[i, 0], wires=i) + qml.RY(weights[i, 1], wires=i) + qml.RX(weights[i, 2], wires=i) + qml.CNOT(wires=[i, (i + 1) % n_wires]) + + # we use a sum of local Z's as an observable since a + # local Z would only be affected by params on that qubit. + return qml.expval(qml.sum(*[qml.PauliZ(i) for i in range(n_wires)])) + +def my_model(data, weights, bias): + return circuit(data, weights) + bias + +###################################################################### +# We will define a simple cost function that computes the overlap between model output and target +# data, and `just-in-time (JIT) compile `__ it: +# + +@jax.jit +def loss_fn(params, data, targets): + predictions = my_model(data, params["weights"], params["bias"]) + loss = jnp.sum((targets - predictions) ** 2 / len(data)) + return loss + +###################################################################### +# Note that the model above is just an example for demonstration – there are important considerations +# that must be taken into account when performing QML research, including methods for data embedding, +# circuit architecture, and cost function, in order to build models that may have use. This is still +# an active area of research; see our `demonstrations `__ for +# details. +# + +###################################################################### +# Initialize your parameters +# -------------------------- +# + +###################################################################### +# Now, we can generate our trainable parameters ``weights`` and ``bias`` that will be used to train +# our QML model. +# + +weights = jnp.ones([n_wires, 3]) +bias = jnp.array(0.) +params = {"weights": weights, "bias": bias} + +###################################################################### +# Plugging the trainable parameters, data, and target labels into our cost function, we can see the +# current loss as well as the parameter gradients: +# + +print(loss_fn(params, data, targets)) + +print(jax.grad(loss_fn)(params, data, targets)) + +###################################################################### +# Create the optimizer +# -------------------- +# + +###################################################################### +# We can now use JAXopt to create a gradient descent optimizer, and train our circuit. +# +# To do so, we first create a function that returns the loss value *and* the gradient value during +# training; this allows us to track and print out the loss during training within JAXopt’s internal +# optimization loop. +# + +def loss_and_grad(params, data, targets, print_training, i): + loss_val, grad_val = jax.value_and_grad(loss_fn)(params, data, targets) + + def print_fn(): + jax.debug.print("Step: {i} Loss: {loss_val}", i=i, loss_val=loss_val) + + # if print_training=True, print the loss every 5 steps + jax.lax.cond((jnp.mod(i, 5) == 0) & print_training, print_fn, lambda: None) + + return loss_val, grad_val + +###################################################################### +# Note that we use a couple of JAX specific functions here: +# +# - ``jax.lax.cond`` instead of a Python ``if`` statement +# - ``jax.debug.print`` instead of a Python ``print`` function +# +# These JAX compatible functions are needed because JAXopt will automatically JIT compile the +# optimizer update step. +# + +opt = jaxopt.GradientDescent(loss_and_grad, stepsize=0.3, value_and_grad=True) +opt_state = opt.init_state(params) + +for i in range(100): + params, opt_state = opt.update(params, opt_state, data, targets, True, i) + + +###################################################################### +# Jitting the optimization loop +# ----------------------------- +# + +###################################################################### +# In the above example, we JIT compiled our cost function ``loss_fn`` (and JAXopt automatically JIT compiled the `loss_and_grad` function behind the scenes). However, we can +# also JIT compile the entire optimization loop; this means that the for-loop around optimization is +# not happening in Python, but is compiled and executed natively. This avoids (potentially costly) data +# transfer between Python and our JIT compiled cost function with each update step. +# + +@jax.jit +def optimization_jit(params, data, targets, print_training=False): + opt = jaxopt.GradientDescent(loss_and_grad, stepsize=0.3, value_and_grad=True) + opt_state = opt.init_state(params) + + def update(i, args): + params, opt_state = opt.update(*args, i) + return (params, opt_state, *args[2:]) + + args = (params, opt_state, data, targets, print_training) + (params, opt_state, _, _, _) = jax.lax.fori_loop(0, 100, update, args) + + return params + +###################################################################### +# Note that -- similar to above -- we use ``jax.lax.fori_loop`` and ``jax.lax.cond``, rather than a standard Python for loop +# and if statement, to allow the control flow to be JIT compatible. +# + +params = {"weights": weights, "bias": bias} +optimization_jit(params, data, targets, print_training=True) + +###################################################################### +# Appendix: Timing the two approaches +# ----------------------------------- +# +# We can time the two approaches (JIT compiling just the cost function, vs JIT compiling the entire +# optimization loop) to explore the differences in performance: +# + +from timeit import repeat + +def optimization(params, data, targets): + opt = jaxopt.GradientDescent(loss_and_grad, stepsize=0.3, value_and_grad=True) + opt_state = opt.init_state(params) + + for i in range(100): + params, opt_state = opt.update(params, opt_state, data, targets, False, i) + + return params + +reps = 5 +num = 2 + +times = repeat("optimization(params, data, targets)", globals=globals(), number=num, repeat=reps) +result = min(times) / num + +print(f"Jitting just the cost (best of {reps}): {result} sec per loop") + +times = repeat("optimization_jit(params, data, targets)", globals=globals(), number=num, repeat=reps) +result = min(times) / num + +print(f"Jitting the entire optimization (best of {reps}): {result} sec per loop") + +###################################################################### +# In this example, JIT compiling the entire optimization loop +# is significantly more performant. +# diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json new file mode 100644 index 0000000000..85e94f3384 --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json @@ -0,0 +1,53 @@ +{ + "title": "How to optimize a QML model using JAX and JAXopt", + "authors": [ + { + "username": "josh" + }, + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2024-01-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png" + } + ], + "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, and JAXopt.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in new file mode 100644 index 0000000000..375a0e28cc --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +jaxopt +pennylane diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py new file mode 100644 index 0000000000..19c1b684cd --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py @@ -0,0 +1,239 @@ +r"""How to optimize a QML model using JAX and Optax +==================================================================== + +Once you have set up a quantum machine learning model, data to train with and +cost function to minimize as an objective, the next step is to **perform the optimization**. That is, +setting up a classical optimization loop to find a minimal value of your cost function. + +In this example, we’ll show you how to use `JAX `__, an +autodifferentiable machine learning framework, and `Optax `__, a +suite of JAX-compatible gradient-based optimizers, to optimize a PennyLane quantum machine learning +model. + +.. figure:: ../_static/demonstration_assets/How_to_optimize_QML_model_using_JAX_and_Optax/socialsthumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png + :align: center + :width: 50% + +""" + +###################################################################### +# Set up your model, data, and cost +# --------------------------------- +# + +###################################################################### +# Here, we will create a simple QML model for our optimization. In particular: +# +# - We will embed our data through a series of rotation gates. +# - We will then have an ansatz of trainable rotation gates with parameters ``weights``; it is these +# values we will train to minimize our cost function. +# - We will train the QML model on ``data``, a ``(5, 4)`` array, and optimize the model to match +# target predictions given by ``target``. +# + +import pennylane as qml +import jax +from jax import numpy as jnp +import optax + +n_wires = 5 +data = jnp.sin(jnp.mgrid[-2:2:0.2].reshape(n_wires, -1)) ** 3 +targets = jnp.array([-0.2, 0.4, 0.35, 0.2]) + +dev = qml.device("default.qubit", wires=n_wires) + +@qml.qnode(dev) +def circuit(data, weights): + """Quantum circuit ansatz""" + + # data embedding + for i in range(n_wires): + # data[i] will be of shape (4,); we are + # taking advantage of operation vectorization here + qml.RY(data[i], wires=i) + + # trainable ansatz + for i in range(n_wires): + qml.RX(weights[i, 0], wires=i) + qml.RY(weights[i, 1], wires=i) + qml.RX(weights[i, 2], wires=i) + qml.CNOT(wires=[i, (i + 1) % n_wires]) + + # we use a sum of local Z's as an observable since a + # local Z would only be affected by params on that qubit. + return qml.expval(qml.sum(*[qml.PauliZ(i) for i in range(n_wires)])) + +def my_model(data, weights, bias): + return circuit(data, weights) + bias + +###################################################################### +# We will define a simple cost function that computes the overlap between model output and target +# data, and `just-in-time (JIT) compile `__ it: +# + +@jax.jit +def loss_fn(params, data, targets): + predictions = my_model(data, params["weights"], params["bias"]) + loss = jnp.sum((targets - predictions) ** 2 / len(data)) + return loss + +###################################################################### +# Note that the model above is just an example for demonstration – there are important considerations +# that must be taken into account when performing QML research, including methods for data embedding, +# circuit architecture, and cost function, in order to build models that may have use. This is still +# an active area of research; see our `demonstrations `__ for +# details. +# + +###################################################################### +# Initialize your parameters +# -------------------------- +# + +###################################################################### +# Now, we can generate our trainable parameters ``weights`` and ``bias`` that will be used to train +# our QML model. +# + +weights = jnp.ones([n_wires, 3]) +bias = jnp.array(0.) +params = {"weights": weights, "bias": bias} + +###################################################################### +# Plugging the trainable parameters, data, and target labels into our cost function, we can see the +# current loss as well as the parameter gradients: +# + +print(loss_fn(params, data, targets)) + +print(jax.grad(loss_fn)(params, data, targets)) + +###################################################################### +# Create the optimizer +# -------------------- +# + +###################################################################### +# We can now use Optax to create an optimizer, and train our circuit. +# Here, we choose the Adam optimizer, however +# `other available optimizers `__ +# may be used here. +# + +opt = optax.adam(learning_rate=0.3) +opt_state = opt.init(params) + +###################################################################### +# We first define our ``update_step`` function, which needs to do a couple of things: +# +# - Compute the loss function (so we can track training) and the gradients (so we can apply an +# optimization step). We can do this in one execution via the ``jax.value_and_grad`` function. +# +# - Apply the update step of our optimizer via ``opt.update`` +# +# - Update the parameters via ``optax.apply_updates`` +# + +def update_step(opt, params, opt_state, data, targets): + loss_val, grads = jax.value_and_grad(loss_fn)(params, data, targets) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return params, opt_state, loss_val + +loss_history = [] + +for i in range(100): + params, opt_state, loss_val = update_step(opt, params, opt_state, data, targets) + + if i % 5 == 0: + print(f"Step: {i} Loss: {loss_val}") + + loss_history.append(loss_val) + +###################################################################### +# Jitting the optimization loop +# ----------------------------- +# + +###################################################################### +# In the above example, we JIT compiled our cost function ``loss_fn``. However, we can +# also JIT compile the entire optimization loop; this means that the for-loop around optimization is +# not happening in Python, but is compiled and executed natively. This avoids (potentially costly) data +# transfer between Python and our JIT compiled cost function with each update step. +# + +# Define the optimizer we want to work with +opt = optax.adam(learning_rate=0.3) + +@jax.jit +def update_step_jit(i, args): + params, opt_state, data, targets, print_training = args + + loss_val, grads = jax.value_and_grad(loss_fn)(params, data, targets) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + + def print_fn(): + jax.debug.print("Step: {i} Loss: {loss_val}", i=i, loss_val=loss_val) + + # if print_training=True, print the loss every 5 steps + jax.lax.cond((jnp.mod(i, 5) == 0) & print_training, print_fn, lambda: None) + + return (params, opt_state, data, targets, print_training) + +@jax.jit +def optimization_jit(params, data, targets, print_training=False): + + opt_state = opt.init(params) + args = (params, opt_state, data, targets, print_training) + (params, opt_state, _, _, _) = jax.lax.fori_loop(0, 100, update_step_jit, args) + + return params + +###################################################################### +# Note that we use ``jax.lax.fori_loop`` and ``jax.lax.cond``, rather than a standard Python for loop +# and if statement, to allow the control flow to be JIT compatible. We also +# use ``jax.debug.print`` to allow printing to take place at function run-time, +# rather than compile-time. +# + +params = {"weights": weights, "bias": bias} +optimization_jit(params, data, targets, print_training=True) + +###################################################################### +# Appendix: Timing the two approaches +# ----------------------------------- +# +# We can time the two approaches (JIT compiling just the cost function, vs JIT compiling the entire +# optimization loop) to explore the differences in performance: +# + +from timeit import repeat + +def optimization(params, data, targets): + opt = optax.adam(learning_rate=0.3) + opt_state = opt.init(params) + + for i in range(100): + params, opt_state, loss_val = update_step(opt, params, opt_state, data, targets) + + return params + +reps = 5 +num = 2 + +times = repeat("optimization(params, data, targets)", globals=globals(), number=num, repeat=reps) +result = min(times) / num + +print(f"Jitting just the cost (best of {reps}): {result} sec per loop") + +times = repeat("optimization_jit(params, data, targets)", globals=globals(), number=num, repeat=reps) +result = min(times) / num + +print(f"Jitting the entire optimization (best of {reps}): {result} sec per loop") + + +###################################################################### +# In this example, JIT compiling the entire optimization loop +# is significantly more performant. +# diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json new file mode 100644 index 0000000000..fcc9a7a41d --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json @@ -0,0 +1,53 @@ +{ + "title": "How to optimize a QML model using JAX and Optax", + "authors": [ + { + "username": "josh" + }, + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2024-01-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png" + } + ], + "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, and Optax.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in new file mode 100644 index 0000000000..7a30722112 --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +optax +pennylane diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py new file mode 100644 index 0000000000..0e3eb7c779 --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py @@ -0,0 +1,217 @@ +r""" +How to optimize a QML model using Catalyst and quantum just-in-time (QJIT) compilation +====================================================================================== + +Once you have set up your quantum machine learning model (which typically includes deciding on your +circuit architecture/ansatz, determining how you embed or integrate your data, and creating your +cost function to minimize a quantity of interest), the next step is **optimization**. That is, +setting up a classical optimization loop to find a minimal value of your cost function. + +In this example, we’ll show you how to use `JAX `__, an +autodifferentiable machine learning framework, and `Optax `__, a +suite of JAX-compatible gradient-based optimizers, to optimize a PennyLane quantum machine learning +model which has been quantum just-in-time compiled using the :func:`~.pennylane.qjit` decorator and +`Catalyst `__. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_how-to-optimize-qjit-optax_2024-04-23.png + :align: center + :width: 60% + :target: javascript:void(0) + +Set up your model, data, and cost +--------------------------------- + +Here, we will create a simple QML model for our optimization. In particular: + +- We will embed our data through a series of rotation gates. +- We will then have an ansatz of trainable rotation gates with parameters ``weights``; it is these + values we will train to minimize our cost function. +- We will train the QML model on ``data``, a ``(5, 4)`` array, and optimize the model to match + target predictions given by ``target``. +""" + +import pennylane as qml +from jax import numpy as jnp +import optax +import catalyst + +n_wires = 5 +data = jnp.sin(jnp.mgrid[-2:2:0.2].reshape(n_wires, -1)) ** 3 +targets = jnp.array([-0.2, 0.4, 0.35, 0.2]) + +dev = qml.device("lightning.qubit", wires=n_wires) + +@qml.qnode(dev) +def circuit(data, weights): + """Quantum circuit ansatz""" + + @qml.for_loop(0, n_wires, 1) + def data_embedding(i): + qml.RY(data[i], wires=i) + + data_embedding() + + @qml.for_loop(0, n_wires, 1) + def ansatz(i): + qml.RX(weights[i, 0], wires=i) + qml.RY(weights[i, 1], wires=i) + qml.RX(weights[i, 2], wires=i) + qml.CNOT(wires=[i, (i + 1) % n_wires]) + + ansatz() + + # we use a sum of local Z's as an observable since a + # local Z would only be affected by params on that qubit. + return qml.expval(qml.sum(*[qml.PauliZ(i) for i in range(n_wires)])) + +###################################################################### +# The :func:`catalyst.vmap` function allows us to specify that the first argument to circuit (``data``) +# contains a batch dimension. In this example, the batch dimension is the second axis (axis 1). +# + +circuit = qml.qjit(catalyst.vmap(circuit, in_axes=(1, None))) + +###################################################################### +# We will define a simple cost function that computes the overlap between model output and target +# data: +# + +def my_model(data, weights, bias): + return circuit(data, weights) + bias + +@qml.qjit +def loss_fn(params, data, targets): + predictions = my_model(data, params["weights"], params["bias"]) + loss = jnp.sum((targets - predictions) ** 2 / len(data)) + return loss + +###################################################################### +# Note that the model above is just an example for demonstration – there are important considerations +# that must be taken into account when performing QML research, including methods for data embedding, +# circuit architecture, and cost function, in order to build models that may have use. This is still +# an active area of research; see our `demonstrations `__ for +# details. +# +# Initialize your parameters +# -------------------------- +# +# Now, we can generate our trainable parameters ``weights`` and ``bias`` that will be used to train +# our QML model. +# + +weights = jnp.ones([n_wires, 3]) +bias = jnp.array(0.) +params = {"weights": weights, "bias": bias} + +###################################################################### +# Plugging the trainable parameters, data, and target labels into our cost function, we can see the +# current loss as well as the parameter gradients: +# + +loss_fn(params, data, targets) + +print(qml.qjit(catalyst.grad(loss_fn, method="fd"))(params, data, targets)) + +###################################################################### +# Create the optimizer +# -------------------- +# +# We can now use Optax to create an Adam optimizer, and train our circuit. +# +# We first define our ``update_step`` function, which needs to do a couple of things: +# +# - Compute the gradients of the loss function. We can +# do this via the :func:`catalyst.grad` function. +# +# - Apply the update step of our optimizer via ``opt.update`` +# +# - Update the parameters via ``optax.apply_updates`` +# + +opt = optax.adam(learning_rate=0.3) + +@qml.qjit +def update_step(i, args): + params, opt_state, data, targets = args + + grads = catalyst.grad(loss_fn, method="fd")(params, data, targets) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + + return (params, opt_state, data, targets) + +loss_history = [] + +opt_state = opt.init(params) + +for i in range(100): + params, opt_state, _, _ = update_step(i, (params, opt_state, data, targets)) + loss_val = loss_fn(params, data, targets) + + if i % 5 == 0: + print(f"Step: {i} Loss: {loss_val}") + + loss_history.append(loss_val) + +###################################################################### +# JIT-compiling the optimization +# ------------------------------ +# +# In the above example, we just-in-time (JIT) compiled our cost function ``loss_fn``. However, we can +# also JIT compile the entire optimization loop; this means that the for-loop around optimization is +# not happening in Python, but is compiled and executed natively. This avoids (potentially costly) +# data transfer between Python and our JIT compiled cost function with each update step. +# + +params = {"weights": weights, "bias": bias} + +@qml.qjit +def optimization(params, data, targets): + opt_state = opt.init(params) + args = (params, opt_state, data, targets) + (params, opt_state, _, _) = qml.for_loop(0, 100, 1)(update_step)(args) + return params + +###################################################################### +# Note that we use :func:`~pennylane.for_loop` rather than a standard Python for loop, to allow the control +# flow to be JIT compatible. +# + +final_params = optimization(params, data, targets) + +print(final_params) + +###################################################################### +# Timing the optimization +# ----------------------- +# +# We can time the two approaches (JIT compiling just the cost function, vs JIT compiling the entire +# optimization loop) to explore the differences in performance: +# + +from timeit import repeat + +opt = optax.adam(learning_rate=0.3) + +def optimization_noqjit(params): + opt_state = opt.init(params) + + for i in range(100): + params, opt_state, _, _ = update_step(i, (params, opt_state, data, targets)) + + return params + +reps = 5 +num = 2 + +times = repeat("optimization_noqjit(params)", globals=globals(), number=num, repeat=reps) +result = min(times) / num + +print(f"Quantum jitting just the cost (best of {reps}): {result} sec per loop") + +times = repeat("optimization(params, data, targets)", globals=globals(), number=num, repeat=reps) +result = min(times) / num + +print(f"Quantum jitting the entire optimization (best of {reps}): {result} sec per loop") + +###################################################################### diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json new file mode 100644 index 0000000000..d74a06c2e4 --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json @@ -0,0 +1,50 @@ +{ + "title": "How to optimize a QML model using Catalyst and quantum just-in-time (QJIT) compilation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how-to-optimize-qjit-optax.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how-to-optimize-qjit-optax.png" + } + ], + "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, Catalyst, and Optax.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in new file mode 100644 index 0000000000..fc745d6f41 --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in @@ -0,0 +1,5 @@ +pennylane-catalyst +jax +jaxlib +optax +pennylane diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py new file mode 100644 index 0000000000..8086734d2e --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py @@ -0,0 +1,212 @@ +r"""How to simulate quantum circuits with tensor networks +==================================================================== + +:doc:`Tensor networks ` are a powerful computational tool for simulating quantum circuits. +They provide a way to represent quantum states and operations in a compact form. +Unlike the state-vector approach, tensor networks are particularly useful for large-scale simulations of `quantum circuits `__. + +Here, we demonstrate how to simulate quantum circuits using the :class:`~pennylane.devices.default_tensor.DefaultTensor` device in PennyLane. +The used simulator is based on `quimb `__, a Python library for tensor network manipulations, +and we refer to the `documentation `__ for more details. +The ``default.tensor`` device is well-suited for simulations of circuits with tens, hundreds, or even thousands of qubits +as long as the degree of entanglement within the circuit remains manageable. In general, the effectiveness of this device +depends on the specific circuit structure and the provided keyword arguments. +Note that other simulators based on the state-vector approach may be more suitable for small circuits +since the overhead of tensor network contractions can be significant. + +The ``default.tensor`` device has just been released and is still under development. +Further improvements, new features, and additional tutorials are expected in future releases. +Check the latest functionality in the :class:`documentation <.pennylane.devices.default_tensor.DefaultTensor>` +or pick among other `PennyLane devices `__ for your project. + +.. figure:: ../_static/demonstration_assets/how_to_simulate_quantum_circuits_with_tensor_networks/TN_MPS.gif + :align: center + :width: 90% + +""" + +###################################################################### +# Choosing the method to simulate quantum circuits +# ------------------------------------------------ +# +# The ``default.tensor`` device can simulate quantum circuits using two different computational methods. +# The first is the matrix product state (MPS) representation, and the second is the full contraction approach using a tensor network (TN). +# We only need to specify the ``method`` keyword argument when instantiating the device to choose one or the other. +# If not specified, the default method is the MPS. +# +# The MPS method can be seen as a particular case of the TN approach, where the tensor network has a one-dimensional structure. +# It can be beneficial for obtaining approximate results, and the degree of approximation can be controlled +# via the maximum bond dimension at the expense of memory and computational cost. It is particularly useful for simulating circuits with low entanglement. +# +# On the other hand, the TN method always yields an exact result but can demand higher computational and memory costs depending +# on the underlying circuit structure and contraction path. +# + +###################################################################### +# Simulating a quantum circuit with the MPS method +# ------------------------------------------------ +# +# Let's start by showing how to simulate a quantum circuit using the matrix product state (MPS) method. +# We consider a simple short-depth quantum circuit that can be efficiently simulated with such a method. +# The number of gates increases with the number of qubits. +# + +import pennylane as qml +import numpy as np + +# Define the keyword arguments for the MPS method +kwargs_mps = { + # Maximum bond dimension of the MPS + "max_bond_dim": 50, + # Cutoff parameter for the singular value decomposition + "cutoff": np.finfo(np.complex128).eps, + # Contraction strategy to apply gates + "contract": "auto-mps", +} + +# Parameters of the quantum circuit +theta = 0.5 +phi = 0.1 + +# Instantiate the device with the MPS method and the specified kwargs +dev = qml.device("default.tensor", method="mps", **kwargs_mps) + + +# Define the quantum circuit +@qml.qnode(dev) +def circuit(theta, phi, num_qubits): + for qubit in range(num_qubits - 4): + qml.RX(theta, wires=qubit + 1) + qml.CNOT(wires=[qubit, qubit + 1]) + qml.RY(phi, wires=qubit + 1) + qml.DoubleExcitation(theta, wires=[qubit, qubit + 1, qubit + 3, qubit + 4]) + qml.Toffoli(wires=[qubit + 1, qubit + 3, qubit + 4]) + return qml.expval( + qml.X(num_qubits - 1) @ qml.Y(num_qubits - 2) @ qml.Z(num_qubits - 3) + ) + + +###################################################################### +# We set the maximum bond dimension to 50 and the ``cutoff`` parameter is set to the machine epsilon of the ``numpy.complex128`` data type. +# For this circuit, retaining a maximum of 50 singular values in the singular value decomposition is more than enough to represent the quantum state accurately. +# Finally, the contraction strategy is set to ``auto-mps``. For an explanation of these parameters, we refer to +# the :class:`documentation <.pennylane.devices.default_tensor.DefaultTensor>` of the ``default.tensor`` device. +# +# As a general rule, choosing the appropriate method and setting the optimal keyword arguments is essential +# to achieve the best performance for a given quantum circuit. However, the optimal choice depends on the specific circuit structure. +# For optimization tips, we also refer to the `performance checklist `__ +# in the ``quimb`` documentation. +# +# We can now simulate the quantum circuit for different numbers of qubits. +# The execution time will generally increase as the number of qubits grows. +# The first execution is typically slower due to the initial setup and compilation processes of ``quimb``. +# + +import time + +# Simulate the circuit for different numbers of qubits +for num_qubits in range(50, 201, 50): + print(f"Number of qubits: {num_qubits}") + start_time = time.time() + result = circuit(theta, phi, num_qubits) + end_time = time.time() + print(f"Result: {result}") + print(f"Execution time: {end_time - start_time:.4f} seconds") + +###################################################################### +# Selecting the MPS method, each gate is immediately contracted into the MPS representation of the wavefunction. +# Therefore, the structure of the MPS is maintained after each gate application. +# +# To learn more about the MPS method and its theoretical background, +# we refer to the extensive literature available on the subject, such as [#orus]_. +# + +###################################################################### +# Simulating a quantum circuit with the TN method +# ----------------------------------------------- +# +# The tensor network (TN) method is a more general approach than the matrix product state (MPS) method. +# While the MPS method can be very efficient for simulating certain quantum circuits, +# it may require a large bond dimension to accurately represent highly entangled states. This can lead to increased computational and memory costs. +# +# For example, the full contraction scheme can be helpful in simulating circuits with a higher degree of entanglement, +# although it can also face significant computational and memory challenges. +# +# In the following example, we consider a simple quantum circuit with a configurable depth. +# As in the previous circuit, the number of gates increases with the number of qubits. +# + +import pennylane as qml +import numpy as np + +# Define the keyword arguments for the TN method +kwargs_tn = { + # Contraction strategy to apply gates + "contract": False, + # Simplification sequence to apply to the tensor network + "local_simplify": "DCRS", + # Contraction optimizer to use + "contraction_optimizer": None, +} + +# Parameters of the quantum circuit +theta = 0.5 +phi = 0.1 +depth = 10 + +# Instantiate the device with the TN method and the specified kwargs +dev = qml.device("default.tensor", method="tn", **kwargs_tn) + + +@qml.qnode(dev) +def circuit(theta, depth, num_qubits): + for i in range(num_qubits): + qml.X(wires=i) + for _ in range(1, depth - 1): + for i in range(0, num_qubits, 2): + qml.CNOT(wires=[i, i + 1]) + for i in range(num_qubits % 5): + qml.RZ(theta, wires=i) + for i in range(1, num_qubits - 1, 2): + qml.CZ(wires=[i, i + 1]) + for i in range(num_qubits): + qml.CNOT(wires=[i, (i + 1)]) + return qml.var(qml.X(num_qubits - 1)) + + +# Simulate the circuit for different numbers of qubits +for num_qubits in range(25, 101, 25): + print(f"Number of qubits: {num_qubits}") + start_time = time.time() + result = circuit(theta, depth, num_qubits) + end_time = time.time() + print(f"Result: {result}") + print(f"Execution time: {end_time - start_time:.4f} seconds") + +###################################################################### +# Here, we lazily add each gate to the tensor network without contracting by setting the ``contract`` keyword argument to ``False``. +# The contraction optimizer is set to ``None``, and the simplification sequence is set to ``DCRS``. +# As for the MPS method, we refer to the :class:`documentation <.pennylane.devices.default_tensor.DefaultTensor>` for +# a list and explanation of the keyword arguments available for the TN method. +# +# We can also visualize the tensor network representation of the quantum circuit with the ``draw`` method. +# This method produces a graphical representation of the tensor network using ``quimb``'s plotting functionalities. +# The tensor network method usually generates a more complex tensor network than the MPS method. +# +# Since we did not specify the number of qubits when instantiating the device, +# the number of tensors in the tensor network is inferred from the last execution of the quantum circuit. +# We can visualize the tensor network for 15 qubits. +# + +circuit(theta, depth, num_qubits=15) +dev.draw(color="auto", show_inds=True, return_fig=True) + +###################################################################### +# References +# ---------- +# .. [#orus] +# +# R. Orús, Annals of Physics 349, 117 (2014), ISSN 0003- +# 4916, URL https://www.sciencedirect.com/science/article/pii/S0003491614001596. + +###################################################################### diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json new file mode 100644 index 0000000000..8d9e0a72e7 --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json @@ -0,0 +1,51 @@ +{ + "title": "How to simulate quantum circuits with tensor networks using DefaultTensor", + "authors": [ + { + "username": "Frisus95" + } + ], + "dateOfPublication": "2024-07-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing", + "Devices and Performance", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_default_tensor.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_default_tensor.png" + } + ], + "seoDescription": "Learn how to simulate quantum circuits with tensor networks using the default.tensor PennyLane device.", + "doi": "", + "references": [ + { + "id": "orus", + "type": "article", + "title": "A practical introduction to tensor networks: Matrix product states and projected entangled pair states", + "authors": "R. Or\u00fas", + "year": "2014", + "journal": "Annals of Physics", + "url": "https://www.sciencedirect.com/science/article/pii/S0003491614001596" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_tn_circuits", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in new file mode 100644 index 0000000000..8a849c9ca7 --- /dev/null +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in @@ -0,0 +1,3 @@ +numpy +pennylane +quimb diff --git a/demonstrations_v2/tutorial_QGAN/demo.py b/demonstrations_v2/tutorial_QGAN/demo.py new file mode 100644 index 0000000000..5c629bfce6 --- /dev/null +++ b/demonstrations_v2/tutorial_QGAN/demo.py @@ -0,0 +1,289 @@ +""" +.. _quantum_GAN: + +Quantum generative adversarial networks with Cirq + TensorFlow +============================================================== + +.. meta:: + :property="og:description": This demo constructs and trains a Quantum + Generative Adversarial Network (QGAN) using PennyLane, Cirq, and TensorFlow. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qgan3.png + +*Author: Nathan Killoran — Posted: 11 October 2019. Last updated: 30 January 2023.* + +This demo constructs a Quantum Generative Adversarial Network (QGAN) +(`Lloyd and Weedbrook +(2018) `__, +`Dallaire-Demers and Killoran +(2018) `__) +using two subcircuits, a *generator* and a *discriminator*. The +generator attempts to generate synthetic quantum data to match a pattern +of "real" data, while the discriminator tries to discern real data from +fake data (see image below). The gradient of the discriminator’s output provides a +training signal for the generator to improve its fake generated data. + +| + +.. figure:: ../_static/demonstration_assets/QGAN/qgan.png + :align: center + :width: 75% + :target: javascript:void(0) + +| + + +""" + +############################################################################## +# Using Cirq + TensorFlow +# ----------------------- +# PennyLane allows us to mix and match quantum devices and classical machine +# learning software. For this demo, we will link together +# Google's `Cirq `_ and `TensorFlow `_ libraries. +# +# We begin by importing PennyLane, NumPy, and TensorFlow. + +import numpy as np +import pennylane as qml +import tensorflow as tf + +############################################################################## +# We also declare a 3-qubit simulator device running in Cirq. + +dev = qml.device('cirq.simulator', wires=3) + + +############################################################################## +# Generator and Discriminator +# --------------------------- +# +# In classical GANs, the starting point is to draw samples either from +# some "real data" distribution, or from the generator, and feed them to +# the discriminator. In this QGAN example, we will use a quantum circuit +# to generate the real data. +# +# For this simple example, our real data will be a qubit that has been +# rotated (from the starting state :math:`\left|0\right\rangle`) to some +# arbitrary, but fixed, state. + +def real(angles, **kwargs): + qml.Hadamard(wires=0) + qml.Rot(*angles, wires=0) + + +############################################################################## +# For the generator and discriminator, we will choose the same basic +# circuit structure, but acting on different wires. +# +# Both the real data circuit and the generator will output on wire 0, +# which will be connected as an input to the discriminator. Wire 1 is +# provided as a workspace for the generator, while the discriminator’s +# output will be on wire 2. + +def generator(w, **kwargs): + qml.Hadamard(wires=0) + qml.RX(w[0], wires=0) + qml.RX(w[1], wires=1) + qml.RY(w[2], wires=0) + qml.RY(w[3], wires=1) + qml.RZ(w[4], wires=0) + qml.RZ(w[5], wires=1) + qml.CNOT(wires=[0, 1]) + qml.RX(w[6], wires=0) + qml.RY(w[7], wires=0) + qml.RZ(w[8], wires=0) + + +def discriminator(w): + qml.Hadamard(wires=0) + qml.RX(w[0], wires=0) + qml.RX(w[1], wires=2) + qml.RY(w[2], wires=0) + qml.RY(w[3], wires=2) + qml.RZ(w[4], wires=0) + qml.RZ(w[5], wires=2) + qml.CNOT(wires=[0, 2]) + qml.RX(w[6], wires=2) + qml.RY(w[7], wires=2) + qml.RZ(w[8], wires=2) + + +############################################################################## +# We create two QNodes. One where the real data source is wired up to the +# discriminator, and one where the generator is connected to the +# discriminator. + +@qml.qnode(dev) +def real_disc_circuit(phi, theta, omega, disc_weights): + real([phi, theta, omega]) + discriminator(disc_weights) + return qml.expval(qml.PauliZ(2)) + + +@qml.qnode(dev) +def gen_disc_circuit(gen_weights, disc_weights): + generator(gen_weights) + discriminator(disc_weights) + return qml.expval(qml.PauliZ(2)) + + +############################################################################## +# QGAN cost functions +# ------------------- +# +# There are two cost functions of interest, corresponding to the two +# stages of QGAN training. These cost functions are built from two pieces: +# the first piece is the probability that the discriminator correctly +# classifies real data as real. The second piece is the probability that the +# discriminator classifies fake data (i.e., a state prepared by the +# generator) as real. +# +# The discriminator is trained to maximize the probability of +# correctly classifying real data, while minimizing the probability of +# mistakenly classifying fake data. +# +# .. math:: +# +# Cost_D = \mathrm{Pr}(real|\mathrm{fake}) - \mathrm{Pr}(real|\mathrm{real}) +# +# The generator is trained to maximize the probability that the +# discriminator accepts fake data as real. +# +# .. math:: +# +# Cost_G = - \mathrm{Pr}(real|\mathrm{fake}) +# + +def prob_real_true(disc_weights): + true_disc_output = real_disc_circuit(phi, theta, omega, disc_weights) + # convert to probability + prob_real_true = (true_disc_output + 1) / 2 + return prob_real_true + + +def prob_fake_true(gen_weights, disc_weights): + fake_disc_output = gen_disc_circuit(gen_weights, disc_weights) + # convert to probability + prob_fake_true = (fake_disc_output + 1) / 2 + return prob_fake_true + + +def disc_cost(disc_weights): + cost = prob_fake_true(gen_weights, disc_weights) - prob_real_true(disc_weights) + return cost + + +def gen_cost(gen_weights): + return -prob_fake_true(gen_weights, disc_weights) + + +############################################################################## +# Training the QGAN +# ----------------- +# +# We initialize the fixed angles of the "real data" circuit, as well as +# the initial parameters for both generator and discriminator. These are +# chosen so that the generator initially prepares a state on wire 0 that +# is very close to the :math:`\left| 1 \right\rangle` state. + +phi = np.pi / 6 +theta = np.pi / 2 +omega = np.pi / 7 +np.random.seed(0) +eps = 1e-2 +init_gen_weights = np.array([np.pi] + [0] * 8) + \ + np.random.normal(scale=eps, size=(9,)) +init_disc_weights = np.random.normal(size=(9,)) + +gen_weights = tf.Variable(init_gen_weights) +disc_weights = tf.Variable(init_disc_weights) + + +############################################################################## +# We begin by creating the optimizer: + +opt = tf.keras.optimizers.SGD(0.4) +opt.build([disc_weights, gen_weights]) + +############################################################################## +# In the first stage of training, we optimize the discriminator while +# keeping the generator parameters fixed. + +cost = lambda: disc_cost(disc_weights) + +for step in range(50): + opt.minimize(cost, [disc_weights]) + if step % 5 == 0: + cost_val = cost().numpy() + print("Step {}: cost = {}".format(step, cost_val)) + + +############################################################################## +# At the discriminator’s optimum, the probability for the discriminator to +# correctly classify the real data should be close to one. + +print("Prob(real classified as real): ", prob_real_true(disc_weights).numpy()) + + +############################################################################## +# For comparison, we check how the discriminator classifies the +# generator’s (still unoptimized) fake data: + +print("Prob(fake classified as real): ", prob_fake_true(gen_weights, disc_weights).numpy()) + + +############################################################################## +# In the adversarial game we now have to train the generator to better +# fool the discriminator. For this demo, we only perform one stage of the +# game. For more complex models, we would continue training the models in an +# alternating fashion until we reach the optimum point of the two-player +# adversarial game. + +cost = lambda: gen_cost(gen_weights) + +for step in range(50): + opt.minimize(cost, [gen_weights]) + if step % 5 == 0: + cost_val = cost().numpy() + print("Step {}: cost = {}".format(step, cost_val)) + + +############################################################################## +# At the optimum of the generator, the probability for the discriminator +# to be fooled should be close to 1. + +print("Prob(fake classified as real): ", prob_fake_true(gen_weights, disc_weights).numpy()) + + +############################################################################## +# At the joint optimum the discriminator cost will be close to zero, +# indicating that the discriminator assigns equal probability to both real and +# generated data. + +print("Discriminator cost: ", disc_cost(disc_weights).numpy()) + +############################################################################## +# The generator has successfully learned how to simulate the real data +# enough to fool the discriminator. +# +# Let's conclude by comparing the states of the real data circuit and the generator. We expect +# the generator to have learned to be in a state that is very close to the one prepared in the +# real data circuit. An easy way to access the state of the first qubit is through its +# `Bloch sphere `__ representation: + +obs = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)] + +@qml.qnode(dev) +def bloch_vector_real(angles): + real(angles) + return [qml.expval(o) for o in obs] + +@qml.qnode(dev) +def bloch_vector_generator(angles): + generator(angles) + return [qml.expval(o) for o in obs] + +print(f"Real Bloch vector: {bloch_vector_real([phi, theta, omega])}") +print(f"Generator Bloch vector: {bloch_vector_generator(gen_weights)}") + +############################################################################## diff --git a/demonstrations_v2/tutorial_QGAN/metadata.json b/demonstrations_v2/tutorial_QGAN/metadata.json new file mode 100644 index 0000000000..5f3d04a5f8 --- /dev/null +++ b/demonstrations_v2/tutorial_QGAN/metadata.json @@ -0,0 +1,28 @@ +{ + "title": "Quantum generative adversarial networks with Cirq + TensorFlow", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_generative_adversarial_networks.png" + } + ], + "seoDescription": "This demo constructs and trains a Quantum Generative Adversarial Network (QGAN) using PennyLane, Cirq, and TensorFlow.", + "doi": "", + "references": [], + "basedOnPapers": [ + "10.1103/PhysRevA.98.012324" + ], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_QGAN/requirements.in b/demonstrations_v2/tutorial_QGAN/requirements.in new file mode 100644 index 0000000000..911f2c73ac --- /dev/null +++ b/demonstrations_v2/tutorial_QGAN/requirements.in @@ -0,0 +1,3 @@ +numpy +pennylane +tensorflow diff --git a/demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_slack.json b/demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_slack.json new file mode 100644 index 0000000000..29acc8a5a0 --- /dev/null +++ b/demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_slack.json @@ -0,0 +1 @@ +{"x_0":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":0,"6":0,"7":0,"8":0,"9":1,"10":0,"11":1,"12":0,"13":0,"14":0,"15":1,"16":1,"17":1,"18":1,"19":1,"20":0,"21":0,"22":1,"23":1,"24":0,"25":1,"26":1,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":1,"35":0,"36":0,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"46":1,"47":0,"48":0,"49":0,"50":0,"51":1,"52":1,"53":1,"54":0,"55":0,"56":0,"57":1,"58":0,"59":0,"60":1,"61":1,"62":1,"63":1,"64":0,"65":1,"66":1,"67":1,"68":0,"69":0,"70":0,"71":1,"72":0,"73":0,"74":1,"75":1,"76":1,"77":1,"78":1,"79":0,"80":1,"81":0,"82":0,"83":1,"84":0,"85":0,"86":1,"87":0,"88":0,"89":0,"90":0,"91":1,"92":1,"93":1,"94":1,"95":0,"96":1,"97":0,"98":1,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":1,"106":1,"107":1,"108":1,"109":1,"110":1,"111":0,"112":1,"113":1,"114":0,"115":0,"116":0,"117":0,"118":0,"119":1,"120":1,"121":0,"122":0,"123":0,"124":1,"125":1,"126":1,"127":1,"128":1,"129":1,"130":0,"131":0,"132":0,"133":0,"134":0,"135":0,"136":0,"137":0,"138":1,"139":1,"140":1,"141":0,"142":0,"143":1,"144":1,"145":0,"146":0,"147":1,"148":0,"149":0,"150":0,"151":0,"152":1,"153":1,"154":0,"155":0,"156":0,"157":1,"158":1,"159":0,"160":0,"161":0,"162":1,"163":1,"164":1,"165":1,"166":0,"167":0,"168":1,"169":1,"170":0,"171":1,"172":0,"173":0,"174":0,"175":0,"176":1,"177":0,"178":1,"179":0,"180":0,"181":0,"182":1,"183":1,"184":1,"185":0,"186":0,"187":1,"188":0,"189":0,"190":1,"191":0,"192":1,"193":1,"194":0,"195":0,"196":0,"197":0,"198":1,"199":1,"200":1,"201":1,"202":1,"203":0,"204":0,"205":0,"206":0,"207":1,"208":1,"209":1,"210":1,"211":1,"212":0,"213":0,"214":0,"215":0,"216":0,"217":1,"218":1,"219":0,"220":0,"221":0,"222":0,"223":1,"224":1,"225":0,"226":0,"227":0,"228":1,"229":1,"230":0,"231":0,"232":1,"233":1,"234":1,"235":1,"236":0,"237":0,"238":0,"239":0,"240":1,"241":1,"242":1,"243":0,"244":0,"245":0,"246":0,"247":1,"248":1,"249":1,"250":1,"251":0,"252":0,"253":1,"254":1,"255":1,"256":0,"257":0,"258":1,"259":0,"260":0,"261":1,"262":1,"263":1,"264":1,"265":0,"266":1,"267":0,"268":0,"269":0,"270":1,"271":1,"272":1,"273":0,"274":1,"275":1,"276":0,"277":1,"278":1,"279":0,"280":0,"281":0,"282":0,"283":1,"284":0,"285":0,"286":0,"287":1,"288":1,"289":1,"290":0,"291":1,"292":0,"293":1,"294":1,"295":0,"296":0,"297":1,"298":1,"299":1,"300":0,"301":1,"302":0,"303":0,"304":0,"305":0,"306":0,"307":0,"308":1,"309":0,"310":0,"311":1,"312":0,"313":1,"314":1,"315":1,"316":1,"317":0,"318":0,"319":0,"320":0,"321":1,"322":1,"323":1,"324":1,"325":0,"326":1,"327":1,"328":1,"329":0,"330":0,"331":0,"332":0,"333":0,"334":1,"335":1,"336":0,"337":1,"338":0,"339":0,"340":1,"341":0,"342":1,"343":1,"344":0,"345":1,"346":0,"347":1,"348":1,"349":1,"350":0,"351":1,"352":0,"353":1,"354":1,"355":0,"356":1,"357":1},"x_1":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":0,"43":1,"44":0,"45":0,"46":0,"47":1,"48":1,"49":1,"50":1,"51":0,"52":0,"53":0,"54":0,"55":1,"56":0,"57":0,"58":0,"59":1,"60":1,"61":1,"62":0,"63":0,"64":0,"65":1,"66":1,"67":0,"68":1,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":1,"82":1,"83":1,"84":0,"85":0,"86":0,"87":0,"88":0,"89":1,"90":1,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":1,"105":0,"106":0,"107":0,"108":0,"109":0,"110":1,"111":0,"112":0,"113":1,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":1,"122":1,"123":0,"124":0,"125":0,"126":0,"127":0,"128":1,"129":1,"130":0,"131":0,"132":0,"133":0,"134":0,"135":1,"136":1,"137":0,"138":0,"139":0,"140":0,"141":0,"142":0,"143":1,"144":0,"145":0,"146":0,"147":0,"148":0,"149":0,"150":1,"151":1,"152":0,"153":0,"154":0,"155":0,"156":0,"157":0,"158":0,"159":1,"160":0,"161":0,"162":0,"163":1,"164":1,"165":1,"166":0,"167":0,"168":0,"169":0,"170":1,"171":0,"172":0,"173":0,"174":0,"175":0,"176":0,"177":0,"178":0,"179":1,"180":1,"181":1,"182":0,"183":0,"184":1,"185":0,"186":0,"187":1,"188":0,"189":0,"190":0,"191":0,"192":0,"193":1,"194":0,"195":0,"196":1,"197":1,"198":0,"199":0,"200":1,"201":0,"202":1,"203":0,"204":0,"205":0,"206":1,"207":0,"208":0,"209":1,"210":1,"211":0,"212":0,"213":0,"214":0,"215":1,"216":1,"217":1,"218":0,"219":0,"220":0,"221":0,"222":1,"223":0,"224":0,"225":1,"226":0,"227":0,"228":1,"229":0,"230":0,"231":0,"232":1,"233":0,"234":0,"235":0,"236":1,"237":1,"238":0,"239":0,"240":1,"241":1,"242":0,"243":1,"244":0,"245":0,"246":0,"247":1,"248":1,"249":0,"250":0,"251":1,"252":0,"253":0,"254":0,"255":1,"256":1,"257":0,"258":0,"259":0,"260":1,"261":0,"262":1,"263":0,"264":1,"265":0,"266":0,"267":1,"268":1,"269":0,"270":1,"271":0,"272":0,"273":1,"274":1,"275":1,"276":0,"277":0,"278":0,"279":1,"280":0,"281":0,"282":1,"283":0,"284":0,"285":0,"286":0,"287":1,"288":0,"289":0,"290":0,"291":1,"292":1,"293":0,"294":1,"295":0,"296":0,"297":0,"298":1,"299":1,"300":0,"301":0,"302":0,"303":0,"304":0,"305":0,"306":1,"307":0,"308":0,"309":0,"310":0,"311":1,"312":1,"313":1,"314":0,"315":1,"316":1,"317":1,"318":0,"319":0,"320":0,"321":1,"322":0,"323":1,"324":1,"325":1,"326":0,"327":1,"328":1,"329":0,"330":0,"331":1,"332":0,"333":1,"334":0,"335":1,"336":0,"337":0,"338":1,"339":1,"340":0,"341":0,"342":1,"343":1,"344":1,"345":0,"346":0,"347":1,"348":1,"349":1,"350":0,"351":1,"352":1,"353":0,"354":1,"355":1,"356":1,"357":1},"x_2":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":1,"9":1,"10":1,"11":0,"12":0,"13":1,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":1,"21":1,"22":0,"23":0,"24":0,"25":1,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":1,"33":0,"34":0,"35":1,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":1,"43":1,"44":1,"45":1,"46":1,"47":0,"48":0,"49":0,"50":0,"51":0,"52":1,"53":1,"54":1,"55":1,"56":1,"57":0,"58":1,"59":1,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":1,"72":1,"73":1,"74":0,"75":1,"76":1,"77":1,"78":0,"79":0,"80":1,"81":0,"82":0,"83":1,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":1,"95":1,"96":1,"97":1,"98":0,"99":0,"100":0,"101":1,"102":1,"103":1,"104":1,"105":0,"106":0,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":0,"117":1,"118":1,"119":1,"120":1,"121":0,"122":1,"123":0,"124":0,"125":0,"126":1,"127":1,"128":0,"129":0,"130":0,"131":0,"132":0,"133":0,"134":0,"135":0,"136":0,"137":0,"138":0,"139":0,"140":0,"141":1,"142":1,"143":1,"144":0,"145":1,"146":1,"147":0,"148":0,"149":0,"150":0,"151":0,"152":0,"153":0,"154":0,"155":0,"156":0,"157":1,"158":1,"159":1,"160":0,"161":0,"162":1,"163":0,"164":1,"165":0,"166":0,"167":0,"168":0,"169":0,"170":0,"171":0,"172":1,"173":1,"174":1,"175":1,"176":0,"177":1,"178":0,"179":0,"180":0,"181":1,"182":0,"183":0,"184":0,"185":0,"186":0,"187":0,"188":0,"189":0,"190":1,"191":0,"192":1,"193":1,"194":0,"195":0,"196":0,"197":0,"198":0,"199":0,"200":0,"201":1,"202":0,"203":1,"204":1,"205":1,"206":1,"207":0,"208":0,"209":0,"210":0,"211":0,"212":0,"213":1,"214":1,"215":0,"216":0,"217":1,"218":0,"219":0,"220":0,"221":0,"222":0,"223":1,"224":1,"225":1,"226":0,"227":0,"228":0,"229":0,"230":1,"231":1,"232":1,"233":0,"234":1,"235":0,"236":0,"237":0,"238":0,"239":0,"240":0,"241":0,"242":0,"243":1,"244":1,"245":0,"246":1,"247":0,"248":0,"249":0,"250":0,"251":0,"252":0,"253":1,"254":1,"255":1,"256":0,"257":0,"258":0,"259":1,"260":1,"261":0,"262":0,"263":1,"264":1,"265":0,"266":0,"267":0,"268":0,"269":1,"270":0,"271":0,"272":0,"273":1,"274":0,"275":0,"276":0,"277":1,"278":1,"279":0,"280":0,"281":1,"282":0,"283":0,"284":1,"285":1,"286":1,"287":1,"288":0,"289":0,"290":0,"291":0,"292":0,"293":0,"294":1,"295":1,"296":1,"297":0,"298":0,"299":0,"300":0,"301":1,"302":0,"303":0,"304":1,"305":1,"306":0,"307":0,"308":0,"309":1,"310":1,"311":1,"312":0,"313":0,"314":1,"315":0,"316":0,"317":0,"318":1,"319":1,"320":1,"321":0,"322":1,"323":0,"324":0,"325":0,"326":1,"327":1,"328":0,"329":1,"330":1,"331":1,"332":0,"333":0,"334":1,"335":1,"336":1,"337":0,"338":0,"339":0,"340":1,"341":1,"342":0,"343":0,"344":0,"345":1,"346":1,"347":0,"348":0,"349":0,"350":1,"351":0,"352":0,"353":1,"354":0,"355":0,"356":0,"357":0},"x_3":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":1,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":1,"65":0,"66":0,"67":1,"68":1,"69":1,"70":1,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":1,"87":1,"88":1,"89":0,"90":0,"91":1,"92":1,"93":1,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"101":0,"102":0,"103":0,"104":0,"105":0,"106":0,"107":0,"108":0,"109":1,"110":0,"111":1,"112":1,"113":0,"114":1,"115":1,"116":1,"117":0,"118":0,"119":0,"120":0,"121":1,"122":0,"123":0,"124":0,"125":0,"126":0,"127":0,"128":0,"129":0,"130":0,"131":0,"132":0,"133":0,"134":1,"135":0,"136":0,"137":1,"138":1,"139":1,"140":1,"141":0,"142":0,"143":0,"144":0,"145":0,"146":0,"147":0,"148":0,"149":0,"150":0,"151":0,"152":0,"153":0,"154":1,"155":1,"156":1,"157":0,"158":0,"159":0,"160":0,"161":0,"162":0,"163":0,"164":0,"165":0,"166":0,"167":0,"168":1,"169":1,"170":1,"171":1,"172":0,"173":0,"174":0,"175":0,"176":0,"177":0,"178":0,"179":0,"180":0,"181":0,"182":0,"183":0,"184":0,"185":1,"186":1,"187":0,"188":1,"189":0,"190":0,"191":0,"192":0,"193":0,"194":0,"195":0,"196":0,"197":0,"198":1,"199":1,"200":1,"201":0,"202":1,"203":0,"204":0,"205":0,"206":0,"207":0,"208":0,"209":0,"210":0,"211":1,"212":1,"213":0,"214":0,"215":1,"216":1,"217":0,"218":0,"219":0,"220":0,"221":1,"222":0,"223":0,"224":0,"225":0,"226":0,"227":0,"228":0,"229":1,"230":0,"231":0,"232":0,"233":0,"234":0,"235":0,"236":0,"237":0,"238":1,"239":1,"240":1,"241":1,"242":1,"243":0,"244":0,"245":0,"246":0,"247":0,"248":0,"249":0,"250":0,"251":1,"252":1,"253":0,"254":0,"255":0,"256":0,"257":0,"258":1,"259":0,"260":0,"261":0,"262":0,"263":0,"264":0,"265":0,"266":1,"267":0,"268":0,"269":0,"270":1,"271":0,"272":0,"273":0,"274":0,"275":0,"276":1,"277":0,"278":0,"279":1,"280":0,"281":1,"282":0,"283":1,"284":0,"285":0,"286":0,"287":0,"288":0,"289":0,"290":1,"291":0,"292":1,"293":1,"294":0,"295":0,"296":0,"297":0,"298":1,"299":1,"300":1,"301":0,"302":0,"303":0,"304":0,"305":0,"306":0,"307":1,"308":1,"309":0,"310":0,"311":0,"312":0,"313":1,"314":0,"315":0,"316":0,"317":1,"318":0,"319":0,"320":0,"321":0,"322":1,"323":1,"324":0,"325":1,"326":0,"327":0,"328":1,"329":0,"330":0,"331":0,"332":1,"333":1,"334":0,"335":0,"336":0,"337":1,"338":1,"339":0,"340":0,"341":0,"342":1,"343":1,"344":1,"345":0,"346":0,"347":1,"348":1,"349":1,"350":0,"351":1,"352":1,"353":0,"354":1,"355":1,"356":1,"357":1},"x_4":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":0,"9":0,"10":0,"11":0,"12":1,"13":0,"14":1,"15":0,"16":1,"17":0,"18":1,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":1,"30":1,"31":0,"32":1,"33":0,"34":1,"35":0,"36":0,"37":1,"38":0,"39":0,"40":0,"41":0,"42":1,"43":0,"44":1,"45":1,"46":1,"47":1,"48":1,"49":0,"50":0,"51":1,"52":1,"53":1,"54":1,"55":0,"56":1,"57":1,"58":1,"59":1,"60":0,"61":0,"62":1,"63":1,"64":1,"65":1,"66":1,"67":1,"68":0,"69":1,"70":1,"71":0,"72":1,"73":1,"74":1,"75":0,"76":1,"77":1,"78":1,"79":1,"80":0,"81":0,"82":0,"83":0,"84":1,"85":1,"86":0,"87":1,"88":1,"89":1,"90":1,"91":0,"92":1,"93":0,"94":0,"95":0,"96":0,"97":1,"98":0,"99":1,"100":1,"101":0,"102":0,"103":1,"104":0,"105":1,"106":1,"107":0,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":1,"115":0,"116":0,"117":0,"118":0,"119":1,"120":1,"121":0,"122":1,"123":0,"124":0,"125":0,"126":0,"127":0,"128":1,"129":1,"130":1,"131":1,"132":0,"133":0,"134":0,"135":0,"136":0,"137":0,"138":1,"139":0,"140":0,"141":1,"142":1,"143":0,"144":1,"145":0,"146":0,"147":1,"148":0,"149":0,"150":1,"151":1,"152":0,"153":0,"154":1,"155":0,"156":0,"157":0,"158":0,"159":0,"160":1,"161":1,"162":1,"163":0,"164":1,"165":0,"166":0,"167":0,"168":0,"169":0,"170":0,"171":1,"172":0,"173":0,"174":0,"175":1,"176":0,"177":1,"178":0,"179":0,"180":0,"181":1,"182":1,"183":1,"184":1,"185":0,"186":0,"187":1,"188":1,"189":0,"190":0,"191":0,"192":0,"193":0,"194":1,"195":1,"196":1,"197":1,"198":0,"199":0,"200":0,"201":1,"202":0,"203":0,"204":0,"205":0,"206":0,"207":0,"208":0,"209":0,"210":0,"211":1,"212":0,"213":1,"214":1,"215":0,"216":0,"217":1,"218":1,"219":0,"220":0,"221":1,"222":0,"223":0,"224":0,"225":1,"226":1,"227":1,"228":1,"229":0,"230":0,"231":0,"232":0,"233":0,"234":1,"235":0,"236":1,"237":1,"238":0,"239":0,"240":0,"241":0,"242":1,"243":0,"244":1,"245":0,"246":1,"247":0,"248":0,"249":1,"250":1,"251":0,"252":1,"253":0,"254":0,"255":1,"256":0,"257":1,"258":0,"259":0,"260":1,"261":0,"262":1,"263":1,"264":0,"265":0,"266":1,"267":1,"268":1,"269":1,"270":0,"271":1,"272":1,"273":0,"274":0,"275":0,"276":1,"277":0,"278":0,"279":0,"280":1,"281":0,"282":0,"283":0,"284":0,"285":0,"286":0,"287":1,"288":0,"289":0,"290":0,"291":1,"292":1,"293":1,"294":0,"295":1,"296":1,"297":1,"298":0,"299":0,"300":1,"301":0,"302":1,"303":1,"304":0,"305":0,"306":0,"307":0,"308":1,"309":1,"310":1,"311":0,"312":1,"313":0,"314":0,"315":0,"316":0,"317":0,"318":0,"319":1,"320":1,"321":1,"322":0,"323":0,"324":0,"325":0,"326":1,"327":1,"328":1,"329":1,"330":1,"331":0,"332":0,"333":0,"334":1,"335":1,"336":1,"337":0,"338":0,"339":0,"340":1,"341":1,"342":1,"343":0,"344":0,"345":1,"346":1,"347":0,"348":0,"349":0,"350":1,"351":0,"352":0,"353":1,"354":0,"355":0,"356":1,"357":0},"x_5":{"0":1,"1":0,"2":0,"3":1,"4":1,"5":0,"6":1,"7":1,"8":1,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":1,"16":0,"17":1,"18":0,"19":1,"20":1,"21":1,"22":0,"23":0,"24":1,"25":1,"26":0,"27":1,"28":0,"29":1,"30":1,"31":0,"32":0,"33":1,"34":1,"35":0,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":0,"43":0,"44":1,"45":1,"46":1,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":1,"55":1,"56":0,"57":0,"58":0,"59":1,"60":0,"61":0,"62":1,"63":1,"64":0,"65":0,"66":0,"67":1,"68":0,"69":1,"70":1,"71":1,"72":1,"73":1,"74":0,"75":0,"76":1,"77":1,"78":0,"79":1,"80":0,"81":1,"82":1,"83":1,"84":0,"85":0,"86":0,"87":0,"88":0,"89":1,"90":1,"91":1,"92":0,"93":1,"94":1,"95":0,"96":1,"97":0,"98":1,"99":1,"100":1,"101":1,"102":1,"103":0,"104":0,"105":1,"106":1,"107":0,"108":0,"109":0,"110":1,"111":1,"112":0,"113":1,"114":1,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":1,"122":0,"123":0,"124":1,"125":1,"126":0,"127":0,"128":1,"129":1,"130":0,"131":0,"132":1,"133":1,"134":1,"135":0,"136":0,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":0,"144":0,"145":1,"146":1,"147":0,"148":0,"149":0,"150":0,"151":0,"152":0,"153":0,"154":0,"155":0,"156":0,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1,"163":0,"164":0,"165":0,"166":1,"167":1,"168":0,"169":0,"170":0,"171":0,"172":0,"173":0,"174":0,"175":0,"176":1,"177":0,"178":1,"179":1,"180":1,"181":1,"182":1,"183":1,"184":0,"185":1,"186":1,"187":0,"188":1,"189":0,"190":0,"191":0,"192":0,"193":1,"194":0,"195":0,"196":1,"197":1,"198":1,"199":1,"200":0,"201":0,"202":0,"203":1,"204":1,"205":1,"206":0,"207":0,"208":0,"209":1,"210":1,"211":1,"212":0,"213":1,"214":1,"215":1,"216":1,"217":1,"218":0,"219":1,"220":1,"221":0,"222":0,"223":1,"224":1,"225":0,"226":1,"227":1,"228":1,"229":0,"230":0,"231":0,"232":0,"233":1,"234":1,"235":1,"236":0,"237":0,"238":1,"239":1,"240":1,"241":1,"242":0,"243":1,"244":0,"245":0,"246":0,"247":0,"248":0,"249":1,"250":1,"251":0,"252":1,"253":0,"254":0,"255":0,"256":1,"257":0,"258":1,"259":1,"260":1,"261":0,"262":0,"263":0,"264":1,"265":1,"266":1,"267":1,"268":1,"269":1,"270":0,"271":0,"272":0,"273":0,"274":1,"275":1,"276":0,"277":1,"278":1,"279":1,"280":1,"281":1,"282":0,"283":0,"284":0,"285":0,"286":0,"287":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":0,"294":0,"295":0,"296":0,"297":1,"298":1,"299":1,"300":1,"301":0,"302":0,"303":0,"304":1,"305":1,"306":1,"307":0,"308":1,"309":1,"310":1,"311":1,"312":1,"313":0,"314":1,"315":1,"316":1,"317":1,"318":0,"319":0,"320":0,"321":1,"322":1,"323":1,"324":0,"325":0,"326":0,"327":0,"328":1,"329":1,"330":1,"331":0,"332":1,"333":1,"334":1,"335":1,"336":0,"337":1,"338":0,"339":1,"340":0,"341":1,"342":1,"343":0,"344":1,"345":1,"346":0,"347":1,"348":1,"349":1,"350":1,"351":0,"352":1,"353":1,"354":1,"355":1,"356":0,"357":1},"x_6":{"0":1,"1":0,"2":1,"3":0,"4":0,"5":1,"6":1,"7":0,"8":0,"9":0,"10":1,"11":0,"12":0,"13":0,"14":0,"15":0,"16":1,"17":1,"18":0,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":0,"26":1,"27":1,"28":1,"29":0,"30":1,"31":0,"32":0,"33":0,"34":1,"35":0,"36":0,"37":1,"38":0,"39":1,"40":1,"41":0,"42":0,"43":1,"44":0,"45":0,"46":1,"47":1,"48":1,"49":1,"50":0,"51":0,"52":1,"53":1,"54":1,"55":0,"56":1,"57":1,"58":0,"59":0,"60":0,"61":0,"62":1,"63":0,"64":1,"65":1,"66":0,"67":0,"68":0,"69":0,"70":1,"71":0,"72":0,"73":0,"74":0,"75":1,"76":1,"77":0,"78":0,"79":0,"80":0,"81":1,"82":1,"83":1,"84":0,"85":1,"86":0,"87":0,"88":0,"89":1,"90":0,"91":0,"92":1,"93":1,"94":1,"95":0,"96":1,"97":1,"98":1,"99":1,"100":1,"101":0,"102":1,"103":0,"104":1,"105":0,"106":1,"107":0,"108":1,"109":1,"110":1,"111":1,"112":1,"113":0,"114":0,"115":1,"116":0,"117":1,"118":1,"119":0,"120":0,"121":0,"122":1,"123":1,"124":0,"125":0,"126":0,"127":1,"128":0,"129":0,"130":1,"131":0,"132":1,"133":0,"134":0,"135":1,"136":0,"137":0,"138":1,"139":1,"140":0,"141":1,"142":1,"143":0,"144":1,"145":1,"146":0,"147":1,"148":0,"149":0,"150":0,"151":0,"152":1,"153":0,"154":1,"155":0,"156":1,"157":0,"158":0,"159":1,"160":0,"161":0,"162":0,"163":1,"164":0,"165":1,"166":0,"167":1,"168":0,"169":0,"170":1,"171":0,"172":0,"173":0,"174":0,"175":0,"176":1,"177":1,"178":1,"179":0,"180":0,"181":1,"182":1,"183":0,"184":1,"185":1,"186":1,"187":0,"188":1,"189":1,"190":0,"191":1,"192":1,"193":0,"194":1,"195":0,"196":1,"197":0,"198":0,"199":0,"200":0,"201":1,"202":0,"203":0,"204":0,"205":1,"206":0,"207":1,"208":0,"209":0,"210":1,"211":0,"212":0,"213":0,"214":0,"215":1,"216":1,"217":0,"218":0,"219":0,"220":1,"221":0,"222":1,"223":1,"224":1,"225":0,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"232":1,"233":0,"234":1,"235":0,"236":1,"237":1,"238":0,"239":0,"240":0,"241":0,"242":1,"243":0,"244":0,"245":0,"246":1,"247":0,"248":0,"249":1,"250":0,"251":0,"252":0,"253":0,"254":1,"255":1,"256":1,"257":0,"258":1,"259":1,"260":0,"261":1,"262":0,"263":0,"264":1,"265":1,"266":1,"267":1,"268":0,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":0,"276":1,"277":0,"278":0,"279":0,"280":0,"281":0,"282":0,"283":0,"284":0,"285":0,"286":0,"287":1,"288":1,"289":1,"290":1,"291":0,"292":0,"293":0,"294":0,"295":0,"296":0,"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":0,"304":0,"305":0,"306":0,"307":0,"308":0,"309":0,"310":0,"311":0,"312":0,"313":0,"314":1,"315":0,"316":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":0,"323":0,"324":0,"325":0,"326":0,"327":1,"328":0,"329":1,"330":1,"331":1,"332":1,"333":0,"334":0,"335":1,"336":0,"337":0,"338":1,"339":0,"340":1,"341":0,"342":1,"343":0,"344":1,"345":1,"346":1,"347":0,"348":0,"349":0,"350":1,"351":1,"352":0,"353":0,"354":1,"355":1,"356":0,"357":0},"x_7":{"0":1,"1":0,"2":1,"3":1,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":1,"12":1,"13":0,"14":0,"15":1,"16":0,"17":0,"18":1,"19":0,"20":0,"21":0,"22":1,"23":0,"24":1,"25":0,"26":1,"27":1,"28":1,"29":1,"30":1,"31":0,"32":0,"33":1,"34":0,"35":1,"36":0,"37":0,"38":0,"39":1,"40":1,"41":0,"42":1,"43":0,"44":1,"45":1,"46":0,"47":1,"48":1,"49":0,"50":1,"51":0,"52":0,"53":1,"54":1,"55":1,"56":1,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":1,"67":0,"68":0,"69":0,"70":0,"71":0,"72":1,"73":0,"74":0,"75":0,"76":1,"77":0,"78":1,"79":1,"80":0,"81":0,"82":0,"83":0,"84":1,"85":1,"86":1,"87":0,"88":1,"89":1,"90":1,"91":1,"92":0,"93":0,"94":0,"95":1,"96":1,"97":0,"98":1,"99":0,"100":1,"101":1,"102":0,"103":1,"104":1,"105":1,"106":1,"107":0,"108":1,"109":1,"110":1,"111":1,"112":0,"113":0,"114":1,"115":1,"116":0,"117":1,"118":0,"119":0,"120":0,"121":0,"122":0,"123":0,"124":1,"125":0,"126":1,"127":1,"128":0,"129":1,"130":0,"131":0,"132":0,"133":0,"134":1,"135":0,"136":1,"137":0,"138":0,"139":1,"140":0,"141":0,"142":0,"143":1,"144":1,"145":1,"146":0,"147":1,"148":1,"149":0,"150":1,"151":0,"152":0,"153":1,"154":1,"155":1,"156":0,"157":1,"158":1,"159":1,"160":0,"161":0,"162":0,"163":1,"164":0,"165":0,"166":1,"167":1,"168":0,"169":0,"170":0,"171":1,"172":0,"173":0,"174":0,"175":1,"176":0,"177":0,"178":0,"179":1,"180":0,"181":0,"182":1,"183":1,"184":1,"185":0,"186":0,"187":0,"188":1,"189":1,"190":1,"191":1,"192":1,"193":1,"194":0,"195":0,"196":0,"197":0,"198":0,"199":0,"200":0,"201":0,"202":0,"203":0,"204":0,"205":1,"206":0,"207":0,"208":1,"209":1,"210":0,"211":1,"212":1,"213":1,"214":0,"215":0,"216":0,"217":0,"218":0,"219":1,"220":1,"221":0,"222":1,"223":0,"224":1,"225":1,"226":1,"227":0,"228":1,"229":0,"230":1,"231":0,"232":1,"233":0,"234":0,"235":1,"236":0,"237":0,"238":1,"239":0,"240":0,"241":0,"242":1,"243":0,"244":0,"245":1,"246":1,"247":1,"248":1,"249":0,"250":0,"251":1,"252":0,"253":0,"254":0,"255":0,"256":1,"257":1,"258":0,"259":0,"260":1,"261":1,"262":0,"263":1,"264":1,"265":0,"266":1,"267":0,"268":0,"269":1,"270":0,"271":0,"272":0,"273":0,"274":0,"275":1,"276":0,"277":0,"278":0,"279":1,"280":1,"281":0,"282":0,"283":1,"284":1,"285":1,"286":1,"287":0,"288":1,"289":1,"290":1,"291":0,"292":0,"293":0,"294":0,"295":0,"296":0,"297":0,"298":0,"299":0,"300":0,"301":0,"302":1,"303":1,"304":1,"305":1,"306":0,"307":0,"308":0,"309":0,"310":0,"311":0,"312":1,"313":1,"314":0,"315":0,"316":1,"317":1,"318":0,"319":0,"320":0,"321":0,"322":0,"323":1,"324":0,"325":0,"326":0,"327":1,"328":0,"329":0,"330":0,"331":1,"332":0,"333":0,"334":0,"335":1,"336":1,"337":0,"338":0,"339":1,"340":0,"341":1,"342":0,"343":0,"344":0,"345":0,"346":1,"347":0,"348":0,"349":0,"350":1,"351":0,"352":1,"353":1,"354":0,"355":1,"356":0,"357":1},"x_8":{"0":0,"1":1,"2":0,"3":0,"4":1,"5":1,"6":1,"7":1,"8":0,"9":0,"10":0,"11":1,"12":1,"13":0,"14":1,"15":1,"16":1,"17":1,"18":0,"19":1,"20":0,"21":0,"22":1,"23":1,"24":1,"25":0,"26":1,"27":1,"28":1,"29":1,"30":0,"31":0,"32":0,"33":1,"34":0,"35":0,"36":0,"37":1,"38":1,"39":1,"40":1,"41":1,"42":0,"43":0,"44":0,"45":0,"46":0,"47":1,"48":0,"49":0,"50":1,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":1,"59":0,"60":1,"61":0,"62":0,"63":0,"64":0,"65":0,"66":1,"67":0,"68":0,"69":0,"70":0,"71":1,"72":0,"73":1,"74":0,"75":1,"76":0,"77":0,"78":0,"79":0,"80":1,"81":1,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":1,"90":0,"91":0,"92":0,"93":0,"94":1,"95":1,"96":0,"97":1,"98":0,"99":0,"100":0,"101":1,"102":1,"103":0,"104":0,"105":0,"106":1,"107":1,"108":0,"109":0,"110":0,"111":0,"112":0,"113":0,"114":0,"115":0,"116":1,"117":1,"118":1,"119":0,"120":1,"121":0,"122":0,"123":1,"124":0,"125":1,"126":1,"127":0,"128":0,"129":1,"130":0,"131":1,"132":1,"133":1,"134":0,"135":1,"136":0,"137":1,"138":0,"139":0,"140":0,"141":1,"142":0,"143":0,"144":1,"145":1,"146":1,"147":0,"148":1,"149":1,"150":0,"151":0,"152":1,"153":0,"154":0,"155":0,"156":1,"157":0,"158":1,"159":0,"160":0,"161":1,"162":1,"163":0,"164":0,"165":0,"166":1,"167":0,"168":0,"169":1,"170":0,"171":0,"172":0,"173":0,"174":1,"175":1,"176":1,"177":0,"178":0,"179":0,"180":1,"181":0,"182":0,"183":1,"184":1,"185":0,"186":1,"187":0,"188":0,"189":1,"190":0,"191":0,"192":1,"193":0,"194":1,"195":0,"196":0,"197":0,"198":1,"199":1,"200":0,"201":1,"202":0,"203":0,"204":0,"205":0,"206":1,"207":0,"208":1,"209":0,"210":0,"211":0,"212":1,"213":1,"214":0,"215":0,"216":0,"217":0,"218":1,"219":0,"220":1,"221":1,"222":0,"223":0,"224":1,"225":0,"226":1,"227":1,"228":1,"229":1,"230":0,"231":0,"232":0,"233":0,"234":1,"235":1,"236":0,"237":0,"238":1,"239":0,"240":0,"241":0,"242":0,"243":1,"244":0,"245":0,"246":1,"247":0,"248":0,"249":1,"250":1,"251":0,"252":1,"253":0,"254":0,"255":0,"256":0,"257":1,"258":1,"259":0,"260":0,"261":1,"262":0,"263":1,"264":0,"265":0,"266":0,"267":0,"268":0,"269":1,"270":0,"271":1,"272":1,"273":1,"274":0,"275":0,"276":1,"277":0,"278":0,"279":0,"280":1,"281":0,"282":1,"283":1,"284":0,"285":0,"286":0,"287":0,"288":1,"289":1,"290":1,"291":0,"292":0,"293":1,"294":1,"295":0,"296":0,"297":1,"298":0,"299":0,"300":1,"301":0,"302":1,"303":1,"304":0,"305":0,"306":1,"307":0,"308":1,"309":0,"310":0,"311":1,"312":0,"313":0,"314":0,"315":0,"316":0,"317":0,"318":0,"319":0,"320":0,"321":0,"322":0,"323":0,"324":1,"325":1,"326":0,"327":0,"328":0,"329":0,"330":0,"331":1,"332":0,"333":1,"334":0,"335":0,"336":0,"337":0,"338":1,"339":1,"340":0,"341":0,"342":0,"343":1,"344":1,"345":0,"346":0,"347":1,"348":1,"349":1,"350":0,"351":1,"352":1,"353":0,"354":1,"355":1,"356":1,"357":1},"x_9":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":1,"32":0,"33":0,"34":0,"35":0,"36":1,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":1,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":1,"58":0,"59":0,"60":0,"61":1,"62":1,"63":1,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":1,"75":0,"76":0,"77":0,"78":1,"79":1,"80":0,"81":0,"82":1,"83":0,"84":1,"85":1,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":1,"99":1,"100":1,"101":0,"102":0,"103":0,"104":0,"105":1,"106":0,"107":1,"108":1,"109":0,"110":0,"111":0,"112":0,"113":1,"114":0,"115":0,"116":0,"117":0,"118":0,"119":0,"120":0,"121":0,"122":0,"123":1,"124":1,"125":1,"126":0,"127":0,"128":0,"129":0,"130":1,"131":1,"132":1,"133":1,"134":0,"135":0,"136":1,"137":0,"138":0,"139":0,"140":0,"141":0,"142":0,"143":0,"144":0,"145":0,"146":0,"147":1,"148":1,"149":1,"150":0,"151":1,"152":1,"153":1,"154":0,"155":0,"156":0,"157":0,"158":0,"159":0,"160":1,"161":1,"162":0,"163":0,"164":0,"165":1,"166":1,"167":1,"168":0,"169":0,"170":0,"171":0,"172":1,"173":1,"174":0,"175":0,"176":1,"177":0,"178":1,"179":1,"180":0,"181":0,"182":1,"183":0,"184":0,"185":0,"186":0,"187":0,"188":0,"189":1,"190":0,"191":1,"192":0,"193":0,"194":1,"195":1,"196":0,"197":1,"198":0,"199":0,"200":0,"201":0,"202":0,"203":1,"204":1,"205":0,"206":0,"207":1,"208":1,"209":0,"210":1,"211":0,"212":0,"213":0,"214":0,"215":0,"216":0,"217":0,"218":1,"219":1,"220":1,"221":0,"222":1,"223":0,"224":0,"225":0,"226":0,"227":1,"228":0,"229":0,"230":0,"231":1,"232":0,"233":1,"234":0,"235":1,"236":0,"237":1,"238":0,"239":0,"240":0,"241":0,"242":0,"243":0,"244":0,"245":1,"246":0,"247":0,"248":1,"249":0,"250":1,"251":0,"252":0,"253":1,"254":0,"255":0,"256":1,"257":1,"258":0,"259":1,"260":0,"261":1,"262":1,"263":0,"264":0,"265":1,"266":0,"267":1,"268":0,"269":0,"270":0,"271":0,"272":1,"273":0,"274":0,"275":1,"276":0,"277":0,"278":1,"279":0,"280":1,"281":0,"282":1,"283":0,"284":1,"285":0,"286":1,"287":0,"288":0,"289":1,"290":0,"291":1,"292":0,"293":0,"294":0,"295":1,"296":1,"297":1,"298":0,"299":0,"300":0,"301":1,"302":1,"303":0,"304":1,"305":1,"306":1,"307":1,"308":0,"309":1,"310":1,"311":0,"312":1,"313":0,"314":1,"315":0,"316":1,"317":0,"318":0,"319":1,"320":1,"321":1,"322":0,"323":0,"324":1,"325":0,"326":1,"327":0,"328":0,"329":1,"330":1,"331":0,"332":1,"333":0,"334":1,"335":0,"336":1,"337":1,"338":0,"339":1,"340":1,"341":1,"342":0,"343":0,"344":0,"345":1,"346":1,"347":0,"348":0,"349":0,"350":1,"351":0,"352":0,"353":1,"354":0,"355":0,"356":0,"357":0},"chain_break_fraction":{"0":0.0,"1":0.0,"2":0.0,"3":0.0,"4":0.0,"5":0.0,"6":0.0,"7":0.0,"8":0.0,"9":0.0,"10":0.0,"11":0.0,"12":0.0,"13":0.0,"14":0.0,"15":0.0,"16":0.0,"17":0.0,"18":0.0,"19":0.1,"20":0.0,"21":0.1,"22":0.1,"23":0.0,"24":0.0,"25":0.0,"26":0.0,"27":0.1,"28":0.0,"29":0.0,"30":0.0,"31":0.0,"32":0.0,"33":0.0,"34":0.0,"35":0.0,"36":0.0,"37":0.0,"38":0.1,"39":0.1,"40":0.0,"41":0.0,"42":0.0,"43":0.0,"44":0.0,"45":0.1,"46":0.0,"47":0.0,"48":0.0,"49":0.0,"50":0.0,"51":0.0,"52":0.0,"53":0.0,"54":0.0,"55":0.0,"56":0.0,"57":0.0,"58":0.0,"59":0.0,"60":0.0,"61":0.0,"62":0.0,"63":0.0,"64":0.0,"65":0.0,"66":0.0,"67":0.0,"68":0.0,"69":0.0,"70":0.0,"71":0.0,"72":0.0,"73":0.0,"74":0.0,"75":0.0,"76":0.0,"77":0.0,"78":0.0,"79":0.0,"80":0.0,"81":0.0,"82":0.0,"83":0.0,"84":0.0,"85":0.0,"86":0.0,"87":0.0,"88":0.0,"89":0.0,"90":0.0,"91":0.0,"92":0.0,"93":0.0,"94":0.0,"95":0.0,"96":0.0,"97":0.0,"98":0.0,"99":0.0,"100":0.0,"101":0.0,"102":0.0,"103":0.0,"104":0.0,"105":0.0,"106":0.0,"107":0.0,"108":0.0,"109":0.0,"110":0.0,"111":0.0,"112":0.0,"113":0.0,"114":0.0,"115":0.0,"116":0.0,"117":0.0,"118":0.0,"119":0.0,"120":0.0,"121":0.0,"122":0.0,"123":0.0,"124":0.0,"125":0.0,"126":0.0,"127":0.0,"128":0.0,"129":0.0,"130":0.0,"131":0.0,"132":0.0,"133":0.0,"134":0.0,"135":0.0,"136":0.0,"137":0.0,"138":0.0,"139":0.0,"140":0.0,"141":0.0,"142":0.0,"143":0.0,"144":0.0,"145":0.0,"146":0.0,"147":0.0,"148":0.0,"149":0.0,"150":0.0,"151":0.0,"152":0.0,"153":0.0,"154":0.0,"155":0.0,"156":0.0,"157":0.0,"158":0.0,"159":0.0,"160":0.0,"161":0.0,"162":0.0,"163":0.0,"164":0.0,"165":0.0,"166":0.0,"167":0.0,"168":0.0,"169":0.0,"170":0.0,"171":0.0,"172":0.1,"173":0.0,"174":0.0,"175":0.0,"176":0.0,"177":0.0,"178":0.0,"179":0.0,"180":0.0,"181":0.0,"182":0.0,"183":0.0,"184":0.0,"185":0.0,"186":0.0,"187":0.0,"188":0.0,"189":0.0,"190":0.0,"191":0.0,"192":0.0,"193":0.0,"194":0.0,"195":0.0,"196":0.0,"197":0.0,"198":0.1,"199":0.0,"200":0.0,"201":0.0,"202":0.1,"203":0.1,"204":0.0,"205":0.0,"206":0.0,"207":0.0,"208":0.0,"209":0.0,"210":0.0,"211":0.0,"212":0.0,"213":0.0,"214":0.0,"215":0.0,"216":0.1,"217":0.0,"218":0.0,"219":0.0,"220":0.0,"221":0.0,"222":0.0,"223":0.0,"224":0.0,"225":0.0,"226":0.0,"227":0.0,"228":0.0,"229":0.0,"230":0.0,"231":0.0,"232":0.0,"233":0.0,"234":0.0,"235":0.0,"236":0.0,"237":0.0,"238":0.0,"239":0.0,"240":0.0,"241":0.1,"242":0.0,"243":0.0,"244":0.0,"245":0.0,"246":0.0,"247":0.0,"248":0.0,"249":0.0,"250":0.0,"251":0.0,"252":0.0,"253":0.0,"254":0.0,"255":0.0,"256":0.0,"257":0.0,"258":0.0,"259":0.0,"260":0.0,"261":0.0,"262":0.0,"263":0.0,"264":0.0,"265":0.0,"266":0.0,"267":0.0,"268":0.0,"269":0.0,"270":0.1,"271":0.0,"272":0.0,"273":0.0,"274":0.0,"275":0.0,"276":0.0,"277":0.0,"278":0.0,"279":0.0,"280":0.0,"281":0.0,"282":0.0,"283":0.0,"284":0.1,"285":0.0,"286":0.0,"287":0.0,"288":0.0,"289":0.0,"290":0.0,"291":0.0,"292":0.0,"293":0.0,"294":0.0,"295":0.0,"296":0.1,"297":0.0,"298":0.0,"299":0.1,"300":0.0,"301":0.0,"302":0.0,"303":0.0,"304":0.0,"305":0.1,"306":0.0,"307":0.0,"308":0.0,"309":0.1,"310":0.0,"311":0.0,"312":0.0,"313":0.0,"314":0.0,"315":0.0,"316":0.0,"317":0.0,"318":0.0,"319":0.0,"320":0.1,"321":0.0,"322":0.0,"323":0.0,"324":0.0,"325":0.1,"326":0.1,"327":0.0,"328":0.0,"329":0.0,"330":0.1,"331":0.0,"332":0.0,"333":0.1,"334":0.1,"335":0.0,"336":0.1,"337":0.0,"338":0.1,"339":0.0,"340":0.1,"341":0.1,"342":0.1,"343":0.1,"344":0.1,"345":0.1,"346":0.1,"347":0.2,"348":0.1,"349":0.2,"350":0.1,"351":0.1,"352":0.1,"353":0.1,"354":0.1,"355":0.1,"356":0.1,"357":0.1},"energy":{"0":-71.0,"1":-69.0,"2":-69.0,"3":-63.0,"4":-63.0,"5":-63.0,"6":-61.0,"7":-61.0,"8":-57.0,"9":-57.0,"10":-55.0,"11":-55.0,"12":-55.0,"13":-55.0,"14":-55.0,"15":-53.0,"16":-53.0,"17":-53.0,"18":-53.0,"19":-53.0,"20":-49.0,"21":-49.0,"22":-47.0,"23":-47.0,"24":-47.0,"25":-47.0,"26":-47.0,"27":-47.0,"28":-45.0,"29":-45.0,"30":-45.0,"31":-45.0,"32":-41.0,"33":-39.0,"34":-39.0,"35":-39.0,"36":-39.0,"37":-39.0,"38":-37.0,"39":-37.0,"40":-37.0,"41":-37.0,"42":-34.0,"43":-33.0,"44":-32.0,"45":-32.0,"46":-32.0,"47":-31.0,"48":-31.0,"49":-29.0,"50":-29.0,"51":-27.0,"52":-26.0,"53":-26.0,"54":-26.0,"55":-25.0,"56":-24.0,"57":-24.0,"58":-24.0,"59":-23.0,"60":-23.0,"61":-23.0,"62":-22.0,"63":-22.0,"64":-21.0,"65":-21.0,"66":-21.0,"67":-21.0,"68":-20.0,"69":-19.0,"70":-19.0,"71":-18.0,"72":-18.0,"73":-18.0,"74":-16.0,"75":-16.0,"76":-16.0,"77":-16.0,"78":-16.0,"79":-16.0,"80":-16.0,"81":-15.0,"82":-15.0,"83":-15.0,"84":-14.0,"85":-14.0,"86":-13.0,"87":-13.0,"88":-13.0,"89":-13.0,"90":-13.0,"91":-11.0,"92":-11.0,"93":-11.0,"94":-10.0,"95":-10.0,"96":-10.0,"97":-8.0,"98":-8.0,"99":-8.0,"100":-8.0,"101":-8.0,"102":-8.0,"103":-8.0,"104":-7.0,"105":-6.0,"106":-6.0,"107":-6.0,"108":-6.0,"109":-5.0,"110":-5.0,"111":-5.0,"112":-5.0,"113":-5.0,"114":-3.0,"115":-3.0,"116":-3.0,"117":-2.0,"118":-2.0,"119":-2.0,"120":-2.0,"121":-2.0,"122":-1.0,"123":0.0,"124":0.0,"125":0.0,"126":0.0,"127":0.0,"128":1.0,"129":1.0,"130":2.0,"131":2.0,"132":2.0,"133":2.0,"134":3.0,"135":3.0,"136":3.0,"137":3.0,"138":3.0,"139":5.0,"140":5.0,"141":6.0,"142":6.0,"143":7.0,"144":8.0,"145":8.0,"146":8.0,"147":8.0,"148":8.0,"149":8.0,"150":9.0,"151":9.0,"152":10.0,"153":10.0,"154":11.0,"155":13.0,"156":13.0,"157":14.0,"158":14.0,"159":15.0,"160":16.0,"161":16.0,"162":16.0,"163":17.0,"164":17.0,"165":17.0,"166":18.0,"167":18.0,"168":19.0,"169":19.0,"170":20.0,"171":21.0,"172":22.0,"173":22.0,"174":22.0,"175":24.0,"176":24.0,"177":24.0,"178":24.0,"179":25.0,"180":25.0,"181":25.0,"182":26.0,"183":26.0,"184":27.0,"185":27.0,"186":27.0,"187":27.0,"188":29.0,"189":32.0,"190":32.0,"191":32.0,"192":32.0,"193":33.0,"194":34.0,"195":34.0,"196":35.0,"197":35.0,"198":37.0,"199":37.0,"200":38.0,"201":38.0,"202":38.0,"203":40.0,"204":40.0,"205":40.0,"206":41.0,"207":42.0,"208":42.0,"209":43.0,"210":43.0,"211":43.0,"212":45.0,"213":46.0,"214":46.0,"215":46.0,"216":46.0,"217":47.0,"218":48.0,"219":50.0,"220":50.0,"221":51.0,"222":51.0,"223":54.0,"224":54.0,"225":55.0,"226":56.0,"227":56.0,"228":57.0,"229":59.0,"230":62.0,"231":62.0,"232":63.0,"233":64.0,"234":64.0,"235":64.0,"236":65.0,"237":65.0,"238":67.0,"239":67.0,"240":68.0,"241":68.0,"242":69.0,"243":71.0,"244":72.0,"245":72.0,"246":72.0,"247":73.0,"248":73.0,"249":74.0,"250":74.0,"251":76.0,"252":77.0,"253":80.0,"254":80.0,"255":81.0,"256":81.0,"257":82.0,"258":85.0,"259":88.0,"260":89.0,"261":90.0,"262":91.0,"263":94.0,"264":97.0,"265":98.0,"266":99.0,"267":99.0,"268":99.0,"269":102.0,"270":102.0,"271":104.0,"272":104.0,"273":105.0,"274":107.0,"275":107.0,"276":107.0,"277":110.0,"278":110.0,"279":110.0,"280":112.0,"281":113.0,"282":115.0,"283":115.0,"284":118.0,"285":118.0,"286":118.0,"287":119.0,"288":120.0,"289":120.0,"290":123.0,"291":129.0,"292":132.0,"293":133.0,"294":135.0,"295":136.0,"296":136.0,"297":138.0,"298":140.0,"299":140.0,"300":141.0,"301":144.0,"302":146.0,"303":146.0,"304":152.0,"305":152.0,"306":153.0,"307":157.0,"308":171.0,"309":174.0,"310":174.0,"311":177.0,"312":179.0,"313":182.0,"314":182.0,"315":187.0,"316":187.0,"317":190.0,"318":190.0,"319":216.0,"320":216.0,"321":217.0,"322":219.0,"323":228.0,"324":233.0,"325":236.0,"326":254.0,"327":257.0,"328":262.0,"329":262.0,"330":262.0,"331":281.0,"332":283.0,"333":286.0,"334":304.0,"335":311.0,"336":312.0,"337":325.0,"338":340.0,"339":345.0,"340":358.0,"341":366.0,"342":374.0,"343":390.0,"344":398.0,"345":416.0,"346":424.0,"347":452.0,"348":452.0,"349":452.0,"350":486.0,"351":518.0,"352":526.0,"353":544.0,"354":588.0,"355":670.0,"356":724.0,"357":740.0},"num_occurrences":{"0":63,"1":16,"2":29,"3":85,"4":42,"5":20,"6":44,"7":35,"8":42,"9":17,"10":15,"11":25,"12":19,"13":9,"14":10,"15":44,"16":37,"17":38,"18":14,"19":1,"20":60,"21":1,"22":1,"23":15,"24":32,"25":51,"26":26,"27":1,"28":15,"29":24,"30":27,"31":37,"32":19,"33":22,"34":51,"35":16,"36":109,"37":29,"38":1,"39":1,"40":27,"41":33,"42":9,"43":17,"44":41,"45":1,"46":32,"47":13,"48":11,"49":38,"50":8,"51":17,"52":7,"53":8,"54":22,"55":32,"56":7,"57":19,"58":2,"59":39,"60":9,"61":43,"62":60,"63":69,"64":11,"65":11,"66":17,"67":49,"68":21,"69":40,"70":20,"71":11,"72":17,"73":7,"74":32,"75":8,"76":14,"77":17,"78":21,"79":39,"80":6,"81":10,"82":69,"83":38,"84":15,"85":16,"86":16,"87":13,"88":15,"89":14,"90":18,"91":28,"92":10,"93":25,"94":13,"95":5,"96":21,"97":6,"98":42,"99":46,"100":21,"101":9,"102":7,"103":7,"104":8,"105":45,"106":15,"107":11,"108":20,"109":6,"110":18,"111":10,"112":8,"113":64,"114":17,"115":7,"116":9,"117":5,"118":3,"119":4,"120":6,"121":28,"122":6,"123":14,"124":32,"125":28,"126":4,"127":3,"128":32,"129":27,"130":12,"131":4,"132":30,"133":20,"134":17,"135":3,"136":12,"137":10,"138":22,"139":14,"140":19,"141":9,"142":7,"143":7,"144":4,"145":10,"146":5,"147":14,"148":6,"149":11,"150":6,"151":17,"152":14,"153":8,"154":1,"155":6,"156":3,"157":16,"158":10,"159":14,"160":27,"161":18,"162":5,"163":6,"164":12,"165":20,"166":14,"167":12,"168":7,"169":3,"170":5,"171":2,"172":1,"173":8,"174":1,"175":2,"176":10,"177":1,"178":22,"179":22,"180":7,"181":13,"182":15,"183":6,"184":6,"185":6,"186":10,"187":7,"188":8,"189":5,"190":2,"191":5,"192":1,"193":13,"194":7,"195":11,"196":9,"197":20,"198":1,"199":6,"200":3,"201":1,"202":4,"203":1,"204":16,"205":4,"206":2,"207":5,"208":6,"209":6,"210":16,"211":10,"212":1,"213":5,"214":6,"215":7,"216":1,"217":5,"218":4,"219":8,"220":10,"221":1,"222":7,"223":1,"224":3,"225":5,"226":3,"227":7,"228":5,"229":1,"230":1,"231":5,"232":2,"233":10,"234":4,"235":8,"236":2,"237":7,"238":8,"239":3,"240":11,"241":6,"242":3,"243":1,"244":1,"245":2,"246":1,"247":1,"248":3,"249":7,"250":4,"251":2,"252":3,"253":4,"254":1,"255":2,"256":10,"257":3,"258":4,"259":4,"260":2,"261":3,"262":5,"263":1,"264":3,"265":1,"266":2,"267":4,"268":2,"269":3,"270":5,"271":1,"272":1,"273":1,"274":1,"275":6,"276":1,"277":3,"278":6,"279":4,"280":3,"281":3,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":2,"290":2,"291":9,"292":4,"293":1,"294":1,"295":4,"296":43,"297":2,"298":3,"299":14,"300":1,"301":1,"302":1,"303":1,"304":2,"305":1,"306":4,"307":2,"308":2,"309":54,"310":2,"311":2,"312":3,"313":1,"314":1,"315":1,"316":2,"317":1,"318":1,"319":1,"320":10,"321":1,"322":1,"323":1,"324":1,"325":19,"326":6,"327":1,"328":1,"329":1,"330":8,"331":1,"332":1,"333":61,"334":3,"335":1,"336":22,"337":2,"338":8,"339":1,"340":1,"341":30,"342":1,"343":96,"344":34,"345":1,"346":1,"347":1,"348":332,"349":1,"350":3,"351":62,"352":1,"353":2,"354":136,"355":1,"356":3,"357":1}} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_unbalanced.json b/demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_unbalanced.json new file mode 100644 index 0000000000..bb6639ae4a --- /dev/null +++ b/demonstrations_v2/tutorial_QUBO/QUBO/dwave_results_unbalanced.json @@ -0,0 +1 @@ +{"x_0":{"0":1,"1":1,"2":0,"3":0,"4":1},"x_1":{"0":1,"1":1,"2":1,"3":1,"4":1},"x_2":{"0":0,"1":1,"2":0,"3":1,"4":0},"x_3":{"0":0,"1":0,"2":0,"3":0,"4":1},"x_4":{"0":1,"1":1,"2":1,"3":1,"4":1},"chain_break_fraction":{"0":0.0,"1":0.0,"2":0.0,"3":0.0,"4":0.0},"energy":{"0":-76.9421,"1":-73.5021,"2":-69.93,"3":-69.6064,"4":-60.1776},"num_occurrences":{"0":4851,"1":144,"2":2,"3":2,"4":1}} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_QUBO/demo.py b/demonstrations_v2/tutorial_QUBO/demo.py new file mode 100644 index 0000000000..29585889a9 --- /dev/null +++ b/demonstrations_v2/tutorial_QUBO/demo.py @@ -0,0 +1,741 @@ +r"""Quadratic Unconstrained Binary Optimization (QUBO) +====================================================== + + +Solving combinatorial optimization problems using quantum computing is one of those promising +applications for the near term. But, why are combinatorial optimization problems even important? +We care about them because, in fields such as logistics, finance, and engineering, there exist useful applications that can be translated into combinatorial optimization problems. But +useful applications are not enough to justify the use of quantum devices. It is here where the second ingredient comes in—many +combinatorial optimization problems are difficult to solve! Finding good solutions (classically) +for large instances of them requires an enormous amount of computational resources and time 😮‍💨. + +In this demo, we will be using the quantum approximate optimization algorithm (QAOA) and quantum +annealing (QA) to solve a combinatorial optimization problem. First, we show how to translate +combinatorial optimization problems into the quadratic unconstrained binary optimization (QUBO) +formulation. In the first part of this notebook, we will show how to encode the Knapsack problem as +a target Hamiltonian and solve it using the optimization-free version of QAOA and QA on D-Wave +Advantage quantum annealer. + +.. figure:: ../_static/demonstration_assets/QUBO/items_QUBO.png + :align: center + :width: 50% + :target: javascript:void(0) + + +""" + +###################################################################### +# +# Combinatorial Optimization Problems +# ----------------------------------------- +# +# Combinatorial optimization problems involve finding the +# best way to arrange a set of objects or values to achieve a specific goal. The word ‘combinatorial’ +# refers to the fact that we are dealing with combinations of objects, while ‘optimization’ means +# that we are trying to find the best possible arrangement of them. +# +# Let’s start with a basic example. Imagine we have 5 items ⚽️, 💻, 📸, 📚, and 🎸 and we would love +# to bring all of them with us. Unfortunately, our knapsack does not fit +# all of them 😔. So we need to find the best way to bring the most important items with us. +# +# This is an example of the infamous *Knapsack Problem.* From our problem statement, we know +# that we need to maximize the value of the most important items. So we need to assign a value based +# on the importance the items have to us: +# + +items_values = {"⚽️": 8, "💻": 47, "📸": 10, "📚": 5, "🎸": 16} +values_list = [8, 47, 10, 5, 16] + +###################################################################### +# Additionally, we know that we the knapsack has limited space. For simplicity, let’s assume +# there is a limit to the weight it can hold. So we need to assign an estimate of the weight of each +# item: +# + +items_weight = {"⚽️": 3, "💻": 11, "📸": 14, "📚": 19, "🎸": 5} +weights_list = [3, 11, 14, 19, 5] + +###################################################################### +# Finally, we need to know the maximum weight we can bring in the knapsack: +# + +maximum_weight = 26 + +###################################################################### +# Now we have well-defined optimization problem to work with. Let’s start with the easiest way to solve it, i.e., +# by trying all possible combinations of the items. But the number of combinations is equal to :math:`2^n` where :math:`n` +# is the number of items. Why is this the case? For each item, we have two options—“1” +# if we bring the item and “0” otherwise. With 2 options for each item and 5 items to choose from, we have +# :math:`2 \cdot 2 \cdot 2 \cdot 2 \cdot 2 = 2^5 = 32` combinations in our case. For each of these cases, we calculate the sum +# of the values and the sum of the weights, selecting the one that fulfills the maximum weight constraint and +# has the largest sum of values (this is the optimization step). +# Now, let's write some code to solve the Knapsack problem with this brute-force method! + +import numpy as np + +def sum_weight(bitstring, items_weight): + weight = 0 + for n, i in enumerate(items_weight): + if bitstring[n] == "1": + weight += i + return weight + + +def sum_values(bitstring, items_value): + value = 0 + for n, i in enumerate(items_value): + if bitstring[n] == "1": + value += i + return value + +items = list(items_values.keys()) +n_items = len(items) +combinations = {} +max_value = 0 +for case_i in range(2**n_items): # all possible options + combinations[case_i] = {} + bitstring = np.binary_repr( + case_i, n_items + ) # bitstring representation of a possible combination, e.g, "01100" in our problem means bringing (-💻📸--) + combinations[case_i]["items"] = [items[n] for n, i in enumerate(bitstring) if i == "1"] + combinations[case_i]["value"] = sum_values(bitstring, values_list) + combinations[case_i]["weight"] = sum_values(bitstring, weights_list) + # save the information of the optimal solution (the one that maximizes the value while respecting the maximum weight) + if ( + combinations[case_i]["value"] > max_value + and combinations[case_i]["weight"] <= maximum_weight + ): + max_value = combinations[case_i]["value"] + optimal_solution = { + "items": combinations[case_i]["items"], + "value": combinations[case_i]["value"], + "weight": combinations[case_i]["weight"], + } + + +print( + f"The best combination is {optimal_solution['items']} with a total value: {optimal_solution['value']} and total weight {optimal_solution['weight']} " +) + +###################################################################### +# That was easy, right? But what if we have larger cases like 10, 50, or 100? Just to see how this +# scales, suppose it takes 1 ns to try one case. +# + +def time_to_solution(n, time_single_case): + """ + n (int): number of variables + time_single_case (float): time to solve a single case + """ + return time_single_case * 2 ** n + +time_per_case = 1e-9 # time to execute a single case in seconds +sec_day = 3600 * 24 # seconds in a day +sec_year = sec_day * 365 # seconds in a year + +print( + f"- For 10 items, 2^10 cases, we need {time_to_solution(2, time_per_case)} seconds." +) +print( + f"- For 50 items, 2^50 cases, we need {round(time_to_solution(50, time_per_case) / sec_day)} days." +) +print( + f"- For 100 items, 2^100 cases, we need {round(time_to_solution(100, time_per_case) / sec_year)} years." +) + + +###################################################################### +# Guess we don’t have the time to try all the possible solutions for 100 items 😅! Thankfully, +# we don't need to — there are algorithms to find good solutions to combinatorial +# optimization problems, and maybe one day we will show that one of these algorithms is quantum. So +# let’s continue with our quest 🫡. +# +# Our next step is to represent our problem mathematically. First, we represent our items by binary +# variables :math:`x_i` that take the value :math:`1` if we bring the :math:`i`-th item and :math:`0` otherwise. Next, we know that we want to **maximize** +# the value of the items carried, so let’s create a function :math:`f(\mathrm{x})` with these +# characteristics. To do so, we assign the variables :math:`x_i` to each of the items +# :math:`\mathrm{x} = \{x_0:⚽️ , x_1:💻, x_2:📸, x_3:📚, x_4:🎸\},` multiply each variable by the corresponding item value, and define a function that calculates the weighted sum +# value of the item: +# +# .. math:: \max_x f(\mathrm{x}) = \max_x \left(8x_0 + 47x_1 + 10x_2 + 5x_3 + 16x_4\right) \tag{1} +# +# This function, called the ``objective function``, represents the value of the items we can +# transport. Usually, solvers `minimize functions `__, so a simple trick in our case is to minimize the +# negative of our function (which ends up being maximizing our original function 🤪) +# +# .. math:: \min_x -(8x_0 + 47x_1 + 10x_2 + 5x_3 + 16x_4 ) \tag{2} +# +# We can write our equation above using the general form of the `QUBO +# representation `__, i.e., +# using an upper triangular matrix :math:`Q \in \mathbb{R}^{n \ \mathrm{x} \ n}:` +# +# .. math:: \min_x \mathrm{x}^TQ \mathrm{x} = \min_x \left(\sum_i \sum_{j\ge i} Q_{ij} x_i x_j\right) = \min_x \left(\sum_i Q_{ii} x_i + \sum_i\sum_{j>i} Q_{ij}x_i x_j\right) \tag{3} +# +# where :math:`\mathrm{x}` is a vector representing the items of our problem. Note that +# :math:`x_i x_i = x_i` for binary variables. Let's look at an example of how to calculate the :math:`\mathrm{x}^TQ \mathrm{x}` above: +# + +Q = -np.diag(list(items_values.values())) # Matrix Q for the problem above. +x_opt = np.array( + [[1 if i in optimal_solution["items"] else 0] for i in items_values.keys()] +) # Optimal solution. +opt_str = "".join(str(i[0]) for i in x_opt) +min_cost = (x_opt.T @ Q @ x_opt)[0, 0] # using Equation 3 above +print(f"Q={Q}") +print(f"The minimum cost is {min_cost}") + +###################################################################### +# But just with this function, we cannot solve the problem. We also need the weight restriction. Based +# on our variables, the weight list (items_weight = {“⚽️”: :math:`3`, “💻”: :math:`11`, “📸”: :math:`14`, “📚”: :math:`19,` “🎸”: :math:`5`}), and +# the knapsack maximum weight (maximum_weight :math:`W = 26`), we can construct our constraint +# +# .. math:: 3x_0 + 11x_1 + 14x_2 + 19x_3 + 5x_4 \le 26 +# +# Here comes a crucial step in the solution of the problem: we need to find a way to combine our +# *objective function* with this *inequality constraint*. One common method is to include the +# constraint as a **penalty** term in the objective function. This penalty term should be +# zero when the total weight of the items is less or equal to 26 and large otherwise. So to make them +# zero in the range of validity of the constraint, the usual approach is to use *slack variables*. +# There is an alternative method that has shown to perform better, called +# `unbalanced penalization `_ , but we present this method +# later 😉. +# +# The slack variable is an auxiliary variable that allows us to convert inequality constraints into equality +# constraints. The slack variable :math:`S` represents the amount by which the left-hand side of the +# inequality falls short of the right-hand side. If the left-hand side is less than the right-hand +# side, then :math:`S` will be positive and equal to the difference between the two sides. In our case +# +# .. math:: 3x_0 + 11x_1 + 14x_2 + 19x_3 + 5x_4 + S = 26\tag{4} +# +# where :math:`0 \le S \le 26.` But let’s take this slowly because we can get lost here, so let’s see +# this with some examples: +# +# - Imagine this case. No item is selected {:math:`x_0`: :math:`0`, :math:`x_1`: :math:`0`, :math:`x_2:` :math:`0,` +# :math:`x_3`: :math:`0,` :math:`x_4:` :math:`0`}, so the overall weight is zero (a valid solution) and the equality +# constraint Eq.(4) must be fulfilled. So we select our slack variable to be 26. +# +# - Now, what if we bring ⚽️ and 📚 {:math:`x_0`: :math:`1`, :math:`x_1`: :math:`0`, :math:`x_2`: :math:`0`, :math:`x_3:` :math:`1,` +# :math:`x_4:`0}. In this case, the overall weight is :math:`3 + 19 = 22` (a valid solution) and the equality +# constraint is fulfilled if :math:`22 + S = 26 \rightarrow S = 4.` +# +# - Finally, what if we try to bring all the items {:math:`x_0`: :math:`1`, :math:`x_1`: :math:`1`, :math:`x_2:` :math:`1,` +# :math:`x_3`: :math:`1,` :math:`x_4:` :math:`1`}, the total weight, in this case, is :math:`3+11+14+19+5=52` (not a valid +# solution), to fulfill the constraint, we need :math:`52 + S = 26 \rightarrow S=-26` but the slack +# variable is in the range :math:`(0,26)` in our definition, so, in this case, there is valid solution for :math:`S.` +# +# Excellent, now we have a way to represent the inequality constraint. Two further steps are needed. +# First, the slack variable has to be represented in binary form so we can cast it as a sum +# +# .. math:: S = \sum_{k=0}^{N-1} 2^k s_k, +# +# where :math:`N = \lfloor\log_2(\max S)\rfloor + 1.` In our case +# :math:`N = \lfloor\log_2(26)\rfloor + 1 = 5.` We need 5 binary variables to represent the range +# of our :math:`S` variable. +# +# .. math:: S = 2^0 s_0 + 2^1 s_1 + 2^2 s_2 + 2^3 s_3 + 2^4 s_4 +# +# To compact our equation later, let’s rename our slack variables by :math:`s_0=x_5`, :math:`s_1=x_6,` +# :math:`s_3=x_7`, :math:`s_4=x_8,` and :math:`s_5=x_9.` Then we have +# +# .. math:: S = 1 x_5 + 2 x_6 + 4 x_7 + 8 x_8 + 16 x_9. +# +# For example, if we need to represent the second case above (⚽️, 📚), +# :math:`S = 4 \rightarrow\{x_5:0, x_6:0,x_7:1,x_8:0, x_9:0\}.` +# +# We are almost done in our quest to represent our problem in such a way that our quantum computer can +# manage it. The last step is to add the penalty term, a usual choice for it is to use a +# quadratic penalty +# +# .. math:: p(x,s) = \lambda \left(3x_0 + 11x_1 + 14x_2 + 19 x_3 + 5x_4 + x_5 + 2 x_6 + 4x_7 + 8 x_8 + 16 x_9 - 26\right)^2. \tag{5} +# +# Note that this is simply the difference between the left- and right-hand sides of equation :math:`(4).` With this +# expression, the condition is satisfied only when the term inside the parentheses is zero. +# :math:`\lambda` is a penalty coefficient that we must tune to make that the constraint will always +# be fulfilled. +# +# Now, the objective function can be given by: +# +# .. math:: \min_{x,s} f(x) + p(x,s) = \min_{x,s} -(8x_0 + 47x_1 + 10x_2 + 5x_3 + 16x_4) + +# .. math:: \lambda \left(3x_0 + 11x_1 + 14x_2 + 19x_3 + 5x_4 + x_5 + 2 x_6 + 4x_7 + 8 x_8 + 16 x_9 - 26\right)^2 \tag{6} +# +# or, compacted, +# +# .. math:: \min_{x,s} \left(f(x) + p(x,s) = -\sum_i v_i x_i +\lambda \left(\sum_i w_i x_i - W\right)^2\right), \tag{7} +# +# where :math:`v_i` and :math:`w_i` are the value and weight of the :math:`i`-th item. Because of +# the square in the second term, some :math:`x_i x_i` terms show up. We can apply the property +# :math:`x_i x_i = x_i` (if :math:`x_i = 0 \rightarrow x_ix_i = 0\cdot0 = 0` or +# :math:`x_i = 1 \rightarrow x_ix_i = 1\cdot1 = 1`). +# +# The quadratic term on the right-hand side of equation :math:`(7)` can be rewritten as +# +# .. math:: \left(\sum_i w_i x_i - C\right)^2 = \left(\sum_i w_i x_i - C\right)\left(\sum_j w_j x_j - C\right) +# .. math:: = \sum_i \sum_j w_i w_j x_i x_j - 2C \sum_i w_i x_i + C^2 +# .. math:: = 2\sum_i \sum_{j>i} w_i w_j x_i x_j - \sum_i w_i(2C - w_i) x_i + C^2 \tag{8} +# +# where :math:`w_i` represent the weights for the items and :math:`2^k` for the slack variables. We +# can combine equations :math:`(7)` and :math:`(8)` to get the terms of the matrix :math:`Q.` So we end up with +# +# .. math:: Q_{ij} = 2\lambda w_i w_j,\tag{9} +# +# .. math:: Q_{ii} = - v_i + \lambda w_i(w_i - 2W).\tag{10} +# +# The term :math:`\lambda W^2` is only an offset value that does not affect the optimization result +# and can be added after the optimization to represent the right cost. Let's see how it looks +# like in our particular example. +# + +N = round(np.ceil(np.log2(maximum_weight))) # number of slack variables +weights = list(items_weight.values()) + [2**k for k in range(N)] + +QT = np.pad(Q, ((0, N), (0, N))) # adding the extra slack variables at the end of the Q matrix +n_qubits = len(QT) +lambd = 2 # We choose a lambda parameter enough large for the constraint to always be fulfilled +# Adding the terms for the penalty term +for i in range(len(QT)): + QT[i, i] += lambd * weights[i] * (weights[i] - 2 * maximum_weight) # Eq. 10 + for j in range(i + 1, len(QT)): + QT[i, j] += 2 * lambd * weights[i] * weights[j] # Eq. 9 +offset = lambd * maximum_weight**2 +print(f"Q={QT}") +# optimal string slack string +slack_string = np.binary_repr(maximum_weight - optimal_solution["weight"], N)[::-1] +x_opt_slack = np.concatenate( + (x_opt, np.array([[int(i)] for i in slack_string])) +) # combining the optimal string and slack string +opt_str_slack = "".join(str(i[0]) for i in x_opt_slack) +cost = (x_opt_slack.T @ QT @ x_opt_slack)[0, 0] + offset # Optimal cost using equation 3 +print(f"Cost:{cost}") + +# At this point, we have encoded the problem in a format that we can use to solve it on quantum +# computers. Now it only remains to solve it using quantum algorithms! +###################################################################### +# +# QAOA +# ------- +# +# We use QAOA `[1] `__ to find the solution to our Knapsack +# Problem (`read this demo `__ for a more detailed explanation +# of the QAOA algorithm). In this case, the cost Hamiltonian, :math:`H_c(Z),` obtained from the QUBO +# formulation is translated into a parametric unitary gate given by +# +# .. math:: +# +# +# U(H_c, \gamma_i)=e^{-i \gamma_i H_c},\tag{11} +# +# .. math:: +# +# +# U(H_c, \gamma_i)=e^{-i \gamma_i \left( \sum_{i`__. We +# start in the ground state :math:`|+\rangle ^{\otimes n}` of the mixer Hamiltonian :math:`X` and move +# to the ground state of the cost Hamiltonian :math:`H_c` slowly enough to always be close to the +# ground state of the Hamiltonian. How slow? In our case the rate is determined by the number of layers +# :math:`p.` We can adopt this principle and initialize the :math:`\beta_i` and :math:`\gamma_i` in +# this way, moving :math:`\beta_i` from :math:`1` to :math:`0` and :math:`\gamma_i` from :math:`0` to :math:`1.` With this approach, +# we can skip the optimization part in QAOA. +# + +import matplotlib.pyplot as plt +# Annealing schedule for QAOA +betas = np.linspace(0, 1, 10)[::-1] # Parameters for the mixer Hamiltonian +gammas = np.linspace(0, 1, 10) # Parameters for the cost Hamiltonian (Our Knapsack problem) + +fig, ax = plt.subplots() +ax.plot(betas, label=r"$\beta_i$", marker="o", markersize=8, markeredgecolor="black") +ax.plot(gammas, label=r"$\gamma_i$", marker="o", markersize=8, markeredgecolor="black") +ax.set_xlabel("i", fontsize=18) +ax.legend() +fig.show() + +###################################################################### +# +# This Figure shows the annealing schedule we will use in our QAOA protocol. The y-axis represents the +# angle in radians and the x-axis represents the i-th layer of QAOA, from :math:`0` to :math:`9` for a total of :math:`p=10` layers. + + +###################################################################### +# +# I know this is a lot of information so far, but we are almost done! The last step to represent the +# QUBO problem on QPUs is to change the :math:`x_i\in \{0, 1\}` variables to spin variables +# :math:`z_i \in \{1, -1\}` via the transformation :math:`x_i = (1 - z_i) / 2.` We also want to set +# the penalty term, so a value of :math:`\lambda = 2` will be enough for our problem. In +# practice, we choose a value for :math:`\lambda` and, if after the optimization the solution does not +# fulfill the constraints, we try again using a larger value. On the other hand, if the solution is suspected +# to be a valid but suboptimal, then we will reduce :math:`\lambda` a little. Eq.(3) can be +# represented by an Ising Hamiltonian with quadratic and linear terms plus a constant :math:`O,` namely +# +# .. math:: +# +# +# H_c(\mathrm{z}) = \sum_{i, j > i}^{n} J_{ij} z_i z_j + \sum_{i=1}^n h_{i}z_i + O. \tag{13} +# +# Here, :math:`J_{ij}` are interaction terms and :math:`h_i` are linear terms, all of them depending +# on the combinatorial optimization problem. +# + + +def from_Q_to_Ising(Q, offset): + """Convert the matrix Q of Eq.3 into Eq.13 elements J and h""" + n_qubits = len(Q) # Get the number of qubits (variables) in the QUBO matrix + # Create default dictionaries to store h and pairwise interactions J + h = defaultdict(int) + J = defaultdict(int) + + # Loop over each qubit (variable) in the QUBO matrix + for i in range(n_qubits): + # Update the magnetic field for qubit i based on its diagonal element in Q + h[(i,)] -= Q[i, i] / 2 + # Update the offset based on the diagonal element in Q + offset += Q[i, i] / 2 + # Loop over other qubits (variables) to calculate pairwise interactions + for j in range(i + 1, n_qubits): + # Update the pairwise interaction strength (J) between qubits i and j + J[(i, j)] += Q[i, j] / 4 + # Update the magnetic fields for qubits i and j based on their interactions in Q + h[(i,)] -= Q[i, j] / 4 + h[(j,)] -= Q[i, j] / 4 + # Update the offset based on the interaction strength between qubits i and j + offset += Q[i, j] / 4 + # Return the magnetic fields, pairwise interactions, and the updated offset + return h, J, offset + + +def energy_Ising(z, h, J, offset): + """ + Calculate the energy of an Ising model given spin configurations. + + Parameters: + - z: A dictionary representing the spin configurations for each qubit. + - h: A dictionary representing the magnetic fields for each qubit. + - J: A dictionary representing the pairwise interactions between qubits. + - offset: An offset value. + + Returns: + - energy: The total energy of the Ising model. + """ + if isinstance(z, str): + z = [(1 if int(i) == 0 else -1) for i in z] + # Initialize the energy with the offset term + energy = offset + # Loop over the magnetic fields (h) for each qubit and update the energy + for k, v in h.items(): + energy += v * z[k[0]] + # Loop over the pairwise interactions (J) between qubits and update the energy + for k, v in J.items(): + energy += v * z[k[0]] * z[k[1]] + # Return the total energy of the Ising model + return energy + + +# Our previous example should give us the same result +z_exp = [ + (1 if i == 0 else -1) for i in x_opt_slack +] # Converting the optimal solution from (0,1) to (1, -1) +h, J, zoffset = from_Q_to_Ising(QT, offset) # Eq.13 for our problem +energy = energy_Ising( + z_exp, h, J, zoffset +) # Caluclating the energy (Should be the same that for the QUBO) +print(f"Minimum energy:{energy}") + +samples_slack = samples_dict(qaoa_circuit(gammas, betas, h, J, num_qubits=len(QT)), n_qubits) +values_slack = { + sum_values(sample_i, values_list): count + for sample_i, count in samples_slack.items() + if sum_weight(sample_i, weights_list) <= maximum_weight +} # saving only the solutions that fulfill the constraint +print( + f"The number of optimal solutions using slack variables is {samples_slack[opt_str_slack]} out of {shots}" +) + +###################################################################### +# As you can see, only a few samples from the 5000 shots give us the right answer, there are only +# :math:`2^5 = 32` options. Randomly guessing the solution will give us on average :math:`5000/32 \approx 156` +# optimal solutions. Why don’t we get good results using QAOA? Maybe we can blame the algorithm +# or we look deeper— it turns out our encoding method is really bad. Randomly guessing using the whole set of +# variables (:math:`5` items + :math:`5` slack) :math:`2^{10} = 1024` options, :math:`5000/1024 \approx 5.` So in fact we have a +# tiny improvement. +# + +###################################################################### +# Unbalanced penalization (An alternative to slack variables) +# ----------------------------------------------------------- +# +# Unbalanced penalization is a function characterized by a larger penalty when the inequality +# constraint is not achieved than when it is. So we have to modify Eq. 7 to include a linear term in +# the following way: +# +# .. math:: \min_{x,s} \left(f(x) + p(x,s)\right) = \min_{x,s} \left(-\sum_i v_i x_i - \lambda_1 \left(\sum_i w_i x_i - W\right) + \lambda_2 \left(\sum_i w_i x_i - W\right)^2\right)\tag{14}. +# +# where :math:`\lambda_{1,2}` are again penalty coefficients. Here `[2] `__ and `[3] `__ some details about unbalanced penalization. +# The method is already implemented in `OpenQAOA `__ and `D-Wave Ocean `__ so we don't have to code it ourselves. **The cliffnotes are +# that you don’t need slack variables for the inequality constraints anymore using this approach**. +# + +from openqaoa.problems import FromDocplex2IsingModel +from docplex.mp.model import Model + + +def Knapsack(values, weights, maximum_weight): + """Create a docplex model of the problem. (Docplex is a classical solver from IBM)""" + n_items = len(values) + mdl = Model() + x = mdl.binary_var_list(range(n_items), name="x") + cost = -mdl.sum(x[i] * values[i] for i in range(n_items)) + mdl.minimize(cost) + mdl.add_constraint(mdl.sum(x[i] * weights[i] for i in range(n_items)) <= maximum_weight) + return mdl + + +# Docplex model, we need to convert our problem in this format to use the unbalanced penalization approach +mdl = Knapsack(values_list, weights_list, maximum_weight) +lambda_1, lambda_2 = ( + 0.96, + 0.0371, +) # Parameters of the unbalanced penalization function (They are in the main paper) +ising_hamiltonian = FromDocplex2IsingModel( + mdl, + unbalanced_const=True, + strength_ineq=[lambda_1, lambda_2], # https://arxiv.org/abs/2211.13914 +).ising_model + +h_new = { + tuple(i): w for i, w in zip(ising_hamiltonian.terms, ising_hamiltonian.weights) if len(i) == 1 +} +J_new = { + tuple(i): w for i, w in zip(ising_hamiltonian.terms, ising_hamiltonian.weights) if len(i) == 2 +} + +samples_unbalanced = samples_dict( + qaoa_circuit(gammas, betas, h_new, J_new, num_qubits=n_items), n_items +) +values_unbalanced = { + sum_values(sample_i, values_list): count + for sample_i, count in samples_unbalanced.items() + if sum_weight(sample_i, weights_list) <= maximum_weight +} # saving only the solutions that fulfill the constraint + +print( + f"The number of solutions using unbalanced penalization is {samples_unbalanced[opt_str]} out of {shots}" +) + +###################################################################### +# We have improved the QAOA solution by encoding our QUBO wisely, with almost 2000 out of the 5000 samples +# being the optimal solution. Below, we compare the two different methods to encode the problem. The +# x-axis is the value of the items we bring based on the optimization (the larger the better) and the +# y-axis is the number of samples with that value (in log scale to observe the slack variables +# approach). In this sense, QAOA is pointing to the optimal and suboptimal solutions. +# + +fig, ax = plt.subplots() +ax.hist( + values_unbalanced.keys(), + weights=values_unbalanced.values(), + bins=50, + edgecolor="black", + label="unbalanced", + align="right", +) +ax.hist( + values_slack.keys(), + weights=values_slack.values(), + bins=50, + edgecolor="black", + label="slack", + align="left", +) +ax.vlines(-min_cost, 0, 3000, linestyle="--", color="black", label="Optimal", linewidth=2) +ax.set_yscale("log") +ax.legend() +ax.set_ylabel("counts") +ax.set_xlabel("values") +fig.show() + +###################################################################### +# Quantum Annealing Solution +# -------------------------- +# + +###################################################################### +# `Quantum +# annealing `__ +# is a process that exploits quantum mechanical effects to find low energy states of Ising +# Hamiltonians. We will use the quantum annealer D-Wave Advantage, a quantum computing system +# developed by D-Wave Systems Inc that has more than 5000 qubits. +# + +from dwave.system import DWaveSampler, EmbeddingComposite +from dwave.cloud import Client +import dimod +import pandas as pd + +bqm = {} +# BQM - Binary Quadratic Model +# This creates the QUBO model of our Knapsack problem using the slack variables approach +# offset is the constant term in our QUBO formulation +# ----------- SLACK METHOD ----------- +bqm["slack"] = dimod.BQM.from_qubo(QT, offset=lambd * maximum_weight**2) +bqm["slack"].relabel_variables({i: f"x_{i}" for i in range(bqm["slack"].num_variables)}) +# ----------- UNBALANCED METHOD ----------- +lagrange_multiplier = [0.96, 0.0371] # Again values from the paper +bqm["unbalanced"] = dimod.BQM.from_qubo(Q) # This adds the objective function to the model +bqm["unbalanced"].add_linear_inequality_constraint( + [(n, i) for n, i in enumerate(weights_list)], # This adds the constraint + lagrange_multiplier, + "unbalanced", + ub=maximum_weight, + penalization_method="unbalanced", +) +bqm["unbalanced"].relabel_variables({i: f"x_{i}" for i in range(bqm["unbalanced"].num_variables)}) + +# If you have an account you can execute the following code, otherwise read the file. +account = False +df = {} +if account: + # Replace with your client information + sampler = DWaveSampler(region="eu-central-1") + sampler_qpu = EmbeddingComposite(sampler) + for method in ["slack", "unbalanced"]: + samples = sampler_qpu.sample(bqm[method], num_reads=5000) # Executing on real hardware + df[method] = ( + samples.to_pandas_dataframe().sort_values("energy").reset_index(drop=True) + ) # Converting the sampling information and sort it by cost + df[method].to_json(f"QUBO/dwave_results_{method}.json") # save the results +else: + df = {} + for method in ["slack", "unbalanced"]: + df[method] = pd.read_json(f"QUBO/dwave_results_{method}.json") + # Loading the data from an execution on D-Wave Advantage + + +samples_dwave = {} +values = {} +for method in ["slack", "unbalanced"]: + samples_dwave[method] = defaultdict(int) + for i, row in df[method].iterrows(): + # Postprocessing the information + sample_i = "".join(str(round(row[q])) for q in bqm[method].variables) + samples_dwave[method][sample_i] += row["num_occurrences"] + values[method] = { + sum_values(sample_i, values_list): count + for sample_i, count in samples_dwave[method].items() + if sum_weight(sample_i, weights_list) <= maximum_weight + } + +###################################################################### +# The histogram below shows the results of both encodings on D-Wave Advantage. Once again, we prove +# that depending on the encoding method for our problem, we get good or bad results. +# + +fig, ax = plt.subplots() +bins = {"unbalanced": 5, "slack": 40} +for method in ["unbalanced", "slack"]: + ax.hist( + values[method].keys(), + weights=values[method].values(), + bins=bins[method], + edgecolor="black", + label=method, + align="right", + ) +ax.vlines(-min_cost, 0, 5000, linestyle="--", color="black", label="Optimal", linewidth=2) +ax.set_yscale("log") +ax.legend() +ax.set_ylabel("counts") +ax.set_xlabel("value") +fig.show() + +###################################################################### +# Conclusion +# ------------ +# +# We have come to the end of this demo. We have covered the definition of combinatorial optimization problems +# and how to formulate one of them, the Knapsack Problem, using QUBO, and two different encodings: slack +# variables and unbalanced penalization. Then, we solved them using optimization-free QAOA and QA. Now, +# it’s your turn to experiment with QAOA! If you need some inspiration: +# +# - Look at the `OpenQAOA `__ set of problems. There are plenty of them like bin packing, +# traveling salesman, and maximal independent set, among others. +# +# - Play around with larger problems. +# +# References +# ----------- +# +# [1] Farhi, E., Goldstone, J., & Gutmann, S. (2014). A Quantum Approximate Optimization Algorithm. +# http://arxiv.org/abs/1411.4028 +# +# [2] Montanez-Barrera, A., Willsch, D., A., Maldonado-Romo, & Michielsen, K. (2022). Unbalanced +# penalization: A new approach to encode inequality constraints of combinatorial problems for quantum +# optimization algorithms. http://arxiv.org/abs/2211.13914 +# +# [3] Montanez-Barrera, J. A., Heuvel, P. van den, Willsch, D., & Michielsen, K. (2023). Improving +# Performance in Combinatorial Optimization Problems with Inequality Constraints: An Evaluation of the +# Unbalanced Penalization Method on D-Wave Advantage. https://doi.org/10.1109/QCE57702.2023.00067 +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_QUBO/metadata.json b/demonstrations_v2/tutorial_QUBO/metadata.json new file mode 100644 index 0000000000..797233c76a --- /dev/null +++ b/demonstrations_v2/tutorial_QUBO/metadata.json @@ -0,0 +1,31 @@ +{ + "title": "Quadratic Unconstrained Binary Optimization", + "authors": [ + { + "username": "alejomonbar" + } + ], + "dateOfPublication": "2024-02-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/QUBO/thumbnail_QUBO_2024-02-06.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuBO_2024-02-06.png" + } + ], + "seoDescription": "Learn how to solve a QUBO problem in a quantum computer.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [], + "discussionForumUrl": "https://discuss.pennylane.ai/t/quadratic-unconstrained-binary-optimization-qubo-demo/7339" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_QUBO/requirements.in b/demonstrations_v2/tutorial_QUBO/requirements.in new file mode 100644 index 0000000000..152f7b0907 --- /dev/null +++ b/demonstrations_v2/tutorial_QUBO/requirements.in @@ -0,0 +1,8 @@ +dimod +docplex +dwave-ocean-sdk==7.0.0 +matplotlib +numpy +openqaoa-core +pandas +pennylane diff --git a/demonstrations_v2/tutorial_adaptive_circuits/demo.py b/demonstrations_v2/tutorial_adaptive_circuits/demo.py new file mode 100644 index 0000000000..918e12a1c7 --- /dev/null +++ b/demonstrations_v2/tutorial_adaptive_circuits/demo.py @@ -0,0 +1,397 @@ +r""" + +Adaptive circuits for quantum chemistry +======================================= + +.. meta:: + :property="og:description": Learn how to build quantum chemistry circuits adaptively + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_adaptive_circuits.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 13 September 2021. Last updated: 10 April 2023* + +The key component of variational quantum algorithms for quantum chemistry is the circuit used to +prepare electronic ground states of a molecule. The variational quantum eigensolver (VQE) +[#peruzzo2014]_, [#yudong2019]_ is the method of choice for performing such quantum chemistry +simulations on quantum devices with few qubits. For a given molecule, the appropriate circuit can +be generated by using a pre-selected wavefunction ansatz, for example, the unitary coupled cluster +with single and double excitations (UCCSD) [#romero2017]_. In this approach, we include all +possible single and double excitations of electrons from the occupied spin-orbitals of a reference +state to the unoccupied spin-orbitals [#givenstutorial]_. This makes the construction of the ansatz +straightforward for any given molecule. However, using a pre-constructed ansatz has the disadvantage +of reducing performance in favour of generality: the approach may work well in many cases, but it +will not be optimized for a specific problem. + +In practical applications, including all possible excitations usually increases the cost of the +simulations without improving the accuracy of the results. This motivates implementing a strategy +that allows for approximation of the contribution of the excitations and selects only those +excitations that are found to be important for the given molecule. This can be done by using +adaptive methods to construct a circuit for each given problem [#grimsley2019]_. Using adaptive +circuits helps improve performance at the cost of reducing generality. + +.. figure:: /_static/demonstration_assets/adaptive_circuits/main.png + :width: 75% + :align: center + + Examples of selecting specific gates to generate adaptive circuits. + +In this tutorial, you will learn how to **adaptively** build customized quantum chemistry circuits +to perform ADAPT-VQE [#grimsley2019]_ simulations. This includes a recipe to adaptively select gates +that have a significant contribution to the desired state, while neglecting those that have a small +contribution. You will also learn how to use PennyLane to leverage the sparsity of a molecular +Hamiltonian to make the computation of the expectation values even more efficient. Let's get +started! + +Adaptive circuits +----------------- + +The main idea behind building adaptive circuits is to compute the gradients with respect to all +possible excitation gates and then select gates based on the magnitude of the computed gradients. + +There are different ways to make use of the gradient information and here we discuss one of +these strategies and apply it to compute the ground state energy of LiH. This method requires constructing the +Hamiltonian and determining all possible excitations, which we can do with functionality built into PennyLane. +But we first need to define the molecular parameters, including atomic symbols and coordinates. +Note that the atomic coordinates are in `Bohr `_. +""" + +import pennylane as qml +from pennylane import qchem +from pennylane import numpy as np +import time + +symbols = ["Li", "H"] +geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) +molecule = qchem.Molecule(symbols, geometry) + +############################################################################## +# We now compute the molecular Hamiltonian in the +# `STO-3G `_ basis and obtain the electronic +# excitations. We restrict ourselves to single and double excitations, but higher-level ones such +# as triple and quadruple excitations can be considered as well. Each of these electronic excitations +# is represented by a gate that excites electrons from the occupied orbitals of a reference state to +# the unoccupied ones. This allows us to prepare a state that is a superposition of the reference +# state and all of the excited states. + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + +active_electrons = 2 + +singles, doubles = qchem.excitations(active_electrons, qubits) + +print(f"Total number of excitations = {len(singles) + len(doubles)}") + +############################################################################## +# Note that we have a total of 24 excitations which can be represented by the same number of +# excitation gates [#givenstutorial]_. Let's now use an :class:`~.pennylane.AdaptiveOptimizer` +# implemented in PennyLane to construct an adaptive circuit. +# +# Adaptive Optimizer +# ~~~~~~~~~~~~~~~~~~ +# The adaptive optimizer +# grows an input quantum circuit by adding and optimizing gates selected from a user-defined +# collection of operators. The algorithm first appends all of the gates provided in the initial +# operator pool and computes the circuit gradients with respect to the gate parameters. It retains +# the gate which has the largest gradient and then optimizes its parameter. +# The process of growing the circuit can be repeated until the computed gradients converge to zero. +# Let's use :class:`~.pennylane.AdaptiveOptimizer` to perform an ADAPT-VQE [#grimsley2019]_ +# simulation and build an adaptive circuit for LiH. +# +# We first create the operator pool which contains all single and double excitations. + +singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] +doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] +operator_pool = doubles_excitations + singles_excitations + +############################################################################## +# We now define an initial circuit that prepares a Hartree-Fock state and returns the expectation +# value of the Hamiltonian. We also need to define a device. + +hf_state = qchem.hf_state(active_electrons, qubits) +dev = qml.device("default.qubit", wires=qubits) +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +############################################################################## +# We instantiate the optimizer and use it to build the circuit adaptively. + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool) + if i % 3 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# The resulting energy matches the exact energy of the ground electronic state of LiH, which is +# -7.8825378193 Ha, within chemical accuracy. Note that some of the gates appear more than once in +# the circuit. By default, :class:`~.pennylane.AdaptiveOptimizer` does not eliminate the selected +# gates from the pool. We can set ``drain_pool=True`` to prevent repetition of the gates by +# removing the selected gate from the operator pool. + +@qml.qnode(dev) +def circuit(): + [qml.PauliX(i) for i in np.nonzero(hf_state)[0]] + return qml.expval(H) + +opt = qml.optimize.AdaptiveOptimizer() +for i in range(len(operator_pool)): + circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) + if i % 2 == 0: + print("n = {:}, E = {:.8f} H, Largest Gradient = {:.3f}".format(i, energy, gradient)) + print(qml.draw(circuit, decimals=None)()) + print() + if gradient < 3e-3: + break + +############################################################################## +# Manual construction +# ~~~~~~~~~~~~~~~~~~~ +# We can also build adaptive circuits manually by adding groups of gates one at a time. We follow +# these steps: +# +# 1. Compute gradients for all double excitations. +# 2. Select the double excitations with gradients larger than a pre-defined threshold. +# 3. Perform VQE to obtain the optimized parameters for the selected double excitations. +# 4. Repeat steps 1 and 2 for the single excitations. +# 5. Perform the final VQE optimization with all the selected excitations. +# +# We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. + +def circuit_1(params, excitations): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + else: + qml.SingleExcitation(params[i], wires=excitation) + return qml.expval(H) + +############################################################################## +# We now construct our first group of gates by including all the double excitations and compute the +# gradient for each one. We also need to define a cost +# function. We initialize the parameter values to zero such that the gradients are computed +# with respect to the Hartree-Fock state. + + +dev = qml.device("default.qubit", wires=qubits) +cost_fn = qml.QNode(circuit_1, dev, interface="autograd") + +circuit_gradient = qml.grad(cost_fn, argnum=0) + +params = [0.0] * len(doubles) +grads = circuit_gradient(params, excitations=doubles) + +for i in range(len(doubles)): + print(f"Excitation : {doubles[i]}, Gradient: {grads[i]}") + +############################################################################## +# The computed gradients have different values, reflecting the contribution of each gate +# in the final state prepared by the circuit. Many of the gradient values are zero and we select +# those gates that have a gradient above a pre-defined threshold, which we set to :math:`10^{-5}.` + +doubles_select = [doubles[i] for i in range(len(doubles)) if abs(grads[i]) > 1.0e-5] +doubles_select + +############################################################################## +# There are only 6 double excitation gates, out of the original 16, that have gradients above the +# threshold. We add the selected gates to the circuit and optimize it to determine +# the updated parameters for the selected gates. We also need to define an optimizer. Note that the +# optimization is not very costly as we only have six gates in our circuit. + +opt = qml.GradientDescentOptimizer(stepsize=0.5) + +params_doubles = np.zeros(len(doubles_select), requires_grad=True) + +for n in range(20): + params_doubles = opt.step(cost_fn, params_doubles, excitations=doubles_select) + +############################################################################## +# Now, we keep the selected gates in the circuit and compute the gradients with respect to all of +# the single excitation gates, selecting those that have a non-negligible gradient. To do that, we +# need to slightly modify our circuit such that parameters of the double excitation gates are kept +# fixed while the gradients are computed for the single excitation gates. + + +def circuit_2(params, excitations, gates_select, params_select): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, gate in enumerate(gates_select): + if len(gate) == 4: + qml.DoubleExcitation(params_select[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params_select[i], wires=gate) + + for i, gate in enumerate(excitations): + if len(gate) == 4: + qml.DoubleExcitation(params[i], wires=gate) + elif len(gate) == 2: + qml.SingleExcitation(params[i], wires=gate) + return qml.expval(H) + + +############################################################################## +# We now compute the gradients for the single excitation gates. + +cost_fn = qml.QNode(circuit_2, dev, interface="autograd") +circuit_gradient = qml.grad(cost_fn, argnum=0) +params = [0.0] * len(singles) + +grads = circuit_gradient( + params, + excitations=singles, + gates_select=doubles_select, + params_select=params_doubles +) + +for i in range(len(singles)): + print(f"Excitation : {singles[i]}, Gradient: {grads[i]}") + +############################################################################## +# Similar to the double excitation gates, we select those single excitations that have a gradient +# larger than a predefined threshold. + +singles_select = [singles[i] for i in range(len(singles)) if abs(grads[i]) > 1.0e-5] +singles_select + +############################################################################## +# We now have all of the gates we need to build our circuit. The selected single and double +# excitation gates are highlighted in the figure below. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/adapted_circuit.png +# :width: 90% +# :align: center +# +# We perform a final circuit optimization to get the ground-state energy. The resulting energy +# should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. + +cost_fn = qml.QNode(circuit_1, dev, interface="autograd") + +params = np.zeros(len(doubles_select + singles_select), requires_grad=True) + +gates_select = doubles_select + singles_select + +for n in range(20): + t1 = time.time() + params, energy = opt.step_and_cost(cost_fn, params, excitations=gates_select) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Success! We obtained the ground state energy of LiH, within chemical accuracy, by having +# only 10 gates in our circuit. This is less than half of the total number of single and double +# excitations of LiH (24). + +############################################################################## +# Sparse Hamiltonians +# ------------------- +# +# Molecular Hamiltonians and quantum states are sparse. For instance, let’s look at the Hamiltonian +# we built for LiH. We can compute its matrix representation in the computational basis using the +# Hamiltonian function :meth:`~.pennylane.Hamiltonian.sparse_matrix`. This function +# returns the matrix in the SciPy `sparse coordinate `_ format. + +H_sparse = H.sparse_matrix() +H_sparse + +############################################################################## +# The matrix has :math:`1024^2=1,048,576` entries, but only :math:`11264` of them are non-zero. +# +# .. figure:: /_static/demonstration_assets/adaptive_circuits/h_sparse.png +# :width: 65% +# :align: center +# +# Matrix representation of the LiH Hamiltonian in the computational basis. +# +# Leveraging this sparsity can significantly reduce the +# simulation times. We use the implemented functionality in PennyLane for computing the expectation +# value of the sparse Hamiltonian observable. This can reduce the cost of simulations by +# orders of magnitude depending on the size of the molecule. We use the selected gates obtained in +# the previous steps and perform the final optimization step with the sparse method. Note that the +# sparse method currently only works with the parameter-shift differentiation method. + +excitations = doubles_select + singles_select + +params = np.zeros(len(excitations), requires_grad=True) + +@qml.qnode(dev, diff_method="parameter-shift", interface="autograd") +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(params[i], wires=excitation) + elif len(excitation) == 2: + qml.SingleExcitation(params[i], wires=excitation) + + return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) + + +for n in range(20): + t1 = time.time() + params, energy = opt.step_and_cost(circuit, params) + t2 = time.time() + print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) + +############################################################################## +# Using the sparse method reproduces the ground state energy while the optimization time is +# much shorter. The average iteration time for the sparse method is about 18 times smaller than that +# of the original non-sparse approach. The performance of the sparse optimization will be even +# better for larger molecules. +# +# Conclusions +# ----------- +# We have learned that building quantum chemistry circuits adaptively and using the +# functionality for sparse objects makes molecular simulations significantly more efficient. We +# learned how to use an adaptive optimizer implemented in PennyLane, that selects the gates one at +# time, to perform ADAPT-VQE [#grimsley2019]_ simulations. We also followed an adaptive strategy +# that selects a group of gates based on information about the gradients. +# +# References +# ---------- +# +# .. [#peruzzo2014] +# +# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nat. Commun. 5, 4213 (2014). +# `__ +# +# .. [#yudong2019] +# +# Y. Cao, J. Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `__ +# +# .. [#romero2017] +# +# J. Romero, R. Babbush, *et al.*, "Strategies for quantum computing molecular +# energies using the unitary coupled cluster ansatz". `arXiv:1701.02691 +# `_ +# +# .. [#givenstutorial] +# +# :doc:`tutorial_givens_rotations` +# +# .. [#grimsley2019] +# +# H. R. Grimsley, S. E. Economou, E. Barnes, N. J. Mayhall, "An adaptive +# variational algorithm for exact molecular simulations on a quantum computer". +# `Nat. Commun. 2019, 10, 3007. +# `__ +# +# diff --git a/demonstrations_v2/tutorial_adaptive_circuits/metadata.json b/demonstrations_v2/tutorial_adaptive_circuits/metadata.json new file mode 100644 index 0000000000..2cc1f32e63 --- /dev/null +++ b/demonstrations_v2/tutorial_adaptive_circuits/metadata.json @@ -0,0 +1,96 @@ +{ + "title": "Adaptive circuits for quantum chemistry", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2021-09-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/adaptive_circuits/thumbnail_adaptive_circuits.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adaptive_circuits_new.png" + } + ], + "seoDescription": "Learn how to build quantum chemistry circuits adaptively.", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean, et al.", + "year": "2014", + "journal": "Nature Communications", + "doi": "10.1038/ncomms5213", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "yudong2019", + "type": "article", + "title": "Quantum Chemistry in the Age of Quantum Computing", + "authors": "Yudong Cao, Jonathan Romero, et al.", + "year": "2019", + "journal": "Chem. Rev.", + "doi": "10.1021/acs.chemrev.8b00803", + "url": "https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803" + }, + { + "id": "romero2017", + "type": "article", + "title": "Strategies for quantum computing molecular energies using the unitary coupled cluster ansatz", + "authors": "J. Romero, R. Babbush, et al.", + "doi": "10.48550/arXiv.1701.02691", + "url": "https://arxiv.org/abs/1701.02691" + }, + { + "id": "givenstutorial", + "type": "article", + "title": "Givens rotations for quantum chemistry", + "authors": "Juan Miguel Arrazola", + "year": "2021", + "journal": "", + "url": "https://pennylane.ai/qml/demos/tutorial_givens_rotations.html" + }, + { + "id": "grimsley2019", + "type": "article", + "title": "An adaptive variational algorithm for exact molecular simulations on a quantum computer", + "authors": "Harper R. Grimsley, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2019", + "journal": "Nat. Commun.", + "doi": "10.1038/s41467-019-10988-2", + "url": "https://www.nature.com/articles/s41467-019-10988-2" + } + ], + "basedOnPapers": [ + "10.1038/s41467-019-10988-2" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_adaptive_circuits/requirements.in b/demonstrations_v2/tutorial_adaptive_circuits/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_adaptive_circuits/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_adjoint_diff/demo.py b/demonstrations_v2/tutorial_adjoint_diff/demo.py new file mode 100644 index 0000000000..12d5614213 --- /dev/null +++ b/demonstrations_v2/tutorial_adjoint_diff/demo.py @@ -0,0 +1,455 @@ +r""" + +.. _adjoint_differentiation: + +Adjoint Differentiation +======================= + +.. meta:: + :property="og:description": Learn how to use the adjoint method to compute gradients of quantum circuits." + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/icon.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_general_parshift Generalized parameter-shift rules + tutorial_stochastic_parameter_shift The Stochastic Parameter-Shift Rule + + +""" + +############################################################################## +# *Author: Christina Lee. Posted: 23 Nov 2021. Last updated: 20 Jun 2023.* +# +# `Classical automatic differentiation `__ +# has two methods of calculation: forward and reverse. +# The optimal choice of method depends on the structure of the problem; is the function +# many-to-one or one-to-many? We use the properties of the problem to optimize how we +# calculate derivatives. +# +# Most methods for calculating the derivatives of quantum circuits are either direct applications +# of classical gradient methods to quantum simulations, or quantum hardware methods like parameter-shift +# where we can only extract restricted pieces of information. +# +# Adjoint differentiation straddles these two strategies, taking benefits from each. +# On simulators, we can examine and modify the state vector at any point. At the same time, we know our +# quantum circuit holds specific properties not present in an arbitrary classical computation. +# +# Quantum circuits only involve: +# +# 1) initialization, +# +# .. math:: |0\rangle, +# +# 2) application of unitary operators, +# +# .. math:: |\Psi\rangle = U_{n} U_{n-1} \dots U_0 |0\rangle, +# +# 3) measurement, such as estimating an expectation value of a Hermitian operator, +# +# .. math:: \langle M \rangle = \langle \Psi | M | \Psi \rangle. +# +# Since all our operators are unitary, we can easily "undo" or "erase" them by applying their adjoint: +# +# .. math:: U^{\dagger} U | \phi \rangle = |\phi\rangle. +# +# The **adjoint differentiation method** takes advantage of the ability to erase, creating a time- and +# memory-efficient method for computing quantum gradients on state vector simulators. Tyson Jones and Julien Gacon describe this +# algorithm in their paper +# `"Efficient calculation of gradients in classical simulations of variational quantum algorithms" `__ . +# +# In this demo, you will learn how adjoint differentiation works and how to request it +# for your PennyLane QNode. We will also look at the performance benefits. +# +# Time for some code +# ------------------ +# +# So how does it work? Instead of jumping straight to the algorithm, let's explore the above equations +# and their implementation in a bit more detail. +# +# To start, we import PennyLane and Jax's numpy: + +import pennylane as qml +import jax +from jax import numpy as np + +jax.config.update("jax_platform_name", "cpu") + + +############################################################################## +# We also need a circuit to simulate: +# + +dev = qml.device('default.qubit', wires=2) + +x = np.array([0.1, 0.2, 0.3]) + +@qml.qnode(dev, diff_method="adjoint") +def circuit(a): + qml.RX(a[0], wires=0) + qml.CNOT(wires=(0,1)) + qml.RY(a[1], wires=1) + qml.RZ(a[2], wires=1) + return qml.expval(qml.PauliX(wires=1)) + +############################################################################## +# The fast c++ simulator device ``"lightning.qubit"`` also supports adjoint differentiation, +# but here we want to quickly prototype a minimal version to illustrate how the algorithm works. +# We recommend performing +# adjoint differentiation on ``"lightning.qubit"`` for substantial performance increases. +# +# We will use the ``circuit`` QNode just for comparison purposes. Throughout this +# demo, we will instead use a list of its operations ``ops`` and a single observable ``M``. + +n_gates = 4 +n_params = 3 + +ops = [ + qml.RX(x[0], wires=0), + qml.CNOT(wires=(0,1)), + qml.RY(x[1], wires=1), + qml.RZ(x[2], wires=1) +] +M = qml.PauliX(wires=1) + +############################################################################## +# We will be using internal functions to manipulate the nuts and bolts of a statevector +# simulation. +# +# Internally, the statevector simulation uses a 2x2x2x... array to represent the state, whereas +# the result of a measurement ``qml.state()`` flattens this internal representation. Each dimension +# in the statevector corresponds to a different qubit. +# +# The internal functions ``create_initial_state`` and ``apply_operation`` +# make additional assumptions about their inputs, and will fail or give incorrect results +# if those assumptions are not met. To work with these simulation tools, all operations should provide +# a matrix, and all wires must corresponding to dimensions of the statevector. This means all wires must already +# be integers starting from ``0``, and not exceed the number of dimensions in the state vector. +# + +from pennylane.devices.qubit import create_initial_state, apply_operation + +state = create_initial_state((0, 1)) + +for op in ops: + state = apply_operation(op, state) + +print(state) + +############################################################################## +# We can think of the expectation :math:`\langle M \rangle` as an inner product between a bra and a ket: +# +# .. math:: \langle M \rangle = \langle b | k \rangle = \langle \Psi | M | \Psi \rangle, +# +# where +# +# .. math:: \langle b | = \langle \Psi| M = \langle 0 | U_0^{\dagger} \dots U_n^{\dagger} M, +# +# .. math:: | k \rangle = |\Psi \rangle = U_n U_{n-1} \dots U_0 |0\rangle. +# +# We could have attached :math:`M,` a Hermitian observable (:math:`M^{\dagger}=M`), to either the +# bra or the ket, but attaching it to the bra side will be useful later. +# +# Using the ``state`` calculated above, we can create these :math:`|b\rangle` and :math:`|k\rangle` +# vectors. + +bra = apply_operation(M, state) +ket = state + +############################################################################## +# Now we use ``np.vdot`` to take their inner product. ``np.vdot`` sums over all dimensions +# and takes the complex conjugate of the first input. + +M_expval = np.vdot(bra, ket) +print("vdot : ", M_expval) +print("QNode : ", circuit(x)) + +############################################################################## +# We got the same result via both methods! This validates our use of ``vdot`` and +# device methods. +# +# But the dividing line between what makes the "bra" and "ket" vector is actually +# fairly arbitrary. We can divide the two vectors at any point from one :math:`\langle 0 |` +# to the other :math:`|0\rangle.` For example, we could have used +# +# .. math:: \langle b_n | = \langle 0 | U_1^{\dagger} \dots U_n^{\dagger} M U_n, +# +# .. math:: |k_n \rangle = U_{n-1} \dots U_1 |0\rangle, +# +# and gotten the exact same results. Here, the subscript :math:`n` is used to indicate that :math:`U_n` +# was moved to the bra side of the expression. Let's calculate that instead: + +bra_n = create_initial_state((0, 1)) + +for op in ops: + bra_n = apply_operation(op, bra_n) +bra_n = apply_operation(M, bra_n) +bra_n = apply_operation(qml.adjoint(ops[-1]), bra_n) + +ket_n = create_initial_state((0, 1)) + +for op in ops[:-1]: # don't apply last operation + ket_n = apply_operation(op, ket_n) + +M_expval_n = np.vdot(bra_n, ket_n) +print(M_expval_n) + +############################################################################## +# Same answer! +# +# We can calculate this in a more efficient way if we already have the +# initial ``state`` :math:`| \Psi \rangle.` To shift the splitting point, we don't +# have to recalculate everything from scratch. We just remove the operation from +# the ket and add it to the bra: +# +# .. math:: \langle b_n | = \langle b | U_n, +# +# .. math:: |k_n\rangle = U_n^{\dagger} |k\rangle . +# +# For the ket vector, you can think of :math:`U_n^{\dagger}` as "eating" its +# corresponding unitary from the vector, erasing it from the list of operations. +# +# Of course, we actually work with the conjugate transpose of :math:`\langle b_n |,` +# +# .. math:: |b_n\rangle = U_n^{\dagger} | b \rangle. +# +# Once we write it in this form, we see that the adjoint of the operation :math:`U_n^{\dagger}` +# operates on both :math:`|k_n\rangle` and :math:`|b_n\rangle` to move the splitting point right. +# +# Let's call this the "version 2" method. + +bra_n_v2 = apply_operation(M, state) +ket_n_v2 = state + +adj_op = qml.adjoint(ops[-1]) + +bra_n_v2 = apply_operation(adj_op, bra_n_v2) +ket_n_v2 = apply_operation(adj_op, ket_n_v2) + +M_expval_n_v2 = np.vdot(bra_n_v2, ket_n_v2) +print(M_expval_n_v2) + +############################################################################## +# Much simpler! +# +# We can easily iterate over all the operations to show that the same result occurs +# no matter where you split the operations: +# +# .. math:: \langle b_i | = \langle b_{i+1}| U_{i}, +# +# .. math:: |k_{i+1} \rangle = U_{i} |k_{i}\rangle. +# +# Rewritten, we have our iteration formulas +# +# .. math:: | b_i \rangle = U_i^{\dagger} |b_{i+1}\rangle, +# +# .. math:: | k_i \rangle = U_i^{\dagger} |k_{i+1}\rangle. +# +# For each iteration, we move an operation from the ket side to the bra side. +# We start near the center at :math:`U_n` and reverse through the operations list until we reach :math:`U_0.` + +bra_loop = apply_operation(M, state) +ket_loop = state + +for op in reversed(ops): + adj_op = qml.adjoint(op) + bra_loop = apply_operation(adj_op, bra_loop) + ket_loop = apply_operation(adj_op, ket_loop) + print(np.vdot(bra_loop, ket_loop)) + +############################################################################## +# Finally to Derivatives! +# ----------------------- +# +# We showed how to calculate the same thing a bunch of different ways. Why is this useful? +# Wherever we cut, we can stick additional things in the middle. What are we sticking in the middle? +# The derivative of a gate. +# +# For simplicity's sake, assume each unitary operation :math:`U_i` is a function of a single +# parameter :math:`\theta_i.` For non-parametrized gates like CNOT, we say its derivative is zero. +# We can also generalize the algorithm to multi-parameter gates, but we leave those out for now. +# +# Remember that each parameter occurs twice in :math:`\langle M \rangle:` once in the bra and once in +# the ket. Therefore, we use the product rule to take the derivative with respect to both locations: +# +# .. math:: +# \frac{\partial \langle M \rangle}{\partial \theta_i} = +# \langle 0 | U_1^{\dagger} \dots \frac{\text{d} U_i^{\dagger}}{\text{d} \theta_i} \dots M \dots U_i \dots U_1 | 0\rangle. +# +# +# .. math:: +# + \langle 0 | U_1^{\dagger} \dots U_i^{\dagger} \dots M \dots \frac{\text{d} U_i}{\text{d} \theta_i} \dots U_1 |0\rangle +# +# We can now notice that those two components are complex conjugates of each other, so we can +# further simplify. Note that each term is not an expectation value of a Hermitian observable, +# and therefore not guaranteed to be real. +# When we add them together, the imaginary part cancels out, and we obtain twice the +# value of the real part: +# +# .. math:: +# = 2 \cdot \text{Re}\left( \langle 0 | U_1^{\dagger} \dots U_i^{\dagger} \dots M \dots \frac{\text{d} U_i}{\text{d} \theta_i} \dots U_1 |0\rangle \right). +# +# We can take that formula and break it into its "bra" and "ket" halves for a derivative at the :math:`i` th position: +# +# .. math:: +# \frac{\partial \langle M \rangle }{\partial \theta_i } = +# 2 \text{Re} \left( \langle b_i | \frac{\text{d} U_i }{\text{d} \theta_i} | k_i \rangle \right) +# +# where +# +# .. math:: \langle b_i | = \langle 0 | U_1^{\dagger} \dots U_n^{\dagger} M U_n \dots U_{i+1}, +# +# +# .. math :: |k_i \rangle = U_{i-1} \dots U_1 |0\rangle. +# +# Notice that :math:`U_i` does not appear in either the bra or the ket in the above equations. +# These formulas differ from the ones we used when just calculating the expectation value. +# For the actual derivative calculation, we use a temporary version of the bra, +# +# .. math:: | \tilde{k}_i \rangle = \frac{\text{d} U_i}{\text{d} \theta_i} | k_i \rangle +# +# and use these to get the derivative +# +# .. math:: +# \frac{\partial \langle M \rangle}{\partial \theta_i} = 2 \text{Re}\left( \langle b_i | \tilde{k}_i \rangle \right). +# +# Both the bra and the ket can be calculated recursively: +# +# .. math:: | b_{i} \rangle = U^{\dagger}_{i+1} |b_{i+1}\rangle, +# +# .. math:: | k_{i} \rangle = U^{\dagger}_{i} |k_{i+1}\rangle. +# +# We can iterate through the operations starting at :math:`n` and ending at :math:`1.` +# +# We do have to calculate initial state first, the "forward" pass: +# +# .. math:: |\Psi\rangle = U_{n} U_{n-1} \dots U_0 |0\rangle. +# +# Once we have that, we only have about the same amount of work to calculate all the derivatives, +# instead of quadratically more work. +# +# Derivative of an Operator +# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# One final thing before we get back to coding: how do we get the derivative of an operator? +# +# Most parametrized gates can be represented in terms of Pauli Rotations, which can be written as +# +# .. math:: U = e^{i c \hat{G} \theta} +# +# for a Pauli matrix :math:`\hat{G}`, a constant :math:`c,` and the parameter :math:`\theta.` +# Thus we can easily calculate their derivatives: +# +# .. math:: \frac{\text{d} U}{\text{d} \theta} = i c \hat{G} e^{i c \hat{G} \theta} = i c \hat{G} U . +# +# Luckily, PennyLane already has a built-in function for calculating this. + +grad_op0 = qml.operation.operation_derivative(ops[0]) +print(grad_op0) + +############################################################################## +# Now for calculating the full derivative using the adjoint method! +# +# We loop over the reversed operations, just as before. But if the operation has a parameter, +# we calculate its derivative and append it to a list before moving on. Since the ``operation_derivative`` +# function spits back out a matrix instead of an operation, +# we have to use :class:`~pennylane.QubitUnitary` instead to create :math:`|\tilde{k}_i\rangle.` + +bra = apply_operation(M, state) +ket = state + +grads = [] + +for op in reversed(ops): + adj_op = qml.adjoint(op) + ket = apply_operation(adj_op, ket) + + # Calculating the derivative + if op.num_params != 0: + dU = qml.operation.operation_derivative(op) + ket_temp = apply_operation(qml.QubitUnitary(dU, op.wires), ket) + + dM = 2 * np.real(np.vdot(bra, ket_temp)) + grads.append(dM) + + bra = apply_operation(adj_op, bra) + + +# Finally reverse the order of the gradients +# since we calculated them in reverse +grads = grads[::-1] + +print("our calculation: ", [float(grad) for grad in grads]) + +grad_compare = jax.grad(circuit)(x) +print("comparison: ", grad_compare) + +############################################################################## +# It matches!!! +# +# If you want to use adjoint differentiation without having to code up your own +# method that can support arbitrary circuits, you can use ``diff_method="adjoint"`` in PennyLane with +# ``"default.qubit"`` or PennyLane's fast C++ simulator ``"lightning.qubit"``. + + +dev_lightning = qml.device('lightning.qubit', wires=2) + +@qml.qnode(dev_lightning, diff_method="adjoint") +def circuit_adjoint(a): + qml.RX(a[0], wires=0) + qml.CNOT(wires=(0,1)) + qml.RY(a[1], wires=1) + qml.RZ(a[2], wires=1) + return qml.expval(M) + +print(jax.grad(circuit_adjoint)(x)) + +############################################################################## +# Performance +# -------------- +# +# The algorithm gives us the correct answers, but is it worth using? Parameter-shift +# gradients require at least two executions per parameter, so that method gets more +# and more expensive with the size of the circuit, especially on simulators. +# Backpropagation demonstrates decent time scaling, but requires more and more +# memory as the circuit gets larger. Simulation of large circuits is already +# RAM-limited, and backpropagation constrains the size of possible circuits even more. +# PennyLane also achieves backpropagation derivatives from a Python simulator and interface-specific functions. +# The ``"lightning.qubit"`` device does not support +# backpropagation, so backpropagation derivatives lose the speedup from an optimized +# simulator. +# +# With adjoint differentiation on ``"lightning.qubit"``, you can get the best of both worlds: fast and +# memory efficient. +# +# But how fast? The provided script `here `__ +# generated the following images on a mid-range laptop. +# The backpropagation times were produced with the Python simulator ``"default.qubit"``, while parameter-shift +# and adjoint differentiation times were calculated with ``"lightning.qubit"``. +# The adjoint method clearly wins out for performance. +# +# .. figure:: ../_static/demonstration_assets/adjoint_diff/scaling.png +# :width: 80% +# :align: center +# +# +# Conclusions +# ----------- +# +# So what have we learned? Adjoint differentiation is an efficient method for differentiating +# quantum circuits with state vector simulation. It scales nicely in time without +# excessive memory requirements. Now that you've seen how the algorithm works, you can +# better understand what is happening when you select adjoint differentiation from one +# of PennyLane's simulators. +# +# +# Bibliography +# ------------- +# +# Jones and Gacon. Efficient calculation of gradients in classical simulations of variational quantum algorithms. +# `https://arxiv.org/abs/2009.02823 `__ +# +# Xiu-Zhe Luo, Jin-Guo Liu, Pan Zhang, and Lei Wang. Yao.jl: `Extensible, efficient framework for quantum +# algorithm design `__ , 2019 +# diff --git a/demonstrations_v2/tutorial_adjoint_diff/metadata.json b/demonstrations_v2/tutorial_adjoint_diff/metadata.json new file mode 100644 index 0000000000..ddf22df68a --- /dev/null +++ b/demonstrations_v2/tutorial_adjoint_diff/metadata.json @@ -0,0 +1,68 @@ +{ + "title": "Adjoint Differentiation", + "authors": [ + { + "username": "christina" + } + ], + "dateOfPublication": "2021-11-23T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adjoint_differentiation.png" + } + ], + "seoDescription": "Learn how to use the adjoint method to compute gradients of quantum circuits.", + "doi": "", + "references": [ + { + "id": "Jones2020", + "type": "article", + "title": "Efficient calculation of gradients in classical simulations of variational quantum algorithms", + "authors": "Jones and Gacon", + "year": "2020", + "journal": "", + "doi": "10.48550/arXiv.2009.02823", + "url": "https://arxiv.org/abs/2009.02823" + }, + { + "id": "Luo2019", + "type": "article", + "title": "Yao.jl: Extensible, efficient framework for quantum algorithm design", + "authors": "Xiu-Zhe Luo, Jin-Guo Liu, Pan Zhang, and Lei Wang", + "year": "2019", + "journal": "Quantum", + "doi": "10.22331/q-2020-10-11-341", + "url": "https://quantum-journal.org/papers/q-2020-10-11-341/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_stochastic_parameter_shift", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_adjoint_diff/requirements.in b/demonstrations_v2/tutorial_adjoint_diff/requirements.in new file mode 100644 index 0000000000..277e023bab --- /dev/null +++ b/demonstrations_v2/tutorial_adjoint_diff/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +pennylane +matplotlib diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py b/demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py new file mode 100644 index 0000000000..5af0f4090f --- /dev/null +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py @@ -0,0 +1,465 @@ +r"""Adversarial attacks and robustness for quantum machine learning +=============================================== +""" + +###################################################################### +# This demo is based on the paper *A Comparative Analysis of Adversarial Robustness for Quantum and +# Classical Machine Learning Models* [#Wendlinger]_ by M. Wendlinger, K. Tscharke and P. Debus, which dives into the +# world of adversarial attacks on quantum computers to find relations to classical adversarial machine +# learning. In the following, we briefly cover the necessary theoretical baselines of adversarial +# attacks on classification networks before giving a hands-on example of how to construct such an +# attack and how we can make QML models more robust. Let’s go! +# +# .. figure:: ../_static/demonstration_assets/adversarial_attacks_QML/QAML_overview.png +# :align: center +# :width: 80% +# + +###################################################################### +# What are adversarial attacks? +# ----------------------------- +# +# Adversarial attacks are small, carefully crafted perturbations to input data that lead to high rates +# of misclassification for machine learning models, while the data seems to be identical to the +# original for human observers. In particular, images are sensitive to this type of attack, as small +# manipulations of the pixels can fool a classifier without any changes being visible to humans. A +# typical example of an adversarial attack is shown in the picture above, where an image of a panda is +# manipulated by adding a small noise to each pixel. The original image is correctly classified as +# "Panda," while the image with tiny manipulations is falsely classified as "Gibbon" (the noise is +# magnified in the figure so we can actually see it). This example is adapted from a famous paper [#Goodfellow]_ +# showing the vulnerability of classical machine learning models to adversarial attacks. +# +# Mathematically, the goal of an (untargeted) attack is to achieve a misclassification of the model +# such that a sample :math:`x` leads to a predicted label :math:`y' \neq y` that is not the true label +# :math:`y.` This is achieved by finding the perturbation :math:`\delta\in\Delta` to the original +# input that maximizes the loss of the true class. For a loss function :math:`\mathcal{L},` a model +# :math:`f: \mathbb{R}^{D} \to \mathbb{R}^K` (mapping :math:`D`-dimensional input to `softmax `_ +# probability scores of :math:`K` classes with model parameters :math:`\theta^*`), the objective of +# the untargeted attack is: +# +# .. math:: \delta \equiv \; \underset{\delta^{\prime} \in \Delta}{\operatorname{argmax}} \;\mathcal{L}\left(f\left(x+\delta^{\prime} ; \theta^*\right), y\right). +# +# Later, when we show how to actually construct such an attack, we will revisit this +# equation. For an adversarial attack to be considered useful, it must hold that the modifications to +# the input elements are imperceptible, i.e. that +# :math:`\Delta=\{\delta \in \mathbb{R}^{D}: \| \delta\|_{\infty} \le \varepsilon\},` where +# :math:`\varepsilon` is some small bound, typically below :math:`0.1.` +# + +###################################################################### +# Why are adversarial attacks dangerous? +# -------------------------------------- +# +# Machine learning (ML) is becoming essential in many security-critical applications, for example in +# `cybersecurity `_, the `medical sector `_, or `autonomous vehicles `_. In cybersecurity, ML models are used, +# amongst others, for malware detection or the prevention of attacks on networks. Given their critical +# roles, any successful attack of these ML models can lead to severe consequences, ranging from +# financial losses and privacy violations to threats to human life. As we have seen above, adversarial +# attacks are imperceptible to humans, and hence difficult to detect. For this reason, it is essential +# that ML models in security-critical applications are robust against these types of attacks. +# +# `Quantum machine learning (QML) <[/whatisqml](https://pennylane.ai/qml/whatisqml/)>`__ has been shown to have theoretical advantages over classical ML methods [#Liu]_ +# and is becoming increasingly popular. However, first works in this direction suggest that QML +# suffers from the same vulnerabilities as classical ML [#Lu]_. How the vulnerability of QML models +# relates to classical models and how robust the models are in comparison is evaluated in [#Wendlinger]_. But +# enough talk, let’s see how we can actually attack a QML model! +# + +###################################################################### +# Let’s see this in action! +# ------------------------- +# +# Setting up the environment +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# For this tutorial, we will use the PennyLane :class:`~pennylane.qnn.TorchLayer` class to perform circuit operations +# and optimizations with the PyTorch backend. Thus, we need to import the torch library alongside +# PennyLane: +# + +import pennylane as qml +from pennylane import numpy as np +import torch +from matplotlib import pyplot as plt + +###################################################################### +# Visualization of the dataset +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# As in the paper [#Wendlinger]_, we make use of the `PlusMinus `_ dataset (available via `PennyLane Datasets `_), which serves as a good baseline for +# evaluating a QML image classification model’s ability to find useful features in the input. It also +# allows us to define the usefulness of attacks on the QML model while being low-dimensional enough to +# perform scalable training (more info on the dataset can be found in [#Wendlinger]_). It consists of four +# classes of :math:`16\times16` pixel grayscale images which show one of the four symbols +# :math:`\{+,-,\vdash,\dashv\}.` Below we visualize one sample of each class to get an understanding +# of the dataset. +# +# The data can be loaded directly from `PennyLane Datasets `_ for easy integration into PennyLane circuits and optimization code. +# + +# we can use the dataset hosted on PennyLane +[pm] = qml.data.load('other', name='plus-minus') + +X_train = pm.img_train # shape (1000,16,16) +X_test = pm.img_test # shape (200,16,16) +Y_train = pm.labels_train # shape (1000,) +Y_test = pm.labels_test # shape (200,) + +# show one image from each class (in the train- and testsets, the images are randomly permuted) +x_vis = [ + (X_train[Y_train == 0])[0], + (X_train[Y_train == 1])[0], + (X_train[Y_train == 2])[0], + (X_train[Y_train == 3])[0], +] +y_vis = [0, 1, 2, 3] + + +# later when we train the model we include the predictions as well, so let's just add the functionality here +def visualize_data(x, y, pred=None): + n_img = len(x) + labels_list = ["\u2212", "\u002b", "\ua714", "\u02e7"] + fig, axes = plt.subplots(1, 4, figsize=(8, 2)) + for i in range(n_img): + axes[i].imshow(x[i], cmap="gray") + if pred is None: + axes[i].set_title("Label: {}".format(labels_list[y[i]])) + else: + axes[i].set_title("Label: {}, Pred: {}".format(labels_list[y[i]], labels_list[pred[i]])) + plt.tight_layout(w_pad=2) + # plt.show() + + +visualize_data(x_vis, y_vis) + +###################################################################### +# Building the QML circuit for classification +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We will make use of a :doc:`data-reuploading ` scheme to encode the 256 input pixels into the latent space +# of the quantum classifier. To this end, the :class:`~pennylane.StronglyEntanglingLayers` template provides an +# easy-to-use structure for the circuit design. The output of our classifier is a four-dimensional +# vector resulting from Pauli Z oberservables along the first four qubits. These outputs (unnormalized +# probability scores — i.e. logits) are then used in the `CrossEntropyLoss `_ function to optimize the +# model parameters. Together with the PyTorch integration mentioned above, the classifier code looks +# like the following: +# + +#### Hyperparameters #### +input_dim = 256 +num_classes = 4 +num_layers = 32 +num_qubits = 8 +num_reup = 3 + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +print("Using device:", device) + + +class QML_classifier(torch.nn.Module): + """ + Class for creating a quantum machine learning (classification) model based on the StronglyEntanglingLayers template. + + Args: + input_dim: the dimension of the input samples + output_dim: the dimension of the output, i.e. the numbers of classes + num_qubits: the number of qubits in the circuit + num_layers: the number of layers within the StronglyEntanglingLayers template + """ + def __init__(self, input_dim, output_dim, num_qubits, num_layers): + super().__init__() + torch.manual_seed(1337) # fixed seed for reproducibility + self.num_qubits = num_qubits + self.output_dim = output_dim + self.num_layers = num_layers + self.device = qml.device("lightning.qubit", wires=self.num_qubits) + self.weights_shape = qml.StronglyEntanglingLayers.shape( + n_layers=self.num_layers, n_wires=self.num_qubits + ) + + @qml.qnode(self.device) + def circuit(inputs, weights, bias): + inputs = torch.reshape(inputs, self.weights_shape) + qml.StronglyEntanglingLayers( + weights=weights * inputs + bias, wires=range(self.num_qubits) + ) + return [qml.expval(qml.PauliZ(i)) for i in range(self.output_dim)] + + param_shapes = {"weights": self.weights_shape, "bias": self.weights_shape} + init_vals = { + "weights": 0.1 * torch.rand(self.weights_shape), + "bias": 0.1 * torch.rand(self.weights_shape), + } + + # initialize the quantum circuit + self.qcircuit = qml.qnn.TorchLayer( + qnode=circuit, weight_shapes=param_shapes, init_method=init_vals + ) + + def forward(self, x): + inputs_stack = torch.hstack([x] * num_reup) + return self.qcircuit(inputs_stack) + + +###################################################################### +# Training the classifier +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +# The ’test’ set will be used as validation in each training step to verify the generalization +# capabilities of our classifier. +# + +#### Hyperparameters #### +learning_rate = 0.1 +epochs = 4 +batch_size = 20 + +# we use a subset of the training and validation data for this tutorial to speed up the training +feats_train = torch.from_numpy(X_train[:200]).reshape(200, -1).to(device) +feats_test = torch.from_numpy(X_test[:50]).reshape(50, -1).to(device) +labels_train = torch.from_numpy(Y_train[:200]).to(device) +labels_test = torch.from_numpy(Y_test[:50]).to(device) +num_train = feats_train.shape[0] + +# initialize the model, loss function and optimization algorithm (Adam optimizer) +qml_model = QML_classifier(input_dim, num_classes, num_qubits, num_layers) +loss = torch.nn.CrossEntropyLoss() +optimizer = torch.optim.Adam(qml_model.parameters(), lr=learning_rate) +num_batches = feats_train.shape[0] // batch_size + + +# set up a measure for performance +def accuracy(labels, predictions): + acc = 0 + for l, p in zip(labels, predictions): + if torch.argmax(p) == l: + acc += 1 + acc = acc / len(labels) + return acc + + +# generate randomly permutated batches to speed up training +def gen_batches(num_samples, num_batches): + assert num_samples % num_batches == 0 + perm_ind = torch.reshape(torch.randperm(num_samples), (num_batches, -1)) + return perm_ind + + +# display accuracy and loss after each epoch (to speed up runtime, only do this for first 100 samples) +def print_acc(epoch, max_ep=4): + predictions_train = [qml_model(f) for f in feats_train[:50]] + predictions_test = [qml_model(f) for f in feats_test] + cost_approx_train = loss(torch.stack(predictions_train), labels_train[:50]) + cost_approx_test = loss(torch.stack(predictions_test), labels_test) + acc_approx_train = accuracy(labels_train[:50], predictions_train) + acc_approx_test = accuracy(labels_test, predictions_test) + print( + f"Epoch {epoch}/{max_ep} | Approx Cost (train): {cost_approx_train:0.7f} | Cost (val): {cost_approx_test:0.7f} |" + f" Approx Acc train: {acc_approx_train:0.7f} | Acc val: {acc_approx_test:0.7f}" + ) + + +print( + f"Starting training loop for quantum variational classifier ({num_qubits} qubits, {num_layers} layers)..." +) + +# optimize over model parameters for given number of epochs +for ep in range(0, epochs): + batch_ind = gen_batches(num_train, num_batches) + print_acc(epoch=ep) + + for it in range(num_batches): + optimizer.zero_grad() + feats_train_batch = feats_train[batch_ind[it]] + labels_train_batch = labels_train[batch_ind[it]] + + outputs = [qml_model(f) for f in feats_train_batch] + batch_loss = loss(torch.stack(outputs), labels_train_batch) + # if REG: + # loss = loss + lipschitz_regularizer(regularization_rate, model.qcircuit.weights) + batch_loss.backward() + optimizer.step() + +print_acc(epochs) + +###################################################################### +# Evaluation - benign data +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# + +# show accuracy +x_vis_torch = torch.from_numpy(np.array(x_vis).reshape(4, -1)) +y_vis_torch = torch.from_numpy(np.array(y_vis)) +benign_preds = [qml_model(f) for f in x_vis_torch] + +benign_class_output = [torch.argmax(p) for p in benign_preds] +visualize_data(x_vis, y_vis, benign_class_output) + +###################################################################### +# Let’s break stuff! +# ~~~~~~~~~~~~~~~~~~ +# +# As described before, the mathematical notation for an adversarial attack is as follows: +# +# .. math:: \delta \equiv \; \underset{\delta^{\prime} \in \Delta}{\operatorname{argmax}} \;\mathcal{L}\left(f\left(x+\delta^{\prime} ; \theta^*\right), y\right). +# +# This equation can be summarized in a simple step-by-step recipe. In basic terms, we +# perform a forward and backward pass through the model and loss function (just like we do during +# training) for the specific samples that we want to find perturbations for. The difference to +# training steps is that the gradients we calculate are **with respect to the input features** of the +# data samples. Using these gradients, we find the direction of ascent in the loss function (as we +# want to force high losses, i.e. bad model performance for the specific samples). Finally, we clip +# the delta (perturbations), such that they lie within the epsilon boundary we set beforehand (this is +# just some hyperparameter wich limits the attack strength). All of these steps can be seen in the +# code below: +# + + +# simple implementation of projected gradient descent (PGD) attack (without randomized starting points — cf. BIM) +# for an introduction to PGD, see https://adversarial-ml-tutorial.org/adversarial_examples/#projected-gradient-descent +def PGD(model, feats, labels, epsilon=0.1, alpha=0.01, num_iter=10): + + # initialize image perturbations with zero + delta = torch.zeros_like(feats, requires_grad=True) + for t in range(num_iter): + feats_adv = feats + delta + outputs = [model(f) for f in feats_adv] + + # forward & backward pass through the model, accumulating gradients + l = loss(torch.stack(outputs), labels) + l.backward() + + # use gradients with respect to inputs and clip the results to lie within the epsilon boundary + delta.data = (delta + alpha * delta.grad.detach().sign()).clamp(-epsilon, epsilon) + delta.grad.zero_() + return delta.detach() + + +###################################################################### +# Evaluation — model under attack +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + +perturbations = PGD(model=qml_model, feats=x_vis_torch, labels=y_vis_torch, epsilon=0.1) +perturbed_x = x_vis_torch + perturbations + +# check model performance +adversarial_preds = [qml_model(f) for f in perturbed_x] +adversarial_class_output = [torch.argmax(p) for p in adversarial_preds] + +visualize_data(perturbed_x.reshape(-1, 16, 16), y_vis, adversarial_class_output) + +###################################################################### +# We can see the devastating effect of a simple PGD (projected gradient descent) attack using a perturbation strength +# :math:`\varepsilon=0.1,` where the model misclassifies each of the four samples we used for +# visualization of the dataset. For humans, the images are still very easily classifiable, the +# perturbations look mostly like random noise added to the images. All in all, the accuracy of the +# model for the perturbed trainset decreases to around :math:`0.1,` so almost all samples of the +# dataset are misclassified! +# +# Using the code above, you can try the attack on your own and check which samples are more robust +# against attacks and which labels the model (wrongly) assigns to the data. The remaining question is: +# Can we defend against such attacks? How can we train our (QML) models to be more robust against +# adversarial attacks? This is a field of much active research and in general a very hard problem to +# solve for different kinds of attacks, model architectures and input data, but a very simple approach +# is given below. +# + +###################################################################### +# Increasing the robustness of QML models +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The easiest way to make our model aware of slight modifications is to simply include the perturbed +# images into the training dataset (this can be seen as data augmentation and is often done in +# classical machine learning using noise). That way, the model is expected to be more robust against +# samples that were constructed using the same attack without changing anything in the model +# architecture itself; this method is called **adversarial (re)training**. +# + +adv_dataset = ( + PGD(model=qml_model, feats=feats_train[:20], labels=labels_train[:20], epsilon=0.1) + + feats_train[:20] +) + +feats_retrain = torch.cat((feats_train, adv_dataset)) +labels_retrain = torch.cat((labels_train, labels_train[:20])) +epochs_retraining = 2 + +for ep in range(0, epochs_retraining): + batch_ind = gen_batches(num_train, num_batches) + print_acc(epoch=ep, max_ep=2) + + for it in range(num_batches): + optimizer.zero_grad() + feats_train_batch = feats_retrain[batch_ind[it]] + labels_train_batch = labels_retrain[batch_ind[it]] + + outputs = [qml_model(f) for f in feats_train_batch] + batch_loss = loss(torch.stack(outputs), labels_train_batch) + # if REG: + # loss = loss + lipschitz_regularizer(regularization_rate, model.qcircuit.weights) + batch_loss.backward() + optimizer.step() + +print_acc(epochs_retraining, max_ep=2) + +###################################################################### +# Evaluation of the retrained model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + +adversarial_preds = [qml_model(f) for f in perturbed_x] +adversarial_class_output = [torch.argmax(p) for p in adversarial_preds] + +visualize_data(perturbed_x.reshape(-1, 16, 16), y_vis, adversarial_class_output) + +###################################################################### +# We can see that the model now correctly classifies three out of the four perturbed input images. As +# before, you can adapt the code above and test the retrained model for the whole dataset to see how +# much the accuracy under attack improves overall. +# + +###################################################################### +# Conclusion +# ---------- +# +# In this demo we saw how to perform adversarial attacks on quantum variational classification models. +# The resulting perturbed images (which — for humans — still closely resemble the original ones) +# result in large misclassification rates of the QML models, showing the vulnerability of such models +# to adversarial attacks. Consequently, we showcased one possibility to increase the adversarial +# robustness using adversarial retraining, which led the model under attack to perform better. +# +# While this method does seem useful, it needs to be noted that this is a very basic approach (just +# include the pertubed images in the trainset, duh!) and might not work equally well for a different +# attack or slightly changed epsilon values. There are approaches (e.g. using Lipschitz regularization +# as shown in [#Wendlinger]_) that have the potential to be more effective in increasing the robustness, but this +# is still a field of ongoing research, and many different approaches are waiting to be discovered! +# + +###################################################################### +# References +# ---------- +# +# .. [#Wendlinger] +# Maximilian Wendlinger and Kilian Tscharke and Pascal Debus +# "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models" +# `arXiv:2404.16154 `__, 2024 +# +# .. [#Goodfellow] +# Ian J. Goodfellow and Jonathon Shlens and Christian Szegedy +# "Explaining and harnessing adversarial examples" +# `arXiv:1412.6572 `__, 2014 +# +# .. [#Liu] +# Yunchao Liu and Srinivasan Arunachalam and Kristan Temme +# "A rigorous and robust quantum speed-up in supervised machine learning" +# `arXiv:2010.02174 `__, 2020 +# +# .. [#Lu] +# Sirui Lu and Lu-Ming Duan and Dong-Ling Deng +# "Quantum Adversarial Machine Learning" +# `arXiv:2001.00030 `__, 2019 +# +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json new file mode 100644 index 0000000000..c1041f3d21 --- /dev/null +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json @@ -0,0 +1,85 @@ +{ + "title": "Adversarial attacks and robustness for quantum machine learning", + "authors": [ + { + "username": "mxw" + }, + { + "username": "kil" + } + ], + "dateOfPublication": "2024-09-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Quantum Computing" + ], + "tags": [ + "Quantum machine learning", + "QML", + "Adversarial attacks", + "Quantum security" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" + } + ], + "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", + "doi": "", + "references": [ + { + "id": "Wendlinger2024", + "type": "preprint", + "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", + "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", + "year": "2024", + "doi": "10.48550/arXiv.2404.16154", + "url": "https://arxiv.org/abs/2404.16154" + }, + { + "id": "Goodfellow2014", + "type": "preprint", + "title": "Explaining and harnessing adversarial examples", + "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", + "year": "2014", + "doi": "10.48550/arXiv.1412.6572", + "url": "https://arxiv.org/abs/1412.6572" + }, + { + "id": "Liu2020", + "type": "preprint", + "title": "A rigorous and robust quantum speed-up in supervised machine learning", + "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", + "year": "2020", + "doi": "10.48550/arXiv.2010.02174", + "url": "https://arxiv.org/abs/2010.02174" + }, + { + "id": "Lu2019", + "type": "preprint", + "title": "Quantum Adversarial Machine Learning", + "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", + "year": "2019", + "doi": "10.48550/arXiv.2001.00030", + "url": "https://arxiv.org/abs/2001.00030" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2404.16154" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "hardware": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in b/demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in new file mode 100644 index 0000000000..929357a2b9 --- /dev/null +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +torch diff --git a/demonstrations_v2/tutorial_apply_qsvt/demo.py b/demonstrations_v2/tutorial_apply_qsvt/demo.py new file mode 100644 index 0000000000..575cab9280 --- /dev/null +++ b/demonstrations_v2/tutorial_apply_qsvt/demo.py @@ -0,0 +1,404 @@ +r""" +QSVT in Practice +====================================== + +.. meta:: + :property="og:description": Quantum Singular Value Transformation algorithm + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_intro_qsvt.png + +.. related:: + + tutorial_intro_qsvt Intro to QSVT + function_fitting_qsp Function Fitting using Quantum Signal Processing + +*Authors: Jay Soni, Jarrett Smalley [Rolls-Royce] — Posted: 22 August 2023.* + +The Quantum Singular Value Transformation (QSVT) is a quantum algorithm that +allows us to apply arbitrary polynomial transformations to the singular values +of a matrix [#qsvt]_. This demo, written in collaboration between Xanadu and +Rolls-Royce, provides a practical guide for the QSVT functionality in PennyLane, +by solving a linear system of equations (LSE) as a guiding example. + +| + +.. figure:: ../_static/demonstration_assets/apply_qsvt/thumbnail_tutorial_QSVT_for_Matrix_Inversion.png + :align: center + :width: 50% + :target: javascript:void(0) + +| + +Preliminaries +------------- +For a refresher on the basics of QSVT check out our :doc:`Intro to QSVT ` +tutorial. Let's recall how to apply QSVT in a circuit. This requires two pieces of information +as input: the matrix to be transformed and a set of phase angles which determine the polynomial +transformation. For now, we use placeholder values for the phase angles; we'll later describe +how to obtain them. The code below shows how to construct a basic QSVT circuit on two qubits: +""" + +import pennylane as qml +from pennylane import numpy as np + +dev = qml.device("default.qubit", wires=[0, 1]) + +A = np.array([[0.1, 0.2], [0.3, 0.4]]) +phase_angles = np.array([0.0, 1.0, 2.0, 3.0]) + + +@qml.qnode(dev) +def my_circuit(phase_angles): + qml.qsvt(A, phase_angles, wires=[0, 1]) + return qml.state() + + +############################################################################### +# We can now execute the circuit and visualize it. + +my_circuit(phase_angles) +print(qml.draw(my_circuit)(phase_angles)) + +############################################################################### +# We can inspect details by drawing the expanded circuit. The :class:`~.pennylane.QSVT` +# operation is composed of repeated applications of the :class:`~.pennylane.BlockEncode` and +# :class:`~.pennylane.PCPhase` (:math:`\Pi_{\phi}`) operations. + +print(my_circuit.tape.expand().draw()) + +############################################################################### +# Now let's look at an application of QSVT --- solving a linear system of equations. +# +# Problem +# ------- +# The most convenient way to represent a linear system of equations is as a matrix vector problem. +# Given a matrix :math:`A` and a vector :math:`\vec{b},` we want to solve :math:`A \cdot \vec{x} = \vec{b}.` +# This ultimately requires computing :math:`\vec{x} = A^{-1} \cdot \vec{b},` where for simplicity we +# assume that :math:`A` is invertible. +# +# :math:`A^{-1}` can be constructed directly by inverting the singular values of :math:`A^{T}.` We can +# leverage QSVT to accomplish this by finding the phase angles which apply a polynomial approximation +# to the transformation :math:`\frac{1}{x}.` This may seem simple in theory, but in practice there are +# a few technical details that need to be addressed. +# +# First, it is difficult to approximate :math:`\frac{1}{x}` close to :math:`x=0.` This leads to +# large degree polynomials and very deep quantum circuits. However, it turns out that +# we only need a good approximation up to the smallest singular value of the target matrix. +# We introduce the parameter :math:`\kappa` that defines the domain :math:`[\frac{1}{\kappa}, 1]` for which the +# approximation should be good. +# +# Second, the QSVT algorithm produces polynomials which are bounded in magnitude by one on +# the domain :math:`x \in [-1, 1].` However, :math:`\frac{1}{x}` falls outside the bounds on +# this domain. To remedy this, we introduce a scale factor :math:`s` and approximate +# :math:`s \cdot \frac{1}{x}.` +# +# Obtaining Phase Angles +# ---------------------- +# The QSVT phase angles :math:`\vec{\phi}` define the polynomial transformation. +# Here we describe two approaches to obtain the phase angles: +# +# 1. Using external packages that provide numerical angle solvers (`pyqsp `_). +# 2. Using PennyLane's differentiable workflow to optimize the phase angles. +# +# Let's use both methods to apply a polynomial transformation that approximates +# +# .. math:: P(x) = s \cdot \frac{1}{x}. +# +# Phase Angles from PyQSP +# ^^^^^^^^^^^^^^^^^^^^^^^ +# There are many numerical methods for computing the phase angles (see +# [#machineprecision]_, [#productdecomp]_). They can be readily used with +# PennyLane as long as the convention used to define the rotations +# matches the one used when applying QSVT. This is as simple as specifying the +# convention as a keyword argument to the :func:`~.pennylane.qsvt()` function. +# We demonstrate this by obtaining angles using the `pyqsp `_ library. +# +# The phase angles generated from pyqsp are presented below. A value of :math:`\kappa=4` was used +# and the scale factor was extracted from the pyqsp module. Remember that the number of +# phase angles determines the degree of the polynomial approximation. Below we display the 44 +# angles which produce a polynomial of degree 43. + +kappa = 4 +s = 0.10145775 +phi_pyqsp = [-2.287, 2.776, -1.163, 0.408, -0.16, -0.387, 0.385, -0.726, 0.456, 0.062, -0.468, 0.393, 0.028, -0.567, 0.76, -0.432, -0.011, 0.323, -0.573, 0.82, -1.096, 1.407, -1.735, 2.046, -2.321, 2.569, -2.819, -0.011, 2.71, -2.382, 2.574, 0.028, -2.749, 2.673, 0.062, -2.685, 2.416, 0.385, -0.387, -0.16, 0.408, -1.163, -0.365, 2.426] + +############################################################################### +# .. note:: +# +# We generated the angles using the following pyqsp functions. These methods have a randomized +# component which results in slightly different phase angles each time producing the same +# transformation: +# +# .. code-block:: bash +# +# >>> pcoefs, s = pyqsp.poly.PolyOneOverX().generate(kappa, return_coef=True, ensure_bounded=True, return_scale=True) +# >>> phi_pyqsp = pyqsp.angle_sequence.QuantumSignalProcessingPhases(pcoefs, signal_operator="Wx", tolerance=0.00001) +# +# +# Let's confirm that these angles perform the correct transformation. +# We use the :func:`~.pennylane.matrix()` function to obtain the output matrix +# of the QSVT circuit. The top-left entry is a polynomial approximation whose +# real component corresponds to our target function :math:`P(x).` + +x_vals = np.linspace(0, 1, 50) +target_y_vals = [s * (1 / x) for x in np.linspace(s, 1, 50)] + +qsvt_y_vals = [] +for x in x_vals: + poly_x = qml.matrix(qml.qsvt, wire_order=[0])( + x, phi_pyqsp, wires=[0], convention="Wx" # specify angles using convention=`Wx` + ) + qsvt_y_vals.append(np.real(poly_x[0, 0])) + +############################################################################### +# We plot the target function and our approximation generated from QSVT. + +import matplotlib.pyplot as plt + +plt.plot(x_vals, np.array(qsvt_y_vals), label="Re(qsvt)") +plt.plot(np.linspace(s, 1, 50), target_y_vals, label="target") + +plt.vlines(1 / kappa, -1.0, 1.0, linestyle="--", color="grey", label="1/kappa") +plt.vlines(0.0, -1.0, 1.0, color="black") +plt.hlines(0.0, -0.1, 1.0, color="black") + +plt.legend() +plt.show() + +############################################################################### +# Yay! We were able to get an approximation of the function :math:`s \cdot \frac{1}{x}` on the +# domain :math:`[\frac{1}{\kappa}, 1].` +# +# +# Phase Angles from Optimization +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# The QSVT operation, like all other operations in PennyLane, is fully differentiable. We can +# take advantage of this as an alternate approach to obtaining the phase angles by using gradient-based +# optimization. This method is very versatile because we can use the target function directly +# to generate a polynomial approximation from QSVT. +# +# A single QSVT circuit will produce a transformation that has a fixed parity. We can be +# clever and get a better approximation by using a sum of an even and odd polynomial. +# This is achieved by using a simple linear combination of unitaries +# (`LCU `_). We first split the phase angles into +# two groups (even and odd parity). Next, an ancilla qubit is prepared in equal superposition. +# We apply each QSVT operation, even or odd, conditioned on the ancilla. Finally, the ancilla +# qubit is reset. + + +def sum_even_odd_circ(x, phi, ancilla_wire, wires): + phi1, phi2 = phi[: len(phi) // 2], phi[len(phi) // 2:] + + qml.Hadamard(wires=ancilla_wire) # equal superposition + + # apply even and odd polynomial approx + qml.ctrl(qml.qsvt, control=(ancilla_wire,), control_values=(0,))(x, phi1, wires=wires) + qml.ctrl(qml.qsvt, control=(ancilla_wire,), control_values=(1,))(x, phi2, wires=wires) + + qml.Hadamard(wires=ancilla_wire) # un-prepare superposition + + +############################################################################### +# We now randomly initialize a total of 51 phase angles. This implies that the resulting +# transformation will be a sum of polynomials with degrees 25 and 26, respectively. + +np.random.seed(42) # set seed for reproducibility +phi = np.random.rand(51) + +############################################################################### +# Next, we select a mean-squared error (MSE) loss +# function. The error is computed using samples from the domain :math:`[\frac{1}{\kappa}, 1]` +# where the target function is defined. Since the polynomial produced by the QSVT circuit is +# complex valued, we compare its real value against our target function. We +# ignore the imaginary component for now. + +samples_x = np.linspace(1 / kappa, 1, 100) + +def target_func(x): + return s * (1 / x) + +def loss_func(phi): + sum_square_error = 0 + for x in samples_x: + qsvt_matrix = qml.matrix(sum_even_odd_circ, wire_order=["ancilla", 0])(x, phi, ancilla_wire="ancilla", wires=[0]) + qsvt_val = qsvt_matrix[0, 0] + sum_square_error += (np.real(qsvt_val) - target_func(x)) ** 2 + + return sum_square_error / len(samples_x) + + +############################################################################### +# Thanks to PennyLane's fully differentiable workflow, we can execute the optimization in just +# a few lines of code: + +# Optimization: +cost = 1 +iter = 0 +opt = qml.AdagradOptimizer(0.1) + +while cost > 0.5e-4: + iter += 1 + phi, cost = opt.step_and_cost(loss_func, phi) + + if iter % 10 == 0 or iter == 1: + print(f"iter: {iter}, cost: {cost}") + + if iter > 100: + print("Iteration limit reached!") + break + +print(f"Completed Optimization! (final cost: {cost})") + +############################################################################### +# Now we plot the results: + +samples_inv = np.linspace(s, 1, 50) +inv_x = [target_func(x) for x in samples_inv] + +samples_x = np.linspace(0, 1, 100) +qsvt_y_vals = [ + np.real(qml.matrix(sum_even_odd_circ, wire_order=["ancilla", 0])(x, phi, "ancilla", wires=[0])[0, 0]) + for x in samples_x +] + +plt.plot(samples_x, qsvt_y_vals, label="Re(qsvt)") +plt.plot(samples_inv, inv_x, label="target") + +plt.vlines(1 / kappa, -1.0, 1.0, linestyle="--", color="grey", label="1/kappa") +plt.vlines(0.0, -1.0, 1.0, color="black") +plt.hlines(0.0, -0.1, 1.0, color="black") + +plt.legend() +plt.show() + +############################################################################### +# Awesome, we successfully optimized the phase angles! While we used a simple loss function +# and optimizer, more sophisticated optimization schemes have been presented in literature to +# robustly train the phase angles for QSVT (see [#phaseeval]_). +# +# Let :math:`\hat{U}_{qsvt}(\vec{\phi}, x)` represent the unitary matrix of the QSVT algorithm. +# Both of the methods above produce phase angles :math:`\vec{\phi}` such that: +# +# .. math:: +# +# Re[\hat{U}_{qsvt}(\vec{\phi}, x)] \approx P(x). +# +# In general, the imaginary part of this transformation will not be zero. We need an operator +# which only applies the real component. Note that we can express the real part of a complex number +# :math:`z` as :math:`Re[z] = \frac{1}{2}(z + z^{*}).` Similarly, for operators this is given by: +# +# .. math:: +# +# \hat{U}_{real}(\vec{\phi}) = \frac{1}{2} \ ( \hat{U}_{qsvt}(\vec{\phi}) + \hat{U}^{*}_{qsvt}(\vec{\phi}) ). +# +# Here we use a two-term LCU to define the quantum function for this operator. We obtain the complex +# conjugate of :math:`\hat{U}_{qsvt}` by taking the adjoint of the operator block-encoding :math:`A^{T}:` + + +def real_u(A, phi): + qml.Hadamard(wires="ancilla1") + + qml.ctrl(sum_even_odd_circ, control=("ancilla1",), control_values=(0,))(A, phi, "ancilla2", [0, 1, 2]) + qml.ctrl(qml.adjoint(sum_even_odd_circ), control=("ancilla1",), control_values=(1,))(A.T, phi, "ancilla2", [0, 1, 2]) + + qml.Hadamard(wires="ancilla1") + +############################################################################### +# Let's take everything we have learned and apply it to solve a linear system of equations. +# +# Solving a Linear System with QSVT +# --------------------------------- +# Our goal is to solve the equation :math:`A \cdot \vec{x} = \vec{b}.` This method assumes +# the matrix we will invert is hermitian. Let's begin by defining the specific matrix :math:`A` +# and vector :math:`\vec{b}` : +# + +A = np.array( + [ + [0.65713691, -0.05349524, 0.08024556, -0.07242864], + [-0.05349524, 0.65713691, -0.07242864, 0.08024556], + [0.08024556, -0.07242864, 0.65713691, -0.05349524], + [-0.07242864, 0.08024556, -0.05349524, 0.65713691], + ] +) + +b = np.array([1, 2, 3, 4], dtype="complex") +target_x = np.linalg.inv(A) @ b # true solution + +# Normalize states: +norm_b = np.linalg.norm(b) +normalized_b = b / norm_b + +norm_x = np.linalg.norm(target_x) +normalized_x = target_x / norm_x + +############################################################################### +# To solve the linear system we construct a quantum circuit that first prepares the normalized +# vector :math:`\vec{b}` in the working qubit register. Next we call the :code:`real_u(A.T, phi)` +# function that we previously constructed. This is equivalent to applying :math:`s \cdot A^{-1}` to the prepared state. +# Finally, we return the state at the end of the circuit. +# +# The subset of qubits which prepared the :math:`\vec{b}` vector should be transformed to +# represent :math:`\vec{x}` (up to scaling factors): + + +@qml.qnode(qml.device("default.qubit", wires=["ancilla1", "ancilla2", 0, 1, 2])) +def linear_system_solver_circuit(phi): + qml.StatePrep(normalized_b, wires=[1, 2]) + real_u(A.T, phi) # invert the singular values of A transpose to get A^-1 + return qml.state() + + +transformed_state = linear_system_solver_circuit(phi)[:4] # first 4 entries of the state +rescaled_computed_x = transformed_state * norm_b / s +normalized_computed_x = rescaled_computed_x / np.linalg.norm(rescaled_computed_x) + +print("target x:", np.round(normalized_x, 3)) +print("computed x:", np.round(normalized_computed_x, 3)) + +############################################################################### +# We have successfully solved the linear system 🎉! Notice that the target state and +# computed state agree well with only some slight deviations. +# +# Conclusion +# ------------------------------- +# In this demo, we showcased the :func:`~.pennylane.qsvt()` functionality in PennyLane. +# We explained how to integrate phase angles computed with external packages and how to use +# PennyLane to optimize the phase angles directly. Finally, we described how to apply QSVT +# to solve an example linear system. +# +# While this demo covered simple example, the general problem of solving linear systems of equations is often the +# bottleneck in many applications from regression analysis in financial modelling to simulating +# fluid dynamics for jet engine design. We hope that PennyLane can help you along the way to +# your next big discovery in quantum algorithms. +# +# References +# ---------- +# +# .. [#qsvt] +# +# András Gilyén, Yuan Su, Guang Hao Low, Nathan Wiebe, +# "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", +# `Proceedings of the 51st Annual ACM SIGACT Symposium on the Theory of Computing `__, 2019 +# +# +# .. [#phaseeval] +# +# Dong Y, Meng X, Whaley K, Lin L, +# "Efficient phase-factor evaluation in quantum signal processing", +# `Phys. Rev. A 103, 042419 – `__, 2021 +# +# +# .. [#machineprecision] +# +# Chao R, Ding D, Gilyen A, Huang C, Szegedy M, +# "Finding Angles for Quantum Signal Processing with Machine Precision", +# `arXiv, 2003.02831 `__, 2020 +# +# +# .. [#productdecomp] +# +# Haah J, +# "Product decomposition of periodic functions in quantum signal processing", +# `Quantum 3, 190 `__, 2019 + +############################################################################## diff --git a/demonstrations_v2/tutorial_apply_qsvt/metadata.json b/demonstrations_v2/tutorial_apply_qsvt/metadata.json new file mode 100644 index 0000000000..716a9a3eda --- /dev/null +++ b/demonstrations_v2/tutorial_apply_qsvt/metadata.json @@ -0,0 +1,91 @@ +{ + "title": "QSVT in Practice", + "authors": [ + { + "username": "Jay" + }, + { + "username": "jsmalley" + } + ], + "dateOfPublication": "2023-08-22T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms", + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/apply_qsvt/thumbnail_tutorial_QSVT_for_Matrix_Inversion.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QSVT_for_Matrix_Inversion.png" + } + ], + "seoDescription": "Applying QSVT for matrix inversion", + "doi": "", + "references": [ + { + "type": "article", + "id": "qsvt", + "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", + "authors": "Andr\u00e1s Gily\u00e9n, Yuan Su, Guang Hao Low, and Nathan Wiebe", + "year": "2019", + "publisher": "", + "journal": "", + "doi": "10.1145/3313276.3316366", + "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" + }, + { + "type": "article", + "id": "phaseeval", + "title": "Efficient phase-factor evaluation in quantum signal processing", + "authors": "Dong Y, Meng X, Whaley K and Lin L", + "year": "2021", + "publisher": "", + "journal": "", + "doi": "10.1103/PhysRevA.103.042419", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.042419" + }, + { + "type": "article", + "id": "machineprecision", + "title": "Finding Angles for Quantum Signal Processing with Machine Precision", + "authors": "Chao R, Ding D, Gilyen A, Huang C, and Szegedy M", + "year": "2020", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2003.02831", + "url": "https://arxiv.org/abs/2003.02831" + }, + { + "type": "article", + "id": "productdecomp", + "title": "Product decomposition of periodic functions in quantum signal processing", + "authors": "Haah J", + "year": "2019", + "publisher": "", + "journal": "Quantum", + "doi": "10.48550/arXiv.2003.02831", + "url": "https://quantum-journal.org/papers/q-2019-10-07-190/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "function_fitting_qsp", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_apply_qsvt/requirements.in b/demonstrations_v2/tutorial_apply_qsvt/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_apply_qsvt/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_backprop/demo.py b/demonstrations_v2/tutorial_backprop/demo.py new file mode 100644 index 0000000000..39f9511e69 --- /dev/null +++ b/demonstrations_v2/tutorial_backprop/demo.py @@ -0,0 +1,453 @@ +r""" +Quantum gradients with backpropagation +====================================== + +.. meta:: + :property="og:description": Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sphx_glr_tutorial_backprop_002.png + +.. related:: + + tutorial_quantum_natural_gradient Quantum natural gradient + +*Author: Josh Izaac — Posted: 11 August 2020. Last updated: 31 January 2021.* + +In PennyLane, any quantum device, whether a hardware device or a simulator, can be +trained using the :doc:`parameter-shift rule ` to compute quantum +gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does +not require any knowledge about the internal workings of the device; it is sufficient to treat +the device as a 'black box', and to query it with different input values in order to determine the gradient. + +When working with simulators, however, we *do* have access to the internal (classical) +computations being performed. This allows us to take advantage of other methods of computing the +gradient, such as backpropagation, which may be advantageous in certain regimes. In this tutorial, +we will compare and contrast the parameter-shift rule against backpropagation, using +the PennyLane :class:`default.qubit ` +device. + +The parameter-shift rule +------------------------ + +The parameter-shift rule states that, given a variational quantum circuit :math:`U(\boldsymbol +\theta)` composed of parametrized Pauli rotations, and some measured observable :math:`\hat{B},` the +derivative of the expectation value + +.. math:: + + \langle \hat{B} \rangle (\boldsymbol\theta) = + \langle 0 \vert U(\boldsymbol\theta)^\dagger \hat{B} U(\boldsymbol\theta) \vert 0\rangle + +with respect to the input circuit parameters :math:`\boldsymbol{\theta}` is given by + +.. math:: + + \nabla_{\theta_i}\langle \hat{B} \rangle(\boldsymbol\theta) + = \frac{1}{2} + \left[ + \langle \hat{B} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + - \langle \hat{B} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right) + \right]. + +Thus, the gradient of the expectation value can be calculated by evaluating the same variational +quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!). + +Let's have a go implementing the parameter-shift rule manually in PennyLane. +""" +import pennylane as qml +from jax import numpy as jnp +from matplotlib import pyplot as plt +import jax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +# set the random seed +key = jax.random.PRNGKey(42) + + +# create a device to execute the circuit on +dev = qml.device("default.qubit", wires=3) + + +def CNOT_ring(wires): + """Apply CNOTs in a ring pattern""" + n_wires = len(wires) + + for w in wires: + qml.CNOT([w % n_wires, (w + 1) % n_wires]) + + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.RZ(params[2], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + + qml.RX(params[3], wires=0) + qml.RY(params[4], wires=1) + qml.RZ(params[5], wires=2) + + CNOT_ring(wires=[0, 1, 2]) + return qml.expval(qml.PauliY(0) @ qml.PauliZ(2)) + + +############################################################################## +# Let's test the variational circuit evaluation with some parameter input: + +# initial parameters +params = jax.random.normal(key, [6]) + + +print("Parameters:", params) +print("Expectation value:", circuit(params)) + +############################################################################## +# We can also draw the executed quantum circuit: + +fig, ax = qml.draw_mpl(circuit, decimals=2)(params) +plt.show() + + +############################################################################## +# Now that we have defined our variational circuit QNode, we can construct +# a function that computes the gradient of the :math:`i\text{th}` parameter +# using the parameter-shift rule. + +def parameter_shift_term(qnode, params, i): + shifted = params.copy() + shifted = shifted.at[i].add(jnp.pi/2) + forward = qnode(shifted) # forward evaluation + + shifted = shifted.at[i].add(-jnp.pi) + backward = qnode(shifted) # backward evaluation + + return 0.5 * (forward - backward) + +# gradient with respect to the first parameter +print(parameter_shift_term(circuit, params, 0)) + +############################################################################## +# In order to compute the gradient with respect to *all* parameters, we need +# to loop over the index ``i``: + +def parameter_shift(qnode, params): + gradients = jnp.zeros([len(params)]) + + for i in range(len(params)): + gradients = gradients.at[i].set(parameter_shift_term(qnode, params, i)) + + return gradients + +print(parameter_shift(circuit, params)) + +############################################################################## +# We can compare this to PennyLane's *built-in* quantum gradient support by using +# the ``jax.grad`` function. Remember, when we defined the +# QNode, we specified that we wanted it to be differentiable using the parameter-shift +# method (``diff_method="parameter-shift"``). + +grad_function = jax.grad(circuit) +print(grad_function(params)[0]) + +############################################################################## +# Alternatively, we can directly compute quantum gradients of QNodes using +# PennyLane's built in :mod:`qml.gradients ` module: + +print(jnp.stack(qml.gradients.param_shift(circuit)(params))) + +############################################################################## +# If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit +# ``2*len(params)`` number of times in order to compute the quantum gradient with respect to all +# parameters. While reasonably fast for a small number of parameters, as the number of parameters in +# our quantum circuit grows, so does both +# +# 1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and +# +# 2. the number of parameter-shift evaluations required. +# +# Both of these factors increase the time taken to compute the gradient with +# respect to all parameters. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# Let's consider an example with a significantly larger number of parameters. +# We'll make use of the :class:`~pennylane.StronglyEntanglingLayers` template +# to make a more complicated QNode. + +dev = qml.device("default.qubit", wires=4) + +@qml.qnode(dev, diff_method="parameter-shift") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(params.size) +print(circuit(params)) + +############################################################################## +# This circuit has 180 parameters. Let's see how long it takes to perform a forward +# pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num + +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# We can now estimate the time taken to compute the full gradient vector, +# and see how this compares. + +# create the gradient function +grad_fn = jax.grad(circuit) + +times = timeit.repeat("grad_fn(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num + +print(f"Gradient computation (best of {reps}): {backward_time} sec per loop") + + +############################################################################## +# Based on the parameter-shift rule, we expect that the amount of time to compute the quantum +# gradients should be approximately :math:`2p\Delta t_{f}` where :math:`p` is the number of +# parameters and :math:`\Delta t_{f}` if the time taken for the forward pass. Let's verify this: + +print(2 * forward_time * params.size) + + +############################################################################## +# Backpropagation +# --------------- +# +# An alternative to the parameter-shift rule for computing gradients is +# `reverse-mode autodifferentiation `__. +# Unlike the parameter-shift method, which requires :math:`2p` circuit evaluations for +# :math:`p` parameters, reverse-mode requires only a *single* forward pass of the +# differentiable function to compute +# the gradient of all variables, at the expense of increased memory usage. +# During the forward pass, the results of all intermediate subexpressions are stored; +# the computation is then traversed *in reverse*, with the gradient computed by repeatedly +# applying the chain rule. +# In most classical machine learning settings (where we are training scalar loss functions +# consisting of a large number of parameters), +# reverse-mode autodifferentiation is the +# preferred method of autodifferentiation—the reduction in computational time enables larger and +# more complex models to be successfully trained. The backpropagation algorithm is a particular +# special-case of reverse-mode autodifferentiation, which has helped lead to the machine learning +# explosion we see today. +# +# In quantum machine learning, however, the inability to store and utilize the results of +# *intermediate* quantum operations on hardware remains a barrier to using backprop; +# while reverse-mode +# autodifferentiation works fine for small quantum simulations, only the +# parameter-shift rule can be used to compute gradients on quantum hardware directly. Nevertheless, +# when training quantum models via classical simulation, it's useful to explore the regimes where +# reverse-mode differentiation may be a better choice than the parameter-shift rule. +# +# Benchmarking +# ~~~~~~~~~~~~ +# +# When creating a QNode, :doc:`PennyLane supports various methods of differentiation +# `, including ``"parameter-shift"`` (which we used previously), +# ``"finite-diff"``, ``"reversible"``, and ``"backprop"``. While ``"parameter-shift"`` works with all devices +# (simulator or hardware), ``"backprop"`` will only work for specific simulator devices that are +# designed to support backpropagation. +# +# One such device is :class:`default.qubit `. It +# has backends written using TensorFlow, JAX, and Autograd, so when used with the +# TensorFlow, JAX, and Autograd interfaces respectively, supports backpropagation. +# In this demo, we will use the JAX interface. + +dev = qml.device("default.qubit", wires=4) + +############################################################################## +# When defining the QNode, we specify ``diff_method="backprop"`` to ensure that +# we are using backpropagation mode. Note that this is the *default differentiation +# mode* for the ``default.qubit`` device. + + +@qml.qnode(dev, diff_method="backprop") +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +# initialize circuit parameters +param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=15) +params = jax.random.normal(key, param_shape) * 0.1 + +print(circuit(params)) + +############################################################################## +# Let's see how long it takes to perform a forward pass of the circuit. + +import timeit + +reps = 3 +num = 10 +times = timeit.repeat("circuit(params)", globals=globals(), number=num, repeat=reps) +forward_time = min(times) / num +print(f"Forward pass (best of {reps}): {forward_time} sec per loop") + + +############################################################################## +# Comparing this to the forward pass from ``default.qubit``, we note that there is some potential +# overhead from using backpropagation. We can now estimate the time required to perform a +# gradient computation via backpropagation: + +times = timeit.repeat("jax.grad(circuit)(params)", globals=globals(), number=num, repeat=reps) +backward_time = min(times) / num +print(f"Backward pass (best of {reps}): {backward_time} sec per loop") + +############################################################################## +# Unlike with the parameter-shift rule, the time taken to perform the backwards pass appears +# of the order of a single forward pass! This can significantly speed up training of simulated +# circuits with many parameters. +# +# Time comparison +# --------------- +# +# Let's compare the two differentiation approaches as the number of trainable parameters +# in the variational circuit increases, by timing both the forward pass and the gradient +# computation as the number of layers is allowed to increase. + +dev = qml.device("default.qubit", wires=4) + +def circuit(params): + qml.StronglyEntanglingLayers(params, wires=[0, 1, 2, 3]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3)) + +############################################################################## +# We'll continue to use the same ansatz as before, but to reduce the time taken +# to collect the data, we'll reduce the number and repetitions of timings per data +# point. Below, we loop over a variational circuit depth ranging from 0 (no gates/ +# trainable parameters) to 20. Each layer will contain :math:`3N` parameters, where +# :math:`N` is the number of wires (in this case, we have :math:`N=4`). + +reps = 2 +num = 3 + +forward_shift = [] +gradient_shift = [] +forward_backprop = [] +gradient_backprop = [] + +qnode_shift = jax.jit(qml.QNode(circuit, dev, diff_method="parameter-shift")) +qnode_backprop = jax.jit(qml.QNode(circuit, dev, diff_method="backprop")) + +grad_qnode_shift = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="parameter-shift"))) +grad_qnode_backprop = jax.jit(jax.grad(qml.QNode(circuit, dev, diff_method="backprop"))) + +for depth in range(0, 21): + param_shape = qml.StronglyEntanglingLayers.shape(n_wires=4, n_layers=depth) + params = jax.random.normal(key, param_shape) * 0.1 + + num_params = params.size + + # forward pass timing + # =================== + + + # parameter-shift + t = timeit.repeat("qnode_shift(params)", globals=globals(), number=num, repeat=reps) + forward_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + forward_backprop.append([num_params, min(t) / num]) + + if num_params == 0: + continue + + # Gradient timing + # =============== + + # parameter-shift + t = timeit.repeat("grad_qnode_shift(params)", globals=globals(), number=num, repeat=reps) + gradient_shift.append([num_params, min(t) / num]) + + # backprop + t = timeit.repeat("grad_qnode_backprop(params)", globals=globals(), number=num, repeat=reps) + gradient_backprop.append([num_params, min(t) / num]) + +gradient_shift = jnp.array(gradient_shift).T +gradient_backprop = jnp.array(gradient_backprop).T +forward_shift = jnp.array(forward_shift).T +forward_backprop = jnp.array(forward_backprop).T + +############################################################################## +# We now import matplotlib, and plot the results. + +plt.style.use("bmh") + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") +ax.set_ylabel("Time (s)") +ax.set_xlabel("Number of parameters") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can see that the computational time for the parameter-shift rule increases with +# increasing number of parameters, as expected, whereas the computational time +# for backpropagation appears much more constant, with perhaps a minute linear increase +# with :math:`p.` Note that the plots are not perfectly linear, with some 'bumpiness' or +# noisiness. This is likely due to low-level operating system jitter, and +# other environmental fluctuations—increasing the number of repeats can help smooth +# out the plot. +# +# For a better comparison, we can scale the time required for computing the quantum +# gradients against the time taken for the corresponding forward pass: + +gradient_shift = gradient_shift.at[1].divide(forward_shift[1, 1:]) +gradient_backprop = gradient_backprop.at[1].divide(forward_backprop[1, 1:]) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) + +ax.plot(*gradient_shift, '.-', label="Parameter-shift") +ax.plot(*gradient_backprop, '.-', label="Backprop") + +# perform a least squares regression to determine the linear best fit/gradient +# for the normalized time vs. number of parameters +x = gradient_shift[0] +m_shift, c_shift = jnp.polyfit(*gradient_shift, deg=1) +m_back, c_back = jnp.polyfit(*gradient_backprop, deg=1) + +ax.plot(x, m_shift * x + c_shift, '--', label=f"{m_shift:.2f}p{c_shift:+.2f}") +ax.plot(x, m_back * x + c_back, '--', label=f"{m_back:.2f}p{c_back:+.2f}") + +ax.set_ylabel("Normalized time") +ax.set_xlabel("Number of parameters") +ax.set_xscale("log") +ax.set_yscale("log") +ax.legend() + +plt.show() + +############################################################################## +# .. raw:: html +# +#
+# +# We can now see clearly that there is constant overhead for backpropagation with +# ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` +# +# diff --git a/demonstrations_v2/tutorial_backprop/metadata.json b/demonstrations_v2/tutorial_backprop/metadata.json new file mode 100644 index 0000000000..46f85775ee --- /dev/null +++ b/demonstrations_v2/tutorial_backprop/metadata.json @@ -0,0 +1,32 @@ +{ + "title": "Quantum gradients with backpropagation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2020-08-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_gradients_backpropagation.png" + } + ], + "seoDescription": "Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule\u2014if you are using a simulator.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_backprop/requirements.in b/demonstrations_v2/tutorial_backprop/requirements.in new file mode 100644 index 0000000000..a7e20f9b86 --- /dev/null +++ b/demonstrations_v2/tutorial_backprop/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py new file mode 100644 index 0000000000..0e2929dc41 --- /dev/null +++ b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/barren_gadgets.py @@ -0,0 +1,130 @@ +import pennylane as qml +from pennylane import numpy as np + +def non_identity_obs(obs): + return [o for o in obs if not isinstance(o, qml.Identity)] + +class PerturbativeGadgets: + """ Class to generate the gadget Hamiltonian corresponding to a given + computational hamiltonian according to the gadget construction derived + by Faehrmann & Cichy + + Args: + perturbation_factor (float) : parameter controlling the magnitude of the + perturbation (aa pre-factor to \lambda_max) + """ + def __init__(self, perturbation_factor=1): + self.perturbation_factor = perturbation_factor + + def gadgetize(self, Hamiltonian, target_locality=3): + """Generation of the perturbative gadget equivalent of the given + Hamiltonian according to the proceedure in Cichy, Fährmann et al. + Args: + Hamiltonian (qml.Hamiltonian) : target Hamiltonian to decompose + into more local terms + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + Hgad (qml.Hamiltonian) : gadget Hamiltonian + """ + # checking for unaccounted for situations + self.run_checks(Hamiltonian, target_locality) + computational_qubits, computational_locality, computational_terms = self.get_params(Hamiltonian) + Hamiltonian_coeffs, Hamiltonian_ops = Hamiltonian.terms() + + # total qubit count, updated progressively when adding ancillaries + total_qubits = computational_qubits + #TODO: check proper convergence guarantee + gap = 1 + perturbation_norm = np.sum(np.abs(Hamiltonian_coeffs)) \ + + computational_terms * (computational_locality - 1) + lambda_max = gap / (4 * perturbation_norm) + l = self.perturbation_factor * lambda_max + sign_correction = (-1)**(computational_locality % 2 + 1) + # creating the gadget Hamiltonian + coeffs_anc = [] + coeffs_pert = [] + obs_anc = [] + obs_pert = [] + ancillary_register_size = int(computational_locality / (target_locality - 2)) + for str_count, string in enumerate(Hamiltonian_ops): + previous_total = total_qubits + total_qubits += ancillary_register_size + # Generating the ancillary part + for anc_q in range(previous_total, total_qubits): + coeffs_anc += [0.5, -0.5] + obs_anc += [qml.Identity(anc_q), qml.PauliZ(anc_q)] + # Generating the perturbative part + for anc_q in range(ancillary_register_size): + term = qml.PauliX(previous_total+anc_q) @ qml.PauliX(previous_total+(anc_q+1)%ancillary_register_size) + term = qml.prod(term, *non_identity_obs(string.operands)[ + (target_locality-2)*anc_q:(target_locality-2)*(anc_q+1)]) + obs_pert.append(term) + coeffs_pert += [l * sign_correction * Hamiltonian_coeffs[str_count]] \ + + [l] * (ancillary_register_size - 1) + coeffs = coeffs_anc + coeffs_pert + obs = obs_anc + obs_pert + Hgad = qml.Hamiltonian(coeffs, obs) + return Hgad + + def get_params(self, Hamiltonian): + """ retrieving the parameters n, k and r from the given Hamiltonian + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian from which to get the + relevant parameters + Returns: + computational_qubits (int) : total number of qubits acted upon by + the Hamiltonian + computational_locality (int) : maximum number of qubits acted upon + by a single term of the Hamiltonian + computational_terms (int) : number of terms in the sum + composing the Hamiltonian + """ + _, Hamiltonian_ops = Hamiltonian.terms() + # checking how many qubits the Hamiltonian acts on + computational_qubits = len(Hamiltonian.wires) + # getting the number of terms in the Hamiltonian + computational_terms = len(Hamiltonian_ops) + # getting the locality, assuming all terms have the same + computational_locality = max([len(non_identity_obs(Hamiltonian_ops[s])) + for s in range(computational_terms)]) + return computational_qubits, computational_locality, computational_terms + + def run_checks(self, Hamiltonian, target_locality): + """ method to check a few conditions for the correct application of + the methods + Args: + Hamiltonian (qml.Hamiltonian) : Hamiltonian of interest + target_locality (int > 2) : desired locality of the resulting + gadget Hamiltonian + Returns: + None + """ + _, Hamiltonian_ops = Hamiltonian.terms() + computational_qubits, computational_locality, _ = self.get_params(Hamiltonian) + computational_qubits = len(Hamiltonian.wires) + if computational_qubits != Hamiltonian.wires[-1] + 1: + raise Exception('The studied computational Hamiltonian is not acting on ' + + 'the first {} qubits. '.format(computational_qubits) + + 'Decomposition not implemented for this case') + # Check for same string lengths + localities=[] + for string in Hamiltonian_ops: + localities.append(len(non_identity_obs(string))) + if len(np.unique(localities)) > 1: + raise Exception('The given Hamiltonian has terms with different locality.' + + ' Gadgetization not implemented for this case') + # validity of the target locality given the computational locality + if target_locality < 3: + raise Exception('The target locality can not be smaller than 3') + ancillary_register_size = computational_locality / (target_locality - 2) + if int(ancillary_register_size) != ancillary_register_size: + raise Exception('The locality of the Hamiltonian and the target' + + ' locality are not compatible. The gadgetization' + + ' with "unfull" ancillary registers is not' + + ' supported yet. Please choose such that the' + + ' computational locality is divisible by the' + + ' target locality - 2') + + + diff --git a/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py new file mode 100644 index 0000000000..7a26d9e059 --- /dev/null +++ b/demonstrations_v2/tutorial_barren_gadgets/barren_gadgets/layered_ansatz.py @@ -0,0 +1,54 @@ +import pennylane as qml +from pennylane import numpy as np + +""" Based on the SimplifiedTwoDesign template from pennylane +https://docs.pennylane.ai/en/latest/code/api/pennylane.SimplifiedTwoDesign.html +as proposed in `Cerezo et al. (2021) `_. +but changin the Y-rotations for a random choice of {X, Y, Z}-rotations. +""" + +def build_ansatz(initial_layer_weights, weights, wires, gate_sequence=None): + + n_layers = qml.math.shape(weights)[0] + op_list = [] + + # initial rotations + for i in range(len(wires)): + op_list.append(qml.RY(initial_layer_weights[i], wires=wires[i])) + + # generating the rotation sequence + if gate_sequence is None: + gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + + # repeated layers + for layer in range(n_layers): + + # even layer of entanglers + even_wires = [wires[i : i + 2] for i in range(0, len(wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, i, 0].item()(weights[layer, i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, i, 1].item()(weights[layer, i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, i, 1], wires=wire_pair[1])) + + # odd layer of entanglers + odd_wires = [wires[i : i + 2] for i in range(1, len(wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + op_list.append(qml.CZ(wires=wire_pair)) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 0].item()(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + op_list.append(gate_sequence[layer, len(wires) // 2 + i, 1].item()(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 0], wires=wire_pair[0])) + # op_list.append(qml.RX(weights[layer, len(wires) // 2 + i, 1], wires=wire_pair[1])) + + return op_list + +def generate_random_gate_sequence(shape): + gate_set = [qml.RX, qml.RY, qml.RZ] + return np.random.choice(gate_set, size=shape) + +def get_parameter_shape(n_layers, n_wires): + if n_wires == 1: + return [(n_wires,), (n_layers,)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] + diff --git a/demonstrations_v2/tutorial_barren_gadgets/demo.py b/demonstrations_v2/tutorial_barren_gadgets/demo.py new file mode 100644 index 0000000000..600186f0ea --- /dev/null +++ b/demonstrations_v2/tutorial_barren_gadgets/demo.py @@ -0,0 +1,387 @@ +r""" +Perturbative Gadgets for Variational Quantum Algorithms +========================================== + +.. meta:: + :property="og:description": Use perturbative gadgets to avoid cost-function-dependent barren plateaus + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_barren_gadgets.png + + +.. related:: + tutorial_barren_plateaus Barren plateaus in quantum neural networks¶ + tutorial_local_cost_functions Alleviating barren plateaus with local cost functions + +*Author: Simon Cichy — Posted: 09 December 2022. Last updated: 09 December 2022.* + +Variational quantum algorithms are seen as one of the most primising candidates +for useful applications of quantum computers in the near term, but there are +still a few hurdles to overcome when it comes to practical implementation. +One of them, is the trainability. +In other words, one needs to ensure that the cost function is not flat. +In this tutorial, we will explore the application of perturbative gadgets in +variational quantum algorithms to outgo the issue of cost-function-dependent +barren plateaus, as proposed in Ref. [#cichy2022]_ + +Some context +------------ + +Barren plateaus refer to the phenomenon where the gradients of the cost function +decay exponentially with the size of the problem. Essentially, the cost +landscape becomes flat, with exception of some small regions, e.g., around +the minimum. +That is a problem because increasing the precision of the cost +function requires more measurements from the quantum device due to shot noise, +and an exponential number of measurements would render the algorithm impractical. +If you are not familiar yet with the concept of barren plateaus, I recommend you +first check out the demonstrations on :doc:`barren plateaus ` +and :doc:`avoiding barren plateaus with local cost functions `. + +As presented in the second aforementioned demo, barren plateaus are more severe when using global +cost functions compared to local ones. +A global cost function requires the simultaneous measurement of all +qubits at once. In contrast, a local one is constructed from terms that only +act on a small subset of qubits. + +We want to explore this topic further and learn about one possible mitigation +strategy. +Thinking about Variational Quantum Eigensolver (VQE) applications, let us consider cost functions that are +expectation values of Hamiltonians such as + +.. math:: C(\theta) = \operatorname{Tr} \left[ H V(\theta) |00\ldots 0\rangle \! \langle 00\ldots 0| V(\theta)^\dagger\right]. + +Here :math:`|00\ldots 0\rangle` is our initial state, +:math:`V(\theta)` is the circuit ansatz and :math:`H` the Hamiltonian +whose expectation value we need to minimize. +In some cases, it is easy to find a local cost function which can substitute a global one with the same ground state. +Take, for instance, the following Hamiltonians that induce global and local cost functions, respectively. + + +.. math:: H_G = \mathbb{I} - |00\ldots 0\rangle \! \langle 00\ldots 0| \quad \textrm{ and } \quad H_L = \mathbb{I} - \frac{1}{n} \sum_j |0\rangle \! \langle 0|_j. + +Those are two different Hamiltonians (not just different formulations of the +same one), but they share the same ground state: + + +.. math:: |\psi_{\textrm{min}} \rangle = |00\ldots 0\rangle. + +Therefore, one can work with either Hamiltonian to perform the VQE routine. +However, it is not always so simple. +What if we want to find the minimum eigenenergy of +:math:`H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X` ? +It is not always trivial to construct a local cost +function that has the same minimum as the cost function of interest. +This is where perturbative gadgets come into play! + + +The definitions +--------------- +Perturbative gadgets are a common tool in adiabatic quantum computing. +Their goal is to find a Hamiltonian with local interactions that mimics +another Hamiltonian with more complex couplings. + +Ideally, they would want to implement the target Hamiltonian with complex couplings, but since it's hard to implement more than few-body interactions on hardware, they cannot do so. Perturbative gadgets work by increasing the dimension of the Hilbert space (i.e., the number +of qubits) and "encoding" the target Hamiltonian in the low-energy +subspace of a so-called "gadget" Hamiltonian. + +Let us now construct such a gadget Hamiltonian tailored for VQE applications. +First, we start from a target Hamiltonian that is a linear combination of +Pauli words acting on :math:`k` qubits each: + +.. math:: H^\text{target} = \sum_i c_i h_i, + +where :math:`h_i = \sigma_{i,1} \otimes \sigma_{i,2} \otimes \ldots \otimes \sigma_{i,k},` +:math:`\sigma_{i,j} \in \{ X, Y, Z \},` and :math:`c_i \in \mathbb{R}.` +Now we construct the gadget Hamiltonian. +For each term :math:`h_i,` we will need :math:`k` additional qubits, which we +call auxiliary qubits, and to add two terms to the Hamiltonian: +an "unperturbed" part :math:`H^\text{aux}_i` and a perturbation :math:`V_i` +of strength :math:`\lambda.` +The unperturbed part penalizes each of the newly added qubits for not being in +the :math:`|0\rangle` state + +.. math:: H^\text{aux}_i = \sum_{j=1}^k |1\rangle \! \langle 1|_{i,j} = \sum_{j=1}^k \frac{1}{2}(\mathbb{I} - Z_{i,j}). + +On the other hand, the perturbation part implements one of the operators in the Pauli word +:math:`\sigma_{i,j}` on the corresponding qubit of the target register and a +pair of Pauli :math:`X` gates on two of the auxiliary qubits: + +.. math:: V_i = \sum_{j=1}^k c_{i,j} \sigma_{i,j} \otimes X_{i,j} \otimes X_{i,(j+1) \mathrm{mod }k}. + +In the end, + +.. math:: H^\text{gad} = \sum_{i} \left( H^\text{aux}_i + \lambda V_i \right). + + + +To grasp this idea better, this is what would result from working with a Hamiltonian +acting on a total of :math:`8` qubits and having :math:`3` terms, each of them being a +:math:`4`-body interaction. + +.. figure:: ../_static/demonstration_assets/barren_gadgets/gadget-terms-tutorial.png + :align: center + :width: 90% + +For each of the terms :math:`h_1`, :math:`h_2,` and :math:`h_3` we add :math:`4` auxiliary qubits. +In the end, our gadget Hamiltonian acts on :math:`8+3\cdot 4 = 20` qubits. + +The penalization (red) acts only on the auxiliary registers, penalizing each +qubit individually, while the perturbations couple the target with the auxiliary qubits. + +As shown in Ref. [#cichy2022]_, this construction results in a spectrum that, for low energies, is similar +to that of the original Hamiltonian. +This means that by minimizing the gadget Hamiltonian and reaching its global +minimum, the resulting state will be close to the global minimum of +:math:`H^\text{target}.` + +Since it is a local cost function, it is better behaved with respect to +barren plateaus than the global cost function, making it more trainable. +As a result, one can mitigate the onset of cost-function-dependent barren +plateaus by substituting the global cost function with the resulting gadget +and using that for training instead. That is what we will do in the rest of this tutorial. +""" + +############################################################################## +# First, a few imports. PennyLane and NumPy of course, and a few +# functions specific to our tutorial. +# The ``PerturbativeGadget`` class allows the user to generate the gadget Hamiltonian +# from a user-given target Hamiltonian in an automated way. +# For those who want to check its inner workings, +# you can find the code here: +# :download:`barren_gadgets.py `. +# The functions ``get_parameter_shape``, ``generate_random_gate_sequence``, and +# ``build_ansatz`` (for the details: +# :download:`layered_ansatz.py <../_static/demonstration_assets/barren_gadgets/layered_ansatz.py>` +# ) are there to build the parameterized quantum circuit we use in this demo. +# The first computes the shape of the array of trainable parameters that the +# circuit will need. The second generates a random sequence of Pauli rotations +# from :math:`\{R_X, R_Y, R_Z\}` with the right dimension. +# Finally, ``build_ansatz`` puts the pieces together. + +import pennylane as qml +from pennylane import numpy as np +from barren_gadgets.barren_gadgets import PerturbativeGadgets +from barren_gadgets.layered_ansatz import ( + generate_random_gate_sequence, + get_parameter_shape, + build_ansatz, +) + +np.random.seed(3) + +############################################################################## +# Now, let's take the example given above: +# +# .. math:: H = X \otimes X \otimes Y \otimes Z + Z \otimes Y \otimes X \otimes X. +# +# First, we construct our target Hamiltonian in PennyLane. +# For this, we use the +# :class:`~pennylane.Hamiltonian` class. + + +H_target = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) \ + + qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) + +############################################################################## +# Now we can check that we constructed what we wanted. + +print(H_target) + +############################################################################## +# We indeed have a Hamiltonian composed of two terms with the expected Pauli +# words. +# Next, we can construct the corresponding gadget Hamiltonian. +# Using the class ``PerturbativeGadgets``, we can automatically +# generate the gadget Hamiltonian from the target Hamiltonian. +# The object ``gadgetizer`` will contain all the information about the settings of +# the gadgetization procedure (there are quite a few knobs one can tweak, +# but we'll skip that for now). +# Then, the method ``gadgetize`` takes a +# :class:`~pennylane.Hamiltonian` +# object and generates the +# corresponding gadget Hamiltonian. + +gadgetizer = PerturbativeGadgets() +H_gadget = gadgetizer.gadgetize(H_target) +H_gadget + +############################################################################## +# So, let's see what we got. +# We started with 4 target qubits (labelled ``0`` to ``3``) and two 4-body terms. +# Thus we get 4 additional qubits twice (``4`` to ``11``). +# The first 16 elements of our Hamiltonian correspond to the unperturbed part. +# The last 8 are the perturbation. They are a little scrambled, but one can +# recognize the 8 Paulis from the target Hamiltonian on the qubits ``0`` to +# ``3`` and the cyclic pairwise :math:`X` structure on the auxiliaries. +# Indeed, they are :math:`(X_4X_5, X_5X_6, X_6X_7, X_7X_4)` and +# :math:`(X_8X_9, X_9X_{10}, X_{10}X_{11}, X_{11}X_8).` + +############################################################################## +# Training with the gadget Hamiltonian +# ----------------------------------- +# Now that we have a little intuition on how the gadget Hamiltonian construction +# works, we will use it to train. +# Classical simulations of qubit systems are expensive, so we will simplify further +# to a target Hamiltonian with a single term, and show that using the +# gadget Hamiltonian for training allows us to minimize the target Hamiltonian. +# So, let us construct the two Hamiltonians of interest. + + +H_target = 1 * qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliY(2) @ qml.PauliZ(3) +gadgetizer = PerturbativeGadgets(perturbation_factor=10) +H_gadget = gadgetizer.gadgetize(H_target) + +############################################################################## +# Then we need to set up our variational quantum algorithm. +# That is, we choose a circuit ansatz with randomly initialized weights, +# the cost function, the optimizer with its step size, the number of +# optimization steps, and the device to run the circuit on. +# For an ansatz, we will use a variation of the +# `qml.SimplifiedTwoDesign `_, +# which was proposed in previous +# works on cost-function-dependent barren plateaus [#cerezo2021]_. +# I will skip the details of the construction, since it is not our focus here, +# and just show what it looks like. +# Here is the circuit for a small example + +shapes = get_parameter_shape(n_layers=3, n_wires=5) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) + + +@qml.qnode(qml.device("default.qubit", wires=range(5))) +def display_circuit(weights): + build_ansatz(initial_layer_weights=init_weights, weights=weights, wires=range(5)) + return qml.expval(qml.PauliZ(wires=0)) + +import matplotlib.pyplot as plt +qml.draw_mpl(display_circuit)(weights) +plt.show() + +############################################################################## +# Now we build the circuit for our actual experiment. + + +# Total number of qubits: target + auxiliary +num_qubits = 4 + 1 * 4 + +# Other parameters of the ansatz: weights and gate sequence +shapes = get_parameter_shape(n_layers=num_qubits, n_wires=num_qubits) +init_weights = [np.pi / 4] * shapes[0][0] +weights = np.random.uniform(0, np.pi, size=shapes[1]) +random_gate_sequence = generate_random_gate_sequence(qml.math.shape(weights)) + +############################################################################## +# For the classical optimization, we will use the standard gradient descent +# algorithm and perform 500 iterations. For the quantum part, we will simulate +# our circuit using the +# `default.qubit `_ +# simulator. + +opt = qml.GradientDescentOptimizer(stepsize=0.1) +max_iter = 500 +dev = qml.device("default.qubit", wires=range(num_qubits)) + +############################################################################## +# Finally, we will use two cost functions and create a +# `QNode `_ for each. +# The first cost function, the training cost, is the loss function of the optimization. +# For the training, we use the gadget Hamiltonian. To ensure +# that our gadget optimization is proceeding as intended, +# we also define another cost function based on the target Hamiltonian. +# We will evaluate its value at each iteration for monitoring purposes, but it +# will not be used in the optimization. + + + +@qml.qnode(dev) +def training_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_gadget) + + +@qml.qnode(dev) +def monitoring_cost(weights): + build_ansatz( + initial_layer_weights=init_weights, + weights=weights, + wires=range(num_qubits), + gate_sequence=random_gate_sequence, + ) + return qml.expval(H_target) + + +############################################################################## +# The idea is that if we reach the global minimum for the gadget Hamiltonian, we +# should also be close to the global minimum of the target Hamiltonian, which is +# what we are ultimately interested in. +# To see the results and plot them, we will save the cost values +# at each iteration. + +costs_lists = {} +costs_lists["training"] = [training_cost(weights)] +costs_lists["monitoring"] = [monitoring_cost(weights)] + +############################################################################## +# Now everything is set up, let's run the optimization and see how it goes. +# Be careful, this will take a while. + +for it in range(max_iter): + weights = opt.step(training_cost, weights) + costs_lists["training"].append(training_cost(weights)) + costs_lists["monitoring"].append(monitoring_cost(weights)) + + +plt.style.use("seaborn") + +plt.figure() +plt.plot(costs_lists["training"]) +plt.plot(costs_lists["monitoring"]) +plt.legend(["training", "monitoring"]) +plt.xlabel("Number of iterations") +plt.ylabel("Cost values") +plt.show() + +############################################################################## +# +# Since our example target Hamiltonian is a single Pauli string, we know +# without needing any training that it has only :math:`\pm 1` eigenvalues. +# It is a very simple example, but we see that the training of our circuit using +# the gadget Hamiltonian as a cost function did indeed allow us to reach the +# global minimum of the target cost function. +# +# Now that you have an idea of how you can use perturbative gadgets in +# variational quantum algorithms, you can try applying them to more complex +# problems! However, be aware of the exponential scaling of classical +# simulations of quantum systems; adding linearly many auxiliary qubits +# quickly becomes hard to simulate. +# For those interested in the theory behind it or more formal statements of +# "how close" the results using the gadget are from the targeted ones, +# check out the original paper [#cichy2022]_. +# There you will also find further discussions on the advantages and limits of +# this proposal, +# as well as a more general recipe to design other gadget +# constructions with similar properties. +# Also, the complete code with explanations on how to reproduce the +# figures from the paper can be found in +# `this repository `_. +# +# References +# ---------- +# +# .. [#cichy2022] +# +# Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J. +# "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms." `arXiv:2210.03099 +# `__, 2022. +# +# .. [#cerezo2021] +# +# Cerezo, M., Sone, A., Volkoff, T. et al. +# "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 +# `__, 2021. +# diff --git a/demonstrations_v2/tutorial_barren_gadgets/metadata.json b/demonstrations_v2/tutorial_barren_gadgets/metadata.json new file mode 100644 index 0000000000..2d5cb152b6 --- /dev/null +++ b/demonstrations_v2/tutorial_barren_gadgets/metadata.json @@ -0,0 +1,63 @@ +{ + "title": "Perturbative Gadgets for Variational Quantum Algorithms", + "authors": [ + { + "username": "scichy" + } + ], + "dateOfPublication": "2022-12-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/barren_gadgets/thumbnail_tutorial_barren_gadgets.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_perturbative_gadget.png" + } + ], + "seoDescription": "Use perturbative gadgets to avoid cost-function-dependent barren plateaus.", + "doi": "", + "references": [ + { + "id": "cichy2022", + "type": "article", + "title": "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms.", + "authors": "Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J.", + "year": "2022", + "journal": "", + "doi": "10.48550/arXiv.2210.03099", + "url": "https://arxiv.org/abs/2210.03099" + }, + { + "id": "cerezo2021", + "type": "article", + "title": "Cost function dependent barren plateaus in shallow parametrized quantum circuits.", + "authors": "Cerezo, M., Sone, A., Volkoff, T. et al.", + "year": "2021", + "journal": "Nat Commun", + "url": "https://doi.org/10.1038/s41467-021-21728-w" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2210.03099" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_barren_gadgets/requirements.in b/demonstrations_v2/tutorial_barren_gadgets/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_barren_gadgets/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_barren_plateaus/demo.py b/demonstrations_v2/tutorial_barren_plateaus/demo.py new file mode 100644 index 0000000000..c730c388f5 --- /dev/null +++ b/demonstrations_v2/tutorial_barren_plateaus/demo.py @@ -0,0 +1,211 @@ +r""" +.. _barren_plateaus: + +Barren plateaus in quantum neural networks +========================================== + +.. meta:: + :property="og:description": Showing how randomized quantum circuits face the problem of barren plateaus using PennyLane. + We will partly reproduce some of the findings in McClean et. al., 2018 with just a few lines of code. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/surface.png + +.. related:: + + tutorial_local_cost_functions Alleviating barren plateaus with local cost functions + +*Author: Shahnawaz Ahmed — Posted: 11 October 2019. Last updated: 26 October 2020.* + +In classical optimization, it is suggested that saddle +points, not local minima, provide a fundamental impediment +to rapid high-dimensional non-convex optimization +(Dauphin et al., 2014). + +The problem of such barren plateaus manifests in a different +form in variational quantum circuits, which are at the heart +of techniques such as quantum neural networks or approximate +optimization e.g., QAOA (Quantum Adiabatic Optimization Algorithm) +which can be found in this `PennyLane QAOA tutorial +`_. + +While starting from a parameterized +random quantum circuit seems like a good unbiased choice if +we do not know the problem structure, McClean et al. (2018) +show that + +*"for a wide class of reasonable parameterized quantum +circuits, the probability that the gradient along any +reasonable direction is non-zero to some fixed precision +is exponentially small as a function of the number +of qubits."* + +Thus, randomly selected quantum circuits might not be the best +option to choose while implementing variational quantum +algorithms. + + +.. figure:: ../_static/demonstration_assets/barren_plateaus/surface.png + :width: 90% + :align: center + :alt: surface + +| + +In this tutorial, we will show how randomized quantum circuits +face the problem of barren plateaus using PennyLane. We will +partly reproduce some of the findings in McClean et. al., 2018 +with just a few lines of code. + +.. note:: + + **An initialization strategy to tackle barren plateaus** + + How do we avoid the problem of barren plateaus? + In Grant et al. (2019), the authors present one strategy to + tackle the barren plateau problem in randomized quantum circuits: + + *"The technique involves randomly selecting some of the initial + parameter values, then choosing the remaining values so that + the final circuit is a sequence of shallow unitary blocks that + each evaluates to the identity. Initializing in this way limits + the effective depth of the circuits used to calculate the first + parameter update so that they cannot be stuck in a barren plateau + at the start of training."* + +Exploring the barren plateau problem with PennyLane +--------------------------------------------------- + +First, we import PennyLane, NumPy, and Matplotlib +""" + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt + + +############################################################################## +# Next, we create a randomized variational circuit + +# Set a seed for reproducibility +np.random.seed(42) + +num_qubits = 4 +dev = qml.device("default.qubit", wires=num_qubits) +gate_set = [qml.RX, qml.RY, qml.RZ] + + +def rand_circuit(params, random_gate_sequence=None, num_qubits=None): + """A random variational quantum circuit. + + Args: + params (array[float]): array of parameters + random_gate_sequence (dict): a dictionary of random gates + num_qubits (int): the number of qubits in the circuit + + Returns: + float: the expectation value of the target observable + """ + for i in range(num_qubits): + qml.RY(np.pi / 4, wires=i) + + for i in range(num_qubits): + random_gate_sequence[i](params[i], wires=i) + + for i in range(num_qubits - 1): + qml.CZ(wires=[i, i + 1]) + + H = np.zeros((2 ** num_qubits, 2 ** num_qubits)) + H[0, 0] = 1 + wirelist = [i for i in range(num_qubits)] + return qml.expval(qml.Hermitian(H, wirelist)) + + +############################################################################## +# Now we can compute the gradient and calculate the variance. +# While we only sample 200 random circuits to allow the code +# to run in a reasonable amount of time, this can be +# increased for more accurate results. We only consider the +# gradient of the output with respect to the last parameter in the +# circuit. Hence we choose to save ``gradient[-1]`` only. + +grad_vals = [] +num_samples = 200 + +for i in range(num_samples): + gate_sequence = {i: np.random.choice(gate_set) for i in range(num_qubits)} + qcircuit = qml.QNode(rand_circuit, dev, interface="autograd") + grad = qml.grad(qcircuit, argnum=0) + params = np.random.uniform(0, 2 * np.pi, size=num_qubits) + gradient = grad(params, random_gate_sequence=gate_sequence, num_qubits=num_qubits) + grad_vals.append(gradient[-1]) + +print("Variance of the gradients for {} random circuits: {}".format( + num_samples, np.var(grad_vals) + ) +) +print("Mean of the gradients for {} random circuits: {}".format( + num_samples, np.mean(grad_vals) + ) +) + +############################################################################## +# Evaluate the gradient for more qubits +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# We can repeat the above analysis with increasing number of qubits. + + +qubits = [2, 3, 4, 5, 6] +variances = [] + + +for num_qubits in qubits: + grad_vals = [] + for i in range(num_samples): + dev = qml.device("default.qubit", wires=num_qubits) + qcircuit = qml.QNode(rand_circuit, dev, interface="autograd") + grad = qml.grad(qcircuit, argnum=0) + + gate_set = [qml.RX, qml.RY, qml.RZ] + random_gate_sequence = {i: np.random.choice(gate_set) for i in range(num_qubits)} + + params = np.random.uniform(0, np.pi, size=num_qubits) + gradient = grad( + params, random_gate_sequence=random_gate_sequence, num_qubits=num_qubits + ) + grad_vals.append(gradient[-1]) + variances.append(np.var(grad_vals)) + +variances = np.array(variances) +qubits = np.array(qubits) + + +# Fit the semilog plot to a straight line +p = np.polyfit(qubits, np.log(variances), 1) + + +# Plot the straight line fit to the semilog +plt.semilogy(qubits, variances, "o") +plt.semilogy(qubits, np.exp(p[0] * qubits + p[1]), "o-.", label="Slope {:3.2f}".format(p[0])) +plt.xlabel(r"N Qubits") +plt.ylabel(r"$\langle \partial \theta_{1, 1} E\rangle$ variance") +plt.legend() +plt.show() + + +############################################################################## +# References +# ---------- +# +# 1. Dauphin, Yann N., et al., +# Identifying and attacking the saddle point problem in high-dimensional non-convex +# optimization. Advances in Neural Information Processing +# systems (2014). +# +# 2. McClean, Jarrod R., et al., +# Barren plateaus in quantum neural network training landscapes. +# Nature communications 9.1 (2018): 4812. +# +# 3. Grant, Edward, et al. +# An initialization strategy for addressing barren plateaus in +# parametrized quantum circuits. arXiv preprint arXiv:1903.05076 (2019). +# +# diff --git a/demonstrations_v2/tutorial_barren_plateaus/metadata.json b/demonstrations_v2/tutorial_barren_plateaus/metadata.json new file mode 100644 index 0000000000..fb14843951 --- /dev/null +++ b/demonstrations_v2/tutorial_barren_plateaus/metadata.json @@ -0,0 +1,62 @@ +{ + "title": "Barren plateaus in quantum neural networks", + "authors": [ + { + "username": "quantshah" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-15T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_barren_plateaus_QNW.png" + } + ], + "seoDescription": "Showing how randomized quantum circuits face the problem of barren plateaus using PennyLane. We will partly reproduce some of the findings in McClean et. al., 2018 with just a few lines of code.", + "doi": "", + "references": [ + { + "id": "Dauphin2014", + "type": "article", + "title": "Identifying and attacking the saddle point problem in high-dimensional non-convex optimization", + "authors": "Dauphin, Yann N., et al.", + "year": "2014", + "journal": "Advances in Neural Information Processing systems", + "url": "" + }, + { + "id": "McClean2018", + "type": "article", + "title": "Barren plateaus in quantum neural network training landscapes", + "authors": "McClean, Jarrod R., et al.", + "year": "2018", + "journal": "Nature communications", + "url": "" + }, + { + "id": "Grant2019", + "type": "article", + "title": "An initialization strategy for addressing barren plateaus in parametrized quantum circuits", + "authors": "Grant, Edward, et al.", + "year": "2019", + "journal": "arXiv preprint", + "url": "" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1803.11173" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_barren_plateaus/requirements.in b/demonstrations_v2/tutorial_barren_plateaus/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_barren_plateaus/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_block_encoding/demo.py b/demonstrations_v2/tutorial_block_encoding/demo.py new file mode 100644 index 0000000000..c1c71fd31a --- /dev/null +++ b/demonstrations_v2/tutorial_block_encoding/demo.py @@ -0,0 +1,391 @@ +r""" + +Block encoding with matrix access oracles +========================================= + + +Prominent quantum algorithms such as quantum phase estimation and quantum singular value +transformation sometimes need to use **non-unitary** matrices inside quantum circuits. This is problematic +because quantum computers can only perform unitary evolutions 🔥. Block encoding is a technique +that solves this problem by embedding a non-unitary operator as a sub-block of a larger unitary +matrix 🧯. + +In previous demos we have discussed methods for `simulator-friendly `_ +encodings and block encodings using `linear combination of unitaries `_ +(LCU) decompositions. In this tutorial we explore another general block encoding framework that can be +very efficient for sparse and structured matrices: block encoding with matrix access oracles. + +.. figure:: ../_static/demonstration_assets/block_encoding/thumbnail_Block_Encodings_Matrix_Oracle.png + :align: center + :width: 50% + :target: javascript:void(0) + +A general circuit for block encoding an arbitrary matrix :math:`A \in \mathbb{C}^{N \times N}` with +:math:`N = 2^{n}` can be constructed as shown in the figure below, if we have access to the oracles +:math:`U_A` and :math:`U_B:` + +.. figure:: ../_static/demonstration_assets/block_encoding/general_circuit.png + :width: 50% + :align: center + +Where the :math:`H^{\otimes n}` operation is a Hadamard transformation on :math:`n` qubits. The +:math:`U_A` operation is an oracle which encodes the matrix element :math:`A_{i,j}` into the the +amplitude of the ancilla qubit. The :math:`U_B` oracle ensures that we iterate over every +combination of :math:`(i,j).` + +Finding an optimal quantum gate decomposition that implements :math:`U_A` and :math:`U_B` is not +always possible. We now explore two different approaches for constructing these oracles that can be +very efficient for matrices with specific structure or sparsity. + +Block encoding structured matrices +----------------------------------- +In order to better understand the oracle access framework, let us first define :math:`U_A` and :math:`U_B` +for the exact block-encoding of :math:`A.` The :math:`U_A` oracle is responsible for encoding the +matrix entries of :math:`A` into the amplitude of an auxilary qubit :math:`|0\rangle_{\text{anc}}:` + +.. math:: + + U_A |0\rangle_{\text{anc}} |i\rangle |j\rangle = |A_{i,j}\rangle_{\text{anc}} |i\rangle |j\rangle, + +where + +.. math:: + + |A_{i,j}\rangle_{\text{anc}} \equiv A_{i,j}|0\rangle_{\text{anc}} + \sqrt{1 - |A_{i,j}|^2}|1\rangle_{\text{anc}}. + +The :math:`U_B` oracle is responsible for ensuring proper indexing of each entry in :math:`A` +and for this algorithm, it simplifies to be the :class:`~.pennylane.SWAP` gate: + +.. math:: U_B |i\rangle|j\rangle \ = \ |j\rangle |i\rangle + +The naive approach is to construct :math:`U_A` using a sequence of multi-controlled :math:`Ry(\alpha)` +rotation gates with rotation angles computed as :math:`\alpha = \text{arccos}(A_{i,j}).` It turns out +that this requires :math:`O(N^{4})` gates and is very inefficient. A more efficient approach is the +Fast Approximate BLock Encodings (FABLE) technique [#fable]_, which +uses the oracle access framework and some clever approximations 🧠. The level of approximation in FABLE +can be adjusted to simplify the resulting circuit. For matrices with specific structures, FABLE provides an +efficient circuit *without* reducing accuracy. + +The FABLE circuit is constructed from a set of single :math:`Ry` rotation and C-NOT gates. We can remove +the need for multi-controlled rotations using a special transformation of the angles (see [#fable]_ for details). +The rotation angles, :math:`(\theta_1, ..., \theta_n),` are obtained from a transformation of the elements +of the block-encoded matrix. + +.. math:: \begin{pmatrix} \theta_1 \\ \cdots \\ \theta_n \end{pmatrix} = + M \begin{pmatrix} \alpha_1 \\ \cdots \\ \alpha_n \end{pmatrix}. + +The angles :math:`\alpha` are obtained from the matrix elements of the matrix :math:`A` as +:math:`\alpha_1 = \text{arccos}(A_{00}), ...,` and :math:`M` is the transformation matrix that can +be obtained with the :func:`~.pennylane.templates.state_preparations.mottonen.compute_theta` +function of PennyLane. + +The :class:`~.pennylane.FABLE` circuit is implemented in PennyLane and +can be easily used to block encode matrices of any given shape. Here, we manually construct the +circuit for a structured :math:`4 \times 4` matrix. +""" + +import pennylane as qml +from pennylane.templates.state_preparations.mottonen import compute_theta, gray_code +import numpy as np +import matplotlib.pyplot as plt + +A = np.array([[-0.51192128, -0.51192128, 0.6237114 , 0.6237114 ], + [ 0.97041007, 0.97041007, 0.99999329, 0.99999329], + [ 0.82429855, 0.82429855, 0.98175843, 0.98175843], + [ 0.99675093, 0.99675093, 0.83514837, 0.83514837]]) + +############################################################################## +# We also compute the rotation angles. + +alphas = np.arccos(A).flatten() +thetas = compute_theta(alphas) + +############################################################################## +# The next step is to identify and prepare the qubit registers used in the oracle access framework. +# There are three registers :code:`"ancilla"`, :code:`"wires_i"`, :code:`"wires_j"`. The +# :code:`"ancilla"` register will always contain a single qubit, this is the auxilary qubit where we +# apply the rotation gates mentioned above. The :code:`"wires_i"` and :code:`"wires_j"` registers are +# the same size for this algorithm and need to be able to encode :math:`A` itself, so they will both +# have :math:`2` qubits for our matrix. + +ancilla_wires = ["ancilla"] + +s = int(np.log2(A.shape[0])) +wires_i = [f"i{index}" for index in range(s)] +wires_j = [f"j{index}" for index in range(s)] + +############################################################################## +# Finally, we obtain the control wires for the C-NOT gates and a wire map that we later use to +# translate the control wires into the wire registers we prepared. + +code = gray_code(2 * np.log2(len(A))) +n_selections = len(code) + +control_wires = [int(np.log2(int(code[i], 2) ^ int(code[(i + 1) % + n_selections], 2))) for i in range(n_selections)] + +wire_map = {control_index : wire for control_index, wire in enumerate(wires_j + wires_i)} + +############################################################################## +# We now construct the :math:`U_A` and :math:`U_B` oracles as well as the operator representing the +# tensor product of Hadamard gates. + +def UA(thetas, control_wires, ancilla): + for theta, control_index in zip(thetas, control_wires): + qml.RY(2 * theta, wires=ancilla) + qml.CNOT(wires=[wire_map[control_index]] + ancilla) + + +def UB(wires_i, wires_j): + for w_i, w_j in zip(wires_i, wires_j): + qml.SWAP(wires=[w_i, w_j]) + + +def HN(input_wires): + for w in input_wires: + qml.Hadamard(wires=w) + +############################################################################## +# We construct the circuit using these oracles and draw it. + +dev = qml.device('default.qubit', wires=ancilla_wires + wires_i + wires_j) +@qml.qnode(dev) +def circuit(): + HN(wires_i) + qml.Barrier() # to separate the sections in the circuit + UA(thetas, control_wires, ancilla_wires) + qml.Barrier() + UB(wires_i, wires_j) + qml.Barrier() + HN(wires_i) + return qml.probs(wires=ancilla_wires + wires_i) + +qml.draw_mpl(circuit, style='pennylane')() +plt.show() + +############################################################################## +# Finally, we compute the matrix representation of the circuit and print its top-left block to +# compare it with the original matrix. + +print(f"Original matrix:\n{A}", "\n") +wire_order = ancilla_wires + wires_i[::-1] + wires_j[::-1] +M = len(A) * qml.matrix(circuit, wire_order=wire_order)().real[0:len(A),0:len(A)] +print(f"Block-encoded matrix:\n{M}", "\n") + +############################################################################## +# You can easily confirm that the circuit block encodes the original matrix defined above. Note that +# the dimension of :math:`A` should be :math:`2^n` where :math:`n` is an integer. For matrices with +# an arbitrary size, we can add zeros to reach the correct dimension. The padding will be +# automatically applied in :class:`~.pennylane.FABLE` implemented in +# PennyLane. +# +# The interesting point about the FABLE method is that we can eliminate those rotation gates that +# have an angle smaller than a defined threshold. This leaves a sequence of C-NOT gates that in +# most cases cancel each other out. You can confirm that two C-NOT gates applied to the same wires +# cancel each other. Let's now remove all the rotation gates that have an angle smaller than +# :math:`0.01` and draw the circuit. + +tolerance= 0.01 + +def UA(thetas, control_wires, ancilla): + for theta, control_index in zip(thetas, control_wires): + if abs(2 * theta)>tolerance: + qml.RY(2 * theta, wires=ancilla) + qml.CNOT(wires=[wire_map[control_index]] + ancilla) + +qml.draw_mpl(circuit, style='pennylane')() +plt.show() + +############################################################################## +# Compressing the circuit by removing some of the rotations is an approximation. We can now remove +# the C-NOT gates that cancel each other out and see how good this approximation is in the case +# of our example. We will make a small modification to :math:`U_A` so that it removes those +# C-NOT gates that cancel each other out. + +def UA(thetas, control_wires, ancilla): + nots=[] + for theta, control_index in zip(thetas, control_wires): + if abs(2 * theta) > tolerance: + for c_wire in nots: + qml.CNOT(wires=[c_wire] + ancilla) + qml.RY(2 * theta,wires=ancilla) + nots=[] + if (cw := wire_map[control_index]) in nots: + del(nots[nots.index(cw)]) + else: + nots.append(wire_map[control_index]) + for c_wire in nots: + qml.CNOT([c_wire] + ancilla) + +qml.draw_mpl(circuit, style='pennylane')() +plt.show() + +print(f"Original matrix:\n{A}", "\n") +wire_order = ancilla_wires + wires_i[::-1] + wires_j[::-1] +M = len(A) * qml.matrix(circuit,wire_order=wire_order)().real[0:len(A),0:len(A)] +print(f"Block-encoded matrix:\n{M}", "\n") + +############################################################################## +# You can see that the compressed circuit is equivalent to the original circuit. This happens +# because our original matrix is highly structured and many of the rotation angles are zero. +# However, this is not always true for an arbitrary matrix. Can you construct another matrix that +# allows a significant compression of the block encoding circuit without affecting the accuracy? +# +# Block encoding sparse matrices +# ------------------------------ +# The quantum circuit for the oracle :math:`U_A,` presented above, accesses every entry of +# :math:`A` and thus requires :math:`~ O(N^2)` gates to implement the oracle [#fable]_. In the +# special cases where :math:`A` is structured and sparse, we can generate a more efficient quantum +# circuit representation for :math:`U_A` and :math:`U_B` [#sparse]_ by only keeping track of the +# non-zero entries of the matrix. +# +# Let :math:`b(i,j)` be a function such that it takes a column index :math:`j` and returns the +# row index for the :math:`i^{th}` non-zero entry in that column of :math:`A.` Note, in this +# formulation, the :math:`|i\rangle` qubit register now refers to the number of non-zero entries +# in :math:`A.` For sparse matrices, this can be much smaller than :math:`N,` thus saving us many +# qubits. We use this to define :math:`U_A` and :math:`U_B.` +# +# Like in the structured approach, the :math:`U_A` oracle is responsible for encoding the matrix +# entries of :math:`A` into the amplitude of the ancilla qubit. However, we now use :math:`b(i,j)` +# to access the row index of the non-zero entries: +# +# .. math:: +# +# U_A |0\rangle_{\text{anc}} |i\rangle |j\rangle = |A_{b(i,j),j}\rangle_{\text{anc}} |i\rangle |j\rangle, +# +# In this case the :math:`U_B` oracle is responsible for implementing the :math:`b(i,j)` function +# and taking us from the column index to the row index in the qubit register: +# +# .. math:: U_B |i\rangle|j\rangle \ = \ |i\rangle |b(i,j)\rangle +# +# Let's work through an example. Consider the sparse matrix given by: +# +# .. math:: A = \begin{bmatrix} +# \alpha & \gamma & 0 & \dots & \beta\\ +# \beta & \alpha & \gamma & \ddots & 0 \\ +# 0 & \beta & \alpha & \gamma \ddots & 0\\ +# 0 & \ddots & \beta & \alpha & \gamma\\ +# \gamma & 0 & \dots & \beta & \alpha \\ +# \end{bmatrix}, +# +# where :math:`\alpha,` :math:`\beta` and :math:`\gamma` are real numbers. The following code block +# prepares the matrix representation of :math:`A` for an :math:`8 \times 8` sparse matrix. + +alpha, beta, gamma = 0.1, 0.6, 0.3 + +A = np.array([[alpha, gamma, 0, 0, 0, 0, 0, beta], + [ beta, alpha, gamma, 0, 0, 0, 0, 0], + [ 0, beta, alpha, gamma, 0, 0, 0, 0], + [ 0, 0, beta, alpha, gamma, 0, 0, 0], + [ 0, 0, 0, beta, alpha, gamma, 0, 0], + [ 0, 0, 0, 0, beta, alpha, gamma, 0], + [ 0, 0, 0, 0, 0, beta, alpha, gamma], + [gamma, 0, 0, 0, 0, 0, beta, alpha]]) + +print(f"Original A:\n{A}", "\n") + +############################################################################## +# Once again we identify and prepare the qubit registers used in the oracle access framework. +# +# The :code:`"ancilla"` register will still contain a single qubit, the target for the +# controlled rotation gates. The :code:`"wires_i"` register needs to be large enough to binary +# encode the maximum number of non-zero entries in any column or row. Given the structure of +# :math:`A` defined above, we have at most 3 non-zero entries, thus this register will have 2 +# qubits. Finally, the :code:`"wires_j"` register will be used to encode :math:`A` itself, so it +# will have 3 qubits. We prepare the wires below: + +s = int(np.log2(A.shape[0])) # number of qubits needed to encode A + +ancilla_wires = ["ancilla"] # always 1 qubit for controlled rotations +wires_i = ["i0", "i1"] # depends on the sparse structure of A +wires_j = [f"j{index}" for index in range(s)] # depends on the size of A + +############################################################################## +# The :math:`U_A` oracle for this matrix is constructed from controlled rotation gates, similar to +# the FABLE circuit. + +def UA(theta, wire_i, ancilla): + qml.ctrl(qml.RY, control=wire_i, control_values=[0, 0])(theta[0], wires=ancilla) + qml.ctrl(qml.RY, control=wire_i, control_values=[1, 0])(theta[1], wires=ancilla) + qml.ctrl(qml.RY, control=wire_i, control_values=[0, 1])(theta[2], wires=ancilla) + +############################################################################## +# The :math:`U_B` oracle is defined in terms of the so-called ``Left`` and ``Right`` shift operators. +# They correspond to the modular arithmetic operations :math:`+1` or :math:`-1` respectively [#sparse]_. + +def shift_op(s_wires, shift="Left"): + for index in range(len(s_wires)-1, 0, -1): + control_values = [1] * index if shift == "Left" else [0] * index + qml.ctrl(qml.PauliX, control=s_wires[:index], control_values=control_values)(wires=s_wires[index]) + qml.PauliX(s_wires[0]) + + +def UB(wires_i, wires_j): + qml.ctrl(shift_op, control=wires_i[0])(wires_j, shift="Left") + qml.ctrl(shift_op, control=wires_i[1])(wires_j, shift="Right") + +############################################################################## +# We now construct our circuit to block encode the sparse matrix and draw it. + +dev = qml.device("default.qubit", wires=(ancilla_wires + wires_i + wires_j)) + +@qml.qnode(dev) +def complete_circuit(thetas): + HN(wires_i) + qml.Barrier() # to separate the sections in the circuit + UA(thetas, wires_i, ancilla_wires) + qml.Barrier() + UB(wires_i, wires_j) + qml.Barrier() + HN(wires_i) + return qml.probs(wires=ancilla_wires + wires_i) + +s = 4 # normalization constant +thetas = 2 * np.arccos(np.array([alpha - 1, beta, gamma])) + +qml.draw_mpl(complete_circuit, style='pennylane')(thetas) +plt.show() + +############################################################################## +# Finally, we compute the matrix representation of the circuit and print its top-left block to +# compare it with the original matrix. + +print("\nBlockEncoded Mat:") +wire_order = ancilla_wires + wires_i[::-1] + wires_j[::-1] +mat = qml.matrix(complete_circuit, wire_order=wire_order)(thetas).real[:len(A), :len(A)] * s +print(mat, "\n") + +############################################################################## +# You can confirm that the circuit block encodes the original sparse matrix defined above. +# Note that if we wanted to increase the dimension of :math:`A` (for example :math:`16 \times 16`), +# then we need only to add more wires to the ``j`` register in the device and in :code:`UB`. +# +# Conclusion +# ----------------------- +# Block encoding is a powerful technique in quantum computing that allows us to implement a +# non-unitary operation in a quantum circuit by embedding the operation in a larger unitary gate. +# In this tutorial, we reviewed two important block encoding methods with code examples using +# PennyLane. This allows you to use PennyLane to explore and benchmark several block encoding +# approaches for a desired problem. The efficiency of the block encoding methods typically depends +# on the sparsity and structure of the original matrix. We hope that you can use these tips and +# tricks to find a more efficient block encoding for your matrix! +# +# References +# ---------- +# +# .. [#fable] +# +# Daan Camps, Roel Van Beeumen, +# "FABLE: fast approximate quantum circuits for block-encodings", +# `arXiv:2205.00081 `__, 2022 +# +# +# .. [#sparse] +# +# Daan Camps, Lin Lin, Roel Van Beeumen, Chao Yang, +# "Explicit quantum circuits for block encodings of certain sparse matrices", +# `arXiv:2203.10236 `__, 2022 +# +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_block_encoding/metadata.json b/demonstrations_v2/tutorial_block_encoding/metadata.json new file mode 100644 index 0000000000..c123f5192a --- /dev/null +++ b/demonstrations_v2/tutorial_block_encoding/metadata.json @@ -0,0 +1,67 @@ +{ + "title": "Block Encodings", + "authors": [ + { + "username": "Jay" + }, + { + "username": "Diego" + }, + { + "username": "soran" + } + ], + "dateOfPublication": "2023-11-28T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/block_encoding/thumbnail_Block_Encodings_Matrix_Oracle.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Block_Encodings_Matrix_Oracle.png" + } + ], + "seoDescription": "Learn about methods to achieve block-encoding for a given matrix.", + "doi": "", + "references": [ + { + "id": "Daan2023", + "type": "article", + "title": "Explicit Quantum Circuits for Block Encodings of Certain Sparse Matrices.", + "authors": "Daan C., Lin L., Roel V. B, Chao Y.", + "year": "2023", + "journal": "Preprint", + "url": "https://arxiv.org/pdf/2203.10236.pdf" + }, + { + "id": "McClean2018", + "type": "article", + "title": "FABLE: Fast Approximate Quantum Circuits for Block-Encodings", + "authors": "Daan C., Roel V. B.", + "year": "2022", + "journal": "arXiv", + "url": "https://arxiv.org/pdf/2205.00081.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_lcu_blockencoding", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_block_encoding/requirements.in b/demonstrations_v2/tutorial_block_encoding/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_block_encoding/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_bluequbit/demo.py b/demonstrations_v2/tutorial_bluequbit/demo.py new file mode 100644 index 0000000000..a6bfea608a --- /dev/null +++ b/demonstrations_v2/tutorial_bluequbit/demo.py @@ -0,0 +1,182 @@ +r"""Using the BlueQubit (CPU) device with PennyLane +============================================================= + +Running large-scale simulations usually requires lots of memory and compute power, regular laptops already struggle above 20 qubits +and most of the time 30+ qubits are a no-go. +Using the `BlueQubit device `_, PennyLane users can now run circuit simulations on souped up machines and go up to 33 qubits! +Also, Bluequbit uses a custom build of `PennyLane-Lightning `__ that +enables multi-threading and other configurations to achieve the best possible performance. + +Below we will show 2 examples of how to use BlueQubit with PennyLane. +The first is a very simple example building a Bell pair, and the second one is a large 26-qubit circuit that demonstrates the central limit theorem using quantum arithmetic. + + +.. note:: + + To follow along with this tutorial on your own computer, you will need the + `BlueQubit SDK `_. It can be installed via pip: + + .. code-block:: bash + + pip install bluequbit + + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_bluequbit-pennylane_device.png + :align: center + :width: 70% + :target: javascript:void(0) + + + +Build your PennyLane circuit +---------------------------- + +Here we will build a simple :doc:`Bell pair ` and simulate it on the BlueQubit backend. +Later in this tutorial we will show a larger example — a 26-qubit circuit that demonstrates the central limit theorem using a `Draper QFT adder `__. + +Here is the example circuit we will be simulating: +""" + +import pennylane as qml +import matplotlib.pyplot as plt +import numpy as np + +# This filter will suppress deprecation warnings for viewability +import warnings +warnings.filterwarnings("ignore", "QubitDevice", qml.PennyLaneDeprecationWarning) + + +def bell_pair(): + qml.Hadamard(0) + qml.CNOT(wires=(0, 1)) + return qml.probs() + +fig = qml.draw_mpl(bell_pair)() + + +############################################################################## +# Use the BlueQubit device +# ------------------------ +# Here are the 3 easy steps to simulate the above circuit on the BlueQubit backend: +# +# 1. Open an account at `app.bluequbit.io `__ to get a token. You can also view your submitted jobs here later. +# 2. Initialize the `bluequbit` device with your token. +# 3. Submit the circuit for simulation! + +import bluequbit +bluequbit.logger.setLevel("ERROR") + +# STEP 2: Initialize the bluequbit device! +# Using a guest token here. Replace it with your own token for a better experience. +bq_dev = qml.device("bluequbit.cpu", wires=2, token="3hmIGLWGKzKdWmxLoJ5F24P3rivGL04d") + +bell_qnode = qml.QNode(bell_pair, bq_dev) + +# STEP 3: Simulate the circuit! +result = bell_qnode() +print(result) + + +############################################# +# And that's it! Circuit details and visualizations will also appear in your BlueQubit account after this run. +# +# Now we can run even larger (up to 33-qubit) circuit simulations the same way! +# +# Larger workloads: 26 qubits +# --------------------------- +# Here we will see a much larger example — a 26-qubit circuit. +# Inspired by `Guillermo Allonso's `__ PennyLane Demo `Basic arithmetic with the quantum Fourier transform (QFT) `__, +# which implements a quantum adder in PennyLane, we build our own adder and use it to add together quantum registers. +# +# In the quantum world we can use the idea of superposition to add multiple numbers at the same time. +# Furthermore, since each number in the superposition can have its own weight, we can use this adder to sum together distributions! +# Below we will use that idea to demonstrate the `central limit theorem `__: we will add together a couple of sequences of independent and identically distributed variables (namely uniformly distributed) +# and see what their outcome will look like. +# +# It should take approximately 1 minute to run the code below. + + + +def draper_adder(wires_a, wires_b, kind="fixed", include_qft=True, include_iqft=True): + """ + Implement the Draper adder for qubit registers of different sizes using PennyLane. + + Args: + wires_a (list): Wires for the first register (smaller or equal size). + wires_b (list): Wires for the second register (larger or equal size). + kind (str): The kind of adder, can be 'half' or 'fixed' (default: 'fixed'). if kind='half' min(wires_b)-1 is the additional qubit of second register + include_qft (bool): Whether to include the QFT part (default: True). + include_iqft (bool): Whether to include the inverse QFT part (default: True). + """ + m = len(wires_a) + n = len(wires_b) + if kind == "half": + wires_sum = [min(wires_b) - 1] + wires_b + else: + wires_sum = wires_b + # QFT part + if include_qft: + qml.QFT(wires=wires_sum) + # Controlled rotations + for j in range(m): + for k in range(n - j): + lam = np.pi / (2**k) + qml.ControlledPhaseShift(lam, wires=[wires_a[-j-1], wires_sum[j+k]]) + if kind == "half": + for j in range(m): + lam = np.pi / (2 ** (j + 1 + n - m)) + qml.ControlledPhaseShift(lam, wires=[wires_a[j], wires_sum[-1]]) + # Inverse QFT part + if include_iqft: + qml.adjoint(qml.QFT)(wires=wires_sum) + +# Using a guest token here. Replace it with your own token for a better experience. +dev = qml.device("bluequbit.cpu", wires = 26, shots = None, token="3hmIGLWGKzKdWmxLoJ5F24P3rivGL04d") + +@qml.qnode(dev) +def add_4_6qubit_uniforms(): + regs = [list(range(0,6)), + list(range(6,12)), + list(range(12,18)), + list(range(18,26))] + # make each register uniform 0-63 + for reg in regs: + for j in range(6): + qml.Hadamard(reg[-j-1]) + # calcualte sum + draper_adder(regs[0], regs[3][-6:], kind="half") + draper_adder(regs[1], regs[3][-7:], kind="half", include_iqft=False) + draper_adder(regs[2], regs[3], include_qft=False) # skip I=QFT+iQFT, a small optimization + return qml.probs(wires=regs[3]) + +res = add_4_6qubit_uniforms() +plt.figure(figsize=(32, 8)) +bar = plt.bar(np.arange(len(res)), res) +plt.tick_params(axis='x', labelsize=30) +plt.tick_params(axis='y', labelsize=30) + +############################################# +# Wow, that looks like a Gaussian distribution! +# +# That's exactly what's expected from the central limit theorem — adding together multiple sequences of independent and identically distributed variables approximates the normal distribution. + +############################################# +# Conclusion +# ---------- +# +# In this tutorial we saw how PennyLane users can run large circuit simulations on BlueQubit's souped up machines. +# We demonstrated this both on a small example, as well as a large 26-qubit simulation where we added together uniform +# distributions to approximate a normal distribution. +# +# PennyLane users can now simulate large circuits of up to 33 qubits for free using `BlueQubit `_ — we are looking forward to seeing the +# creative and innovative ways researchers and quantum enthusiasts will be using this capability! + +############################################# +# References +# ---------- +# +# .. [#Draper2000] +# +# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. + +############################################################################## diff --git a/demonstrations_v2/tutorial_bluequbit/metadata.json b/demonstrations_v2/tutorial_bluequbit/metadata.json new file mode 100644 index 0000000000..51bd85b68b --- /dev/null +++ b/demonstrations_v2/tutorial_bluequbit/metadata.json @@ -0,0 +1,59 @@ +{ + "title": "Using the BlueQubit (CPU) device with PennyLane", + "authors": [ + { + "username": "hayk_tepanyan" + } + ], + "dateOfPublication": "2024-09-24T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_bluequbit-pennylane_device.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_bluequbit-pennylane_device.png" + } + ], + "seoDescription": "Run large-scale quantum simulations with PennyLane and BlueQubit.", + "doi": "", + "references": [ + { + "id": "Draper2000", + "type": "article", + "title": "Addition on a Quantum Computer", + "authors": "Thomas G. Draper", + "year": "2000", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0008033" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qft", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "bluequbit", + "link": "https://app.bluequbit.io/", + "logo": "/_static/hardware_logos/bluequbit.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_bluequbit/requirements.in b/demonstrations_v2/tutorial_bluequbit/requirements.in new file mode 100644 index 0000000000..1972a95adc --- /dev/null +++ b/demonstrations_v2/tutorial_bluequbit/requirements.in @@ -0,0 +1,4 @@ +bluequbit +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_chemical_reactions/demo.py b/demonstrations_v2/tutorial_chemical_reactions/demo.py new file mode 100644 index 0000000000..eef34f1810 --- /dev/null +++ b/demonstrations_v2/tutorial_chemical_reactions/demo.py @@ -0,0 +1,434 @@ +r""" +Modelling chemical reactions on a quantum computer +================================================== + +.. meta:: + :property="og:description": Construct potential energy surfaces for chemical reactions + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/reaction.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + +*Authors: Varun Rishi and Juan Miguel Arrazola — Posted: 23 July 2021. Last updated: 21 February 2023.* + +The term "chemical reaction" is another name for the transformation of molecules – the breaking and +forming of bonds. They are characterized by an energy barrier that determines +the likelihood that a reaction takes place. The energy landscapes formed by these barriers are the +key to understanding how chemical reactions occur, at the deepest possible level. + +.. figure:: /_static/demonstration_assets/vqe_bond_dissociation/reaction.png + :width: 50% + :align: center + + An example chemical reaction. + +In this tutorial, you will learn how to use PennyLane to simulate chemical reactions by +constructing potential energy surfaces for molecular transformations. In the process, you will +learn how quantum computers can be used to calculate equilibrium bond lengths, activation energy +barriers, and reaction rates. As illustrative +examples, we use tools implemented in PennyLane to study diatomic bond dissociation and reactions +involving the exchange of hydrogen atoms. + + +Potential Energy Surfaces +--------------------------------------------------------------------- + +`Potential energy surfaces (PES) `_ +describe the energy of molecules for different positions of +its atoms. The concept originates from the fact that the electrons are much lighter than protons +and neutrons, so they will adjust instantaneously to the new positions of the nuclei. This leads +to a separation of the nuclear and electronic parts of the Schrödinger equation, meaning we only need +to solve the electronic equation: + +.. math:: H(R)|\Psi \rangle = E|\Psi\rangle. + +From this perspective arises the concept of the electronic energy of a molecule, :math:`E(R),` +as a function of nuclear coordinates :math:`R.` The energy :math:`E(R)` is the expectation value +of the molecular Hamiltonian, :math:`E(R)=\langle \Psi_0|H(R)|\Psi_0\rangle,` taken +with respect to the ground state :math:`|\Psi_0(R)\rangle.` The potential energy surface is +precisely this function :math:`E(R),` which connects energies to different geometries of the +molecule. It gives us a visual tool to understand chemical reactions by associating +stable molecules (reactants and products) with local minima, transition states with peaks, +and by identifying the possible routes for a chemical reaction to occur. + +To build the potential energy surface, we compute the energy for fixed positions of the nuclei, +and subsequently adjust the positions of the nuclei in incremental steps, computing the energies at each new configuration. +The obtained set of energies corresponds to a grid of nuclear positions and the plot of +:math:`E(R)` gives rise to the potential energy surface. + + +.. figure:: /_static/demonstration_assets/vqe_bond_dissociation/pes.png + :width: 75% + :align: center + + Illustration of a potential energy surface for a diatomic molecule. + +Bond dissociation in a Hydrogen molecule +---------------------------------------- + +We now construct a potential energy surface and use it to compute equilibrium bond lengths and +the bond dissociation energy. We begin with the simplest of molecules: :math:`H_2.` +The formation or breaking of the :math:`H-H` bond is also the most +elementary of all reactions: + +.. math:: H_2 \rightarrow H + H. + +Using a minimal `basis set `_, +this molecular system can be described by two electrons in four +spin-orbitals. When mapped to a qubit representation, we need a total of four qubits. +The *Hartree-Fock (HF) state* is represented as :math:`|1100\rangle,` where the two +lowest-energy orbitals are occupied, and the remaining two are unoccupied. + +We design a quantum circuit consisting of :class:`~.pennylane.SingleExcitation` and +:class:`~.pennylane.DoubleExcitation` gates applied to the Hartree-Fock state. This circuit +will be optimized to prepare ground states for different configurations of the molecule. +""" + +import pennylane as qml +from pennylane import qchem + +# Hartree-Fock state +hf = qml.qchem.hf_state(electrons=2, orbitals=4) + + +############################################################################## +# To construct the potential energy surface, we vary the location of the nuclei and calculate the +# energy for each resulting geometry of the molecule. We keep +# an :math:`H` atom fixed at the origin and change only the +# coordinate of the other atom in a single direction. The potential energy +# surface is then a one-dimensional function depending only on the bond length, i.e., the separation +# between the atoms. For each value of the bond length, we construct the corresponding +# Hamiltonian, then optimize the circuit using gradient descent to obtain the ground-state energy. +# We vary the bond length in the range +# :math:`0.5` to :math:`5.0` `Bohrs `_ in steps of +# :math:`0.25` Bohr. This covers the point where the :math:`H-H` bond is formed, +# the equilibrium bond length, and the point where the bond is broken, which occurs when the atoms +# are far away from each other. + +from pennylane import numpy as np + +# atomic symbols defining the molecule +symbols = ['H', 'H'] + +# list to store energies +energies = [] + +# set up a loop to change bond length +r_range = np.arange(0.5, 5.0, 0.25) + +# keeps track of points in the potential energy surface +pes_point = 0 + +############################################################################## +# We build the Hamiltonian using the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function, and use standard Pennylane techniques to optimize the circuit. + +for r in r_range: + # Change only the z coordinate of one atom + coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r]]) + + # Construct the Molecule object + molecule = qchem.Molecule(symbols, coordinates) + + # Obtain the qubit Hamiltonian + H, qubits = qchem.molecular_hamiltonian(molecule, method='openfermion') + + # define the device, optimizer and circuit + dev = qml.device("default.qubit", wires=qubits) + opt = qml.GradientDescentOptimizer(stepsize=0.4) + + @qml.qnode(dev, interface='autograd') + def circuit(parameters): + # Prepare the HF state: |1100> + qml.BasisState(hf, wires=range(qubits)) + qml.DoubleExcitation(parameters[0], wires=[0, 1, 2, 3]) + qml.SingleExcitation(parameters[1], wires=[0, 2]) + qml.SingleExcitation(parameters[2], wires=[1, 3]) + + return qml.expval(H) # we are interested in minimizing this expectation value + + # initialize the gate parameters + params = np.zeros(3, requires_grad=True) + + # initialize with converged parameters from previous point + if pes_point > 0: + params = params_old + + prev_energy = 0.0 + for n in range(50): + # perform optimization step + params, energy = opt.step_and_cost(circuit, params) + + if np.abs(energy - prev_energy) < 1e-6: + break + prev_energy = energy + + # store the converged parameters + params_old = params + pes_point = pes_point + 1 + + energies.append(energy) + + +############################################################################## +# Let's plot the results 📈 + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() +ax.plot(r_range, energies) + +ax.set( + xlabel="Bond length (Bohr)", + ylabel="Total energy (Hartree)", + title="Potential energy surface for H$_2$ dissociation", +) +ax.grid() +plt.show() + + +############################################################################## +# This is the potential energy surface for the dissociation of a hydrogen molecule into +# two hydrogen atoms. It is a numerical calculation of the same type of plot that was +# illustrated in the beginning. In a diatomic molecule such as :math:`H_2,` it +# can be used to obtain the equilibrium bond length — the distance between the two atoms that +# minimizes the total electronic energy. This is simply the minimum of the curve. We can also +# obtain the bond dissociation energy, which is the difference in the energy of the system when +# the atoms are far apart and the energy at equilibrium. At sufficiently large separations, +# the atoms no longer form a molecule, and the system is called "dissociated". +# +# Let's use our results to compute the equilibrium bond length and the bond dissociation energy: + +# equilibrium energy +e_eq = min(energies) +# energy when atoms are far apart +e_dis = energies[-1] + +# Bond dissociation energy +bond_energy = e_dis - e_eq + +# Equilibrium bond length +idx = energies.index(e_eq) +bond_length = r_range[idx] + +print(f"The equilibrium bond length is {bond_length:.1f} Bohrs") +print(f"The bond dissociation energy is {bond_energy:.6f} Hartrees") + + +############################################################################## +# These estimates can be improved +# by using bigger basis sets and extrapolating to the complete basis set limit [#motta2020]_. +# The calculations are of course are subject to the grid size of interatomic +# distances considered. The finer the grid size, the better the estimates. +# +# .. note:: +# +# Did you notice a trick we used to speed up the calculations? The converged +# gate parameters for a particular geometry on the PES are used as the initial guess for the +# calculation at the adjacent geometry. With a better guess, the algorithm converges +# faster and we save considerable time. + +############################################################################## +# Hydrogen Exchange Reaction +# ----------------------------- +# +# After studying a simple diatomic bond dissociation, we move to a slightly more complicated +# case: a hydrogen exchange reaction. +# +# .. math:: H_2 + H \rightarrow H + H_2. +# +# This reaction has a barrier, the `transition state +# `_, that must be crossed +# for the exchange of an atom to be complete. In this case, the transition state +# corresponds to a specific linear arrangement of the atoms where one :math:`H-H` bond is +# partially broken and the other :math:`H-H` bond is partially formed. +# The molecular movie ⚛️🎥 below is an illustration of the reaction trajectory. It depicts how the distance +# between the hydrogen atoms changes as one bond is broken and another one is formed. +# The path along which the reaction proceeds is known as the `reaction coordinate +# `_. +# +# .. figure:: /_static/demonstration_assets/vqe_bond_dissociation/h3_mol_movie.gif +# :width: 50% +# :align: center +# +# In a minimal basis like STO-3G, this system consists of three electrons in six spin +# molecular orbitals. This translates into a six-qubit problem, for which the Hartree-Fock state +# is :math:`|111000\rangle.` As there is an unpaired +# electron, the spin multiplicity is equal to two and needs to be specified, since it differs +# from the default value of one. + +symbols = ["H", "H", "H"] +multiplicity = 2 + +############################################################################## +# To build a potential energy surface for the hydrogen exchange, we fix the positions of the +# outermost atoms, and change only the placement of the middle atom. For this circuit, we employ all +# single and double excitation gates, which can be conveniently done with the +# :class:`~.pennylane.templates.subroutines.AllSinglesDoubles` template. The rest of the procedure follows as before. + +from pennylane.templates import AllSinglesDoubles + +energies = [] +pes_point = 0 + +# get all the singles and doubles excitations, and Hartree-Fock state +electrons = 3 +orbitals = 6 +singles, doubles = qchem.excitations(electrons, orbitals) +hf = qml.qchem.hf_state(electrons, orbitals) + + +# loop to change reaction coordinate +r_range = np.arange(1.0, 3.0, 0.1) +for r in r_range: + + coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r], [0.0, 0.0, 4.0]]) + + # We now specify the multiplicity + molecule = qchem.Molecule(symbols, coordinates, mult=multiplicity) + + H, qubits = qchem.molecular_hamiltonian(molecule, method='openfermion') + + dev = qml.device("default.qubit", wires=qubits) + opt = qml.GradientDescentOptimizer(stepsize=1.5) + + @qml.qnode(dev, interface='autograd') + def circuit(parameters): + AllSinglesDoubles(parameters, range(qubits), hf, singles, doubles) + return qml.expval(H) # we are interested in minimizing this expectation value + + params = np.zeros(len(singles) + len(doubles), requires_grad=True) + + if pes_point > 0: + params = params_old + + prev_energy = 0.0 + + for n in range(60): + params, energy = opt.step_and_cost(circuit, params) + if np.abs(energy - prev_energy) < 1e-6: + break + prev_energy = energy + + # store the converged parameters + params_old = params + pes_point = pes_point + 1 + + energies.append(energy) + +############################################################################## +# Once the calculation is complete, we can plot the resulting potential energy surface. + +fig, ax = plt.subplots() +ax.plot(r_range, energies) + +ax.set( + xlabel="Distance (Bohr)", + ylabel="Total energy (Hartree)", +) +ax.grid() +plt.show() + +############################################################################## +# The two minima in the curve represent the energy of the reactants and products. The +# transition state is represented by the local maximum. These are the configurations illustrated in +# the animation above. + +############################################################################## +# Activation energy barriers and reaction rates +# --------------------------------------------- +# +# The potential energy surfaces we computed so far can be leveraged for other important tasks, +# such as computing activation energy barriers and reaction rates. The activation energy barrier ( +# :math:`E_{a}`) is defined as the difference between the energy of the reactants and the energy +# of the transition state. +# +# .. math:: E_{a} = E_{TS} - E_{R}. +# +# This can be computed from the potential energy surface: + +# Energy of the reactants and products - two minima on the PES +e_eq1 = min(energies) +e_eq2 = min([x for x in energies if x != e_eq1]) + +idx1 = energies.index(e_eq1) +idx2 = energies.index(e_eq2) + +# Transition state is the local maximum between reactant and products +idx_min = min(idx1, idx2) +idx_max = max(idx1, idx2) + +# Transition state energy +energy_ts = max(energies[idx_min:idx_max]) + +# Activation energy +activation_energy = energy_ts - e_eq1 + +print(f"The activation energy is {activation_energy:.6f} Hartrees") + +############################################################################## +# The reaction rate constant (:math:`k`) has an exponential dependence on the activation energy +# barrier, as shown in the `Arrhenius equation +# `_ (Arrr! 🏴‍☠️): +# +# .. math:: k = Ae^{-{E_{a}}/k_BT}, +# +# where :math:`k_B` is the Boltzmann constant, :math:`T` is the temperature, and :math:`A` is a +# pre-exponential factor that can be determined empirically for each reaction. Crucially, the rate at which +# a chemical reaction occurs depends exponentially on the activation energy computed from the PES — this is a good reminder of the importance +# of performing highly-accurate calculations in quantum chemistry! +# +# For example, let's calculate the ratio of reaction rates when the temperature is doubled. We have +# +# .. math:: \frac{k_2}{k_1}=\frac{Ae^{-{E_{a}}/k_B(2T)}}{Ae^{-{E_{a}}/k_BT}}=e^{E_a/2k_B T}. +# +# We choose :math:`T=300` Kelvin, which is essentially room temperature. This makes doubling the +# temperature roughly equivalent to the temperature inside a pizza oven. We have + +# convert to joules +activation_energy *= 4.36e-18 +# Boltzmann constant in Joules/Kelvin +k_B = 1.38e-23 +# Temperature +T = 300 + +ratio = np.exp(activation_energy / (2 * k_B * T)) + +print(f"Ratio of reaction rates is {ratio:.0f}") + +############################################################################## +# Doubling the temperature can increase the rate by a factor of almost two million! For a similar +# reason, changing the activation energy can lead to drastic changes in the reaction rates, +# which means we have to be careful to compute it very accurately. +# +# Conclusion +# ---------- +# We can learn how atoms combine to form different molecules by performing experiments; this is +# the approach many of us learn as children by playing with chemistry sets. However, a deeper +# quantitative understanding of chemical reactions can be achieved by performing theoretical +# simulations of the mechanisms for forming and breaking bonds. This tutorial described how +# simple chemical reactions can be simulated using quantum algorithms that reconstruct +# potential energy surfaces, allowing us to identify reactants and products as minima of the +# energy, and transition states as local maxima. These results can then be used to calculate +# activation energies and reaction rates. The goal (and challenge!) for quantum computing is to +# improve both hardware and algorithms to reach the regime where providing accurate simulations +# becomes intractable for existing methods. If successful, this quest will +# allow us to understand the properties of quantum systems in ways that have so far been out of +# reach. +# +# +# +# .. _references: +# +# References +# ---------- +# +# .. [#motta2020] +# +# Mario Motta, Tanvi Gujarati, and Julia Rice, `"A Tale of Colliding Electrons: Boosting the +# Accuracy of Chemical Simulations on Quantum Computers" (2020). +# `__ +# +# diff --git a/demonstrations_v2/tutorial_chemical_reactions/metadata.json b/demonstrations_v2/tutorial_chemical_reactions/metadata.json new file mode 100644 index 0000000000..0d04e79081 --- /dev/null +++ b/demonstrations_v2/tutorial_chemical_reactions/metadata.json @@ -0,0 +1,50 @@ +{ + "title": "Modelling chemical reactions on a quantum computer", + "authors": [ + { + "username": "vrishi" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2021-07-23T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_modelling_chemical_reactions_QC.png" + } + ], + "seoDescription": "Construct potential energy surfaces for chemical reactions", + "doi": "", + "references": [ + { + "id": "motta2020", + "type": "article", + "title": "A Tale of Colliding Electrons: Boosting the Accuracy of Chemical Simulations on Quantum Computers", + "authors": "Mario Motta, Tanvi Gujarati, and Julia Rice", + "year": "2020", + "journal": "", + "url": "https://medium.com/qiskit/a-tale-of-colliding-electrons-boosting-the-accuracy-of-chemical-simulations-on-quantum-computers-50a4b4ee5c64" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_chemical_reactions/requirements.in b/demonstrations_v2/tutorial_chemical_reactions/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_chemical_reactions/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_circuit_compilation/demo.py b/demonstrations_v2/tutorial_circuit_compilation/demo.py new file mode 100644 index 0000000000..39917ff51b --- /dev/null +++ b/demonstrations_v2/tutorial_circuit_compilation/demo.py @@ -0,0 +1,241 @@ +r"""Compilation of quantum circuits +=============================== + +.. meta:: + :property="og:description": Learn about circuit transformations and quantum circuit compilation with PennyLane + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_circuit_compilation.png + +.. related:: + + tutorial_quantum_circuit_cutting Introduction to circuit cutting + + +*Author: Borja Requena — Posted: 14 June 2023.* + +Quantum circuits take many different forms from the moment we design them to the point where they +are ready to run on a quantum device. These changes happen during the compilation process of the +quantum circuits, which relies on the fact that there are many equivalent representations of quantum +circuits that use different sets of gates but provide the same output. + +Out of those representations, we are typically interested in finding the most suitable one for our +purposes. For example, we usually look for the one that will incur the least amount of errors in the +specific hardware on which we are compiling the circuit. This usually implies decomposing the +quantum gates in terms of the native ones of the quantum device, adapting the operations to the +hardrware's topology, combining them to reduce the circuit depth, etc. + +A crucial part of the compilation process consists of repeatedly performing minor circuit modifications. +In PennyLane, we can apply :mod:`~pennylane.transforms` to our quantum functions in order to obtain +equivalent ones that may be more convenient for our task. In this tutorial, we introduce the most +fundamental transforms involved in the compilation of quantum circuits. + +Circuit transforms +------------------ + +When we implement quantum algorithms, it is typically in our best interest that the resulting +circuits are as shallow as possible, especially with the currently available noisy quantum devices. +However, they are often more complex than needed, containing multiple operations that could be +combined to reduce the circuit complexity, although it is not always obvious. Here, we +introduce three simple :mod:`~pennylane.transforms` that can be implemented together to obtain +simpler quantum circuits. The transforms are based on very basic circuit equivalences: + +.. figure:: ../_static/demonstration_assets/circuit_compilation/circuit_transforms.jpg + :align: center + :width: 90% + +To illustrate their combined effect, let us consider the following circuit. +""" + +from functools import partial +import pennylane as qml +import matplotlib.pyplot as plt + +dev = qml.device("default.qubit", wires=3) + + +def circuit(angles): + qml.Hadamard(wires=1) + qml.Hadamard(wires=2) + qml.RX(angles[0], 0) + qml.CNOT(wires=[1, 0]) + qml.CNOT(wires=[2, 1]) + qml.RX(angles[2], wires=0) + qml.RZ(angles[1], wires=2) + qml.CNOT(wires=[2, 1]) + qml.RZ(-angles[1], wires=2) + qml.CNOT(wires=[1, 0]) + qml.Hadamard(wires=1) + qml.CY(wires=[1, 2]) + qml.CNOT(wires=[1, 0]) + return qml.expval(qml.PauliZ(wires=0)) + + +angles = [0.1, 0.3, 0.5] +qnode = qml.QNode(circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# Given an arbitrary quantum circuit, it is usually hard to clearly understand what is really +# happening. To obtain a better picture, we can shuffle the commuting operations to better distinguish +# between groups of single-qubit and two-qubit gates. We can easily do so by applying the +# :func:`~pennylane.transforms.commute_controlled` transform, which pushes all single-qubit gates +# towards a ``direction`` (defaults to the right). +# + +commuted_circuit = qml.transforms.commute_controlled(circuit, direction="right") + +qnode = qml.QNode(commuted_circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# With this rearrangement, we can clearly identify a few operations that can be merged together. +# For instance, the two consecutive CNOT gates from the third to the second qubits will cancel each other +# out. We can remove these with the :func:`~pennylane.transforms.cancel_inverses` +# transform, which removes consecutive inverse operations. +# + +cancelled_circuit = qml.transforms.cancel_inverses(commuted_circuit) + + +qnode = qml.QNode(cancelled_circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# Consecutive rotations along the same axis can also be merged. For example, we can combine the two +# :class:`~pennylane.RX` rotations on the first qubit into a single one with the sum of the angles. +# Additionally, the two :class:`~pennylane.RZ` rotations on the third qubit will cancel each other. +# We can combine these rotations with the :func:`~pennylane.transforms.merge_rotations` transform. +# We can choose which rotations to merge with ``include_gates`` (defaults to ``None``, meaning all). +# Furthermore, the merged rotations with a resulting angle lower than ``atol`` are directly removed. +# + +merged_circuit = qml.transforms.merge_rotations(cancelled_circuit, atol=1e-8, include_gates=None) + + +qnode = qml.QNode(merged_circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# Combining these simple circuit transforms, we have reduced the complexity of our original circuit. +# This will make it faster to execute and less prone to errors. However, there is still room for +# improvement. Let's take it a step further in the following section! +# +# As a final remark, we can directly apply the transforms to our quantum function when we define it +# using their decorator forms (beware the reverse order!). +# + + +@qml.qnode(dev) +@partial(qml.transforms.merge_rotations, atol=1e-8, include_gates=None) +@qml.transforms.cancel_inverses +@partial(qml.transforms.commute_controlled, direction="right") +def q_fun(angles): + qml.Hadamard(wires=1) + qml.Hadamard(wires=2) + qml.RX(angles[0], 0) + qml.CNOT(wires=[1, 0]) + qml.CNOT(wires=[2, 1]) + qml.RX(angles[2], wires=0) + qml.RZ(angles[1], wires=2) + qml.CNOT(wires=[2, 1]) + qml.RZ(-angles[1], wires=2) + qml.CNOT(wires=[1, 0]) + qml.Hadamard(wires=1) + qml.CY(wires=[1, 2]) + qml.CNOT(wires=[1, 0]) + return qml.expval(qml.PauliZ(wires=0)) + + +qml.draw_mpl(q_fun, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# Circuit compilation +# ------------------- +# +# Rearranging and combining operations is an essential part of circuit compilation. Indeed, it is +# usually performed repeatedly as the compiler does multiple *passes* over the circuit. During every +# pass, the compiler applies a series of circuit transforms to obtain better and better circuit +# representations. +# +# We can apply all the transforms introduced above with the +# :func:`~pennylane.compile` function, which yields the same final circuit. +# + +compiled_circuit = qml.compile(circuit) + +qnode = qml.QNode(compiled_circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# In the resulting circuit, we can identify further operations that can be combined, such as the +# consecutive CNOT gates applied from the second to the first qubit. To do so, we would simply need to +# apply the same transforms again in a second pass of the compiler. We can adjust the desired number +# of passes with ``num_passes``. +# +# Let us see the resulting circuit with two passes. +# + +compiled_circuit = qml.compile(circuit, num_passes=2) + +qnode = qml.QNode(compiled_circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# This can be further simplified with an additional pass of the compiler. In this case, we also define +# explicitly the transforms to be applied by the compiler with the ``pipeline`` argument. This allows +# us to control the transforms, their parameters, and the order in which they are applied. For example, +# let's apply the same transforms in a different order, shift the single-qubit gates towards the +# opposite direction, and only merge :class:`~pennylane.RZ` rotations. +# + +compiled_circuit = qml.compile( + circuit, + pipeline=[ + partial(qml.transforms.commute_controlled, direction="left"), # Opposite direction + partial(qml.transforms.merge_rotations, include_gates=["RZ"]), # Different threshold + qml.transforms.cancel_inverses, # Cancel inverses after rotations + ], + num_passes=3, +) + +qnode = qml.QNode(compiled_circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# Notice how the :class:`~pennylane.RX` gates on the first qubit have been pushed towards the left +# and they have not been merged, unlike in the previous cases. +# +# Finally, we can specify a finite basis of gates to describe the circuit by providing a ``basis_set``. +# For example, suppose we wish to run our circuit on a device that can only implement single-qubit +# rotations and CNOT operations. In this case, the compiler will need to decompose the gates +# in terms of our basis, then apply the transforms. +# + +compiled_circuit = qml.compile(circuit, basis_set=["CNOT", "RX", "RY", "RZ"], num_passes=2) + +qnode = qml.QNode(compiled_circuit, dev) +qml.draw_mpl(qnode, decimals=1, style="sketch")(angles) +plt.show() + +###################################################################### +# We can see how the Hadamard and control-Y gates have been decomposed into a series of single-qubit +# rotations and CNOT gates. We're ready to run our circuit on the quantum device. Great job! +# +# Conclusion +# ---------- +# +# In this tutorial, we have learned the basic principles of the compilation of quantum circuits. +# Combining simple circuit transforms that are applied repeatedly during each pass of the compiler, we can +# significantly reduce the complexity of our quantum circuits. +# +# To continue learning, you can explore other circuit transformations present in the PennyLane +# :mod:`~pennylane.transforms` module. Furthermore, you can even learn how to create your own in +# `this blogpost `__. +# diff --git a/demonstrations_v2/tutorial_circuit_compilation/metadata.json b/demonstrations_v2/tutorial_circuit_compilation/metadata.json new file mode 100644 index 0000000000..761bad1daa --- /dev/null +++ b/demonstrations_v2/tutorial_circuit_compilation/metadata.json @@ -0,0 +1,37 @@ +{ + "title": "Compilation of quantum circuits", + "authors": [ + { + "username": "brequena" + } + ], + "dateOfPublication": "2023-06-14T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/circuit_compilation/thumbnail_tutorial_circuit_compilation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_circuit_compilation.png" + } + ], + "seoDescription": "Learn about circuit transformations and quantum circuit compilation with PennyLane", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_circuit_cutting", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_circuit_compilation/requirements.in b/demonstrations_v2/tutorial_circuit_compilation/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_circuit_compilation/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_classical_expval_estimation/demo.py b/demonstrations_v2/tutorial_classical_expval_estimation/demo.py new file mode 100644 index 0000000000..ecd47004fb --- /dev/null +++ b/demonstrations_v2/tutorial_classical_expval_estimation/demo.py @@ -0,0 +1,568 @@ +r""" +Classically estimating expectation values from parametrized quantum circuits +============================================================================ + +In the race between classical and `quantum computing `__, an important question +is whether there exist efficient classical algorithms to simulate quantum +circuits. +Probably the most widely-known result of this type is the Gottesman–Knill +theorem [#gottesman]_. It states that quantum circuits consisting of Clifford +gates alone can be simulated classically, provided that the initial state +of the circuit is "nice enough" (also see the :doc:`PennyLane Demo on Clifford simulation +`). + +In this demo we will showcase a new result on classical simulation of quantum +circuits at a glance! For this, we will learn about *Pauli propagation*, how to +truncate it, and how it results in an efficient classical algorithm for +estimating expectation values of parametrized quantum circuits (on average +across parameter settings). +We will implement a basic variant in PennyLane, and discuss limitations and the +important details that lie at the heart of the new preprint called +*Classically estimating observables of noiseless quantum circuits* +by Angrisani et al. [#angrisani]_. + +This result is important, as it casts doubt on the usefulness of generic parametrized quantum +circuits in quantum computations. Read on if you are wondering whether this +preprint just dequantized your work! + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_classical_expval_estimation.png + :align: center + :width: 60% + :target: javascript:void(0) + +Our target: Estimating expectation values from quantum circuits +--------------------------------------------------------------- + +Let's start by looking at the type of quantum computations that we want to +simulate classically. +Given an initial state :math:`|\psi_0\rangle,` a parametrized quantum +circuit :math:`U(\theta)`, and an observable :math:`H,` a common task in +many variational quantum algorithms is to compute the expectation value + +.. math:: + + E(\theta) = \langle \psi_0 | U^\dagger(\theta) H U(\theta) |\psi_0\rangle + +for various parameter settings :math:`\theta.` +Being able to estimate such an expectation value efficiently is required +to train the parametrized quantum circuit in applications such as +:doc:`QAOA `, +the :doc:`variational quantum eigensolver ` and a wide +range of `quantum machine learning `__ tasks. + +For simplicity, we will assume the initial state to be +:math:`|\psi_0\rangle=|0\rangle` throughout. +Similarly, we will work with a particular example for the circuit +:math:`U(\theta)` and discuss the class of :doc:`parametrized circuits ` the algorithm +can tackle further below. + +For our demo, we choose the widely used hardware-efficient ansatz, which alternates +arbitrary single-qubit rotations with layers of entangling gates. +Lastly, the observable :math:`H` can be decomposed into a sum of Pauli words as + +.. math:: + + H = \sum_{\ell=1}^L h_\ell P_\ell, \quad P_\ell \in \{I, X, Y, Z\}^{\otimes N}. + +Here, :math:`N` is the total number of qubits. +The number of qubits on which a Pauli word is supported (i.e., has a non-identity +component) is called its *weight*. +For the algorithm to work, the weights of the Hamiltonian's Pauli words may not +exceed a set threshold, see the truncation step below. +For our example we pick the Heisenberg model Hamiltonian + +.. math:: + + H_{XYZ} = \sum_{j=1}^{N-1} h_j^{(X)} X_j X_{j+1} + h_j^{(Y)} Y_j Y_{j+1} + h_j^{(Z)} Z_j Z_{j+1} + +with random coefficients :math:`h_j^{(X|Y|Z)}.` In :math:`H_{XYZ},` each Pauli word +has weight two. + +We will discuss the type of Hamiltonians that the algorithm can tackle +further below, but for now, let's define the Hamiltonian and the circuit ansatz in code. +We pick a :math:`\operatorname{CNOT}` layer on a ring as entangler for the ansatz. +We transform the ``ansatz`` function with :func:`~.pennylane.transforms.make_tape`, making +the function into one that returns a tape containing the gates (and expectation value). +This also allows us to draw the ansatz easily with :func:`~.pennylane.drawer.tape_text`. +To maintain an overview, we set the number of qubits and layers to just 4 and 3, respectively. +""" + +import pennylane as qml +import numpy as np +from itertools import combinations, product + + +def _ansatz(params, num_qubits, H): + """Parametrized quantum circuit ansatz that alternates arbitrary single-qubit + rotations with strongly entangling CNOT layers. The depth of the ansatz and the + number of qubits are given by the first dimension of the input parameters.""" + + for i, params_layer in enumerate(params): + # Execute arbitrary parametrized single-qubit rotations + for j, params_qubit in enumerate(params_layer): + qml.RZ(params_qubit[0], j) + qml.RY(params_qubit[1], j) + qml.RZ(params_qubit[2], j) + # If we are not in the last layer, execute an entangling CNOT layer + if i < len(params) - 1: + for j in range(num_qubits): + qml.CNOT([j, (j + 1) % num_qubits]) + + return qml.expval(H) + + +ansatz = qml.transforms.make_tape(_ansatz) + +num_qubits = 4 +num_layers = 3 +np.random.seed(852) +H_coeffs = np.random.random((num_qubits - 1) * 3) +H_ops = [op(j) @ op(j + 1) for j in range(num_qubits - 1) for op in [qml.X, qml.Y, qml.Z]] +H = qml.dot(H_coeffs, H_ops) + +# Smaller parameter set to get smaller circuit to draw +params = np.random.random((num_layers, num_qubits, 3)) +tape = ansatz(params, num_qubits, H) +print(qml.drawer.tape_text(tape)) + +############################################################################## +# Now that we have our example set up, let's look at the core technique behind +# the simulation algorithm. +# +# Pauli propagation +# ----------------- +# +# A standard classical simulation technique for quantum circuits is based on +# state vector simulation, which updates the state vector of the quantum system +# with each gate applied to it. From a physics perspective, this is the evolution of +# a quantum state in the Schrödinger picture. To conclude the simulation, this approach +# then contracts the evolved state with the observable :math:`H.` +# +# Here we will use a technique based on the Heisenberg picture, which +# describes the evolution of the measurement observable :math:`H` instead. +# This technique is called *Pauli propagation* and has also been used by +# related simulation algorithms [#aharonov]_, [#lowesa]_, [#begusic]_. +# +# In the Heisenberg picture, each gate :math:`V,` be it parametrized or not, +# acts on the observable via +# +# .. math:: +# +# H' = V^\dagger H V. +# +# The evolved observable can then be contracted with the initial state at the end +# of the simulation. +# +# Pauli propagation tracks the Pauli words :math:`P_\ell` in the Hamiltonian throughout +# this Heisenberg picture evolution, requiring us to only determine how a gate +# :math:`V` acts on any Pauli word. For Clifford gates, including the Hadamard +# gate, :math:`\operatorname{CNOT},` :math:`\operatorname{CZ}` and Pauli gates themselves, any Pauli word is mapped +# to another Pauli word. As a matter of fact, this is a standard way to *define* +# Clifford gates. As an example, consider a :math:`\operatorname{CNOT}` gate acting on the Pauli +# word :math:`Z\otimes Z` in the Heisenberg picture. We can evaluate (note that +# :math:`\operatorname{CNOT}^\dagger=\operatorname{CNOT}`) +# +# .. math:: +# +# \operatorname{CNOT} (Z\otimes Z) \operatorname{CNOT} +# &=(P_0 \otimes \mathbb{I}+P_1\otimes X) (Z\otimes Z) (P_0 \otimes \mathbb{I}+P_1\otimes X)\\ +# &=(Z \otimes \mathbb{I}) (P_0 \otimes Z - P_1 \otimes Z)\\ +# &=\mathbb{I}\otimes Z. +# +# Here we abbreviated the projectors :math:`P_i=|i\rangle\langle i|` and used simple +# operator arithmetic. We can similarly look at the action of :math:`\operatorname{CNOT}` +# on any other two-qubit Pauli word. However, this might get tedious, so let us +# do it in code. + +cnot = qml.CNOT([0, 1]) + +for op0, op1 in product([qml.Identity, qml.X, qml.Y, qml.Z], repeat=2): + original_op = op0(0) @ op1(1) + new_op = cnot @ original_op @ cnot + new_op = qml.pauli_decompose(new_op.matrix()) + print(f"CNOT transformed {original_op} to {new_op}") + +############################################################################## +# This fully specifies the action of :math:`\operatorname{CNOT}` on any Pauli word, +# because any tensor factors that the gate does not act on are simply left unchanged. +# We will use these results as a lookup table for the simulation below, +# so let's store them in a string-based dictionary: + +cnot_table = { + ("I", "I"): (("I", "I"), 1), + ("I", "X"): (("I", "X"), 1), + ("I", "Y"): (("Z", "Y"), 1), + ("I", "Z"): (("Z", "Z"), 1), + ("X", "I"): (("X", "X"), 1), + ("X", "X"): (("X", "I"), 1), + ("X", "Y"): (("Y", "Z"), 1), + ("X", "Z"): (("Y", "Y"), -1), + ("Y", "I"): (("Y", "X"), 1), + ("Y", "X"): (("Y", "I"), 1), + ("Y", "Y"): (("X", "Z"), -1), + ("Y", "Z"): (("X", "Y"), 1), + ("Z", "I"): (("Z", "I"), 1), + ("Z", "X"): (("Z", "X"), 1), + ("Z", "Y"): (("I", "Y"), 1), + ("Z", "Z"): (("I", "Z"), 1), +} + + +############################################################################## +# Now, on to some non-Clifford gates, namely Pauli rotation gates. They +# have the important property of mapping a Pauli word to two Pauli words whenever +# the rotation generator and the transformed Pauli word do not commute. +# +# As an example, we can compute the action of :class:`~.pennylane.RZ` on the Pauli +# :class:`~.pennylane.X` operator: +# +# .. math:: +# +# R_Z^\dagger(\theta) X R_Z(\theta) +# &= (\cos(\theta/2) \mathbb{I} + i\sin(\theta/2) Z) X (\cos(\theta/2) \mathbb{I} - i\sin(\theta/2) Z)\\ +# &= (\cos(\theta/2)^2 - \sin(\theta/2)^2) X + i\sin(\theta/2)\cos(\theta/2)[Z, X]\\ +# &= \cos(\theta) X - \sin(\theta) Y. +# +# Here we used trigonometric identities and :math:`[Z, X]=2iY` in the last step. +# Again, the tensor factors of the Pauli word on which the rotation does not act is +# left unchanged. +# And if the rotation generator commutes with the transformed Pauli word, the +# gate will of course leave the word unchanged. +# +# Great, now that we know how a :math:`\operatorname{CNOT}` gate and single-qubit +# Pauli rotations act on a Pauli word, we will be able to tackle our example +# circuit. +# +# But before we move on to implementing it, there is still a *crucial* point missing! +# +# Truncating the Pauli propagation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Pauli propagation is a neat way to approach the task of estimating expectation +# values that we are after, like :math:`E(\theta)`\ . However, if we were to run +# a classical simulation using this approach as is, we would quickly run into +# a scaling problem. There are :math:`4^N` Pauli words on :math:`N` qubits, an +# unfeasibly large number if we hit all of them during our simulation. +# +# Both existing algorithms and the new work [#angrisani]_ therefore use +# truncation methods to keep the number of Pauli words that need to be tracked +# below a reasonable threshold. +# The algorithm we discuss here does this based on the weight of the tracked Pauli +# words. For a chosen threshold :math:`k,` it simply discards all Pauli words with +# non-trivial tensor factors on more than :math:`k` qubits. +# +# This is clearly an approximation, and in principle we could introduce a large +# error in this truncation. However, Angrisani et al. show that this is not the case +# for a wide class of parametrized circuits at most parameter settings. +# +# As anticipated above, the truncation step requires the Pauli words of the initial :math:`H` +# to be at most :math:`k`-local, as they get truncated away otherwise. Alternatively, +# non-local terms need to make a negligible contribution to the expectation value +# in order for the truncation to be a good approximation. +# +# So let's move on to implementing truncated Pauli propagation technique! +# We will make use of :class:`~.pennylane.pauli.PauliWord` and +# :class:`~.pennylane.pauli.PauliSentence` objects that allow us to handle the +# observable easily. Let's start with two functions that implement a +# single-qubit rotation and a :math:`\operatorname{CNOT}` gate in the Heisenberg +# picture, respectively. Note that single-qubit rotation gates do not +# require us to implement truncation, because they never increase the weight of +# a Pauli word. +# + +from pennylane.pauli import PauliWord, PauliSentence + + +def apply_cnot(wires, H, k=None): + """Apply a CNOT gate on given wires to operator H in the Heisenberg picture. + Truncate all Pauli words in the transformed operator that have weight larger than k.""" + new_H = PauliSentence() + for pauli_word, coeff in H.items(): + # Extract the Pauli tensor factors on the wires of the CNOT + op_pw_0 = pauli_word.get(wires[0], "I") + op_pw_1 = pauli_word.get(wires[1], "I") + # Look up the prefactor and new Pauli tensor factors in our lookup table + (new_op_pw_0, new_op_pw_1), factor = cnot_table[(op_pw_0, op_pw_1)] + # Create new Pauli word from old one and update it with new tensor factors + new_pw = pauli_word.copy() + new_pw.update({wires[0]: new_op_pw_0, wires[1]: new_op_pw_1}) + new_pw = PauliWord(new_pw) + + # Truncation: Only add to the new H if the new Pauli word is small enough + if (k is None) or len(new_pw) <= k: + new_H[new_pw] += factor * coeff + + return new_H + + +def apply_single_qubit_rot(pauli, wire, param, H): + """Apply a single-qubit rotation about the given ``pauli`` on the given ``wire`` + by a rotation angle ``param`` to an operator ``H``.""" + new_H = PauliSentence() + rot_pauli_word = PauliWord({wire: pauli}) + for pauli_word, coeff in H.items(): + if pauli_word.commutes_with(rot_pauli_word): + # Rotation generator commutes with Pauli word from H, the word is unchanged + new_H[pauli_word] += coeff + else: + # Rotation generator does not commute with Pauli word from H; + # Multiply old coefficient by cosine, and add new term with modified Pauli word + new_H[pauli_word] += qml.math.cos(param) * coeff + new_pauli_word, factor = list((rot_pauli_word @ pauli_word).items())[0] + new_H[new_pauli_word] += (qml.math.sin(param) * coeff * factor * 1j).real + + return new_H + + +############################################################################## +# Completing the simulation with the initial state +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# With those two essential functions implemented, we're almost ready to put the algorithm together. +# Before doing so, we need a function that computes the expectation value of the evolved observable +# with respect to the initial state :math:`|0\rangle.` This is simple, though, because we know for +# each Pauli word :math:`P_\ell` in the Hamiltonian that it will contribute its coefficient :math:`h_\ell` +# to the expectation value if (and only if) all tensor factors are :math:`I` or :math:`Z.` + + +def initial_state_expval(H): + """Compute the expectation value of an operator ``H`` in the state |0>.""" + expval = 0.0 + for pauli_word, coeff in H.items(): + if all(pauli in {"I", "Z"} for pauli in pauli_word.values()): + expval += coeff + return expval + + +############################################################################## +# Putting the pieces together +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now let's combine everything into a function that can handle the tape of our quantum +# circuit from the beginning. It simply extracts the measurement observable and then +# goes through the circuit backwards, propagating the Pauli words of the observable +# according to the Heisenberg picture. Finally, it evaluates the expectation value +# with respect to :math:`|0\rangle.` The truncation threshold :math:`k` for ``apply_cnot`` +# is a hyperparameter of the execution function. + + +def execute_tape(tape, k=None): + """Classically simulate a tape and estimate the expectation value + of its output observable using truncated Pauli propagation.""" + H = tape.measurements[0].obs.pauli_rep + for op in reversed(tape.operations): + if isinstance(op, qml.CNOT): + # Apply CNOT + H = apply_cnot(op.wires, H, k=k) + elif isinstance(op, (qml.RZ, qml.RX, qml.RY)): + # Extract the Pauli rotation generator, wire, and parameter from the gate + pauli = op.name[-1] + wire = op.wires[0] + param = op.data[0] + H = apply_single_qubit_rot(pauli, wire, param, H) + else: + raise NotImplementedError + + return initial_state_expval(H) + + +############################################################################## +# Great! So let's run it on our circuit, but now on 25 qubits and with 5 layers, +# and compare the result to the exact value from PennyLane's `fast statevector simulator, +# Lightning Qubit `__. +# We also set the truncation threshold to :math:`k=8`\ . + +num_qubits = 25 +num_layers = 5 +k = 7 +H_coeffs = np.random.random((num_qubits - 1) * 3) +H_ops = [op(j) @ op(j + 1) for j in range(num_qubits - 1) for op in [qml.X, qml.Y, qml.Z]] +H = qml.dot(H_coeffs, H_ops) +params = np.random.random((num_layers, num_qubits, 3)) + + +def run_estimate(params, H): + tape = ansatz(params, num_qubits, H) + expval = execute_tape(tape, k=k) + return expval + + +expval = run_estimate(params, H) + + +@qml.qnode(qml.device("lightning.qubit", wires=num_qubits)) +def run_lightning(params, H): + return _ansatz(params, num_qubits, H) + + +exact_expval = run_lightning(params, H) + +print(f"Expectation value estimated by truncated Pauli propagation: {expval:.6f}") +print(f"Numerically exact expectation value: {exact_expval:.6f}") + + +############################################################################## +# Wonderful, we have a working approximate simulation of the circuit that is scalable (for +# fixed :math:`k`) that estimates the expectation value of :math:`H!` +# Note that a single estimate neither is a proof that the algorithm works in general, +# nor is it the subject of the main results by Angrisani et al. +# However, a full-fledged benchmark goes beyond this demo. +# +# Fine print: Which circuits can be simulated? +# -------------------------------------------- +# +# As anticipated multiple times throughout the demo, we now want to consider some details on +# the circuits for which the truncated Pauli propagation is guaranteed to work. +# +# It's a statistical thing +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# First, it is very important to note that the guarantee of good approximations does not +# apply to any single instance of a quantum circuit, but to the full parametrized *family* +# defined by a circuit ansatz, together with a probability distribution to pick the parameters. +# The guarantee then is that the approximation error of truncated Pauli propagation +# *on average across the sampled parameter settings* can be suppressed exponentially by +# increasing the truncation threshold :math:`k.` +# +# This can be rephrased as follows: the probability of obtaining an error larger than some +# tolerance can be suppressed exponentially by increasing :math:`k`\ . +# Note that this does not prevent the simulation +# algorithm to be *very* wrong at some rare parameter settings! +# +# The circuit needs to be locally scrambling +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Second, there is a requirement for the structure and gates of the parametrized circuit: +# We need to be able to divide the circuit into layers such that each layer, together with +# the distribution of parameters we consider for the parametrized family, does not change under +# random single-qubit rotations. The circuit and its parameter distribution are said to be +# *locally scrambling* in this case. +# +# This may sound complicated, but is easy to understand for a +# small example: Consider an arbitrary rotation :class:`~.pennylane.Rot` on a single qubit, +# together with a distribution for its three angles that leads to Haar random rotations +# (see the :doc:`PennyLane Demo on the Haar measure ` for details). +# This parametrized rotation then is unchanged if we apply another Haar random rotation! +# That is, even though an individual rotation does get modified, the *distribution* of rotations +# remains the same (an important property of the Haar measure!). +# +# For our purposes it is sufficient +# to note that the hardware-efficient layers from above do indeed satisfy this requirement. +# Please take a look at the original paper for further details [#angrisani]_, in particular +# Sections II and VIII and Appendix A. +# +# Scrambling layers need to be shallow +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Third, the parametrized circuit may not "branch too much." That is, if a Pauli word has +# weight :math:`r,` none of the locally scrambling layers of the circuit +# may produce more than :math:`n^r` different Pauli words under the Heisenberg evolution. +# Intuitively, this requirement limits the layers of the circuit to be sufficiently shallow. +# +# For our hardware-efficient circuit, we can bound this amount of branching directly: +# Each :math:`\operatorname{CNOT}` in the entangling layer can at most double the weight of the Pauli word, e.g., +# if each :math:`\operatorname{CNOT}` gate hits a :math:`Z` on its target qubit. +# Afterwards, each single-qubit rotation can create all three Pauli operators on each +# qubit in the support of the enlarged Pauli word, leading to a factor of three. +# Taken together, a Pauli word with weight :math:`r` is transformed into at most +# :math:`3^{2r}=9^r` Pauli words with weights at most :math:`2r.` The requirement +# of not "branching too much" therefore is satisfied, because :math:`9^r`__), +# but does not help the truncated Pauli propagation. + +num_qubits = 15 +num_layers = 3 +H_coeffs = np.random.random((num_qubits - 1) * 3) +H_ops = [op(j) @ op(j + 1) for j in range(num_qubits - 1) for op in [qml.X, qml.Y, qml.Z]] +H = qml.dot(H_coeffs, H_ops) +specific_params = np.ones((num_layers, num_qubits, 3)) * np.pi / 4 + +expval = run_estimate(specific_params, H) +exact_expval = run_lightning(specific_params, H) + +print(f"Expectation value estimated by truncated Pauli propagation: {expval:.6f}") +print(f"Numerically exact expectation value: {exact_expval:.6f}") + +############################################################################## +# As we can see, the estimation error became quite large, although we reduced the +# qubit and layer count while keeping :math:`k` fixed. +# This is a manifestation of the statement that the algorithm only will estimate expectation +# values successfully *on average* over the parameter domain. +# +# Conclusion +# ---------- +# We learned about Pauli propagation, how to truncate it, and how it results in an efficient +# classical algorithm that estimates expectation values of parametrized quantum circuits. +# This result is important, as it casts doubt on the usefulness of generic parametrized quantum +# circuits in quantum computations. Instead of such generic circuits, we will need to make +# use of specialized circuit architectures and smart parametrization and initialization +# techniques if we want to employ parametrized circuits in a useful manner. +# +# Besides the caveat that the approximation is only guaranteed across the full distribution +# of parameters, it is important to note that expectation value estimation is not the only +# interesting task on a quantum computer. +# Similar to the result discussed here, there already exist other classes of quantum circuits +# for which this estimation task is easy, but sampling from the quantum state prepared by +# the circuit is hard. +# One prominent example are so-called instantaneous quantum polynomial-time (IQP) circuits +# [#bremner]_, [#bremner2]_, which arise in the context of :doc:`Boson sampling `. +# +# Finally, it is important to note that while truncated Pauli propagation scales +# polynomially with the qubit count, the exponent of this scaling contains :math:`k,` +# which still can lead to impractical computational cost, e.g., for deep circuits. +# +# References +# ---------- +# +# .. [#gottesman] +# +# Daniel Gottesman +# "The Heisenberg Representation of Quantum Computers" +# `arXiv:quant-ph/9807006 `__, 1998. +# +# .. [#angrisani] +# +# Armando Angrisani, Alexander Schmidhuber, Manuel S. Rudolph, M. Cerezo, Zoë Holmes, Hsin-Yuan Huang +# "Classically estimating observables of noiseless quantum circuits" +# `arXiv:2409.01706 `__, 2024. +# +# .. [#aharonov] +# +# Dorit Aharonov, Xun Gao, Zeph Landau, Yunchao Liu, Umesh Vazirani +# "A polynomial-time classical algorithm for noisy random circuit sampling" +# `arXiv:2211.03999 `__, 2022. +# +# .. [#lowesa] +# +# Manuel S. Rudolph, Enrico Fontana, Zoë Holmes, Lukasz Cincio +# "Classical surrogate simulation of quantum systems with LOWESA" +# `arXiv:2308.09109 `__, 2023. +# +# .. [#begusic] +# +# Tomislav Begušić, Johnnie Gray, Garnet Kin-Lic Chan +# "Fast and converged classical simulations of evidence for the utility of quantum computing before fault tolerance" +# `arXiv:2308.05077 `__, 2023. +# +# .. [#bremner] +# +# M. Bremner, R. Jozsa, D. Shepherd. +# "Classical simulation of commuting quantum computations implies collapse of the polynomial hierarchy." +# `arXiv:1005.1407 `__, 2010. +# +# .. [#bremner2] +# +# Michael J. Bremner, Ashley Montanaro, Dan J. Shepherd +# "Achieving quantum supremacy with sparse and noisy commuting quantum computations." +# `arXiv:1610.01808 `__, 2016. +# diff --git a/demonstrations_v2/tutorial_classical_expval_estimation/metadata.json b/demonstrations_v2/tutorial_classical_expval_estimation/metadata.json new file mode 100644 index 0000000000..799204178c --- /dev/null +++ b/demonstrations_v2/tutorial_classical_expval_estimation/metadata.json @@ -0,0 +1,89 @@ +{ + "title": "Classically estimating expectation values from parametrized quantum circuits", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-09-10T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classical_expval_estimation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_classical_expval_estimation.png" + } + ], + "seoDescription": "Estimate expectation values from parametrized quantum circuits efficiently on average with truncated Pauli propagation.", + "doi": "", + "references": [ + { + "id": "gottesman", + "type": "article", + "title": "The Heisenberg Representation of Quantum Computers.", + "authors": "D. Gottesman", + "year": "1998", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/9807006" + }, + { + "id": "angrisani", + "type": "article", + "title": "Classically estimating observables of noiseless quantum circuits.", + "authors": "Armando Angrisani, Alexander Schmidhuber, Manuel S. Rudolph, M. Cerezo, Zo\u00eb Holmes, Hsin-Yuan Huang", + "year": "2024", + "journal": "", + "url": "https://arxiv.org/abs/2409.01706" + }, + { + "id": "aharonov", + "type": "article", + "title": "A polynomial-time classical algorithm for noisy random circuit sampling.", + "authors": "Dorit Aharonov, Xun Gao, Zeph Landau, Yunchao Liu, Umesh Vazirani", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2211.03999" + }, + { + "id": "lowesa", + "type": "article", + "title": "Classical surrogate simulation of quantum systems with LOWESA.", + "authors": "Manuel S. Rudolph, Enrico Fontana, Zo\u00eb Holmes, Lukasz Cincio", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2308.09109" + }, + { + "id": "begusic", + "type": "article", + "title": "Fast and converged classical simulations of evidence for the utility of quantum computing before fault tolerance.", + "authors": "Tomislav Begu\u0161i\u0107, Johnnie Gray, Garnet Kin-Lic Chan", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2308.05077" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2409.01706" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_clifford_circuit_simulations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_classical_expval_estimation/requirements.in b/demonstrations_v2/tutorial_classical_expval_estimation/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_classical_expval_estimation/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_classical_kernels/demo.py b/demonstrations_v2/tutorial_classical_kernels/demo.py new file mode 100644 index 0000000000..172ce234b6 --- /dev/null +++ b/demonstrations_v2/tutorial_classical_kernels/demo.py @@ -0,0 +1,769 @@ +r""" + +.. _classical_kernels: + +Approximating a classical kernel with a quantum computer +============================================================= + +.. meta:: + :property="og:description": Finding a QK to approximate the Gaussian kernel. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/toy_qek.png + +.. related:: + + tutorial_kernels_module Training and evaluating quantum kernels + tutorial_kernel_based_training Kernel-based training of quantum models with + scikit-learn + tutorial_expressivity_fourier_series Quantum models as Fourier series + + +*Author: Elies Gil-Fuster (Xanadu Resident) — Posted: 01 Mar 2022. Last updated: 02 March 2022* + +Forget about advantages, supremacies, or speed-ups. +Let us understand better what we can and cannot do with a quantum computer. +More specifically, in this demo, we want to look into quantum kernels and ask +whether we can replicate classical kernel functions with a quantum computer. +Lots of researchers have lengthily stared at the opposite question, namely that +of classical simulation of quantum algorithms. +Yet, by studying what classes of functions we can realize with quantum kernels, +we can gain some insight into their inner workings. + +Usually, in quantum machine learning (QML), we use parametrized quantum circuits +(PQCs) to find good functions, whatever *good* means here. +Since kernels are just one specific kind of well-defined +functions, the task of finding a quantum kernel (QK) that approximates a given +classical one could be posed as an optimization problem. +One way to attack this task is to define a loss function +quantifying the distance between both functions (the classical kernel function +and the PQC-based hypothesis). +This sort of approach does not help us much to gain theoretical insights about the +structure of kernel-emulating quantum circuits, though. + +In order to build intuition, we will instead study the link between classical and quantum kernels through the +lens of the Fourier representation of a kernel, which is a common tool in +classical machine learning. +Two functions can only have the same Fourier spectrum if they are the same +function. It turns out that, for certain classes of quantum circuits, `we can +theoretically describe the Fourier spectrum rather well +`_. + +Using this theory, together with some good old-fashioned convex optimization, we +will derive a quantum circuit that approximates the famous Gaussian kernel. + +In order to keep the demo short and sweet, we focus on one simple example. The +same ideas apply to more general scenarios. +Also, Refs. [#QEK]_, [#Fourier]_, and [#qkernels]_ should be helpful for those +who'd like to see the underlying theory of QKs (and also so-called *Quantum Embedding +Kernels*) and their Fourier representation. +So tag along if you'd like to see how we build a quantum kernel that +approximates the well-known Gaussian kernel function! + +| + +.. figure:: ../_static/demonstration_assets/classical_kernels/classical_kernels_flow_chart.png + :align: center + :width: 60% + :target: javascript:void(0) + + Schematic of the steps covered in this demo. + + + +Kernel-based Machine Learning +---------------------------------------------- + +We will not be reviewing all the notions of kernels in-depth here. +Instead, we only need to know that an entire branch of machine learning +revolves around some functions we call kernels. +If you'd like to learn more about where these functions +come from, why they're important, and how we can use them (e.g. with +PennyLane), check out the following demos, which cover different +aspects extensively: + +#. `Training and evaluating quantum kernels `_ +#. `Kernel-based training of quantum models with scikit-learn `_ + +For the purposes of this demo, a *kernel* is a real-valued function of two +variables :math:`k(x_1,x_2)` from a given data domain :math:`x_1, +x_2\in\mathcal{X}`. +In this demo, we'll deal with real vector spaces as the data +domain :math:`\mathcal{X}\subseteq\mathbb{R}^d,` of some dimension :math:`d.` +A kernel has to be symmetric with respect to exchanging both variables +:math:`k(x_1,x_2) = k(x_2,x_1).` +We also enforce kernels to be positive semi-definite, but let's avoid getting +lost in mathematical lingo. You can trust that all kernels featured in this +demo are positive semi-definite. + +Shift-invariant kernels +--------------------------------------------- + +Some kernels fulfill another important restriction, called *shift-invariance*. +Shift-invariant kernels are those whose value doesn't change if we add a shift +to both inputs. +Explicitly, for any suitable shift vector :math:`\zeta\in\mathcal{X},` +shift-invariant kernels are those for which +:math:`k(x_1+\zeta,x_2+\zeta)=k(x_1,x_2)` holds. +Having this property means the function can be written in +terms of only one variable, which we call the *lag vector* +:math:`\delta:=x_1-x_2\in\mathcal{X}.` Abusing notation a bit: + +.. math:: k(x_1,x_2)=k(x_1-x_2,0) = k(\delta). + +For shift-invariant kernels, the exchange symmetry property +:math:`k(x_1,x_2)=k(x_2,x_1)` translates into reflection symmetry +:math:`k(\delta)=k(-\delta).` +Accordingly, we say :math:`k` is an *even function*. + +Warm up: Implementing the Gaussian kernel +------------------------------------------- + +First, let's introduce a simple classical kernel that we will +approximate on the quantum computer. +Start importing the usual suspects: +""" + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt +import math +np.random.seed(53173) + +############################################################################### +# We'll look at the Gaussian kernel: +# :math:`k_\sigma(x_1,x_2):=e^{-\lVert x_1-x_2\rVert^2/2\sigma^2}.` +# This function is clearly shift-invariant: +# +# .. math:: +# +# k_\sigma(x_1+\zeta,x_2+\zeta) &= e^{-\lVert(x_1+\zeta)-(x_2+\zeta)\rVert^2/2\sigma^2} \\ +# & = e^{-\lVert x_1-x_2\rVert^2/2\sigma^2} \\ +# & = k_\sigma(x_1,x_2). +# +# The object of our study will be a simple version of the Gaussian kernel, +# where we consider :math:`1`-dimensional data, so :math:`\lVert +# x_1-x_2\rVert^2=(x_1-x_2)^2`. +# Also, we take :math:`\sigma=1/\sqrt{2}` so that we further simplify the +# exponent. +# We can always re-introduce it later by rescaling the data. +# Again, we can write the function in terms of the lag vector only: +# +# .. math:: k(\delta)=e^{-\delta^2}. +# +# Now let's write a few lines to plot the Gaussian kernel: + +def gaussian_kernel(delta): + return math.exp(-delta ** 2) + +def make_data(n_samples, lower=-np.pi, higher=np.pi): + x = np.linspace(lower, higher, n_samples) + y = np.array([gaussian_kernel(x_) for x_ in x]) + return x,y + +X, Y_gaussian = make_data(100) + +plt.plot(X, Y_gaussian) +plt.suptitle("The Gaussian kernel with $\sigma=1/\sqrt{2}$") +plt.xlabel("$\delta$") +plt.ylabel("$k(\delta)$") +plt.show() + +############################################################################### +# In this demo, we will consider only this one example. However, the arguments we +# make and the code we use are also amenable to any kernel with the following +# mild restrictions: +# +# #. Shift-invariance +# #. Normalization :math:`k(0)=1.` +# #. Smoothness (in the sense of a quickly decaying Fourier spectrum). +# +# Note that is a very large class of kernels! +# And also an important one for practical applications. +# +# Fourier analysis of the Gaussian kernel +# ------------------------------------------- +# +# The next step will be to find the Fourier spectrum of the Gaussian +# kernel, which is an easy problem for classical computers. +# Once we've found it, we'll build a QK that produces a finite Fourier series +# approximation to that spectrum. +# +# Let's briefly recall that a Fourier series is the representation of a +# periodic function using the sine and cosine functions. +# Fourier analysis tells us that we can write any given periodic function as +# +# .. math:: f(x) = a_0 + \sum_{n=1}^\infty a_n\cos(n\omega_0x) + b_n\sin(n\omega_0x). +# +# For that, we only need to find the suitable base frequency :math:`\omega_0` +# and coefficients :math:`a_0, a_1, \ldots, b_0, b_1,\ldots.` +# +# But the Gaussian kernel is an aperiodic function, whereas the Fourier series +# only makes sense for periodic functions! +# +# *What can we do?!* +# +# We can cook up a periodic extension to the Gaussian kernel, for a given +# period :math:`2L` (we take :math:`L=\pi` as default): + +def Gauss_p(x, L=np.pi): + # Send x to x_mod in the period around 0 + x_mod = np.mod(x+L, 2*L) - L + return gaussian_kernel(x_mod) + +############################################################################### +# which we can now plot + +x_func = np.linspace(-10, 10, 321) +y_func = [Gauss_p(x) for x in x_func] + +plt.plot(x_func, y_func) +plt.xlabel("$\delta$") +plt.suptitle("Periodic extension to the Gaussian kernel") +plt.show() + +################################################################################## +# In practice, we would construct several periodic extensions of the aperiodic +# function, with increasing periods. +# This way, we can study the behaviour when the period approaches +# infinity, i.e. the regime where the function stops being periodic. +# +# Next up, how does the Fourier spectrum of such an object look like? +# We can find out using PennyLane's ``fourier`` module! + +from pennylane.fourier import coefficients + +################################################################################## +# The function ``coefficients`` computes for us the coefficients of the Fourier +# series up to a fixed term. +# One tiny detail here: ``coefficients`` returns one complex number :math:`c_n` +# for each frequency :math:`n.` +# The real part corresponds to the :math:`a_n` coefficient, and the imaginary +# part to the :math:`b_n` coefficient: :math:`c_n=a_n+ib_n.` +# Because the Gaussian kernel is an even function, we know that the imaginary part +# of every coefficient will be zero, so :math:`c_n=a_n.` + +def fourier_p(d): + """ + We only take the first d coefficients [:d] + because coefficients() treats the negative frequencies + as different from the positive ones. + For real functions, they are the same. + """ + return np.real(coefficients(Gauss_p, 1, d-1)[:d]) + +################################################################################## +# We are restricted to considering only a finite number of Fourier +# terms. +# But isn't that problematic, one may say? +# Well, maybe. +# Since we know the Gaussian kernel is a smooth function, we expect that the +# coefficients converge to :math:`0` at some point, and we will only need to +# consider terms up to this point. +# Let's look at the coefficients we obtain by setting a low value for the +# number of coefficients and then slowly letting it grow: + +N = [0] +for n in range(2,7): + N.append(n) + F = fourier_p(n) + plt.plot(N, F, 'x', label='{}'.format(n)) + +plt.legend() +plt.xlabel("frequency $n$") +plt.ylabel("Fourier coefficient $c_n$") +plt.show() + +################################################################################## +# What do we see? +# For very small coefficient counts, like :math:`2` and :math:`3,` we see that +# the last allowed coefficient is still far from :math:`0.` +# That's a very clear indicator that we need to consider more frequencies. +# At the same time, it seems like starting at :math:`5` or :math:`6` all the +# non-zero contributions have already been well captured. +# This is important for us, since it tells us the minimum number of qubits we should use. +# One can see that every new qubit doubles the number of frequencies we can +# use, so for :math:`n` qubits, we will have :math:`2^n.` +# At minimum of :math:`6` frequencies means at least :math:`3` qubits, corresponding +# to :math:`2^3=8` frequencies. +# As we'll see later, we'll work with :math:`5` qubits, so :math:`32` +# frequencies. +# That means the spectrum we will be trying to replicate will be the following: + +plt.plot(range(32), fourier_p(32), 'x') +plt.xlabel("frequency $n$") +plt.ylabel("Fourier coefficient $c_n$") +plt.suptitle("Fourier spectrum of the Gaussian kernel") +plt.show() + +################################################################################## +# We just need a QK with the same Fourier spectrum! +# +# Designing a suitable QK +# -------------------------- +# +# Designing a suitable QK amounts to designing a suitable parametrized quantum +# circuit. +# Let's take a moment to refresh the big scheme of things with the following +# picture: +# +# | +# +# .. figure:: ../_static/demonstration_assets/classical_kernels/QEK.jpg +# :align: center +# :width: 70% +# :target: javascript:void(0) +# +# The quantum kernel considered in this demo. +# +# We construct the quantum kernel from a quantum embedding (see the demo on +# `Quantum Embedding Kernels `_). +# The quantum embedding circuit will consist of two parts. +# The first one, trainable, will be a parametrized general state preparation +# scheme :math:`W_a,` with parameters :math:`a.` +# In the second one, we input the data, denoted by :math:`S(x).` +# +# Start with the non-trainable gate we'll use to encode the data :math:`S(x).` +# It consists of applying one Pauli-:math:`Z` rotation to each qubit with +# rotation parameter :math:`x` times some constant :math:`\vartheta_i,` for +# the :math:`i^\text{th}` qubit. + +def S(x, thetas, wires): + for (i, wire) in enumerate(wires): + qml.RZ(thetas[i] * x, wires = [wire]) +############################################################################### +# By setting the ``thetas`` properly, we achieve the integer-valued spectrum, +# as required by the Fourier series expansion of a function of period +# :math:`2\pi:` +# :math:`\{0, 1, \ldots, 2^n-2, 2^n-1\},` for :math:`n` qubits. +# Some math shows that setting :math:`\vartheta_i=2^{n-i},` for +# :math:`\{1,\ldots,n\}` produces the desired outcome. + +def make_thetas(n_wires): + return [2 ** i for i in range(n_wires-1, -1, -1)] + +############################################################################### +# Next, we introduce the only trainable gate we need to make use of. +# Contrary to the usual Ansätze used in supervised and unsupervised learning, +# we use a state preparation template called ``MottonenStatePreparation``. +# This is one option for amplitude encoding already implemented in PennyLane, +# so we don't need to code it ourselves. +# Amplitude encoding is a common way of embedding classical data into a quantum +# system in QML. +# The unitary associated to this template transforms the :math:`\lvert0\rangle` +# state into a state with amplitudes :math:`a=(a_0,a_1,\ldots,a_{2^n-1}),` +# namely :math:`\lvert a\rangle=\sum_j a_j\lvert j\rangle,` provided +# :math:`\lVert a\rVert^2=1.` + +def W(features, wires): + qml.templates.state_preparations.MottonenStatePreparation(features, wires) + +############################################################################### +# With that, we have the feature map onto the Hilbert space of the quantum +# computer: +# +# .. math:: \lvert x_a\rangle = S(x)W_a\lvert0\rangle, +# +# for a given :math:`a,` which we will specify later. +# +# Accordingly, we can build the QK corresponding to this feature map as +# +# .. math:: +# +# k_a(x_1,x_2) &= \lvert\langle0\rvert W_a^\dagger S^\dagger(x_1) +# S(x_2)W_a\lvert0\rangle\rvert^2 \\ +# &= \lvert\langle0\rvert W_a^\dagger S(x_2-x_1) W_a\lvert0\rangle\rvert^2. +# +# In the code below, the variable ``amplitudes`` corresponds to our set :math:`a.` + +def ansatz(x1, x2, thetas, amplitudes, wires): + W(amplitudes, wires) + S(x1 - x2, thetas, wires) + qml.adjoint(W)(amplitudes, wires) + +############################################################################### +# Since this kernel is by construction real-valued, we also have +# +# .. math:: +# +# (k_a(x_1,x_2))^\ast &= k_a(x_1,x_2) \\ +# &= \lvert\langle0\rvert W_a^\dagger S(x_1-x_2) W_a\lvert0\rangle\rvert^2 \\ +# &= k_a(x_2,x_1). +# +# Further, this QK is also shift-invariant :math:`k_a(x_1,x_2) = k_a(x_1+\zeta, +# x_2+\zeta)` for any :math:`\zeta\in\mathbb{R}.` +# So we can also write it in terms of the lag :math:`\delta=x_1-x_2:` +# +# .. math:: +# +# k_a(\delta) = \lvert\langle0\rvert W_a^\dagger +# S(\delta)W_a\lvert0\rangle\rvert^2. +# +# So far, we only wrote the gate layout for the quantum circuit, no measurement! +# We need a few more functions for that! +# +# Computing the QK function on a quantum device +# ------------------------------------------------- +# +# Also, at this point, we need to set the number of qubits of our computer. +# For this example, we'll use the variable ``n_wires``, and set it to :math:`5.` + +n_wires = 5 + +############################################################################### +# We initialize the quantum simulator: + +dev = qml.device("lightning.qubit", wires = n_wires, shots = None) + +############################################################################### +# Next, we construct the quantum node: + +@qml.qnode(dev) +def QK_circuit(x1, x2, thetas, amplitudes): + ansatz(x1, x2, thetas, amplitudes, wires = range(n_wires)) + return qml.probs(wires = range(n_wires)) + +############################################################################### +# Recall that the output of a QK is defined as the probability of obtaining +# the outcome :math:`\lvert0\rangle` when measuring in the computational basis. +# That corresponds to the :math:`0^\text{th}` entry of ``qml.probs`:` + +def QK_2(x1, x2, thetas, amplitudes): + return QK_circuit(x1, x2, thetas, amplitudes)[0] + +############################################################################### +# As a couple of quality-of-life improvements, we write a function that implements +# the QK with the lag :math:`\delta` as its argument, and one that implements it +# on a given set of data: + +def QK(delta, thetas, amplitudes): + return QK_2(delta, 0, thetas, amplitudes) + +def QK_on_dataset(deltas, thetas, amplitudes): + y = np.array([QK(delta, thetas, amplitudes) for delta in deltas]) + return y + +############################################################################### +# This is also a good place to fix the ``thetas`` array, so that we don't forget +# later. + +thetas = make_thetas(n_wires) + +############################################################################### +# Let's see how this looks like for one particular choice of ``amplitudes``. +# We need to make sure the array fulfills the normalization conditions. + +test_features = np.asarray([1./(1+i) for i in range(2 ** n_wires)]) +test_amplitudes = test_features / np.sqrt(np.sum(test_features ** 2)) + +Y_test = QK_on_dataset(X, thetas, test_amplitudes) + +plt.plot(X, Y_test) +plt.xlabel("$\delta$") +plt.suptitle("QK with test amplitudes") +plt.show() + +############################################################################### +# One can see that the stationary kernel with this particular initial state has +# a decaying spectrum that looks similar to :math:`1/\lvert x\rvert` — but not +# yet like a Gaussian. +# +# How to find the amplitudes emulating a Gaussian kernel +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# If we knew exactly which amplitudes to choose in order to build a given +# Fourier spectrum, our job would be done here. +# However, the equations derived in the literature are not trivial to solve. +# +# As mentioned in the introduction, one could just "learn" this relation, that +# is, tune the parameters of the quantum kernel in a gradient-based manner +# until it matches the classical one. +# +# We want to take an intermediate route between analytical solution and +# black-box optimization. +# For that, we derive an equation that links the amplitudes to the spectrum we +# want to construct and then use old-fashioned convex optimization to find the +# solution. +# If you are not interested in the details, you can just jump to the last plots +# of this demo and confirm that we can to emulate the Gaussian kernel +# using the ansatz for our QK constructed above. +# +# In order to simplify the formulas, we introduce new variables, which we call +# ``probabilities`` :math:`(p_0, p_1, p_2, \ldots, p_{2^n-1}),` and we define as +# :math:`p_j=\lvert a_j\rvert^2.` +# Following the normalization property above, we have :math:`\sum_j p_j=1.` +# Don't get too fond of them, we only need them for this step! +# Remember we introduced the vector :math:`a` for the +# ``MottonenStatePreparation`` as the *amplitudes* of a quantum state? +# Then it makes sense that we call its squares *probabilities*, doesn't it? +# +# There is a crazy formula that matches the entries of *probabilities* with the +# Fourier series of the resulting QK function: +# +# .. math:: +# +# \text{probabilities} &\longrightarrow \text{Fourier coefficients} \\ +# \begin{pmatrix} p_0 \\ p_1 \\ p_2 \\ \vdots \\ p_{2^n-1} \end{pmatrix} +# &\longmapsto \begin{pmatrix} \sum_{j=0}^{2^n-1} p_j^2 \\ \sum_{j=1}^{2^n-1} +# p_j p_{j-1} \\ \sum_{j=2}^{2^n-1} p_j p_{j-2} \\ \vdots \\ p_{2^n-1} p_0 +# \end{pmatrix} +# +# This looks a bit scary, it follows from expanding the matrix product +# :math:`W_a^\dagger S(\delta)W_a,` and then collecting terms according to +# Fourier basis monomials. +# In this sense, the formula is general and it applies to any shift-invariant +# kernel we might want to approximate, not only the Gaussian kernel. +# +# Our goal is to find the set of :math:`p_j's` that produces the Fourier +# coefficients of a given kernel function (in our case, the Gaussian kernel), +# namely its spectrum :math:`(s_0, s_1, s_2, \ldots, s_{2^n-1}).` +# We consider now a slightly different map :math:`F_s,` for a given spectrum +# :math:`(s_0, s_1, \ldots, s_{2^n-1}):` +# +# .. math:: +# +# F_s: \text{probabilities} &\longrightarrow \text{Difference between Fourier +# coefficients} \\ +# \begin{pmatrix} p_0 \\ p_1 \\ p_2 \\ \vdots \\ p_{2^n-1} \end{pmatrix} +# &\longmapsto \begin{pmatrix} \sum_{j=0}^{2^n-1} p_j^2 - s_0 \\ +# \sum_{j=1}^{2^n-1} p_j p_{j-1} - s_1 \\ \sum_{j=2}^{2^n-1} p_j +# p_{j-2} - s_2 \\ \vdots \\ p_{2^n-1}p_0 - s_{2^n-1} \end{pmatrix}. +# +# If you look at it again, you'll see that the zero (or solution) of this +# second map :math:`F_s` is precisely the array of *probabilities* we are +# looking for. +# We can write down the first map as: + +def predict_spectrum(probabilities): + d = len(probabilities) + spectrum = [] + for s in range(d): + s_ = 0 + + for j in range(s, d): + s_ += probabilities[j] * probabilities[j - s] + + spectrum.append(s_) + + # This is to make the output have the same format as + # the output of pennylane.fourier.coefficients + for s in range(1,d): + spectrum.append(spectrum[d - s]) + + return spectrum + +############################################################################### +# And then :math:`F_s` is just ``predict_spectrum`` minus the spectrum we want to +# predict: + +def F(probabilities, spectrum): + d = len(probabilities) + return predict_spectrum(probabilities)[:d] - spectrum[:d] + +############################################################################### +# These closed-form equations allow us to find the solution numerically, using +# Newton's method! +# Newton's method is a classical one from convex optimization theory. +# For our case, since the formula is quadratic, we rest assured that we are +# within the realm of convex functions. +# +# Finding the solution +# ------------------------- +# +# In order to use Newton's method we need the Jacobian of :math:`F_s.` + +def J_F(probabilities): + d = len(probabilities) + J = np.zeros(shape=(d,d)) + for i in range(d): + for j in range(d): + if (i + j < d): + J[i][j] += probabilities[i + j] + if(i - j <= 0): + J[i][j] += probabilities[j - i] + return J + +############################################################################### +# Showing that this is indeed :math:`\nabla F_s` is left as an exercise for the +# reader. +# For Newton's method, we also need an initial guess. +# Finding a good initial guess requires some tinkering; different problems will +# benefit from different ones. +# Here is a tame one that works for the Gaussian kernel. + +def make_initial_probabilities(d): + probabilities = np.ones(d) + deg = np.array(range(1, d + 1)) + probabilities = probabilities / deg + return probabilities + +probabilities = make_initial_probabilities(2 ** n_wires) + +############################################################################### +# Recall the ``spectrum`` we want to match is that of the periodic extension of +# the Gaussian kernel. + +spectrum = fourier_p(2 ** n_wires) + +############################################################################### +# We fix the hyperparameters for Newton's method: + +d = 2 ** n_wires +max_steps = 100 +tol = 1.e-20 + +############################################################################### +# And we're good to go! + +for step in range(max_steps): + inc = np.linalg.solve(J_F(probabilities), -F(probabilities, spectrum)) + probabilities = probabilities + inc + if (step+1) % 10 == 0: + print("Error norm at step {0:3}: {1}".format(step + 1, + np.linalg.norm(F(probabilities, + spectrum)))) + if np.linalg.norm(F(probabilities, spectrum)) < tol: + print("Tolerance trespassed! This is the end.") + break + +############################################################################### +# The tolerance we set was fairly low, one should expect good things to come +# out of this. +# Let's have a look at the solution: + +plt.plot(range(d), probabilities, 'x') +plt.xlabel("array entry $j$") +plt.ylabel("probabilities $p_j$") +plt.show() + +############################################################################### +# Would you be able to tell whether this is correct? +# Me neither! +# But all those probabilities being close to :math:`0` should make us fear some +# of them must've turned negative. +# That would be fatal for us. +# For ``MottonenStatePreparation``, we'll need to give ``amplitudes`` as one of the +# arguments, which is the component-wise square root of ``probabilities``. +# And hence the problem! +# Even if they are very small values, if any entry of ``probabilities`` is +# negative, the square root will give ``nan``. +# In order to avoid that, we use a simple thresholding where we replace very +# small entries by :math:`0.` + +def probabilities_threshold_normalize(probabilities, thresh = 1.e-10): + d = len(probabilities) + p_t = probabilities.copy() + for i in range(d): + if np.abs(probabilities[i] < thresh): + p_t[i] = 0.0 + + p_t = p_t / np.sum(p_t) + + return p_t + +############################################################################### +# Then, we need to take the square root: + +probabilities = probabilities_threshold_normalize(probabilities) +amplitudes = np.sqrt(probabilities) + +############################################################################### +# A little plotting never killed nobody + +plt.plot(range(d), probabilities, '+', label = "probability $p_j = |a_j|^2$") +plt.plot(range(d), amplitudes, 'x', label = "amplitude $a_j$") +plt.xlabel("array entry $j$") +plt.legend() +plt.show() + +############################################################################### +# Visualizing the solution +# -------------------------- +# +# And the moment of truth! +# Does the solution really match the spectrum? +# We try it first using ``predict_spectrum`` only + +plt.plot(range(d), fourier_p(d)[:d], '+', label = "Gaussian kernel") +plt.plot(range(d), predict_spectrum(probabilities)[:d], 'x', label = "QK predicted") +plt.xlabel("frequency $n$") +plt.ylabel("Fourier coefficient") +plt.suptitle("Fourier spectrum of the Gaussian kernel") +plt.legend() +plt.show() + +############################################################################### +# It seems like it does! +# But as we just said, this is still only the predicted spectrum. +# We haven't called the quantum computer at all yet! +# +# Let's see what happens when we call the function ``coefficients`` on the QK +# function we defined earlier. +# Good coding practice tells us we should probably turn this step into a function +# itself, in case it is of use later: + +def fourier_q(d, thetas, amplitudes): + def QK_partial(x): + squeezed_x = qml.math.squeeze(x) + return QK(squeezed_x, thetas, amplitudes) + return np.real(coefficients(QK_partial, 1, d-1)) + +############################################################################### +# And with this, we can finally visualize how the Fourier spectrum of the +# QK function compares to that of the Gaussian kernel: + +plt.plot(range(d), fourier_p(d)[:d], '+', label = "Gaussian kernel") +plt.plot(range(d), predict_spectrum(probabilities)[:d], 'x', label="QK predicted") +plt.plot(range(d), fourier_q(d, thetas, amplitudes)[:d], '.', label = "QK computer") +plt.xlabel("frequency $n$") +plt.ylabel("Fourier coefficient") +plt.suptitle("Fourier spectrum of the Gaussian kernel") +plt.legend() +plt.show() + +############################################################################### +# It seems it went well! +# Matching spectra should mean matching kernel functions, right? + +Y_learned = QK_on_dataset(X, thetas, amplitudes) +Y_truth = [Gauss_p(x_) for x_ in X] + +plt.plot(X, Y_learned, '-.', label = "QK") +plt.plot(X, Y_truth, '--', label = "Gaussian kernel") +plt.xlabel("$\delta$") +plt.ylabel("$k(\delta)$") +plt.legend() +plt.show() + +############################################################################### +# Yeah! +# We did it! +# +# .. figure:: ../_static/demonstration_assets/classical_kernels/salesman.PNG +# :align: center +# :width: 70% +# :target: javascript:void(0) +# +# References +# ---------- +# +# .. [#QEK] +# +# Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan HS +# Derks, Paul K Faehrmann, Johannes Jakob Meyer. +# "Training Quantum Embedding Kernels on Near-Term Quantum Computers". +# `arXiv preprint arXiv:2105.02276 `__. +# +# .. [#Fourier] +# +# Maria Schuld, Ryan Sweke, Johannes Jakob Meyer. +# "The effect of data encoding on the expressive power of variational +# quantum machine learning models". +# `Phys. Rev. A 103, 032430 `__, +# `arXiv preprint arXiv:2008.08605 `__. +# +# .. [#qkernels] +# +# Maria Schuld. +# "Supervised quantum machine learning models are kernel methods". +# `arXiv preprint arXiv:2101.11020 `__. +# +# diff --git a/demonstrations_v2/tutorial_classical_kernels/metadata.json b/demonstrations_v2/tutorial_classical_kernels/metadata.json new file mode 100644 index 0000000000..4109d433a3 --- /dev/null +++ b/demonstrations_v2/tutorial_classical_kernels/metadata.json @@ -0,0 +1,70 @@ +{ + "title": "Approximating a classical kernel with a quantum computer", + "authors": [ + { + "username": "egfuster" + } + ], + "dateOfPublication": "2022-03-01T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_approximate_classical_kernal.png" + } + ], + "seoDescription": "Finding a quantum kernel to approximate the Gaussian kernel.", + "doi": "", + "references": [ + { + "id": "QEK", + "type": "article", + "title": "Training Quantum Embedding Kernels on Near-Term Quantum Computers", + "authors": "Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan HS Derks, Paul K Faehrmann, Johannes Jakob Meyer", + "year": "2021", + "journal": "arXiv preprint", + "url": "https://arxiv.org/abs/2105.02276" + }, + { + "id": "Fourier", + "type": "article", + "title": "The effect of data encoding on the expressive power of variational quantum machine learning models", + "authors": "Maria Schuld, Ryan Sweke, Johannes Jakob Meyer", + "year": "2021", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.032430" + }, + { + "id": "qkernels", + "type": "article", + "title": "Supervised quantum machine learning models are kernel methods", + "authors": "Maria Schuld", + "year": "2021", + "journal": "arXiv preprint", + "url": "https://arxiv.org/abs/2101.11020" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_classical_kernels/requirements.in b/demonstrations_v2/tutorial_classical_kernels/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_classical_kernels/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_classical_shadows/demo.py b/demonstrations_v2/tutorial_classical_shadows/demo.py new file mode 100644 index 0000000000..f09689884a --- /dev/null +++ b/demonstrations_v2/tutorial_classical_shadows/demo.py @@ -0,0 +1,715 @@ +r""" +Classical shadows +================= +.. meta:: + :property="og:description": Learn how to construct classical shadows + and use them to estimate observables. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/atom_shadow.png + +.. related:: + + tutorial_measurement_optimize Measurement optimization + quantum_volume Quantum volume + tutorial_quantum_metrology Variationally optimizing measurement protocols + +*Authors: Roeland Wiersema and Brian Doolittle (Xanadu Residents) — Posted: 14 June 2021. Last updated: 14 June 2021.* + +.. figure:: ../_static/demonstration_assets/classical_shadows/atom_shadow.png + :align: center + :width: 75% + +Estimating properties of unknown quantum states is a key objective of quantum +information science and technology. +For example, one might want to check whether an apparatus prepares a particular target state, +or verify that an unknown system is entangled. +In principle, any unknown quantum state can be fully characterized by `quantum state +tomography `_ [#Mauro2003]_. +However, this procedure requires accurate expectation values for a set of observables +whose size grows exponentially with the number of qubits. +A potential workaround for these scaling concerns is provided by the classical shadow approximation +introduced in a recent paper by Huang et al. [#Huang2020]_. + +The approximation is an efficient protocol for constructing a *classical shadow* +representation of an unknown quantum state. +The classical shadow can be used to estimate properties such as +quantum state fidelity, expectation values of Hamiltonians, entanglement witnesses, and two-point correlators. + +.. figure:: ../_static/demonstration_assets/classical_shadows/classical_shadow_overview.png + :align: center + :width: 90% + + (Image from Huang et al. [#Huang2020]_.) + +In this demo, we use PennyLane to obtain classical shadows of a quantum state prepared by +a quantum circuit, and use them to reconstruct the state and estimate expectation values of +observables. +""" + +##################################################################### +# Constructing a Classical Shadow +# ############################### +# +# Classical shadow estimation relies on the fact that for a particular choice of measurement, +# we can efficiently store snapshots of the state that contain enough information to accurately +# predict linear functions of observables. Depending on what type of measurements we choose, +# we have an information-theoretic bound that allows us to control the precision of our estimator. +# +# Let us consider an :math:`n`-qubit quantum state :math:`\rho` (prepared by a circuit) and apply a random unitary +# :math:`U` to the state: +# +# .. math:: +# +# \rho \to U \rho U^\dagger. +# +# Next, we measure in the computational basis and obtain a bit string of outcomes :math:`|b\rangle = |0011\ldots10\rangle.` +# If the unitaries :math:`U` are chosen at random from a particular ensemble, then we can store the reverse operation +# :math:`U^\dagger |b\rangle\langle b| U` efficiently in classical memory. +# We call this a *snapshot* of the state. +# Moreover, we can view the average over these snapshots as a measurement channel: +# +# .. math:: +# +# \mathbb{E}\left[U^\dagger |b\rangle\langle b| U\right] = \mathcal{M}(\rho). +# +# If the ensemble of unitaries defines a tomographically complete set of measurements, +# we can invert the channel and reconstruct the state: +# +# .. math:: +# +# \rho = \mathbb{E}\left[\mathcal{M}^{-1}\left(U^\dagger |b\rangle\langle b| U \right)\right]. +# +# If we apply the procedure outlined above :math:`N` times, then the collection of inverted snapshots +# is what we call the *classical shadow* +# +# .. math:: +# +# S(\rho,N) = \left\{\hat{\rho}_1= \mathcal{M}^{-1}\left(U_1^\dagger |b_1\rangle\langle b_1| U_1 \right) +# ,\ldots, \hat{\rho}_N= \mathcal{M}^{-1}\left(U_N^\dagger |b_N\rangle\langle b_N| U_N \right) +# \right\}. +# +# The inverted channel is not physical, i.e., it is not completely postive and trace preserving (CPTP). +# However, this is of no concern to us, since all we care about is efficiently applying this inverse channel to the +# observed snapshots as a post-processing step. +# +# Since the shadow approximates :math:`\rho,` we can now estimate **any** observable with the empirical mean: +# +# .. math:: +# +# \langle O \rangle = \frac{1}{N}\sum_i \text{Tr}{\hat{\rho}_i O}. +# +# Note that the classical shadow is independent of the observables we want to estimate, as :math:`S(\rho,N)` contains +# only information about the state! +# +# Furthermore, the authors of [#Huang2020]_ prove that with a shadow of size :math:`N,` we can predict :math:`M` arbitary linear functions +# :math:`\text{Tr}{O_1\rho},\ldots,\text{Tr}{O_M \rho}` up to an additive error :math:`\epsilon` if :math:`N\geq \mathcal{O}\left(\log{M} \max_i ||O_i||^2_{\text{shadow}}/\epsilon^2\right).` +# The shadow norm :math:`||O_i||^2_{\text{shadow}}` depends on the unitary ensemble that is chosen. +# +# Two different ensembles can be considered for selecting the random unitaries :math:`U:` +# +# 1. Random :math:`n`-qubit Clifford circuits. +# 2. Tensor products of random single-qubit Clifford circuits. +# +# Although ensemble 1 leads to the most powerful estimators, it comes with serious practical limitations +# since :math:`n^2 / \log(n)` entangling gates are required to sample the Clifford circuit. The snapshots of both ensembles +# can be stored efficiently using the `stabilizer formalism `_ [#Gottesman1997]_. +# Single-qubit Clifford circuits rotate the measurement basis to one of the Pauli eigenbases, so ensemble 2 +# is equivalent to measuring single shots of single-qubit Pauli observables on all qubits. +# For the purposes of this demo we focus on ensemble 2, which is a more NISQ-friendly approach. +# +# This ensemble comes with a significant drawback: the shadow norm :math:`||O_i||^2_{\text{shadow}}` +# becomes dependent on the locality :math:`k` of the observables that we want to estimate: +# +# .. math:: +# +# ||O_i||^2_{\text{shadow}} \leq 4^k ||O_i||_\infty^2. +# +# Say that we want to estimate the single expectation value of a Pauli observable +# :math:`\langle X_1 \otimes X_2 \otimes \ldots \otimes X_n \rangle.` Estimating this from repeated measurements +# would require :math:`1/\epsilon^2` samples, whereas we would need an exponentially large shadow due to the :math:`4^n` appearing in the bound. +# Therefore, classical shadows based on Pauli measurements only offer an advantage when we have to measure a large number +# of observables with modest locality. +# +# We will now demonstrate how to obtain classical shadows using PennyLane. + +import pennylane as qml +import pennylane.numpy as np +import matplotlib.pyplot as plt +import time + +np.random.seed(666) + + +############################################################################## +# A classical shadow is a collection of :math:`N` individual snapshots :math:`\hat{\rho}_i.` +# Each snapshot is obtained with the following procedure: +# +# 1. The quantum state :math:`\rho` is prepared with a circuit. +# 2. A unitary :math:`U` is randomly selected from the ensemble and applied to :math:`\rho.` +# 3. A computational basis measurement is performed. +# 4. The snapshot is recorded as the observed eigenvalue :math:`1,-1` for :math:`|0\rangle,|1\rangle,` respectively, and the index of the randomly selected unitary :math:`U.` +# +# To obtain a classical shadow using PennyLane, we design the ``calculate_classical_shadow`` +# function below. +# This function obtains a classical shadow for the state prepared by an input +# ``circuit_template``. + + +def calculate_classical_shadow(circuit_template, params, shadow_size, num_qubits): + """ + Given a circuit, creates a collection of snapshots consisting of a bit string + and the index of a unitary operation. + + Args: + circuit_template (function): A Pennylane QNode. + params (array): Circuit parameters. + shadow_size (int): The number of snapshots in the shadow. + num_qubits (int): The number of qubits in the circuit. + + Returns: + Tuple of two numpy arrays. The first array contains measurement outcomes (-1, 1) + while the second array contains the index for the sampled Pauli's (0,1,2=X,Y,Z). + Each row of the arrays corresponds to a distinct snapshot or sample while each + column corresponds to a different qubit. + """ + # applying the single-qubit Clifford circuit is equivalent to measuring a Pauli + unitary_ensemble = [qml.PauliX, qml.PauliY, qml.PauliZ] + + # sample random Pauli measurements uniformly, where 0,1,2 = X,Y,Z + unitary_ids = np.random.randint(0, 3, size=(shadow_size, num_qubits)) + outcomes = np.zeros((shadow_size, num_qubits)) + + for ns in range(shadow_size): + # for each snapshot, add a random Pauli observable at each location + obs = [unitary_ensemble[int(unitary_ids[ns, i])](i) for i in range(num_qubits)] + outcomes[ns, :] = circuit_template(params, observable=obs) + + # combine the computational basis outcomes and the sampled unitaries + return (outcomes, unitary_ids) + + +############################################################################## +# As an example, we demonstrate how to use ``calculate_classical_shadow`` and +# check its performance as the number of snapshots increases. +# First, we will create a two-qubit device and a circuit that applies an +# ``RY`` rotation to each qubit. + +num_qubits = 2 + +# set up a two-qubit device with shots = 1 to ensure that we only get a single measurement +dev = qml.device("lightning.qubit", wires=num_qubits, shots=1) + + +# simple circuit to prepare rho +@qml.qnode(dev) +def local_qubit_rotation_circuit(params, **kwargs): + observables = kwargs.pop("observable") + for w in dev.wires: + qml.RY(params[w], wires=w) + + return [qml.expval(o) for o in observables] + + +# arrays in which to collect data +elapsed_times = [] +shadows = [] + +# collecting shadows and elapsed times +params = np.random.randn(2) +for num_snapshots in [10, 100, 1000, 10000]: + start = time.time() + shadow = calculate_classical_shadow( + local_qubit_rotation_circuit, params, num_snapshots, num_qubits + ) + elapsed_times.append(time.time() - start) + shadows.append(shadow) + +# printing out the smallest shadow as an example +print(shadows[0][0]) +print(shadows[0][1]) + +############################################################################## +# Observe that the shadow simply consists of two matrices. +# Each qubit corresponds to a different column. The first matrix describes +# outcome of the measurement +# while the second matrix indexes the measurement applied to each qubit. +# We now plot the computation times taken to acquire the shadows. + +plt.plot([10, 100, 1000, 10000], elapsed_times) +plt.title("Time taken to obtain a classical shadow from a two-qubit state") +plt.xlabel("Number of Snapshots in Shadow") +plt.ylabel("Elapsed Time") +plt.show() + + +############################################################################## +# As one might expect, the computation time increases linearly with the number +# of snapshots. +# This linear scaling is useful for predicting the length of time required to +# obtain a sufficient number of snapshots for observable estimation. + +############################################################################## +# State Reconstruction from a Classical Shadow +# ############################################ +# +# To verify that the classical shadow approximates the exact state that we want to estimate, +# we tomographically reconstruct the original quantum state :math:`\rho` from a classical +# shadow. Remember that we can approximate :math:`\rho` by averaging +# over the snapshots and applying the inverse measurement channel, +# +# .. math:: +# +# \rho = \mathbb{E}\left[\mathcal{M}^{-1}(U^{\dagger}|\hat{b}\rangle\langle\hat{b}|U)\right]. +# +# The expectation :math:`\mathbb{E}[\cdot]` describes the average over the measurement outcomes +# :math:`|b\rangle` and the sampled unitaries. +# Inverting the measurement channel may seem formidable at first, however, Huang et al. +# [#Huang2020]_ +# show that for Pauli measurements we end up with a rather convenient expression, +# +# .. math:: +# +# \rho=\mathbb{E}[\hat{\rho}], \quad \text{where} \quad +# \hat{\rho} = \bigotimes_{j=1}^n(3U^{\dagger}_j|\hat{b}_j\rangle\langle\hat{b}_j|U_j-\mathbb{I}). +# +# Here :math:`\hat{\rho}` is a snapshot state reconstructed from a single sample in the +# classical shadow, and :math:`\rho` is the average over all snapshot states :math:`\hat{\rho}` in the +# shadow. +# +# To implement the state reconstruction of :math:`\rho` in PennyLane, we develop the +# ``shadow_state_reconstruction`` function. + + +def snapshot_state(b_list, obs_list): + """ + Helper function for `shadow_state_reconstruction` that reconstructs + a state from a single snapshot in a shadow. + + Implements Eq. (S44) from https://arxiv.org/pdf/2002.08953.pdf + + Args: + b_list (array): The list of classical outcomes for the snapshot. + obs_list (array): Indices for the applied Pauli measurement. + + Returns: + Numpy array with the reconstructed snapshot. + """ + num_qubits = len(b_list) + + # computational basis states + zero_state = np.array([[1, 0], [0, 0]]) + one_state = np.array([[0, 0], [0, 1]]) + + # local qubit unitaries + phase_z = np.array([[1, 0], [0, -1j]], dtype=complex) + hadamard = qml.matrix(qml.Hadamard(0)) + identity = qml.matrix(qml.Identity(0)) + + # undo the rotations that were added implicitly to the circuit for the Pauli measurements + unitaries = [hadamard, hadamard @ phase_z, identity] + + # reconstructing the snapshot state from local Pauli measurements + rho_snapshot = [1] + for i in range(num_qubits): + state = zero_state if b_list[i] == 1 else one_state + U = unitaries[int(obs_list[i])] + + # applying Eq. (S44) + local_rho = 3 * (U.conj().T @ state @ U) - identity + rho_snapshot = np.kron(rho_snapshot, local_rho) + + return rho_snapshot + + +def shadow_state_reconstruction(shadow): + """ + Reconstruct a state approximation as an average over all snapshots in the shadow. + + Args: + shadow (tuple): A shadow tuple obtained from `calculate_classical_shadow`. + + Returns: + Numpy array with the reconstructed quantum state. + """ + num_snapshots, num_qubits = shadow[0].shape + + # classical values + b_lists, obs_lists = shadow + + # Averaging over snapshot states. + shadow_rho = np.zeros((2 ** num_qubits, 2 ** num_qubits), dtype=complex) + for i in range(num_snapshots): + shadow_rho += snapshot_state(b_lists[i], obs_lists[i]) + + return shadow_rho / num_snapshots + + +############################################################################## +# Example: Reconstructing a Bell State +# ************************************ +# First, we construct a single-shot, ``'lightning.qubit'`` device and +# define the ``bell_state_circuit`` QNode to construct and measure a Bell state. + +num_qubits = 2 + +dev = qml.device("lightning.qubit", wires=num_qubits, shots=1) + + +# circuit to create a Bell state and measure it in +# the bases specified by the 'observable' keyword argument. +@qml.qnode(dev) +def bell_state_circuit(params, **kwargs): + observables = kwargs.pop("observable") + + qml.Hadamard(0) + qml.CNOT(wires=[0, 1]) + + return [qml.expval(o) for o in observables] + + +############################################################################## +# Then, construct a classical shadow consisting of 1000 snapshots. + +num_snapshots = 1000 +params = [] + +shadow = calculate_classical_shadow( + bell_state_circuit, params, num_snapshots, num_qubits +) +print(shadow[0]) +print(shadow[1]) + +############################################################################## +# To reconstruct the Bell state we use ``shadow_state_reconstruction``. + +shadow_state = shadow_state_reconstruction(shadow) +print(np.round(shadow_state, decimals=6)) + +############################################################################## +# Note the resemblance to the exact Bell state density matrix. + +bell_state = np.array([[0.5, 0, 0, 0.5], [0, 0, 0, 0], [0, 0, 0, 0], [0.5, 0, 0, 0.5]]) + + +############################################################################## +# To measure the closeness we can use the operator norm. + + +def operator_2_norm(R): + """ + Calculate the operator 2-norm. + + Args: + R (array): The operator whose norm we want to calculate. + + Returns: + Scalar corresponding to the norm. + """ + return np.sqrt(np.trace(R.conjugate().transpose() @ R)) + + +# Calculating the distance between ideal and shadow states. +operator_2_norm(bell_state - shadow_state) + +############################################################################## +# Finally, we see how the approximation improves as we increase the +# number of snapshots. We run the estimator 10 times for each :math:`N.` + +number_of_runs = 10 +snapshots_range = [100, 1000, 6000] +distances = np.zeros((number_of_runs, len(snapshots_range))) + +# run the estimation multiple times so that we can include error bars +for i in range(number_of_runs): + for j, num_snapshots in enumerate(snapshots_range): + shadow = calculate_classical_shadow( + bell_state_circuit, params, num_snapshots, num_qubits + ) + shadow_state = shadow_state_reconstruction(shadow) + + distances[i, j] = np.real(operator_2_norm(bell_state - shadow_state)) + +plt.errorbar( + snapshots_range, + np.mean(distances, axis=0), + yerr=np.std(distances, axis=0), +) +plt.title("Distance between Ideal and Shadow Bell States") +plt.xlabel("Number of Snapshots") +plt.ylabel("Distance") +plt.show() + + +############################################################################## +# As expected, when the number of snapshots increases, the state reconstruction +# becomes closer to the ideal state. + +############################################################################## +# Estimating Pauli Observables with Classical Shadows +# ################################################### +# +# We have confirmed that classical shadows can be used to reconstruct +# the state. However, the goal of classical shadows is not to perform full tomography, which takes +# an exponential amount of resources. Instead, we want to use the shadows to efficiently +# calculate linear functions of a quantum state. To do this, we write a function +# ``estimate_shadow_observable`` that takes in the previously constructed shadow +# :math:`S(\rho, N)=[\hat{\rho}_1,\hat{\rho}_2,\ldots,\hat{\rho}_N],` and +# estimates any observable via a median of means estimation. This makes the estimator +# more robust to outliers and is required to formally prove the aforementioned theoretical +# bound. The procedure is simple: split up the shadow into :math:`K` equally sized chunks +# and estimate the mean for each of these chunks, +# +# .. math:: +# +# \langle O_{(k)}\rangle = \text{Tr}\{O \hat{\rho}_{(k)}\} \quad +# \text{and} \quad \hat{\rho}_{(k)} = \frac{1}{ \lfloor N/K \rfloor } +# \sum_{i=(k-1)\lfloor N/K \rfloor + 1}^{k \lfloor N/K \rfloor } \hat{\rho}_i. +# +# The median of means estimator is then simply the median of this set +# +# .. math:: +# +# \langle O\rangle = \text{median}\{\langle O_{(1)} \rangle,\ldots, \langle O_{(K)} \rangle \}. +# +# Note that the shadow bound has a failure probability :math:`\delta.` By choosing the number of splits :math:`K` to be +# suitably large, we can exponentially suppress this failure probability. +# Assume now that :math:`O=\bigotimes_j^n P_j,` where :math:`P_j \in \{I, X, Y, Z\}.` +# To efficiently calculate the estimator for :math:`O,` we look at a single snapshot outcome and plug in the inverse measurement channel: +# +# .. math:: +# +# \text{Tr}\{O\hat{\rho}_i\} &= \text{Tr}\{\bigotimes_{j=1}^n P_j (3U^{\dagger}_j|\hat{b}_j\rangle\langle\hat{b}_j|U_j-\mathbb{I})\}\\ +# &= \prod_j^n \text{Tr}\{ 3 P_j U^{\dagger}_j|\hat{b}_j\rangle\langle\hat{b}_j|U_j\}. +# +# Due to the orthogonality of the Pauli operators, this evaluates to :math:`\pm 3` if :math:`P_j` is the +# corresponding measurement basis :math:`U_j` and 0 otherwise. Hence if a single :math:`U_j` in the snapshot +# does not match the one in :math:`O,` the whole product evaluates to zero. As a result, calculating the mean estimator +# can be reduced to counting the number of exact matches in the shadow with the observable, and multiplying with the appropriate +# sign. Below, we develop the function ``estimate_shadow_obervable`` to estimate any observable given a classical shadow. + + +def estimate_shadow_obervable(shadow, observable, k=10): + """ + Adapted from https://github.com/momohuang/predicting-quantum-properties + Calculate the estimator E[O] = median(Tr{rho_{(k)} O}) where rho_(k)) is set of k + snapshots in the shadow. Use median of means to ameliorate the effects of outliers. + + Args: + shadow (tuple): A shadow tuple obtained from `calculate_classical_shadow`. + observable (qml.Observable): Single PennyLane observable consisting of single Pauli + operators e.g. qml.PauliX(0) @ qml.PauliY(1). + k (int): number of splits in the median of means estimator. + + Returns: + Scalar corresponding to the estimate of the observable. + """ + shadow_size, num_qubits = shadow[0].shape + + # convert Pennylane observables to indices + map_name_to_int = {"PauliX": 0, "PauliY": 1, "PauliZ": 2} + if isinstance(observable, (qml.PauliX, qml.PauliY, qml.PauliZ)): + target_obs, target_locs = np.array( + [map_name_to_int[observable.name]] + ), np.array([observable.wires[0]]) + else: + target_obs, target_locs = np.array( + [map_name_to_int[o.name] for o in observable.operands] + ), np.array([o.wires[0] for o in observable.operands]) + + # classical values + b_lists, obs_lists = shadow + means = [] + + # loop over the splits of the shadow: + for i in range(0, shadow_size, shadow_size // k): + + # assign the splits temporarily + b_lists_k, obs_lists_k = ( + b_lists[i: i + shadow_size // k], + obs_lists[i: i + shadow_size // k], + ) + + # find the exact matches for the observable of interest at the specified locations + indices = np.all(obs_lists_k[:, target_locs] == target_obs, axis=1) + + # catch the edge case where there is no match in the chunk + if sum(indices) > 0: + # take the product and sum + product = np.prod(b_lists_k[indices][:, target_locs], axis=1) + means.append(np.sum(product) / sum(indices)) + else: + means.append(0) + + return np.median(means) + + +############################################################################## +# Next, we can define a function that calculates the number of samples +# required to get an error :math:`\epsilon` on our estimator for a given set of observables. + + +def shadow_bound(error, observables, failure_rate=0.01): + """ + Calculate the shadow bound for the Pauli measurement scheme. + + Implements Eq. (S13) from https://arxiv.org/pdf/2002.08953.pdf + + Args: + error (float): The error on the estimator. + observables (list) : List of matrices corresponding to the observables we intend to + measure. + failure_rate (float): Rate of failure for the bound to hold. + + Returns: + An integer that gives the number of samples required to satisfy the shadow bound and + the chunk size required attaining the specified failure rate. + """ + M = len(observables) + K = 2 * np.log(2 * M / failure_rate) + shadow_norm = ( + lambda op: np.linalg.norm( + op - np.trace(op) / 2 ** int(np.log2(op.shape[0])), ord=np.inf + ) + ** 2 + ) + N = 34 * max(shadow_norm(o) for o in observables) / error ** 2 + return int(np.ceil(N * K)), int(K) + + +############################################################################## +# Example: Estimating a simple set of observables +# ************************************************* +# Here, we give an example for estimating multiple observables on a 10-qubit circuit. +# We first create a simple circuit + +num_qubits = 10 +dev = qml.device("lightning.qubit", wires=num_qubits, shots=1) + + +def circuit_base(params, **kwargs): + observables = kwargs.pop("observable") + for w in range(num_qubits): + qml.Hadamard(wires=w) + qml.RY(params[w], wires=w) + for w in dev.wires[:-1]: + qml.CNOT(wires=[w, w + 1]) + for w in dev.wires: + qml.RZ(params[w + num_qubits], wires=w) + return [qml.expval(o) for o in observables] + + +circuit = qml.QNode(circuit_base, dev) + +params = np.random.randn(2 * num_qubits) + +############################################################################## +# Next, we define our set of observables +# +# .. math:: +# +# O = \sum_{i=0}^{n-1} X_i X_{i+1} + Y_i Y_{i+1} + Z_i Z_{i+1}. + +list_of_observables = ( + [qml.PauliX(i) @ qml.PauliX(i + 1) for i in range(num_qubits - 1)] + + [qml.PauliY(i) @ qml.PauliY(i + 1) for i in range(num_qubits - 1)] + + [qml.PauliZ(i) @ qml.PauliZ(i + 1) for i in range(num_qubits - 1)] +) + +############################################################################## +# With the ``shadow_bound`` function, we calculate how many shadows we need to +# ensure that the absolute error of all individual terms in :math:`O` satisfies +# +# .. math:: +# +# |\langle{O_i}\rangle_{shadow} - \langle{O_i}\rangle_{exact}| \leq \epsilon +# +# for all :math:`1\leq i \leq M.` + +shadow_size_bound, k = shadow_bound( + error=2e-1, observables=[qml.matrix(o) for o in list_of_observables] +) +shadow_size_bound + +############################################################################## +# We verify the bound by considering a grid of errors :math:`\epsilon_i` and checking that +# :math:`|\langle{O_i}\rangle_{shadow} - \langle{O_i}\rangle_{exact}|` stays below this value +# for the shadow size calculated in ``shadow_bound``. First, we get the classical shadow estimate. + +# create a grid of errors +epsilon_grid = [1 - 0.1 * x for x in range(9)] +shadow_sizes = [] +estimates = [] + +for error in epsilon_grid: + # get the number of samples needed so that the absolute error < epsilon. + shadow_size_bound, k = shadow_bound( + error=error, observables=[qml.matrix(o) for o in list_of_observables] + ) + shadow_sizes.append(shadow_size_bound) + print(f"{shadow_size_bound} samples required ") + # calculate a shadow of the appropriate size + shadow = calculate_classical_shadow(circuit, params, shadow_size_bound, num_qubits) + + # estimate all the observables in O + estimates.append([estimate_shadow_obervable(shadow, o, k=k) for o in list_of_observables]) + +############################################################################## +# Then, we calculate the ground truth by changing the device backend. + +dev_exact = qml.device("lightning.qubit", wires=num_qubits) +# change the simulator to be the exact one. +circuit = qml.QNode(circuit_base, dev_exact) + +expval_exact = [ + circuit(params, observable=[o]) for o in list_of_observables +] + +############################################################################## +# Finally, we plot the errors :math:`|\langle{O_i}\rangle_{shadow} - \langle{O_i}\rangle_{exact}|` +# for all individual terms in :math:`O`. We expect that these errors are always smaller than :math:`\epsilon.` + +for j, error in enumerate(epsilon_grid): + plt.scatter( + [shadow_sizes[j] for _ in estimates[j]], + [np.abs(obs - estimates[j][i]) for i, obs in enumerate(expval_exact)], + marker=".", + ) +plt.plot( + shadow_sizes, + [e for e in epsilon_grid], + linestyle="--", + color="gray", + label=rf"$\epsilon$", + marker=".", +) +plt.xlabel(r"$N$ (Shadow size) ") +plt.ylabel(r"$|\langle O_i \rangle_{exact} - \langle O_i \rangle_{shadow}|$") +plt.legend() +plt.show() +############################################################################## +# The points in the plot indicate the individual errors for all :math:`O_i` at a given shadow size. The dashed line +# represents the error threshold that these points must stay under to satisfy the bound. +# As expected, the bound is satisfied for all :math:`O_i` and the errors decrease with the size of +# the shadow. +# +# To conclude, we have shown that classical shadows can be used to reconstruct quantum states and +# estimate expectation values of observables. This is but the tip of the iceberg of what is possible +# with this technique. In the original work [#Huang2020]_, the authors estimate fidelities, +# calculate entanglement witnesses, and even find a way to approximate the von Neumann entropy. +# These applications illustrate the potential power +# of classical shadows for the characterization of quantum systems. + + +############################################################################## +# References +# ########## +# .. [#Mauro2003] G. Mauro D’Ariano, Matteo G.A. Paris, Massimiliano F. Sacchi, +# `"Quantum Tomography" `_, +# Advances in Imaging and Electron Physics, 128 (2003): 205-308. +# .. [#Huang2020] Huang, Hsin-Yuan, Richard Kueng, and John Preskill, +# `"Predicting many properties of a quantum system from very few measurements" `_, +# Nature Physics 16.10 (2020): 1050-1057. +# .. [#Gottesman1997] Gottesman, Daniel, +# `"Stabilizer Codes and Quantum Error Correction", `_ +# Ph.D. thesis, Caltech, eprint quantph/9705052. +# +# diff --git a/demonstrations_v2/tutorial_classical_shadows/metadata.json b/demonstrations_v2/tutorial_classical_shadows/metadata.json new file mode 100644 index 0000000000..d8ac5954ae --- /dev/null +++ b/demonstrations_v2/tutorial_classical_shadows/metadata.json @@ -0,0 +1,74 @@ +{ + "title": "Classical shadows", + "authors": [ + { + "username": "therooler" + }, + { + "username": "bdoolittle" + } + ], + "dateOfPublication": "2021-06-14T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classical_shadows.png" + } + ], + "seoDescription": "Learn how to construct classical shadows and use them to estimate observables.", + "doi": "", + "references": [ + { + "id": "Mauro2003", + "type": "article", + "title": "Quantum Tomography", + "authors": "G. Mauro D\u2019Ariano, Matteo G.A. Paris, Massimiliano F. Sacchi", + "year": "2003", + "journal": "Advances in Imaging and Electron Physics", + "url": "https://arxiv.org/pdf/quant-ph/0302028.pdf" + }, + { + "id": "Huang2020", + "type": "article", + "title": "Predicting many properties of a quantum system from very few measurements", + "authors": "Huang, Hsin-Yuan, Richard Kueng, and John Preskill", + "year": "2020", + "journal": "Nature Physics", + "url": "https://arxiv.org/pdf/2002.08953.pdf" + }, + { + "id": "Gottesman1997", + "type": "article", + "title": "Stabilizer Codes and Quantum Error Correction", + "authors": "Gottesman, Daniel", + "year": "1997", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/9705052" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_measurement_optimize", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_metrology", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/classical-shadows-in-calculating-fidelity/3727" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_classical_shadows/requirements.in b/demonstrations_v2/tutorial_classical_shadows/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_classical_shadows/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py b/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py new file mode 100644 index 0000000000..60c4d6fa7d --- /dev/null +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py @@ -0,0 +1,559 @@ +r""" +Classically-boosted variational quantum eigensolver +=================================================== + +.. meta:: + :property="og:description": Learn how to implement classically-boosted VQE in PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/CB_VQE.png + +.. related:: + + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe Variational Quantum Eigensolver + +*Authors: Joana Fraxanet & Isidor Schoch (Xanadu Residents). +Posted: 31 October 2022. Last updated: 31 October 2022.* + +One of the most important applications of quantum computers is expected +to be the computation of ground-state energies of complicated molecules +and materials. Even though there are already some solid proposals on how +to tackle these problems when fault-tolerant quantum computation comes +into play, we currently live in the `noisy intermediate-scale quantum +(NISQ) `__ +era, meaning that we can only access noisy and limited devices. +That is why a large part of the current research on quantum algorithms is +focusing on what can be done with few resources. In particular, most +proposals rely on variational quantum algorithms (VQA), which are +optimized classically and adapt to the limitations of the quantum +devices. For the specific problem of computing ground-state energies, +the paradigmatic algorithm is the `Variational Quantum Eigensolver +(VQE) `__ algorithm. + + +Although VQE is intended to run on NISQ devices, it is nonetheless +sensitive to noise. This is particularly problematic when applying VQE to complicated molecules which requires a large number of gates. +As a consequence, several modifications to the +original VQE algorithm have been proposed. These variants are usually +intended to improve the algorithm’s performance on NISQ-era devices. + +In this demo, we will go through one of these proposals step-by-step: the +Classically-Boosted Variational Quantum Eigensolver (CB-VQE) [#Radin2021]_. +Implementing CB-VQE reduces the number of measurements required to obtain the +ground-state energy with a certain precision. This is done by making use +of classical states, which in this context are product states that can be +written as a single `Slater determinant `__ +and that already contain some information about the ground-state of the problem. +Their structure allows for efficient classical computation of expectation values. +An example of such classical state would be the `Hartree-Fock state `__, +in which the electrons occupy the molecular orbitals with the lowest energy. + +.. figure:: ../_static/demonstration_assets/classically_boosted_vqe/CB_VQE.png + :align: center + :width: 50% + +We will restrict ourselves to the :math:`H_2` molecule for +the sake of simplicity. First, we will give a short introduction to how +to perform standard VQE for the molecule of interest. For more details, +we recommend the tutorial :doc:`tutorial_vqe` to learn +how to implement VQE for molecules step by step. Then, we will implement +the CB-VQE algorithm for the specific case in which we rely only on one +classical state⁠—that being the Hartree-Fock state. Finally, we will +discuss the number of measurements needed to obtain a certain +error-threshold by comparing the two methods. + +Let’s get started! + +""" + +###################################################################### +# Prerequisites: Standard VQE +# --------------------------- +# +# If you are not already familiar with the VQE family of algorithms and +# wish to see how one can apply it to the :math:`H_2` molecule, feel free to +# work through the aforementioned demo before reading this section. +# Here, we will only briefly review the main idea behind standard VQE +# and highlight the important concepts in connection with CB-VQE. +# +# Given a Hamiltonian :math:`H,` the main goal of VQE is to find the ground state energy of a system governed by the Schrödinger +# equation +# +# .. math:: H \vert \phi \rangle = E \vert \phi \rangle. +# +# This corresponds to the problem of diagonalizing the Hamiltonian and +# finding the smallest eigenvalue. Alternatively, one can formulate the +# problem using the `variational principle `__, +# in which we are interested in minimizing the energy +# +# .. math:: E = \langle \phi \vert H \vert \phi \rangle. +# +# In VQE, we prepare a statevector :math:`\vert \phi \rangle` by applying +# the parameterized ansatz :math:`A(\Theta),` represented by a unitary matrix, +# to an initial state :math:`\vert 0 \rangle^{\otimes n}` where :math:`n` is the number of qubits. Then, the parameters :math:`\Theta` are +# optimized to minimize a cost function, which in this case is the energy: +# +# .. math:: E(\Theta) = \langle 0 \vert^{\otimes n} A(\Theta)^{\dagger} H A(\Theta) \vert 0 \rangle^{\otimes n}. +# +# This is done using a classical optimization method, which is typically +# gradient descent. +# +# To implement our example of VQE, we first define the molecular +# Hamiltonian for the :math:`H_2` molecule in the minimal `STO-3G basis `__ +# using PennyLane +# + +import pennylane as qml +from pennylane import qchem +from pennylane import numpy as np + +symbols = ["H", "H"] +coordinates = np.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) +basis_set = "sto-3g" +electrons = 2 + +molecule = qchem.Molecule(symbols, coordinates, basis_name=basis_set) + +H, qubits = qchem.molecular_hamiltonian(molecule) + + +###################################################################### +# We then initialize the Hartree-Fock state +# :math:`\vert \phi_{HF}\rangle=\vert 1100 \rangle` +# + +hf = qchem.hf_state(electrons, qubits) + + +###################################################################### +# Next, we implement the ansatz :math:`A(\Theta).` In this case, we use the +# class :class:`~.pennylane.AllSinglesDoubles`, which enables us to apply all possible combinations of single and +# double excitations obeying the Pauli principle to the Hartree-Fock +# state. Single and double excitation gates, denoted :math:`G^{(1)}(\Theta)` and :math:`G^{(2)}(\Theta)` respectively, are +# conveniently implemented in PennyLane with :class:`~.pennylane.SingleExcitation` +# and :class:`~.pennylane.DoubleExcitation` classes. You can find more information +# about how these gates work in this `video `__ and in the demo :doc:`tutorial_givens_rotations`. +# + +singles, doubles = qchem.excitations(electrons=electrons, orbitals=qubits) +num_theta = len(singles) + len(doubles) + + +def circuit_VQE(theta, wires): + qml.AllSinglesDoubles(weights=theta, wires=wires, hf_state=hf, singles=singles, doubles=doubles) + + +###################################################################### +# Once this is defined, we can run the VQE algorithm. We first need to +# define a circuit for the cost function. +# + +dev = qml.device("lightning.qubit", wires=qubits) + + +@qml.qnode(dev, interface="autograd") +def cost_fn(theta): + circuit_VQE(theta, range(qubits)) + return qml.expval(H) + + +###################################################################### +# We then fix the classical optimization parameters ``stepsize`` and +# ``max_iteration``: +# + +stepsize = 0.4 +max_iterations = 30 +opt = qml.GradientDescentOptimizer(stepsize=stepsize) +theta = np.zeros(num_theta, requires_grad=True) + + +###################################################################### +# Finally, we run the algorithm. +# + +for n in range(max_iterations): + theta, prev_energy = opt.step_and_cost(cost_fn, theta) + samples = cost_fn(theta) + +energy_VQE = cost_fn(theta) +theta_opt = theta + +print("VQE energy: %.4f" % (energy_VQE)) +print("Optimal parameters:", theta_opt) + + +###################################################################### +# Note that as an output we obtain the VQE approximation to the ground +# state energy and a set of optimized parameters :math:`\Theta` that +# define the ground state through the ansatz :math:`A(\Theta).` We will need +# to save these two quantities, as they are necessary to implement CB-VQE +# in the following steps. +# + + +###################################################################### +# Classically-Boosted VQE +# ----------------------- +# +# Now we are ready to present the classically-boosted version of VQE. +# +# The key of this new method relies on the notion of the +# `generalized eigenvalue problem `__. +# The main idea is to restrict the problem of finding the ground state to +# an eigenvalue problem in a subspace :math:`\mathcal{H}^{\prime}` of the +# complete Hilbert space :math:`\mathcal{H}.` If this subspace is spanned +# by a combination of both classical and quantum states, we can run parts +# of our algorithm on classical hardware and thus reduce the number of +# measurements needed to reach a certain precision threshold. The generalized +# eigenvalue problem is expressed as +# +# .. math:: \bar{H} \vec{v}= \lambda \bar{S} \vec{v}, +# +# where the matrix :math:`\bar{S}` contains the overlaps between the basis states and :math:`\bar{H}` +# is the Hamiltonian :math:`H` projected into the +# subspace of interest, i.e. with the entries +# +# .. math:: \bar{H}_{\alpha, \beta} = \langle \phi_\alpha \vert H \vert \phi_\beta \rangle, +# +# for all :math:`\vert \phi_\alpha \rangle` and :math:`\vert \phi_\beta \rangle` in :math:`\mathcal{H}^{\prime}.` +# For a complete orthonormal basis, the overlap matrix +# :math:`\bar{S}` would simply be the identity matrix. However, we need to +# take a more general approach which works for a subspace spanned by +# potentially non-orthogonal states. We can retrieve the representation of +# :math:`S` by calculating +# +# .. math:: \bar{S}_{\alpha, \beta} = \langle \phi_\alpha \vert \phi_\beta \rangle, +# +# for all :math:`\vert \phi_\alpha \rangle` and :math:`\vert \phi_\beta \rangle` in :math:`\mathcal{H}^{\prime}.` +# Finally, note that :math:`\vec{v}` and :math:`\lambda` are the eigenvectors and +# eigenvalues respectively. Our goal is to find the lowest +# eigenvalue :math:`\lambda_0.` +# + + +###################################################################### +# Equipped with the useful mathematical description of generalized +# eigenvalue problems, we can now choose our subspace such that some of +# the states :math:`\vert \phi_{\alpha} \rangle \in \mathcal{H}^{\prime}` are +# classically tractable. +# +# We will consider the simplest case in which the subspace is spanned only +# by one classical state :math:`\vert \phi_{HF} \rangle` and one quantum +# state :math:`\vert \phi_{q} \rangle.` More precisely, we define the +# classical state to be a single +# `Slater determinant `__, +# which directly hints towards using the *Hartree-Fock* state for several +# reasons. First of all, it is well-known that the Hartree-Fock state is a +# good candidate to approximate the ground state in the mean-field limit. +# Secondly, we already computed it when we built the molecular Hamiltonian +# for the standard VQE! +# + + +###################################################################### +# To summarize, our goal is to build the Hamiltonian :math:`\bar{H}` and +# the overlap matrix :math:`\bar{S},` which act on the subspace +# :math:`\mathcal{H}^{\prime} \subseteq \mathcal{H}` spanned by +# :math:`\{\vert \phi_{HF} \rangle, \vert \phi_q \rangle\}.` These will be +# two-dimensional matrices, and in the following sections we will show how +# to compute all their entries step by step. +# +# As done previously, we start by importing *PennyLane*, *Qchem* and +# differentiable *NumPy* followed by defining the molecular Hamiltonian in +# the Hartree-Fock basis for :math:`H_2.` +# + +symbols = ["H", "H"] +coordinates = np.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) +basis_set = "sto-3g" + +molecule = qchem.Molecule(symbols, coordinates, basis_name=basis_set) + +H, qubits = qchem.molecular_hamiltonian(molecule) + + +###################################################################### +# Computing Classical Quantities +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# We first set out to calculate the purely classical part of the +# Hamiltonian :math:`H.` Since we only have one classical state this will +# already correspond to a scalar energy value. The terms can be expressed +# as +# +# .. math:: H_{11} = \langle \phi_{HF} \vert H \vert \phi_{HF} \rangle \quad \text{and} \quad S_{11} = \langle \phi_{HF} \vert \phi_{HF} \rangle +# +# which is tractable using classical methods. This energy corresponds to +# the Hartree-Fock energy due to our convenient choice of the classical +# state. Note that the computation of the classical component of the +# overlap matrix +# :math:`S_{11} = \langle \phi_{HF} \vert \phi_{HF} \rangle = 1` is +# trivial. +# +# Using PennyLane, we can access the Hartree-Fock energy by looking at the +# fermionic Hamiltonian, which is the Hamiltonian on the basis of Slater +# determinants. The basis is organized in lexicographic order, meaning +# that if we want the entry corresponding to the Hartree-Fock determinant +# :math:`\vert 1100 \rangle,` we will have to take the entry +# :math:`H_{i,i},` where :math:`1100` is the binary representation of the +# index :math:`i.` +# + +hf_state = qchem.hf_state(electrons, qubits) +fermionic_Hamiltonian = H.sparse_matrix().todense() + +# we first convert the HF slater determinant to a string +binary_string = "".join([str(i) for i in hf_state]) +# we then obtain the integer corresponding to its binary representation +idx0 = int(binary_string, 2) +# finally we access the entry that corresponds to the HF energy +H11 = fermionic_Hamiltonian[idx0, idx0] +S11 = 1 + + +###################################################################### +# Computing Quantum Quantities +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# We now move on to the purely quantum part of the Hamiltonian, i.e. the +# entry +# +# .. math:: H_{22} = \langle \phi_{q} \vert H \vert \phi_{q} \rangle, +# +# where :math:`\vert \phi_q \rangle` is the quantum state. This state is +# just the output of the standard VQE with a given ansatz, following the +# steps in the first section. Therefore, the entry :math:`H_{22}` just +# corresponds to the final energy of the VQE. In particular, note that the +# quantum state can be written as +# :math:`\vert \phi_{q} \rangle = A(\Theta^*) \vert \phi_{HF} \rangle` +# where :math:`A(\Theta^*)` is the ansatz of the VQE with the optimised +# parameters :math:`\Theta^*.` Once again, we have +# :math:`S_{22}=\langle \phi_{q} \vert \phi_{q} \rangle = 1` for the +# overlap matrix. +# + +H22 = energy_VQE +S22 = 1 + + +###################################################################### +# Computing Mixed Quantities +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# The final part of the algorithm computes the cross-terms between the +# classical and quantum state +# +# .. math:: H_{12} = \langle \phi_{HF} \vert H \vert \phi_{q} \rangle = H_{21}^{\dagger}. +# +# This part of the algorithm is slightly more complicated than the +# previous steps, since we still want to make use of the classical component +# of the problem in order to minimize the number of required measurements. +# +# Keep in mind that most algorithms usually perform computations either on +# fully classically or quantum tractable Hilbert spaces. CB-VQE takes +# advantage of the classical part of the problem while still calculating a +# classically-intractable quantity by using the so-called +# `Hadamard test `__ +# to construct :math:`H_{12}.` The Hadamard test is a prime example of an +# indirect measurement, which allows us to measure properties of a state +# without (completely) destroying it. +# +# .. figure:: ../_static/demonstration_assets/classically_boosted_vqe/boosted_hadamard_test.png +# :align: center +# :width: 50% +# +# As the Hadamard test returns the real part of a coefficient from a unitary representing +# an operation, we will focus on calculating the quantities +# +# .. math:: H_{12} = \sum_{i} Re(\langle \phi_q \vert i \rangle) \langle i \vert H \vert \phi_{HF} \rangle, +# +# .. math:: S_{12} = Re(\langle \phi_q \vert \phi_{HF} \rangle), +# +# where :math:`\lvert i \rangle` are the computational basis states of the system, +# i.e. the basis of single Slater determinants. Note that we have to decompose the Hamiltonian +# into a sum of unitaries. For the problem under consideration, the set of relevant computational basis states for which +# :math:`\langle i \vert H \vert \phi_{HF}\rangle \neq 0` contains all the +# single and double excitations (allowed by spin symmetries), namely, the states +# +# .. math:: \vert 1100 \rangle, \vert 1001 \rangle, \vert 0110 \rangle, \vert 0011 \rangle. +# +# Specifically, the set of computational basis states includes the +# *Hartree-Fock* state :math:`\lvert i_0 \rangle = \vert \phi_{HF} \rangle = \vert 1100 \rangle` and the +# projections :math:`\langle i \vert H \vert \phi_{HF} \rangle` can be +# extracted analytically from the fermionic Hamiltonian that we computed +# above. This is done by accessing the entries by the index given by the binary +# expression of each Slater determinant. +# +# The Hadamard test is required to compute the real part of +# :math:`\langle \phi_q \vert i \rangle.` +# +# To implement the Hadamard test, we need a register of :math:`n` qubits +# given by the size of the molecular Hamiltonian (:math:`n=4` in our case) +# initialized in the state :math:`\rvert 0 \rangle^{\otimes n}` and an ancillary +# qubit prepared in the :math:`\rvert 0 \rangle` state. +# +# In order to generate :math:`\langle \phi_q \vert i \rangle,` we take +# :math:`U_q` such that +# :math:`U_q \vert 0 \rangle^{\otimes n} = \vert \phi_q \rangle.` +# This is equivalent to using the standard VQE ansatz with the optimized +# parameters :math:`\Theta^*` that we obtained in the previous section +# :math:`U_q = A(\Theta^*).` Moreover, +# we also need :math:`U_i` such that +# :math:`U_i \vert 0^n \rangle = \vert \phi_i \rangle.` In this case, this +# is just a mapping of a classical basis state into the circuit consisting +# of :math:`X` gates and can be easily implemented using PennyLane’s +# function ``qml.BasisState(i, n))``. +# + +wires = range(qubits + 1) +dev = qml.device("lightning.qubit", wires=wires) + + +@qml.qnode(dev, interface="autograd") +def hadamard_test(Uq, Ucl, component="real"): + if component == "imag": + qml.RX(math.pi / 2, wires=wires[1:]) + + qml.Hadamard(wires=[0]) + qml.ControlledQubitUnitary(Uq.conjugate().T @ Ucl, control_wires=[0], wires=wires[1:]) + qml.Hadamard(wires=[0]) + + return qml.probs(wires=[0]) + + +###################################################################### +# Now, we are ready to compute the Hamiltonian +# cross-terms. +# + + +def circuit_product_state(state): + qml.BasisState(state, range(qubits)) + + +wire_order = list(range(qubits)) +Uq = qml.matrix(circuit_VQE, wire_order=wire_order)(theta_opt, wire_order) + +H12 = 0 +relevant_basis_states = np.array( + [[1, 1, 0, 0], [0, 1, 1, 0], [1, 0, 0, 1], [0, 0, 1, 1]], requires_grad=True +) +for j, basis_state in enumerate(relevant_basis_states): + Ucl = qml.matrix(circuit_product_state, wire_order=wire_order)(basis_state) + probs = hadamard_test(Uq, Ucl) + # The projection Re() corresponds to 2p-1 + y = 2 * probs[0] - 1 + # We retrieve the quantities from the fermionic Hamiltonian + binary_string = "".join([str(coeff) for coeff in basis_state]) + idx = int(binary_string, 2) + overlap_H = fermionic_Hamiltonian[idx0, idx] + # We sum over all computational basis states + H12 += y * overlap_H + # y0 corresponds to Re() + if j == 0: + y0 = y + +H21 = np.conjugate(H12) + + +###################################################################### +# The cross terms of the :math:`S` matrix are defined making +# use of the projections with the *Hartree-Fock* state. +# + +S12 = y0 +S21 = y0.conjugate() + + +###################################################################### +# Solving the generalized eigenvalue problem +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# We are ready to solve the generalized eigenvalue problem. For +# this, we will build the matrices :math:`H` and :math:`S` and use `scipy` +# to obtain the lowest eigenvalue. +# + +from scipy import linalg + +S = np.array([[S11, S12], [S21, S22]]) +H = np.array([[H11, H12], [H21, H22]]) + +evals = linalg.eigvals(H, S) +energy_CBVQE = np.min(evals).real + +print("CB-VQE energy %.4f" % (energy_CBVQE)) + + +###################################################################### +# Measurement analysis +# ----------------------- +# +# +# CB-VQE is helpful when it comes to reducing the number of measurements +# that are required to reach a given precision in the ground state energy. +# In fact, for very small systems it can be shown that the classically-boosted method +# reduces the number of required measurements by a factor of :math:`1000` [#Radin2021]_. +# +# Let's see if this is the case for the example above. +# Now that we know how to run standard VQE and CB-VQE algorithms, we can re-run the code above +# for a finite number of measurements. This is done by specifying the number of +# shots in the definition of the devices, for example, ``num_shots = 20``. By doing this, Pennylane +# will output the expectation value of the energy computed from a sample of 20 measurements. +# Then, we simply run both VQE and CB-VQE enough times to obtain statistics on the results. +# +# .. figure:: ../_static/demonstration_assets/classically_boosted_vqe/energy_deviation.png +# :align: center +# :width: 80% +# +# In the plot above, the dashed line corresponds to the true ground state energy of the :math:`H_2` molecule. +# In the x-axis we represent the number of measurements that are used to compute the expected value of the +# Hamiltonian (`num_shots`). In the y-axis, we plot the mean value and the standard deviation of the energies +# obtained from a sample of 100 circuit evaluations. +# As expected, CB-VQE leads to a better approximation of the ground state energy - the mean energies are lower- +# and, most importantly, to a much smaller standard deviation, improving on the results given +# by standard VQE by several orders of magnitude when considering a small number of measurements. +# As expected, for a large number of measurements both algorithms start to converge to similar +# results and the standard deviation decreases. +# +# +# `Note: In order to obtain these results, we had to discard the samples in which the VQE shot noise +# underestimated the true ground state energy of the problem, since this was leading to large +# variances in the CB-VQE estimation of the energy.` +# +# + + +###################################################################### +# Conclusions +# ----------------------- +# +# In this demo, we have learnt how to implement the CB-VQE algorithm in PennyLane. Furthermore, it was observed that we require +# fewer measurements to be executed on a quantum computer to reach the same accuracy as standard VQE. +# Such algorithms could be executed on smaller quantum computers, potentially allowing us to implement useful +# quantum algorithms on real hardware sooner than expected. +# +# + + +###################################################################### +# References +# ----------------------- +# +# .. [#Radin2021] +# +# M. D. Radin. (2021) "Classically-Boosted Variational Quantum Eigensolver", +# `arXiv:2106.04755 [quant-ph] `__ (2021) +# diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json b/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json new file mode 100644 index 0000000000..1cf9202c02 --- /dev/null +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json @@ -0,0 +1,53 @@ +{ + "title": "Classically-boosted variational quantum eigensolver", + "authors": [ + { + "username": "jfraxanet" + }, + { + "username": "ischoch" + } + ], + "dateOfPublication": "2022-10-31T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classically-boosted_VQE.png" + } + ], + "seoDescription": "Learn how to implement classically-boosted VQE in PennyLane.", + "doi": "", + "references": [ + { + "id": "Radin2021", + "type": "article", + "title": "Classically-Boosted Variational Quantum Eigensolver", + "authors": "M. D. Radin", + "year": "2021", + "journal": "", + "doi": "10.48550/arXiv.2106.04755", + "url": "https://arxiv.org/abs/2106.04755" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2106.04755" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in b/demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in new file mode 100644 index 0000000000..fc12179ec4 --- /dev/null +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in @@ -0,0 +1,2 @@ +pennylane +scipy diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py b/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py new file mode 100644 index 0000000000..3f05c28efb --- /dev/null +++ b/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py @@ -0,0 +1,514 @@ +r""" +.. _clifford_circuit_simulations: + +Efficient Simulation of Clifford Circuits +========================================= + +Classical simulation of quantum circuits doesn't always require an exponential amount of +computational resources. They can be performed efficiently if there exists a classical +description that enables evolving the quantum state by unitary operations and performing +measurements in a polynomial number of steps [#supremecy_exp1]_, [#supremecy_exp2]_. In this +tutorial, we take a deep dive into learning about Clifford circuits, which are known to be +efficiently simulable by classical computers and play an essential role in the practical +implementation of quantum computation. We will learn how to use PennyLane to simulate these +circuits scaling up to more than thousands of qubits. We also look at the ability to +decompose quantum circuits into a set of universal quantum gates comprising Clifford gates. + + +Clifford Group and Clifford Gates +--------------------------------- + +In classical computation, one can define a universal set of logic gate operations such as +``{AND, NOT, OR}`` that can be used to perform any boolean function. A similar analogue +in quantum computation is to have a set of quantum gates that can approximate any unitary +transformation up to the desired accuracy. One such universal quantum gate set is the +:math:`\textrm{Clifford + T}` set, ``{H, S, CNOT, T}``, where the gates ``H``, ``S`,` and +``CNOT`` are the generators of the *Clifford group*. The elements of this group are +called *Clifford gates*, which transform *Pauli* words to *Pauli* words under +`conjugation `__. This means an +:math:`n`-qubit unitary :math:`C` belongs to the Clifford group if the conjugates +:math:`C P C^{\dagger}` are also Pauli words for all :math:`n`-qubit Pauli words :math:`P.` +We can see this ourselves by conjugating the Pauli `X` operation with the elements of +the universal set defined above: + +.. figure:: ../_static/demonstration_assets/clifford_simulation/pauli-normalizer.jpeg + :align: center + :width: 70% + :target: javascript:void(0) + +Clifford gates can be obtained by combining the generators of the Clifford group along +with their inverses and include the following quantum operations, which are all +supported in PennyLane: + +1. Single-qubit Pauli gates: :class:`~.pennylane.I`, :class:`~.pennylane.X`, + :class:`~.pennylane.Y`, and :class:`~.pennylane.Z`. +2. Other single-qubit gates: :class:`~.pennylane.S` and :class:`~.pennylane.Hadamard`. +3. The two-qubit ``controlled`` Pauli gates: :class:`~.pennylane.CNOT`, + :class:`~.pennylane.CY`, and :class:`~.pennylane.CZ`. +4. Other two-qubit gates: :class:`~.pennylane.SWAP` and :class:`~.pennylane.ISWAP`. +5. Adjoints of the above gate operations via :func:`~pennylane.adjoint`. + +| Each of these gates can be uniquely described by how they transform the Pauli words. For + example, ``Hadamard`` conjugates :math:`X` to :math:`Z` and :math:`Z` to :math:`X.` + Similarly, ``ISWAP`` acting on a subspace of qubits `i` and `j` conjugates :math:`X_{i}` + to :math:`Z_{i}Y_{j}` and :math:`Z_{i}` to :math:`Z_{j}.` These transformations can + be presented in tabulated forms called *Clifford tableaus*, as shown below: + +.. figure:: ../_static/demonstration_assets/clifford_simulation/clifford_tableaus.jpeg + :align: center + :width: 90% + :target: javascript:void(0) + + +Clifford circuits and Stabilizer Tableaus +----------------------------------------- + +The quantum circuits that consist only of Clifford gates are called *Clifford circuits* +or, more generally, Clifford group circuits. Moreover, the Clifford circuits that +also have single-qubit measurements are known as *stabilizer circuits*. The states +such circuits can evolve to are known as the *stabilizer states*. For example, the +following figure shows the single-qubit stabilizer states: + +.. figure:: ../_static/demonstration_assets/clifford_simulation/clifford-octahedron.jpg + :align: center + :width: 40% + :target: javascript:void(0) + :alt: The octahedron in the Bloch sphere. + + The octahedron in the Bloch sphere defines the states accessible via single-qubit Clifford gates. + +These types of circuits represent extremely important classes of quantum circuits in the +context of quantum error correction and measurement-based quantum computation [#mbmqc_2009]_. +More importantly, they can be efficiently simulated classically, according to the +*Gottesman-Knill* theorem, which states that any :math:`n`-qubit Clifford circuit with +:math:`m` Clifford gates can be simulated in time :math:`poly(m, n)` on a probabilistic +classical computer. + +There are several ways for representing :math:`n`-qubit stabilizer states :math:`|\psi\rangle` +and tracking their evolution with a :math:`poly(n)` number of bits. The `CHP` (CNOT-Hadamard-Phase) +formalism, also called the *phase-sensitive* formalism, is one of these methods, where one +efficiently describes the state using a *Stabilizer tableau* structure based on its +``stabilizer`` set :math:`\mathcal{S}.` The `stabilizers,` represented by the elements ``s`` in +:math:`\mathcal{S},` are n-qubit Pauli words that have the state :math:`|\psi\rangle` as their +:math:`+1` eigenstate, i.e., :math:`s|\psi\rangle = |\psi\rangle,` +:math:`\forall s \in \mathcal{S}.` These are often viewed as virtual ``Z`` operators, while their +conjugates, termed `destabilizers` (``d``), correspond to virtual ``X`` operators, forming a +similar set referred to as ``destabilizer`` set :math:`\mathcal{D}.` + +The stabilizer tableau for an :math:`n`-qubit state is made of binary variables representing +the Pauli words for the ``generators`` of stabilizer :math:`\mathcal{S}` and destabilizer +:math:`\mathcal{D}`and their ``phases`.` These are generally arranged as the following +tabulated structure [#lowrank_2019]_: + +.. figure:: ../_static/demonstration_assets/clifford_simulation/stabilizer-tableau.jpeg + :align: center + :width: 90% + :target: javascript:void(0) + +Here, the first and last :math:`n` rows represent the generators :math:`\mathcal{d}_i` +and :math:`\mathcal{s}_i` as `check vectors +`__, +respectively, and they generate the entire Pauli group :math:`\mathcal{P}_n` together. +The last column contains the binary variable `r` corresponding to the phase of each +generator and gives the sign (:math:`\pm`) for the Pauli word that represents them. + +For evolving the state, i.e., replicating the application of the Clifford gates on the state, +we update each generator and the corresponding phase according to the Clifford tableau +described above [#aaronson-gottesman2004]_. In the :ref:`simulation-section` section, +we will expand on this in greater detail. + + +Clifford Simulations in PennyLane +--------------------------------- + +PennyLane has a ``default.clifford`` +`device `_ +that enables efficient simulation of large-scale Clifford circuits. The device uses the +`stim `__ simulator [#stim]_ as an underlying backend and +can be used to run Clifford circuits in the same way we run any other regular circuits +in the PennyLane ecosystem. Let's look at an example that constructs a Clifford circuit and +performs several measurements. + +""" + +import pennylane as qml + +dev = qml.device("default.clifford", wires=2, tableau=True) + +@qml.qnode(dev) +def circuit(return_state=True): + qml.X(wires=[0]) + qml.CNOT(wires=[0, 1]) + qml.Hadamard(wires=[0]) + qml.Hadamard(wires=[1]) + return [ + qml.expval(op=qml.X(0) @ qml.X(1)), + qml.var(op=qml.Z(0) @ qml.Z(1)), + qml.probs(), + ] + ([qml.state()] if return_state else []) + + +expval, var, probs, state = circuit(return_state=True) +print(expval, var) + +###################################################################### +# As observed, the full range of PennyLane measurements like :func:`~pennylane.expval` +# and :func:`~pennylane.probs` can be performed analytically with this device. Additionally, +# we support all the sample-based measurements on this device, similar to ``default.qubit``. +# For instance, we can simulate the circuit with :math:`10,000` shots and compare +# the results obtained from sampling with the analytic case: +# + +import numpy as np +import matplotlib.pyplot as plt + +# Get the results with 10000 shots and assert them +shot_result = circuit(return_state=False, shots=10000) +shot_exp, shot_var, shot_probs = shot_result +assert qml.math.allclose([shot_exp, shot_var], [expval, var], atol=1e-3) + +# Define computational basis states +basis_states = ["|00⟩", "|01⟩", "|10⟩", "|11⟩"] + +# Plot the probabilities +bar_width, bar_space = 0.25, 0.01 +colors = ["#70CEFF", "#C756B2"] +labels = ["Analytical", "Statistical"] +for idx, prob in enumerate([probs, shot_probs]): + bars = plt.bar( + np.arange(4) + idx * (bar_width + bar_space), prob, + width=bar_width, label=labels[idx], color=colors[idx], + ) + plt.bar_label(bars, padding=1, fmt="%.3f", fontsize=8) + +# Add labels and show +plt.title("Comparing Probabilities from Circuits", fontsize=11) +plt.xlabel("Basis States") +plt.ylabel("Probabilities") +plt.xticks(np.arange(4) + bar_width / 2, basis_states) +plt.ylim(0.0, 0.30) +plt.legend(loc="upper center", ncols=2, fontsize=9) +plt.show() + +###################################################################### +# Benchmarking +# ~~~~~~~~~~~~ +# + +###################################################################### +# Now that we've had a slight taste of what ``default.clifford`` can do, let's push +# the limits to benchmark its capabilities. To achieve this, we'll examine a set +# of experiments with the :math:`n`-qubits `Greenberger-Horne-Zeilinger state +# `_ (GHZ state) +# preparation circuit: +# + +dev = qml.device("default.clifford") + +@qml.qnode(dev) +def GHZStatePrep(num_wires): + """Prepares the GHZ State""" + qml.Hadamard(wires=[0]) + for wire in range(num_wires): + qml.CNOT(wires=[wire, wire + 1]) + return qml.expval(qml.Z(0) @ qml.Z(num_wires - 1)) + + +###################################################################### +# In our experiments, we will vary the number of qubits to see how it impacts the +# execution timings for the circuit both the analytic and finite-shots cases. +# + +from timeit import timeit + +dev = qml.device("default.clifford") + +num_shots = [None, 100000] +num_wires = [10, 100, 1000, 10000] + +shots_times = np.zeros((len(num_shots), len(num_wires))) + +# Iterate over different number of shots and wires +for ind, num_shot in enumerate(num_shots): + for idx, num_wire in enumerate(num_wires): + shots_times[ind][idx] = timeit( + "GHZStatePrep(num_wire, shots=num_shot)", number=5, globals=globals() + ) / 5 # average over 5 trials + +# Figure set up +fig = plt.figure(figsize=(10, 5)) + +# Plot the data +bar_width, bar_space = 0.3, 0.01 +colors = ["#70CEFF", "#C756B2"] +labels = ["Analytical", "100k shots"] +for idx, num_shot in enumerate(num_shots): + bars = plt.bar( + np.arange(len(num_wires)) + idx * bar_width, shots_times[idx], + width=bar_width, label=labels[idx], color=colors[idx], + ) + plt.bar_label(bars, padding=1, fmt="%.2f", fontsize=9) + +# Add labels and titles +plt.xlabel("#qubits") +plt.ylabel("Time (s)") +plt.gca().set_axisbelow(True) +plt.grid(axis="y", alpha=0.5) +plt.xticks(np.arange(len(num_wires)) + bar_width / 2, num_wires) +plt.title("Execution Times with varying shots") +plt.legend(fontsize=9) +plt.show() + + +###################################################################### +# These results clearly demonstrate that large-scale analytic and sampling simulations can +# be performed using ``default.clifford``. Remarkably, the computation time remains consistent, +# particularly when the number of qubits scales up, making it evident that this device +# significantly outperforms state vector-based devices like ``default.qubit`` or +# ``lightning.qubit`` for simulating stabilizer circuits. +# + +###################################################################### +# +# .. _simulation-section: +# +# Simulating Stabilizer Tableau +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Looking at the benchmarks, one may want to delve into understanding what makes the +# underlying stabilizer tableau formalism performant. To do this, we need to access the +# state of the device as a stabilizer tableau using the :func:`~pennylane.state` function. +# This can be done if the device is initialized with the default ``tableau=True`` keyword +# argument. For example, the tableau for the above circuit is: +# + +print(state) + +###################################################################### +# Since looking at the tableaus in the matrix form could be difficult to comprehend, +# one can opt for using the Pauli representation of the generators of ``destabilizer`` +# and ``stabilizer`` contained in them. This approach offers a more human-readable +# visualization of tableaus and the following methods assist us in achieving it. Let's +# generate the stabilizers and destabilizers of the state obtained above. +# + +from pennylane.pauli import binary_to_pauli, pauli_word_to_string + +def tableau_to_pauli_group(tableau): + """Get stabilizers, destabilizers and phase from a Tableau""" + num_qubits = tableau.shape[0] // 2 + stab_mat, destab_mat = tableau[num_qubits:, :-1], tableau[:num_qubits, :-1] + stabilizers = [binary_to_pauli(stab) for stab in stab_mat] + destabilizers = [binary_to_pauli(destab) for destab in destab_mat] + phases = tableau[:, -1].reshape(-1, num_qubits).T + return stabilizers, destabilizers, phases + +def tableau_to_pauli_rep(tableau): + """Get a string representation for stabilizers and destabilizers from a Tableau""" + wire_map = {idx: idx for idx in range(tableau.shape[0] // 2)} + stabilizers, destabilizers, phases = tableau_to_pauli_group(tableau) + stab_rep, destab_rep = [], [] + for phase, stabilizer, destabilizer in zip(phases, stabilizers, destabilizers): + p_rep = ["+" if not p else "-" for p in phase] + stab_rep.append(p_rep[1] + pauli_word_to_string(stabilizer, wire_map)) + destab_rep.append(p_rep[0] + pauli_word_to_string(destabilizer, wire_map)) + return {"Stabilizers": stab_rep, "Destabilizers": destab_rep} + +tableau_to_pauli_rep(state) + +###################################################################### +# As previously suggested, the evolution of the stabilizer tableau after the application of +# each Clifford gate operation can be understood by learning how the generator set is +# transformed based on their Clifford tableaus. For example, the first circuit operation +# ``qml.X(0)`` has the following tableau: +# + +def clifford_tableau(op): + """Prints a Clifford Tableau representation for a given operation.""" + # Print the op and set up Pauli operators to be conjugated + print(f"Tableau: {op.name}({', '.join(map(str, op.wires))})") + pauli_ops = [pauli(wire) for wire in op.wires for pauli in [qml.X, qml.Z]] + # obtain conjugation of Pauli op and decompose it in Pauli basis + for pauli in pauli_ops: + conjugate = qml.prod(qml.adjoint(op), pauli, op).simplify() + decompose = qml.pauli_decompose(conjugate.matrix(), wire_order=op.wires) + decompose_coeffs, decompose_ops = decompose.terms() + phase = "+" if list(decompose_coeffs)[0] >= 0 else "-" + print(pauli, "-—>", phase, list(decompose_ops)[0]) + +clifford_tableau(qml.X(0)) + +###################################################################### +# We now have the two key components for studying the evolution of the stabilizer tableau of the +# described circuit - (i) the `generator` set representation to describe the stabilizer state, +# and (ii) `tableau` representation for the Clifford gate that is applied to it. However, +# in addition to these we would also need a method to access the circuit's state before +# and after the application of gate operation. This is achieved by inserting the +# :func:`~pennylane.snapshots` in the circuit using the following transform. +# + +@qml.transform +def state_at_each_step(tape): + """Transforms a circuit to access state after every operation""" + # This builds list with a qml.Snapshot operation before every tape operation + operations = [] + for op in tape.operations: + operations.append(qml.Snapshot()) + operations.append(op) + operations.append(qml.Snapshot()) # add a final qml.Snapshot operation at end + new_tape = type(tape)(operations, tape.measurements, shots=tape.shots) + postprocessing = lambda results: results[0] # func for processing results + return [new_tape], postprocessing + +snapshots = qml.snapshots(state_at_each_step(circuit))() + +###################################################################### +# We can now access the tableau state via the ``snapshots`` dictionary, where the integer keys +# represent each step. The step ``0`` corresponds to the initial all zero :math:`|00\rangle` +# state, which is stabilized by the Pauli operators :math:`Z_0` and :math:`Z_1.` Evolving +# it by a ``qml.X(0)`` would correspond to transforming its stabilizer generators +# from :math:`+Z_0` to :math:`-Z_0,` while keeping the destabilizer generators the same. +# + +print("Intial State: ", tableau_to_pauli_rep(snapshots[0])) +print("Applying X(0): ", tableau_to_pauli_rep(snapshots[1])) + +###################################################################### +# The process worked as anticipated! So, to track and compute the evolved state, one only needs +# to know the transformation rules for each gate operation described by their tableau. This makes +# the tableau formalism much more efficient than the state vector formalism, where a more +# computationally expensive matrix-vector multiplication has to be performed at each step. +# Let's examine the remaining operations to confirm this. +# + +circuit_ops = circuit.tape.operations +print("Circ. Ops: ", circuit_ops) + +for step in range(1, len(circuit_ops)): + print("--" * 7 + f" Step {step} - {circuit_ops[step]} " + "--" * 7) + clifford_tableau(circuit_ops[step]) + print(f"Before - {tableau_to_pauli_rep(snapshots[step])}") + print(f"After - {tableau_to_pauli_rep(snapshots[step+1])}\n") + + +###################################################################### +# Clifford + T Decomposition +# -------------------------- +# + +###################################################################### +# Finally, you might wonder if there's a programmatic way to determine whether a given circuit +# is a Clifford or a stabilizer circuit, or which gates in the circuit are non-Clifford +# operations. While the ``default.clifford`` device internally attempts this by decomposing +# each gate operation into the Clifford basis, one can also do this independently +# on their own. In PennyLane, any quantum circuit can be decomposed in a universal basis +# using the :func:`~pennylane.clifford_t_decomposition`. This transform, under the hood, +# decomposes the entire circuit up to a desired operator norm error :math:`\epsilon \geq 0` +# using :func:`~pennylane.ops.sk_decomposition` that employs an iter-recursive variant +# of the original Solovay-Kitaev algorithm described in +# `Dawson and Nielsen (2005) `__. +# Let's see this in action for the following two-qubit parameterized circuit: +# + +dev = qml.device("default.qubit") +@qml.qnode(dev) +def original_circuit(x, y): + qml.RX(x, 0) + qml.CNOT([0, 1]) + qml.RY(y, 0) + return qml.probs() + +x, y = np.pi / 2, np.pi / 4 +unrolled_circuit = qml.transforms.clifford_t_decomposition(original_circuit) + +qml.draw_mpl(unrolled_circuit, decimals=2, style="pennylane")(x, y) +plt.show() + +###################################################################### +# In the *unrolled* quantum circuit, we can see that the non-Clifford rotation gates +# :class:`~.pennylane.RX` and :class:`~.pennylane.RY` at the either side of +# :class:`~.pennylane.CNOT` has been replaced by the sequence of single-qubit Clifford and +# :math:`\textrm{T}` gates, which depend on their parameter values. In order to ensure that the +# performed decomposition is correct, we can compare the measurement results of the unrolled +# and original circuits. +# + +original_probs, unrolled_probs = original_circuit(x, y), unrolled_circuit(x, y) +assert qml.math.allclose(original_probs, unrolled_probs, atol=1e-3) + +###################################################################### +# Ultimately, one can use this decomposition to perform some basic resource analysis for +# fault-tolerant quantum computation, such as calculating the number of non-Clifford +# :math:`\textrm{T}` gate operations as shown below. Generally, increase in the number of +# such gates escalates the computational resource demands because the stabilizer formalism +# can no longer be directly applied to evolve the circuit. This is due to their influence +# on the fault-tolerant thresholds for error correction codes, as outlined in the +# `Eastin-Knill `__ theorem. +# + +with qml.Tracker(dev) as tracker: + unrolled_circuit(x, y) + +resources_lst = tracker.history["resources"] +print(resources_lst[0]) + + +###################################################################### +# Conclusion +# ---------- +# + +###################################################################### +# Stabilizer circuits are an important class of quantum circuits that are used +# in quantum error correction and benchmarking the performance of quantum hardware. +# The ``default.clifford`` device in PennyLane enables efficient classical simulations +# of large-scale Clifford circuits and their use for these purposes. +# The device allows one to obtain the Tableau form of the quantum state and supports a wide +# range of essential analytical and statistical measurements such as expectation values, +# samples, entropy-based results and even classical shadow-based results. Additionally, +# it supports finite-shot execution with noise channels that add single or +# multi-qubit Pauli noise, such as depolarization and flip errors. PennyLane also +# provides a functional way to decompose and compile a circuit into a universal basis, +# which can ultimately enable the simulation of near-Clifford circuits. +# + +############################################################################## +# References +# ---------- +# +# .. [#supremecy_exp1] +# +# D. Maslov, S. Bravyi, F. Tripier, A. Maksymov, and J. Latone +# "Fast classical simulation of Harvard/QuEra IQP circuits" +# `arXiv:2402.03211 `__, 2024. +# +# .. [#supremecy_exp2] +# +# C. Huang, F. Zhang, M. Newman, J. Cai, X. Gao, Z. Tian, and *et al.* +# "Classical Simulation of Quantum Supremacy Circuits" +# `arXiv:2005.06787 `__, 2020. +# +# .. [#mbmqc_2009] +# +# H. J. Briegel, D. E. Browne, W. Dür, R. Raussendorf, and M. V. den Nest +# "Measurement-based quantum computation" +# `arXiv:0910.1116 `__, 2009. +# +# .. [#lowrank_2019] +# +# S. Bravyi, D. Browne, P. Calpin, E. Campbell, D. Gosset, and M. Howard +# "Simulation of quantum circuits by low-rank stabilizer decompositions" +# `Quantum 3, 181 `__, 2019. +# +# .. [#aaronson-gottesman2004] +# S. Aaronson and D. Gottesman +# "Improved simulation of stabilizer circuits" +# `Phys. Rev. A 70, 052328 `__, 2004. +# +# .. [#stim] +# C. Gidney +# "Stim: a fast stabilizer circuit simulator" +# `Quantum 5, 497 `__, 2021. +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json b/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json new file mode 100644 index 0000000000..107a2f385b --- /dev/null +++ b/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json @@ -0,0 +1,96 @@ +{ + "title": "Efficient Simulation of Clifford Circuits", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2024-04-12T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/clifford_simulation/thumbnail_clifford_circuit_simulations.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_clifford_circuit_simulations.png" + } + ], + "seoDescription": "Perform efficient simulations for Clifford circuits.", + "doi": "", + "references": [ + { + "id": "supremecy_exp1", + "type": "article", + "title": "Fast classical simulation of Harvard/QuEra IQP circuits.", + "authors": "D. Maslov, S. Bravyi, F. Tripier, A. Maksymov, and J. Latone", + "year": "2024", + "journal": "", + "url": "https://arxiv.org/abs/2402.03211" + }, + { + "id": "supremecy_exp2", + "type": "article", + "title": "Classical Simulation of Quantum Supremacy Circuits.", + "authors": "C. Huang, F. Zhang, M. Newman, J. Cai, X. Gao, Z. Tian, and et al.", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2005.06787" + }, + { + "id": "mbmqc_2009", + "type": "article", + "title": "Measurement-based quantum computation.", + "authors": "H. J. Briegel, D. E. Browne, W. D\u00fcr, R. Raussendorf, and M. V. den Nest", + "year": "2009", + "journal": "", + "url": "https://arxiv.org/abs/0910.1116" + }, + { + "id": "lowrank_2019", + "type": "article", + "title": "Simulation of quantum circuits by low-rank stabilizer decompositions.", + "authors": "S. Bravyi, D. Browne, P. Calpin, E. Campbell, D. Gosset, and M. Howard", + "year": "2019", + "journal": "Quantum 3, 181", + "url": "https://quantum-journal.org/papers/q-2019-09-02-181/" + }, + { + "id": "aaronson-gottesman2004", + "type": "article", + "title": "ClassiImproved simulation of stabilizer circuits.", + "authors": "S. Aaronson and D. Gottesman", + "year": "2004", + "journal": "Phys. Rev. A 70, 052328", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.052328" + }, + { + "id": "stim", + "type": "article", + "title": "Stim: a fast stabilizer circuit simulator.", + "authors": "C. Gidney", + "year": "2021", + "journal": "Quantum 5, 497", + "url": "https://doi.org/10.22331/q-2021-07-06-497" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in b/demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_coherent_vqls/demo.py b/demonstrations_v2/tutorial_coherent_vqls/demo.py new file mode 100644 index 0000000000..7cef42afed --- /dev/null +++ b/demonstrations_v2/tutorial_coherent_vqls/demo.py @@ -0,0 +1,558 @@ +r""" +.. _coherent_vqls: + +Coherent Variational Quantum Linear Solver +========================================== + +.. meta:: + :property="og:description": This demonstration extends the variational quantum + linear solver to solve linear equations defined by a probabilistic coherent operation. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/cvqls_zoom.png + +.. related:: + + tutorial_vqls Variational Quantum Linear Solver + +*Author: Andrea Mari — 06 November 2019. Last updated: 15 January 2021.* + +In this tutorial we propose and implement an algorithm that we call +*coherent variational quantum linear solver* (CVQLS). +This is inspired by the VQLS proposed in Ref. [1] (implemented in a :doc:`previous demo `), with an important difference: +the matrix :math:`A` associated to the problem is physically +applied as a probabilistic coherent operation. This approach has some advantages and +disadvantages and its practical convenience depends on the specific linear problem +to be solved and on experimental constraints. + +.. figure:: ../_static/demonstration_assets/coherent_vqls/cvqls_circuit.png + :align: center + :width: 100% + :target: javascript:void(0) + +Introduction +------------ + +We first define the problem and the general structure of the CVQLS. +As a second step, we consider a particular case and we solve it explicitly with PennyLane. + +The problem +^^^^^^^^^^^ + +We are given a :math:`2^n \times 2^n` matrix :math:`A` which can be expressed as a linear +combination of :math:`L` unitary matrices :math:`A_0, A_1, \dots A_{L-1},` i.e., + +.. math:: + + A = \sum_{l=0}^{L-1} c_l A_l, + +where :math:`c_l` are arbitrary complex numbers. Importantly, we assume that each of the +unitary components :math:`A_l` can be efficiently implemented with a quantum circuit +acting on :math:`n` qubits. + +We are also given a normalized complex vector in the physical form of a quantum +state :math:`|b\rangle,` which can be generated by a unitary operation :math:`U` +applied to the ground state of :math:`n` qubits. , i.e., + +.. math:: + + |b\rangle = U_b |0\rangle, + +where again we assume that :math:`U_b` can be efficiently implemented with a quantum circuit. + +The problem that we aim to solve is that of preparing a quantum state :math:`|x\rangle,` such that +:math:`A |x\rangle` is proportional to :math:`|b\rangle` or, equivalently, such that + +.. math:: + + |\Psi\rangle := \frac{A |x\rangle}{\sqrt{\langle x |A^\dagger A |x\rangle}} \approx |b\rangle. + + +Coherent Variational Quantum Linear Solver (CVQLS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We approximate the solution :math:`|x\rangle` with a variational quantum +circuit, i.e., a unitary circuit :math:`V` depending on a finite number of classical real parameters +:math:`w = (w_0, w_1, \dots):` + +.. math:: + + |x \rangle = V(w) |0\rangle. + +The parameters should be optimized in order to maximize the overlap between the quantum states +:math:`|\Psi\rangle` and :math:`|b\rangle.` We define the following cost function, + +.. math:: + + C = 1- |\langle b | \Psi \rangle|^2, + +such that its minimization with respect to the variational parameters should lead towards the problem solution. + +The approach used in Ref. [1] is to decompose the cost function in terms of many expectation values associated to the +individual components :math:`A_l` of the problem matrix :math:`A.` For this reason, in the VQLS of Ref. [1], +the state vector proportional to :math:`A |x\rangle` is not physically prepared. +On the contrary, the idea presented in this tutorial is to physically implement the linear map :math:`A` as +a coherent probabilistic operation. This approach allows to prepare the state +:math:`|\Psi\rangle := A |x\rangle/\sqrt{\langle x |A^\dagger A |x\rangle}` which can be used to estimate the +cost function of the problem in a more direct way. + + +Coherently applying :math:`A` +>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +The problem of coherently applying a liner combination of unitary operations has been already studied in Ref. [2] +and here we follow a very similar approach. + +Without loss of generality we can assume that the coefficients :math:`c=(c_1, c_2, \dots c_L)` appearing +in the definition of :math:`A` represent a positive and normalized probability distribution, i.e., + +.. math:: + + c_l \ge 0 \quad \forall l, \qquad \sum_{l=0}^{L-1} c_l=1. + +Indeed the complex phase of each coefficient :math:`c_l` can always be absorbed into the associated unitary :math:`A_l,` obtaining +in this way a vector of positive values. Moreover, since the linear problem is +defined up to a constant scaling factor, we can also normalize the coefficients to get a probability distribution. + +For simplicity, since we can always pad :math:`c` with additional zeros, we assume that :math:`L=2^m` for some positive integer :math:`m.` + +Let us consider a unitary circuit :math:`U_c,` embedding the square root of :math:`c` into the quantum state :math:`|\sqrt{c}\rangle` of :math:`m` ancillary qubits: + +.. math:: + + |\sqrt{c} \rangle = U_c |0\rangle = \sum_{l=0}^{L-1} \sqrt{c_l} | l \rangle, + +where :math:`\{ |l\rangle \}` is the computational basis of the ancillary system. + + +Now, for each component :math:`A_l` of the problem matrix :math:`A`, we can define an associated controlled unitary operation :math:`CA_l,` +acting on the system and on the ancillary basis states as follows: + +.. math:: + + CA_l \, |j\rangle |l' \rangle = + \Bigg\{ + \begin{array}{c} + \left(A_l \otimes \mathbb{I}\right) \; |j\rangle |l \rangle \quad \; \mathrm{for}\; l'=l \\ + \qquad \qquad |j\rangle |l' \rangle \quad \mathrm{for}\; l'\neq l + \end{array}, + +i.e., the unitary :math:`A_l` is applied only when the ancillary system is in the corresponding basis state :math:`|l\rangle.` + +A natural generalization of the `Hadamard test `_, to the case of multiple unitary operations, is the following +(see also the figure at the top of this tutorial): + +1. Prepare all qubits in the ground state. +2. Apply :math:`U_c` to the ancillary qubits. +3. Apply the variational circuit :math:`V` to the system qubits. +4. Apply all the controlled unitaries :math:`CA_l` for all values of :math:`l.` +5. Apply :math:`U_c^\dagger` to the ancillary qubits. +6. Measure the ancillary qubits in the computational basis. +7. If the outcome of the measurement is the ground state, the system collapses to + :math:`|\Psi\rangle := A |x\rangle/\sqrt{\langle x |A^\dagger A |x\rangle}.` + If the outcome is not the ground state, the experiment should be repeated. + + +Estimating the cost function +>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +From a technical point of view, the previous steps represent the most difficult part of the algorithm. +Once we have at our disposal the quantum system prepared in the state :math:`|\Psi\rangle,` +it is very easy to compute the cost function. +Indeed one could simply continue the previous protocol with the following two steps: + +8. Apply :math:`U_b^\dagger` to the system. +9. Measure the system in the computational basis. The probability of finding it + in the ground state (given the ancillary qubits measured in their ground state), + is :math:`|\langle 0 | U_b^\dagger |\Psi \rangle|^2 = |\langle b | \Psi \rangle|^2.` + +So, with sufficiently many shots of the previous experiment, one can directly estimate +the cost function of the problem. + +Importantly, the operations of steps 7 and 8 commute. Therefore all the measurements can be +delayed until the end of the quantum circuit (as shown in the figure at the top of this tutorial), +making the structure of the experiment more straightforward. + +A simple example +^^^^^^^^^^^^^^^^ + +In this tutorial we apply the previous theory to the following simple example +based on a system of 3 qubits, which was already considered in Ref. [1] and also reproduced in PennyLane (:doc:`VQLS `): + +.. math:: + \begin{align} + A &= c_0 A_0 + c_1 A_1 + c_2 A_2 = \mathbb{I} + 0.2 X_0 Z_1 + 0.2 X_0, \\ + \\ + |b\rangle &= U_b |0 \rangle = H_0 H_1 H_2 |0\rangle, + \end{align} + +where :math:`Z_j, X_j, H_j` represent the Pauli :math:`Z,` Pauli :math:`X` and Hadamard gates applied to the qubit with index :math:`j.` + +This problem is computationally quite easy since a single layer of local rotations is enough to generate the +solution state, i.e., we can use the following simple ansatz: + +.. math:: + |x\rangle = V(w) |0\rangle = \Big [ R_y(w_0) \otimes R_y(w_1) \otimes R_y(w_2) \Big ] H_0 H_1 H_2 |0\rangle. + + +In the code presented below we solve this particular problem, by following the general scheme of the CVQLS previously discussed. +Eventually we will compare the quantum solution with the classical one. + +General setup +------------- +This Python code requires *PennyLane* and the plotting library *matplotlib*. + +""" + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt + +############################################################################## +# Setting of the main hyper-parameters of the model +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +n_qubits = 3 # Number of system qubits +m = 2 # Number of ancillary qubits +n_shots = 10 ** 6 # Number of quantum measurements +tot_qubits = n_qubits + m # System + ancillary qubits +ancilla_idx = n_qubits # Index of the first ancillary qubit +steps = 10 # Number of optimization steps +eta = 0.8 # Learning rate +q_delta = 0.001 # Initial spread of random quantum weights +rng_seed = 0 # Seed for random number generator + + +############################################################################## +# Circuits of the quantum linear problem +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +############################################################################## +# We need to define the unitary operations associated to the simple example +# presented in the introduction. +# +# The coefficients of the linear combination are three positive numbers :math:`(1, 0.2, 0.2).` +# So we can embed them in the state of :math:`m=2` ancillary qubits by adding a final zero element and +# normalizing their sum to :math:`1:` + +c = np.array([1, 0.2, 0.2, 0]) +c = c / np.sum(c) +# We also compute the square root of c +sqrt_c = np.sqrt(c) + +############################################################################## +# We need to embed the square root of the probability distribution ``c`` into the amplitudes +# of the ancillary state. It is easy to check that one can always embed 3 positive +# amplitudes with just three gates: +# a local :math:`R_y` rotation, a controlled-:math:`R_y` and a controlled-NOT. + + +def U_c(): + """Unitary matrix rotating the ground state of the ancillary qubits + to |sqrt(c)> = U_c |0>.""" + # Circuit mapping |00> to sqrt_c[0] |00> + sqrt_c[1] |01> + sqrt_c[2] |10> + qml.RY(-2 * np.arccos(sqrt_c[0]), wires=ancilla_idx) + qml.CRY(-2 * np.arctan(sqrt_c[2] / sqrt_c[1]), wires=[ancilla_idx, ancilla_idx + 1]) + qml.CNOT(wires=[ancilla_idx + 1, ancilla_idx]) + + +def U_c_dagger(): + """Adjoint of U_c.""" + qml.CNOT(wires=[ancilla_idx + 1, ancilla_idx]) + qml.CRY(2 * np.arctan(sqrt_c[2] / sqrt_c[1]), wires=[ancilla_idx, ancilla_idx + 1]) + qml.RY(2 * np.arccos(sqrt_c[0]), wires=ancilla_idx) + + +############################################################################## +# We are left to define the sequence of all controlled-unitaries :math:`CA_l,` acting +# as :math:`A_l` on the system whenever the ancillary state is :math:`|l\rangle.` +# Since in our case :math:`A_0=\mathbb{I}` and ``c[3] = 0`,` we only need to apply :math:`A_1` and +# :math:`A_2` controlled by the first and second ancillary qubits respectively. + + +def CA_all(): + """Controlled application of all the unitary components A_l of the problem matrix A.""" + # Controlled-A_1 + qml.CNOT(wires=[ancilla_idx, 0]) + qml.CZ(wires=[ancilla_idx, 1]) + + # Controlled-A2 + qml.CNOT(wires=[ancilla_idx + 1, 0]) + + +############################################################################## +# The circuit for preparing the problem vector :math:`|b\rangle` is very simple: + + +def U_b(): + """Unitary matrix rotating the system ground state to the + problem vector |b> = U_b |0>.""" + for idx in range(n_qubits): + qml.Hadamard(wires=idx) + + +############################################################################## +# Variational quantum circuit +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# What follows is the variational quantum circuit that should generate the solution +# state :math:`|x\rangle= V(w)|0\rangle.` +# +# The first layer of the circuit is a product of Hadamard gates preparing a +# balanced superposition of all basis states. +# +# After that, we apply a very simple variational ansatz +# which is just a single layer of qubit rotations +# :math:`R_y(w_0) \otimes R_y(w_1) \otimes R_y(w_2).` +# For solving more complex problems, we suggest to use more expressive circuits as, +# e.g., the PennyLane ``StronglyEntanglingLayers`` template. + + +def variational_block(weights): + """Variational circuit mapping the ground state |0> to the ansatz state |x>.""" + # We first prepare an equal superposition of all the states of the computational basis + for idx in range(n_qubits): + qml.Hadamard(wires=idx) + + # A very minimal variational circuit + for idx, element in enumerate(weights): + qml.RY(element, wires=idx) + + +############################################################################## +# Full quantum circuit +# -------------------- +# +# Now, we can define the full circuit associated to the CVQLS protocol presented in the introduction and +# corresponding to the figure at the top of this tutorial. + + +def full_circuit(weights): + """Full quantum circuit necessary for the CVQLS protocol, + without the final measurement.""" + # U_c applied to the ancillary qubits + U_c() + + # Variational circuit generating a guess for the solution vector |x> + variational_block(weights) + + # Application of all the controlled-unitaries CA_l associated to the problem matrix A + CA_all() + + # Adjoint of U_b, where U_b |0> = |b> + # For this particular problem adjoint(U_b)=U_b + U_b() + + # Adjoint of U_c, applied to the ancillary qubits + U_c_dagger() + + +############################################################################## +# To estimate the overlap of the ground state with the post-selected state, one could +# directly make use of the measurement samples. However, since we want to optimize the cost +# function, it is useful to express everything in terms of expectation values through +# Bayes' theorem: +# +# .. math:: +# |\langle b | \Psi \rangle|^2= +# P( \mathrm{sys}=\mathrm{ground}\,|\, \mathrm{anc} = \mathrm{ground}) = +# P( \mathrm{all}=\mathrm{ground})/P( \mathrm{anc}=\mathrm{ground}) +# +# To evaluate the two probabilities appearing on the right hand side of the previous equation +# we initialize a ``default.qubit`` device and we define two different ``qnode`` circuits. + +dev = qml.device("default.qubit", wires=tot_qubits) + +@qml.qnode(dev, interface="autograd") +def global_ground(weights): + # Circuit gates + full_circuit(weights) + # Projector on the global ground state + P = np.zeros((2 ** tot_qubits, 2 ** tot_qubits)) + P[0, 0] = 1.0 + return qml.expval(qml.Hermitian(P, wires=range(tot_qubits))) + +@qml.qnode(dev, interface="autograd") +def ancilla_ground(weights): + # Circuit gates + full_circuit(weights) + # Projector on the ground state of the ancillary system + P_anc = np.zeros((2 ** m, 2 ** m)) + P_anc[0, 0] = 1.0 + return qml.expval(qml.Hermitian(P_anc, wires=range(n_qubits, tot_qubits))) + + +############################################################################## +# Variational optimization +# ----------------------------- +# +# In order to variationally solve our linear problem, we first define the cost function +# :math:`C = 1- |\langle b | \Psi \rangle|^2` that we are going to minimize. +# As explained above, we express it in terms of expectation values through Bayes' theorem. + + +def cost(weights): + """Cost function which tends to zero when A |x> tends to |b>.""" + + p_global_ground = global_ground(weights) + p_ancilla_ground = ancilla_ground(weights) + p_cond = p_global_ground / p_ancilla_ground + + return 1 - p_cond + + +############################################################################## +# To minimize the cost function we use the gradient-descent optimizer. +opt = qml.GradientDescentOptimizer(eta) + +############################################################################## +# We initialize the variational weights with random parameters (with a fixed seed). + +np.random.seed(rng_seed) +w = q_delta * np.random.randn(n_qubits, requires_grad=True) + +############################################################################## +# We are ready to perform the optimization loop. + +cost_history = [] +for it in range(steps): + w, _cost = opt.step_and_cost(cost, w) + print("Step {:3d} Cost = {:9.7f}".format(it, _cost)) + cost_history.append(_cost) + + +############################################################################## +# We plot the cost function with respect to the optimization steps. +# We remark that this is not an abstract mathematical quantity +# since it also represents a bound for the error between the generated state +# and the exact solution of the problem. + +plt.style.use("seaborn") +plt.plot(cost_history, "g") +plt.ylabel("Cost function") +plt.xlabel("Optimization steps") +plt.show() + +############################################################################## +# Comparison of quantum and classical results +# ------------------------------------------- +# +# Since the specific problem considered in this tutorial has a small size, we can also +# solve it in a classical way and then compare the results with our quantum solution. +# + +############################################################################## +# Classical algorithm +# ^^^^^^^^^^^^^^^^^^^ +# To solve the problem in a classical way, we use the explicit matrix representation in +# terms of numerical NumPy arrays. + +Id = np.identity(2) +Z = np.array([[1, 0], [0, -1]]) +X = np.array([[0, 1], [1, 0]]) + +A_0 = np.identity(8) +A_1 = np.kron(np.kron(X, Z), Id) +A_2 = np.kron(np.kron(X, Id), Id) + +A_num = c[0] * A_0 + c[1] * A_1 + c[2] * A_2 +b = np.ones(8) / np.sqrt(8) + +############################################################################## +# We can print the explicit values of :math:`A` and :math:`b:` + +print("A = \n", A_num) +print("b = \n", b) + + +############################################################################## +# The solution can be computed via a matrix inversion: + +A_inv = np.linalg.inv(A_num) +x = np.dot(A_inv, b) + +############################################################################## +# Finally, in order to compare :math:`x` with the quantum state :math:`|x\rangle,` +# we normalize and square its elements. +c_probs = (x / np.linalg.norm(x)) ** 2 + +############################################################################## +# Preparation of the quantum solution +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +############################################################################## +# Given the variational weights ``w`` that we have previously optimized, +# we can generate the quantum state :math:`|x\rangle.` By measuring :math:`|x\rangle` +# in the computational basis we can estimate the probability of each basis state. +# +# For this task, we initialize a new PennyLane device and define the associated +# QNode. + +dev_x = qml.device("default.qubit", wires=n_qubits, shots=n_shots) + +@qml.qnode(dev_x, interface="autograd") +def prepare_and_sample(weights): + + # Variational circuit generating a guess for the solution vector |x> + variational_block(weights) + + # We assume that the system is measured in the computational basis. + # Therefore, sampling from the device will give us a value of 0 or 1 for each qubit (n_qubits) + # this will be repeated for the total number of shots provided (n_shots). + return qml.sample() + + +############################################################################## +# To estimate the probability distribution over the basis states we first take ``n_shots`` +# samples and then compute the relative frequency of each outcome. + +raw_samples = prepare_and_sample(w) + +# convert the raw samples (bit strings) into integers and count them +samples = [] +for sam in raw_samples: + samples.append(int("".join(str(bs) for bs in sam), base=2)) + +q_probs = np.bincount(samples, minlength=2 ** n_qubits) / n_shots + +############################################################################## +# Comparison +# ^^^^^^^^^^ +# +# Let us print the classical result. +print("x_n^2 =\n", c_probs) + +############################################################################## +# The previous probabilities should match the following quantum state probabilities. +print("||^2=\n", q_probs) + +############################################################################## +# Let us graphically visualize both distributions. + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(7, 4)) + +ax1.bar(np.arange(0, 2 ** n_qubits), c_probs, color="blue") +ax1.set_xlim(-0.5, 2 ** n_qubits - 0.5) +ax1.set_xlabel("Vector space basis") +ax1.set_title("Classical probabilities") + +ax2.bar(np.arange(0, 2 ** n_qubits), q_probs, color="green") +ax2.set_xlim(-0.5, 2 ** n_qubits - 0.5) +ax2.set_xlabel("Hilbert space basis") +ax2.set_title("Quantum probabilities") + +plt.show() + + +############################################################################## +# References +# ---------- +# +# 1. Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles. +# "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems." +# `arXiv:1909.05820 `__, 2019. +# +# 2. Robin Kothari. +# "Efficient algorithms in quantum query complexity." +# PhD thesis, University of Waterloo, 2014. +# +# diff --git a/demonstrations_v2/tutorial_coherent_vqls/metadata.json b/demonstrations_v2/tutorial_coherent_vqls/metadata.json new file mode 100644 index 0000000000..91701d4e9b --- /dev/null +++ b/demonstrations_v2/tutorial_coherent_vqls/metadata.json @@ -0,0 +1,51 @@ +{ + "title": "Coherent Variational Quantum Linear Solver", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2019-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_coherent_variational_quantum_learner_solver.png" + } + ], + "seoDescription": "This demonstration extends the variational quantum linear solver to solve linear equations defined by a probabilistic coherent operation.", + "doi": "", + "references": [ + { + "id": "BravoPrieto2019", + "type": "article", + "title": "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems", + "authors": "Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.05820" + }, + { + "id": "Kothari2014", + "type": "phdthesis", + "title": "Efficient algorithms in quantum query complexity", + "authors": "Robin Kothari", + "year": "2014", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqls", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_coherent_vqls/requirements.in b/demonstrations_v2/tutorial_coherent_vqls/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_coherent_vqls/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py b/demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py new file mode 100644 index 0000000000..7bcafa4c27 --- /dev/null +++ b/demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py @@ -0,0 +1,848 @@ +r"""Constant-depth preparation of matrix product states with dynamic circuits +============================================================================= + +Matrix product states (MPS) are used in a plethora of applications on quantum +many-body systems, both on classical and quantum computers. +This makes the preparation of MPS on a quantum computer an important subroutine, +for example when warm-starting a quantum algorithm with a classically optimized +MPS or preparing initial states with well-studied physical properties. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_constant_depth_mps_prep.png + :align: center + :width: 70% + :alt: Illustration of two matrix product states merged together by a tree growing out of and back into them + :target: javascript:void(0); + +In this demo you will learn to prepare certain MPS with a dynamic +quantum circuit of constant depth. We will implement the circuit, which makes use +of mid-circuit measurements and conditionally applied operations, for a specific +MPS. Then we will compare it to a (static) sequential circuit with linear depth. +Throughout, we closely follow the similarly titled article by Smith et al. [#smith]_. + +.. note:: + + If you are new to dynamic circuit tools (mid-circuit measurements and classically + controlled operations) used in this algorithm, we recommend to check out + our :doc:`introduction to mid-circuit measurements + ` and learn + :doc:`how to create dynamic circuits with mid-circuit measurements + `. + + Optionally, you may want to familiarize yourself with tensor network states + and MPS in particular. For this, take a look at our demos on + :doc:`tensor-network quantum circuits ` and + :doc:`initial state preparation for quantum chemistry + `, which uses a prepared MPS in + an application. + +Outline +------- + +We will start by briefly (no, really!) introducing the building blocks for +the algorithm from a mathematical perspective. One of these blocks +will be the sequential MPS preparation circuit to which we will compare later. +Alongside this introduction we will describe and code up each building block for a +specific MPS. Then we will combine the building blocks into the constant-depth +algorithm by Smith et al. and will run it for the example MPS. +As a preview, this is a schematic overview of the algorithm: + +.. image:: ../_static/demonstration_assets/constant_depth_mps_prep/algorithm.png + :width: 75% + :align: center + +We will get to all these steps in detail below, but in short, the algorithm +runs through five steps. It (1) creates independent MPS on small groups of qubits +and then (2) fuses them together with mid-circuit measurements. This leaves +the overall state with some defects, which are (3) corrected and moved to the +boundary of the state with dynamic operations and classical processing. +Then, all that is left to do is to (4) correct the defect at the boundary and +finally (5) to measure two remaining bond qudits projectively. + +Building blocks of the algorithm +-------------------------------- + +We briefly introduce MPS and a sequential circuit with linear +depth to prepare them, as well as two tools from quantum information theory +that we will use below: fusion of MPS with mid-circuit measurements +and *operator pushing*. + +Matrix product states (MPS) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Matrix product states (MPS) are an important class of quantum states. +They can be described and manipulated conveniently on classical computers and +are able to approximate relevant states in quantum many-body systems. +In particular, MPS can efficiently describe ground states of (gapped local) +one-dimensional Hamiltonians, which in addition can be found efficiently using +density matrix renormalization group (DMRG) algorithms. +Also check out our :doc:`introduction to matrix product states `, +and see [#schollwoeck]_ for a review of MPS. + +Following [#smith]_, we will look at translation-invariant MPS +of a quantum :math:`N`-body system where each body, or site, has local (physical) +dimension :math:`d.` We will further restrict to periodic boundary conditions, +corresponding to translational invariance of the MPS on a closed loop. +A general MPS with these properties is given by + +.. math:: + + |\Psi\rangle = \sum_{\vec{m}} \operatorname{tr}[A^{m_1}A^{m_2}\cdots A^{m_N}]|\vec{m}\rangle, + +where :math:`\vec{m}` is a multi-index of :math:`N` indices ranging over :math:`d,` +i.e., :math:`\vec{m}\in\{0, 1 \dots, d-1\}^N,` and :math:`\{A^{m}\}_{m}` are :math:`d` +square matrices of equal dimension :math:`D.` (Note that we are using the same +:math:`A^m` for each physical site, due to the translational invariance.) +This dimension :math:`D` is called the bond dimension, +which is a crucial quantity for the expressivity and complexity of the MPS. +We see that :math:`|\Psi\rangle` is fully specified by the :math:`d\times D\times D=dD^2` +numbers in the rank-3 tensor :math:`A.` +However, this specification is not unique. We remove some of the redundancies by assuming +that :math:`A` is in so-called left-canonical form, meaning that it satisfies + +.. math:: + + \sum_m {A^m}^\dagger A^m = \mathbb{I}. + +We will not discuss additional redundancies here but refer to [#smith]_ and the +mentioned reviews for more details. + +**Example** + +As an example, consider a chain of :math:`N` qubits :math:`(d=2)` and an MPS +:math:`|\Psi(g)\rangle` on this system with :math:`D=2,` defined by the matrices + +.. math:: + + A^0 = \eta \left(\begin{matrix} 1 & 0 \\ \sqrt{-g} & 0 \end{matrix}\right); + \quad + A^1 = \eta \left(\begin{matrix} 0 & -\sqrt{-g} \\ 0 & 1 \end{matrix}\right), + +where :math:`\eta = \frac{1}{\sqrt{1-g}}` and :math:`g\in[-1, 0]` is a freely chosen parameter. + +This MPS is a simple yet important example (also discussed in Sec. III C 1. of [#smith]_) +because it can be tuned from the GHZ state +:math:`|\Psi(0)\rangle=\frac{1}{\sqrt{2}}(|0\rangle^{\otimes N} + |1\rangle^{\otimes N})` +to the product state :math:`|\Psi(-1)\rangle=|+\rangle^{\otimes N},` two states that differ +greatly in their physical properties. + +One such property is the correlation length. +For the GHZ state, a correlator between sites :math:`0` and :math:`j` is + +.. math:: + + C_{\text{GHZ}}(j) = \langle Z(0) Z(j) \rangle_{\text{GHZ}} - \langle Z(0)\rangle_{\text{GHZ}}\langle Z(j)\rangle_{\text{GHZ}} = 1. + +That is, the correlation does not decay with the distance :math:`j,` but the correlation length is +infinite. For the product state, on the other hand, we get + +.. math:: + + C_{+}(j) = \langle Z(0) Z(j) \rangle_{+} - \langle Z(0)\rangle_{+}\langle Z(j)\rangle_{+} = 0, + +so that correlations decay "instantaneously." The correlation length vanishes. +Long-range correlated states require linear-depth circuits if we are restricted to unitary +operations. Therefore, the constant-depth circuit for :math:`|\Psi(0)\rangle` will demonstrate +that dynamic quantum circuits, which are not purely unitary, are more powerful than unitary +operations alone! + +The MPS :math:`|\Psi(g)\rangle` will be our running example throughout this demo. +To warm up our coding, let's define the tensor :math:`A` and test that it is in left-canonical form. +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import FancyBboxPatch + +import pennylane as qml + + +def A(g): + """Compute the tensor A in form of d=2 matrices with shape (D, D) = (2, 2) each.""" + eta = 1 / np.sqrt(1 - g) + A_0 = np.array([[1, 0], [np.sqrt(-g), 0]]) * eta + A_1 = np.array([[0, -np.sqrt(-g)], [0, 1]]) * eta + return (A_0, A_1) + + +g = -0.6 +A_0, A_1 = A(g) +is_left_canonical = np.allclose(A_0.conj().T @ A_0 + A_1.conj().T @ A_1, np.eye(2)) +print(f"The matrices A^m are in left-canonical form: {is_left_canonical}") + +xi = 1 / abs(np.log((1 + g) / (1 - g))) +print(f"For {g=}, the theoretical correlation length is {xi=:.4f}") + +###################################################################### +# Sequential preparation circuit +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# An MPS like the one above can be prepared in linear depth using an established technique +# by Schön et al. [#schoen]_. We introduce this technique here because the new +# constant-depth construction uses it as a (parallelized) subroutine, and because we will +# later compare the two approaches. +# +# The MPS :math:`|\Psi\rangle` above is given by the rank-3 tensor :math:`A.` +# We can think of this as an operator that acts on a bond site and simultaneously *creates* +# a physical site. However, a unitary quantum circuit is not allowed to +# create qudits out of nowhere. To fix this, we construct a unitary operation +# from :math:`A` that already takes a physical site as input. +# This means that we build a new rank-4 tensor :math:`U` with an additional +# axis of dimension :math:`d.` Then we demand that :math:`U` acts like :math:`A` if this +# new physical input axis is in the state :math:`|0\rangle.` That is, after applying :math:`U` +# to a physical site in :math:`|0\rangle` and a bond site, we obtain the same result as +# applying :math:`A` to the bond site alone. +# +# We leave the action of :math:`U` on other physical input states undefined, and only require +# it to make :math:`U` unitary overall. +# In short, our construction can be written as +# +# .. math:: +# +# U = \sum_m A^m \otimes |m\rangle\!\langle 0| + C_\perp, +# +# where :math:`C_\perp` can be any operator making :math:`U` unitary. +# +# In the definition of the MPS, the bond site is passed on between copies of :math:`A,` +# sequentially creating physical sites. Likewise, we will apply our unitary :math:`U` to +# a sequence of physical sites (each in the initial state :math:`|0\rangle`) while passing +# on a single bond site. This way, we chain up the unitaries on the bond site like beads +# on a string. +# +# .. image:: ../_static/demonstration_assets/constant_depth_mps_prep/sequential.png +# :width: 75% +# :align: center +# +# Alongside the correspondence between :math:`U` acting on :math:`|0\rangle` and :math:`A,` +# these steps are visualized in the sketch above. +# Note that the sketch deviates from standard circuit diagrams: +# Usually each wire corresponds to one qudit in the algorithm and the position of +# the qudits is fixed through the wire position. Here, the two bond qudits start as +# the top two wires, and one of them is moved to the bottom throughout the circuit. +# +# The sequential preparation circuit now consists of the following steps. +# +# #. Start in the state :math:`|\psi_0\rangle = |0\rangle^{\otimes N}\otimes |00\rangle_D.` +# The last two sites are non-physical bond sites, encoded by :math:`D`-dimensional qudits. +# +# #. Entangle the bond qudits into the state +# :math:`|I\rangle = \frac{1}{\sqrt{D}}\sum_j |jj\rangle_D.` +# +# #. Apply the unitary :math:`U` to each of the :math:`N` physical sites, with the +# :math:`D`-dimensional tensor factor always acting on the first bond qudit. +# Denoting :math:`U` acting on the :math:`i`\ -th physical site and the first bond site +# by :math:`U^{(i)},` this produces the following state. +# +# .. math:: +# +# |\psi_1\rangle +# &= \prod_{i=N}^{1} U^{(i)} |0\rangle^{\otimes N} \frac{1}{\sqrt{D}} \sum_j|jj\rangle _D\\ +# &= \frac{1}{\sqrt{D}} \sum_j \prod_{i=N-1}^{1} U^{(i)} +# \sum_{m_N=0}^{d-1} |0\rangle^{\otimes N-1}|m_N\rangle A^{m_N}|jj\rangle _D\\ +# &= \frac{1}{\sqrt{D}} \sum_j +# \sum_{\vec{m}} |\vec{m}\rangle A^{m_1} A^{m_2}\cdots A^{m_N}|jj\rangle_D\\ +# &= \frac{1}{\sqrt{D}} \sum_{j,k} +# \sum_{\vec{m}} |\vec{m}\rangle \langle j|A^{m_1} A^{m_2}\cdots A^{m_N}|k\rangle |jk\rangle_D +# +# We can see how each :math:`U^{(i)}` contributes a factor of :math:`A` and a corresponding +# state :math:`|m_i\rangle` on the :math:`i`\ -th physical site. The same would have +# come out when applying the 3-tensor :math:`A` to the first bond site :math:`N` times. +# +# #. Measure the two bond qudits in the (generalized) Bell basis and postselect on the outcome +# being :math:`|I\rangle.` Then discard the bond qudits, which collapses :math:`|\psi_1\rangle` +# into the state +# +# .. math:: +# +# |\psi_2\rangle +# = \sum_{\vec{m}} \operatorname{tr}[A^{m_1}A^{m_2}\cdots A^{m_N}]|\vec{m}\rangle +# = |\Psi\rangle. +# +# Note that this step is *probabilistic* and we only succeed to produce the state if we measure +# the state :math:`|I\rangle.` +# +# We see that we prepared :math:`|\Psi\rangle` with an entangling operation on the bond qudits, one unitary per +# physical qubit, and a final basis change to measure the Bell basis. Overall, this amounts to a linear +# operation count and circuit depth. +# +# **Example** +# +# For our example MPS :math:`|\Psi(g)\rangle,` let us first find the unitary :math:`U.` +# For this, consider the fixed part of :math:`U.` + +E_00 = np.array([[1, 0], [0, 0]]) # |0><0| +E_10 = np.array([[0, 0], [1, 0]]) # |1><0| +U_first_term = np.kron(A_0, E_00) + np.kron(A_1, E_10) + +print(np.round(U_first_term, 5)) +print(np.linalg.norm(U_first_term, axis=0)) + +###################################################################### +# We see that this fixed part has two norm-1 columns already, i.e., it maps the input states +# :math:`|00\rangle` and :math:`|10\rangle` to normalized, orthogonal quantum states. +# We can complement this by mapping :math:`|01\rangle` and :math:`|11\rangle` to two other +# normalized states that are mutually orthogonal again. This turns out to be easy for the +# small example here and leads to the following: + +E_01 = np.array([[0, 1], [0, 0]]) # |0><1| +E_11 = np.array([[0, 0], [0, 1]]) # |1><1| +C_perp = np.kron(A_1, E_01) + np.kron(A_0, E_11) + +U = U_first_term + C_perp + +print(np.round(U, 5)) +print(f"\nU is unitary: {np.allclose(U.conj().T @ U, np.eye(4))}") + + +###################################################################### +# Great! This means that we already found the unitary :math:`U` for this MPS. +# We will implement it as the matrix that it is via :class:`~.pennylane.QubitUnitary`. +# If you're curious, you can try to decompose it into elementary gates (hint: you only +# need two!). +# +# Now let's code up the entire sequential preparation circuit (we will be applying +# the unitary :math:`U` to the second instead of the first bond qubit). We implement the +# measurement and postselection step as a separate function ``project_measure`` for +# easier reusability later on. + + +def label_fn(self, *_, **__): + """Report the physical wire this operation acts on in its label.""" + return f"$U^{{({self.wires[1]})}}$" + + +qml.QubitUnitary.label = label_fn + + +def sequential_preparation(g, wires): + """Prepare the example MPS Ψ(g) on N qubits where N is the length + of the passed wires minus 2. The bond qubits are still entangled.""" + eta = 1 / np.sqrt(1 - g) + # Define bond qubits [0, N+1] and physical qubits [1, 2, ... N] + bond0, bond1 = wires[0], wires[-1] + phys_wires = wires[1:-1] + + # Prepare bond qubits in the Bell state (|00>+|11>)/sqrt(2) + qml.Hadamard(bond0) + qml.CNOT([bond0, bond1]) + + # Apply unitary U to second bond qubit and each physical qubit + for phys_wire in phys_wires: + u = qml.QubitUnitary(U, wires=[bond1, phys_wire]) + + +def project_measure(wire_0, wire_1): + """Measure two qubits in the Bell basis and postselect on (|00>+|11>)/sqrt(2).""" + # Move bond qubits from Bell basis to computational basis + qml.CNOT([wire_0, wire_1]) + qml.Hadamard(wire_0) + # Measure in computational basis and postselect |00> + qml.measure(wire_0, postselect=0) + qml.measure(wire_1, postselect=0) + + +dev = qml.device("default.qubit") + + +@qml.qnode(dev) +def sequential_circuit(N, g): + """Run the preparation circuit and projectively measure the bond qubits.""" + wires = list(range(N + 2)) + sequential_preparation(g, wires) + project_measure(0, N + 1) + return qml.probs(wires=wires[1 : N + 1]) + + +###################################################################### +# We will use this circuit later to compare the constant-depth circuit +# against it. For now, let's draw it. + +N = 7 +_ = qml.draw_mpl(sequential_circuit)(N, g) + +###################################################################### +# The sequential preparation circuit already uses mid-circuit measurements, as is visible +# from the measurement steps on the first and last qubit, which are set to postselect the +# outcome :math:`|0\rangle.` However, there is no feedforward control that modifies the circuit +# dynamically based on measured values. This will change in the following sections. +# +# Fusion of MPS states with mid-circuit measurements +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The next ingredient for the constant-depth MPS preparation circuit is to fuse the product +# state of two MPS states together into one (entangled) MPS. Recall that a single sequential +# creation of this final MPS would pass a bond site between the first and second group of physical +# sites, chaining them like beads on the same string. The fusion step allows us to replace +# this connection with dynamic circuit components after creating two independent MPS. +# This is like knotting together two strings on which we chained beads independently before. +# As a matter of fact, chaining beads on smaller strings and the fact that we can knot them +# together simultaneously is at the heart of going from linear to constant circuit depth. +# +# Before we dive into the mathematical details, we visualize the idea behind the fusion step +# schematically: +# +# .. image:: ../_static/demonstration_assets/constant_depth_mps_prep/fusion.png +# :width: 75% +# :align: center +# +# Consider a state :math:`|\Phi\rangle=|\Phi_1\rangle\otimes|\Phi_2\rangle,` where +# +# .. math:: +# +# |\Phi_{r}\rangle = \frac{1}{\sqrt{D}} \sum_{j,k} +# \sum_{\vec{m}} |\vec{m}\rangle \langle j|A^{m_1} A^{m_2}\cdots A^{m_{N_r}}|k\rangle |jk\rangle_D +# +# for :math:`r=1,2` are MPS on :math:`N_r` qudits, respectively. We assume that they have been +# prepared with the sequential technique from above, but that the bond +# qudits have not been measured yet. In particular, the postselection step has not been performed +# yet. Using the form of :math:`|\psi_1\rangle` from the recipe above, we can write +# this state as +# +# .. math:: +# +# |\Phi\rangle = \frac{1}{D} \sum_{j,k,\ell,p} +# \sum_{\vec{m}} |\vec{m}\rangle |jk\ell p\rangle_D +# \langle j|A^{m_1} \cdots A^{m_{N_1}}|k\rangle_D +# \langle \ell|A^{m_{{N_1}+1}} \cdots A^{m_{{N_1}+{N_2}}}|p\rangle_D. +# +# Now we want to measure two bond qudits in an entangled basis, one of +# :math:`|\Phi_1\rangle` and :math:`|\Phi_2\rangle` each (e.g., the ones indexed +# by :math:`k` and :math:`\ell` above). Assume this basis +# to be given in the form of (orthogonal) states +# +# .. math:: +# +# |B^k\rangle=\frac{1}{D} \sum_{j,\ell=1}^D \left(B_{j\ell}^k\right)^\ast |j\ell\rangle, +# +# which in turn are characterized by :math:`D\times D` matrices :math:`B^k.` +# We can change the basis on the bond qudits to be measured by applying the unitary +# :math:`V=\sum_k |k\rangle\!\langle B^k|,` which leads to the state +# +# .. math:: +# +# |\Phi'\rangle = \frac{1}{D} \sum_k \sum_{i,j,\ell,p} +# \sum_{\vec{m}} |\vec{m}\rangle |k\rangle_{D^2} |ip\rangle_D +# \langle i|A^{m_1} \cdots A^{m_K}B^kA^{m_{K+1}} \cdots A^{m_{K+L}}|p\rangle_D, +# +# where we used that :math:`\sum_{j,\ell} |j\rangle B^k_{j\ell}\langle\ell|=B^k.` +# We now can measure the two bond qudits and will know that for a given measurement +# outcome :math:`k,` we obtained the MPS state on :math:`K+L` qubits, up to +# a defect, namely the matrix :math:`B^k` that depends to the outcome. +# +# In full generality, the fusion strategy may seem complicated, but it can take +# a very simple form, as we will now see in our example. +# +# **Example** +# +# We will use the Bell basis to perform the mid-circuit measurement, i.e., +# +# .. math:: +# +# |B^0\rangle &= \frac{1}{\sqrt{2}}(|00\rangle+|11\rangle)\quad +# |B^1\rangle = \frac{1}{\sqrt{2}}(|01\rangle+|10\rangle)\\ +# |B^2\rangle &= \frac{1}{\sqrt{2}}(|00\rangle-|11\rangle)\quad +# |B^3\rangle = \frac{i}{\sqrt{2}}(|01\rangle-|10\rangle). +# +# This choice must seem arbitrary at this point, but will become clear +# later on. There, we will see that in general the matrices :math:`B^k` and the MPS +# tensor :math:`A` have to "play well" together. +# As an example, this choice leads to :math:`B^1_{j\ell} = 1` for +# :math:`(j,\ell)=(0, 1)` and :math:`(j, \ell)=(1,0),` and :math:`B^1_{j\ell}=0` +# otherwise (note that the factor :math:`\frac{1}{\sqrt{2}}` is not part of :math:`B^k` +# in the general equation above). This means that :math:`B^1=X,` the Pauli matrix! +# Similarly, we find +# +# .. math:: +# +# B^0 = I \quad B^1 = X \quad B^2 = Z \quad B^3 = Y, +# +# i.e., all standard Pauli matrices. +# The measurement procedure can then be coded up in the following short function, +# which is similar to the ``project_measure`` function from the sequential preparation +# routine. Note, however, that instead of postselecting, we record the measurement +# outcome in the form of two bits representing the index :math:`k` of the operator +# :math:`B^k, which we will use further below. + + +def fuse(wire_0, wire_1): + """Measure two qubits in the Bell basis and return the outcome + encoded in two bits.""" + qml.CNOT([wire_0, wire_1]) + qml.Hadamard(wire_0) + return np.array([qml.measure(w) for w in [wire_0, wire_1]]) + + +###################################################################### +# Fusing two MPS together that have been prepared by the sequential routine +# now is really simple: + + +def two_qubit_mps_by_fusion(g): + sequential_preparation(g, [0, 1, 2]) + sequential_preparation(g, [3, 4, 5]) + mcms = fuse(2, 3) + + +###################################################################### +# However, as mentioned above, the produced state is not quite the MPS state on two qubits, +# because of the impurities caused by the fusion measurement. We can check this +# by undoing the fusion-based preparation with the two-qubit sequential circuit +# and measuring an exemplary expectation value. +# If the two separately prepared MPS and the fusion step did prepare the correct +# MPS already, the test measurement would just be :math:`\langle 00 | Z_0| 00\rangle=1.` + + +@qml.qnode(qml.device("default.qubit", wires=6)) +def prepare_and_unprepare(g): + two_qubit_mps_by_fusion(g) + # The bond qubits for a sequential preparation are just 0 and 5 + # The bond qubits 2 and 3 have been measured out in the fusion protocol + qml.adjoint(sequential_preparation)(g, [0, 1, 4, 5]) + return qml.expval(qml.PauliZ(1)) + + +test = prepare_and_unprepare(g) +print(f"Test measurement of fusion preparation + sequential unpreparation is {test:.2f}") + +###################################################################### +# This means we still need a way to remove the defect matrices :math:`B^k` from the +# state that ended up in the prepared state when we performed the fusion measurement. +# *Operator pushing* will allow us to do exactly that! +# +# Operator pushing +# ~~~~~~~~~~~~~~~~ +# +# The last building block we need is so-called operator pushing. In fact, whether or not an MPS +# can be prepared with the constant-depth circuit depends on whether certain operator pushing +# relations are satisfied between the matrices :math:`B^k` from the fusion step and the MPS +# tensor :math:`A.` We will not go into detail about these conditions but assume here that +# we work with compatible MPS and measurements. +# +# Operator pushing then allows us to push a defect operator :math:`B^k` from one bond +# axis through the tensor :math:`A` of the MPS to the other bond axis and to the physical axis. +# In general, doing so will change the operator, i.e., we end up with different operators +# :math:`C^k` and :math:`D^k` on the bond and physical axes, respectively. +# Visually, we find: +# +# .. image:: ../_static/demonstration_assets/constant_depth_mps_prep/operator_pushing.png +# :width: 80% +# :align: center +# +# For simplicity, we denote a push by :math:`B^k\mapsto (C^k, D^k).` +# +# **Example** +# +# In the fusion step above we picked the Bell basis as measurement basis, without +# further specifying why. As we saw, this basis leads to Pauli matrices as impurities +# :math:`B^k.` It turns out that those matrices satisfy simple pushing relations together with +# the tensor :math:`A` of our example MPS :math:`|\Psi(g)\rangle,` explaining this choice of +# measurement basis. In particular, the relations are +# +# .. image:: ../_static/demonstration_assets/constant_depth_mps_prep/operator_pushing_example.png +# :width: 90% +# :align: center +# +# | +# +# That is, in the notation above we have +# :math:`X\mapsto (Y, Y),` +# :math:`Y\mapsto (Y, X),` and +# :math:`Z\mapsto (I, Z).` +# Note that the bond axis operator either is trivial (the identity), or a Pauli :math:`Y` operator. +# These pushing relations allow us to push the bond axis operator through to an open boundary +# bond axis of the fused MPS. In addition, we can remove the operators on the physical axes by +# applying the correct Pauli operation to the physical site, conditioned on the fusion measurement +# outcomes. If we have an MPS on :math:`L` sites, pushing through a Pauli will have the +# following effect: +# +# - If it is a Pauli :math:`Y,` all physical sites need to be corrected by a Pauli :math:`X,` and +# a Pauli :math:`Y` is pushed to the opposite bond site. +# +# - If it is a Pauli :math:`X,` a Pauli :math:`Y` is applied to the first physical qubit, and afterwards +# the case above is recovered, leading to applying Pauli :math:`X` and pushing through a Pauli :math:`Y.` +# +# - If it is a Pauli :math:`Z,` a Pauli :math:`Z` is applied to the first physical qubit and we are done. +# No operator is pushed through on the bond axis. +# +# We can recast this condition into the following dynamic circuit instructions: +# +# - If the first/second bit of the measurement outcome bitstring is active, apply a Pauli :math:`Z/Y` +# to the first physical qubit. +# +# - If the second bit is active, apply a Pauli :math:`X` to all other physical qubits. +# +# This pushing and correction step is captured by the following function. + + +def push_and_correct(op_id, phys_wires): + """Push an operator from left to right along the bond sites of an MPS and + conditionally apply correcting operations on the corresponding physical sites. + - The operator is given by two bits indicating the Pauli operator type. + - The physical MPS sites are given by phys_wires + """ + w = phys_wires[0] + + # Apply Z if input is Z or Y + qml.cond(op_id[0], qml.Z)(w) + # Apply Y if input is X or Y + qml.cond(op_id[1], qml.Y)(w) + # Apply X on other physical sites if input is X or Y + for i in phys_wires[1:]: + qml.cond(op_id[1], qml.X)(i) + # Push through Y if input is X or Y + return np.array([op_id[1], op_id[1]]) + + +###################################################################### +# Piecing the algorithm together +# ------------------------------ +# +# Now that we discussed the sequential preparation algorithm, fusion with mid-circuit +# measurements, and operator pushing, we have all components for the constant-depth +# preparation algorithm. As inputs it requires the tensor :math:`A` (or the unitary +# :math:`U` to reproduce :math:`A`), the total number of physical sites :math:`N,` +# and the block size :math:`q.` +# +# #. Prepare :math:`\frac{N}{q}` MPS of size :math:`q` +# in parallel, using the sequential preparation algorithm (without the final +# projection step). +# +# #. Fuse together the blocks of MPS using mid-circuit measurements and store the +# measurement outcomes. +# +# #. Push the resulting defect operators to one of the outer bond sites and +# conditionally apply correction operators to the physical sites. +# +# #. Undo the operator that was pushed to the outer bond site by conditionally applying +# its inverse. +# +# #. Perform the same projection step as in the sequential preparation algorithm on +# the two remaining bond sites. +# +# We summarize the algorithm in the following sketch, which again follows [#smith]_. +# +# .. image:: ../_static/demonstration_assets/constant_depth_mps_prep/algorithm.png +# :width: 90% +# :align: center +# +# It is important to remember that showing the existence of suitable operator pushing +# relations (and finding them explicitly) is a crucial step of the algorithm, which goes +# beyond the scope of the demo. +# +# The projective measurement step at the end is the same as for the sequential +# preparation algorithm, and therefore is *probabilistic*, with the same +# success probability. +# +# **The block size** :math:`q` +# +# The size of the blocks that have to be prepared in the first step depends on multiple +# considerations. First, the block must be such that operator pushing relations are +# available. Often this will determine a minimal block size, and multiples of that +# size are valid choices as well. Second, the depth of the (parallelized) sequential +# preparation circuits is proportional to :math:`q,` determining a key contribution +# to the overall depth of the algorithm. Third, the sequential algorithm requires +# two bond qudits for each block, leading to :math:`\frac{2N}{q}` auxiliary qudits +# overall. Note how the product of the depth and the number of auxiliary qubits is +# linear in :math:`N.` +# +# **Example** +# +# For our example MPS :math:`|\Psi(g)\rangle,` we already defined and coded up the +# sequential preparation circuit for a flexible number of qubits (Step 1), +# the measurement-based fusion (Step 2), and the operator pushing and correction step +# (Step 3). This makes putting the algorithm together quite simple. We only need to +# define an XOR for our operator bit strings and a function that applies a correcting +# Pauli operator to the final bond site after pushing through the operator (Step 4). + + +def xor(op_id_0, op_id_1): + """Express logical XOR as "SUM - 2 * AND" on integers.""" + return op_id_0 + op_id_1 - 2 * op_id_0 * op_id_1 + + +def correct_end_bond(bond_idx, op_id): + """Perform a correction on the end bond site.""" + # Apply Z if correction op is Z or Y + qml.cond(op_id[0], qml.Z)(bond_idx) + # Apply X if correction op is X or Y + qml.cond(op_id[1], qml.X)(bond_idx) + + +def constant_depth(N, g, q): + """Prepare the MPS |Ψ(g)> in constant depth.""" + num_blocks = N // q + block_wires = q + 2 # 2 bond wires added + + # Step 1: Prepare small block MPS + for i in range(num_blocks): + wires = list(range(block_wires * i, block_wires * (i + 1))) + sequential_preparation(g, wires) + + # Step 2: Fusion with mid-circuit measurements + mcms = [] + for i in range(1, num_blocks): + bond_wires = (block_wires * i - 1, block_wires * i) + mcms.append(fuse(*bond_wires)) + + # Step 3: Push operators through to highest-index wire and correct phys. sites + pushed_op_id = np.array([0, 0]) # Start with identity + for i in range(1, num_blocks): + phys_wires = list(range(block_wires * i + 1, block_wires * (i + 1) - 1)) + pushed_op_id = push_and_correct(xor(mcms[i - 1], pushed_op_id), phys_wires) + + # Step 4: Undo the pushed-through operator on the highest-index wire bond site. + correct_end_bond(num_blocks * block_wires - 1, pushed_op_id) + + +def constant_depth_ansatz(N, g, q): + """Circuit ansatz for constant-depth preparation routine.""" + num_blocks = N // q + block_wires = q + 2 + outer_bond_sites = [0, num_blocks * block_wires - 1] + # Steps 1-4 + constant_depth(N, g, q) + # Step 5: Perform projective measurement on outer-most bond sites. + project_measure(*outer_bond_sites) + # Collect wire ranges for physical wires, skipping bond wires + phys_wires = ( + range(block_wires * i + 1, block_wires * (i + 1) - 1) for i in range(num_blocks) + ) + # Turn ranges to lists and sum them together + return sum(map(list, phys_wires), start=[]) + + +@qml.qnode(dev) +def constant_depth_circuit(N, g, q): + phys_wires = constant_depth_ansatz(N, g, q) + return qml.probs(wires=phys_wires) + + +###################################################################### +# +# We built the full constant-depth circuit to prepare the MPS +# :math:`|\Psi(g)\rangle.` +# Before we evaluate the states it produces, let's see how the circuit looks. +# We'll add some boxes for the individual subroutines, and recommend opening +# the figure in a separate tab. +# + +N = 9 +q = 3 +g = -0.8 +fig, ax = qml.draw_mpl(constant_depth_circuit)(N, g, q) + +# Cosmetics +options = { + "facecolor": "white", + "linewidth": 2, + "zorder": -1, + "boxstyle": "round, pad=0.1", +} +text_options = {"fontsize": 15, "ha": "center", "va": "top"} +box_data = [ + ((-0.45, -0.35), 1.7, 4.7, "#FF87EB"), # Bond entangling 1 + ((-0.45, 4.65), 1.7, 4.7, "#FF87EB"), # Bond entangling 2 + ((-0.45, 9.65), 1.7, 4.7, "#FF87EB"), # Bond entangling 3 + ((1.55, 0.55), 2.85, 3.8, "#FFE096"), # Sequential prep 1 + ((1.55, 5.55), 2.85, 3.8, "#FFE096"), # Sequential prep 2 + ((1.55, 10.55), 2.85, 3.8, "#FFE096"), # Sequential prep 3 + ((4.7, 3.6), 1.65, 1.65, "#D7A2F6"), # Fuse 1 and 2 + ((8.7, 8.65), 1.65, 1.65, "#D7A2F6"), # Fuse (1,2) and 3 + ((6.65, 3.6), 9.7, 4.75, "#70CEFF"), # Push and correct b/w 1 and 2 + ((10.65, 8.65), 9.7, 4.75, "#70CEFF"), # Push and correct b/w (1, 2) and 3 + ((20.6, 13.6), 1.75, 0.8, "#C756B2"), # Correct pushed bond operator + ((22.7, -0.35), 2.7, 14.75, "#B5F2ED"), # Projective measurement +] + +labels = [ + ("Step 1a", 0.5, 14.75), + ("Step 1b", 3.0, 14.75), + ("Step 2", 5.5, 5.75), + ("Step 2", 9.5, 10.75), + ("Step 3", 17.5, 7.5), + ("Step 4", 21.5, 13), + ("Step 5", 24, 14.75), +] + +for xy, width, height, color in box_data: + ax.add_patch(FancyBboxPatch(xy, width, height, edgecolor=color, **options)) +for label, x, y in labels: + t = ax.text(x, y, label, **text_options) + t.set_bbox({"facecolor": "white", "alpha": 0.85, "edgecolor": "white"}) + +###################################################################### +# +# Let's check that the constant-depth circuit produces the same probability +# distribution as the sequential circuit. +# + +p_seq = sequential_circuit(N, g) +p_const = constant_depth_circuit(N, g, q) +print(f"The two probabilities agree for {g=:.1f}: {np.allclose(p_seq, p_const)}") +plt.show() + + +###################################################################### +# Nice, we arrived at a *constant-depth* circuit that reproduces the existing sequential +# preparation circuit, which has linear depth! +# Note that while the dynamic operations of steps 2 and 3 appear visually to have a linear depth, +# they can be applied in parallel because they are only controlled by classical +# registers. +# +# Conclusion +# ---------- +# +# We successfully implemented a constant-depth dynamic quantum circuit that +# prepares an MPS with a parametrized correlation length. In particular, +# we saw that a dynamic quantum circuit can reach quantum states in constant depth +# that require linear depth with purely unitary circuits. +# The cost for this advantage are the auxiliary qubits we had to add as intermediate +# bond sites. +# This constant-depth algorithm is an exciting advance in +# state preparation techniques, alleviating requirements of coherence times and +# connectivity on hardware. +# +# We want to note that there are other MPS preparation algorithms with depths that only +# scale logarithmically in the qubit count [#malz1]_. They, too, can be improved further by +# using mid-circuit measurements and dynamic circuit operations [#malz2]_ +# +# For more information, consider the original article, the mentioned reviews, as well +# as our demos on dynamic circuits and tensor network states. +# +# Happy preparing! +# +# References +# ---------- +# +# .. [#smith] +# +# Kevin C. Smith, Abid Khan, Bryan K. Clark, S.M. Girvin, Tzu-Chieh Wei +# "Constant-depth preparation of matrix product states with adaptive quantum circuits", +# `arXiv:2404.16083 `__, 2024. +# +# .. [#schoen] +# +# C. Schön, E. Solano, F. Verstraete, J. I. Cirac, M. M. Wolf +# "Sequential Generation of Entangled Multiqubit States", Physical Review Letters, **95**, 110503, +# `closed access `__, 2005. +# `arXiv:quant-ph/0501096 `__, 2005. +# +# .. [#schollwoeck] +# +# U. Schollwoeck +# "The density-matrix renormalization group in the age of matrix product states", +# Annals of Physics **326**, 96, +# `closed access `__, 2011. +# `arXiv:1008.3477 `__, 2010. +# +# .. [#malz1] +# +# Z. Wei, D. Malz, J. I. Cirac +# "Efficient adiabatic preparation of tensor network states", Physical Review Research, **5**, L022037, +# `open access `__, 2023. +# +# .. [#malz2] +# +# Z. Wei, D. Malz, J. I. Cirac +# "Preparation of Matrix Product States with Log-Depth Quantum Circuits", Physical Review Letters, **132**, 040404, +# `open access `__, 2024. +# diff --git a/demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json b/demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json new file mode 100644 index 0000000000..727931b57e --- /dev/null +++ b/demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json @@ -0,0 +1,49 @@ +{ + "title": "Constant-depth preparation of matrix product states with dynamic circuits", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-10-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-09T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_constant_depth_mps_prep.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_constant_depth_mps_prep.png" + } + ], + "seoDescription": "Prepare a matrix product state (MPS) in constant depth with mid-circuit measurements and feedforward control.", + "doi": "", + "references": [], + "basedOnPapers": [ + "10.48550/arXiv.2404.16083" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_mcm_introduction", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mps", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_tn_circuits", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in b/demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_contextuality/demo.py b/demonstrations_v2/tutorial_contextuality/demo.py new file mode 100644 index 0000000000..53db5fc211 --- /dev/null +++ b/demonstrations_v2/tutorial_contextuality/demo.py @@ -0,0 +1,804 @@ +r""" +Contextuality and inductive bias in QML +============================================================ + +.. meta:: + :property="og:description": Train a tailored quantum model on a contextuality-inspired dataset + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/contextuality_thumbnail.png + +.. related:: + tutorial_geometric_qml + +*Author: Joseph Bowles — Posted: 21 March 2023* + +""" + + +###################################################################### +# What machine learning problems are quantum computers likely to excel +# at? +# +# In the article *Contextuality and inductive bias in quantum machine +# learning* [#paper]_ by Joseph Bowles, +# Victoria J Wright, Máté Farkas, Nathan Killoran and Maria Schuld, we +# look to contextuality for answers to this question. +# +# Contextuality is a nonclassical phenomenon exhibited by quantum +# systems, and it is necessary for computational advantage relative to +# classical machines. To be a little more specific, we focus on the +# framework of *generalized +# contextuality* [#contextuality]_, +# which was introduced by Robert Spekkens in 2004. We find learning +# problems for which contextuality plays a key role, and these problems +# may therefore be good areas where quantum machine learning algorithms +# shine. In this demo we will: +# +# - Describe a specific example of a contextuality-relevant problem that is based on the +# well-known rock-paper-scissors game, and +# - Construct and train a quantum model that is tailored to the +# symmetries of the problem. +# +# Throughout the demo we will make use of JAX to vectorise and just-in-time compile +# certain functions, which will speed things up. For more information on how to +# combine JAX and PennyLane, see the PennyLane +# `documentation `__. +# +# .. figure:: ../_static/demonstration_assets/contextuality/socialthumbnail_large_Contextuality.png +# :align: center +# :width: 50% +# + +###################################################################### +# Generalized contextuality +# ------------------------- +# + + +###################################################################### +# Suppose we want to prepare the maximally mixed state of a single qubit, +# with :math:`\rho = \frac{1}{2}\mathbb{I}.` Although this corresponds to a +# single density matrix, there are many ways we could prepare the state. +# For example, we could mix the states :math:`\vert 0 \rangle` or +# :math:`\vert 1 \rangle` with equal probability. Alternatively, we could +# use the :math:`X` basis, and mix the states :math:`\vert + \rangle` or +# :math:`\vert - \rangle.` Even though this may not strike us as particularly +# strange, a remarkable coincidence is in fact going on here: an +# experimentalist can perform two physically distinct procedures (namely, +# preparing :math:`\rho` in the :math:`Z` or :math:`X` basis), however it +# is impossible to distinguish which procedure was performed, since they +# both result in the same density matrix and therefore give identical +# predictions for all future measurements. +# +# Such a coincidence demands an explanation. Something that one might expect +# is the following: the description of the experiment in terms of quantum +# states is not the most fundamental, and there are in fact other states +# (we’ll write them as :math:`\lambda`), that comprise our quantum states. +# In contextuality these are called *ontic states*, although they also go +# by the name of *hidden variables*. When we prepare a state +# :math:`\vert 0 \rangle`, :math:`\vert 1 \rangle,` +# :math:`\vert + \rangle`, :math:`\vert - \rangle,` what is really going +# on is that we prepare a mixture :math:`P_{\vert 0 \rangle}(\lambda),` +# :math:`P_{\vert 1 \rangle}(\lambda),` +# :math:`P_{\vert + \rangle}(\lambda),` +# :math:`P_{\vert - \rangle}(\lambda)` over the true ontic states. One may +# imagine that the corresponding mixtures over the :math:`\lambda` s are +# the same for the :math:`Z` and :math:`X` basis preparation: +# +# .. math:: \frac{1}{2}P_{\vert 0 \rangle}(\lambda)+\frac{1}{2}P_{\vert 1 \rangle}(\lambda)=\frac{1}{2}P_{\vert + \rangle}(\lambda)+\frac{1}{2}P_{\vert - \rangle}(\lambda). +# +# This is a rather natural explanation of our coincidence: the two +# procedures are indistinguishable because they actually correspond to the +# same mixture over the fundamental states :math:`\lambda.` This sort of +# explanation is called *non-contextual*, since the two mixtures do not +# depend on the basis (that is, the context) in which the state is +# prepared. It turns out that if one tries to apply this logic to all the +# indistinguishabilities in quantum theory, one arrives at contradictions: +# it simply cannot be done. For this reason we say that quantum theory is +# a *contextual* theory. +# +# In the paper we frame generalized contextuality in the machine learning +# setting, which allows us to define what we mean by a contextual learning +# model. In a nutshell, this definition demands that if a learning model +# is non-contextual, then any indistinguishabilities in the model should +# be explained in a non-contextual way similar to the above. This results +# in constraints on the learning model, which limits their expressivity. +# Since quantum models are contextual, they can of course go beyond these +# constraints, and understanding when and how they do this may shed light +# on the non-classical features that separate quantum models from +# classical ones. +# +# Below we describe a specific learning problem that demonstrates this +# approach. As we will see, the corresponding indistinguishability relates +# to an *inductive bias* of the learning model. +# + + +###################################################################### +# The rock-paper-scissors game +# ------------------------------ +# + + +###################################################################### +# The learning problem we will consider involves three players +# (we'll call them players 0, 1 and 2) playing a +# variant of the rock-paper-scissors game with a referee. +# The game goes as follows. In each round, a player can choose to play +# either rock (R), paper (P) or scissors (S). Each player also has a +# ‘special’ action. For player 0 it is R, for player 1 it is P and for +# player 2 it is S. The actions of the players are then compared pairwise, +# with the following rules: +# +# - If two players play different actions, then one player beats the +# other following the usual rule (rock beats scissors, scissors beats +# paper, paper beats rock). +# - If two players play the same action, the one who plays their special +# action beats the other. If neither plays their special action, it is +# a draw. +# +# A referee then decides the winners and the losers of that round: the +# winners receive :math:`\$1` and the losers lose :math:`\$1` (we will +# call this their *payoff* for that round). +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/contextuality/rps.png +# :align: center +# :width: 50% + +###################################################################### +# Naturally, the more players a given player beats, the higher the +# probability that they get a positive payoff. In particular, if we denote +# the payoff of player :math:`k` by :math:`y_k=\pm1` then +# +# .. math:: \mathbb{E}(y_k) = \frac{n^k_{\text{win}}-n^k_{\text{lose}}}{2}, +# +# where :math:`n^k_{\text{win}},` :math:`n^k_{\text{lose}}` is the number +# of players that player :math:`k` beats or loses to in that round. This +# ensures that a player is certain to get a positive (or negative) payoff +# if they beat (or lose) to everyone. +# +# To make this concrete, we will construct three 3x3 matrices ``A01``, +# ``A02``, ``A12`` which determine the rules for each pair of players. +# ``A01`` contains the expected payoff values of player 0 when playing +# against player 1. Using the rules of the game it looks as follows. +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/contextuality/rpstable.png +# :align: center +# :width: 50% + + +###################################################################### +# The matrices ``A02`` and ``A12`` are defined similarly. +# + +import jax +import jax.numpy as jnp +import pennylane as qml +import numpy as np +jax.config.update("jax_platform_name", "cpu") +np.random.seed(666) # seed used for random functions + +A01 = np.array([[1, -1, 1], [1, -1, -1], [-1, 1, 0]]) # rules for player 0 vs player 1 +A02 = np.array([[1, -1, 1], [1, 0, -1], [-1, 1, -1]]) +A12 = np.array([[0, -1, 1], [1, 1, -1], [-1, 1, -1]]) + + +###################################################################### +# We can also define the matrices ``A10``, ``A20``, ``A21``. Since +# switching the players corresponds to taking the transpose matrix and +# a positive payoff for one player implies a negative for the other, +# these matrices are given by +# the negative of the transposed matrix: +# + +A10 = -A01.T # rules for player 1 vs player 0 +A20 = -A02.T +A21 = -A12.T + +###################################################################### +# Note that the above game is an example of a *zero-sum game*: if player 1 beats +# player 2 then necessarily player 2 loses to player 1. This implies +# :math:`\sum_k n^k_{\text{wins}}=\sum_kn^k_{\text{lose}}` and so in every +# round we have +# +# .. math:: \mathbb{E}(y_1)+\mathbb{E}(y_2)+\mathbb{E}(y_3)=0. +# + + +###################################################################### +# Constructing the dataset +# ------------ +# + + +###################################################################### +# Here we construct a dataset based on the above game. Our data points +# correspond to probability +# distributions over possible actions: in the zero-sum game literature +# these are called *strategies*. +# For example, a strategy for player k is a +# vector +# +# .. math:: x_k=(P(a_k=R), P(a_k=P), P(a_k=S)) +# +# where :math:`a_k` denotes player :math:`k`\ ’s action. We collect these +# into a strategy matrix X +# +# .. math:: +# +# X = \begin{pmatrix} +# P(a_0=R) & P(a_0=P) & P(a_0=S) \\ +# P(a_1=R) & P(a_1=P) & P(a_1=S) \\ +# P(a_2=R) & P(a_2=P) & P(a_2=S) . +# \end{pmatrix} +# +# +# + + +###################################################################### +# Let’s write a function +# to generate a set of strategy matrices. +# + + +def get_strategy_matrices(N): + """ + Generates N strategy matrices, normalised by row + """ + X = np.random.rand(N, 3, 3) + for i in range(N): + norm = np.array(X[i].sum(axis=1)) + for k in range(3): + X[i, k, :] = X[i, k, :] / norm[k] + return X + + +###################################################################### +# The labels in our dataset correspond to payoff values :math:`y_k` of the +# three players. Following the rules of probability we find that if the +# players use strategies :math:`x_0, x_1, x_2` the expected values of +# :math:`n_{\text{wins}}^k - n_{\text{lose}}^k` are given +# by +# +# .. math:: \mathbb{E}[n_{\text{wins}}^0 - n_{\text{lose}}^0] = x_0 \cdot A_{01}\cdot x_1^T+x_0 \cdot A_{02}\cdot x_2^T +# +# .. math:: \mathbb{E}[n_{\text{wins}}^1 - n_{\text{lose}}^1] = x_1 \cdot A_{10}\cdot x_0^T+x_1 \cdot A_{12}\cdot x_2^T +# +# .. math:: \mathbb{E}[n_{\text{wins}}^2 - n_{\text{lose}}^2] = x_2 \cdot A_{20}\cdot x_0^T+x_2 \cdot A_{21}\cdot x_1^T +# +# Since we have seen that +# :math:`\mathbb{E}(y_k) = \frac{n^k_{\text{win}}-n^k_{\text{lose}}}{2}` +# it follows that the probability for player :math:`k` to receive a +# positive payoff given strategies :math:`X` is +# +# .. math:: P(y_k=+1\vert X) = \frac{\mathbb{E}(y_k\vert X)+1}{2} = \frac{(\mathbb{E}[n_{\text{wins}}^k - n_{\text{lose}}^k])/2+1}{2} +# +# Putting all this together we can write some code to generate the labels +# for our data set. +# + + +def payoff_probs(X): + """ + get the payoff probabilities for each player given a strategy matrix X + """ + n0 = X[0] @ A01 @ X[1] + X[0] @ A02 @ X[2] # n0 = + n1 = X[1] @ A10 @ X[0] + X[1] @ A12 @ X[2] + n2 = X[2] @ A20 @ X[0] + X[2] @ A21 @ X[1] + probs = (jnp.array([n0, n1, n2]) / 2 + 1) / 2 + return probs + + +# JAX vectorisation +vpayoff_probs = jax.vmap(payoff_probs) + + +def generate_data(N): + X = get_strategy_matrices(N) # strategies + P = vpayoff_probs(X) # payoff probabilities + r = np.random.rand(*P.shape) + Y = np.where(P > r, 1, -1) # sampled payoffs for data labels + return X, Y, P + + +X, Y, P = generate_data(2000) + +print(X[0]) # the first strategy matrix in our dataset +print(Y[0]) # the corresponding sampled payoff values + +###################################################################### +# Note that since strategies are probabilistic mixtures of actions, our +# data labels satisfy a zero-sum condition +# +# .. math:: \mathbb{E}(y_1\vert X_i)+\mathbb{E}(y_2\vert X_i)+\mathbb{E}(y_3\vert X_i)=0. +# +# We can verify this using the payoff probability matrix ``P`` that we +# used to sample the labels: +# + +expvals = 2 * P - 1 # convert probs to expvals +expvals[:10].sum(axis=1) # check first 10 entries + + +###################################################################### +# The learning problem +# -------------------- +# + + +###################################################################### +# Suppose we are given a data set :math:`\{X_i,\vec{y}_i\}` consisting of +# strategy matrices and payoff values, however we don’t know what the +# underlying game is (that is, we don’t know the players were playing the +# rock, paper scissors game described above). We do have one piece of +# information though: we know the game is zero-sum so that the data +# generation process satisfies +# +# .. math:: \mathbb{E}(y_0\vert X_i)+\mathbb{E}(y_1\vert X_i)+\mathbb{E}(y_2\vert X_i)=0. +# +# Can we learn the rock, paper scissors game from this data? More +# precisely, if we are given an unseen strategy matrix +# :math:`X_{\text{test}}` our task is to sample from the three +# distributions +# +# .. math:: P(y_0\vert X_{\text{test}}), P(y_1\vert X_{\text{test}}), P(y_2\vert X_{\text{test}}). +# +# Note we are not asking to sample from the joint distribution +# :math:`P(\vec{y}\vert X_{\text{test}})` but the three marginal +# distributions only. This can be seen as an instance of multi-task +# learning, where a single task corresponds to sampling the payoff for one +# of the three players. +# + + +###################################################################### +# Building inductive bias into a quantum model +# -------------------------------------------- +# + + +###################################################################### +# Here we describe a simple three qubit model to tackle this problem. +# Since we know that the data satisfies the zero-sum condition, we aim to +# create a quantum model that encodes this knowledge. That is, like +# the data we want our model to satisfy +# +# .. math:: \mathbb{E}(y_0\vert X_i)+\mathbb{E}(y_1\vert X_i)+\mathbb{E}(y_2\vert X_i)=0. +# +# In machine learning, this is called encoding an *inductive +# bias* into the model, and considerations like this are often crucial for +# good generalisation performance. +# +# .. note:: +# Since the above holds for all :math:`X_i,` it implies an +# indistinguishability of the model: if we look at one of the labels at +# random, we are equally likely to see a positive or negative payoff +# regardless of :math:`X_i,` and so the :math:`X_i` are indistinguishable +# with respect to this observation. This implies a corresponding constraint +# on non-contextual learning models, which limits their expressivity and +# may therefore hinder their performance: see the paper for more details +# on how this looks in practice. Luckily for us quantum theory is a +# contextual theory, so these limitations don’t apply to our model! +# +# The quantum model we consider has the following structure: +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/contextuality/model.png +# :align: center +# :width: 50% + + +###################################################################### +# The parameters :math:`\theta` and :math:`\alpha` are trainable +# parameters of the model, and we will use the three :math:`Z` +# measurements at the end of the circuit to sample the three labels. +# Therefore, if we write the entire circuit as +# :math:`\vert \psi(\alpha,\theta,X)\rangle` the zero sum condition will +# be satisfied if +# +# .. math:: \langle \psi(\alpha,\theta,X) \vert (Z_0+Z_1+Z_2) \vert \psi(\alpha,\theta,X) \rangle = 0. +# +# Let’s see how we can create a model class that satisfies this. For +# precise details on the structure of the model, check out Figure 6 in the +# paper. We’ll first look at the parameterised unitary :math:`V_{\alpha},` +# that we call the *input preparation unitary*. This prepares a state +# :math:`V_\alpha\vert 0 \rangle` such that +# +# .. math:: \langle 0 \vert V^\dagger_\alpha (Z_0+Z_1+Z_2) V_\alpha\vert 0 \rangle = 0. +# +# An example of such a circuit is the following. +# + + +def input_prep(alpha): + # This ensures the prepared state has =0 + qml.Hadamard(wires=0) + qml.Hadamard(wires=1) + qml.Hadamard(wires=2) + qml.RY(alpha[0], wires=0) + qml.RY(alpha[0] + np.pi, wires=1) + + +###################################################################### +# The second unitary is a *bias invariant layer*: it preserves the value +# of :math:`\langle Z_0+Z_1+Z_2 \rangle` for all input states into the +# layer. To achieve this, the generators of the unitaries in this layer +# must commute with the operator :math:`Z_0+Z_1+Z_2.` For example the +# operator :math:`X\otimes X + Y\otimes Y + Z\otimes Z` (on any pair of +# qubits) commutes with :math:`Z_0+Z_1+Z_2` and so a valid parameterised +# gate could be +# +# .. math:: e^{i\theta(X\otimes X\otimes\mathbb{I} + Y\otimes Y\otimes\mathbb{I} + Z\otimes Z\otimes\mathbb{I})}. +# +# This kind of reasoning is an example of geometric quantum machine +# learning (check out [#reptheory]_ and [#equivariant]_ or our own +# `demo `__ for an awesome introduction to the subject). +# Below we construct the +# bias invariant layer: note that all the generators commute with +# :math:`Z_0+Z_1+Z_2.` The variables ``blocks`` and ``layers`` are model +# hyperparameters that we will fix as ``blocks=1`` and ``layers=2``. +# + +blocks = 1 +layers = 2 + + +def swap_rot(weights, wires): + """ + bias-invariant unitary with swap matrix as generator + """ + qml.PauliRot(weights, "XX", wires=wires) + qml.PauliRot(weights, "YY", wires=wires) + qml.PauliRot(weights, "ZZ", wires=wires) + + +def param_unitary(weights): + """ + A bias-invariant unitary (U in the paper) + """ + for b in range(blocks): + for q in range(3): + qml.RZ(weights[b, q], wires=q) + qml.PauliRot(weights[b, 3], "ZZ", wires=[0, 1]) + qml.PauliRot(weights[b, 4], "ZZ", wires=[0, 2]) + qml.PauliRot(weights[b, 5], "ZZ", wires=[1, 2]) + swap_rot(weights[b, 6], wires=[0, 1]) + swap_rot(weights[b, 7], wires=[1, 2]) + swap_rot(weights[b, 8], wires=[0, 2]) + + +def data_encoding(x): + """ + S_x^1 in paper + """ + for q in range(3): + qml.RZ(x[q], wires=q) + + +def data_encoding_pairs(x): + """ + S_x^2 in paper + """ + qml.PauliRot(x[0] * x[1], "ZZ", wires=[0, 1]) + qml.PauliRot(x[1] * x[2], "ZZ", wires=[1, 2]) + qml.PauliRot(x[0] * x[2], "ZZ", wires=[0, 2]) + + +def bias_inv_layer(weights, x): + """ + The full bias invariant layer. + """ + # data preprocessing + x1 = jnp.array([x[0, 0], x[1, 1], x[2, 2]]) + x2 = jnp.array(([x[0, 1] - x[0, 2], x[1, 2] - x[1, 0], x[2, 0] - x[2, 1]])) + for l in range(0, 2 * layers, 2): + param_unitary(weights[l]) + data_encoding(x1) + param_unitary(weights[l + 1]) + data_encoding_pairs(x2) + param_unitary(weights[2 * layers]) + + +###################################################################### +# With our ``input_prep`` and ``bias_inv_layer`` functions we can now +# define our quantum model. +# + +dev = qml.device("default.qubit", wires=3) + + +@qml.qnode(dev) +def model(weights, x): + input_prep(weights[2 * layers + 1, 0]) # alpha is stored in the weights array + bias_inv_layer(weights, x) + return [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2))] + + +# jax vectorisation, we vectorise over the data input (the second argument) +vmodel = jax.vmap(model, (None, 0)) +vmodel = jax.jit(vmodel) + + +###################################################################### +# To investigate the effect of the encoded inductive bias, we will compare +# this model to a generic model with the same data encoding and similar +# number of parameters (46 vs 45 parameters). +# + + +def generic_layer(weights, x): + # data preprocessing + x1 = jnp.array([x[0, 0], x[1, 1], x[2, 2]]) + x2 = jnp.array(([x[0, 1] - x[0, 2], x[1, 2] - x[1, 0], x[2, 0] - x[2, 1]])) + for l in range(0, 2 * layers, 2): + qml.StronglyEntanglingLayers(weights[l], wires=range(3)) + data_encoding(x1) + qml.StronglyEntanglingLayers(weights[l + 1], wires=range(3)) + data_encoding_pairs(x2) + qml.StronglyEntanglingLayers(weights[2 * layers], wires=range(3)) + + +dev = qml.device("default.qubit", wires=3) + + +@qml.qnode(dev) +def generic_model(weights, x): + generic_layer(weights, x) + return [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2))] + + +vmodel_generic = jax.vmap(generic_model, (None, 0)) +vmodel_generic = jax.jit(vmodel_generic) + + +###################################################################### +# **Warning**: Since we are using JAX it is important that our ``model`` +# and ``generic model`` functions are functionally pure (read more +# `here `__). +# This means we cannot change the values of ``blocks`` or ``layers`` from +# hereon since these values have been cached for JIT compilation. +# + + +###################################################################### +# Training and evaluation +# ----------------------- +# + + +###################################################################### +# To train the model we will minimise the negative log likelihood of the +# labels given the data +# +# .. math:: \mathcal{L} = -\frac{1}{3\vert N \vert}\sum_{(X_i,\vec{y}_i)} \log(\mathcal{P}_0(y_i^{(0)}\vert X_i))+\log(\mathcal{P}_1(y_i^{(1)}\vert X_i))+\log(\mathcal{P}_2(y_i^{(2)}\vert X_i)) +# +# Here :math:`\mathcal{P}_k` is the probability distribution of the +# :math:`k` label from the model, :math:`y_i^{(k)}` is the kth element +# of the payoff vector :math:`\vec{y}_i` in the dataset, and :math:`N` is +# the size of the training dataset. We remark that +# training the negative log likelihood is in some sense cheating, since +# for large quantum circuits we don’t know how to estimate it efficiently. +# As generative modeling in QML progresses, we can hope however that +# scalable methods that approximate this type of training may appear. +# + + +def likelihood(weights, X, Y, model): + """ + The cost function. Returns the negative log likelihood + """ + expvals = jnp.array(model(weights, X)).T + probs = (1 + Y * expvals) / 2 # get the relevant probabilites + probs = jnp.log(probs) + llh = jnp.sum(probs) / len(X) / 3 + return -llh + + +###################################################################### +# For evaluation we will use the average KL divergence between the true +# data distribution and the model distribution +# +# .. math:: \mathbb{E}_{P^\text{data}(X)} \left[\frac{1}{3}\sum_{k=1}^{3} D_{\text{KL}}(P^\text{data}_k(y\vert X)\vert\vert \mathcal{P}_k(y\vert X)) \right]. +# +# To estimate this we sample a test set of strategies, calculate their +# payoff probabilities, and estimate the above expectation via the sample +# mean. +# + +N_test = 10000 +X_test = get_strategy_matrices(N_test) + +probs_test = np.zeros([N_test, 3, 2]) +probs_test[:, :, 0] = vpayoff_probs(X_test) # the true probabilities for the test set +probs_test[:, :, 1] = 1 - probs_test[:, :, 0] +probs_test = jnp.array(probs_test) + + +def kl_div(p, q): + """ + Get the KL divergence between two probability distribtuions + """ + p = jnp.vstack([p, jnp.ones(len(p)) * 10 ** (-8)]) # lower cutoff of prob values of 10e-8 + p = jnp.max(p, axis=0) + return jnp.sum(q * jnp.log(q / p)) # forward kl div + + +def kl_marginals(probs, probs_test): + """ + get the mean KL divergence of the three marginal distributions + (the square brackets above) + """ + kl = 0 + for t in range(3): + kl = kl + kl_div(probs[t, :], probs_test[t, :]) + return kl / 3 + + +# vectorise the kl_marginals function. Makes estimating the average KL diverence of a model faster. +vkl_marginals = jax.vmap(kl_marginals, (0, 0)) + + +def get_av_test_kl(model, weights, probs_test, X_test): + """ + returns the average KL divergence for a test set X_test. + """ + N_test = len(X_test) + probs = np.zeros(probs_test.shape) + expvals = jnp.array(model(weights, X_test)).T + for t in range(3): + probs[:, t, 0] = (1 + expvals[:, t]) / 2 + probs[:, t, 1] = (1 - expvals[:, t]) / 2 + return np.sum(vkl_marginals(probs, probs_test)) / N_test + + +###################################################################### +# To optimise the model we make use of the JAX optimization library optax. +# We will use the adam gradient descent optimizer. +# + +import optax +from tqdm import tqdm + + +def optimise_model(model, nstep, lr, weights): + plot = [[], [], []] + optimizer = optax.adam(lr) + opt_state = optimizer.init(weights) + steps = tqdm(range(nstep)) + for step in steps: + # use optax to update parameters + llh, grads = jax.value_and_grad(likelihood)(weights, X, Y, model) + updates, opt_state = optimizer.update(grads, opt_state, weights) + weights = optax.apply_updates(weights, updates) + + kl = get_av_test_kl(model, weights, probs_test, X_test) + steps.set_description( + "Current divergence: %s" % str(kl) + " :::: " + "Current likelihood: %s" % str(llh) + ) + plot[0].append(step) + plot[1].append(float(llh)) + plot[2].append(float(kl)) + return weights, llh, kl, plot + + +###################################################################### +# We are now ready to generate a data set and optimize our models! +# + +# generate data +N = 2000 # number of data points +X, Y, P = generate_data(N) + +nstep = 2000 # number of optimisation steps + +lr = 0.001 # initial learning rate +weights_model = np.random.rand(2 * layers + 2, blocks, 9) * 2 * np.pi +weights_generic = np.random.rand(2 * layers + 1, blocks, 3, 3) * 2 * np.pi + +# optimise the structured model +weights_model, llh, kl, plot_model = optimise_model(vmodel, nstep, lr, weights_model) +# optimise the generic model +weights_generic, llh, kl, plot_genereic = optimise_model(vmodel_generic, nstep, lr, weights_generic) + + +###################################################################### +# Let’s plot the average KL divergence and the negative log likelihood for +# both models. +# + +import matplotlib.pyplot as plt + +plt.style.use('pennylane.drawer.plot') + +# subplots +fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(8, 10)) +fig.tight_layout(pad=10.0) + +# KL divergence +ax1.plot(plot_model[0], plot_model[2], label="biased model") +ax1.plot(plot_genereic[0], plot_genereic[2], label="generic model") + +ax1.set_yscale("log") +ax1.set_ylabel("KL divergence (test)") +ax1.set_xlabel("training step") +ax1.legend() + +# negative log likelihood +ax2.plot(plot_model[0], plot_model[1]) +ax2.plot(plot_genereic[0], plot_genereic[1]) + +ax2.set_yscale("log") +ax2.set_ylabel("Negative log likelihood (train)") +ax2.set_xlabel("training step") + +plt.show() + +###################################################################### +# We see that the model that encodes the inductive bias achieves both a +# lower training error and generalisation error, as can be expected. +# Incorporating knowledge about the data into the model design is +# generally a very good idea! +# + + +###################################################################### +# Conclusion +# ---------- +# + +###################################################################### +# In this demo we have constructed a dataset whose structure is +# connected to generalized contextuality, and have shown how to encode +# this structure as an inductive bias of a quantum model class. As is +# often the case, we saw that this approach outperforms a generic model +# class that does not take this knowledge into account. As a general rule, +# considerations like this should be at the front of one's mind when +# building a quantum model for a specific task. +# +# That is all for this demo. In our paper [#paper]_, it is also shown how models of +# this kind can perform better than classical surrogate +# models [#surrogates]_ at this specific task, +# which further strengthens the claim that the inductive bias of the +# quantum model is useful. For more information and to read more about the +# link between contextuality and QML, check out the full paper. +# +# +# References +# ---------- +# +# .. [#paper] +# +# J. Bowles, V. J. Wright, M. Farkas, N. Killoran, M. Schuld +# "Contextuality and inductive bias in quantum machine learning." +# `arXiv:2302.01365 `__, 2023. +# +# .. [#contextuality] +# +# R. W. Spekkens +# "Contextuality for preparations, transformations, and unsharp measurements." +# `Phys. Rev. A 71, 052108 `__, 2005. +# +# .. [#reptheory] +# +# M. Ragone, P. Braccia, Q. T. Nguyen, L. Schatzki, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo +# "Representation Theory for Geometric Quantum Machine Learning." +# `arXiv:2210.07980 `__, 2023. +# +# .. [#equivariant] +# +# Q. T. Nguyen, L. Schatzki, P. Braccia, M. Ragone, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo +# "Theory for Equivariant Quantum Neural Networks." +# `arXiv:2210.08566 `__, 2022. +# +# .. [#surrogates] +# +# F. J. Schreiber, J. Eiser, J. J. Meyer +# "Classical surrogates for quantum learning models." +# `arXiv:2206.11740 `__, 2022. +# +# diff --git a/demonstrations_v2/tutorial_contextuality/metadata.json b/demonstrations_v2/tutorial_contextuality/metadata.json new file mode 100644 index 0000000000..30808e2b99 --- /dev/null +++ b/demonstrations_v2/tutorial_contextuality/metadata.json @@ -0,0 +1,82 @@ +{ + "title": "Contextuality and inductive bias in QML", + "authors": [ + { + "username": "josephbowles" + } + ], + "dateOfPublication": "2023-09-06T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/contextuality/thumbnail_tutorial_Contextuality.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Contextuality.png" + } + ], + "seoDescription": "Train a problem-inspired ansatz on a contextuality-inspired dataset.", + "doi": "", + "references": [ + { + "id": "paper", + "type": "article", + "title": "Contextuality and inductive bias in quantum machine learning.", + "authors": "J. Bowles, V. J. Wright, M. Farkas, N. Killoran, M. Schuld", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2302.01365" + }, + { + "id": "contextuality", + "type": "article", + "title": "Contextuality for preparations, transformations, and unsharp measurements.", + "authors": "R. W. Spekkens", + "year": "2005", + "journal": "Phys. Rev. A 71", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.71.052108" + }, + { + "id": "reptheory", + "type": "article", + "title": "Representation Theory for Geometric Quantum Machine Learning.", + "authors": "M. Ragone, P. Braccia, Q. T. Nguyen, L. Schatzki, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2210.07980" + }, + { + "id": "equivariant", + "type": "article", + "title": "Theory for Equivariant Quantum Neural Networks.", + "authors": "Q. T. Nguyen, L. Schatzki, P. Braccia, M. Ragone, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.08566" + }, + { + "id": "surrogates", + "type": "article", + "title": "Classical surrogates for quantum learning models.", + "authors": "F. J. Schreiber, J. Eiser, J. J. Meyer", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2206.11740" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_contextuality/requirements.in b/demonstrations_v2/tutorial_contextuality/requirements.in new file mode 100644 index 0000000000..2612555b82 --- /dev/null +++ b/demonstrations_v2/tutorial_contextuality/requirements.in @@ -0,0 +1,7 @@ +jax +jaxlib +matplotlib +numpy +optax +pennylane +tqdm diff --git a/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py b/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py new file mode 100644 index 0000000000..c3034b38c7 --- /dev/null +++ b/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py @@ -0,0 +1,470 @@ +r""" +.. _data_reuploading_classifier: + +Data-reuploading classifier +=========================== + +.. meta:: + :property="og:description": Implement a single-qubit universal quantum classifier using PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/universal_dnn1.png + +.. related:: + + tutorial_variational_classifier Variational classifier + tutorial_multiclass_classification Multiclass margin classifier + tutorial_expressivity_fourier_series Quantum models as Fourier series + +*Author: Shahnawaz Ahmed — Posted: 11 October 2019. Last updated: 19 January 2021.* + +A single-qubit quantum circuit which can implement arbitrary unitary +operations can be used as a universal classifier much like a single +hidden-layered Neural Network. As surprising as it sounds, +`Pérez-Salinas et al. (2019) `_ +discuss this with their idea of 'data +reuploading'. It is possible to load a single qubit with arbitrary +dimensional data and then use it as a universal classifier. + +In this example, we will implement this idea with Pennylane - a +python based tool for quantum machine learning, automatic +differentiation, and optimization of hybrid quantum-classical +computations. + +Background +---------- + +We consider a simple classification problem and will train a +single-qubit variational quantum circuit to achieve this goal. The data +is generated as a set of random points in a plane :math:`(x_1, x_2)` and +labeled as 1 (blue) or 0 (red) depending on whether they lie inside or +outside a circle. The goal is to train a quantum circuit to predict the +label (red or blue) given an input point's coordinate. + +.. figure:: ../_static/demonstration_assets/data_reuploading/universal_circles.png + :scale: 65% + :alt: circles + + +Transforming quantum states using unitary operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A single-qubit quantum state is characterized by a two-dimensional state +vector and can be visualized as a point in the so-called Bloch sphere. +Instead of just being a 0 (up) or 1 (down), it can exist in a +superposition with say 30% chance of being in the :math:`|0 \rangle` and +70% chance of being in the :math:`|1 \rangle` state. This is represented +by a state vector :math:`|\psi \rangle = \sqrt{0.3}|0 \rangle + \sqrt{0.7}|1 \rangle` - +the probability "amplitude" of the quantum state. In general we can take +a vector :math:`(\alpha, \beta)` to represent the probabilities of a qubit +being in a particular state and visualize it on the Bloch sphere as an +arrow. + +.. figure:: ../_static/demonstration_assets/data_reuploading/universal_bloch.png + :scale: 65% + :alt: bloch + +Data loading using unitaries +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to load data onto a single qubit, we use a unitary operation +:math:`U(x_1, x_2, x_3)` which is just a parameterized +matrix multiplication representing the rotation of the state vector in +the Bloch sphere. E.g., to load :math:`(x_1, x_2)` into the qubit, we +just start from some initial state vector, :math:`|0 \rangle,` +apply the unitary operation :math:`U(x_1, x_2, 0)` and end up at a new +point on the Bloch sphere. Here we have padded 0 since our data is only +2D. Pérez-Salinas et al. (2019) discuss how to load a higher +dimensional data point (:math:`[x_1, x_2, x_3, x_4, x_5, x_6]`) by +breaking it down in sets of three parameters +(:math:`U(x_1, x_2, x_3), U(x_4, x_5, x_6)`). + +Model parameters with data re-uploading +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once we load the data onto the quantum circuit, we want to have some +trainable nonlinear model similar to a neural network as well as a way of +learning the weights of the model from data. This is again done with +unitaries, :math:`U(\theta_1, \theta_2, \theta_3),` such that we load the +data first and then apply the weights to form a single layer +:math:`L(\vec \theta, \vec x) = U(\vec \theta)U(\vec x).` In principle, +this is just application of two matrix multiplications on an input +vector initialized to some value. In order to increase the number of +trainable parameters (similar to increasing neurons in a single layer of +a neural network), we can reapply this layer again and again with new +sets of weights, +:math:`L(\vec \theta_1, \vec x) L(\vec \theta_2, , \vec x) ... L(\vec \theta_L, \vec x)` +for :math:`L` layers. The quantum circuit would look like the following: + +.. figure:: ../_static/demonstration_assets/data_reuploading/universal_layers.png + :scale: 75% + :alt: Layers + + +The cost function and "nonlinear collapse" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So far, we have only performed linear operations (matrix +multiplications) and we know that we need to have some nonlinear +squashing similar to activation functions in neural networks to really +make a universal classifier (Cybenko 1989). Here is where things gets a +bit quantum. After the application of the layers, we will end up at some +point on the Bloch sphere due to the sequence of unitaries implementing +rotations of the input. These are still just linear transformations of +the input state. Now, the output of the model should be a class label +which can be encoded as fixed vectors (Blue = :math:`[1, 0],` Red = +:math:`[0, 1]`) on the Bloch sphere. We want to end up at either of them +after transforming our input state through alternate applications of +data layer and weights. + +We can use the idea of the "collapse" of our quantum state into +one or other class. This happens when we measure the quantum state which +leads to its projection as either the state 0 or 1. We can compute the +fidelity (or closeness) of the output state to the class label making +the output state jump to either :math:`| 0 \rangle` or +:math:`|1\rangle.` By repeating this process several times, we can +compute the probability or overlap of our output to both labels and +assign a class based on the label our output has a higher overlap. This +is much like having a set of output neurons and selecting the one which +has the highest value as the label. + +We can encode the output label as a particular quantum state that we want +to end up in and use Pennylane to find the probability of ending up in that +state after running the circuit. We construct an observable corresponding to +the output label using the `Hermitian `_ +operator. The expectation value of the observable gives the overlap or fidelity. +We can then define the cost function as the sum of the fidelities for all +the data points after passing through the circuit and optimize the parameters +:math:`(\vec \theta)` to minimize the cost. + +.. math:: + + \texttt{Cost} = \sum_{\texttt{data points}} (1 - \texttt{fidelity}(\psi_{\texttt{output}}(\vec x, \vec \theta), \psi_{\texttt{label}})) + +Now, we can use our favorite optimizer to maximize the sum of the +fidelities over all data points (or batches of datapoints) and find the +optimal weights for classification. Gradient-based optimizers such as +Adam (Kingma et. al., 2014) can be used if we have a good model of +the circuit and how noise might affect it. Or, we can use some +gradient-free method such as L-BFGS (Liu, Dong C., and Nocedal, J., 1989) +to evaluate the gradient and find the optimal weights where we can +treat the quantum circuit as a black-box and the gradients are computed +numerically using a fixed number of function evaluations and iterations. +The L-BFGS method can be used with the PyTorch interface for Pennylane. + +Multiple qubits, entanglement and Deep Neural Networks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Universal Approximation Theorem declares that a neural network with +two or more hidden layers can serve as a universal function approximator. +Recently, we have witnessed remarkable progress of learning algorithms using +Deep Neural Networks. + +Pérez-Salinas et al. (2019) make a connection to Deep Neural Networks by +describing that in their approach the +"layers" :math:`L_i(\vec \theta_i, \vec x )` are analogous to the size +of the intermediate hidden layer of a neural network. And the concept of +deep (multiple layers of the neural network) relates to the number +of qubits. So, multiple qubits with entanglement between them could +provide some quantum advantage over classical neural networks. But here, +we will only implement a single qubit classifier. + +.. figure:: ../_static/demonstration_assets/data_reuploading/universal_dnn.png + :scale: 35% + :alt: DNN + +"Talk is cheap. Show me the code." - Linus Torvalds +--------------------------------------------------- +""" + +import pennylane as qml +from pennylane import numpy as np +from pennylane.optimize import AdamOptimizer, GradientDescentOptimizer + +import matplotlib.pyplot as plt + + +# Set a random seed +np.random.seed(42) + + +# Make a dataset of points inside and outside of a circle +def circle(samples, center=[0.0, 0.0], radius=np.sqrt(2 / np.pi)): + """ + Generates a dataset of points with 1/0 labels inside a given radius. + + Args: + samples (int): number of samples to generate + center (tuple): center of the circle + radius (float: radius of the circle + + Returns: + Xvals (array[tuple]): coordinates of points + yvals (array[int]): classification labels + """ + Xvals, yvals = [], [] + + for i in range(samples): + x = 2 * (np.random.rand(2)) - 1 + y = 0 + if np.linalg.norm(x - center) < radius: + y = 1 + Xvals.append(x) + yvals.append(y) + return np.array(Xvals, requires_grad=False), np.array(yvals, requires_grad=False) + + +def plot_data(x, y, fig=None, ax=None): + """ + Plot data with red/blue values for a binary classification. + + Args: + x (array[tuple]): array of data points as tuples + y (array[int]): array of data points as tuples + """ + if fig == None: + fig, ax = plt.subplots(1, 1, figsize=(5, 5)) + reds = y == 0 + blues = y == 1 + ax.scatter(x[reds, 0], x[reds, 1], c="red", s=20, edgecolor="k") + ax.scatter(x[blues, 0], x[blues, 1], c="blue", s=20, edgecolor="k") + ax.set_xlabel("$x_1$") + ax.set_ylabel("$x_2$") + + +Xdata, ydata = circle(500) +fig, ax = plt.subplots(1, 1, figsize=(4, 4)) +plot_data(Xdata, ydata, fig=fig, ax=ax) +plt.show() + + +# Define output labels as quantum state vectors +def density_matrix(state): + """Calculates the density matrix representation of a state. + + Args: + state (array[complex]): array representing a quantum state vector + + Returns: + dm: (array[complex]): array representing the density matrix + """ + return state * np.conj(state).T + + +label_0 = [[1], [0]] +label_1 = [[0], [1]] +state_labels = np.array([label_0, label_1], requires_grad=False) + + +############################################################################## +# Simple classifier with data reloading and fidelity loss +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +dev = qml.device("lightning.qubit", wires=1) +# Install any pennylane-plugin to run on some particular backend + + +@qml.qnode(dev) +def qcircuit(params, x, y): + """A variational quantum circuit representing the Universal classifier. + + Args: + params (array[float]): array of parameters + x (array[float]): single input vector + y (array[float]): single output state density matrix + + Returns: + float: fidelity between output state and input + """ + for p in params: + qml.Rot(*x, wires=0) + qml.Rot(*p, wires=0) + return qml.expval(qml.Hermitian(y, wires=[0])) + + +def cost(params, x, y, state_labels=None): + """Cost function to be minimized. + + Args: + params (array[float]): array of parameters + x (array[float]): 2-d array of input vectors + y (array[float]): 1-d array of targets + state_labels (array[float]): array of state representations for labels + + Returns: + float: loss value to be minimized + """ + # Compute prediction for each input in data batch + loss = 0.0 + dm_labels = [density_matrix(s) for s in state_labels] + for i in range(len(x)): + f = qcircuit(params, x[i], dm_labels[y[i]]) + loss = loss + (1 - f) ** 2 + return loss / len(x) + + +############################################################################## +# Utility functions for testing and creating batches +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +def test(params, x, y, state_labels=None): + """ + Tests on a given set of data. + + Args: + params (array[float]): array of parameters + x (array[float]): 2-d array of input vectors + y (array[float]): 1-d array of targets + state_labels (array[float]): 1-d array of state representations for labels + + Returns: + predicted (array([int]): predicted labels for test data + output_states (array[float]): output quantum states from the circuit + """ + fidelity_values = [] + dm_labels = [density_matrix(s) for s in state_labels] + predicted = [] + + for i in range(len(x)): + fidel_function = lambda y: qcircuit(params, x[i], y) + fidelities = [fidel_function(dm) for dm in dm_labels] + best_fidel = np.argmax(fidelities) + + predicted.append(best_fidel) + fidelity_values.append(fidelities) + + return np.array(predicted), np.array(fidelity_values) + + +def accuracy_score(y_true, y_pred): + """Accuracy score. + + Args: + y_true (array[float]): 1-d array of targets + y_predicted (array[float]): 1-d array of predictions + state_labels (array[float]): 1-d array of state representations for labels + + Returns: + score (float): the fraction of correctly classified samples + """ + score = y_true == y_pred + return score.sum() / len(y_true) + + +def iterate_minibatches(inputs, targets, batch_size): + """ + A generator for batches of the input data + + Args: + inputs (array[float]): input data + targets (array[float]): targets + + Returns: + inputs (array[float]): one batch of input data of length `batch_size` + targets (array[float]): one batch of targets of length `batch_size` + """ + for start_idx in range(0, inputs.shape[0] - batch_size + 1, batch_size): + idxs = slice(start_idx, start_idx + batch_size) + yield inputs[idxs], targets[idxs] + + +############################################################################## +# Train a quantum classifier on the circle dataset +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Generate training and test data +num_training = 200 +num_test = 2000 + +Xdata, y_train = circle(num_training) +X_train = np.hstack((Xdata, np.zeros((Xdata.shape[0], 1), requires_grad=False))) + +Xtest, y_test = circle(num_test) +X_test = np.hstack((Xtest, np.zeros((Xtest.shape[0], 1), requires_grad=False))) + + +# Train using Adam optimizer and evaluate the classifier +num_layers = 3 +learning_rate = 0.6 +epochs = 10 +batch_size = 32 + +opt = AdamOptimizer(learning_rate, beta1=0.9, beta2=0.999) + +# initialize random weights +params = np.random.uniform(size=(num_layers, 3), requires_grad=True) + +predicted_train, fidel_train = test(params, X_train, y_train, state_labels) +accuracy_train = accuracy_score(y_train, predicted_train) + +predicted_test, fidel_test = test(params, X_test, y_test, state_labels) +accuracy_test = accuracy_score(y_test, predicted_test) + +# save predictions with random weights for comparison +initial_predictions = predicted_test + +loss = cost(params, X_test, y_test, state_labels) + +print( + "Epoch: {:2d} | Cost: {:3f} | Train accuracy: {:3f} | Test Accuracy: {:3f}".format( + 0, loss, accuracy_train, accuracy_test + ) +) + +for it in range(epochs): + for Xbatch, ybatch in iterate_minibatches(X_train, y_train, batch_size=batch_size): + params, _, _, _ = opt.step(cost, params, Xbatch, ybatch, state_labels) + + predicted_train, fidel_train = test(params, X_train, y_train, state_labels) + accuracy_train = accuracy_score(y_train, predicted_train) + loss = cost(params, X_train, y_train, state_labels) + + predicted_test, fidel_test = test(params, X_test, y_test, state_labels) + accuracy_test = accuracy_score(y_test, predicted_test) + res = [it + 1, loss, accuracy_train, accuracy_test] + print( + "Epoch: {:2d} | Loss: {:3f} | Train accuracy: {:3f} | Test accuracy: {:3f}".format( + *res + ) + ) + + +############################################################################## +# Results +# ~~~~~~~ + +print( + "Cost: {:3f} | Train accuracy {:3f} | Test Accuracy : {:3f}".format( + loss, accuracy_train, accuracy_test + ) +) + +print("Learned weights") +for i in range(num_layers): + print("Layer {}: {}".format(i, params[i])) + + +fig, axes = plt.subplots(1, 3, figsize=(10, 3)) +plot_data(X_test, initial_predictions, fig, axes[0]) +plot_data(X_test, predicted_test, fig, axes[1]) +plot_data(X_test, y_test, fig, axes[2]) +axes[0].set_title("Predictions with random weights") +axes[1].set_title("Predictions after training") +axes[2].set_title("True test data") +plt.tight_layout() +plt.show() + + +############################################################################## +# References +# ---------- +# [1] Pérez-Salinas, Adrián, et al. "Data re-uploading for a universal +# quantum classifier." arXiv preprint arXiv:1907.02085 (2019). +# +# [2] Kingma, Diederik P., and Ba, J. "Adam: A method for stochastic +# optimization." arXiv preprint arXiv:1412.6980 (2014). +# +# [3] Liu, Dong C., and Nocedal, J. "On the limited memory BFGS +# method for large scale optimization." Mathematical programming +# 45.1-3 (1989): 503-528. +# +# diff --git a/demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json b/demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json new file mode 100644 index 0000000000..9b078937cf --- /dev/null +++ b/demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json @@ -0,0 +1,72 @@ +{ + "title": "Data-reuploading classifier", + "authors": [ + { + "username": "quantshah" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-15T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_data-reuploading_classified.png" + } + ], + "seoDescription": "Implement a single-qubit universal quantum classifier using PennyLane.", + "doi": "", + "references": [ + { + "id": "PerezSalinas2019", + "type": "article", + "title": "Data re-uploading for a universal quantum classifier", + "authors": "P\u00e9rez-Salinas, Adri\u00e1n, et al.", + "year": "2019", + "journal": "", + "url": "" + }, + { + "id": "Kingma2014", + "type": "article", + "title": "Adam: A method for stochastic optimization", + "authors": "Kingma, Diederik P., and Ba, J.", + "year": "2014", + "journal": "", + "url": "" + }, + { + "id": "Liu1989", + "type": "article", + "title": "On the limited memory BFGS method for large scale optimization.", + "authors": "Liu, Dong C., and Nocedal, J.", + "year": "1989", + "journal": "Mathematical programming", + "url": "" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1907.02085" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_multiclass_classification", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in b/demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_diffable-mitigation/demo.py b/demonstrations_v2/tutorial_diffable-mitigation/demo.py new file mode 100644 index 0000000000..7c0b4249e2 --- /dev/null +++ b/demonstrations_v2/tutorial_diffable-mitigation/demo.py @@ -0,0 +1,287 @@ +r"""Differentiating quantum error mitigation transforms +======================================================= + +.. meta:: + :property="og:description": Differentiable error mitigation + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/diffable_mitigation_thumb.png + +.. related:: + + tutorial_error_mitigation Error mitigation with Mitiq and PennyLane + +*Author: Korbinian Kottmann — Posted: 22 August 2022.* + +Error mitigation is an important strategy for minimizing noise when using noisy-intermediate scale quantum (NISQ) hardware, +especially when designing and testing variational algorithms. In this demo, we will show how error mitigation +can be combined with variational workflows, allowing you to differentiate `through` the error mitigation. + +Differentiating quantum error mitigation transforms +--------------------------------------------------- + +Most variational quantum algorithms (VQAs) are concerned with optimizing a `quantum function`, + +.. math:: f(\theta) = \langle 0 | U^\dagger(\theta) H U(\theta) | 0 \rangle, + +for some Ansatz unitary :math:`U` with variational parameters :math:`\theta` and observable :math:`H.` +These algorithms arose due to the constraints of noisy near-term quantum hardware. +This means that naturally in that scenario we do not have direct access to :math:`f,` but rather a noisy version :math:`f^{⚡}` where the variational state +:math:`|\psi(\theta)\rangle = U^\dagger(\theta)|0\rangle` is distorted via a noise channel :math:`\Phi(|\psi(\theta)\rangle \langle \psi(\theta)|).` Since noisy channels generally +yield mixed states (see e.g. :doc:`tutorial_noisy_circuits`), we can formally write + +.. math:: f^{⚡}(\theta) := \text{tr}\left[H \Phi(|\psi(\theta)\rangle \langle \psi(\theta)|) \right]. + +To be able to get the most out of these devices, it is advisable to use quantum error mitigation — a method of +altering and/or post-processing the quantum function :math:`f^{⚡}(\theta)` to improve the result and be closer to the ideal scenario of an error free execution, :math:`f(\theta).` + +Formally, we can treat error mitigation as yet another transform that maps the noisy quantum function :math:`f^{⚡}` to a new, mitigated, quantum function :math:`\tilde{f},` + +.. math:: \text{mitigate}: f^{⚡} \mapsto \tilde{f}. + +In order to run our VQA with our mitigated quantum function, we need to ensure that :math:`\tilde{f}` is differentiable — both formally and practically in our implementation. +PennyLane now provides one such differentiable quantum error mitigation technique with `zero noise extrapolation` (ZNE), which can be used and differentiated in simulation and on hardware. +Thus, we can improve the estimates of observables without breaking the differentiable workflow of our variational algorithm. +We will briefly introduce these functionalities and afterwards go more in depth to explore what happens under the hood. + +We start by initializing a noisy device using a noise model with :class:`~.pennylane.DepolarizingChannel` errors: +""" + +import pennylane as qml +import pennylane.numpy as np +from pennylane.transforms import mitigate_with_zne + +from matplotlib import pyplot as plt + +n_wires = 4 +np.random.seed(1234) + +# Describe noise model +fcond = qml.noise.wires_in(range(n_wires)) +noise = qml.noise.partial_wires(qml.DepolarizingChannel, 0.05) +noise_model = qml.NoiseModel({fcond: noise}) + +# Load devices +dev_ideal = qml.device("default.mixed", wires=n_wires) +dev_noisy = qml.add_noise(dev_ideal, noise_model=noise_model) + +############################################################################## +# We are going to use the transverse field Ising model Hamiltonian :math:`H = - \sum_i X_i X_{i+1} + 0.5 \sum_i Z_i` as our observable: + +coeffs = [1.0] * (n_wires - 1) + [0.5] * n_wires +observables = [qml.PauliX(i) @ qml.PauliX(i + 1) for i in range(n_wires - 1)] +observables += [qml.PauliZ(i) for i in range(n_wires)] + +H = qml.Hamiltonian(coeffs, observables) + + +############################################################################## +# The quantum function, the expectation value of :math:`H,` can then be executed on the noisy or ideal device +# by creating respective QNodes for both. As our ansatz, we'll use a :class:`~.pennylane.SimplifiedTwoDesign` with all-constant parameters set to ``1``: + +n_layers = 2 + +w1 = np.ones((n_wires), requires_grad=True) +w2 = np.ones((n_layers, n_wires - 1, 2), requires_grad=True) + +def qfunc(w1, w2): + qml.SimplifiedTwoDesign(w1, w2, wires=range(n_wires)) + return qml.expval(H) + +qnode_ideal = qml.QNode(qfunc, dev_ideal) +qnode_noisy = qml.QNode(qfunc, dev_noisy) +qnode_noisy = qml.transforms.decompose(qnode_noisy, gate_set = ["RY", "CZ"]) + +############################################################################## +# We can then simply transform the noisy QNode :math:`f^{⚡}` with :func:`~.pennylane.transforms.mitigate_with_zne` to generate :math:`\tilde{f}.` +# If everything goes as planned, executing the mitigated QNode is then closer to the ideal result: + +scale_factors = [1, 2, 3] + +qnode_mitigated = mitigate_with_zne(qnode_noisy, + scale_factors=scale_factors, + folding=qml.transforms.fold_global, + extrapolate=qml.transforms.richardson_extrapolate, +) + +print("Ideal QNode: ", qnode_ideal(w1, w2)) +print("Mitigated QNode: ", qnode_mitigated(w1, w2)) +print("Noisy QNode: ", qnode_noisy(w1, w2)) + +############################################################################## +# The transforms provided for the ``folding`` and ``extrapolate`` arguments can be treated as default black boxes for the moment. +# We will explain them in more detail in the following section. +# +# The cool thing about this new mitigated QNode is that it is still differentiable! That is, we can compute its gradient as usual: + +grad = qml.grad(qnode_mitigated)(w1, w2) +print(grad[0]) +print(grad[1]) + + +############################################################################## +# Under the hood of Zero Noise Extrapolation +# ------------------------------------------ +# What is happening here under the hood? The basic idea of ZNE is to artificially increase the noise in a circuit, +# controlled by a parameter :math:`\lambda` that is called the ``scale_factor`,` to then be able to extrapolate back to zero noise. +# +# Consider two circuits: :math:`U` and :math:`U U^\dagger U.` They are logically equivalent, but we can expect the latter to have more noise due its larger gate count. +# This is the underlying concept of unitary folding, which is used to artificially increase the noise of a quantum function. Given a unitary circuit :math:`U = L_d .. L_1,` +# where :math:`L_i` can be either a gate or layer, we use :func:`~.pennylane.transforms.fold_global` to construct +# +# .. math:: \texttt{fold_global}(U) = U (U^\dagger U)^n (L^\dagger_d L^\dagger_{d-1} .. L^\dagger_s) (L_s .. L_d), +# +# where :math:`n = \lfloor (\lambda - 1)/2 \rfloor` and :math:`s = \lfloor \left((\lambda -1) \mod 2 \right) (d/2) \rfloor` are determined via the ``scale_factor`` :math:`\lambda.` +# +# The version of ZNE that we are showcasing is simply executing the noisy quantum function :math:`f^{⚡}` for different scale factors, +# and then extrapolate to :math:`\lambda \rightarrow 0` (zero noise). This is done with a polynomial fit in :math:`f^{⚡}` as a function of :math:`\lambda.` +# Note that ``scale_factor = 1`` corresponds to the original circuit, i.e. the noisy execution. +# + +scale_factors = [1, 2, 3] +folded_res = [ + qml.transforms.fold_global(qnode_noisy, lambda_)(w1, w2) for lambda_ in scale_factors +] + +ideal_res = qnode_ideal(w1, w2) + +# coefficients are ordered like +# coeffs[0] * x**2 + coeffs[1] * x + coeffs[0] +# i.e. fitted_func(0)=coeff[-1] +coeffs = np.polyfit(scale_factors, folded_res, 2) +zne_res = coeffs[-1] + +x_fit = np.linspace(0, scale_factors[-1], 20) +y_fit = np.poly1d(coeffs)(x_fit) + +plt.figure(figsize=(8, 5)) +plt.plot(scale_factors, folded_res, "x--", label="folded result") +plt.plot(0, ideal_res, "X", label="ideal result") +plt.plot(0, zne_res, "X", label="ZNE result", color="tab:red") +plt.plot(x_fit, y_fit, label="fit", color="tab:red", alpha=0.5) +plt.xlabel("$\\lambda$") +plt.ylabel("f⚡") +plt.legend() +plt.show() + +############################################################################## +# We see that the mitigated result comes close to the ideal result, whereas the noisy result is further off (see value at ``scale_factor=1``). +# +# Note that this folding scheme is relatively simple and only really is sensible for integer values of ``scale_factor``. At the same time, ``scale_factor`` is +# limited from above by the noise as the noisy quantum function quickly decoheres under this folding. I.e., for :math:`\lambda\geq 4` the results are typically already decohered. +# Therefore, one typically only uses ``scale_factors = [1, 2, 3]``. +# In principle, one can think of more fine grained folding schemes and test them by providing custom folding operations. How this can be done in PennyLane with the given +# API is described in :func:`~.pennylane.transforms.mitigate_with_zne`. +# +# Note that Richardson extrapolation, which we used to define the ``mitigated_qnode``, is just a fancy +# way to describe a polynomial fit of ``order = len(x) - 1``. +# Alternatively, you can use :func:`~.pennylane.transforms.poly_extrapolate` and manually pass the order via a keyword argument ``extrapolate_kwargs={'order': 2}``. +# +# Differentiable mitigation in a variational quantum algorithm +# ------------------------------------------------------------ +# +# We will now use mitigation while we optimize the parameters of our variational circuit to obtain the ground state of the Hamiltonian +# — this is the variational quantum eigensolving (VQE), see :doc:`tutorial_vqe`. +# Then, we will compare VQE optimization runs for the ideal, noisy, and mitigated QNodes and see that the mitigated one comes close to the ideal (zero noise) results, +# whereas the noisy execution is further off. + + +def VQE_run(cost_fn, max_iter, stepsize=0.1): + """VQE Optimization loop""" + opt = qml.AdamOptimizer(stepsize=stepsize) + + # fixed initial guess + w1 = np.ones((n_wires), requires_grad=True) + w2 = np.ones((n_layers, n_wires - 1, 2), requires_grad=True) + + energy = [] + + # Optimization loop + for _ in range(max_iter): + (w1, w2), prev_energy = opt.step_and_cost(cost_fn, w1, w2) + + energy.append(prev_energy) + + energy.append(cost_fn(w1, w2)) + + return energy + + +max_iter = 70 + +energy_ideal = VQE_run(qnode_ideal, max_iter) +energy_noisy = VQE_run(qnode_noisy, max_iter) +energy_mitigated = VQE_run(qnode_mitigated, max_iter) + +energy_exact = np.min(np.linalg.eigvalsh(qml.matrix(H))) + +plt.figure(figsize=(8, 5)) +plt.plot(energy_noisy, ".--", label="VQE E_noisy") +plt.plot(energy_mitigated, ".--", label="VQE E_mitigated") +plt.plot(energy_ideal, ".--", label="VQE E_ideal") +plt.plot([1, max_iter + 1], [energy_exact] * 2, "--", label="E_exact") +plt.legend(fontsize=14) +plt.xlabel("Iteration", fontsize=18) +plt.ylabel("Energy", fontsize=18) +plt.show() + +############################################################################## +# We see that during the optimization we are for the most part significantly closer to the ideal simulation and +# end up with a better energy compared to executing the noisy device without ZNE. +# +# So far we have been using PennyLane gradient methods that use ``autograd`` for simulation and ``parameter-shift`` rules for real device +# executions. We can also use the other interfaces that are supported by PennyLane, ``jax``, ``torch`` and ``tensorflow``, in the usual way +# as described in the interfaces section of the documentation :doc:`introduction/interfaces`. +# +# Differentiating the mitigation transform itself +# ----------------------------------------------- +# +# In the previous sections, we have been concerned with differentiating `through` the mitigation transform. An interesting direction for future work +# is differentiating the transform itself [#DiffableTransforms]_. In particular, the authors in [#VAQEM]_ make the interesting observation +# that for some error mitigation schemes, the cost function is smooth in some of the mitigation parameters. Here, we show one of their +# examples, which is a time-sensitive dynamical decoupling scheme: +# +# .. figure:: /_static/demonstration_assets/diffable-mitigation/Mitigate_real_vs_sim3.png +# :width: 50% +# :align: center +# +# Time-sensitive dynamical decoupling scheme. +# +# In this mitigation technique, the single qubit state is put into an equal superposition: +# :math:`|+\rangle = (|0\rangle + |1\rangle)/\sqrt{2}.` During the first idle time :math:`t_1,` the state is altered due to noise. +# Applying :math:`X` reverses the roles of each computational basis state. The idea is that the noise in the second idle time +# :math:`T-t_1` is cancelling out the effect of the first time window. We see that the output fidelity is a smooth function of :math:`t_1.` +# This was executed on ``ibm_perth``, and we note that simple noise models, +# like the simulated IBM device, do not suffice to reproduce the behavior of the real device. +# +# Obtaining the gradient with respect to this parameter is difficult. +# Formally, writing down the derivative of this transform with respect to the idle time in order to derive its parameter-shift +# rules would require access to the noise model. +# This is very difficult for a realistic scenario. Further, most mitigation parameters are integers and would have +# to be smoothed in a differentiable way. A simple but effective strategy is using finite differences for the gradient with respect to mitigation parameters. +# +# Overall, this is a nice example of a mitigation scheme where varying the mitigation parameter has direct impact to the simulation result. +# It is therefore desirable to be able to optimize this parameter at the same time as we perform a variational quantum algorithm. +# +# Conclusion +# ---------- +# +# We demonstrated how zero-noise extrapolation can be seamlessly incorporated in a differentiable workflow in PennyLane to achieve better results. +# Further, the possibility of differentiating error mitigation transforms themselves has been discussed and we have seen +# that some mitigation schemes require execution on real devices or more advanced noise simulations. +# +# +# References +# ---------- +# +# .. [#DiffableTransforms] +# +# Olivia Di Matteo, Josh Izaac, Tom Bromley, Anthony Hayes, Christina Lee, Maria Schuld, Antal Száva, Chase Roberts, Nathan Killoran. +# "Quantum computing with differentiable quantum transforms." +# `arXiv:2202.13414 `__, 2021. +# +# .. [#VAQEM] +# +# Gokul Subramanian Ravi, Kaitlin N. Smith, Pranav Gokhale, Andrea Mari, Nathan Earnest, Ali Javadi-Abhari, Frederic T. Chong. +# "VAQEM: A Variational Approach to Quantum Error Mitigation." +# `arXiv:2112.05821 `__, 2021. +# +# diff --git a/demonstrations_v2/tutorial_diffable-mitigation/metadata.json b/demonstrations_v2/tutorial_diffable-mitigation/metadata.json new file mode 100644 index 0000000000..eb901817e0 --- /dev/null +++ b/demonstrations_v2/tutorial_diffable-mitigation/metadata.json @@ -0,0 +1,51 @@ +{ + "title": "Differentiating quantum error mitigation transforms", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2022-08-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_differentiating_quantum_error_mitigation_transforms.png" + } + ], + "seoDescription": "Differentiable error mitigation", + "doi": "", + "references": [ + { + "id": "DiffableTransforms", + "type": "article", + "title": "Quantum computing with differentiable quantum transforms", + "authors": "Olivia Di Matteo, Josh Izaac, Tom Bromley, Anthony Hayes, Christina Lee, Maria Schuld, Antal Sz\u00e1va, Chase Roberts, Nathan Killoran", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2202.13414" + }, + { + "id": "VAQEM", + "type": "article", + "title": "VAQEM: A Variational Approach to Quantum Error Mitigation", + "authors": "Gokul Subramanian Ravi, Kaitlin N. Smith, Pranav Gokhale, Andrea Mari, Nathan Earnest, Ali Javadi-Abhari, Frederic T. Chong", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2112.05821" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_error_mitigation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_diffable-mitigation/requirements.in b/demonstrations_v2/tutorial_diffable-mitigation/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_diffable-mitigation/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_diffable_shadows/demo.py b/demonstrations_v2/tutorial_diffable_shadows/demo.py new file mode 100644 index 0000000000..85efe65971 --- /dev/null +++ b/demonstrations_v2/tutorial_diffable_shadows/demo.py @@ -0,0 +1,505 @@ +r"""Estimating observables with classical shadows in the Pauli basis +==================================================================== + +.. meta:: + :property="og:description": Classical shadows in the Pauli basis + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pauli_shadows.jpg + + +.. related:: + + tutorial_classical_shadows Introduction to classic shadows + ml_classical_shadows Classic shadows in machine learning + +*Author: Korbinian Kottmann — Posted: 07 October 2022. Last updated: 11 October 2022.* + +We briefly introduce the classical shadow formalism in the Pauli basis and showcase PennyLane's new implementation of it. +Classical shadows are sometimes believed to provide advantages in quantum resources to simultaneously estimate multiple observables. +We demystify this misconception and perform fair comparisons between classical shadow measurements and simultaneously measuring +qubit-wise-commuting observable groups. + +Classical shadow theory +----------------------- + +A `classical shadow` is a classical description of a quantum state that is capable of reproducing expectation values of local Pauli observables, see [#Huang2020]_. +We briefly go through their theory here, and note the two additional demos in :doc:`tutorial_classical_shadows` and :doc:`ml_classical_shadows`. + +We are here focussing on the case where measurements are performed in the Pauli basis. +The idea of classical shadows is to measure each qubit in a random Pauli basis. +While doing so, one keeps track of the performed measurement (its ``recipes``) in form +of integers ``[0, 1, 2]`` corresponding to the measurement bases ``[X, Y, Z]``, respectively. +At the same time, the measurement outcome (its ``bits``) are recorded, where ``[0, 1]`` +corresponds to the eigenvalues ``[1, -1]``, respectively. + +We record :math:`T` of such measurements, and for the :math:`t`-th measurement, we can reconstruct the ``local_snapshot`` for :math:`n` qubits via + +.. math:: \rho^{(t)} = \bigotimes_{i=1}^{n} 3 U^\dagger_i |b^{(t)}_i \rangle \langle b^{(t)}_i | U_i - \mathbb{I}, + +where :math:`U_i` is the diagonalizing rotation for the respective Pauli basis (e.g. :math:`U_i=H` for measurement in :math:`X`) for qubit `i.` +:math:`|b^{(t)}_i\rangle = (1 - b^{(t)}_i, b^{(t)}_i)` is the corresponding computational basis state given by the output bit :math:`b^{(t)}_i \in \{0, 1\}.` + +From these local snapshots, one can compute expectation values of q-local Pauli strings, where locality refers to the number of non-Identity operators. +The expectation value of any Pauli string :math:`\bigotimes_iO_i` with :math:`O_i \in \{X, Y, Z, \mathbb{I}\}` can be estimated +by computing + +.. math:: \Big\langle \bigotimes_iO_i \Big\rangle = \frac{1}{T} \sum_{t=1}^T \text{tr}\left[ \rho^{(t)} \left(\bigotimes_i O_i\right) \right]. + +Error bounds given by the number of measurements :math:`T = \mathcal{O}\left( \log(M) 4^q/\varepsilon^2 \right)` guarantee that sufficiently many correct measurements +were performed to estimate :math:`M` different observables up to additive error :math:`\varepsilon.` This :math:`\log(M)` factor may lead one to think that with classical shadows one can +`magically` estimate multiple observables at a lower cost than with direct measurement. We resolve this misconception in the following section. + + +Unraveling the mystery +~~~~~~~~~~~~~~~~~~~~~~ +Using algebraic properties of Pauli operators, we show how to exactly compute the above expression from just the ``bits`` and ``recipes`` +without explicitly reconstructing any snapshots. This gives us insights to what is happening under the hood and how the ``T`` measuerements are used to estimate the observable. + +Let us start by looking at individual snapshot expectation values +:math:`\langle \bigotimes_iO_i \rangle ^{(t)} = \text{tr}\left[\rho^{(t)} \left(\bigotimes_iO_i \right)\right].` +First, we convince ourselves of the identity + +.. math:: U_i^\dagger |b^{(t)}_i\rangle \langle b^{(t)}_i| U_i = \frac{1}{2}\left((1-2b^{(t)}_i) P_i + \mathbb{I}\right), + +where :math:`P_i \in \{X, Y, Z\}` is the Pauli operator corresponding to :math:`U_i` (note that in this case :math:`P_i` is never the identity). +The snapshot expectation value then reduces to + +.. math:: \Big\langle\bigotimes_iO_i\Big\rangle^{(t)} = \prod_{i=1}^n \text{tr}\left[\frac{3}{2}(1-2b^{(t)}_i)P_i O_i + \frac{1}{2}O_i\right]. + +For that trace we find three different cases. +The cases where :math:`O_i=\mathbb{I}` yield a trivial factor :math:`1` to the product. +The full product is always zero if any of the non-trivial :math:`O_i` do not match :math:`P_i.` So in total, `only` in the case that all :math:`q` Pauli operators match, we find + +.. math:: \Big\langle\bigotimes_iO_i\Big\rangle^{(t)} = 3^q \prod_{\text{i non-trivial}}(1-2b^{(t)}_i). + +This implies that in order to compute the expectation value of a Pauli string + +.. math:: \Big\langle\bigotimes_iO_i\Big\rangle = \frac{1}{\tilde{T}} \sum_{\tilde{t}} \prod_{\text{i non-trivial}}(1-2b^{(t)}_i) + +we simply need average over the product of :math:`1 - 2b^{(t)}_i = \pm 1` for those :math:`\tilde{T}` snapshots where the measurement recipe matches the observable, +indicated by the special index :math:`\tilde{t}` for the matching measurements. Note that the probability of a match is :math:`1/3^q` such that we have +:math:`\tilde{T} \approx T / 3^q` on average. + +This implies that computing expectation values with classical shadows comes down to picking the specific subset of snapshots where those specific observables +were already measured and discarding the remaining. If the desired observables are known prior to the measurement, +one is thus advised to directly perform those measurements. +This was referred to as `derandomization` by the authors in a follow-up paper [#Huang2021]_. + + +We will later compare the naive classical shadow approach to directly measuring the desired observables and make use of simultaneously +measuring qubit-wise-commuting observables. Before that, let us demonstrate how to perform classical shadow measurements in a differentiable manner in PennyLane. + +PennyLane implementation +------------------------ + +There are two ways of computing expectation values with classical shadows in PennyLane. The first is to return :func:`qml.shadow_expval ` directly from the qnode. +This has the advantage that it preserves the typical PennyLane syntax *and* is differentiable. +""" + +import pennylane as qml +import pennylane.numpy as np +from matplotlib import pyplot as plt +from pennylane import classical_shadow, shadow_expval, ClassicalShadow + +np.random.seed(666) + +H = qml.Hamiltonian([1., 1.], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]) + +dev = qml.device("default.qubit", wires=range(2), shots=10000) +@qml.qnode(dev, interface="autograd") +def qnode(x, H): + qml.Hadamard(0) + qml.CNOT((0,1)) + qml.RX(x, wires=0) + return shadow_expval(H) + +x = np.array(0.5, requires_grad=True) + +############################################################################## +# Compute expectation values and derivatives thereof in the common way in PennyLane. + +print(qnode(x, H), qml.grad(qnode)(x, H)) + +############################################################################## +# Each call of :func:`qml.shadow_expval ` performs the number of shots dictated by the device. +# So to avoid unnecessary device executions you can provide a list of observables to :func:`qml.shadow_expval `. + +Hs = [H, qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)] +print(qnode(x, Hs)) +print(qml.jacobian(qnode)(x, Hs)) + +############################################################################## +# Alternatively, you can compute expectation values by first performing the shadow measurement and then perform classical post-processing using the :class:`~.pennylane.ClassicalShadow` +# class methods. + +dev = qml.device("default.qubit", wires=range(2), shots=1000) +@qml.qnode(dev, interface="autograd") +def qnode(x): + qml.Hadamard(0) + qml.CNOT((0,1)) + qml.RX(x, wires=0) + return classical_shadow(wires=range(2)) + +bits, recipes = qnode(0.5) +shadow = ClassicalShadow(bits, recipes) +print(bits.shape, recipes.shape) + +############################################################################## +# After recording these ``T=1000`` quantum measurements, we can post-process the results to arbitrary local expectation values of Pauli strings. +# For example, we can compute the expectation value of a Pauli string + +print(shadow.expval(qml.PauliX(0) @ qml.PauliX(1))) + +############################################################################## +# or of a Hamiltonian: + +H = qml.Hamiltonian([1., 1.], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]) +print(shadow.expval(H)) + +############################################################################## +# This way of computing expectation values is not automatically differentiable in PennyLane though. + + + +############################################################################## +# Comparing quantum resources with conventional measurement methods +# ----------------------------------------------------------------- +# +# The goal of the following section is to compare estimation accuracy for a given number of quantum executions with more conventional methods +# like simultaneously measuring qubit-wise-commuting (qwc) groups, see :doc:`tutorial_measurement_optimize`. We are going to look at three different cases: The two extreme scenarios of measuring one single +# and `all` q-local Pauli strings, as well as the more realistic scenario of measuring a molecular Hamiltonian. We find that for a fix budget of measurements, one is +# almost never advised to use classical shadows for estimating expectation values. +# +# Measuring one single observable +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We start with the case of one single measurement. From the analysis above it should be quite clear that in the case of random Pauli measurement in the classical shadows +# formalism, a lot of quantum resources are wasted as all the measurements that do not match the observable are discarded. This is certainly not what classical shadows were intended for +# in the first place, but it helps to stress the point of wasted measurements. +# +# We start by fixing a circuit and an observable, for which we compute the exact result for infinite shots. + +def rmsd(x, y): + """root mean square difference""" + return np.sqrt(np.mean((x - y)**2)) + +n_wires = 10 + +x = np.arange(2*n_wires, dtype="float64") +def circuit(): + for i in range(n_wires): + qml.RY(x[i], i) + for i in range(n_wires-1): + qml.CNOT((i, i+1)) + for i in range(n_wires): + qml.RY(x[i+n_wires], i) + +obs = qml.PauliX(0) @ qml.PauliZ(3) @ qml.PauliX(6) @ qml.PauliZ(7) + +dev_ideal = qml.device("default.qubit", wires=range(n_wires), shots=None) +@qml.qnode(dev_ideal, interface="autograd") +def qnode_ideal(): + circuit() + return qml.expval(obs) + +exact = qnode_ideal() + +############################################################################## +# We now compare estimating the observable with classical shadows vs the canonical estimation. + +finite = [] +shadow = [] +shotss = range(100, 1000, 100) +for shots in shotss: + for _ in range(10): + # repeating experiment 10 times to obtain averages and standard deviations + dev = qml.device("default.qubit", wires=range(10), shots=shots) + + @qml.qnode(dev, interface="autograd") + def qnode_finite(): + circuit() + return qml.expval(obs) + + @qml.qnode(dev, interface="autograd") + def qnode_shadow(): + circuit() + return qml.shadow_expval(obs) + + finite.append(rmsd(qnode_finite(), exact)) + shadow.append(rmsd(qnode_shadow(), exact)) + + +dq = np.array(finite).reshape(len(shotss), 10) +dq, ddq = np.mean(dq, axis=1), np.var(dq, axis=1) +ds = np.array(shadow).reshape(len(shotss), 10) +ds, dds = np.mean(ds, axis=1), np.var(ds, axis=1) + +plt.errorbar(shotss, ds, yerr=dds, fmt="x-", label="shadow") +plt.errorbar(shotss, dq, yerr=ddq, fmt="x-", label="direct") +plt.xlabel("total number of shots T", fontsize=20) +plt.ylabel("Error (RMSD)", fontsize=20) +plt.legend() +plt.tight_layout() +plt.show() + +############################################################################## +# +# Unsurprisingly, the deviation is consistently smaller by directly measuring the observable since we are not discarding any measurement results. + +############################################################################## +# All q-local observables +# ~~~~~~~~~~~~~~~~~~~~~~~ +# For the case of measuring `all` q-local Pauli strings we expect both strategies to yield more or less the same results. +# In this extreme case, no measurements are discarded in the classical shadow protocol. +# Let us put that to test. First, we generate a list of all q-local observables for n qubits. + +from itertools import product, combinations +from functools import reduce + +all_observables = [] +n = 5 +q = 2 +# create all combination of q entries of range(n) +for w in combinations(range(n), q): + # w = [0, 1], [0, 2], .., [1, 2], [1, 3], .., [n-1, n] + observables = [] + + # Create all combinations of possible Pauli products P_i P_j P_k.... for wires w + for obs in product( + *[[qml.PauliX, qml.PauliY, qml.PauliZ] for _ in range(len(w))] + ): + # Perform tensor product (((P_i @ P_j) @ P_k ) @ ....) + observables.append(reduce(lambda a, b: a @ b, [ob(wire) for ob, wire in zip(obs, w)])) + all_observables.extend(observables) + +for observable in all_observables[:10]: + print(observable) + +############################################################################## +# We now group these into qubit-wise-commuting (qwc) groups using :func:`~pennylane.pauli.group_observables` to learn the number of +# groups. We need this number to make a fair comparison with classical shadows as we allow for only ``T/n_groups`` shots per group, such that +# the total number of shots is the same as for the classical shadow execution. We again compare both approaches. + +n_groups = len(qml.pauli.group_observables(all_observables)) + +dev_ideal = qml.device("default.qubit", wires=range(n), shots=None) + +x = np.random.rand(n*2) +def circuit(): + for i in range(n): + qml.RX(x[i], i) + + for i in range(n): + qml.CNOT((i, (i+1)%n)) + + for i in range(n): + qml.RY(x[i+n], i) + + for i in range(n): + qml.CNOT((i, (i+1)%n)) + +@qml.qnode(dev_ideal, interface="autograd") +def qnode_ideal(): + circuit() + return qml.expval(H) + +exact = qnode_ideal() +finite = [] +shadow = [] +shotss = range(100, 10000, 2000) +for shots in shotss: + # random Hamiltonian with all q-local observables + coeffs = np.random.rand(len(all_observables)) + H = qml.Hamiltonian(coeffs, all_observables, grouping_type="qwc") + + @qml.qnode(dev_ideal, interface="autograd") + def qnode_ideal(): + circuit() + return qml.expval(H) + + exact = qnode_ideal() + + for _ in range(10): + dev = qml.device("default.qubit", wires=range(5), shots=shots) + + @qml.qnode(dev, interface="autograd") + def qnode_finite(): + circuit() + return qml.expval(H) + + dev = qml.device("default.qubit", wires=range(5), shots=shots*n_groups) + @qml.qnode(dev, interface="autograd") + def qnode_shadow(): + circuit() + return qml.shadow_expval(H) + + finite.append(rmsd(qnode_finite(), exact)) + shadow.append(rmsd(qnode_shadow(), exact)) + + +dq = np.array(finite).reshape(len(shotss), 10) +dq, ddq = np.mean(dq, axis=1), np.var(dq, axis=1) +ds = np.array(shadow).reshape(len(shotss), 10) +ds, dds = np.mean(ds, axis=1), np.var(ds, axis=1) +plt.errorbar(shotss, ds, yerr=dds, fmt="x-", label="shadow") +plt.errorbar(shotss, dq, yerr=ddq, fmt="x-", label="qwc") +plt.xlabel("total number of shots T", fontsize=20) +plt.ylabel("Error (RMSD)", fontsize=20) +plt.legend() +plt.tight_layout() +plt.show() + + +############################################################################## +# +# We see that as expected the performance is more or less the same since no quantum measurements are discarded for the shadows in this case. +# Depending on the chosen random seed there are quantitative variations to this image, but the overall qualitative result remains the same. +# +# Molecular Hamiltonians +# ~~~~~~~~~~~~~~~~~~~~~~ +# We now look at the more realistic case of measuring a molecular Hamiltonian. We tak :math:`\text{H}_2\text{O}` as an example. +# You can find more details on this Hamiltonian in :doc:`tutorial_quantum_chemistry`. +# We start by building the Hamiltonian and enforcing qwc groups by setting ``grouping_type='qwc'``. + +symbols = ["H", "O", "H"] +coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) +basis_set = "sto-3g" + +molecule = qml.qchem.Molecule(symbols, coordinates, basis_name=basis_set) + +H, n_wires = qml.qchem.molecular_hamiltonian( + molecule, + active_electrons=4, + active_orbitals=4, + mapping="bravyi_kitaev", + method="openfermion", +) + +coeffs, obs = H.terms() +H_qwc = qml.Hamiltonian(coeffs, obs, grouping_type="qwc") + +groups = qml.pauli.group_observables(obs) +n_groups = len(groups) +print(f"number of ops in H: {len(obs)}, number of qwc groups: {n_groups}") +print(f"Each group has sizes {[len(_) for _ in groups]}") + +############################################################################## +# We use a pre-prepared Ansatz that approximates the :math:`\text{H}_2\text{O}` ground state for the given geometry. You can construct this Ansatz by running VQE, see :doc:`tutorial_vqe.` +# We ran this once on an ideal simulator to get the exact result of the energy for the given Ansatz. + +singles, doubles = qml.qchem.excitations(electrons=4, orbitals=n_wires) +hf = qml.qchem.hf_state(4, n_wires) +theta = np.array([ 2.20700008e-02, 8.29716448e-02, 2.19227085e+00, + 3.19128513e+00, -1.35370403e+00, 6.61615333e-03, + 7.40317830e-01, -3.73367029e-01, 4.35206518e-02, + -1.83668679e-03, -4.59312535e-03, -1.91103984e-02, + 8.21320961e-03, -1.48452294e-02, -1.88176061e-03, + -1.66141213e-02, -8.94505652e-03, 6.92045656e-01, + -4.54217610e-04, -8.22532179e-04, 5.27283799e-03, + 6.84640451e-03, 3.02313759e-01, -1.23117023e-03, + 4.42283398e-03, 6.02542038e-03]) + +res_exact = -74.57076341 +def circuit(): + qml.AllSinglesDoubles(weights = theta, + wires = range(n_wires), + hf_state = hf, + singles = singles, + doubles = doubles) + +############################################################################## +# We again follow the same simple strategy of giving each group the same number of shots ``T/n_groups`` for ``T`` total shots. + +d_qwc = [] +d_sha = [] + +shotss = np.arange(20, 220, 20) + +for shots in shotss: + for _ in range(10): + + # execute qwc measurements + dev_finite = qml.device("default.qubit", wires=range(n_wires), shots=int(shots)) + + @qml.qnode(dev_finite, interface="autograd") + def qnode_finite(H): + circuit() + return qml.expval(H) + + with qml.Tracker(dev_finite) as tracker_finite: + res_finite = qnode_finite(H_qwc) + + # execute shadows measurements + dev_shadow = qml.device("default.qubit", wires=range(n_wires), shots=int(shots)*n_groups) + @qml.qnode(dev_shadow, interface="autograd") + def qnode(): + circuit() + return classical_shadow(wires=range(n_wires)) + + with qml.Tracker(dev_shadow) as tracker_shadows: + bits, recipes = qnode() + + shadow = ClassicalShadow(bits, recipes) + res_shadow = shadow.expval(H, k=1) + + # Guarantuee that we are not cheating and its a fair fight + assert tracker_finite.totals["shots"] <= tracker_shadows.totals["shots"] + + d_qwc.append(rmsd(res_finite, res_exact)) + d_sha.append(rmsd(res_shadow, res_exact)) + + +dq = np.array(d_qwc).reshape(len(shotss), 10) +dq, ddq = np.mean(dq, axis=1), np.var(dq, axis=1) +ds = np.array(d_sha).reshape(len(shotss), 10) +ds, dds = np.mean(ds, axis=1), np.var(ds, axis=1) +plt.errorbar(shotss*n_groups, ds, yerr=dds, fmt="x-", label="shadow") +plt.errorbar(shotss*n_groups, dq, yerr=ddq, fmt="x-", label="qwc") +plt.xlabel("total number of shots T", fontsize=20) +plt.ylabel("Error (RMSD)", fontsize=20) +plt.legend() +plt.tight_layout() +plt.show() + +############################################################################## +# For this realistic example, one is clearly better advised to directly compute the expectation values +# and not waste precious quantum resources on unused measurements in the classical shadow protocol. + + + +############################################################################## +# +# Conclusion +# ---------- +# Overall, we saw that classical shadows always waste unused quantum resources for measurements that are not used, except some extreme cases. +# For the rare case that the observables that are to be determined are not known before the measurement, classical shadows may prove advantageous. +# +# We have been using a relatively simple approach to qwc grouping, as :func:`~pennylane.pauli.group_observables` +# is based on the largest first (LF) heuristic (see :func:`~pennylane.pauli.graph_colouring.largest_first`). +# There has been intensive research in recent years on optimizing qwc measurement schemes. +# Similarily, it has been realized by the original authors that the randomized shadow protocol can be improved by what they call derandomization [#Huang2021]_. +# Currently, it seems advanced grouping algorithms are still the preferred choice, as is illustrated and discused in [#Yen]_. +# +# +# +# References +# ---------- +# +# .. [#Huang2020] +# +# Hsin-Yuan Huang, Richard Kueng, John Preskill +# "Predicting Many Properties of a Quantum System from Very Few Measurements." +# `arXiv:2002.08953 `__, 2020. +# +# .. [#Huang2021] +# +# Hsin-Yuan Huang, Richard Kueng, John Preskill +# "Efficient estimation of Pauli observables by derandomization." +# `arXiv:2103.07510 `__, 2021. +# +# .. [#Yen] +# +# Tzu-Ching Yen, Aadithya Ganeshram, Artur F. Izmaylov +# "Deterministic improvements of quantum measurements with grouping of compatible operators, non-local transformations, and covariance estimates." +# `arXiv:2201.01471 `__, 2022. + +############################################################################## diff --git a/demonstrations_v2/tutorial_diffable_shadows/metadata.json b/demonstrations_v2/tutorial_diffable_shadows/metadata.json new file mode 100644 index 0000000000..9e35df95c4 --- /dev/null +++ b/demonstrations_v2/tutorial_diffable_shadows/metadata.json @@ -0,0 +1,66 @@ +{ + "title": "Estimating observables with classical shadows in the Pauli basis", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2022-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_estimating_observables_classical_shadows_Pauli_Basis.png" + } + ], + "seoDescription": "Classical shadows in the Pauli basis", + "doi": "", + "references": [ + { + "id": "Huang2020", + "type": "article", + "title": "Predicting Many Properties of a Quantum System from Very Few Measurements", + "authors": "Hsin-Yuan Huang, Richard Kueng, John Preskill", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2002.08953" + }, + { + "id": "Huang2021", + "type": "article", + "title": "Efficient estimation of Pauli observables by derandomization", + "authors": "Hsin-Yuan Huang, Richard Kueng, John Preskill", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2103.07510" + }, + { + "id": "Yen", + "type": "article", + "title": "Deterministic improvements of quantum measurements with grouping of compatible operators, non-local transformations, and covariance estimates", + "authors": "Tzu-Ching Yen, Aadithya Ganeshram, Artur F. Izmaylov", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2201.01471" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ml_classical_shadows", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_diffable_shadows/requirements.in b/demonstrations_v2/tutorial_diffable_shadows/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_diffable_shadows/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_differentiable_HF/demo.py b/demonstrations_v2/tutorial_differentiable_HF/demo.py new file mode 100644 index 0000000000..b7fa0b4805 --- /dev/null +++ b/demonstrations_v2/tutorial_differentiable_HF/demo.py @@ -0,0 +1,390 @@ +r""" + +Differentiable Hartree-Fock +=========================== + +.. meta:: + :property="og:description": Learn how to use the differentiable Hartree-Fock solver + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/differentiable_HF.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + + +*Author: Soran Jahangiri — Posted: 09 May 2022. Last updated: 09 May 2022.* + +In this tutorial, you will learn how to use PennyLane's differentiable Hartree-Fock solver +[#arrazola2021]_. The quantum chemistry module in PennyLane, :mod:`qml.qchem `, +provides built-in methods for constructing +atomic and molecular orbitals, building Fock matrices and solving the self-consistent field +equations to obtain optimized orbitals which can be used to construct fully-differentiable +molecular Hamiltonians. PennyLane allows users to natively compute derivatives of all these objects +with respect to the underlying parameters using the methods of +`automatic differentiation `_. We +introduce a workflow to jointly optimize circuit parameters, nuclear coordinates and basis set +parameters in a variational quantum eigensolver algorithm. You will also learn how to visualize the +atomic and molecular orbitals which can be used to create an animation like this: + +.. figure:: /_static/demonstration_assets/differentiable_HF/h2.gif + :width: 60% + :align: center + + The bonding molecular orbital of hydrogen visualized during a full geometry, circuit and + basis set optimization. + +Let's get started! + +Differentiable Hamiltonians +--------------------------- + +Variational quantum algorithms aim to calculate the energy of a molecule by constructing a +parameterized quantum circuit and finding a set of parameters that minimize the expectation value of +the electronic `molecular Hamiltonian `_. The +optimization can be carried out by computing the gradients of the expectation value with respect to +these parameters and iteratively updating them until convergence is achieved. In principle, the +optimization process is not limited to the circuit parameters and can be extended to include the +parameters of the Hamiltonian that can be optimized concurrently with the circuit parameters. The +aim is now to obtain the set of parameters that minimize the following expectation value + +.. math:: \left \langle \Psi(\theta) | H(\beta) | \Psi(\theta) \right \rangle, + +where :math:`\theta` and :math:`\beta` represent the circuit and Hamiltonian parameters, +respectively. + +Computing the gradient of a molecular Hamiltonian is challenging because the dependency of the +Hamiltonian on the molecular parameters is typically not very straightforward. This makes symbolic +differentiation methods, which obtain derivatives of an input function by direct mathematical +manipulation, of limited scope. Furthermore, numerical differentiation methods based on +`finite differences `_ are not always +reliable due to their intrinsic instability, especially when the number of +differentiable parameters is large. These limitations can be alleviated by using automatic +differentiation methods which can be used to compute exact gradients of a function, implemented with +computer code, using resources comparable to those required to evaluate the function itself. + +Efficient optimization of the molecular Hamiltonian parameters in a variational quantum algorithm +is essential for tackling problems such as +`geometry optimization `_ and vibrational +frequency +calculations. These problems require computing the first- and second-order derivatives of the +molecular energy with respect to nuclear coordinates which can be efficiently obtained if the +variational workflow is automatically differentiable. Another important example is the simultaneous +optimization of the parameters of the basis set used to construct the atomic orbitals which can in +principle increase the accuracy of the computed energy without increasing the number of qubits in a +quantum simulation. The joint optimization of the circuit and Hamiltonian parameters can also be +used when the chemical problem involves optimizing the parameters of external potentials. + +The Hartree-Fock method +----------------------- + +The main goal of the Hartree-Fock method is to obtain molecular orbitals that minimize the +energy of a system where electrons are treated as independent particles that experience a mean field +generated by the other electrons. These optimized molecular orbitals are then used to +construct one- and two-body electron integrals in the basis of molecular orbitals, :math:`\phi,` + +.. math:: h_{pq} =\int dx \,\phi_p^*(x)\left(-\frac{\nabla^2}{2}-\sum_{i=1}^N\frac{Z_i}{|r-R_i|}\right)\phi_q(x),\\\\ +.. math:: h_{pqrs} = \int dx_1 dx_2\, \frac{\phi_p^*(x_1)\phi_q^*(x_2)\phi_r(x_2)\phi_s(x_1)}{|r_1-r_2|}. + +These integrals are used to generate a differentiable +`second-quantized `_ molecular Hamiltonian as + + +.. math:: H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}h_{pqrs}a_p^\dagger a_q^\dagger a_r a_s, + +where :math:`a^\dagger` and :math:`a` are the fermionic creation and annihilation operators, +respectively. This Hamiltonian is then transformed to the qubit basis. Let's see how this can be +done in PennyLane. + +To get started, we need to define the atomic symbols and the nuclear coordinates of the molecule. +For the hydrogen molecule we have +""" + +from autograd import grad +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt +np.set_printoptions(precision=5) + +symbols = ["H", "H"] +# optimized geometry at the Hartree-Fock level +geometry = np.array([[-0.672943567415407, 0.0, 0.0], + [ 0.672943567415407, 0.0, 0.0]], requires_grad=True) + +############################################################################## +# The use of ``requires_grad=True`` specifies that the nuclear coordinates are differentiable +# parameters. We can now compute the Hartree-Fock energy and its gradient with respect to the +# nuclear coordinates. To do that, we create a molecule object that stores all the molecular +# parameters needed to perform a Hartree-Fock calculation. + +mol = qml.qchem.Molecule(symbols, geometry) + +############################################################################## +# The Hartree-Fock energy can now be computed with the +# :func:`~.pennylane.qchem.hf_energy` function which is a function transform + +qml.qchem.hf_energy(mol)(geometry) + +############################################################################## +# We now compute the gradient of the energy with respect to the nuclear coordinates + +grad(qml.qchem.hf_energy(mol))(geometry) + +############################################################################## +# The obtained gradients are equal or very close to zero because the geometry we used here has been +# previously optimized at the Hartree-Fock level. You can use a different geometry and verify that +# the newly computed gradients are not all zero. +# +# We can also compute the values and gradients of several other quantities that are obtained during +# the Hartree-Fock procedure. These include all integrals over basis functions, matrices formed from +# these integrals and the one- and two-body integrals over molecular orbitals [#arrazola2021]_. +# Let's look at a few examples. +# +# We first compute the overlap integral between the two S-type atomic orbitals of the hydrogen +# atoms. Here we are using the `STO-3G `_ +# basis set in which each of the atomic orbitals is represented by one basis function composed of +# three primitive Gaussian functions. These basis functions can be accessed from the molecule +# object as + +S1 = mol.basis_set[0] +S2 = mol.basis_set[1] + +############################################################################## +# We can check the parameters of the basis functions as + +for param in S1.params: + print(param) + +############################################################################## +# This returns the exponents, contraction coefficients and the centres of the three Gaussian +# functions of the STO-3G basis set. These parameters can be also obtained individually by using +# ``S1.alpha``, ``S1.coeff`` and ``S1.r``, respectively. You can verify that both of the ``S1`` an +# ``S2`` atomic orbitals have the same exponents and contraction coefficients but are centred on +# different hydrogen atoms. You can also verify that the orbitals are S-type by printing the angular +# momentum quantum numbers with + +S1.l + +############################################################################## +# This gives us a tuple of three integers, representing the exponents of the :math:`x,` :math:`y` +# and :math:`z` components in the Gaussian functions [#arrazola2021]_. +# +# We can now compute the overlap integral, +# +# .. math:: +# +# S_{\mu \nu} = \int \chi_\mu^* (r) \chi_\nu (r) dr +# +# between the atomic orbitals :math:`\chi,` by passing the orbitals and the initial values of their +# centres to the :func:`~.pennylane.qchem.overlap_integral` function. The centres of the orbitals +# are those of the hydrogen atoms by default and are therefore treated as differentiable parameters +# by PennyLane. + +qml.qchem.overlap_integral(S1, S2)([geometry[0], geometry[1]]) + +############################################################################## +# You can verify that the overlap integral between two identical atomic orbitals is equal to one. +# We can now compute the gradient of the overlap integral with respect to the orbital centres + +grad(qml.qchem.overlap_integral(S1, S2))([geometry[0], geometry[1]]) + +############################################################################## +# Can you explain why some of the computed gradients are zero? +# +# Let's now plot the atomic orbitals and their overlap. We can do it by using +# the :py:meth:`~.pennylane.qchem.Molecule.atomic_orbital` function, which evaluates the +# atomic orbital at a given coordinate. For instance, the value of the S orbital on the first +# hydrogen atom can be computed at the origin as + +V1 = mol.atomic_orbital(0) +V1(0.0, 0.0, 0.0) + +############################################################################## +# We can evaluate this orbital at different points along the :math:`x` axis and plot it. + +x = np.linspace(-5, 5, 1000) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# We can also plot the second S orbital and visualize the overlap between them + +V2 = mol.atomic_orbital(1) +plt.plot(x, V1(x, 0.0, 0.0), color='teal') +plt.plot(x, V2(x, 0.0, 0.0), color='teal') +plt.fill_between( + x, np.minimum(V1(x, 0.0, 0.0), V2(x, 0.0, 0.0)), color = 'red', alpha = 0.5, hatch = '||') +plt.xlabel('X [Bohr]') +plt.show() + +############################################################################## +# By looking at the orbitals, can you guess at what distance the value of the overlap becomes +# negligible? Can you verify your guess by computing the overlap at that distance? +# +# Similarly, we can plot the molecular orbitals of the hydrogen molecule obtained from the +# Hartree-Fock calculations. We plot the cross-section of the bonding orbital on the :math:`x-y` +# plane. + +n = 30 # number of grid points along each axis + +mol.mo_coefficients = mol.mo_coefficients.T +mo = mol.molecular_orbital(0) +x, y = np.meshgrid(np.linspace(-2, 2, n), + np.linspace(-2, 2, n)) +val = np.vectorize(mo)(x, y, 0) +val = np.array([val[i][j]._value for i in range(n) for j in range(n)]).reshape(n, n) + +fig, ax = plt.subplots() +co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) +ax.clabel(co, inline=2, fontsize=10) +plt.scatter(mol.coordinates[:,0], mol.coordinates[:,1], s = 80, color='black') +ax.set_xlabel('X [Bohr]') +ax.set_ylabel('Y [Bohr]') +plt.show() + +############################################################################## +# VQE simulations +# --------------- +# +# By performing the Hartree-Fock calculations, we obtain a set of one- and two-body integrals +# over molecular orbitals that can be used to construct the molecular Hamiltonian with the +# :func:`~.pennylane.qchem.molecular_hamiltonian` function. + +hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol, args=[mol.coordinates]) +print(hamiltonian) + +############################################################################## +# The Hamiltonian contains 15 terms and, importantly, the coefficients of the Hamiltonian are all +# differentiable. We can construct a circuit and perform a VQE simulation in which both of the +# circuit parameters and the nuclear coordinates are optimized simultaneously by using the computed +# gradients. We will have two sets of differentiable parameters: the first set is the +# rotation angles of the excitation gates which are applied to the reference Hartree-Fock state +# to construct the exact ground state. The second set contains the nuclear coordinates of the +# hydrogen atoms. + +dev = qml.device("default.qubit", wires=4) +def energy(mol): + @qml.qnode(dev, interface="autograd") + def circuit(*args): + qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) + qml.DoubleExcitation(*args[0][0], wires=[0, 1, 2, 3]) + H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] + return qml.expval(H) + return circuit + +############################################################################## +# Note that we only use the :class:`~.pennylane.DoubleExcitation` gate as the +# :class:`~.pennylane.SingleExcitation` ones can be neglected in this particular example +# [#szabo1996]_. We now compute the gradients of the energy with respect to the circuit parameter +# and the nuclear coordinates and update the parameters iteratively. Note that the nuclear +# coordinate gradients are simply the forces on the atomic nuclei. + +# initial value of the circuit parameter +circuit_param = [np.array([0.0], requires_grad=True)] + +for n in range(36): + + args = [circuit_param, geometry] + mol = qml.qchem.Molecule(symbols, geometry) + + # gradient for circuit parameters + g_param = grad(energy(mol), argnum = 0)(*args) + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + forces = -grad(energy(mol), argnum = 1)(*args) + geometry = geometry + 0.5 * forces + + if n % 5 == 0: + print(f'n: {n}, E: {energy(mol)(*args):.8f}, Force-max: {abs(forces).max():.8f}') + +############################################################################## +# After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the +# circuit parameter are both approaching zero, and the energy of the molecule is that of the +# optimized geometry at the +# `full-CI `_ level: +# :math:`-1.1373060483` Ha. You can print the optimized geometry and verify that the final bond +# length of hydrogen is identical to the one computed with full-CI which is :math:`1.389` +# `Bohr `_. +# +# We are now ready to perform a full optimization where the circuit parameters, the nuclear +# coordinates and the basis set parameters are all differentiable parameters that can be optimized +# simultaneously. + +# initial values of the nuclear coordinates +geometry = np.array([[0.0, 0.0, -0.672943567415407], + [0.0, 0.0, 0.672943567415407]], requires_grad=True) + +# initial values of the basis set exponents +alpha = np.array([[3.42525091, 0.62391373, 0.1688554], + [3.42525091, 0.62391373, 0.1688554]], requires_grad=True) + +# initial values of the basis set contraction coefficients +coeff = np.array([[0.1543289673, 0.5353281423, 0.4446345422], + [0.1543289673, 0.5353281423, 0.4446345422]], requires_grad=True) + +# initial value of the circuit parameter +circuit_param = [np.array([0.0], requires_grad=True)] + +for n in range(36): + + args = [circuit_param, geometry, alpha, coeff] + mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) + + # gradient for circuit parameters + g_param = grad(energy(mol), argnum=0)(*args) + circuit_param = circuit_param - 0.25 * g_param[0] + + # gradient for nuclear coordinates + forces = -grad(energy(mol), argnum=1)(*args) + geometry = geometry + 0.5 * forces + + # gradient for basis set exponents + g_alpha = grad(energy(mol), argnum=2)(*args) + alpha = alpha - 0.25 * g_alpha + + # gradient for basis set contraction coefficients + g_coeff = grad(energy(mol), argnum=3)(*args) + coeff = coeff - 0.25 * g_coeff + + if n % 5 == 0: + print(f'n: {n}, E: {energy(mol)(*args):.8f}, Force-max: {abs(forces).max():.8f}') + +############################################################################## +# You can also print the gradients of the circuit and basis set parameters and confirm that they are +# approaching zero. The computed energy of :math:`-1.14040160` Ha is +# lower than the full-CI energy, :math:`-1.1373060483` Ha (obtained with the STO-3G basis set for +# the hydrogen molecule) because we have optimized the basis set parameters in our example. This +# means that we can reach a lower energy for hydrogen without increasing the basis set size, which +# would otherwise lead to a larger number of qubits. +# +# Conclusions +# ----------- +# This tutorial introduces an important feature of PennyLane that allows performing +# fully-differentiable Hartree-Fock and subsequently VQE simulations. This feature provides two +# major benefits: i) All gradient computations needed for parameter optimization can be carried out +# with the elegant methods of automatic differentiation which facilitates simultaneous optimizations +# of circuit and Hamiltonian parameters in applications such as VQE molecular geometry +# optimizations. ii) By optimizing the molecular parameters such as the exponent and contraction +# coefficients of Gaussian functions of the basis set, one can reach a lower energy without +# increasing the number of basis functions. Can you think of other interesting molecular parameters +# that can be optimized along with the nuclear coordinates and basis set parameters that we +# optimized in this tutorial? +# +# References +# ---------- +# +# .. [#arrazola2021] +# +# Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni *et al.*, "Differentiable +# quantum computational chemistry with PennyLane". `arXiv:2111.09967 +# `__ +# +# .. [#szabo1996] +# +# Attila Szabo, Neil S. Ostlund, "Modern Quantum Chemistry: Introduction to Advanced Electronic +# Structure Theory". Dover Publications, 1996. +# +# diff --git a/demonstrations_v2/tutorial_differentiable_HF/metadata.json b/demonstrations_v2/tutorial_differentiable_HF/metadata.json new file mode 100644 index 0000000000..2602060c4a --- /dev/null +++ b/demonstrations_v2/tutorial_differentiable_HF/metadata.json @@ -0,0 +1,66 @@ +{ + "title": "Differentiable Hartree-Fock", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2022-05-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_differentiable_Hartree-Fock.png" + } + ], + "seoDescription": "Learn how to use the differentiable Hartree-Fock solver", + "doi": "", + "references": [ + { + "id": "arrazola2021", + "type": "article", + "title": "Differentiable quantum computational chemistry with PennyLane", + "authors": "Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni et al.", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2111.09967" + }, + { + "id": "szabo1996", + "type": "book", + "title": "Modern Quantum Chemistry: Introduction to Advanced Electronic Structure Theory", + "authors": "Attila Szabo, Neil S. Ostlund", + "year": "1996", + "publisher": "Dover Publications", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_adaptive_circuits", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_differentiable_HF/requirements.in b/demonstrations_v2/tutorial_differentiable_HF/requirements.in new file mode 100644 index 0000000000..8aa5764d33 --- /dev/null +++ b/demonstrations_v2/tutorial_differentiable_HF/requirements.in @@ -0,0 +1,3 @@ +autograd +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_doubly_stochastic/demo.py b/demonstrations_v2/tutorial_doubly_stochastic/demo.py new file mode 100644 index 0000000000..18d6b6df2c --- /dev/null +++ b/demonstrations_v2/tutorial_doubly_stochastic/demo.py @@ -0,0 +1,404 @@ +r""" +Doubly stochastic gradient descent +================================== + +.. meta:: + :property="og:description": Minimize a Hamiltonian via an adaptive shot optimization + strategy with doubly stochastic gradient descent. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/single_shot.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_rosalin Frugal shot optimization with Rosalin + +*Author: Josh Izaac — Posted: 16 October 2019. Last updated: 20 January 2021.* + +In this tutorial we investigate and implement the doubly stochastic gradient descent +paper from `Ryan Sweke et al. (2019) `__. In this paper, +it is shown that quantum gradient descent, where a finite number of measurement samples +(or *shots*) are used to estimate the gradient, is a form of stochastic gradient descent. +Furthermore, if the optimization involves a linear combination of expectation values +(such as VQE), sampling from the terms in this linear combination can further reduce required +resources, allowing for "doubly stochastic gradient descent". + +Note that based on very similar observations, `Jonas Kuebler et al. (2019) `_ +recently proposed an optimizer (which they call the *individual Coupled Adaptive +Number of Shots (iCANS)* optimizer) that adapts the shot number of +measurements during training. + +Background +---------- + +In classical machine learning, `stochastic gradient descent +`_ is a common optimization strategy +where the standard gradient descent parameter update rule, + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta \nabla \mathcal{L}(\theta^{(t)}), + +is modified such that + +.. math:: \theta^{(t+1)} = \theta^{(t)} - \eta g^{(t)}(\theta^{(t)}) + +where :math:`\eta` is the step-size, and :math:`\{g^{(t)}(\theta)\}` is a sequence of random +variables such that + +.. math:: \mathbb{E}[g^{(t)}(\theta)] = \nabla\mathcal{L}(\theta). + +In general, stochastic gradient descent is preferred over standard gradient +descent for several reasons: + +1. Samples of the gradient estimator :math:`g^{(t)}(\theta)` can typically + be computed much more efficiently than :math:`\mathcal{L}(\theta),` + +2. Stochasticity can help to avoid local minima and saddle points, + +3. Numerical evidence shows that convergence properties are superior to regular gradient descent. + +In variational quantum algorithms, a parametrized quantum circuit :math:`U(\theta)` +is optimized by a classical optimization loop in order to minimize a function of the expectation +values. For example, consider the expectation values + +.. math:: \langle A_i \rangle = \langle 0 | U(\theta)^\dagger A_i U(\theta) | 0\rangle + +for a set of observables :math:`\{A_i\},` and loss function + +.. math:: \mathcal{L}(\theta, \langle A_1 \rangle, \dots, \langle A_M \rangle). + +While the expectation values can be calculated analytically in classical simulations, +on quantum hardware we are limited to *sampling* from the expectation values; as the +number of samples (or shots) increase, we converge on the analytic expectation value, but can +never recover the exact expression. Furthermore, the parameter-shift rule +(`Schuld et al., 2018 `__) allows for analytic +quantum gradients to be computed from a linear combination of the variational circuits' +expectation values. + +Putting these two results together, `Sweke et al. (2019) `__ +show that samples of the expectation value fed into the parameter-shift rule provide +unbiased estimators of the quantum gradient—resulting in a form of stochastic gradient descent +(referred to as QSGD). Moreover, they show that convergence of the stochastic gradient +descent is guaranteed in sufficiently simplified settings, even in the case where the number +of shots is 1! + +.. note:: + + It is worth noting that the smaller the number of shots used, the larger the + variance in the estimated expectation value. As a result, it may take + more optimization steps for convergence than using a larger number of shots, + or an exact value. + + At the same time, a reduced number of shots may significantly reduce the + wall time of each optimization step, leading to a reduction in the overall + optimization time. + +""" + +############################################################################## +# Let's consider a simple example in PennyLane, comparing analytic gradient +# descent (with exact expectation values) to stochastic gradient descent +# using a finite number of shots. +# +# A single-shot stochastic gradient descent +# ----------------------------------------- +# +# Consider the Hamiltonian +# +# .. math:: +# +# H = \begin{bmatrix} +# 8 & 4 & 0 & -6\\ +# 4 & 0 & 4 & 0\\ +# 0 & 4 & 8 & 0\\ +# -6 & 0 & 0 & 0 +# \end{bmatrix}. +# +# We can solve for the ground state energy using +# the variational quantum eigensolver (VQE) algorithm. +# +# Let's use the ``lightning.qubit`` simulator for both the analytic gradient, +# as well as the estimated gradient using number of shots :math:`N\in\{1, 100\}.` + +import pennylane as qml +import numpy as np +from pennylane import numpy as pnp + +np.random.seed(3) + +from pennylane import expval +from pennylane.templates.layers import StronglyEntanglingLayers + +num_layers = 2 +num_wires = 2 +eta = 0.01 +steps = 200 + +dev_analytic = qml.device("lightning.qubit", wires=num_wires, shots=None) +dev_stochastic = qml.device("lightning.qubit", wires=num_wires, shots=1000) + +############################################################################## +# We can use ``qml.Hermitian`` to directly specify that we want to measure +# the expectation value of the matrix :math:`H:` + +H = np.array([[8, 4, 0, -6], [4, 0, 4, 0], [0, 4, 8, 0], [-6, 0, 0, 0]]) + + +def circuit(params): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + return expval(qml.Hermitian(H, wires=[0, 1])) + + +############################################################################## +# Now, we create three QNodes, each corresponding to a device above, +# and optimize them using gradient descent via the parameter-shift rule. + +qnode_analytic = qml.QNode(circuit, dev_analytic, interface="autograd", diff_method="parameter-shift") +qnode_stochastic = qml.QNode(circuit, dev_stochastic, interface="autograd", diff_method="parameter-shift") + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = pnp.random.uniform(low=0, high=2 * np.pi, size=param_shape, requires_grad=True) + +# Optimizing using exact gradient descent + +cost_GD = [] +params_GD = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_GD.append(qnode_analytic(params_GD)) + params_GD = opt.step(qnode_analytic, params_GD) + +# Optimizing using stochastic gradient descent with shots=1 + +cost_SGD1 = [] +params_SGD1 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD1.append(qnode_stochastic(params_SGD1, shots=1)) + params_SGD1 = opt.step(qnode_stochastic, params_SGD1, shots=1) + +# Optimizing using stochastic gradient descent with shots=100 + +cost_SGD100 = [] +params_SGD100 = init_params +opt = qml.GradientDescentOptimizer(eta) + +for _ in range(steps): + cost_SGD100.append(qnode_stochastic(params_SGD100, shots=100)) + params_SGD100 = opt.step(qnode_stochastic, params_SGD100, shots=100) + +############################################################################## +# Note that in the latter two cases we are sampling from an unbiased +# estimator of the cost function, not the analytic cost function. +# +# To track optimization convergence, approaches could include: +# +# * Evaluating the cost function with a larger number of samples at specified +# intervals, +# +# * Keeping track of the *moving average* of the low-shot cost evaluations. +# +# We can now plot the cost against optimization step for the three cases above. + +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(cost_GD[:100], label="Vanilla gradient descent") +plt.plot(cost_SGD100[:100], "--", label="QSGD (100 shots)") +plt.plot(cost_SGD1[:100], ".", label="QSGD (1 shot)") + +# analytic ground state +min_energy = min(np.linalg.eigvalsh(H)) +plt.hlines(min_energy, 0, 100, linestyles=":", label="Ground-state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# Using the trained parameters from each optimization strategy, we can +# evaluate the analytic quantum device: + +print("Vanilla gradient descent min energy = ", qnode_analytic(params_GD)) +print( + "Stochastic gradient descent (shots=100) min energy = ", + qnode_analytic(params_SGD100), +) +print( + "Stochastic gradient descent (shots=1) min energy = ", qnode_analytic(params_SGD1) +) + + +############################################################################## +# Amazingly, we see that even the ``shots=1`` optimization converged +# to a reasonably close approximation of the ground-state energy! + +############################################################################## +# Doubly stochastic gradient descent for VQE +# ------------------------------------------ +# +# As noted in `Sweke et al. (2019) `__, +# variational quantum algorithms often include terms consisting of linear combinations +# of expectation values. This is true of the parameter-shift rule (where the +# gradient of each parameter is determined by shifting the parameter by macroscopic +# amounts and taking the difference), as well as VQE, where the Hamiltonian +# is usually decomposed into a sum of Pauli expectation values. +# +# Consider the Hamiltonian from the previous section. As this Hamiltonian is a +# Hermitian observable, we can always express it as a sum of Pauli matrices using +# the relation +# +# .. math:: +# +# H = \sum_{i,j=0,1,2,3} a_{i,j} (\sigma_i\otimes \sigma_j), +# +# where +# +# .. math:: +# +# a_{i,j} = \frac{1}{4}\text{tr}[(\sigma_i\otimes \sigma_j )H], ~~ \sigma = \{I, X, Y, Z\}. +# +# Applying this, we can see that +# +# .. math:: +# +# H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. +# +# To perform "doubly stochastic" gradient descent, we simply apply the stochastic +# gradient descent approach from above, but in addition also uniformly sample +# a subset of the terms for the Hamiltonian expectation at each optimization step. +# This inserts another element of stochasticity into the system—all the while +# convergence continues to be guaranteed! +# +# Let's create a QNode that randomly samples a single term from the above +# Hamiltonian as the observable to be measured. + +I = np.identity(2) +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +terms = np.array( + [ + 2 * np.kron(I, X), + 4 * np.kron(I, Z), + -np.kron(X, X), + 5 * np.kron(Y, Y), + 2 * np.kron(Z, X), + ] +) + + +@qml.qnode(dev_stochastic, interface="autograd") +def circuit(params, n=None): + StronglyEntanglingLayers(weights=params, wires=[0, 1]) + idx = np.random.choice(np.arange(5), size=n, replace=False) + A = np.sum(terms[idx], axis=0) + return expval(qml.Hermitian(A, wires=[0, 1])) + + +def loss(params, shots=None): + return 4 + (5 / 1) * circuit(params, shots=shots, n=1) + + +############################################################################## +# Optimizing the circuit using gradient descent via the parameter-shift rule: + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for _ in range(250): + cost.append(loss(params, shots=100)) + params = opt.step(loss, params, shots=100) + + +############################################################################## +# During doubly stochastic gradient descent, we are sampling from terms of the +# analytic cost function, so it is not entirely instructive to plot the cost +# versus optimization step—partial sums of the terms in the Hamiltonian +# may have minimum energy below the ground state energy of the total Hamiltonian. +# Nevertheless, we can keep track of the cost value moving average during doubly +# stochastic gradient descent as an indicator of convergence. + + +def moving_average(data, n=3): + ret = np.cumsum(data, dtype=np.float64) + ret[n:] = ret[n:] - ret[:-n] + return ret[n - 1:] / n + + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Doubly QSGD") +plt.plot(average[0], average[1], "--", label="Doubly QSGD (moving average)") +plt.hlines(min_energy, 0, 200, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +############################################################################## +# Finally, verifying that the doubly stochastic gradient descent optimization +# correctly provides the ground state energy when evaluated for a larger +# number of shots: + +print("Doubly stochastic gradient descent min energy = ", qnode_analytic(params)) + +############################################################################## +# While stochastic gradient descent requires more optimization steps to achieve +# convergence, it is worth noting that it requires significantly fewer quantum +# device evaluations, and thus may as a result take less time overall. + +############################################################################## +# Adaptive stochasticity +# ---------------------- +# +# To improve on the convergence, we may even consider a crude "adaptive" modification +# of the doubly stochastic gradient descent optimization performed above. In this +# approach, we successively increase the number of terms we are sampling from as +# the optimization proceeds, as well as increasing the number of shots. + +cost = [] +params = init_params +opt = qml.GradientDescentOptimizer(0.005) + +for i in range(250): + n = min(i // 25 + 1, 5) + + def loss(params, shots=None): + return 4 + (5 / n) * circuit(params, shots=shots, n=n) + + cost.append(loss(params, shots=int(1 + (n - 1) ** 2))) + params = opt.step(loss, params, shots=int(1 + (n - 1) ** 2)) + +average = np.vstack([np.arange(25, 200), moving_average(cost, n=50)[:-26]]) + +plt.plot(cost_GD, label="Vanilla gradient descent") +plt.plot(cost, ".", label="Adaptive QSGD") +plt.plot(average[0], average[1], "--", label="Adaptive QSGD (moving average)") +plt.hlines(min_energy, 0, 250, linestyles=":", label="Ground state energy") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.xlim(-2, 200) +plt.legend() +plt.show() + +print("Adaptive QSGD min energy = ", qnode_analytic(params)) + +############################################################################## +# References +# ---------- +# +# 1. Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, +# Barthélémy Meynard-Piganeau, Jens Eisert. "Stochastic gradient descent for +# hybrid quantum-classical optimization." `arXiv:1910.01155 +# `__, 2019. +# +# diff --git a/demonstrations_v2/tutorial_doubly_stochastic/metadata.json b/demonstrations_v2/tutorial_doubly_stochastic/metadata.json new file mode 100644 index 0000000000..a5d00e7511 --- /dev/null +++ b/demonstrations_v2/tutorial_doubly_stochastic/metadata.json @@ -0,0 +1,52 @@ +{ + "title": "Doubly stochastic gradient descent", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_doubly_stochastic_gradient_descent.png" + } + ], + "seoDescription": "Minimize a Hamiltonian via an adaptive shot optimization strategy with doubly stochastic gradient descent.", + "doi": "", + "references": [ + { + "id": "Sweke2019", + "type": "article", + "title": "Stochastic gradient descent for hybrid quantum-classical optimization", + "authors": "Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. F\u00e4hrmann, Barth\u00e9l\u00e9my Meynard-Piganeau, Jens Eisert", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.01155" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rosalin", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_doubly_stochastic/requirements.in b/demonstrations_v2/tutorial_doubly_stochastic/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_doubly_stochastic/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_eqnn_force_field/demo.py b/demonstrations_v2/tutorial_eqnn_force_field/demo.py new file mode 100644 index 0000000000..663d529bc3 --- /dev/null +++ b/demonstrations_v2/tutorial_eqnn_force_field/demo.py @@ -0,0 +1,631 @@ +r"""Symmetry-invariant quantum machine learning force fields +======================================================== + + +Symmetries are ubiquitous in physics. From condensed matter to particle physics, they have helped us +make connections and formulate new theories. In the context of machine learning, inductive bias has +proven to be successful in the presence of symmetries. This framework, known as geometric deep +learning, often enjoys better generalization and trainability. In this demo, we will learn how to +use geometric quantum machine learning to drive molecular dynamics as introduced in recent research [#Le23]_. We +will take as an example a triatomic molecule of :math:`H_2O.` + + +Introduction +----------------------------------- + +First, let’s introduce the overall playground of this work: **molecular dynamics (MD)**. MD is an +essential computational simulation method to analyze the dynamics of atoms or molecules in a +chemical system. The simulations can be used to obtain macroscopic thermodynamic properties of +ergodic systems. Within the simulation, Newton's equations of motion are numerically integrated. Therefore, +it is crucial to have access to the forces acting on the constituents of the system or, equivalently, +the potential energy surface (PES), from which we can obtain the atomic forces. Previous research by [#Kiss22]_ presented variational +quantum learning models (VQLMs) that were able to learn the potential energy and atomic forces of +a selection of molecules from *ab initio* reference data. + + +The description of molecules can be greatly simplified by considering inherent **symmetries**. For +example, actions such as translation, rotation, or the interchange of identical atoms or molecules +leave the system unchanged. To achieve better performance, it is thus desirable to include this +information in our model. To do so, the data input can simply be made invariant itself, e.g., by +making use of so-called symmetry functions–hence yielding invariant energy predictions. + +In this demo, we instead take the high road and design an intrinsically symmetry-aware model based on +equivariant quantum neural networks. Equivariant machine learning models have demonstrated many advantages +such as being more robust to noisy data and enjoying better generalization capabilities. Moreover, this has the additional +advantage of relaxing the need for data preprocessing, as the raw Cartesian coordinates can be given directly as inputs to the learning +model. + +An overview of the workflow is shown in the figure below. First, the relevant symmetries are identified +and used to build the quantum machine model. We then train it on the PES of some molecule, e.g. :math:`H_2O,` +and finally obtain the forces by computing the gradient of the learned PES. + +.. figure:: ../_static/demonstration_assets/eqnn_force_field/overview.png + :align: center + :width: 60% + + +Equivariant Quantum Machine learning +----------------------------------- + +In order to incorporate symmetries into machine learning models, we need a few concepts from group theory. A formal course on the +subject is out of the scope of the present document, which is why we refer to two other demos, `equivariant graph embedding `_ +and `geometric quantum machine learning `_, as well as Ref. [#Meyer23]_ for a more thorough introduction. + +In the following, we will denote elements of a symmetry group :math:`G` with :math:`g \in G.` :math:`G` could be for instance the rotation group :math:`SO(3),` +or the permutation group :math:`S_n.` Groups are often easier understood in terms of their representation :math:`V_g : \mathcal{V} \rightarrow \mathcal{V}` which maps group elements +to invertible linear operations, i.e. to :math:`GL(n),` on some vector space :math:`\mathcal{V}.` We call a function :math:`f: \mathcal{V} \rightarrow \mathcal{W}` *invariant* with respect to the action of +the group, if + +.. math:: f(V_g(v)) = f(v), \text{ for all } g \in G. + +The concept of *equivariance* is a bit weaker, as it only requires the function to *commute* with the group action, instead of remaining constant. +In mathematical terms, we require that + +.. math:: f(V_g(v)) = \mathcal{R}_g(f(v)), \text{ for all } g \in G, + +with :math:`\mathcal{R}` being a representation of :math:`G` on the vector space :math:`\mathcal{W}.` These concepts are important in +machine learning, as they tell us how the internal structure of the data, described by the group, is conserved when passing through the model. +In the remaining, we will refer to :math:`\mathcal{V}` and :math:`V_g` as the data space and the representation on it, respectively, +and :math:`\mathcal{W}` and :math:`\mathcal{R}_g` as the qubit space and the symmetry action on it, respectively. + +Now that we have the basics, we will focus on the task at hand: building an equivariant quantum neural network for chemistry! + +We use a `quantum reuploading model `__, which consists of a +variational ansatz :math:`M_\Theta(\mathcal{X})` applied to some initial state +:math:`|\psi_0\rangle.` Here, :math:`\mathcal{X}` denotes the description of a molecular configuration, i.e., +the set of Cartesian coordinates of the atoms. The quantum circuit is given by + +.. math:: M_\Theta(\mathcal{X}) = \left[ \prod_{d=D}^1 \Phi(\mathcal{X}) \mathcal{U}_d(\vec{\theta}_d) \right] \Phi(\mathcal{X}), + +and depends on both data :math:`\mathcal{X}` and trainable parameters :math:`\Theta = \{\vec{\theta}_d\}_{d=1}^D.` +It is built by interleaving parametrized trainable layers :math:`U_d(\vec{\theta}_d)` with data +encoding layers :math:`\Phi(\mathcal{X}).` The corresponding quantum function +:math:`f_{\Theta}(\mathcal{X})` is then given by the expectation value of a chosen observable +:math:`O` + +.. math:: f_\Theta(\mathcal{X}) = \langle \psi_0 | M_\Theta(\mathcal{X})^\dagger O M_\Theta(\mathcal{X}) |\psi_0 \rangle. + +For the cases of a diatomic molecule (e.g. :math:`LiH`) and a triatomic molecule of two atom types (e.g. :math:`H_2O`), panel (a) +of the following figure displays the descriptions of the chemical systems by the Cartesian coordinates of their +atoms, while the general circuit formulation of the corresponding symmetry-invariant VQLM for these cases is shown in panel (b). Note that +we will only consider the triatomic molecule :math:`H_2O` in the rest of this demo. + +.. figure:: ../_static/demonstration_assets/eqnn_force_field/siVQLM_monomer.jpg + :align: center + :width: 90% + +An overall invariant model is composed of four ingredients: an invariant initial state, an +equivariant encoding layer, equivariant trainable layers, and finally an invariant observable. Here, +equivariant encoding means that applying the symmetry transformation first on the atomic +configuration :math:`\mathcal{X}` and then encoding it into the qubits produces the same results as +first encoding :math:`\mathcal{X}` and then letting the symmetry act on the qubits, i.e., + +.. math:: \Phi(V_g[\mathcal{X}]) = \mathcal{R}_g \Phi(\mathcal{X}) \mathcal{R}_g^\dagger, + +where :math:`V_g` and :math:`\mathcal{R}_g` denote the symmetry representation on the data and qubit level, respectively. + +For the trainable layer, equivariance means that the order of applying the symmetry and the +parametrized operations does not matter: + +.. math:: \left[\mathcal{U}_d(\vec{\theta}_d), \mathcal{R}_g\right]=0. + +Furthermore, we need to find an invariant observable :math:`O = \mathcal{R}_g O \mathcal{R}_g^\dagger` and an initial state +:math:`|\psi_0\rangle = \mathcal{R}_g |\psi_0\rangle,` i.e., which can absorb the symmetry action. Putting all this together +results in a symmetry-invariant VQLM as required. + +In this demo, we will consider the example of a triatomic molecule of two atom types, such as a +water molecule. In this case, the system is invariant under translations, rotations, and the +exchange of the two hydrogen atoms. Translational symmetry is included by taking the +central atom as the origin. Therefore, we only need to encode the coordinates of the two identical *active* atoms, which +we will call :math:`\vec{x}_1` and :math:`\vec{x}_2.` + +Let’s implement the model depicted above! + + +""" + +###################################################################### +# Implementation of the VQLM +# -------------------------- +# We start by importing the libraries that we will need. + +import pennylane as qml +import numpy as np + +import jax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update("jax_enable_x64", True) + +from jax import numpy as jnp + +import scipy +import matplotlib.pyplot as plt +import sklearn + +###################################################################### +# Let us construct Pauli matrices, which are used to build the Hamiltonian. +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1.0j], [1.0j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +sigmas = jnp.array(np.array([X, Y, Z])) # Vector of Pauli matrices +sigmas_sigmas = jnp.array( + np.array( + [ + np.kron(X, X), + np.kron(Y, Y), + np.kron(Z, Z), + ] # Vector of tensor products of Pauli matrices + ) +) + + +###################################################################### +# We start by considering **rotational invariance** and building an initial state invariant under +# rotation, such as the singlet state :math:`|S\rangle = \frac{|01⟩−|10⟩}{\sqrt{2}}.` A general +# :math:`2n`-invariant state can be obtained by taking :math:`n`-fold tensor product. +# + + +def singlet(wires): + # Encode a 2-qubit rotation-invariant initial state, i.e., the singlet state. + + qml.Hadamard(wires=wires[0]) + qml.PauliZ(wires=wires[0]) + qml.PauliX(wires=wires[1]) + qml.CNOT(wires=wires) + + +###################################################################### +# Next, we need a rotationally equivariant data embedding. We choose to encode a three-dimensional +# data point :math:`\vec{x}\in \mathbb{R}^3` via +# +# .. math:: \Phi(\vec{x}) = \exp\left( -i\alpha_\text{enc} [xX + yY + zZ] \right), +# +# where we introduce a trainable encoding angle :math:`\alpha_\text{enc}\in\mathbb{R}.` This encoding +# scheme is indeed equivariant since embedding a rotated data point is the same as embedding the +# original data point and then letting the rotation act on the qubits: +# :math:`\Phi(r(\psi,\theta,\phi)\vec{x}) = U(\psi,\theta,\phi) \Phi(\vec{x}) U(\psi,\theta,\phi)^\dagger.` +# For this, we have noticed that any rotation on the data level can be parametrized by three angles +# :math:`V_g = r(\psi,\theta,\phi),` which can also be used to parametrize the corresponding +# single-qubit rotation :math:`\mathcal{R}_g = U(\psi,\theta,\phi),` implemented by the usual +# `qml.rot `__ +# operation. We choose to encode each atom +# twice in parallel, resulting in higher expressivity. We can do so by simply using this encoding scheme twice for each +# active atom (the two Hydrogens in our case): +# +# .. math:: \Phi(\vec{x}_1, \vec{x}_2) = \Phi^{(1)}(\vec{x}_1) \Phi^{(2)}(\vec{x}_2) \Phi^{(3)}(\vec{x}_1) \Phi^{(4)}(\vec{x}_2). +# + + +def equivariant_encoding(alpha, data, wires): + # data (jax array): cartesian coordinates of atom i + # alpha (jax array): trainable scaling parameter + + hamiltonian = jnp.einsum("i,ijk", data, sigmas) # Heisenberg Hamiltonian + U = jax.scipy.linalg.expm(-1.0j * alpha * hamiltonian / 2) + qml.QubitUnitary(U, wires=wires, id="E") + + +###################################################################### +# Finally, we require an equivariant trainable map and an invariant observable. We take the Heisenberg +# Hamiltonian, which is rotationally invariant, as an inspiration. We define a single summand of it, +# :math:`H^{(i,j)}(J) = -J\left( X^{(i)}X^{(j)} + Y^{(i)}Y^{(j)} + Z^{(i)}Z^{(j)} \right),` as a +# rotationally invariant two-qubit operator and choose +# +# .. math:: O = X^{(0)}X^{(1)} + Y^{(0)}Y^{(1)} + Z^{(0)}Z^{(1)} +# +# as our observable. +# +# Furthermore, we can obtain an equivariant parametrized operator by exponentiating this Heisenberg +# interaction: +# +# .. math:: RH^{(i,j)}(J) = \exp\left( -iH^{(i,j)}(J) \right), +# +# where :math:`J\in\mathbb{R}` is a trainable parameter. By combining this exponentiated operator for +# different pairs of qubits, we can design our equivariant trainable layer +# +# .. math:: \mathcal{U}(\vec{j}) = RH^{(1,2)}(j_1) RH^{(3,4)}(j_2) RH^{(2,3)}(j_3) +# +# In the case of a triatomic molecule of two atom types, we need to modify the previous VQLM to +# additionally take into account the **invariance under permutations of the same atom types**. +# +# Interchanging two atoms is represented on the data level by simply interchanging the corresponding +# coordinates, :math:`V_g = \sigma(\vec{x}_1, \vec{x}_2) = (\vec{x}_2, \vec{x}_1).` On the Hilbert +# space this is represented by swapping the corresponding qubits, +# :math:`\mathcal{R}_g = U(i,j) = SWAP(i,j).` +# +# The singlet state is not only rotationally invariant but also permutationally invariant under swapping +# certain qubit pairs, so we can keep it. The previous embedding scheme for one data point can +# be extended for embedding two atoms and we see that this is indeed not only rotationally +# equivariant but also equivariant with respect to permutations, since encoding two swapped atoms is +# just the same as encoding the atoms in the original order and then swapping the qubits: +# :math:`\Phi\left( \sigma(\vec{x}_1, \vec{x}_2) \right) = SWAP(i,j) \Phi(\vec{x}_1, \vec{x}_2) SWAP(i,j).` +# Again, we choose to encode each atom twice as depicted above. +# +# For the invariant observable :math:`O,` we note that our Heisenberg interaction is invariant under the swapping +# of the two involved qubits, therefore we can make use of the same observable as before. +# +# For the equivariant parametrized layer we need to be careful when it comes to the selection of qubit +# pairs in order to obtain equivariance, i.e., operations that commute with the swappings. This is +# fulfilled by coupling only the qubits with are neighbors with respect to the 1-2-3-4-1 ring topology, leading to the following operation: +# +# .. math:: \mathcal{U}(\vec{j}) = RH^{(1,2)}(j_1) RH^{(3,4)}(j_2) RH^{(2,3)}(j_3) RH^{(1,4)}(j_3) +# +# In code, we have: +# + + +def trainable_layer(weight, wires): + hamiltonian = jnp.einsum("ijk->jk", sigmas_sigmas) + U = jax.scipy.linalg.expm(-1.0j * weight * hamiltonian) + qml.QubitUnitary(U, wires=wires, id="U") + + +# Invariant observable +Heisenberg = [ + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliZ(0) @ qml.PauliZ(1), +] +Observable = qml.Hamiltonian(np.ones((3)), Heisenberg) + +###################################################################### +# It has been observed that a small amount of **symmetry-breaking** (SB) can improve the convergence +# of the VQLM. We implement it by adding a small rotation around the :math:`z`-axis. +# + + +def noise_layer(epsilon, wires): + for _, w in enumerate(wires): + qml.RZ(epsilon[_], wires=[w]) + + +###################################################################### +# When setting up the model, the hyperparameters such as the number of repetitions of encoding and +# trainable layers have to be chosen suitably. In this demo, we choose six layers (:math:`D=6`) and +# one repetition of trainable gates inside each layer (:math:`B=1`) to reduce long runtimes. Note that this choice differs from the original paper, so the results +# therein will not be fully reproduced +# within this demo. We start by defining the relevant hyperparameters and the VQLM. +# + +############ Setup ############## +D = 6 # Depth of the model +B = 1 # Number of repetitions inside a trainable layer +rep = 2 # Number of repeated vertical encoding + +active_atoms = 2 # Number of active atoms +# Here we only have two active atoms since we fixed the oxygen (which becomes non-active) at the origin +num_qubits = active_atoms * rep +################################# + + +dev = qml.device("default.qubit", wires=num_qubits) + + +@qml.qnode(dev, interface="jax") +def vqlm(data, params): + + weights = params["params"]["weights"] + alphas = params["params"]["alphas"] + epsilon = params["params"]["epsilon"] + + # Initial state + for i in range(rep): + singlet(wires=np.arange(active_atoms * i, active_atoms * (1 + i))) + + # Initial encoding + for i in range(num_qubits): + equivariant_encoding( + alphas[i, 0], jnp.asarray(data, dtype=complex)[i % active_atoms, ...], wires=[i] + ) + + # Reuploading model + for d in range(D): + qml.Barrier() + + for b in range(B): + # Even layer + for i in range(0, num_qubits - 1, 2): + trainable_layer(weights[i, d + 1, b], wires=[i, (i + 1) % num_qubits]) + + # Odd layer + for i in range(1, num_qubits, 2): + trainable_layer(weights[i, d + 1, b], wires=[i, (i + 1) % num_qubits]) + + # Symmetry-breaking + if epsilon is not None: + noise_layer(epsilon[d, :], range(num_qubits)) + + # Encoding + for i in range(num_qubits): + equivariant_encoding( + alphas[i, d + 1], + jnp.asarray(data, dtype=complex)[i % active_atoms, ...], + wires=[i], + ) + + return qml.expval(Observable) + + +###################################################################### +# Simulation for the water molecule +# ----------------------------------- +# +# We start by downloading the `dataset `__, which we have prepared for +# convenience as a Python ndarray. In the following, we will load, preprocess and split the data into a +# training and testing set, following standard practices. +# + +# Load the data +energy = np.load("eqnn_force_field_data/Energy.npy") +forces = np.load("eqnn_force_field_data/Forces.npy") +positions = np.load( + "eqnn_force_field_data/Positions.npy" +) # Cartesian coordinates shape = (nbr_sample, nbr_atoms,3) +shape = np.shape(positions) + +### Scaling the energy to fit in [-1,1] +from sklearn.preprocessing import MinMaxScaler + +scaler = MinMaxScaler((-1, 1)) + +energy = scaler.fit_transform(energy) +forces = forces * scaler.scale_ + + +# Placing the oxygen at the origin +data = np.zeros((shape[0], 2, 3)) +data[:, 0, :] = positions[:, 1, :] - positions[:, 0, :] +data[:, 1, :] = positions[:, 2, :] - positions[:, 0, :] +positions = data.copy() + +forces = forces[:, 1:, :] # Select only the forces on the hydrogen atoms since the oxygen is fixed + + +# Splitting in train-test set +indices_train = np.random.choice(np.arange(shape[0]), size=int(0.8 * shape[0]), replace=False) +indices_test = np.setdiff1d(np.arange(shape[0]), indices_train) + +E_train, E_test = (energy[indices_train, 0], energy[indices_test, 0]) +F_train, F_test = forces[indices_train, ...], forces[indices_test, ...] +data_train, data_test = ( + jnp.array(positions[indices_train, ...]), + jnp.array(positions[indices_test, ...]), +) + +################################# +# We will know define the cost function and how to train the model using Jax. We will use the mean-square-error loss function. +# To speed up the computation, we use the decorator ``@jax.jit`` to do just-in-time compilation for this execution. This means the first execution will typically take a little longer with the +# benefit that all following executions will be significantly faster, see the `Jax docs on jitting `_. + +################################# +from jax.example_libraries import optimizers + +# We vectorize the model over the data points +vec_vqlm = jax.vmap(vqlm, (0, None), 0) + + +# Mean-squared-error loss function +@jax.jit +def mse_loss(predictions, targets): + return jnp.mean(0.5 * (predictions - targets) ** 2) + + +# Make prediction and compute the loss +@jax.jit +def cost(weights, loss_data): + data, E_target, F_target = loss_data + E_pred = vec_vqlm(data, weights) + l = mse_loss(E_pred, E_target) + + return l + + +# Perform one training step +@jax.jit +def train_step(step_i, opt_state, loss_data): + + net_params = get_params(opt_state) + loss, grads = jax.value_and_grad(cost, argnums=0)(net_params, loss_data) + + return loss, opt_update(step_i, grads, opt_state) + + +# Return prediction and loss at inference times, e.g. for testing +@jax.jit +def inference(loss_data, opt_state): + + data, E_target, F_target = loss_data + net_params = get_params(opt_state) + + E_pred = vec_vqlm(data, net_params) + l = mse_loss(E_pred, E_target) + + return E_pred, l + + +################################# +# **Parameter initialization:** +# +# We initiliase the model at the identity by setting the initial parameters to 0, except the first one which is chosen uniformly. +# This ensures that the circuit is shallow at the beginning and has less chance of suffering from the barren plateau phenomenon. Moreover, +# we disable the symmetry-breaking strategy, as it is mainly useful for larger systems. +np.random.seed(42) +weights = np.zeros((num_qubits, D, B)) +weights[0] = np.random.uniform(0, np.pi, 1) +weights = jnp.array(weights) + +# Encoding weights +alphas = jnp.array(np.ones((num_qubits, D + 1))) + +# Symmetry-breaking (SB) +np.random.seed(42) +epsilon = jnp.array(np.random.normal(0, 0.001, size=(D, num_qubits))) +epsilon = None # We disable SB for this specific example +epsilon = jax.lax.stop_gradient(epsilon) # comment if we wish to train the SB weights as well. + + +opt_init, opt_update, get_params = optimizers.adam(1e-2) +net_params = {"params": {"weights": weights, "alphas": alphas, "epsilon": epsilon}} +opt_state = opt_init(net_params) +running_loss = [] +################################# +# We train our VQLM using stochastic gradient descent. + + +num_batches = 5000 # number of optimization steps +batch_size = 256 # number of training data per batch + + +for ibatch in range(num_batches): + # select a batch of training points + batch = np.random.choice(np.arange(np.shape(data_train)[0]), batch_size, replace=False) + + # preparing the data + loss_data = data_train[batch, ...], E_train[batch, ...], F_train[batch, ...] + loss_data_test = data_test, E_test, F_test + + # perform one training step + loss, opt_state = train_step(num_batches, opt_state, loss_data) + + # computing the test loss and energy predictions + E_pred, test_loss = inference(loss_data_test, opt_state) + running_loss.append([float(loss), float(test_loss)]) + +################################### +# Let us inspect the results. The following figure displays the training (in red) and testing (in blue) loss during the optimization. We observe that they are on top of each other, meaning that the model +# is training and generalising properly to the unseen test set. +# +history_loss = np.array(running_loss) + +fontsize = 12 +plt.figure(figsize=(4, 4)) +plt.plot(history_loss[:, 0], "r-", label="training error") +plt.plot(history_loss[:, 1], "b-", label="testing error") + +plt.yscale("log") +plt.xlabel("Optimization Steps", fontsize=fontsize) +plt.ylabel("Mean Squared Error", fontsize=fontsize) +plt.legend(fontsize=fontsize) +plt.tight_layout() +plt.show() +###################################################################### +# Energy predictions +# ~~~~~~~~~~~~~~~~~~ +# +# We first inspect the quality of the energy predictions. The exact test energy points are shown in black, while the predictions are in red. +# On the left, we see the exact data against the predicted ones (so the red points should be in the diagonal line), while the right plots show the +# energy as a scatter plot. The model is able to make fair predictions, especially near the equilibrium position. However, a few points in the higher energy range +# could be improved, e.g. by using a deeper model as in the original paper. +# + +plt.figure(figsize=(4, 4)) +plt.title("Energy predictions", fontsize=fontsize) +plt.plot(energy[indices_test], E_pred, "ro", label="Test predictions") +plt.plot(energy[indices_test], energy[indices_test], "k.-", lw=1, label="Exact") +plt.xlabel("Exact energy", fontsize=fontsize) +plt.ylabel("Predicted energy", fontsize=fontsize) +plt.legend(fontsize=fontsize) +plt.tight_layout() +plt.show() + + +###################################################################### +# Force predictions +# ~~~~~~~~~~~~~~~~~ +# +# As stated at the beginning, we are interested in obtaining the forces to drive MD simulations. Since +# we have access to the potential energy surface, the forces are directly available by taking the +# gradient +# +# .. math:: F_{i,j} = -\nabla_{\mathcal{X}_{ij}} E(\mathcal{X}, \Theta), +# +# where :math:`\mathcal{X}_{ij}` contains the :math:`j` coordinate of the :math:`i`-th atom, and :math:`\Theta` +# are the trainable parameters. In our framework, we can simply do the following. We note that we +# do not require the mixed terms of the Jacobian, which is why we select the diagonal part using ``numpy.einsum``. +# + +opt_params = get_params(opt_state) # Obtain the optimal parameters +gradient_coordinates = jax.jacobian( + vec_vqlm, argnums=0 +) # Compute the gradient with respect to the Cartesian coordinates + +pred_forces = gradient_coordinates(jnp.array(positions.real), opt_params) +pred_forces = -np.einsum( + "iijk->ijk", np.array(pred_forces) +) # We are only interested in the diagonal part of the Jacobian + +fig, axs = plt.subplots(2, 3) + +fig.suptitle("Force predictions", fontsize=fontsize) +for k in range(2): + for l in range(3): + + axs[k, l].plot(forces[indices_test, k, l], forces[indices_test, k, l], "k.-", lw=1) + axs[k, l].plot(forces[indices_test, k, l], pred_forces[indices_test, k, l], "r.") + +axs[0, 0].set_ylabel("Hydrogen 1") +axs[1, 0].set_ylabel("Hydrogen 2") +for _, a in enumerate(["x", "y", "z"]): + axs[1, _].set_xlabel("{}-axis".format(a)) + +plt.tight_layout() +plt.show() +###################################################################### +# In this series of plots, we can see the predicted forces on the two Hydrogen atoms in the three :math:`x,` :math:`y` and :math:`z` directions. Again, the model +# does a fairly good job. The few points which are not on the diagonal can be improved using some tricks, such as incorporating the forces in the loss function. + +###################################################################### +# Conclusions +# ----------- +# +# In this demo, we saw how to implement a symmetry-invariant VQLM to learn the energy and forces of +# small chemical systems and trained it for the specific example of water. The strong points with +# respect to symmetry-agnostic techniques are better generalization, more accurate force predictions, +# resilience to small data corruption, and reduction in classical pre- and postprocessing, as +# supported by the original paper. +# +# Further work could be devoted to studying larger systems by adopting a more systematic fragmentation as +# discussed in the original paper. As an alternative to building symmetry-invariant quantum +# architectures, the symmetries could instead be incorporated into the training routine, such as +# recently proposed by [#Wierichs23]_. Finally, symmetry-aware +# models could be used to design quantum symmetry functions, which in turn could serve as +# symmetry-invariant descriptors of the chemical systems within classical deep learning architectures, +# which can be easily operated and trained at scale. +# + +###################################################################### +# Bibliography +# ------------ +# +# .. [#Le23] +# +# Isabel Nha Minh Le, Oriel Kiss, Julian Schuhmacher, Ivano Tavernelli, Francesco Tacchino, +# "Symmetry-invariant quantum machine learning force fields", +# `arXiv:2311.11362 `__, 2023. +# +# +# .. [#Kiss22] +# +# Oriel Kiss, Francesco Tacchino, Sofia Vallecorsa, Ivano Tavernelli, +# "Quantum neural networks force fields generation", +# `Mach.Learn.: Sci. Technol. 3 035004 `__, 2022. +# +# +# .. [#Meyer23] +# +# Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert, +# "Exploiting Symmetry in Variational Quantum Machine Learning", +# `PRX Quantum 4,010328 `__, 2023. +# +# +# .. [#Wierichs23] +# +# David Wierichs, Richard D. P. East, Martín Larocca, M. Cerezo, Nathan Killoran, +# "Symmetric derivatives of parametrized quantum circuits", +# `arXiv:2312.06752 `__, 2023. +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_eqnn_force_field/metadata.json b/demonstrations_v2/tutorial_eqnn_force_field/metadata.json new file mode 100644 index 0000000000..a08c1ad5f2 --- /dev/null +++ b/demonstrations_v2/tutorial_eqnn_force_field/metadata.json @@ -0,0 +1,108 @@ +{ + "title": "Symmetry-invariant quantum machine learning force fields", + "authors": [ + { + "username": "orielkiss" + }, + { + "username": "inmle" + } + ], + "dateOfPublication": "2024-03-12T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/eqnn_force_field/thumbnail_EQNNforcefield_2024-03-07.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_EQNNforceField_2024-03-07.png" + } + ], + "seoDescription": "What is equivariant quantum machine learning for chemistry?", + "doi": "", + "references": [ + { + "id": "Le", + "type": "article", + "title": "Symmetry-invariant quantum machine learning force fields", + "authors": "Isabel Nha Minh Le, Oriel Kiss, Julian Schuhmacher, Ivano Tavernelli and Francesco Tacchino", + "year": "2023", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2311.11362" + }, + { + "id": "kiss", + "type": "article", + "title": "Quantum neural networks force fields generation", + "authors": "Oriel Kiss, Francesco Tacchino, Sofia Vallecorsa and Ivano Tavernelli", + "year": "2023", + "publisher": "IOP", + "journal": " Mach. Learn.: Sci. Technol.", + "url": "https://iopscience.iop.org/article/10.1088/2632-2153/ac7d3c" + }, + { + "id": "schuld", + "type": "article", + "title": "Effect of data encoding on the expressive power of variational quantum-machine-learning models", + "authors": "Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer", + "year": "2021", + "publisher": "APS", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.032430" + }, + { + "id": "wierichs", + "type": "article", + "title": "Symmetric derivatives of parametrized quantum circuits", + "authors": "David Wierichs, Richard D. P. East, Mart\u00edn Larocca, M. Cerezo and Nathan Killoran", + "year": "2023", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2312.06752" + }, + { + "id": "meyer", + "type": "article", + "title": "Exploiting Symmetry in Variational Quantum Machine Learning", + "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, and Jens Eisert", + "year": "2023", + "publisher": "APS", + "journal": "Phys. Rev. X Quantum", + "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.4.010328" + } + ], + "basedOnPapers": [ + "https://arxiv.org/abs/2311.11362" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_contextuality", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_equivariant_graph_embedding", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_eqnn_force_field/requirements.in b/demonstrations_v2/tutorial_eqnn_force_field/requirements.in new file mode 100644 index 0000000000..8090cd5743 --- /dev/null +++ b/demonstrations_v2/tutorial_eqnn_force_field/requirements.in @@ -0,0 +1,7 @@ +jax +jaxlib +matplotlib +numpy +pennylane +scipy +scikit-learn diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py b/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py new file mode 100644 index 0000000000..9c47aa6550 --- /dev/null +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py @@ -0,0 +1,335 @@ +r""" +An equivariant graph embedding +============================== + +.. meta:: + :property="og:description": Find out more about how to embedd graphs into quantum states. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_equivariant_graph_embedding.png + +.. related:: + tutorial_geometric_qml Geometric quantum machine learning +""" + +###################################################################### +# A notorious problem when data comes in the form of graphs -- think of molecules or social media +# networks -- is that the numerical representation of a graph in a computer is not unique. +# For example, if we describe a graph via an `adjacency matrix `_ whose +# entries contain the edge weights as off-diagonals and node weights on the diagonal, +# any simultaneous permutation of rows and columns of this matrix refer to the same graph. +# +# .. figure:: ../_static/demonstration_assets/equivariant_graph_embedding/adjacency-matrices.png +# :width: 60% +# :align: center +# :alt: adjacency-matrices +# +# For example, the graph in the image above is represented by each of the two equivalent adjacency matrices. +# The top matrix can be transformed into the bottom matrix +# by swapping the first row with the third row, then swapping the third column with the third column, then the +# new first row with the second, and finally the first colum with the second. +# +# But the number of such permutations grows factorially with the number of nodes in the graph, which +# is even worse than an exponential growth! +# +# If we want computers to learn from graph data, we usually want our models to "know" that all these +# permuted adjacency matrices refer to the same object, so we do not waste resources on learning +# this property. In mathematical terms, this means that the model should be in- or +# equivariant (more about this distinction below) with respect to permutations. +# This is the basic motivation of `Geometric Deep Learning `_, +# ideas of which have found their way into quantum machine learning. +# +# This tutorial shows how to implement an example of a trainable permutation equivariant graph embedding +# as proposed in `Skolik et al. (2022) `_. The embedding +# maps the adjacency matrix of an undirected graph with edge and node weights to a quantum state, such that +# permutations of an adjacency matrix get mapped to the same states *if only we also +# permute the qubit registers in the same fashion*. +# +# .. note:: +# The tutorial is meant for beginners and does not contain the mathematical details of the +# rich theory of equivariance. Have a look +# `at this demo `_ if you want to know more. +# +# +# Permuted adjacency matrices describe the same graph +# --------------------------------------------------- +# +# Let us first verify that permuted adjacency matrices really describe one and the same graph. +# We also gain some useful data generation functions for later. +# +# First we create random adjacency matrices. +# The entry :math:`a_{ij}` of this matrix corresponds to the weight of the edge between nodes +# :math:`i` and :math:`j` in the graph. We assume that graphs have no self-loops; instead, +# the diagonal elements of the adjacency matrix are interpreted as node weights (or +# "node attributes"). +# +# Taking the example of a Twitter user retweet network, the nodes would be users, +# edge weights indicate how often two users retweet each other and node attributes +# could indicate the follower count of a user. +# + +import numpy as np +import networkx as nx +import matplotlib.pyplot as plt + +rng = np.random.default_rng(4324234) + +def create_data_point(n): + """ + Returns a random undirected adjacency matrix of dimension (n,n). + The diagonal elements are interpreted as node attributes. + """ + mat = rng.random((n, n)) + A = (mat + np.transpose(mat))/2 + return np.round(A, decimals=2) + +A = create_data_point(3) +print(A) + +###################################################################### +# Let's also write a function to generate permuted versions of this adjacency matrix. +# + +def permute(A, permutation): + """ + Returns a copy of A with rows and columns swapped according to permutation. + For example, the permutation [1, 2, 0] swaps 0->1, 1->2, 2->0. + """ + + P = np.zeros((len(A), len(A))) + for i,j in enumerate(permutation): + P[i,j] = 1 + + return P @ A @ np.transpose(P) + +A_perm = permute(A, [1, 2, 0]) +print(A_perm) + +###################################################################### +# If we create `networkx` graphs from both adjacency matrices and plot them, +# we see that they are identical as claimed. +# + +fig, (ax1, ax2) = plt.subplots(1, 2) + +# interpret diagonal of matrix as node attributes +node_labels = {n: A[n,n] for n in range(len(A))} +np.fill_diagonal(A, np.zeros(len(A))) + +G1 = nx.Graph(A) +pos1=nx.spring_layout(G1) +nx.draw(G1, pos1, labels=node_labels, ax=ax1, node_size = 800, node_color = "#ACE3FF") +edge_labels = nx.get_edge_attributes(G1,'weight') +nx.draw_networkx_edge_labels(G1,pos1,edge_labels=edge_labels, ax=ax1) + +# interpret diagonal of permuted matrix as node attributes +node_labels = {n: A_perm[n,n] for n in range(len(A_perm))} +np.fill_diagonal(A_perm, np.zeros(len(A))) + +G2 = nx.Graph(A_perm) +pos2=nx.spring_layout(G2) +nx.draw(G2, pos2, labels=node_labels, ax=ax2, node_size = 800, node_color = "#ACE3FF") +edge_labels = nx.get_edge_attributes(G2,'weight') +nx.draw_networkx_edge_labels(G2,pos2,edge_labels=edge_labels, ax=ax2) + +ax1.set_xlim([1.2*x for x in ax1.get_xlim()]) +ax2.set_xlim([1.2*x for x in ax2.get_xlim()]) +plt.tight_layout() +plt.show() + +###################################################################### +# .. note:: +# +# The issue of non-unique numerical representations of graphs ultimately stems +# from the fact that the nodes in a graph +# do not have an intrinsic order, and by labelling them in a numerical data structure like a matrix +# we therefore impose an arbitrary order. +# +# Permutation equivariant embeddings +# ---------------------------------- +# +# When we design a machine learning model that takes graph data, the first step is to encode +# the adjacency matrix into a quantum state using an embedding or +# `quantum feature map `_ +# :math:`\phi:` +# +# .. math:: +# +# A \rightarrow |\phi(A)\rangle . +# +# We may want the resulting quantum state to be the same for all adjacency matrices describing +# the same graph. In mathematical terms, this means that :math:`\phi` is an *invariant* embedding with respect to +# simultaneous row and column permutations :math:`\pi(A)` of the adjacency matrix: +# +# .. math:: +# +# |\phi(A) \rangle = |\phi(\pi(A))\rangle \;\; \text{ for all } \pi . +# +# However, invariance is often too strong a constraint. Think for example of an encoding that +# associates each node in the graph with a qubit. We might want permutations of the adjacency +# matrix to lead to the same state *up to an equivalent permutation of the qubits* :math:`P_{\pi},` +# where +# +# .. math:: +# +# P_{\pi} |q_1,...,q_n \rangle = |q_{\textit{perm}_{\pi}(1)}, ... q_{\textit{perm}_{\pi}(n)} \rangle . +# +# The function :math:`\text{perm}_{\pi}` maps each index to the permuted index according to :math:`\pi.` +# +# +# .. note:: +# +# The operator :math:`P_{\pi}` is implemented by PennyLane's :class:`~pennylane.Permute.` +# +# This results in an *equivariant* embedding with respect to permutations of the adjacency matrix: +# +# .. math:: +# +# |\phi(A) \rangle = P_{\pi}|\phi(\pi(A))\rangle \;\; \text{ for all } \pi . +# +# +# This is exactly what the following quantum embedding is aiming to do! The mathematical details +# behind these concepts use group theory and are beautiful, but can be a bit daunting. +# Have a look at `this paper `_ if you want to learn more. +# +# +# Implementation in PennyLane +# --------------------------- +# +# Let's get our hands dirty with an example. As mentioned, we will implement the permutation-equivariant +# embedding suggested in `Skolik et al. (2022) `_ which has this structure: +# +# .. figure:: ../_static/demonstration_assets/equivariant_graph_embedding/circuit.png +# :width: 70% +# :align: center +# :alt: Equivariant embedding +# +# The image can be found in `Skolik et al. (2022) `_ and shows one layer of the circuit. +# The :math:`\epsilon` are our edge weights while :math:`\alpha` describe the node weights, and the :math:`\beta,` :math:`\gamma` are variational parameters. +# +# In PennyLane this looks as follows: +# + + +import pennylane as qml + +def perm_equivariant_embedding(A, betas, gammas): + """ + Ansatz to embedd a graph with node and edge weights into a quantum state. + + The adjacency matrix A contains the edge weights on the off-diagonal, + as well as the node attributes on the diagonal. + + The embedding contains trainable weights 'betas' and 'gammas'. + """ + n_nodes = len(A) + n_layers = len(betas) # infer the number of layers from the parameters + + # initialise in the plus state + for i in range(n_nodes): + qml.Hadamard(i) + + for l in range(n_layers): + + for i in range(n_nodes): + for j in range(i): + # factor of 2 due to definition of gate + qml.IsingZZ(2*gammas[l]*A[i,j], wires=[i,j]) + + for i in range(n_nodes): + qml.RX(A[i,i]*betas[l], wires=i) + +###################################################################### +# We can use this ansatz in a circuit. + +n_qubits = 5 +n_layers = 2 + +dev = qml.device("lightning.qubit", wires=n_qubits) + +@qml.qnode(dev) +def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): + """Circuit that uses the permutation equivariant embedding""" + + perm_equivariant_embedding(adjacency_matrix, trainable_betas, trainable_gammas) + return qml.expval(observable) + + +A = create_data_point(n_qubits) +betas = rng.random(n_layers) +gammas = rng.random(n_layers) +observable = qml.PauliX(0) @ qml.PauliX(1) @ qml.PauliX(3) + +qml.draw_mpl(eqc, decimals=2)(A, observable, betas, gammas) +plt.show() + + +###################################################################### +# Validating the equivariance +# --------------------------- +# +# Let's now check if the circuit is really equivariant! +# +# This is the expectation value we get using the original adjacency matrix as an input: +# + +result_A = eqc(A, observable, betas, gammas) +print("Model output for A:", result_A) + + +###################################################################### +# If we permute the adjacency matrix, this is what we get: +# + +perm = [2, 3, 0, 1, 4] +A_perm = permute(A, perm) +result_Aperm = eqc(A_perm, observable, betas, gammas) +print("Model output for permutation of A: ", result_Aperm) + + +###################################################################### +# Why are the two values different? Well, we constructed an *equivariant* ansatz, +# not an *invariant* one! Remember, an *invariant* ansatz means that embedding a permutation of +# the adjacency matrix leads to the same state as an embedding of the original matrix. +# An *equivariant* ansatz embeds the permuted adjacency matrix into a state where the qubits +# are permuted as well. +# +# As a result, the final state before measurement is only the same if we +# permute the qubits in the same manner that we permute the input adjacency matrix. We could insert a +# permutation operator ``qml.Permute(perm)`` to achieve this, or we simply permute the wires +# of the observables! +# + +observable_perm = qml.PauliX(perm[0]) @ qml.PauliX(perm[1]) @ qml.PauliX(perm[3]) + +###################################################################### +# Now everything should work out! +# + +result_Aperm = eqc(A_perm, observable_perm, betas, gammas) +print("Model output for permutation of A, and with permuted observable: ", result_Aperm) + +###################################################################### +# Et voilà! +# +# +# Conclusion +# ---------- +# +# Equivariant graph embeddings can be combined with other equivariant parts of a quantum machine learning pipeline +# (like measurements and the cost function). `Skolik et al. (2022) `_, +# for example, use such a pipeline as part of a reinforcement learning scheme that finds heuristic solutions for the +# traveling salesman problem. Their simulations compare a fully equivariant model to circuits that break +# permutation equivariance and show that it performs better, confirming that if we know +# about structure in our data, we should try to use this knowledge in machine learning. +# +# References +# ---------- +# +# 1. Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko (2022). +# Equivariant quantum circuits for learning on weighted graphs. +# `arXiv:2205.06109 `__ +# +# 2. Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, +# Patrick J. Coles, Frédéric Sauvage, Martín Larocca and Marco Cerezo (2022). +# Theory for Equivariant Quantum Neural Networks. +# `arXiv:2210.08566 `__ +# diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json b/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json new file mode 100644 index 0000000000..1386ffa50e --- /dev/null +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json @@ -0,0 +1,57 @@ +{ + "title": "An equivariant graph embedding", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2023-07-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/equivariant_graph_embedding/thumbnail_tutorial_equivariant_graph_embedding.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_equivariant_graph_embedding.png" + } + ], + "seoDescription": "Find out more about how to embedd graphs into quantum states.", + "doi": "", + "references": [ + { + "id": "Skolik2022", + "type": "article", + "title": "Equivariant quantum circuits for learning on weighted graphs", + "authors": "Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2205.06109" + }, + { + "id": "Nguyen", + "type": "article", + "title": "Theory for Equivariant Quantum Neural Networks.", + "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Fr\u00e9d\u00e9ric Sauvage, Mart\u00edn Larocca and Marco Cerezo", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.08566" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2205.06109" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in b/demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in new file mode 100644 index 0000000000..a9a3103589 --- /dev/null +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in @@ -0,0 +1,4 @@ +matplotlib +networkx +numpy +pennylane diff --git a/demonstrations_v2/tutorial_error_mitigation/demo.py b/demonstrations_v2/tutorial_error_mitigation/demo.py new file mode 100644 index 0000000000..e690d5a7e9 --- /dev/null +++ b/demonstrations_v2/tutorial_error_mitigation/demo.py @@ -0,0 +1,595 @@ +""" +Error mitigation with Mitiq and PennyLane +========================================= + +.. meta:: + :property="og:description": Learn how to mitigate quantum circuits using Mitiq and PennyLane. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/laptop.png + +.. related:: + + tutorial_chemical_reactions Modelling chemical reactions on a quantum computer + tutorial_noisy_circuits Noisy circuits + +*Authors: Tom Bromley (PennyLane) and Andrea Mari (Mitiq) — Posted: 29 November 2021. Last updated: 29 November 2021.* + +Have you ever run a circuit on quantum hardware and not quite got the result you were expecting? +If so, welcome to the world of noisy intermediate-scale quantum (NISQ) devices! These devices must +function in noisy environments and are unable to execute quantum circuits perfectly, resulting in +outputs that can have a significant error. The long-term plan of quantum computing is to develop a +subsequent generation of error-corrected hardware. In the meantime, how can we best utilize our +error-prone NISQ devices for practical tasks? One proposed solution is to adopt an approach called +error *mitigation*, which aims to minimize the effects of noise by executing a family of related +circuits and using the results to estimate an error-free value. + +.. figure:: ../_static/demonstration_assets/error_mitigation/laptop.png + :align: center + :scale: 55% + :alt: Mitiq and PennyLane + :target: javascript:void(0); + +This demo shows how error mitigation can be carried out by combining PennyLane with the +`Mitiq `__ package, a Python-based library providing a range +of error mitigation techniques. Integration with PennyLane is available from the ``0.11`` version +of Mitiq, which can be installed using + +.. code-block:: bash + + pip install "mitiq>=0.11" + +We'll begin the demo by jumping straight into the deep end and seeing how to mitigate a simple noisy +circuit in PennyLane with Mitiq as a backend. After, we'll take a step back and discuss the theory +behind the error mitigation approach we used, known as zero-noise extrapolation. Using this +knowledge, we'll give a more detailed explanation of how error mitigation can be carried out in +PennyLane. The final part of +this demo showcases the application of mitigation to quantum chemistry, allowing us to more +accurately calculate the potential energy surface of molecular hydrogen. + +Mitigating noise in a simple circuit +------------------------------------ + +We first need a noisy device to execute our circuit on. Let's keep things simple +for now by loading the :mod:`default.mixed ` simulator +and artificially adding :class:`PhaseDamping ` noise using a +:class:`NoiseModel `. +""" + +import pennylane as qml + +n_wires = 4 + +# Describe noise model +fcond = qml.noise.wires_in(range(n_wires)) +noise = qml.noise.partial_wires(qml.PhaseDamping, 0.1) +noise_model = qml.NoiseModel({fcond: noise}) + +# Load devices +dev_ideal = qml.device("default.mixed", wires=n_wires) +dev_noisy = qml.add_noise(dev_ideal, noise_model=noise_model) + +############################################################################### +# In the above, we load a noise-free device ``dev_ideal`` and a noisy device ``dev_noisy``, +# which is constructed from the :func:`qml.add_noise ` +# transform. This transform works by intercepting each circuit executed on the device and +# adding the noise to it based on the ``noise_model``. For example, in this case, it will +# add :class:`PhaseDamping ` noise channel after every gate in the +# circuit acting on wires :math:`[0, 1, 2, 3]`. To get a better understanding of noise +# channels like :class:`PhaseDamping ` and using noise models, +# check out the :doc:`tutorial_noisy_circuits` and :doc:`tutorial_how_to_use_noise_models` +# tutorials, respectively. +# +# The next step is to define our circuit. Inspired by the mirror circuits concept introduced by +# Proctor *et al.* [#proctor2020measuring]_ let's fix a circuit that applies a unitary :math:`U` +# followed by its inverse :math:`U^{\dagger},` with :math:`U` given by the +# :class:`SimplifiedTwoDesign ` +# template. We also fix a measurement of the :class:`PauliZ ` observable on our +# first qubit. Importantly, such a circuit performs an identity transformation +# :math:`U^{\dagger} U |\psi\rangle = |\psi\rangle` to any input state :math:`|\psi\rangle` and we +# can show that the expected value of an ideal circuit execution with an input state +# :math:`|0\rangle` is +# +# .. math:: +# +# \langle 0 | U U^{\dagger} Z U^{\dagger} U | 0 \rangle = 1. +# +# Although this circuit seems trivial, it provides an ideal test case for benchmarking noisy +# devices where we expect the output to be less than one due to the detrimental effects of noise. +# Let's check this out in PennyLane code: + +from pennylane import numpy as np + +np.random.seed(1967) + +# Select template to use within circuit and generate parameters +n_layers = 1 +template = qml.SimplifiedTwoDesign +weights_shape = template.shape(n_layers, n_wires) +w1, w2 = [2 * np.pi * np.random.random(s) for s in weights_shape] + + +def circuit(w1, w2): + template(w1, w2, wires=range(n_wires)) + qml.adjoint(template)(w1, w2, wires=range(n_wires)) + return qml.expval(qml.PauliZ(0)) + + +ideal_qnode = qml.QNode(circuit, dev_ideal) +noisy_qnode = qml.QNode(circuit, dev_noisy) +noisy_qnode = qml.transforms.decompose(noisy_qnode, gate_set = ["RY", "CZ"]) + +############################################################################## +# First, we'll visualize the circuit: + +print(qml.draw(ideal_qnode, level="device")(w1, w2)) + +############################################################################## +# As expected, executing the circuit on an ideal noise-free device gives a result of ``1``. + +ideal_qnode(w1, w2) + +############################################################################## +# On the other hand, we obtain a noisy result when running on ``dev_noisy``: + +noisy_qnode(w1, w2) + +############################################################################## +# So, we have set ourselves up with a benchmark circuit and seen that executing on a noisy device +# gives imperfect results. Can the results be improved? Time for error mitigation! We'll first +# show how easy it is to add error mitigation in PennyLane with Mitiq as a backend, before +# explaining what is going on behind the scenes. +# +# .. note:: +# +# To run the code below you will need to have the Qiskit plugin installed. This plugin can be +# installed using: +# +# .. code-block:: bash +# +# pip install pennylane-qiskit +# +# The Qiskit plugin is required to convert our PennyLane circuits to OpenQASM 2.0, which is used +# as an intermediate representation when working with Mitiq. + +from mitiq.zne.scaling import fold_global +from mitiq.zne.inference import RichardsonFactory +from pennylane.transforms import mitigate_with_zne + +extrapolate = RichardsonFactory.extrapolate +scale_factors = [1, 2, 3] + +mitigated_qnode = mitigate_with_zne( + noisy_qnode, scale_factors, fold_global, extrapolate +) +mitigated_qnode(w1, w2) + +############################################################################## +# Amazing! Using PennyLane's :func:`mitigate_with_zne ` +# transform, we can create a new ``mitigated_qnode`` whose result is closer to the ideal noise-free +# value of ``1``. How does this work? +# +# Understanding error mitigation +# ------------------------------ +# +# Error mitigation can be realized through a number of techniques, and the Mitiq +# `documentation `__ is a great resource to start learning +# more. In this demo, we focus upon the zero-noise extrapolation (ZNE) method originally +# introduced by Temme et al. [#temme2017error]_ and Li et al. [#li2017efficient]_. +# +# The ZNE method works by assuming that the amount of noise present when a circuit is run on a +# noisy device is enumerated by a parameter :math:`\gamma.` Suppose we have an input circuit +# that experiences an amount of noise :math:`\gamma = \gamma_{0}` when executed. +# Ideally, we would like to evaluate the result of the circuit in the :math:`\gamma = 0` +# noise-free setting. +# +# To do this, we create a family of equivalent circuits whose ideal noise-free value is the +# same as our input circuit. However, when run on a noisy device, each circuit experiences +# an amount of noise :math:`\gamma = s \gamma_{0}` for some scale factor :math:`s \ge 1.` By +# evaluating the noisy outputs of each circuit, we can extrapolate to :math:`s=0` to estimate +# the result of running a noise-free circuit. +# +# A key element of ZNE is the ability to run equivalent circuits for a range of scale factors +# :math:`s.` When the noise present in a circuit scales with the number of gates, :math:`s` +# can be varied using unitary folding [#giurgica2020digital]_. +# Unitary folding works by noticing that any unitary :math:`V` is equivalent to +# :math:`V V^{\dagger} V.` This type of transform can be applied to individual gates in the +# circuit or to the whole circuit. +# Let's see how +# folding works in code using Mitiq's +# `fold_global `__ +# function, which folds globally by setting :math:`V` to be the whole circuit. +# We begin by making a copy of our above circuit using a +# :class:`QuantumTape `, which provides a low-level approach for circuit +# construction in PennyLane. + +circuit = qml.tape.QuantumTape( + [ + template(w1, w2, wires=range(n_wires)), + qml.adjoint(template(w1, w2, wires=range(n_wires))), + ] +) + +############################################################################## +# Don't worry, in most situations you will not need to work with a PennyLane +# :class:`QuantumTape `! We are just dropping down to this +# representation to gain a greater understanding of the Mitiq integration. Let's see how folding +# works for some typical scale factors: + +scale_factors = [1, 2, 3] +folded_circuits = [fold_global(circuit, scale_factor=s) for s in scale_factors] + +for s, c in zip(scale_factors, folded_circuits): + print(f"Globally-folded circuit with a scale factor of {s}:") + print(qml.drawer.tape_text(c, decimals=2, max_length=80)) + +############################################################################## +# Although these circuits are a bit deep, if you look carefully, you might be able to convince +# yourself that they are all equivalent! In fact, since we have fixed our original circuit to be +# of the form :math:`U U^{\dagger},` we get: +# +# - When the scale factor is :math:`s=1,` the resulting circuit is +# +# .. math:: +# +# V = U^{\dagger} U = \mathbb{I}. +# +# Hence, the :math:`s=1` setting gives us the original unfolded circuit. +# +# - When :math:`s=3,` the resulting circuit is +# +# .. math:: +# +# V V^{\dagger} V = U^{\dagger} U U U^{\dagger} U^{\dagger} U = \mathbb{I}. +# +# In other words, we fold the whole circuit once when :math:`s=3.` Generally, whenever :math:`s` +# is an odd integer, we fold :math:`(s - 1) / 2` times. +# +# - The :math:`s=2` setting is a bit more subtle. Now we apply folding only to the second half of +# the circuit, which is in our case given by :math:`U^{\dagger}.` The resulting partially-folded +# circuit is +# +# .. math:: +# +# (U^{\dagger} U U^{\dagger}) U = \mathbb{I}. +# +# Visit Ref. [#giurgica2020digital]_ to gain a deeper understanding of unitary folding. +# +# If you're still not convinced, we can evaluate the folded circuits on our noise-free device +# ``dev_ideal``. To do this, we'll define an ``executor`` function that adds the +# :class:`PauliZ ` measurement onto the first qubit of each input circuit and +# then runs the circuits on a target device. + + +def executor(circuits, dev=dev_noisy): + # Support both a single circuit and multiple circuit execution + circuits = [circuits] if isinstance(circuits, qml.tape.QuantumTape) else circuits + + circuits_with_meas = [] + + # Loop through circuits and add on measurement + for c in circuits: + circuit_with_meas = qml.tape.QuantumTape( + c.operations, [qml.expval(qml.PauliZ(0))] + ) + circuits_with_meas.append(circuit_with_meas) + + return qml.execute(circuits_with_meas, dev, gradient_fn=None) + + +############################################################################## +# We need to do this step as part of the Mitiq integration with the low-level PennyLane +# :class:`QuantumTape `. You will not have to worry about these details +# when using the main :func:`mitigate_with_zne ` function we +# encountered earlier. +# +# Now, let's execute these circuits: + +executor(folded_circuits, dev=dev_ideal) + +############################################################################## +# By construction, these circuits are equivalent to the original and have the same output value of +# :math:`1.` On the other hand, each circuit has a different depth. If we expect each gate in a +# circuit to contribute an amount of noise when running on NISQ hardware, we should see the +# result of the executed circuit degrade with increased depth. This can be confirmed using the +# ``dev_noisy`` device + +executor(folded_circuits, dev=dev_noisy) + +############################################################################## +# Although this degradation may seem undesirable, it is part of the standard recipe for ZNE error +# mitigation: we have a family of equivalent circuits that experience a varying amount of noise +# when executed on hardware, and we are able to control the amount of noise by varying the folding +# scale factor :math:`s` which determines the circuit depth. The final step is to extrapolate our +# results back to :math:`s=0,` providing us with an estimate of the noise-free result of the +# circuit. +# +# Performing extrapolation is a well-studied numeric method in mathematics, and Mitiq provides +# access to some of the core approaches. Here we use the +# `Richardson extrapolation `__ method with +# the objective of finding a curve :math:`y = f(x)` with some fixed :math:`(x, y)` values given by +# the scale factors and corresponding circuit execution results, respectively. This can be performed +# using: + +# Evaluate noise-scaled expectation values +noisy_expectation_values = executor(folded_circuits, dev=dev_noisy) + +# Initialize extrapolation method +fac = RichardsonFactory(scale_factors) + +# Load data into extrapolation factory +for x, y in zip(scale_factors, noisy_expectation_values): + fac.push({"scale_factor": x}, y) + +# Run extrapolation +zero_noise = fac.reduce() + +print(f"ZNE result: {zero_noise}") + +############################################################################## +# Let's make a plot of the data and fitted extrapolation function. + +_ = fac.plot_fit() + +############################################################################## +# Since we are using the Richardson extrapolation method, the fitted function (dashed line) +# corresponds to a polynomial interpolation of the measured data (blue points). +# +# The zero-noise limit corresponds to the value of the extrapolation function evaluated at `x=0`. +# +# Error mitigation in PennyLane +# ----------------------------- +# +# Now that we understand the ZNE method for error mitigation, we can provide a few more details on +# how it can be performed using PennyLane. As we have seen, the +# :func:`mitigate_with_zne ` function provides the main +# entry point. This function is an example of a :doc:`circuit transform,
` and +# it can be applied to pre-constructed QNodes as well as being used as a decorator when constructing +# new QNodes. For example, suppose we have a ``qnode`` already defined. A mitigated QNode can be +# created using: +# +# .. code-block:: python +# +# mitigated_qnode = mitigate_with_zne(scale_factors, folding, extrapolate)(qnode) +# +# When using ``mitigate_with_zne``, we must specify the target scale factors as well as provide +# functions for folding and extrapolation. Due to PennyLane's integration with Mitiq, it is possible +# to use the folding functions provided in the +# `mitiq.zne.scaling.folding `__ +# module. For extrapolation, one can use the ``extrapolate`` method of the factories in the +# `mitiq.zne.inference `__ +# module. +# +# We now provide an example of how ``mitigate_with_zne`` can be used when constructing a QNode: + +from mitiq.zne.scaling import fold_gates_at_random as folding + +extrapolate = RichardsonFactory.extrapolate + +mitigated_qnode = mitigate_with_zne( + noisy_qnode, scale_factors, folding, extrapolate, reps_per_factor=100 +) + +mitigated_qnode(w1, w2) + +############################################################################## +# In the above, we can easily add in error mitigation using the ``@mitigate_with_zne`` decorator. To +# keep things interesting, we've swapped out our folding function to instead perform folding on +# randomly-selected gates. Whenever the folding function is stochastic, there will not be a unique +# folded circuit corresponding to a given scale factor. For example, the following three distinct +# circuits are all folded with a scale factor of :math:`s=1.1:` + +for _ in range(3): + print( + qml.drawer.tape_text( + folding(circuit, scale_factor=1.1), decimals=2, max_length=80 + ) + ) + +############################################################################## +# To accommodate for this randomness, we can perform multiple repetitions of random folding for a +# fixed :math:`s` and average over the execution results to generate the value :math:`f(s)` used +# for extrapolation. As shown above, the number of repetitions is controlled by setting the optional +# ``reps_per_factor`` argument. +# +# We conclude this section by highlighting the possibility of working directly with the core +# functionality available in Mitiq. For example, the +# `execute_with_zne `__ +# function is one of the central components of ZNE support in Mitiq and is compatible with circuits +# constructed using a PennyLane :class:`QuantumTape `. Working directly +# with Mitiq can be preferable when more flexibility is required in specifying the error mitigation +# protocol. For example, the code below shows how an adaptive approach can be used to determine a +# sequence of scale factors for extrapolation using Mitiq's +# `AdaExpFactory `__. + +from mitiq.zne import execute_with_zne +from mitiq.zne.inference import AdaExpFactory + +factory = AdaExpFactory(steps=20) + +execute_with_zne(circuit, executor, factory=factory, scale_noise=fold_global) + +############################################################################## +# Recall that ``circuit`` is a PennyLane :class:`QuantumTape ` and that +# it should not include measurements. We also use the ``executor`` function defined earlier that +# adds on the target measurement and executes on a noisy device. +# +# Mitigating noisy circuits in quantum chemistry +# ---------------------------------------------- +# +# We're now ready to apply our knowledge to a more practical problem in quantum chemistry: +# calculating the potential energy surface of molecular hydrogen. This is achieved by finding the +# ground state energy of :math:`H_{2}` as we increase the bond length between the hydrogen atoms. As +# shown in :doc:`this ` tutorial, one approach to finding the ground +# state energy is to calculate the corresponding qubit Hamiltonian and to fix an ansatz variational +# quantum circuit that returns its expectation value. We can then vary the parameters of the +# circuit to minimize the energy. +# +# To find the potential energy surface of :math:`H_{2},` we must choose a range of interatomic +# distances and calculate the qubit Hamiltonian corresponding to each distance. We then optimize the +# variational circuit with a new set of parameters for each Hamiltonian and plot the resulting +# energies for each distance. In this demo, we compare the potential energy surface reconstructed +# when the optimized variational circuits are run on ideal, noisy, and noise-mitigated devices. +# +# Instead of modifying the :mod:`default.mixed ` device to add +# simple noise as we do above, let's choose a noise model that is a little closer to physical +# hardware. Suppose we want to simulate the ``ibmq_lima`` hardware device available on IBMQ. We +# can load a noise model that represents this device using: + +from qiskit_ibm_runtime.fake_provider import FakeLimaV2 +from qiskit_aer.noise import NoiseModel + +backend = FakeLimaV2() +noise_model = NoiseModel.from_backend(backend) + +############################################################################## +# We can then set up our ideal device and the noisy simulator of ``ibmq_lima``. + +n_wires = 4 + +dev_ideal = qml.device("default.qubit", wires=n_wires) +dev_noisy = qml.device( + "qiskit.aer", + wires=n_wires, + noise_model=noise_model, + optimization_level=0, +) + +############################################################################## +# Note the use of the ``optimization_level=0`` argument when loading the noisy device. This prevents +# the ``qiskit.aer`` transpiler from performing a pre-execution circuit optimization. +# +# To simplify this demo, we will load pre-trained parameters for our variational circuit. + +params = np.load("error_mitigation/params.npy") + +############################################################################## +# These parameters can be downloaded by clicking +# :download:`here <../_static/demonstration_assets/error_mitigation/params.npy>`. We are now ready to set up the +# variational circuit and run on the ideal and noisy devices. + +from pennylane import qchem + +# Describe quantum chemistry problem +symbols = ["H", "H"] +distances = np.arange(0.5, 3.0, 0.25) + +ideal_energies = [] +noisy_energies = [] + +for r, phi in zip(distances, params): + # Assume atoms lie on the Z axis + coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r]]) + + # Construct Molecule object + molecule = qchem.Molecule(symbols, coordinates) + + # Build qubit Hamiltonian + H, _ = qchem.molecular_hamiltonian(molecule) + + # Define ansatz circuit + def qchem_circuit(phi): + qml.PauliX(wires=0) + qml.PauliX(wires=1) + qml.DoubleExcitation(phi, wires=range(n_wires)) + return qml.expval(H) + + ideal_energy = qml.QNode(qchem_circuit, dev_ideal) + noisy_energy = qml.QNode(qchem_circuit, dev_noisy) + noisy_energy = qml.transforms.decompose(noisy_energy, gate_set=["RX", "RY", "RZ", "CNOT"]) + + ideal_energies.append(ideal_energy(phi)) + noisy_energies.append(noisy_energy(phi)) + +############################################################################## +# An error-mitigated version of the potential energy surface can also be calculated using the +# following: + +mitig_energies = [] + +for r, phi in zip(distances, params): + # Assume atoms lie on the Z axis + coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r]]) + + # Construct Molecule object + molecule = qchem.Molecule(symbols, coordinates) + + # Build qubit Hamiltonian + H, _ = qchem.molecular_hamiltonian(molecule) + + # Define ansatz circuit + ops = [ + qml.PauliX(0), + qml.PauliX(1), + qml.DoubleExcitation(phi, wires=range(n_wires)), + ] + circuit = qml.tape.QuantumTape(ops) + [circuit], _ = qml.transforms.decompose(circuit, gate_set=["RX", "RY", "RZ", "CNOT"]) + + # Define custom executor that expands Hamiltonian measurement + # into a linear combination of tensor products of Pauli + # operators. + def executor(circuit): + + # Add Hamiltonian measurement to circuit + circuit_with_meas = qml.tape.QuantumTape(circuit.operations, [qml.expval(H)], shots=10000) + + # Expand Hamiltonian measurement into tensor product of + # of Pauli operators. We get a list of circuits to execute + # and a postprocessing function to combine the results into + # a single number. + circuits, postproc = qml.transforms.split_non_commuting( + circuit_with_meas, grouping_strategy=None + ) + circuits_executed = qml.execute(circuits, dev_noisy, gradient_fn=None) + return postproc(circuits_executed) + + mitig_energy = execute_with_zne(circuit, executor, scale_noise=fold_global) + mitig_energies.append(mitig_energy) + +############################################################################## +# Finally, we can plot the three surfaces and compare: + +import matplotlib.pyplot as plt + +plt.plot(ideal_energies, label="ideal") +plt.plot(noisy_energies, label="noisy") +plt.plot(mitig_energies, label="mitigated") +plt.xlabel("Bond length (Bohr)") +plt.ylabel("Total energy (Hartree)") +plt.legend() +plt.show() + +############################################################################## +# +# Great, error mitigation has allowed us to more closely approximate the ideal noise-free curve! +# +# We have seen in this demo how easy error mitigation can be in PennyLane when using Mitiq as a +# backend. As well as understanding the basics of the ZNE method, we have also seen how mitigation +# can be used to uplift the performance of noisy devices for practical tasks like quantum chemistry. +# On the other hand, we've only just started to scratch the surface of what can be done with error +# mitigation. We can explore applying the ZNE method to other use cases, or even try out other +# mitigation methods like +# `probabilistic error cancellation `__. +# Let us know where your adventures take you, and the Mitiq and PennyLane teams will keep working to +# help make error mitigation as easy as possible! +# +# References +# ---------- +# +# .. [#proctor2020measuring] T. Proctor, K. Rudinger, K. Young, E. Nielsen, R. Blume-Kohout +# `"Measuring the Capabilities of Quantum Computers" `_, +# arXiv:2008.11294 (2020). +# +# .. [#temme2017error] K. Temme, S. Bravyi, J. M. Gambetta +# `"Error Mitigation for Short-Depth Quantum Circuits" `_, +# Phys. Rev. Lett. 119, 180509 (2017). +# +# .. [#li2017efficient] Y. Li, S. C. Benjamin +# `"Efficient Variational Quantum Simulator Incorporating Active Error Minimization" `_, +# Phys. Rev. X 7, 021050 (2017). +# +# .. [#giurgica2020digital] T. Giurgica-Tiron, Y. Hindy, R. LaRose, A. Mari, W. J. Zeng +# `"Digital zero noise extrapolation for quantum error mitigation" `_, +# IEEE International Conference on Quantum Computing and Engineering (2020). +# +# diff --git a/demonstrations_v2/tutorial_error_mitigation/error_mitigation/params.npy b/demonstrations_v2/tutorial_error_mitigation/error_mitigation/params.npy new file mode 100644 index 0000000000000000000000000000000000000000..381da137dafc047e8c6bba1a90cc3dc343098202 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL411^uO)*CrtL{dv=vH-EWoU$W18OTft+_CoSv_h%R1v*-AG-lV+rk-evVw^ri6r}hBY CM?vWT literal 0 HcmV?d00001 diff --git a/demonstrations_v2/tutorial_error_mitigation/metadata.json b/demonstrations_v2/tutorial_error_mitigation/metadata.json new file mode 100644 index 0000000000..953a5011c9 --- /dev/null +++ b/demonstrations_v2/tutorial_error_mitigation/metadata.json @@ -0,0 +1,78 @@ +{ + "title": "Error mitigation with Mitiq and PennyLane", + "authors": [ + { + "username": "trbromley" + }, + { + "username": "amari" + } + ], + "dateOfPublication": "2021-11-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_error_mitigation_Mitiq_PL.png" + } + ], + "seoDescription": "Learn how to mitigate quantum circuits using Mitiq and PennyLane.", + "doi": "", + "references": [ + { + "id": "proctor2020measuring", + "type": "article", + "title": "Measuring the Capabilities of Quantum Computers", + "authors": "T. Proctor, K. Rudinger, K. Young, E. Nielsen, R. Blume-Kohout", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2008.11294" + }, + { + "id": "temme2017error", + "type": "article", + "title": "Error Mitigation for Short-Depth Quantum Circuits", + "authors": "K. Temme, S. Bravyi, J. M. Gambetta", + "year": "2017", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.180509" + }, + { + "id": "li2017efficient", + "type": "article", + "title": "Efficient Variational Quantum Simulator Incorporating Active Error Minimization", + "authors": "Y. Li, S. C. Benjamin", + "year": "2017", + "journal": "Phys. Rev. X", + "url": "https://journals.aps.org/prx/abstract/10.1103/PhysRevX.7.021050" + }, + { + "id": "giurgica2020digital", + "type": "article", + "title": "Digital zero noise extrapolation for quantum error mitigation", + "authors": "T. Giurgica-Tiron, Y. Hindy, R. LaRose, A. Mari, W. J. Zeng", + "year": "2020", + "journal": "IEEE International Conference on Quantum Computing and Engineering", + "url": "https://ieeexplore.ieee.org/document/9259940" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_chemical_reactions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_error_mitigation/requirements.in b/demonstrations_v2/tutorial_error_mitigation/requirements.in new file mode 100644 index 0000000000..9b8e92a025 --- /dev/null +++ b/demonstrations_v2/tutorial_error_mitigation/requirements.in @@ -0,0 +1,6 @@ +matplotlib +mitiq +pennylane +qiskit +qiskit_aer +qiskit_ibm_runtime diff --git a/demonstrations_v2/tutorial_error_prop/demo.py b/demonstrations_v2/tutorial_error_prop/demo.py new file mode 100644 index 0000000000..fa2e3efd6a --- /dev/null +++ b/demonstrations_v2/tutorial_error_prop/demo.py @@ -0,0 +1,245 @@ +r""" +How to track algorithmic error using PennyLane +============================================== + +In order to accurately determine the resources required to run a given quantum workflow, one must carefully track +and propagate the sources of error within the many algorithms that make up the workflow. Furthermore, there are +a variety of different errors to keep track of: + +- **Input / Encoding Error:** The error from embedding classical data into the quantum circuit (e.g. initial state prep). +- **Algorithm-specific Error:** The error caused by the structure of the algorithm itself (e.g. QPE with limited readout qubits). +- **Approximate Decomposition Error:** The error caused by decomposing gates approximately (e.g. Clifford + T decomposition). +- **Hardware Noise Error:** The error introduced by noisy quantum channels (e.g. :class:`~.pennylane.BitFlip`, :class:`~.pennylane.PhaseFlip`). +- **Measurement Uncertainty:** The error from the probabilistic nature of quantum measurement (e.g. multiple samples required for state tomography). + +We refer to the first three of these as "Algorithmic Error". Typically, these types of error computations are performed by +hand due to the variety of error metrics and the specific handling of such errors for each subroutine. In +this demo, we present the latest tools in PennyLane which **automatically** track algorithmic error. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_error-prop_2024-05-01.png + :align: center + :width: 50% + :target: javascript:void(0) + + +Quantify Error using the Spectral Norm +-------------------------------------- + +Before we can track the error in our quantum workflow, we need to quantify it. A common method for quantifying the error +between operators is to compute the "distance" between them; specifically, the spectral norm of the difference between +the operators. We can use the new :class:`~.pennylane.resource.error.SpectralNormError` class to compute and represent this error. +Consider for example, that instead of applying :code:`qml.RX(1.234)` we incur some *rounding* error in the rotation angle; +how much error would the resulting operators have? + +We can compute this easily with PennyLane: +""" + +import pennylane as qml +from pennylane.resource import SpectralNormError + +exact_op = qml.RX(1.234, wires=0) + +thetas = [1.23, 1.2, 1.0] +ops = [qml.RX(theta, wires=0) for theta in thetas] + +for approx_op, theta in zip(ops, thetas): + error = SpectralNormError.get_error(exact_op, approx_op) + print(f"Spectral Norm error (theta = {theta:.2f}): {error:.5f}") + + +############################################################################### +# The error in the operator increases as we round the rotation angle to fewer decimal places as expected. +# Now that we can quantify the error, let's track the error for one of the most common workflows in quantum +# computing: time evolving a quantum state under a given Hamiltonian! +# +# Tracking Errors in Hamiltonian Simulation +# ----------------------------------------- +# Time evolving a quantum state under a Hamiltonian requires generating the unitary :math:`\hat{U} = \exp(iHt).` +# In general it is difficult to prepare this operator exactly, so it is instead prepared approximately. +# The most common method to accomplish this is the Suzuki-Trotter product formula [#TrotterError]_. This +# subroutine introduces **algorithm-specific error** as it produces an approximation to the matrix exponential +# operator. +# +# Let's explicitly compute the error from this algorithm for a simple Hamiltonian: + +time = 0.1 +Hamiltonian = qml.X(0) + qml.Y(0) + +exact_op = qml.exp(Hamiltonian, 1j * time) # U = e^iHt ~ TrotterProduct(..., order=2) +approx_op = qml.TrotterProduct( # eg: e^iHt ~ e^iXt/2 * e^iYt * e^iXt/2 + Hamiltonian, + time, + order=2, +) + +error = SpectralNormError.get_error(exact_op, approx_op) # Expensive to compute +print(f"Error from Suzuki-Trotter algorithm: {error:.5f}") + + +############################################################################### +# In general, exactly computing the spectral norm is computationally expensive for larger systems as it requires +# diagonalizing the operators. For this reason, we typically use upper bounds on the spectral norm error +# in the product formulas. +# +# We provide two common methods for bounding the error from literature [#TrotterError]_. +# They can be accessed by using :code:`op.error()` and specifying the :code:`method` keyword argument: + +op = qml.TrotterProduct(Hamiltonian, time, order=2) + +one_norm_error_bound = op.error(method="one-norm-bound") +commutator_error_bound = op.error(method="commutator-bound") + +print("one-norm bound: ", one_norm_error_bound) +print("commutator bound: ", commutator_error_bound) + + +############################################################################### +# Custom Error Operations +# ----------------------- +# With the new :class:`~.pennylane.resource.error.SpectralNormError` and :class:`~.pennylane.resource.ErrorOperation` +# classes it's easy for anyone to define their own custom operations with error. All we need to do is to specify +# how the error is computed. Once the error function is defined, PennyLane tracks and propagates the error +# through the circuit. This makes it easy for us to add and combine multiple error operations together in a +# quantum circuit. In the following example we define a custom operation with error to act as an approximate +# decomposition. +# +# Suppose, for example, that our quantum +# hardware does not natively support rotation gates (:class:`~.pennylane.RX`, +# :class:`~.pennylane.RY`, :class:`~.pennylane.RZ`). How could we decompose the :class:`~.pennylane.RX` gate? +# +# Notice that :math:`\hat{R_{x}}(\frac{\pi}{4}) = \hat{H} \cdot \hat{T} \cdot \hat{H}` +# up to a global phase :math:`e^{i \frac{\pi}{8}}.` + +from pennylane import numpy as np + +op1 = qml.RX(np.pi / 4, 0) +op2 = qml.GlobalPhase(np.pi / 8) @ qml.Hadamard(0) @ qml.T(0) @ qml.Hadamard(0) + +np.allclose(qml.matrix(op1), qml.matrix(op2)) + + +############################################################################### +# We can approximate the :class:`~.pennylane.RX` gate by *rounding* the rotation angle to the lowest multiple +# of :math:`\frac{\pi}{4},` then using multiple iterations of the sequence above. +# The **approximation error** we incur from this decomposition is given by the expression: +# +# .. math:: +# +# \epsilon = \sqrt{2 - 2 \cdot sin(\theta)}, +# +# where :math:`\theta = \frac{\pi \ - \ \Delta_{\phi}}{2}` and :math:`\Delta_{\phi}` is the +# absolute difference between the true rotation angle and the next lowest multiple of :math:`\frac{\pi}{4}.` +# +# We can take this approximate decomposition and turn it into a PennyLane operation simply by inheriting +# from the :class:`~.pennylane.resource.ErrorOperation` class, and defining the error method: + +from pennylane.resource.error import ErrorOperation + + +class Approximate_RX(ErrorOperation): + + def __init__(self, phi, wires): + """Approximate decomposition for RX gate""" + return super().__init__(phi, wires) + + @staticmethod + def compute_decomposition(phi, wires): + """Defining the gate decomposition""" + num_iterations = int(phi // (np.pi / 4)) # how many rotations of pi/4 to apply + global_phase = num_iterations * np.pi / 8 + + decomposition = [qml.GlobalPhase(global_phase)] + for _ in range(num_iterations): + decomposition += [qml.Hadamard(wires), qml.T(wires), qml.Hadamard(wires)] + + return decomposition + + def error(self): + """The error in our approximation""" + phi = self.parameters[0] # The error depends on the true rotation angle + delta_phi = phi % (np.pi / 4) + + theta = (np.pi - delta_phi) / 2 + error = np.sqrt(2 - 2 * np.sin(theta)) + return SpectralNormError(error) + + +############################################################################### +# We can verify that evaluating the expression for the approximation error gives us the same result as +# explicitly computing the error. Notice that we can access the error of our new operator in the same way +# we did for Hamiltonian simulation, using :code:`op.error()`. + +phi = 1.23 +true_op = qml.RX(phi, wires=0) +approx_op = Approximate_RX(phi, wires=0) + +error_from_theory = approx_op.error() +explicit_comp = SpectralNormError.get_error(true_op, approx_op) + +print("Explicit computation: ", explicit_comp) +print("Error from function: ", error_from_theory.error) + +############################################################################### +# Bringing it All Together +# ------------------------ +# Tracking the error for each component individually is great, but we ultimately want to put these +# pieces together in a quantum circuit. PennyLane now automatically tracks and propagates these errors through +# the circuit. This means we can write our circuits as usual and get all the benefits of error tracking for free. + +dev = qml.device("default.qubit") + + +@qml.qnode(dev) +def circ(H, t, phi1, phi2): + qml.Hadamard(0) + qml.Hadamard(1) + + # Approx decomposition + Approximate_RX(phi1, 0) + Approximate_RX(phi2, 1) + + qml.CNOT([0, 1]) + + # Approx time evolution: + qml.TrotterProduct(H, t, order=2) + + # Measurement: + return qml.state() + + +############################################################################### +# Along with executing the circuit, we can also compute the error in the circuit through :func:`~.pennylane.specs`: + +phi1, phi2 = (0.12, 3.45) +print("State:") +print(circ(Hamiltonian, time, phi1, phi2), "\n") + +errors_dict = qml.specs(circ)(Hamiltonian, time, phi1, phi2)["errors"] +error = errors_dict["SpectralNormError"] +print("Error:") +print(error) + + +############################################################################### +# Conclusion +# ---------- +# In this demo, we showcased the new :class:`~.pennylane.resource.error.SpectralNormError` and +# :class:`~.pennylane.resource.ErrorOperation` classes in PennyLane. We also highlighted the new functionality +# in :class:`~.pennylane.TrotterProduct` class to compute error bounds in product formulas. +# We explained how to construct a custom error operation and used it in a simple workflow to +# propagate the error through the circuit. Accurately tracking the error in our workflows allows us to +# make more resource-efficient algorithms, ultimately unlocking new applications. We hope that you can make +# use of these tools in your cutting-edge research workflows. +# +# +# References +# ---------- +# +# .. [#TrotterError] +# +# Andrew M. Childs, Yuan Su, Minh C. Tran, Nathan Wiebe, and Shuchen Zhu, +# "Theory of Trotter Error with Commutator Scaling". +# `Phys. Rev. X 11, 011020 (2021) +# `__ +# +# diff --git a/demonstrations_v2/tutorial_error_prop/metadata.json b/demonstrations_v2/tutorial_error_prop/metadata.json new file mode 100644 index 0000000000..d017eba970 --- /dev/null +++ b/demonstrations_v2/tutorial_error_prop/metadata.json @@ -0,0 +1,48 @@ +{ + "title": "How to track algorithmic error using PennyLane", + "authors": [ + { + "username": "Jay" + } + ], + "dateOfPublication": "2024-05-03T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/error_prop/thumbnail_error-prop_2024-05-01.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_error-prop_2024-05-01.png" + } + ], + "seoDescription": "Learn how to propagate error through quantum circuits.", + "doi": "", + "references": [ + { + "id": "TrotterError", + "type": "article", + "title": "Theory of Trotter Error with Commutator Scaling", + "authors": "Andrew M. Childs, Yuan Su, Minh C. Tran, Nathan Wiebe, and Shuchen Zhu", + "year": "2021", + "journal": "Phys. Rev. X", + "url": "https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.011020" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_lcu_blockencoding", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_error_prop/requirements.in b/demonstrations_v2/tutorial_error_prop/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_error_prop/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py b/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py new file mode 100644 index 0000000000..22711d54dc --- /dev/null +++ b/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py @@ -0,0 +1,858 @@ +r""" +Quantum models as Fourier series +================================ + +.. meta:: + :property="og:description": The class of functions a quantum model can learn is characterized by the structure of its corresponding Fourier series. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/scheme.png + +.. related:: + + tutorial_data_reuploading_classifier Data-reuploading classifier + +*Authors: Maria Schuld and Johannes Jakob Meyer — Posted: 24 August 2020. Last updated: 15 January 2021.* + +""" + +###################################################################### +# This demonstration is based on the paper *The effect of data encoding on +# the expressive power of variational quantum machine learning models* by +# `Schuld, Sweke, and Meyer +# (2020) `__ [#schuld2020]_. +# +# .. figure:: ../_static/demonstration_assets/expressivity_fourier_series/scheme_thumb.png +# :width: 50% +# :align: center +# + + +###################################################################### +# The paper links common quantum machine learning models designed for +# near-term quantum computers to Fourier series (and, in more general, to +# Fourier-type sums). With this link, the class of functions a quantum +# model can learn (i.e., its "expressivity") can be characterized by the +# model's control of the Fourier series' frequencies and coefficients. +# + + +###################################################################### +# Background +# ---------- +# + + +###################################################################### +# Ref. [#schuld2020]_ considers quantum machine +# learning models of the form +# +# .. math:: f_{\boldsymbol \theta}(x) = \langle 0| U^{\dagger}(x,\boldsymbol \theta) M U(x, \boldsymbol \theta) | 0 \rangle +# +# where :math:`M` is a measurement observable and +# :math:`U(x, \boldsymbol \theta)` is a variational quantum circuit that +# encodes a data input :math:`x` and depends on a +# set of parameters :math:`\boldsymbol \theta.` Here we will restrict ourselves +# to one-dimensional data inputs, but the paper motivates that higher-dimensional +# features simply generalize to multi-dimensional Fourier series. +# +# The circuit itself repeats :math:`L` layers, each consisting of a data-encoding circuit +# block :math:`S(x)` and a trainable circuit block +# :math:`W(\boldsymbol \theta)` that is controlled by the parameters +# :math:`\boldsymbol \theta.` The data encoding block consists of gates of +# the form :math:`\mathcal{G}(x) = e^{-ix H},` where :math:`H` is a +# Hamiltonian. A prominent example of such gates are Pauli rotations. +# + + +###################################################################### +# The paper shows how such a quantum model can be written as a +# Fourier-type sum of the form +# +# .. math:: f_{ \boldsymbol \theta}(x) = \sum_{\omega \in \Omega} c_{\omega}( \boldsymbol \theta) \; e^{i \omega x}. +# +# As illustrated in the picture below (which is Figure 1 from the paper), +# the "encoding Hamiltonians" in :math:`S(x)` determine the set +# :math:`\Omega` of available "frequencies", and the remainder of the +# circuit, including the trainable parameters, determines the coefficients +# :math:`c_{\omega}.` +# + + +###################################################################### +# .. figure:: ../_static/demonstration_assets/expressivity_fourier_series/scheme.png +# :width: 50% +# :align: center +# +# | +# + + +###################################################################### +# +# The paper demonstrates many of its findings for circuits in which +# :math:`\mathcal{G}(x)` is a single-qubit Pauli rotation gate. For +# example, it shows that :math:`r` repetitions of a Pauli rotation-encoding +# gate in "sequence" (on the same qubit, but with multiple layers :math:`r=L`) or +# in "parallel" (on :math:`r` different qubits, with :math:`L=1`) creates a quantum +# model that can be expressed as a *Fourier series* of the form +# +# .. math:: f_{ \boldsymbol \theta}(x) = \sum_{n \in \Omega} c_{n}(\boldsymbol \theta) e^{i n x}, +# +# where :math:`\Omega = \{ -r, \dots, -1, 0, 1, \dots, r\}` is a spectrum +# of consecutive integer-valued frequencies up to degree :math:`r.` +# +# As a result, we expect quantum models that encode an input :math:`x` by +# :math:`r` Pauli rotations to only be able to fit Fourier series of at +# most degree :math:`r.` +# + + +###################################################################### +# Goal of this demonstration +# -------------------------- +# + + +###################################################################### +# The experiments below investigate this "Fourier-series"-like nature of +# quantum models by showing how to reproduce the simulations underlying +# Figures 3, 4 and 5 in Section II of the paper: +# +# - **Figures 3 and 4** are function-fitting experiments, where quantum +# models with different encoding strategies have the task to fit +# Fourier series up to a certain degree. As in the paper, we will use +# examples of qubit-based quantum circuits where a single data feature +# is encoded via Pauli rotations. +# +# - **Figure 5** plots the Fourier coefficients of randomly sampled +# instances from a family of quantum models which is defined by some +# parametrized ansatz. +# +# The code is presented so you can easily modify it in order to play +# around with other settings and models. The settings used in the paper +# are given in the various subsections. +# + + +###################################################################### +# First of all, let's make some imports and define a standard loss +# function for the training. +# + +import matplotlib.pyplot as plt +import pennylane as qml +from pennylane import numpy as np + +np.random.seed(42) + + +def square_loss(targets, predictions): + loss = 0 + for t, p in zip(targets, predictions): + loss += (t - p) ** 2 + loss = loss / len(targets) + return 0.5 * loss + + +###################################################################### +# Part I: Fitting Fourier series with serial Pauli-rotation encoding +# ------------------------------------------------------------------ +# + + +###################################################################### +# First we will reproduce Figures 3 and 4 from the paper. These +# show how quantum models that use Pauli rotations as data-encoding +# gates can only fit Fourier series up to a certain degree. The +# degree corresponds to the number of times that the Pauli gate gets +# repeated in the quantum model. +# +# Let us consider circuits where the encoding gate gets repeated +# sequentially (as in Figure 2a of the paper). For simplicity we will only +# look at single-qubit circuits: +# +# .. figure:: ../_static/demonstration_assets/expressivity_fourier_series/single_qubit_model.png +# :width: 50% +# :align: center +# + + +###################################################################### +# Define a target function +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# We first define a (classical) target function which will be used as a +# "ground truth" that the quantum model has to fit. The target function is +# constructed as a Fourier series of a specific degree. +# +# We also allow for a rescaling of the data by a hyperparameter ``scaling``, +# which we will do in the quantum model as well. As shown in [#schuld2020]_, for the quantum model to +# learn the classical model in the experiment below, +# the scaling of the quantum model and the target function have to match, +# which is an important observation for +# the design of quantum machine learning models. +# + + +degree = 1 # degree of the target function +scaling = 1 # scaling of the data +coeffs = [0.15 + 0.15j] * degree # coefficients of non-zero frequencies +coeff0 = 0.1 # coefficient of zero frequency + + +def target_function(x): + """Generate a truncated Fourier series, where the data gets re-scaled.""" + res = coeff0 + for idx, coeff in enumerate(coeffs): + exponent = np.complex128(scaling * (idx + 1) * x * 1j) + conj_coeff = np.conjugate(coeff) + res += coeff * np.exp(exponent) + conj_coeff * np.exp(-exponent) + return np.real(res) + + +###################################################################### +# Let's have a look at it. +# + +x = np.linspace(-6, 6, 70, requires_grad=False) +target_y = np.array([target_function(x_) for x_ in x], requires_grad=False) + +plt.plot(x, target_y, c="black") +plt.scatter(x, target_y, facecolor="white", edgecolor="black") +plt.ylim(-1, 1) +plt.show() + + +###################################################################### +# .. note:: +# +# To reproduce the figures in the paper, you can use the following +# settings in the cells above: +# +# - For the settings +# +# :: +# +# degree = 1 +# coeffs = (0.15 + 0.15j) * degree +# coeff0 = 0.1 +# +# this function is the ground truth +# :math:`g(x) = \sum_{n=-1}^1 c_{n} e^{-nix}` from Figure 3 in the +# paper. +# +# - To get the ground truth :math:`g'(x) = \sum_{n=-2}^2 c_{n} e^{-nix}` +# with :math:`c_0=0.1,` :math:`c_1 = c_2 = 0.15 - 0.15i` from Figure 3, +# you need to increase the degree to two: +# +# :: +# +# degree = 2 +# +# - The ground truth from Figure 4 can be reproduced by changing the +# settings to: +# +# :: +# +# degree = 5 +# coeffs = (0.05 + 0.05j) * degree +# coeff0 = 0.0 +# + + +###################################################################### +# Define the serial quantum model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# We now define the quantum model itself. +# + +scaling = 1 + +dev = qml.device("default.qubit", wires=1) + + +def S(x): + """Data-encoding circuit block.""" + qml.RX(scaling * x, wires=0) + + +def W(theta): + """Trainable circuit block.""" + qml.Rot(theta[0], theta[1], theta[2], wires=0) + + +@qml.qnode(dev) +def serial_quantum_model(weights, x): + + for theta in weights[:-1]: + W(theta) + S(x) + + # (L+1)'th unitary + W(weights[-1]) + + return qml.expval(qml.PauliZ(wires=0)) + + +###################################################################### +# You can run the following cell multiple times, each time sampling +# different weights, and therefore different quantum models. +# + +r = 1 # number of times the encoding gets repeated (here equal to the number of layers) +weights = ( + 2 * np.pi * np.random.random(size=(r + 1, 3), requires_grad=True) +) # some random initial weights + +x = np.linspace(-6, 6, 70, requires_grad=False) +random_quantum_model_y = [serial_quantum_model(weights, x_) for x_ in x] + +plt.plot(x, random_quantum_model_y, c="blue") +plt.ylim(-1, 1) +plt.show() + + +###################################################################### +# +# No matter what weights are picked, the single qubit model for `L=1` will always be a sine function +# of a fixed frequency. The weights merely influence the amplitude, y-shift, and phase of the sine. +# +# This observation is formally derived in Section II.A of the paper. +# + + +###################################################################### +# .. note:: +# +# You can increase the number of layers. Figure 4 from the paper, for +# example, uses the settings ``L=1``, ``L=3`` and ``L=5``. +# + + +###################################################################### +# Finally, let's look at the circuit we just created: +# + +print(qml.draw(serial_quantum_model)(weights, x[-1])) + + +###################################################################### +# Fit the model to the target +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# The next step is to optimize the weights in order to fit the ground +# truth. +# + + +def cost(weights, x, y): + predictions = [serial_quantum_model(weights, x_) for x_ in x] + return square_loss(y, predictions) + + +max_steps = 50 +opt = qml.AdamOptimizer(0.3) +batch_size = 25 +cst = [cost(weights, x, target_y)] # initial cost + +for step in range(max_steps): + + # Select batch of data + batch_index = np.random.randint(0, len(x), (batch_size,)) + x_batch = x[batch_index] + y_batch = target_y[batch_index] + + # Update the weights by one optimizer step + weights, _, _ = opt.step(cost, weights, x_batch, y_batch) + + # Save, and possibly print, the current cost + c = cost(weights, x, target_y) + cst.append(c) + if (step + 1) % 10 == 0: + print("Cost at step {0:3}: {1}".format(step + 1, c)) + + +###################################################################### +# To continue training, you may just run the above cell again. Once you +# are happy, you can use the trained model to predict function values, and +# compare them with the ground truth. +# + +predictions = [serial_quantum_model(weights, x_) for x_ in x] + +plt.plot(x, target_y, c="black") +plt.scatter(x, target_y, facecolor="white", edgecolor="black") +plt.plot(x, predictions, c="blue") +plt.ylim(-1, 1) +plt.show() + + +###################################################################### +# Let's also have a look at the cost during training. +# + +plt.plot(range(len(cst)), cst) +plt.ylabel("Cost") +plt.xlabel("Step") +plt.ylim(0, 0.23) +plt.show() + + +###################################################################### +# With the initial settings and enough training steps, the quantum model +# learns to fit the ground truth perfectly. This is expected, since +# the number of Pauli-rotation-encoding gates and the degree of the +# ground truth Fourier series are both one. +# +# If the ground truth's degree is larger than the number of layers in the +# quantum model, the fit will look much less accurate. And finally, we +# also need to have the correct scaling of the data: if one of the models +# changes the ``scaling`` parameter (which effectively scales the +# frequencies), fitting does not work even with enough encoding +# repetitions. +# + + +###################################################################### +# .. note:: +# +# You will find that the training takes much longer, and needs a lot more steps to converge for +# larger L. Some initial weights may not even converge to a good solution at all; the training +# seems to get stuck in a minimum. +# +# It is an open research question whether for asymptotically large L, the single qubit +# model can fit *any* function by constructing arbitrary Fourier coefficients. +# + + +###################################################################### +# Part II: Fitting Fourier series with parallel Pauli-rotation encoding +# --------------------------------------------------------------------- +# + + +###################################################################### +# Our next task is to repeat the function-fitting experiment for a circuit +# where the Pauli rotation gate gets repeated :math:`r` times on +# *different* qubits, using a single layer :math:`L=1.` +# +# As shown in the paper, we expect similar results to the serial model: a +# Fourier series of degree :math:`r` can only be fitted if there are at +# least :math:`r` repetitions of the encoding gate in the quantum model. +# However, in practice this experiment is a bit harder, since the dimension of the +# trainable unitaries :math:`W` grows quickly with the number of qubits. +# +# In the paper, the investigations are made with the assumption that the +# purple trainable blocks :math:`W` are arbitrary unitaries. We could use +# the :class:`~.pennylane.templates.ArbitraryUnitary` template, but since this +# template requires a number of parameters that grows exponentially with +# the number of qubits (:math:`4^L-1` to be precise), this quickly becomes +# cumbersome to train. +# +# We therefore follow Figure 4 in the paper and use an ansatz for +# :math:`W.` +# + + +###################################################################### +# .. figure:: ../_static/demonstration_assets/expressivity_fourier_series/parallel_model.png +# :width: 70% +# :align: center +# + + +###################################################################### +# Define the parallel quantum model +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# The ansatz is PennyLane's layer structure called +# :class:`~.pennylane.templates.StronglyEntanglingLayers`, and as the name suggests, it has itself a +# user-defined number of layers (which we will call "ansatz layers" to +# avoid confusion). +# + +from pennylane.templates import StronglyEntanglingLayers + + +###################################################################### +# Let's have a quick look at the ansatz itself for 3 qubits by making a +# dummy circuit of 2 ansatz layers: +# + +n_ansatz_layers = 2 +n_qubits = 3 + +dev = qml.device("default.qubit", wires=4) + + +@qml.qnode(dev) +def ansatz(weights): + StronglyEntanglingLayers(weights, wires=range(n_qubits)) + return qml.expval(qml.Identity(wires=0)) + + +weights_ansatz = 2 * np.pi * np.random.random(size=(n_ansatz_layers, n_qubits, 3)) +print(qml.draw(ansatz, level="device")(weights_ansatz)) + + +###################################################################### +# Now we define the actual quantum model. +# + +scaling = 1 +r = 3 + +dev = qml.device("default.qubit", wires=r) + + +def S(x): + """Data-encoding circuit block.""" + for w in range(r): + qml.RX(scaling * x, wires=w) + + +def W(theta): + """Trainable circuit block.""" + StronglyEntanglingLayers(theta, wires=range(r)) + + +@qml.qnode(dev) +def parallel_quantum_model(weights, x): + + W(weights[0]) + S(x) + W(weights[1]) + + return qml.expval(qml.PauliZ(wires=0)) + + +###################################################################### +# Again, you can sample random weights and plot the model function: +# + +trainable_block_layers = 3 +weights = 2 * np.pi * np.random.random(size=(2, trainable_block_layers, r, 3), requires_grad=True) + +x = np.linspace(-6, 6, 70, requires_grad=False) +random_quantum_model_y = [parallel_quantum_model(weights, x_) for x_ in x] + +plt.plot(x, random_quantum_model_y, c="blue") +plt.ylim(-1, 1) +plt.show() + + +###################################################################### +# Training the model +# ~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# Training the model is done exactly as before, but it may take a lot +# longer this time. We set a default of 70 steps, which you should +# increase if necessary. Small models of <6 qubits +# usually converge after a few hundred steps at most—but this +# depends on your settings. +# + + +def cost(weights, x, y): + predictions = [parallel_quantum_model(weights, x_) for x_ in x] + return square_loss(y, predictions) + + +max_steps = 70 +opt = qml.AdamOptimizer(0.3) +batch_size = 25 +cst = [cost(weights, x, target_y)] # initial cost + +for step in range(max_steps): + + # select batch of data + batch_index = np.random.randint(0, len(x), (batch_size,)) + x_batch = x[batch_index] + y_batch = target_y[batch_index] + + # update the weights by one optimizer step + weights, _, _ = opt.step(cost, weights, x_batch, y_batch) + + # save, and possibly print, the current cost + c = cost(weights, x, target_y) + cst.append(c) + if (step + 1) % 10 == 0: + print("Cost at step {0:3}: {1}".format(step + 1, c)) + + +###################################################################### +# + + +predictions = [parallel_quantum_model(weights, x_) for x_ in x] + +plt.plot(x, target_y, c="black") +plt.scatter(x, target_y, facecolor="white", edgecolor="black") +plt.plot(x, predictions, c="blue") +plt.ylim(-1, 1) +plt.show() + + +###################################################################### +# + + +plt.plot(range(len(cst)), cst) +plt.ylabel("Cost") +plt.xlabel("Step") +plt.show() + + +###################################################################### +# .. note:: +# +# To reproduce the right column in Figure 4 from the paper, use the +# correct ground truth, :math:`r=3` and ``trainable_block_layers=3`,` +# as well as sufficiently many training steps. The amount of steps +# depends on the initial weights and other hyperparameters, and +# in some settings training may not converge to zero error at all. +# + +###################################################################### +# Part III: Sampling Fourier coefficients +# --------------------------------------- +# + + +###################################################################### +# When we use a trainable ansatz above, it is possible that even with +# enough repetitions of the data-encoding Pauli rotation, the quantum +# model cannot fit the circuit, since the expressivity of quantum models +# also depends on the Fourier coefficients the model can create. +# +# Figure 5 in [#schuld2020]_ shows Fourier coefficients +# from quantum models sampled from a model family defined by an +# ansatz for the trainable circuit block. For this we need a +# function that numerically computes the Fourier coefficients of a +# periodic function f with period :math:`2 \pi.` +# + + +def fourier_coefficients(f, K): + """ + Computes the first 2*K+1 Fourier coefficients of a 2*pi periodic function. + """ + n_coeffs = 2 * K + 1 + t = np.linspace(0, 2 * np.pi, n_coeffs, endpoint=False) + y = np.fft.rfft(f(t)) / t.size + return y + + +###################################################################### +# Define your quantum model +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# Now we need to define a quantum model. This could be any model, using a +# qubit or continuous-variable circuit, or one of the quantum models from +# above. We will use a slight derivation of the ``parallel_qubit_model()`` +# from above, this time using the :class:`~.pennylane.templates.BasicEntanglerLayers` ansatz: +# + +from pennylane.templates import BasicEntanglerLayers + +scaling = 1 +n_qubits = 4 + +dev = qml.device("default.qubit", wires=n_qubits) + + +def S(x): + """Data encoding circuit block.""" + for w in range(n_qubits): + qml.RX(scaling * x, wires=w) + + +def W(theta): + """Trainable circuit block.""" + BasicEntanglerLayers(theta, wires=range(n_qubits)) + + +@qml.qnode(dev) +def quantum_model(weights, x): + + W(weights[0]) + S(x) + W(weights[1]) + + return qml.expval(qml.PauliZ(wires=0)) + + +###################################################################### +# It will also be handy to define a function that samples different random +# weights of the correct size for the model. +# + +n_ansatz_layers = 1 + + +def random_weights(): + return 2 * np.pi * np.random.random(size=(2, n_ansatz_layers, n_qubits)) + + +###################################################################### +# Now we can compute the first few Fourier coefficients for samples from +# this model. The samples are created by randomly sampling different +# parameters using the ``random_weights()`` function. +# + +n_coeffs = 5 +n_samples = 100 + + +coeffs = [] +for i in range(n_samples): + + weights = random_weights() + + def f(x): + return np.array([quantum_model(weights, x_) for x_ in x]) + + coeffs_sample = fourier_coefficients(f, n_coeffs) + coeffs.append(coeffs_sample) + +coeffs = np.array(coeffs) +coeffs_real = np.real(coeffs) +coeffs_imag = np.imag(coeffs) + + +###################################################################### +# Let's plot the real vs. the imaginary part of the coefficients. As a +# sanity check, the :math:`c_0` coefficient should be real, and therefore +# have no contribution on the y-axis. +# + +n_coeffs = len(coeffs_real[0]) + +fig, ax = plt.subplots(1, n_coeffs, figsize=(15, 4)) + +for idx, ax_ in enumerate(ax): + ax_.set_title(r"$c_{}$".format(idx)) + ax_.scatter( + coeffs_real[:, idx], + coeffs_imag[:, idx], + s=20, + facecolor="white", + edgecolor="red", + ) + ax_.set_aspect("equal") + ax_.set_ylim(-1, 1) + ax_.set_xlim(-1, 1) + + +plt.tight_layout(pad=0.5) +plt.show() + + +###################################################################### +# Playing around with different quantum models, you will find +# that some quantum models create different distributions over +# the coefficients than others. For example ``BasicEntanglingLayers`` +# (with the default Pauli-X rotation) seems to have a structure +# that forces the even Fourier coefficients to zero, while +# ``StronglyEntanglingLayers`` will have a non-zero variance +# for all supported coefficients. +# +# Note also how the variance of the distribution decreases for growing +# orders of the coefficients—an effect linked to the convergence of a +# Fourier series. +# + + +###################################################################### +# .. note:: +# +# To reproduce the results from Figure 5 you have to change the ansatz (no +# unitary, ``BasicEntanglerLayers`` or ``StronglyEntanglingLayers``, and +# set ``n_ansatz_layers`` either to :math:`1` or :math:`5`). The +# ``StronglyEntanglingLayers`` requires weights of shape +# ``size=(2, n_ansatz_layers, n_qubits, 3)``. +# + + +###################################################################### +# Continuous-variable model +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Ref. [#schuld2020]_ mentions that a phase rotation in +# continuous-variable quantum computing has a spectrum that supports *all* +# Fourier frequecies. To play with this model, we finally show you the +# code for a continuous-variable circuit. For example, to see its Fourier +# coefficients run the cell below, and then re-run the two cells above. +# + +var = 2 +n_ansatz_layers = 1 +dev_cv = qml.device("default.gaussian", wires=1) + + +def S(x): + qml.Rotation(x, wires=0) + + +def W(theta): + """Trainable circuit block.""" + for r_ in range(n_ansatz_layers): + qml.Displacement(theta[0], theta[1], wires=0) + qml.Squeezing(theta[2], theta[3], wires=0) + + +@qml.qnode(dev_cv) +def quantum_model(weights, x): + W(weights[0]) + S(x) + W(weights[1]) + return qml.expval(qml.QuadX(wires=0)) + + +def random_weights(): + return np.random.normal(size=(2, 5 * n_ansatz_layers), loc=0, scale=var) + + +###################################################################### +# .. note:: +# +# To find out what effect so-called "non-Gaussian" gates like the +# ``Kerr`` gate have, you need to install the +# `strawberryfields plugin `_ +# and change the device to +# +# .. code-block:: python +# +# dev_cv = qml.device('strawberryfields.fock', wires=1, cutoff_dim=50) +# + + +############################################################################## +# References +# --------------- +# +# .. [#schuld2020] +# +# Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer. "The effect of data encoding on +# the expressive power of variational quantum machine learning models." +# `arXiv:2008.08605 `__ (2020). +# +# diff --git a/demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json b/demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json new file mode 100644 index 0000000000..5c3d45f9b3 --- /dev/null +++ b/demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json @@ -0,0 +1,48 @@ +{ + "title": "Quantum models as Fourier series", + "authors": [ + { + "username": "mariaschuld" + }, + { + "username": "jjmeyer" + } + ], + "dateOfPublication": "2020-08-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_models_as_fourier_series.png" + } + ], + "seoDescription": "The class of functions a quantum model can learn is characterized by the structure of its corresponding Fourier series.", + "doi": "", + "references": [ + { + "id": "schuld2020", + "type": "article", + "title": "The effect of data encoding on the expressive power of variational quantum machine learning models.", + "authors": "Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer", + "year": "2020", + "journal": "", + "doi": "10.1103/PhysRevA.103.032430", + "url": "https://arxiv.org/abs/2008.08605" + } + ], + "basedOnPapers": [ + "10.1103/PhysRevA.103.032430" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in b/demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_falqon/demo.py b/demonstrations_v2/tutorial_falqon/demo.py new file mode 100644 index 0000000000..833d5cbb07 --- /dev/null +++ b/demonstrations_v2/tutorial_falqon/demo.py @@ -0,0 +1,475 @@ +r""" +Feedback-Based Quantum Optimization (FALQON) +============================================ + +.. meta:: + :property="og:description": Solve combinatorial optimization problems without a classical optimizer + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/falqon_thumbnail.png + +.. related:: + + tutorial_qaoa_intro Intro to QAOA + tutorial_qaoa_maxcut QAOA for MaxCut + +*Authors: David Wakeham and Jack Ceroni — Posted: 21 May 2021. Last updated: 21 May 2021.* + +----------------------------- + +While the +`Quantum Approximate Optimization Algorithm (QAOA) `__ +is one of the best-known processes for solving combinatorial optimization problems with quantum computers, +it has a major drawback: convergence isn't guaranteed, as the optimization procedure can become "stuck" in local minima. + +.. figure:: ../_static/demonstration_assets/falqon/global_min_graph.png + :align: center + :width: 70% + +This tutorial explores the **FALQON** algorithm introduced in `a recent paper by Magann et al. `__ +It is similar in spirit to QAOA, but uses iterative feedback steps rather than a global optimization +over parameters, avoiding the use of a classical optimizer. + +In this demo, we will implement FALQON to solve the MaxClique problem in graph theory, perform benchmarking, and +combine FALQON with QAOA to create a powerful optimization procedure. + +.. note:: + If you are not familiar with QAOA, we recommend checking out the + `Intro to QAOA tutorial `__, + since many of the same ideas carry over and will be used throughout this demonstration. + +Theory +------ + +To solve a combinatorial optimization problem with a quantum computer, a typical strategy is to encode +the solution to the problem as the ground state of a *cost Hamiltonian* :math:`H_c.` Then, we use some procedure to drive +an initial state into the ground state of :math:`H_c.` +FALQON falls under this broad scheme. + +Consider a quantum system governed by a Hamiltonian of the form :math:`H = H_c + \beta(t) H_d.` These kinds of +Hamiltonians appear often in the theory of `quantum control `__, a +field of inquiry which studies how a quantum system can be driven from one state to another. +The choice of :math:`\beta(t)` corresponds to a "driving strategy", which partially determines how the system evolves +with time. + +Suppose our objective is to drive some quantum system +to the ground state of :math:`H_c.` It is a reasonable goal to +construct a quantum control process such that the energy expectation :math:`\langle H_c \rangle_t` decreases with time: + +.. math:: \frac{d}{dt} \langle H_c\rangle_t = \frac{d}{dt} \langle \psi(t)|H_c|\psi(t)\rangle = i \beta(t)\langle [H_d, H_c] \rangle_t \leq 0, + +where the product rule and +`the Schrödinger equation `__ are used to derive the above formula. +If we pick :math:`\beta(t) = -\langle i[H_d, H_c] \rangle_t,` so that + +.. math:: \frac{d}{dt} \langle H_c\rangle_t = -|\langle i[H_d, H_c] \rangle_t|^2 \leq 0, + +then :math:`\langle H_c \rangle` is guaranteed to strictly decrease, as desired! +Thus, if we evolve some initial state :math:`|\psi_0\rangle` under the time evolution operator :math:`U` corresponding to :math:`H,` + +.. math:: U(T) = \mathcal{T} \exp \Big[ -i \displaystyle\int_{0}^{T} H(t) \ dt \Big] \approx \mathcal{T} \exp \Big[ -i \displaystyle\sum_{k = 0}^{T/\Delta t} H( k \Delta t) \Delta t \Big], + +where :math:`\mathcal{T}` is the `time-ordering operator `__ and :math:`\Delta t` is some small time step, +then the energy expectation will strictly decrease, for a large enough value of :math:`T`. This is exactly the procedure used by FALQON to minimize :math:`\langle H_c \rangle.` +In general, implementing a time evolution unitary in a quantum circuit is +difficult, so we use a +`Trotter-Suzuki decomposition `__ +to perform approximate time evolution. We then have + +.. math:: U(T) \approx \mathcal{T} \exp \Big[ -i \displaystyle\sum_{k = 0}^{T/\Delta t} H( k \Delta t) \Delta t \Big] \approx + e^{-i\beta_n H_d \Delta t} e^{-iH_c \Delta t} \cdots e^{-i\beta_1 H_d \Delta t} e^{-iH_c \Delta t} = U_d(\beta_n) U_c \cdots U_d(\beta_1) U_c, + +where :math:`n = T/\Delta t` and :math:`\beta_k = \beta(k\Delta t).` +For each layer of the time evolution, the value :math:`\beta_k` is required. However, +:math:`\beta_k` is dependent on the state of the system at some time. Recall that + +.. math:: \beta(t) = - \langle \psi(t) | i [H_d, H_c] | \psi(t) \rangle. + +We let :math:`A(t) := i\langle [H_d, H_c] \rangle_t.` Our strategy is to obtain the values +:math:`\beta_k` recursively, by finding the value of :math:`A(t)` for the **previous time step**. We then set + +.. math:: \beta_{k+1} = -A_k = -A(k\Delta t). + +This leads to the FALQON algorithm as a recursive process. On step :math:`k,` we perform the following three substeps: + +1. Prepare the state :math:`|\psi_k\rangle = U_d(\beta_k) U_c \cdots U_d(\beta_1) U_c|\psi_0\rangle.` +2. Measure the expectation value :math:`A_k = \langle i[H_c, H_d]\rangle_{k \Delta t}.` +3. Set :math:`\beta_{k+1} = -A_k.` + +We repeat for all :math:`k` from :math:`1` to :math:`n,` where :math:`n` is a hyperparameter. +At the final step we evaluate :math:`\langle H_c \rangle,` +which gives us an approximation for the ground state of :math:`H_c.` + +.. figure:: ../_static/demonstration_assets/falqon/falqon.png + :align: center + :width: 80% +""" + +###################################################################### +# Simulating FALQON with PennyLane +# -------------------------------- +# To begin, we import the necessary dependencies: +# + +import pennylane as qml +from pennylane import numpy as np +from matplotlib import pyplot as plt +from pennylane import qaoa as qaoa +import networkx as nx + +###################################################################### +# In this demonstration, we will be using FALQON to solve the +# `maximum clique (MaxClique) problem `__: finding the +# largest complete subgraph of some graph :math:`G.` For example, the following graph's maximum +# clique is coloured in red: +# +# .. figure:: ../_static/demonstration_assets/falqon/max_clique.png +# :align: center +# :width: 90% +# +# We attempt to find the maximum clique of the graph below: +# + +edges = [(0, 1), (1, 2), (2, 0), (2, 3), (1, 4)] +graph = nx.Graph(edges) +nx.draw(graph, with_labels=True, node_color="#e377c2") + +###################################################################### +# We must first encode this combinatorial problem into a cost Hamiltonian :math:`H_c.` This ends up being +# +# .. math:: H_c = 3 \sum_{(i, j) \in E(\bar{G})} (Z_i Z_j - Z_i - Z_j) + \displaystyle\sum_{i \in V(G)} Z_i, +# +# where each qubit is a node in the graph, and the states :math:`|0\rangle` and :math:`|1\rangle` +# represent whether the vertex has been marked as part of the clique, as is the case for `most standard QAOA encoding +# schemes `__. +# Note that :math:`\bar{G}` is the complement of :math:`G:` the graph formed by connecting all nodes that **do not** share +# an edge in :math:`G.` +# +# In addition to defining :math:`H_c,` we also require a driver Hamiltonian :math:`H_d` which does not commute +# with :math:`H_c.` The driver Hamiltonian's role is similar to that of the mixer Hamiltonian in QAOA. +# To keep things simple, we choose a sum over Pauli :math:`X` operations on each qubit: +# +# .. math:: H_d = \displaystyle\sum_{i \in V(G)} X_i. +# +# These Hamiltonians come nicely bundled together in the PennyLane QAOA module: +# + +cost_h, driver_h = qaoa.max_clique(graph, constrained=False) + +print("Cost Hamiltonian") +print(cost_h) +print("Driver Hamiltonian") +print(driver_h) + +###################################################################### +# One of the main ingredients in the FALQON algorithm is the operator :math:`i [H_d, H_c].` In +# the case of MaxClique, we can write down the commutator :math:`[H_d, H_c]` explicitly: +# +# .. math:: [H_d, H_c] = 3 \displaystyle\sum_{k \in V(G)} \displaystyle\sum_{(i, j) \in E(\bar{G})} \big( [X_k, Z_i Z_j] - [X_k, Z_i] +# - [X_k, Z_j] \big) + 3 \displaystyle\sum_{i \in V(G)} \displaystyle\sum_{j \in V(G)} [X_i, Z_j]. +# +# There are two distinct commutators that we must calculate, :math:`[X_k, Z_j]` and :math:`[X_k, Z_i Z_j].` +# This is straightforward as we know exactly what the +# `commutators of the Pauli matrices `__ are. +# We have: +# +# .. math:: [X_k, Z_j] = -2 i \delta_{kj} Y_k \ \ \ \text{and} \ \ \ [X_k, Z_i Z_j] = -2 i \delta_{ik} Y_k Z_j - 2i \delta_{jk} Z_i Y_k, +# +# where :math:`\delta_{kj}` is the `Kronecker delta `__. Therefore it +# follows from substitution into the above equation and multiplication by :math:`i` that: +# +# .. math:: i [H_d, H_c] = 6 \displaystyle\sum_{k \in V(G)} \displaystyle\sum_{(i, j) \in E(\bar{G})} \big( \delta_{ki} Y_k Z_j + +# \delta_{kj} Z_{i} Y_{k} - \delta_{ki} Y_k - \delta_{kj} Y_k \big) + 6 \displaystyle\sum_{i \in V(G)} Y_{i}. +# +# This new operator has quite a few terms! Therefore, we write a short method which computes it for us, and returns +# a :class:`~.pennylane.Hamiltonian` object. Note that this method works for any graph: +# + +def build_hamiltonian(graph): + H = qml.Hamiltonian([], []) + + # Computes the complement of the graph + graph_c = nx.complement(graph) + + for k in graph_c.nodes: + # Adds the terms in the first sum + for edge in graph_c.edges: + i, j = edge + if k == i: + H += 6 * (qml.PauliY(k) @ qml.PauliZ(j) - qml.PauliY(k)) + if k == j: + H += 6 * (qml.PauliZ(i) @ qml.PauliY(k) - qml.PauliY(k)) + # Adds the terms in the second sum + H += 6 * qml.PauliY(k) + + return H + + +print("MaxClique Commutator") +print(build_hamiltonian(graph)) + +###################################################################### +# We can now build the FALQON algorithm. Our goal is to evolve some initial state under the Hamiltonian :math:`H,` +# with our chosen :math:`\beta(t).` We first define one layer of the Trotterized time evolution, which is of +# the form :math:`U_d(\beta_k) U_c.` Note that we can use the :class:`~.pennylane.templates.ApproxTimeEvolution` template: + +def falqon_layer(beta_k, cost_h, driver_h, delta_t): + qml.ApproxTimeEvolution(cost_h, delta_t, 1) + qml.ApproxTimeEvolution(driver_h, delta_t * beta_k, 1) + +###################################################################### +# We then define a method which returns a FALQON ansatz corresponding to a particular cost Hamiltonian, driver +# Hamiltonian, and :math:`\Delta t.` This involves multiple repetitions of the "FALQON layer" defined above. The +# initial state of our circuit is an even superposition: + +def build_maxclique_ansatz(cost_h, driver_h, delta_t): + def ansatz(beta, **kwargs): + layers = len(beta) + for w in dev.wires: + qml.Hadamard(wires=w) + qml.layer( + falqon_layer, + layers, + beta, + cost_h=cost_h, + driver_h=driver_h, + delta_t=delta_t + ) + + return ansatz + + +def expval_circuit(beta, measurement_h): + ansatz = build_maxclique_ansatz(cost_h, driver_h, delta_t) + ansatz(beta) + return qml.expval(measurement_h) + +###################################################################### +# Finally, we implement the recursive process, where FALQON is able to determine the values +# of :math:`\beta_k,` feeding back into itself as the number of layers increases. This is +# straightforward using the methods defined above: + +def max_clique_falqon(graph, n, beta_1, delta_t, dev): + comm_h = build_hamiltonian(graph) # Builds the commutator + cost_h, driver_h = qaoa.max_clique(graph, constrained=False) # Builds H_c and H_d + cost_fn = qml.QNode(expval_circuit, dev, interface="autograd") # The ansatz + measurement circuit is executable + + beta = [beta_1] # Records each value of beta_k + energies = [] # Records the value of the cost function at each step + + for i in range(n): + # Adds a value of beta to the list and evaluates the cost function + beta.append(-1 * cost_fn(beta, measurement_h=comm_h)) # this call measures the expectation of the commuter hamiltonian + energy = cost_fn(beta, measurement_h=cost_h) # this call measures the expectation of the cost hamiltonian + energies.append(energy) + + return beta, energies + +###################################################################### +# Note that we return both the list of :math:`\beta_k` values, as well as the expectation value of the cost Hamiltonian +# for each step. +# +# We can now run FALQON for our MaxClique problem! It is important that we choose :math:`\Delta t` small enough +# such that the approximate time evolution is close enough to the real time evolution, otherwise we the expectation +# value of :math:`H_c` may not strictly decrease. For this demonstration, we set :math:`\Delta t = 0.03,` +# :math:`n = 40,` and :math:`\beta_1 = 0.` These are comparable to the hyperparameters chosen in the original paper. + +n = 40 +beta_1 = 0.0 +delta_t = 0.03 + +dev = qml.device("default.qubit", wires=graph.nodes) # Creates a device for the simulation +res_beta, res_energies = max_clique_falqon(graph, n, beta_1, delta_t, dev) + +###################################################################### +# We can then plot the expectation value of the cost Hamiltonian over the +# iterations of the algorithm: +# + +plt.plot(range(n+1)[1:], res_energies) +plt.xlabel("Iteration") +plt.ylabel("Cost Function Value") +plt.show() + +###################################################################### +# The expectation value decreases! +# +# To get a better understanding of the performance of the FALQON algorithm, +# we can create a graph showing the probability of measuring each possible bit string. +# We define the following circuit, feeding in the optimal values of :math:`\beta_k:` + +@qml.qnode(dev, interface="autograd") +def prob_circuit(): + ansatz = build_maxclique_ansatz(cost_h, driver_h, delta_t) + ansatz(res_beta) + return qml.probs(wires=dev.wires) + +###################################################################### +# Running this circuit gives us the following probability distribution: +# + +probs = prob_circuit() +plt.bar(range(2**len(dev.wires)), probs) +plt.xlabel("Bit string") +plt.ylabel("Measurement Probability") +plt.show() + +###################################################################### +# The bit string occurring with the highest probability is the state :math:`|28\rangle = |11100\rangle.` +# This corresponds to nodes :math:`0`, :math:`1`, and :math:`2,` which is precisely the maximum clique. +# FALQON has solved the MaxClique problem 🤩. +# + +graph = nx.Graph(edges) +cmap = ["#00b4d9"]*3 + ["#e377c2"]*2 +nx.draw(graph, with_labels=True, node_color=cmap) + +###################################################################### +# Benchmarking FALQON +# ------------------- +# +# After seeing how FALQON works, it is worth considering how well FALQON performs according to a set of benchmarking +# criteria on a batch of graphs. We generate graphs randomly using the +# `Erdos-Renyi model `__, where we start with +# the complete graph on :math:`n` vertices and then keep each edge with probability :math:`p.` We then find the maximum +# cliques on these graphs using the +# `Bron-Kerbosch algorithm `__. To benchmark FALQON, +# the relative error in the estimated minimum energy +# +# .. math:: r_A = \frac{\langle H_C\rangle - \langle H_C\rangle_\text{min}}{|\langle H_C\rangle_\text{min}|} +# +# makes a good figure of merit. +# +# Final results for :math:`r_A`, along with :math:`\beta,` are plotted below, +# with the number of FALQON layers on the horizontal axis. We have averaged over :math:`50` random graphs per node +# size, for sizes :math:`n = 6, 7, 8, 9,` with probability :math:`p = 0.1` of keeping an edge. Running FALQON for +# :math:`40` steps, with :math:`\Delta t = 0.01,` produces: +# +# .. figure:: ../_static/demonstration_assets/falqon/bench.png +# :align: center +# :width: 60% +# +# The relative error decreases with the number of layers (as we expect from the construction) and graph size (suggesting the errors grows +# more slowly than the minimum energy). +# The exception is :math:`n = 9,` where the step size has become too large +# and the Trotter-Suzuki decomposition breaks down. +# The rate of decrease also slows down. Even though the algorithm will converge to the ground state, it won't always get +# there in a few time steps! + +###################################################################### +# Seeding QAOA with FALQON (Bird Seed 🦅) +# --------------------------------------- +# +# .. figure:: ../_static/demonstration_assets/falqon/bird_seed.png +# :align: center +# :width: 90% +# +# Both FALQON and QAOA have unique benefits and drawbacks. +# While FALQON requires no classical optimization and is guaranteed to decrease the cost function +# with each iteration, its circuit depth grows linearly with the number of iterations. The benchmarking data also shows +# how the reduction in cost slows with each layer, and the additional burden of correctly tuning the time step. On the other hand, QAOA +# has a fixed circuit depth, but does require classical optimization, and is therefore subject to all of the drawbacks +# that come with probing a cost landscape for a set of optimal parameters. +# +# QAOA and FALQON also have many similarities, most notably, their circuit structure. Both +# involve alternating layers of time evolution operators corresponding to a cost and a mixer/driver Hamiltonian. +# The FALQON paper raises the idea of combining FALQON and QAOA to yield a new optimization algorithm that +# leverages the benefits of both. In this final section of the tutorial, we will implement this procedure in PennyLane. +# +# Suppose we want to run a QAOA circuit of depth :math:`p.` Our ansatz will be of the form +# +# .. math:: U_{\text{QAOA}} = e^{-i \alpha_p H_m} e^{-i \gamma_p H_c} \cdots e^{-i \alpha_1 H_m} e^{-i \gamma_1 H_c}, +# +# for sets of parameters :math:`\{\alpha_k\}` and :math:`\{\gamma_k\},` which are optimized. +# If we run FALQON for :math:`p` steps, setting :math:`H_d = H_m,` and use the same cost Hamiltonian, we will end up with +# the following ansatz: +# +# .. math:: U_{\text{FALQON}} = e^{-i \Delta t \beta_p H_m} e^{-i \Delta t H_c} \cdots e^{-i \Delta t \beta_1 H_m} e^{-i \Delta t H_c}. +# +# Thus, our strategy is to initialize our QAOA parameters using the :math:`\beta_k` values that FALQON yields. +# More specifically, we set :math:`\alpha_k = \Delta t \beta_k` and :math:`\gamma_k = \Delta t.` We then optimize +# over these parameters. The goal is that these parameters provide QAOA a good place in the parameter space to +# begin its optimization. +# +# Using the code from earlier in the demonstration, we can easily prototype this process. To illustrate the power of +# this new technique, we attempt to solve MaxClique on a slightly more complicated graph: + +new_edges = [(0, 1), (1, 2), (2, 0), (2, 3), (1, 4), (4, 5), (5, 2), (0, 6)] +new_graph = nx.Graph(new_edges) +nx.draw(new_graph, with_labels=True, node_color="#e377c2") + +###################################################################### +# We can now use the PennyLane QAOA module to create a QAOA circuit corresponding to the MaxClique problem. For this +# demonstration, we set the depth to :math:`5:` + +depth = 5 +dev = qml.device("default.qubit", wires=new_graph.nodes) + +# Creates the cost and mixer Hamiltonians +cost_h, mixer_h = qaoa.max_clique(new_graph, constrained=False) + +# Creates a layer of QAOA +def qaoa_layer(gamma, beta): + qaoa.cost_layer(gamma, cost_h) + qaoa.mixer_layer(beta, mixer_h) + +# Creates the full QAOA circuit as an executable cost function +def qaoa_circuit(params, **kwargs): + for w in dev.wires: + qml.Hadamard(wires=w) + qml.layer(qaoa_layer, depth, params[0], params[1]) + + +@qml.qnode(dev, interface="autograd") +def qaoa_expval(params): + qaoa_circuit(params) + return qml.expval(cost_h) + +###################################################################### +# Now all we have to do is run FALQON for :math:`5` steps to get our initial QAOA parameters. +# We set :math:`\Delta t = 0.02:` + +delta_t = 0.02 + +res, res_energy = max_clique_falqon(new_graph, depth-1, 0.0, delta_t, dev) + +params = np.array([[delta_t for k in res], [delta_t * k for k in res]], requires_grad=True) + +###################################################################### +# Finally, we run our QAOA optimization procedure. We set the number of QAOA executions to :math:`40:` +# + +steps = 40 + +optimizer = qml.GradientDescentOptimizer() + +for s in range(steps): + params, cost = optimizer.step_and_cost(qaoa_expval, params) + print("Step {}, Cost = {}".format(s + 1, cost)) + +###################################################################### +# To conclude, we can check how well FALQON/QAOA solved the optimization problem. We +# define a circuit which outputs the probabilities of measuring each bit string, and +# create a bar graph: + +@qml.qnode(dev, interface="autograd") +def prob_circuit(params): + qaoa_circuit(params) + return qml.probs(wires=dev.wires) + +probs = prob_circuit(params) +plt.bar(range(2**len(dev.wires)), probs) +plt.xlabel("Bit string") +plt.ylabel("Measurement Probability") +plt.show() + +###################################################################### +# The state :math:`|112\rangle = |1110000\rangle` occurs with highest probability. +# This corresponds to nodes :math:`0`, :math:`1,` and :math:`2` of the graph, which is +# the maximum clique! We have successfully combined FALQON and QAOA to solve a combinatorial +# optimization problem 🎉. +# +# References +# ---------- +# +# Magann, A. B., Rudinger, K. M., Grace, M. D., & Sarovar, M. (2021). Feedback-based quantum optimization. arXiv preprint `arXiv:2103.08619 `__. +# +# diff --git a/demonstrations_v2/tutorial_falqon/metadata.json b/demonstrations_v2/tutorial_falqon/metadata.json new file mode 100644 index 0000000000..64d17b0991 --- /dev/null +++ b/demonstrations_v2/tutorial_falqon/metadata.json @@ -0,0 +1,53 @@ +{ + "title": "Feedback-Based Quantum Optimization (FALQON)", + "authors": [ + { + "username": "hapax" + }, + { + "username": "jceroni" + } + ], + "dateOfPublication": "2021-05-21T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_FALQON.png" + } + ], + "seoDescription": "Solve combinatorial optimization problems without a classical optimizer", + "doi": "", + "references": [ + { + "id": "Magann2021", + "type": "article", + "title": "Feedback-based quantum optimization", + "authors": "Magann, A. B., Rudinger, K. M., Grace, M. D., & Sarovar, M.", + "year": "2021", + "journal": "", + "doi": "10.1103/PhysRevLett.129.250502", + "url": "https://arxiv.org/abs/2103.08619" + } + ], + "basedOnPapers": [ + "10.1103/PhysRevLett.129.250502" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_falqon/requirements.in b/demonstrations_v2/tutorial_falqon/requirements.in new file mode 100644 index 0000000000..f84259cda4 --- /dev/null +++ b/demonstrations_v2/tutorial_falqon/requirements.in @@ -0,0 +1,3 @@ +matplotlib +networkx +pennylane diff --git a/demonstrations_v2/tutorial_fermionic_operators/demo.py b/demonstrations_v2/tutorial_fermionic_operators/demo.py new file mode 100644 index 0000000000..41857120ae --- /dev/null +++ b/demonstrations_v2/tutorial_fermionic_operators/demo.py @@ -0,0 +1,227 @@ +r""" + +Fermionic operators +=================== + +.. meta:: + :property="og:description": Learn how to work with fermionic operators + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_fermionic_operators.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + +*Author: Soran Jahangiri — Posted: 27 June 2023. Last updated: 27 June 2023.* + +Fermionic creation and annihilation operators are commonly used to construct +`Hamiltonians `_ and other observables of molecules and spin +systems [#surjan]_. In this demo, you will learn how to use PennyLane to create fermionic operators +and map them to a qubit representation for use in quantum algorithms. + +Constructing fermionic operators +-------------------------------- +The fermionic `creation and annihilation `_ +operators can be constructed in PennyLane similarly to Pauli operators by using +:class:`~.pennylane.FermiC` and :class:`~.pennylane.FermiA` for creation and annihilation operators, +respectively. +""" + +from pennylane import FermiC, FermiA + +a0_dag = FermiC(0) +a1 = FermiA(1) + +############################################################################## +# We used the compact notations ``a0_dag`` to denote a creation operator applied to +# the :math:`0\text{th}` orbital and ``a1`` to denote an annihilation operator applied to the +# :math:`1\text{st}` orbital. Once created, these operators can be multiplied or added to each other +# to create new operators. A product of fermionic operators will be called a *Fermi word* and a +# linear combination of Fermi words will be called a *Fermi sentence*. + +fermi_word = a0_dag * a1 +fermi_sentence = 1.3 * a0_dag * a1 + 2.4 * a1 +print(fermi_sentence) + +############################################################################## +# In this simple example, we first created the operator :math:`a^{\dagger}_0 a_1` and then created +# the linear combination :math:`1.3 a^{\dagger}_0 a_1 + 2.4 a_1.` We can also perform +# arithmetic operations between Fermi words and Fermi sentences. + +fermi_sentence = fermi_sentence * fermi_word + 2.3 * fermi_word +print(fermi_sentence) + +############################################################################## +# Beyond multiplication, summation, and subtraction, we can exponentiate fermionic operators in +# PennyLane to an integer power. For instance, we can create a more complicated operator +# +# .. math:: +# +# 1.2 \times a_0^{\dagger} + 0.5 \times a_1 - 2.3 \times \left ( a_0^{\dagger} a_1 \right )^2, +# +# in the same way that you would write down the operator on a piece of paper: + +fermi_sentence = 1.2 * a0_dag + 0.5 * a1 - 2.3 * (a0_dag * a1) ** 2 +print(fermi_sentence) + +############################################################################## +# This Fermi sentence can be mapped to the qubit basis using the +# `Jordan-Wigner `_ +# transformation to get a linear combination of Pauli operators. + +from pennylane import jordan_wigner + +pauli_sentence = jordan_wigner(fermi_sentence) +pauli_sentence + +############################################################################## +# Fermionic Hamiltonians +# ---------------------- +# Now that we have nice tools to create and manipulate fermionic operators, we can build some +# interesting fermionic Hamiltonians. +# +# A toy model +# ^^^^^^^^^^^ +# Our first example is a toy Hamiltonian inspired by the +# `Hückel method `_, which is a method for +# describing molecules with alternating single and double bonds. Our toy model is a simplified +# version of the Hückel Hamiltonian and assumes only two orbitals and a single electron. +# +# .. math:: +# +# H = \alpha \left (a^{\dagger}_0 a_0 + a^{\dagger}_1 a_1 \right ) + +# \beta \left (a^{\dagger}_0 a_1 + a^{\dagger}_1 a_0 \right ). +# +# This Hamiltonian can be constructed with pre-defined values :math:`\alpha = 0.01` and +# :math:`\beta = -0.02.` + +h1 = 0.01 * (FermiC(0) * FermiA(0) + FermiC(1) * FermiA(1)) +h2 = -0.02 * (FermiC(0) * FermiA(1) + FermiC(1) * FermiA(0)) +h = h1 + h2 +print(h) + +############################################################################## +# The fermionic Hamiltonian can be converted to the qubit Hamiltonian with: + +h = jordan_wigner(h) + +############################################################################## +# The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized +# to get its eigenpairs. + +from pennylane import numpy as np + +val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) +print(f"eigenvalues:\n{val}") +print() +print(f"eigenvectors:\n{np.real(vec.T)}") + +############################################################################## +# +# Hydrogen molecule +# ^^^^^^^^^^^^^^^^^ +# The `second quantized `_ molecular electronic +# Hamiltonian is usually constructed as +# +# .. math:: +# H = \sum_{\alpha \in \{\uparrow, \downarrow \} } \sum_{pq} c_{pq} a_{p,\alpha}^{\dagger} +# a_{q, \alpha} + \frac{1}{2} \sum_{\alpha, \beta \in \{\uparrow, \downarrow \} } \sum_{pqrs} +# c_{pqrs} a_{p, \alpha}^{\dagger} a_{q, \beta}^{\dagger} a_{r, \beta} a_{s, \alpha}, +# +# where :math:`\alpha` and :math:`\beta` denote the electron spin and :math:`p, q, r, s` are the +# orbital indices. The coefficients :math:`c` are integrals over +# molecular orbitals that are obtained from +# `Hartree-Fock `_ +# calculations. These integrals can be computed with PennyLane using the +# :func:`~.pennylane.qchem.electron_integrals` function. We can build the molecular Hamiltonian for +# the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. + +import pennylane as qml + +symbols = ["H", "H"] +geometry = np.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]], requires_grad=False) + +############################################################################## +# Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the +# second quantized molecular Hamiltonian defined above. We also obtain the core constant, which is +# later used to calculate the contribution of the nuclear energy to the Hamiltonian. + +mol = qml.qchem.Molecule(symbols, geometry) +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# These integrals are computed over molecular orbitals. Each molecular orbital contains a pair of +# electrons with different spins. We have assumed that the spatial distribution of these electron +# pairs is the same to simplify the calculation of the integrals. However, to properly account for +# all electrons, we need to duplicate the integrals for electrons with the same spin. For example, +# the :math:`pq` integral, which is the integral over the orbital :math:`p` and the orbital +# :math:`q,` can be used +# for both spin-up and spin-down electrons. Then, if we have a :math:`2 \times 2` matrix of such +# integrals, it will become a :math:`4 \times 4` matrix. The code block below simply extends the +# integrals by duplicating terms to account for both spin-up and spin-down electrons. + +for i in range(4): + if i < 2: + one = one.repeat(2, axis=i) + two = two.repeat(2, axis=i) + +############################################################################## +# We can now construct the fermionic Hamiltonian for the hydrogen molecule. The one-body terms, +# which are the first part in the Hamiltonian above, can be added first. We will use +# `itertools `_ to efficiently +# create all the combinations we need. Some of these combinations are not allowed because of spin +# restrictions and we need to exclude them. You can find more details about +# constructing a molecular Hamiltonian in reference [#surjan]_. + +import itertools + +n = one.shape[0] + +h = 0.0 + +for p, q in itertools.product(range(n), repeat=2): + if p % 2 == q % 2: # to account for spin-forbidden terms + h += one[p, q] * FermiC(p) * FermiA(q) + +############################################################################## +# The two-body terms can be added with: + +for p, q, r, s in itertools.product(range(n), repeat=4): + if p % 2 == s % 2 and q % 2 == r % 2: # to account for spin-forbidden terms + h += two[p, q, r, s] / 2 * FermiC(p) * FermiC(q) * FermiA(r) * FermiA(s) + +############################################################################## +# We then simplify the Hamiltonian to remove terms with negligible coefficients and then map it to +# the qubit basis. + +h.simplify() +h = jordan_wigner(h) + +############################################################################## +# We also need to include the contribution of the nuclear energy. + +h += np.sum(core * qml.Identity(0)) + +############################################################################## +# This gives us the qubit Hamiltonian which can be used as an input for quantum algorithms. We can +# also compute the ground-state energy by diagonalizing the matrix representation of the +# Hamiltonian in the computational basis. + +np.linalg.eigh(h.sparse_matrix().toarray())[0].min() + +############################################################################## +# Summary +# ------- +# This demo explains how to create and manipulate fermionic operators in PennyLane, which is as +# easy as writing the operators on paper. PennyLane supports several arithmetic operations between +# fermionic operators and provides tools for mapping them to the qubit basis. This makes it easy and +# intuitive to construct complicated fermionic Hamiltonians such as +# `molecular Hamiltonians `_. +# +# References +# ---------- +# +# .. [#surjan] +# +# Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. +# +# diff --git a/demonstrations_v2/tutorial_fermionic_operators/metadata.json b/demonstrations_v2/tutorial_fermionic_operators/metadata.json new file mode 100644 index 0000000000..7c566a8536 --- /dev/null +++ b/demonstrations_v2/tutorial_fermionic_operators/metadata.json @@ -0,0 +1,55 @@ +{ + "title": "Fermionic operators", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2023-06-27T00:00:00+00:00", + "dateOfLastModification": "2024-10-30T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/fermionic_operators/thumbnail_tutorial_fermionic_operators.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_fermionic_operators.png" + }, + { + "type": "hero_image", + "uri": "/_static/hero_illustrations/fermionic_ops_hero.png" + } + ], + "seoDescription": "Construct Hamiltonians and other observables using fermionic creation and annihilation operators.", + "doi": "", + "references": [ + { + "id": "surjan", + "type": "book", + "title": "Second Quantized Approach to Quantum Chemistry", + "authors": "Peter R. Surjan", + "year": "1989", + "publisher": "Springer-Verlag", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_fermionic_operators/requirements.in b/demonstrations_v2/tutorial_fermionic_operators/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_fermionic_operators/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_gaussian_transformation/demo.py b/demonstrations_v2/tutorial_gaussian_transformation/demo.py new file mode 100644 index 0000000000..48ba4fb6b0 --- /dev/null +++ b/demonstrations_v2/tutorial_gaussian_transformation/demo.py @@ -0,0 +1,169 @@ +r""" +.. _gaussian_transformation: + +Gaussian transformation +======================= + +.. meta:: + :property="og:description": Use quantum machine learning techniques to tune a beamsplitter. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/gauss-circuit.png + +.. related:: + plugins_hybrid Plugins and Hybrid computation + quantum_neural_net Function fitting with a photonic quantum neural network + qonn Optimizing a quantum optical neural network + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 16 October 2020.* + +This tutorial demonstrates the basic working principles of PennyLane for +continuous-variable (CV) photonic devices. For more details about photonic +quantum computing, the +`Strawberry Fields documentation `_ +is a great starting point. + +The quantum circuit +------------------- + +For this basic tutorial, we will consider a special subset of CV operations: +the *Gaussian transformations*. We work with the following simple Gaussian circuit: + +.. figure:: ../_static/demonstration_assets/gaussian_transformation/gaussian_transformation.svg + :align: center + :width: 40% + :target: javascript:void(0); + +What is this circuit doing? + +1. **We begin with one wire (qumode) in the vacuum state**. Note that we use the same + notation :math:`|0\rangle` for the initial state as the previous qubit tutorial. + In a photonic CV system, this state is the *vacuum state*, i.e., the state with no + photons in the wire. + +2. **We displace the qumode**. The displacement gate linearly shifts the state of the + qumode in phase space. The vacuum state is centered at the origin in phase space, + while the displaced state will be centered at the point :math:`\alpha.` + +3. **We rotate the qumode**. This is another linear transformation in phase space, + albeit a rotation (by angle :math:`\phi`) instead of a displacement. + +4. **Finally, we measure the mean photon number** :math:`\langle\hat{n}\rangle = + \langle\hat{a}^\dagger \hat{a}\rangle`. This quantity, which tells us the average amount of + photons in the final state, is proportional to the energy of the photonic system. + +""" + + +############################################################################### +# +# The aim of this tutorial is to optimize the circuit parameters :math:`(\alpha, \phi)` +# such that the mean photon number is equal to one. The rotation gate is actually a +# *passive transformation*, meaning that it does not change the energy of the system. +# The displacement gate is an *active transformation*, which modifies the energy of the +# photonic system. +# +# Constructing the QNode +# ---------------------- +# +# As before, we import PennyLane, as well as the wrapped version of NumPy provided +# by JAX: + +import pennylane as qml +from jax import numpy as np + +############################################################################### +# Next, we instantiate a device which will be used to evaluate the circuit. +# Because our circuit contains only Gaussian operations, we can make use of the +# built-in ``default.gaussian`` device. + +dev_gaussian = qml.device("default.gaussian", wires=1) + +############################################################################### +# After initializing the device, we can construct our quantum node. As before, we use the +# :func:`~.pennylane.qnode` to convert our quantum function +# (encoded by the circuit above) into a quantum node running on the ``default.gaussian`` +# device. + + +@qml.qnode(dev_gaussian) +def mean_photon_gaussian(mag_alpha, phase_alpha, phi): + qml.Displacement(mag_alpha, phase_alpha, wires=0) + qml.Rotation(phi, wires=0) + return qml.expval(qml.NumberOperator(0)) + + +############################################################################### +# Notice that we have broken up the complex number :math:`\alpha` into two real +# numbers ``mag_alpha`` and ``phase_alpha``, which form a polar representation of +# :math:`\alpha.` This is so that the notion of a gradient is clear and well-defined. + + +############################################################################### +# Optimization +# ------------ +# +# As in the :ref:`qubit rotation ` tutorial, let's now use one +# of the ``jaxopt`` optimizers in order to optimize the quantum circuit +# towards the desired output. We want the mean photon number to be exactly one, +# so we will use a squared-difference cost function: + + +def cost(params): + return (mean_photon_gaussian(params[0], params[1], params[2]) - 1.0) ** 2 + + +############################################################################### +# At the beginning of the optimization, we choose arbitrary small initial parameters: + +init_params = np.array([0.015, 0.02, 0.005]) +print(cost(init_params)) + +############################################################################### +# When the gate parameters are near to zero, the gates are close to the +# identity transformation, which leaves the initial state largely unchanged. +# Since the initial state contains no photons, the mean photon number of the +# circuit output is approximately zero, and the cost is close to one. + +############################################################################### +# .. note:: +# +# We avoided initial parameters which are exactly zero because that +# corresponds to a critical point with zero gradient. +# +# Now, let's use the ``GradientDescent`` optimizer, and update the circuit +# parameters over 100 optimization steps. + +import jaxopt + +# initialise the optimizer +opt = jaxopt.GradientDescent(cost, stepsize=0.1, acceleration = False) + +# set the number of steps +steps = 20 +# set the initial parameter values +params = init_params +opt_state = opt.init_state(params) + +for i in range(steps): + # update the circuit parameters + params, opt_state = opt.update(params, opt_state) + + print("Cost after step {:5d}: {:8f}".format(i + 1, cost(params))) + +print("Optimized mag_alpha:{:8f}".format(params[0])) +print("Optimized phase_alpha:{:8f}".format(params[1])) +print("Optimized phi:{:8f}".format(params[2])) + +############################################################################### +# The optimization converges after about 20 steps to a cost function value +# of zero. +# +# We observe that the two angular parameters ``phase_alpha`` and ``phi`` +# do not change during the optimization. Only the magnitude of the complex +# displacement :math:`|\alpha|` affects the mean photon number of the circuit. +# +# Continue on to the next tutorial, :ref:`plugins_hybrid`, to learn how to +# utilize the extensive plugin ecosystem of PennyLane, +# build continuous-variable (CV) quantum nodes, and to see an example of a +# hybrid qubit-CV-classical computation using PennyLane. +# +# diff --git a/demonstrations_v2/tutorial_gaussian_transformation/metadata.json b/demonstrations_v2/tutorial_gaussian_transformation/metadata.json new file mode 100644 index 0000000000..2047aa555e --- /dev/null +++ b/demonstrations_v2/tutorial_gaussian_transformation/metadata.json @@ -0,0 +1,42 @@ +{ + "title": "Gaussian transformation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_gaussian_transformation.png" + } + ], + "seoDescription": "Use quantum machine learning techniques to tune a beamsplitter.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "plugins_hybrid", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_neural_net", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qonn", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_gaussian_transformation/requirements.in b/demonstrations_v2/tutorial_gaussian_transformation/requirements.in new file mode 100644 index 0000000000..375a0e28cc --- /dev/null +++ b/demonstrations_v2/tutorial_gaussian_transformation/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +jaxopt +pennylane diff --git a/demonstrations_v2/tutorial_general_parshift/demo.py b/demonstrations_v2/tutorial_general_parshift/demo.py new file mode 100644 index 0000000000..8fddd2e8db --- /dev/null +++ b/demonstrations_v2/tutorial_general_parshift/demo.py @@ -0,0 +1,888 @@ +r""" + +.. _general_parshift: + +Generalized parameter-shift rules +================================= + +.. meta:: + + :property="og:description": Reconstruct quantum functions and compute their derivatives. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_genpar.png + +.. related:: + + tutorial_expressivity_fourier_series Quantum models as Fourier series + tutorial_rotoselect Quantum circuit structure learning + tutorial_quantum_analytic_descent Quantum analytic descent + + +*Author: David Wierichs (Xanadu Resident) — Posted: 23 August 2021. Last updated: 27 August 2021* + +In this demo we will look at univariate quantum functions, i.e., those that +depend on a single parameter. We will investigate the form such functions usually take +and demonstrate how we can *reconstruct* them as classical functions, capturing the full +dependence on the input parameter. +Once we have this reconstruction, we use it to compute analytically exact derivatives +of the quantum function. We implement this in two ways: +first, by using autodifferentiation on the classical function that is produced by the +reconstruction, which is flexible with respect to the degree of the derivative. +Second, by computing the derivative manually, resulting in generalized parameter-shift +rules for quantum functions that is more efficient (regarding classical cost) than the +autodifferentiation approach, but requires manual computations if we want to access +higher-order derivatives. +All we will need for the demo is the insight that these functions are Fourier series in their +variable, and the reconstruction itself is a +`trigonometric interpolation `_. + +A full description of the reconstruction, the technical derivation of the parameter-shift +rules, and considerations for multivariate functions can be found in the paper +`General parameter-shift rules for quantum gradients `_ +[#GenPar]_. +The core idea to consider these quantum functions as Fourier series was first presented in +the preprint +`Calculus on parameterized quantum circuits `_ [#CalcPQC]_. +We will follow [#GenPar]_, but there also are two preprints discussing general parameter-shift +rules: an algebraic approach in +`Analytic gradients in variational quantum algorithms: Algebraic extensions of the parameter-shift rule to general unitary transformations `_ [#AlgeShift]_ +and one focusing on special gates and spectral decompositions, namely +`Generalized quantum circuit differentiation rules `_ +[#GenDiffRules]_. + +| + +.. figure:: ../_static/demonstration_assets/general_parshift/thumbnail_genpar.png + :align: center + :width: 50% + :target: javascript:void(0) + + Function reconstruction and differentiation via parameter shifts. + +.. note :: + + Before going through this tutorial, we recommend that readers refer to the + :doc:`Fourier series expressiveness tutorial `. + Additionally, having a basic understanding of the + :doc:`parameter-shift rule ` might make this tutorial easier + to dive into. + +Cost functions arising from quantum gates +----------------------------------------- +We start our investigation by considering a cost function that arises from measuring the expectation +value of an observable in a quantum state, created with a parametrized quantum operation +that depends on a single variational parameter :math:`x.` +That is, the state may be prepared by any circuit, but we will only allow a single parameter +in a single operation to enter the circuit. +For this we will use a handy gate structure that allows us to tune the complexity of the +operation — and thus of the cost function. +More concretely, we initialize a qubit register in a random state :math:`|\psi\rangle` +and apply a layer of Pauli-:math:`Z` rotations ``RZ`` to all qubits, where all rotations are parametrized by the *same* angle :math:`x.` +We then measure the expectation value of a random Hermitian observable :math:`B` in the created +state, so that our cost function overall has the form + +.. math :: + + E(x)=\langle\psi | U^\dagger(x) B U(x)|\psi\rangle. + +Here, :math:`U(x)` consists of a layer of ``RZ`` gates, + +.. math :: + + U(x)=\prod_{a=1}^N R_Z^{(a)}(x) = \prod_{a=1}^N \exp\left(-i\frac{x}{2} Z_a\right). + +Let's implement such a cost function using PennyLane. +We begin with functions that generate the random initial state :math:`|\psi\rangle` +and the random observable :math:`B` for a given number of qubits :math:`N` and a fixed seed: +""" + + +from scipy.stats import unitary_group +import numpy.random as rnd + + +def random_state(N, seed): + """Create a random state on N qubits.""" + states = unitary_group.rvs(2 ** N, random_state=rnd.default_rng(seed)) + return states[0] + + +def random_observable(N, seed): + """Create a random observable on N qubits.""" + rnd.seed(seed) + # Generate real and imaginary part separately and (anti-)symmetrize them for Hermiticity + real_part, imag_part = rnd.random((2, 2 ** N, 2 ** N)) + real_part += real_part.T + imag_part -= imag_part.T + return real_part + 1j * imag_part + + +############################################################################### +# Now let's set up a "cost function generator", namely a function that will create the +# ``cost`` function we discussed above, using :math:`|\psi\rangle` as initial state and +# measuring the expectation value of :math:`B.` This generator has the advantage that +# we can quickly create the cost function for various numbers of qubits — and therefore +# cost functions with different complexity. +# +# We will use the default qubit simulator with its JAX backend and also will rely +# on the NumPy implementation of JAX. +# To obtain precise results, we enable 64-bit ``float`` precision via the JAX config. + +import jax +from jax import numpy as np +import pennylane as qml + +jax.config.update("jax_enable_x64", True) + +def make_cost(N, seed): + """Create a cost function on N qubits with N frequencies.""" + dev = qml.device("default.qubit", wires=N) + + @jax.jit + @qml.qnode(dev, interface="jax") + def cost(x): + """Cost function on N qubits with N frequencies.""" + qml.StatePrep(random_state(N, seed), wires=dev.wires) + for w in dev.wires: + qml.RZ(x, wires=w, id="x") + return qml.expval(qml.Hermitian(random_observable(N, seed), wires=dev.wires)) + + return cost + + +############################################################################### +# We also prepare some plotting functionalities and colors: + + +import matplotlib.pyplot as plt + +# Set a plotting range on the x-axis +xlim = (-np.pi, np.pi) +X = np.linspace(*xlim, 60) +# Colors +green = "#209494" +orange = "#ED7D31" +red = "xkcd:brick red" +blue = "xkcd:cerulean" +pink = "xkcd:bright pink" + + +############################################################################### +# Now that we took care of these preparations, let's dive right into it: +# It can be shown [#GenPar]_ that :math:`E(x)` takes the form of a +# Fourier series in the variable :math:`x.` That is to say that +# +# .. math :: +# +# E(x) = a_0 + \sum_{\ell=1}^R a_{\ell}\cos(\ell x)+b_{\ell}\sin(\ell x). +# +# Here, :math:`a_{\ell}` and :math:`b_{\ell}` are the *Fourier coefficients*. +# If you would like to understand this a bit better still, have a read of +# :mod:`~.pennylane.fourier` and remember to check out the +# :doc:`Fourier module tutorial `. +# +# Due to :math:`B` being Hermitian, :math:`E(x)` is a real-valued function, so +# only positive frequencies and real coefficients appear in the Fourier series for :math:`E(x).` +# This is true for any number of qubits (and therefore ``RZ`` gates) we use. +# +# Using our function ``make_cost`` from above, we create the cost function for several +# numbers of qubits and store both the function and its evaluations on the plotting range ``X``. + + +# Qubit numbers +Ns = [1, 2, 4, 5] +# Fix a seed +seed = 7658741 + +cost_functions = [] +evaluated_cost = [] +for N in Ns: + # Generate the cost function for N qubits and evaluate it + cost = make_cost(N, seed) + evaluated_cost.append([cost(x) for x in X]) + cost_functions.append(cost) + + +############################################################################### +# Let's take a look at the created :math:`E(x)` for the various numbers of qubits: + + +# Figure with multiple axes +fig, axs = plt.subplots(1, len(Ns), figsize=(12, 2)) + +for ax, N, E in zip(axs, Ns, evaluated_cost): + # Plot cost function evaluations + ax.plot(X, E, color=green) + # Axis and plot labels + ax.set_title(f"{N} qubits") + ax.set_xlabel("$x$") + +_ = axs[0].set_ylabel("$E$") +plt.show() + + +############################################################################### +# +# | +# +# Indeed we see that :math:`E(x)` is a periodic function whose complexity grows when increasing +# :math:`N` together with the number of ``RZ`` gates. +# To take a look at the frequencies that are present in these functions, we may use +# PennyLane's :mod:`~.pennylane.fourier` module. +# +# .. note :: +# +# The analysis tool :func:`~.pennylane.fourier.qnode_spectrum` makes use of the internal +# structure of the :class:`~.pennylane.QNode` that encodes the cost function. +# As we used the ``jax.jit`` decorator when defining the cost function above, we +# here need to pass the wrapped function to ``qnode_spectrum``, which is stored in +# ``cost_function.__wrapped__``. + + +from pennylane.fourier import qnode_spectrum + +spectra = [] +for N, cost_function in zip(Ns, cost_functions): + # Compute spectrum with respect to parameter x + spec = qnode_spectrum(cost_function.__wrapped__)(X[0])["x"][()] + print(f"For {N} qubits the spectrum is {spec}.") + # Store spectrum + spectra.append([freq for freq in spec if freq>0.0]) + + +############################################################################### +# The number of positive frequencies that appear in :math:`E(x)` is the same as the +# number of ``RZ`` gates we used in the circuit! Recall that we only need to consider +# the positive frequencies because :math:`E(x)` is real-valued, and that we accounted for +# the zero-frequency contribution in the coefficient :math:`a_0.` +# If you are interested why the number of gates coincides with the number of frequencies, +# check out the :doc:`Fourier module tutorial `. +# +# Before moving on, let's also have a look at the Fourier coefficients in the functions +# we created: + + +from pennylane.fourier.visualize import bar + +fig, axs = plt.subplots(2, len(Ns), figsize=(12, 4.5)) +for i, (cost_function, spec) in enumerate(zip(cost_functions, spectra)): + # Compute the Fourier coefficients + coeffs = qml.fourier.coefficients(cost_function, 1, len(spec)+2) + # Show the Fourier coefficients + bar(coeffs, 1, axs[:, i], show_freqs=True, colour_dict={"real": green, "imag": orange}) + axs[0, i].set_title(f"{Ns[i]} qubits") + # Set x-axis labels + axs[1, i].text(Ns[i] + 2, axs[1, i].get_ylim()[0], f"Frequency", ha="center", va="top") + # Clean up y-axis labels + if i == 0: + _ = [axs[j, i].set_ylabel(lab) for j, lab in enumerate(["$a_\ell/2$", "$b_\ell/2$"])] + else: + _ = [axs[j, i].set_ylabel("") for j in [0, 1]] +plt.show() + + +############################################################################### +# We find the real (imaginary) Fourier coefficients to be (anti-)symmetric. +# This is expected because :math:`E(x)` is real-valued and we again see why it is enough +# to consider positive frequencies: the coefficients of the negative frequencies follow +# from those of the positive frequencies. +# +# Determining the full dependence on :math:`x` +# -------------------------------------------- +# +# Next we will show how to determine the *full* dependence of the cost function on :math:`x,` +# i.e., we will *reconstruct* :math:`E(x).` +# The key idea is not new: Since :math:`E(x)` is periodic with known, integer frequencies, we can +# reconstruct it *exactly* by using trigonometric interpolation. +# For this, we evaluate :math:`E` at shifted positions :math:`x_\mu.` +# We will show the reconstruction both for *equidistant* and random shifts, corresponding to a +# `uniform `_ and a +# `non-uniform `_ +# discrete Fourier transform (DFT), respectively. +# +# Equidistant shifts +# ^^^^^^^^^^^^^^^^^^ +# +# For the equidistant case we can directly implement the trigonometric interpolation: +# +# .. math :: +# +# x_\mu &= \frac{2\mu\pi}{2R+1}\\ +# E(x) &=\sum_{\mu=-R}^R E\left(x_\mu\right) \frac{\sin\left(\frac{2R+1}{2}(x-x_\mu)\right)} {(2R+1)\sin \left(\frac{1}{2} (x-x_\mu)\right)},\\ +# +# where we reformulated :math:`E` in the second expression using the +# `sinc function `__ to enhance the numerical +# stability. Note that we have to take care of a rescaling factor of :math:`\pi` between +# this definition of :math:`\operatorname{sinc}` and the NumPy implementation ``np.sinc`.` +# +# .. note :: +# +# When implementing :math:`E,` we will replace +# +# .. math :: +# +# \frac{\sin\left(\frac{2R+1}{2}(x-x_\mu)\right)} {(2R+1)\sin \left(\frac{1}{2} (x-x_\mu)\right)} +# +# by +# +# .. math :: +# +# \frac{\operatorname{sinc}\left(\frac{2R+1}{2}(x-x_\mu)\right)} {\operatorname{sinc} \left(\frac{1}{2} (x-x_\mu)\right)} +# +# where the sinc function is defined as :math:`\operatorname{sinc}(x)=\sin(x)/x.` +# This enhances the numerical stability since :math:`\operatorname{sinc}(0)=1,` so that the +# denominator does no longer vanish at the shifted points. +# Note that we have to take care of a rescaling factor of :math:`\pi` +# between this definition of :math:`\operatorname{sinc}` and the NumPy implementation +# ``np.sinc``. + + +sinc = lambda x: np.sinc(x / np.pi) + +def full_reconstruction_equ(fun, R): + """Reconstruct a univariate function with up to R frequencies using equidistant shifts.""" + # Shift angles for the reconstruction + shifts = [2 * mu * np.pi / (2 * R + 1) for mu in range(-R, R + 1)] + # Shifted function evaluations + evals = np.array([fun(shift) for shift in shifts]) + + @jax.jit + def reconstruction(x): + """Univariate reconstruction using equidistant shifts.""" + kernels = np.array( + [sinc((R + 0.5) * (x - shift)) / sinc(0.5 * (x - shift)) for shift in shifts] + ) + return np.dot(evals, kernels) + + return reconstruction + +reconstructions_equ = list(map(full_reconstruction_equ, cost_functions, Ns)) + + +############################################################################### +# So how is this reconstruction doing? We will plot it along with the original function +# :math:`E,` mark the shifted evaluation points :math:`x_\mu` (with crosses), and also show +# its deviation from :math:`E(x)` (lower plots). +# For this, a function for the whole procedure of comparing the functions comes in handy, and +# we will reuse it further below. For convenience, showing the deviation will be an optional +# feature controled by the ``show_diff`` keyword argument. + + +def compare_functions(originals, reconstructions, Ns, shifts, show_diff=True): + """Plot two sets of functions next to each other and show their difference (in pairs).""" + # Prepare the axes; we need fewer axes if we don't show the deviations + if show_diff: + fig, axs = plt.subplots(2, len(originals), figsize=(12, 4.5)) + else: + fig, axs = plt.subplots(1, len(originals), figsize=(12, 2)) + _axs = axs[0] if show_diff else axs + + # Run over the functions and reconstructions + for i, (orig, recon, N, _shifts) in enumerate(zip(originals, reconstructions, Ns, shifts)): + # Evaluate the original function and its reconstruction over the plotting range + E = np.array(list(map(orig, X))) + E_rec = np.array(list(map(recon, X))) + # Evaluate the original function at the positions used in the reconstruction + E_shifts = np.array(list(map(orig, _shifts))) + + # Show E, the reconstruction, and the shifts (top axes) + _axs[i].plot(X, E, lw=2, color=orange) + _axs[i].plot(X, E_rec, linestyle=":", lw=3, color=green) + _axs[i].plot(_shifts, E_shifts, ls="", marker="x", c=red) + # Manage plot titles and xticks + _axs[i].set_title(f"{N} qubits") + + if show_diff: + # [Optional] Show the reconstruction deviation (bottom axes) + axs[1, i].plot(X, E - E_rec, color=blue) + axs[1, i].set_xlabel("$x$") + # Hide the xticks of the top x-axes if we use the bottom axes + _axs[i].set_xticks([]) + + # Manage y-axis labels + _ = _axs[0].set_ylabel("$E$") + if show_diff: + _ = axs[1, 0].set_ylabel("$E-E_{rec}$") + + return fig, axs + +equ_shifts = [[2 * mu * np.pi / (2 * N + 1) for mu in range(-N, N + 1)] for N in Ns] +fig, axs = compare_functions(cost_functions, reconstructions_equ, Ns, equ_shifts) +plt.show() + + +############################################################################### +# *It works!* +# +# Non-equidistant shifts +# ^^^^^^^^^^^^^^^^^^^^^^ +# +# Now let's test the reconstruction with less regular sampling points on which to evaluate +# :math:`E.` This means we can no longer use the closed-form expression from above, but switch +# to solving the set of equations +# +# .. math :: +# +# E(x_\mu) = a_0 + \sum_{\ell=1}^R a_{\ell}\cos(\ell x_\mu)+b_{\ell}\sin(\ell x_\mu) +# +# with the—now irregular—sampling points :math:`x_\mu.` +# For this, we set up the matrix +# +# .. math :: +# +# C_{\mu\ell} = \begin{cases} +# 1 &\text{ if } \ell=0\\ +# \cos(\ell x_\mu) &\text{ if } 1\leq\ell\leq R\\ +# \sin(\ell x_\mu) &\text{ if } R<\ell\leq 2R, +# \end{cases} +# +# collect the Fourier coefficients of :math:`E` into the vector +# :math:`\boldsymbol{W}=(a_0, \boldsymbol{a}, \boldsymbol{b}),` and the evaluations of :math:`E` +# into another vector called :math:`\boldsymbol{E}` so that +# +# .. math :: +# +# \boldsymbol{E} = C \boldsymbol{W} \Rightarrow \boldsymbol{W} = C^{-1}\boldsymbol{E}. +# +# Let's implement this right away! We will take the function and the shifts :math:`x_\mu` as +# inputs, inferring :math:`R` from the number of the provided shifts, which is :math:`2R+1.` + + +def full_reconstruction_gen(fun, shifts): + """Reconstruct a univariate trigonometric function using arbitrary shifts.""" + R = (len(shifts) - 1) // 2 + frequencies = np.array(list(range(1, R + 1))) + + # Construct the matrix C case by case + C1 = np.ones((2 * R + 1, 1)) + C2 = np.cos(np.outer(shifts, frequencies)) + C3 = np.sin(np.outer(shifts, frequencies)) + C = np.hstack([C1, C2, C3]) + + # Evaluate the function to reconstruct at the shifted positions + evals = np.array(list(map(fun, shifts))) + + # Solve the system of linear equations by inverting C + W = np.linalg.inv(C) @ evals + + # Extract the Fourier coefficients + a0 = W[0] + a = W[1 : R + 1] + b = W[R + 1 :] + + # Construct the Fourier series + @jax.jit + def reconstruction(x): + """Univariate reconstruction based on arbitrary shifts.""" + return a0 + np.dot(a, np.cos(frequencies * x)) + np.dot(b, np.sin(frequencies * x)) + + return reconstruction + + +############################################################################### +# To see this version of the reconstruction in action, we will sample the +# shifts :math:`x_\mu` at random in :math:`[-\pi,\pi):` + + +shifts = [rnd.random(2 * N + 1) * 2 * np.pi - np.pi for N in Ns] +reconstructions_gen = list(map(full_reconstruction_gen, cost_functions, shifts)) +fig, axs = compare_functions(cost_functions, reconstructions_gen, Ns, shifts) +plt.show() + + +############################################################################### +# Again, we obtain a perfect reconstruction of :math:`E(x)` up to numerical errors. +# We see that the deviation from the original cost function became larger than for equidistant +# shifts for some of the qubit numbers but it still remains much smaller than any energy scale of +# relevance in applications. +# The reason for these larger deviations is that some evaluation positions :math:`x_\mu` were sampled +# very close to each other, so that inverting the matrix :math:`C` becomes less stable numerically. +# Conceptually, we see that the reconstruction does *not* rely on equidistant evaluations points. +# +# .. note :: +# +# For some applications, the number of frequencies :math:`R` is not known exactly but an upper +# bound for :math:`R` might be available. In this case, it is very useful that a reconstruction +# that assumes *too many* frequencies in :math:`E(x)` works perfectly fine. +# However, it has the disadvantage of spending too many evaluations on the reconstruction, +# and the number of required measurements, which is meaningful for the (time) +# complexity of quantum algorithms, does so as well! +# +# Differentiation via reconstructions +# ----------------------------------- +# +# Next, we look at a modified reconstruction strategy that only obtains the odd or even part of +# :math:`E(x).` This can be done by slightly modifying the shifted positions at which we +# evaluate :math:`E` and the kernel functions. +# +# From a perspective of implementing the derivatives there are two approaches, differing in +# which parts we derive on paper and which we leave to the computer: +# In the first approach, we perform a partial reconstruction using the evaluations of the +# original cost function :math:`E` on the quantum computer, as detailed below. +# This gives us a function implemented in ``jax.numpy`` and we may afterwards apply +# ``jax.grad`` to this function and obtain the derivative function. :math:`E(0)` then is only +# one evaluation of this function away. +# In the second approach, we compute the derivative of the partial reconstructions *manually* and +# directly implement the resulting shift rule that multiplies the quantum computer evaluations with +# coefficients and sums them up. This means that the partial reconstruction is not performed at +# all by the classical computer, but only was used on paper to derive the formula for the +# derivative. +# +# *Why do we look at both approaches?*, you might ask. That is because neither of them is +# better than the other for *all* applications. +# The first approach offers us derivatives of any order without additional manual work by +# iteratively applying ``jax.grad``, which is very convenient. +# However, the automatic differentiation via JAX becomes increasingly expensive +# with the order and we always reconstruct the *same* type of function, namely Fourier series, +# so that computing the respective derivatives once manually and coding up the resulting +# coefficients of the parameter-shift rule pays off in the long run. This is the strength of the +# second approach. +# We start with the first approach. +# +# Automatically differentiated reconstructions +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# We implement the partial reconstruction method as a function; using PennyLane's +# automatic differentiation backends, this then enables us to obtain the derivatives at the point +# of interest. For odd-order derivatives, we use the reconstruction of the odd part, for the +# even-order derivatives that of the even part. +# +# We make use of modified `Dirichlet kernels `_ +# :math:`\tilde{D}_\mu(x)` and equidistant shifts for this. For the odd reconstruction we have +# +# .. math :: +# +# E_\text{odd}(x) &= \sum_{\mu=1}^R E_\text{odd}(x_\mu) \tilde{D}_\mu(x)\\ +# \tilde{D}_\mu(x) &= \frac{\sin(R (x-x_\mu))}{2R \tan\left(\frac{1}{2} (x-x_\mu)\right)} - \frac{\sin(R (x+x_\mu))}{2R \tan\left(\frac{1}{2} (x+x_\mu)\right)}, +# +# which we can implement using the reformulation +# +# .. math :: +# +# \frac{\sin(X)}{\tan(Y)}=\frac{X}{Y}\frac{\operatorname{sinc}(X)}{\operatorname{sinc}(Y)}\cos(Y) +# +# for the kernel. + + +shifts_odd = lambda R: [(2 * mu - 1) * np.pi / (2 * R) for mu in range(1, R + 1)] +# Odd linear combination of Dirichlet kernels +D_odd = lambda x, R: np.array( + [ + ( + sinc(R * (x - shift)) / sinc(0.5 * (x - shift)) * np.cos(0.5 * (x - shift)) + - sinc(R * (x + shift)) / sinc(0.5 * (x + shift)) * np.cos(0.5 * (x + shift)) + ) + for shift in shifts_odd(R) + ] +) + + +def odd_reconstruction_equ(fun, R): + """Reconstruct the odd part of an ``R``-frequency input function via equidistant shifts.""" + evaluations = np.array([(fun(shift) - fun(-shift)) / 2 for shift in shifts_odd(R)]) + + @jax.jit + def reconstruction(x): + """Odd reconstruction based on equidistant shifts.""" + return np.dot(evaluations, D_odd(x, R)) + + return reconstruction + + +odd_reconstructions = list(map(odd_reconstruction_equ, cost_functions, Ns)) + + +############################################################################### +# The even part on the other hand takes the form +# +# .. math :: +# +# E_\text{even}(x) &= \sum_{\mu=0}^R E_\text{even}(x_\mu) \hat{D}_\mu(x)\\ +# \hat{D}_\mu(x) &= +# \begin{cases} +# \frac{\sin(Rx)}{2R \tan(x/2)} &\text{if } \mu = 0 \\ +# \frac{\sin(R (x-x_\mu))}{2R \tan\left(\frac{1}{2} (x-x_\mu)\right)} + \frac{\sin(R (x+x_\mu))}{2R \tan\left(\frac{1}{2} (x+x_\mu)\right)} & \text{if } \mu \in [R-1] \\ +# \frac{\sin(R (x-\pi))}{2R \tan\left(\frac{1}{2} (x-\pi)\right)} & \text{if } \mu = R. +# \end{cases} +# +# Note that not only the kernels :math:`\hat{D}_\mu(x)` but also the shifted positions +# :math:`\{x_\mu\}` differ between the odd and even case. + + +shifts_even = lambda R: [mu * np.pi / R for mu in range(1, R)] +# Even linear combination of Dirichlet kernels +D_even = lambda x, R: np.array( + [ + ( + sinc(R * (x - shift)) / sinc(0.5 * (x - shift)) * np.cos(0.5 * (x - shift)) + + sinc(R * (x + shift)) / sinc(0.5 * (x + shift)) * np.cos(0.5 * (x + shift)) + ) + for shift in shifts_even(R) + ] +) +# Special cases of even kernels +D0 = lambda x, R: sinc(R * x) / (sinc(x / 2)) * np.cos(x / 2) +Dpi = lambda x, R: sinc(R * (x - np.pi)) / sinc((x - np.pi) / 2) * np.cos((x - np.pi) / 2) + + +def even_reconstruction_equ(fun, R): + """Reconstruct the even part of ``R``-frequency input function via equidistant shifts.""" + _evaluations = np.array([(fun(shift) + fun(-shift)) / 2 for shift in shifts_even(R)]) + evaluations = np.array([fun(0), *_evaluations, fun(np.pi)]) + kernels = lambda x: np.array([D0(x, R), *D_even(x, R), Dpi(x, R)]) + + @jax.jit + def reconstruction(x): + """Even reconstruction based on equidistant shifts.""" + return np.dot(evaluations, kernels(x)) + + return reconstruction + + +even_reconstructions = list(map(even_reconstruction_equ, cost_functions, Ns)) + + +############################################################################### +# We also set up a function that performs both partial reconstructions and sums the resulting +# functions to the full Fourier series. + + +def summed_reconstruction_equ(fun, R): + """Sum an odd and an even reconstruction into the full function.""" + _odd_part = odd_reconstruction_equ(fun, R) + _even_part = even_reconstruction_equ(fun, R) + + def reconstruction(x): + """Full function based on separate odd/even reconstructions.""" + return _odd_part(x) + _even_part(x) + + return reconstruction + + +summed_reconstructions = list(map(summed_reconstruction_equ, cost_functions, Ns)) + + +############################################################################### +# We show these even (blue) and odd (red) reconstructions and how they indeed +# sum to the full function (orange, dashed). +# We will again use the ``compare_functions`` utility from above for the comparison. + +from matplotlib.lines import Line2D + +# Obtain the shifts for the reconstruction of both parts +odd_and_even_shifts = [ + ( + shifts_odd(R) + + shifts_even(R) + + list(-1 * np.array(shifts_odd(R))) + + list(-1 * np.array(shifts_odd(R))) + + [0, np.pi] + ) + for R in Ns +] + +# Show the reconstructed parts and the sums +fig, axs = compare_functions(cost_functions, summed_reconstructions, Ns, odd_and_even_shifts) +for i, (odd_recon, even_recon) in enumerate(zip(odd_reconstructions, even_reconstructions)): + # Odd part + E_odd = np.array(list(map(odd_recon, X))) + axs[0, i].plot(X, E_odd, color=red) + # Even part + E_even = np.array(list(map(even_recon, X))) + axs[0, i].plot(X, E_even, color=blue) + axs[0, i].set_title('') +_ = axs[1, 0].set_ylabel("$E-(E_{odd}+E_{even})$") +colors = [green, red, blue, orange] +styles = ['-', '-', '-', '--'] +handles = [Line2D([0], [0], color=c, ls=ls, lw=1.2) for c, ls in zip(colors, styles)] +labels = ['Original', 'Odd reconstruction', 'Even reconstruction', 'Summed reconstruction'] +_ = fig.legend(handles, labels, bbox_to_anchor=(0.2, 0.89), loc='lower left', ncol=4) +plt.show() + + +############################################################################### +# Great! The even and odd part indeed sum to the correct function again. But what did we +# gain? +# +# Nothing, actually, for the full reconstruction! Quite the opposite, we spent :math:`2R` +# evaluations of :math:`E` on each part, that is :math:`4R` evaluations overall to obtain a +# description of the full function :math:`E!` This is way more than the :math:`2R+1` +# evaluations needed for the full reconstructions from the beginning. +# +# However, remember that we set out to compute derivatives of :math:`E` at :math:`0,` so that +# for derivatives of odd/even order only the odd/even reconstruction is required. +# Using an autodifferentiation framework, e.g. JAX, we can easily compute such higher-order +# derivatives: + + +# An iterative function computing the ``order``th derivative of a function ``f`` with JAX +grad_gen = lambda f, order: grad_gen(jax.grad(f), order - 1) if order > 0 else f + +# Compute the first, second, and fourth derivative +for order, name in zip([1, 2, 4], ["First", "Second", "4th"]): + recons = odd_reconstructions if order % 2 else even_reconstructions + recon_name = "odd " if order % 2 else "even" + cost_grads = np.array([grad_gen(orig, order)(0.0) for orig in cost_functions]) + recon_grads = np.array([grad_gen(recon, order)(0.0) for recon in recons]) + all_equal = ( + "All entries match" if np.allclose(cost_grads, recon_grads) else "Some entries differ!" + ) + print(f"{name} derivatives via jax: {all_equal}") + print("From the cost functions: ", np.round(np.array(cost_grads), 6)) + print(f"From the {recon_name} reconstructions: ", np.round(np.array(recon_grads), 6), "\n") + + +############################################################################### +# The derivatives coincide. +# +# .. note :: +# +# While we used the :math:`2R+1` evaluations :math:`x_\mu=\frac{2\mu\pi}{2R+1}` for the full +# reconstruction, derivatives only require :math:`2R` calls to the respective circuit. +# Also note that the derivatives can be computed at any position :math:`x_0` other than +# :math:`0` by simply reconstructing the function :math:`E(x+x_0),` which again will be +# a Fourier series like :math:`E(x).` +# +# Generalized parameter-shift rules +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# The second method is based on the previous one. Instead of consulting JAX, we may compute +# the wanted derivative of the odd/even kernel function manually and thus derive general +# parameter-shift rules from this. We will leave the technical derivation of these rules +# to the paper [#GenPar]_. Start with the first derivative, which certainly is used the most: +# +# .. math :: +# +# E'(0) = \sum_{\mu=1}^{2R} E\left(\frac{2\mu-1}{2R}\pi\right) \frac{(-1)^{\mu-1}}{4R\sin^2\left(\frac{2\mu-1}{4R}\pi\right)}, +# +# This is straight-forward to implement by defining the coefficients and evaluating +# :math:`E` at the shifted positions :math:`x_\mu:` + + +def parameter_shift_first(fun, R): + """Compute the first-order derivative of a function with R frequencies at 0.""" + shifts = (2 * np.arange(1, 2 * R + 1) - 1) * np.pi / (4 * R) + # Classically computed coefficients + coeffs = np.array( + [(-1) ** mu / (4 * R * np.sin(shift) ** 2) for mu, shift in enumerate(shifts)] + ) + # Evaluations of the cost function E(x_mu) + evaluations = np.array(list(map(fun, 2 * shifts))) + # Contract coefficients with evaluations + return np.dot(coeffs, evaluations) + +ps_der1 = list(map(parameter_shift_first, cost_functions, Ns)) + + +############################################################################### +# The second-order derivative takes a similar form, but we have to take care of the evaluation at +# :math:`0` and the corresponding coefficient separately: +# +# .. math :: +# +# E''(0) = -E(0)\frac{2R^2+1}{6} - \sum_{\mu=1}^{2R-1} E\left(\frac{\mu\pi}{R}\right)\frac{(-1)^\mu}{2\sin^2 \left(\frac{\mu\pi}{2R}\right)}. +# +# Let's code this up, again we only get slight complications from the special evaluation +# at :math:`0:` + + +def parameter_shift_second(fun, R): + """Compute the second-order derivative of a function with R frequencies at 0.""" + shifts = np.arange(1, 2 * R) * np.pi / (2 * R) + # Classically computed coefficients for the main sum + _coeffs = [(-1) ** mu / (2 * np.sin(shift) ** 2) for mu, shift in enumerate(shifts)] + # Include the coefficients for the "special" term E(0). + coeffs = np.array([-(2 * R ** 2 + 1) / 6] + _coeffs) + # Evaluate at the regularily shifted positions + _evaluations = list(map(fun, 2 * shifts)) + # Include the "special" term E(0). + evaluations = np.array([fun(0.0)] + _evaluations) + # Contract coefficients with evaluations. + return np.dot(coeffs, evaluations) + +ps_der2 = list(map(parameter_shift_second, cost_functions, Ns)) + + +############################################################################### +# We will compare these two shift rules to the finite-difference derivative commonly used for +# numerical differentiation. We choose a finite difference of :math:`d_x=5\times 10^{-5}.` + +dx = 5e-5 + +def finite_diff_first(fun): + """Compute the first order finite difference derivative.""" + return (fun(dx/2) - fun(-dx/2))/dx + +fd_der1 = list(map(finite_diff_first, cost_functions)) + +def finite_diff_second(fun): + """Compute the second order finite difference derivative.""" + fun_p, fun_0, fun_m = fun(dx), fun(0.0), fun(-dx) + return ((fun_p - fun_0)/dx - (fun_0 - fun_m)/dx) /dx + +fd_der2 = list(map(finite_diff_second, cost_functions)) + +############################################################################### +# All that is left is to compare the computed parameter-shift and finite-difference +# derivatives: + +print("Number of qubits/RZ gates: ", *Ns, sep=" " * 9) +print(f"First-order parameter-shift rule: {np.round(np.array(ps_der1), 6)}") +print(f"First-order finite difference: {np.round(np.array(fd_der1), 6)}") +print(f"Second-order parameter-shift rule: {np.round(np.array(ps_der2), 6)}") +print(f"Second-order finite difference: {np.round(np.array(fd_der2), 6)}") + +############################################################################### +# The parameter-shift rules work as expected! And we were able to save +# a circuit evaluation as compared to a full reconstruction. +# +# And this is all we want to show here about univariate function reconstructions and generalized +# parameter shift rules. +# Note that the techniques above can partially be extended to frequencies that are not +# integer-valued, but many closed form expressions are no longer valid. +# For the reconstruction, the approach via Dirichlet kernels no longer works in the general +# case; instead, a system of equations has to be solved, but with generalized +# frequencies :math:`\{\Omega_\ell\}` instead of :math:`\{\ell\}` (see e.g. +# Sections III A-C in [#GenPar]_) +# +# +# References +# ---------- +# +# .. [#GenPar] +# +# David Wierichs, Josh Izaac, Cody Wang, Cedric Yen-Yu Lin. +# "General parameter-shift rules for quantum gradients". +# `arXiv preprint arXiv:2107.12390 `__. +# +# .. [#CalcPQC] +# +# Javier Gil Vidal, Dirk Oliver Theis. "Calculus on parameterized quantum circuits". +# `arXiv preprint arXiv:1812.06323 `__. +# +# .. [#Rotosolve] +# +# Mateusz Ostaszewski, Edward Grant, Marcello Benedetti. +# "Structure optimization for parameterized quantum circuits". +# `arXiv preprint arXiv:1905.09692 `__. +# +# .. [#AlgeShift] +# +# Artur F. Izmaylov, Robert A. Lang, Tzu-Ching Yen. +# "Analytic gradients in variational quantum algorithms: Algebraic extensions of the parameter-shift rule to general unitary transformations". +# `arXiv preprint arXiv:2107.08131 `__. +# +# .. [#GenDiffRules] +# +# Oleksandr Kyriienko, Vincent E. Elfving. +# "Generalized quantum circuit differentiation rules". +# `arXiv preprint arXiv:2108.01218 `__. +# +# .. |brute| replace:: ``brute`` +# .. _brute: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brute.html +# +# .. |shgo| replace:: ``shgo`` +# .. _shgo: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.shgo.html +# +# .. |Rotosolve_code| replace:: ``qml.RotosolveOptimizer`` +# .. _Rotosolve_code: https://pennylane.readthedocs.io/en/stable/code/api/pennylane.RotosolveOptimizer.html + +############################################################################## diff --git a/demonstrations_v2/tutorial_general_parshift/metadata.json b/demonstrations_v2/tutorial_general_parshift/metadata.json new file mode 100644 index 0000000000..d411e7cf26 --- /dev/null +++ b/demonstrations_v2/tutorial_general_parshift/metadata.json @@ -0,0 +1,90 @@ +{ + "title": "Generalized parameter-shift rules", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2021-08-23T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generalized_parametershift_rules.png" + } + ], + "seoDescription": "Reconstruct quantum functions and compute their derivatives.", + "doi": "", + "references": [ + { + "id": "GenPar", + "type": "article", + "title": "General parameter-shift rules for quantum gradients", + "authors": "David Wierichs, Josh Izaac, Cody Wang, Cedric Yen-Yu Lin", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2107.12390" + }, + { + "id": "CalcPQC", + "type": "article", + "title": "Calculus on parameterized quantum circuits", + "authors": "Javier Gil Vidal, Dirk Oliver Theis", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1812.06323" + }, + { + "id": "Rotosolve", + "type": "article", + "title": "Structure optimization for parameterized quantum circuits", + "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Benedetti", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1905.09692" + }, + { + "id": "AlgeShift", + "type": "article", + "title": "Analytic gradients in variational quantum algorithms: Algebraic extensions of the parameter-shift rule to general unitary transformations", + "authors": "Artur F. Izmaylov, Robert A. Lang, Tzu-Ching Yen", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2107.08131" + }, + { + "id": "GenDiffRules", + "type": "article", + "title": "Generalized quantum circuit differentiation rules", + "authors": "Oleksandr Kyriienko, Vincent E. Elfving", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2108.01218" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2107.12390" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rotoselect", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_analytic_descent", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_general_parshift/requirements.in b/demonstrations_v2/tutorial_general_parshift/requirements.in new file mode 100644 index 0000000000..76ab11da75 --- /dev/null +++ b/demonstrations_v2/tutorial_general_parshift/requirements.in @@ -0,0 +1,6 @@ +jax +jaxlib +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_geometric_qml/demo.py b/demonstrations_v2/tutorial_geometric_qml/demo.py new file mode 100644 index 0000000000..65becb2126 --- /dev/null +++ b/demonstrations_v2/tutorial_geometric_qml/demo.py @@ -0,0 +1,898 @@ +r""" + +Introduction to Geometric Quantum Machine Learning +================================================== + +.. meta:: + :property="og:description": Using the natural symmetries in a quantum learning problem can improve learning + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/equivariant_thumbnail.jpeg + +.. related:: + tutorial_equivariant_graph_embedding A permutation equivariant graph embedding + +*Author: Richard East — Posted: 18 October 2022.* + + +Introduction +------------ + + +Symmetries are at the heart of physics. Indeed in condensed matter and +particle physics we often define a thing simply by the symmetries it +adheres to. What does symmetry mean for those in machine learning? In +this context the ambition is straightforward — it is a means to +reduce the parameter space and improve the trained model's ability to +sucessfully label unseen data, i.e., its ability to generalise. + + +Suppose we have a learning task and the data we are learning from has an +underlying symmetry. For example, consider a game of Noughts and +Crosses (aka Tic-tac-toe): if we win a game, we would have won it if the board was rotated +or flipped along any of the lines of symmetry. Now if we want to train +an algorithm to spot the outcome of these games, we can either ignore +the existence of this symmetry or we can somehow include it. The +advantage of paying attention to the symmetry is it identifies multiple +configurations of the board as 'the same thing' as far as the symmetry +is concerned. This means we can reduce our parameter space, and so the +amount of data our algorithm must sift through is immediately reduced. +Along the way, the fact that our learning model must encode a symmetry +that actually exists in the system we are trying to represent naturally +encourages our results to be more generalisable. The encoding of symmetries +into our learning models is where the term *equivariance* will appear. We will see that +demanding that certain symmetries are included in our models means that the +mappings that make up our algorithms must be such that we could transform our input data +with respect to a certain symmetry, then apply our mappings, and this would be the same as applying +the mappings and then transforming the output data with the same symmetry. This is the technical property +that gives us the name "equavariant learning". + +In classical machine learning, this area is often referred to as geometric deep +learning (GDL) due to the traditional association of symmetry to the +world of geometry, and the fact that these considerations usually focus on +deep neural networks (see [#Bronstein2021]_ or [#Nguyen2022]_ for a broad introduction). +We will refer to the quantum computing version of this as *quantum geometric machine learning* (QGML). + + +Representation theory in circuits +--------------------------------- + +The first thing to discuss is how do we work with symmetries in the +first place? The answer lies in the world of group representation +theory. + +First, let's define what we mean by a group: + +**Definition**: A group is a set :math:`G` together with a binary operation +on :math:`G`, here denoted :math:`\circ,` that combines any two elements +:math:`a` and :math:`b` to form an element of :math:`G,` denoted +:math:`a \circ b,` such that the following three requirements, known as +group axioms, are satisfied as follows: + + +1. **Associativity**: For all :math:`a, b, c` in :math:`G,` one has :math:`(a \circ b) \circ c=a \circ (b \circ c).` + +2. **Identity element**: There exists an element :math:`e` in :math:`G` such that, for every :math:`a` in :math:`G,` one + has :math:`e \circ a=a` and :math:`a \circ e=a.` Such an element is unique. It is called the identity element of the + group. + + +3. **Inverse element**: For each :math:`a` in :math:`G,` there exists an element :math:`b` in :math:`G` + such that :math:`a \circ b=e` and :math:`b \circ a=e,` where :math:`e` is the identity element. + For each :math:`a,` the element :math:`b` is unique: it is called the inverse of :math:`a` + and is commonly denoted :math:`a^{-1}.` + + +With groups defined, we are in a position to articulate what a +representation is: Let :math:`\varphi` be a map sending :math:`g` in group +:math:`G` to a linear map :math:`\varphi(g): V \rightarrow V,` for some +vector space :math:`V,` which satisfies + +.. math:: + \varphi\left(g_{1} g_{2}\right)=\varphi\left(g_{1}\right) \circ \varphi\left(g_{2}\right) \quad \text { for all } g_{1}, g_{2} \in G. + +The idea here is that just as elements in a group act on each other to +reach further elements, i.e., :math:`g\circ h = k,` a representation sends us +to a mapping acting on a vector space such that :math:`\varphi(g)\circ \varphi(h) = \varphi(k).` +In this way we are representing the structure of the group as a linear map. For a representation, our mapping must send us to the general linear +group :math:`GL(n)` (the space of invertible :math:`n \times n` matrices with matrix multiplication as the group multiplication). Note how this +is both a group, and by virtue of being a collection of invertible matrices, also a set of linear maps (they're all invertble matrices that can act on row vectors). +Fundamentally, representation theory is based on the prosaic observation that linear algebra is easy and group theory is abstract. So what if we can +study groups via linear maps? + +Now due to the importance of unitarity in quantum mechnics, we are +particularly interested in the unitary representations: representations +where the linear maps are unitary matrices. If we can +identify these then we will have a way to naturally encode groups in +quantum circuits (which are mostly made up of unitary gates). + +.. figure:: ../_static/demonstration_assets/geometric_qml/sphere_equivariant.png + :align: center + :width: 45% + :alt: Basic symmetries of the sphere. + +How does all this relate to symmetries? Well, a large class of +symmetries can be characterised as a group, where all the elements of the group leave +some space we are considering unchanged. Let's consider an example: +the symmetries of a sphere. Now when we think of this symmetry we +probably think something along the lines of "it's the same no matter how +we rotate it, or flip it left to right, etc". There is this idea of being +invariant under some operation. We also have the idea of being able to +undo these actions: if we rotate one way, we can rotate it back. If we +flip the sphere right-to-left we can flip it left-to-right to get back to +where we started (notice too all these inverses are unique). Trivially +we can also do nothing. What exactly are we describing here? We have +elements that correspond to an action on a sphere that can be inverted and +for which there exists an identity. It is also trivially the case here +that if we consider three operations a, b, c from the set of rotations and +reflections of the sphere, that if we combine two of them together then +:math:`a\circ (b \circ c) = (a\circ b) \circ c.` The operations are +associative. These features turn out to literally define a group! + +As we've seen the group in itself is a very abstract creature; this is why we look to +its representations. The group labels what symmetries we care about, they tell +us the mappings that our system is invariant under, and the unitary representations +show us how those symmetries look on a particular +space of unitary matrices. If we want to +encode the structure of the symmeteries in a quantum circuit we must +restrict our gates to being unitary representations of the group. + +There remains one question: *what is equivariance?* With our newfound knowledge +of group representation theory we are ready to tackle this. Let :math:`G` be our group, and +:math:`V` and :math:`W,` with elements :math:`v` and :math:`w` respectively, be vector spaces +over some field :math:`F` with a map :math:`f` between them. +Suppose we have representations :math:`\varphi: G \rightarrow GL(V)` +and :math:`\psi: G \rightarrow GL(W).` Furthermore, let's write +:math:`\varphi_g` for the representation of :math:`g` as a linear map on :math:`V` and :math:`\psi_g` +as the same group element represented as a linear map on :math:`W` respectively. We call :math:`f` *equivariant* if + +.. math:: + + f(\varphi_g(v))=\psi_g(f(v)) \quad \text { for all } g\in G. + +The importance of such a map in machine learning is that if, for example, our neural network layers are +equivariant maps then two inputs that are related by some intrinsic symmetry (maybe they are reflections) +preserve this information in the outputs. + +Consider the following figure for +example. What we see is a board with a cross in a certain square on the left and some numerical encoding of this +on the right, where the 1 is where the X is in the number grid. We present an equivariant mapping between these two spaces +with respect to a group action that is a rotation or a swap (here a :math:`\pi` rotation). We can either apply a group action to the original grid +and then map to the number grid, or we could map to the number grid and then apply the group action. +Equivariance demands that the result of either of these procedures should be the same. + + +.. figure:: ../_static/demonstration_assets/geometric_qml/equivariant-example.jpg + :align: center + :width: 80% + :alt: The commuting square of an equivariant map. + + + +Given the vast amount +of input data required to train a neural network the principle that one can pre-encode known symmetry structures +into the network allows us to learn better and faster. Indeed it is the reason for the success of convolutional neural networks (CNNs) for image +analysis, where it is known they are equivariant with respect to translations. They naturally encode the idea that +a picture of a dog is symmetrically related to the same picture slid to the left by n pixels, and they do this by having +neural network layers that are equivariant maps. With our focus on +unitary representations (and so quantum circuits) we are looking to extend this idea to quantum machine learning. + +""" + +############################################################################## +# +# Noughts and Crosses +# ------------------- +# Let's look at the game of noughts and crosses, as inspired by [#Meyer2022]_. Two players take +# turns to place a O or an X, depending on which player they are, in a 3x3 +# grid. The aim is to get three of your symbols in a row, column, or +# diagonal. As this is not always possible depending +# on the choices of the players, there could be a draw. Our learning task +# is to take a set of completed games labelled with their outcomes and +# teach the algorithm to identify these correctly. +# + + +###################################################################### +# This board of nine elements has the symmetry of the square, also known +# as the *dihedral group*. This means it is symmetric under +# :math:`\frac{\pi}{2}` rotations and flips about the lines of symmetry of +# a square (vertical, horizontal, and both diagonals). + +############################################################################## +# .. figure:: ../_static/demonstration_assets/geometric_qml/NandC_sym.png +# :align: center +# :width: 70% +# :alt: Examples of games that are equivalent under relevant symmetries. + +############################################################################## +# **The question is, how do we encode this in our QML problem?** +# +# First, let us encode this problem classically. We will consider a nine-element +# vector :math:`v,` each element of which identifies a square of +# the board. The entries themselves can be +# :math:`+1`,\ :math:`0,`\ :math:`-1,` representing a nought, no symbol, or +# a cross. The label is one-hot encoded in a vector +# :math:`y=(y_O,y_- , y_X)` with :math:`+1` in the correct label and +# :math:`-1` in the others. For instance (-1,-1,1) would represent an X in +# the relevant position. + + +###################################################################### +# To create the quantum model let us take nine qubits and let them represent squares of our board. We'll initialise them all as :math:`|0\rangle,` +# which we note leaves the board invariant under the symmetries of the problem (flip and +# rotate all you want, it's still going to be zeroes whatever your +# mapping). We will then look to apply single qubit :math:`R_x(\theta)` +# rotations on individual qubits, encoding each of the +# possibilities in the board squares at an angle of +# :math:`\frac{2\pi}{3}` from each other. For our parameterised gates we +# will have a single-qubit :math:`R_x(\theta_1)` and :math:`R_y(\theta_2)` +# rotation at each point. We will then use :math:`CR_y(\theta_3)` for two-qubit +# entangling gates. This implies that, for each encoding, crudely, we'll +# need 18 single-qubit rotation parameters and :math:`\binom{9}{2}=36` +# two-qubit gate rotations. Let's see how, by using symmetries, we can reduce +# this. + +############################################################################## +# .. figure:: ../_static/demonstration_assets/geometric_qml/grid.jpg +# :align: center +# :width: 35% +# :alt: The indexing of our game board. +# +# .. +# +# The indexing of our game board. + +###################################################################### +# The secret will be to encode the symmetries into the gate set so the +# observables we are interested in inherently respect the symmetries. How do +# we do this? We need to select the collections of gates that commute with +# the symmetries. In general, we can use the twirling formula for this: +# +# .. tip:: +# +# Let :math:`\mathcal{S}` be the group that encodes our symmetries and :math:`U` be a +# unitary representation of :math:`\mathcal{S}.` Then, +# +# .. math:: \mathcal{T}_{U}[X]=\frac{1}{|\mathcal{S}|} \sum_{s \in \mathcal{S}} U(s) X U(s)^{\dagger} +# +# defines a projector onto the set of operators commuting with all +# elements of the representation, i.e., +# :math:`\left[\mathcal{T}_{U}[X], U(s)\right]=` 0 for all :math:`X` and +# :math:`s \in \mathcal{S}.` +# +# The twirling process applied to an arbitrary unitary will give us a new unitary that commutes with the group as we require. +# We remember that unitary gates typically have the form :math:`W = \exp(-i\theta H),` where :math:`H` is a Hermitian +# matrix called a *generator*, and :math:`\theta` may be fixed or left as a free parameter. A recipe for creating a unitary +# that commutes with our symmetries is to *twirl the generator of the gate*, i.e., we move from the gate +# :math:`W = \exp(-i\theta H)` to the gate :math:`W' = \exp(-i\theta\mathcal{T}_U[H]).` +# When each term in the twirling formula acts on different qubits, then this unitary +# would further simplify to +# +# .. math:: W' = \bigotimes_{s\in\mathcal{S}}U(s)\exp(-i\tfrac{\theta}{\vert\mathcal{S}\vert})U(s)^\dagger. +# +# For simplicity, we can absorb the normalization factor :math:`\vert\mathcal{S}\vert` into the free parameter :math:`\theta.` +# +# So let's look again at our choice of gates: single-qubit +# :math:`R_x(\theta)` and :math:`R_y(\theta)` rotations, and entangling two-qubit :math:`CR_y(\phi)` +# gates. What will we get by twirling these? +# + + +###################################################################### +# In this particular instance we can see the action of the twirling +# operation geometrically as the symmetries involved are all +# permutations. Let's consider the :math:`R_x` rotation acting on one qubit. Now +# if this qubit is in the centre location on the grid, then we can flip around any symmetry axis we +# like, and this operation leaves the qubit invariant, so we've identified one equivariant +# gate immediately. If the qubit is on the corners, then the flipping will send +# this qubit rotation to each of the other corners. Similarly, if a qubit is on the central +# edge then the rotation gate will be sent round the other edges. So we can see that the +# twirling operation is a sum over all the possible outcomes of performing +# the symmetry action (the sum over the symmetry group actions). Having done this +# we can see that for a single-qubit rotation the invariant maps are rotations +# on the central qubit, at all the corners, and at all the central +# edges (when their rotation angles are fixed to be the same). +# +# As an example consider the following figure, +# where we take a :math:`R_x` gate in the corner and then apply all the symmetries +# of a square. The result of this twirling leads us to have the same gate at all the corners. + +############################################################################## +# .. figure:: ../_static/demonstration_assets/geometric_qml/twirl.jpeg +# :align: center +# :width: 70% +# :alt: The effect of twirling a rotation gate applied in one corner with the symmetries of a square. + + +###################################################################### +# For entangling gates the situation is similar. There are three invariant +# classes, the centre entangled with all corners, with all edges, and the +# edges paired in a ring. +# + + +###################################################################### +# The prediction of a label is obtained via a one-hot-encoding by measuring +# the expectation values of three invariant observables: +# + + +###################################################################### +# .. math:: +# O_{-}=Z_{\text {middle }}=Z_{4} +# +# .. math:: +# O_{\circ}=\frac{1}{4} \sum_{i \in \text { corners }} Z_{i}=\frac{1}{4}\left[Z_{0}+Z_{2}+Z_{6}+Z_{8}\right] +# +# .. math:: +# O_{\times}=\frac{1}{4} \sum_{i \in \text { edges }} Z_{i}=\frac{1}{4}\left[Z_{1}+Z_{3}+Z_{5}+Z_{7}\right] +# +# .. math:: +# \hat{\boldsymbol{y}}=\left(\left\langle O_{\circ}\right\rangle,\left\langle O_{-}\right\rangle,\left\langle O_{\times}\right\rangle\right) +# + + +###################################################################### +# This is the quantum encoding of the symmetries into a learning problem. +# A prediction for a given data point will be obtained by selecting the +# class for which the observed expectation value is the largest. + + +###################################################################### +# Now that we have a specific encoding and have decided on our observables +# we need to choose a suitable cost function to optimise. +# We will use an :math:`l_2` loss function acting on pairs of games and +# labels :math:`D={(g,y)},` where :math:`D` is our dataset. +# + + +###################################################################### +# .. :math: +# \mathcal{L}(\mathcal{D})=\frac{1}{|\mathcal{D}|} \sum_{(\boldsymbol{g}, \boldsymbol{y}) \in \mathcal{D}}\|\hat{\boldsymbol{y}}(\boldsymbol{g})-\boldsymbol{y}\|_{2}^{2} +# + + +###################################################################### +# Let's now implement this! +# +# First let's generate some games. +# Here we are creating a small program that will play Noughts and Crosses against itself in a random fashion. +# On completion, it spits out the winner and the winning board, with noughts as +1, draw as 0, and crosses as -1. +# There are 26,830 different possible games but we will only sample a few hundred. + +import torch +import random + +# Fix seeds for reproducability +torch.backends.cudnn.deterministic = True +torch.manual_seed(16) +random.seed(16) + + +# create an empty board +def create_board(): + return torch.tensor([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + + +# Check for empty places on board +def possibilities(board): + l = [] + for i in range(len(board)): + for j in range(3): + if board[i, j] == 0: + l.append((i, j)) + return l + + +# Select a random place for the player +def random_place(board, player): + selection = possibilities(board) + current_loc = random.choice(selection) + board[current_loc] = player + return board + + +# Check if there is a winner by having 3 in a row +def row_win(board, player): + for x in range(3): + lista = [] + win = True + + for y in range(3): + lista.append(board[x, y]) + + if board[x, y] != player: + win = False + + if win: + break + + return win + + +# Check if there is a winner by having 3 in a column +def col_win(board, player): + for x in range(3): + win = True + + for y in range(3): + if board[y, x] != player: + win = False + + if win: + break + + return win + + +# Check if there is a winner by having 3 along a diagonal +def diag_win(board, player): + win1 = True + win2 = True + for x, y in [(0, 0), (1, 1), (2, 2)]: + if board[x, y] != player: + win1 = False + + for x, y in [(0, 2), (1, 1), (2, 0)]: + if board[x, y] != player: + win2 = False + + return win1 or win2 + + +# Check if the win conditions have been met or if a draw has occurred +def evaluate_game(board): + winner = None + for player in [1, -1]: + if row_win(board, player) or col_win(board, player) or diag_win(board, player): + winner = player + + if torch.all(board != 0) and winner == None: + winner = 0 + + return winner + + +# Main function to start the game +def play_game(): + board, winner, counter = create_board(), None, 1 + while winner == None: + for player in [1, -1]: + board = random_place(board, player) + counter += 1 + winner = evaluate_game(board) + if winner != None: + break + + return [board.flatten(), winner] + + +def create_dataset(size_for_each_winner): + game_d = {-1: [], 0: [], 1: []} + + while min([len(v) for k, v in game_d.items()]) < size_for_each_winner: + board, winner = play_game() + if len(game_d[winner]) < size_for_each_winner: + game_d[winner].append(board) + + res = [] + for winner, boards in game_d.items(): + res += [(board, winner) for board in boards] + + return res + + +NUM_TRAINING = 450 +NUM_VALIDATION = 600 + +# Create datasets but with even numbers of each outcome +with torch.no_grad(): + dataset = create_dataset(NUM_TRAINING // 3) + dataset_val = create_dataset(NUM_VALIDATION // 3) + + +###################################################################### +# Now let's create the relevant circuit expectation values that respect +# the symmetry classes we defined over the single-site and two-site measurements. + + +import pennylane as qml +import matplotlib.pyplot as plt + +# Set up a nine-qubit system +dev = qml.device("default.qubit", wires=9) + +ob_center = qml.PauliZ(4) +ob_corner = (qml.PauliZ(0) + qml.PauliZ(2) + qml.PauliZ(6) + qml.PauliZ(8)) * (1 / 4) +ob_edge = (qml.PauliZ(1) + qml.PauliZ(3) + qml.PauliZ(5) + qml.PauliZ(7)) * (1 / 4) + + +# Now let's encode the data in the following qubit models, first with symmetry +@qml.qnode(dev) +def circuit(x, p): + + qml.RX(x[0], wires=0) + qml.RX(x[1], wires=1) + qml.RX(x[2], wires=2) + qml.RX(x[3], wires=3) + qml.RX(x[4], wires=4) + qml.RX(x[5], wires=5) + qml.RX(x[6], wires=6) + qml.RX(x[7], wires=7) + qml.RX(x[8], wires=8) + + # Centre single-qubit rotation + qml.RX(p[0], wires=4) + qml.RY(p[1], wires=4) + + # Corner single-qubit rotation + qml.RX(p[2], wires=0) + qml.RX(p[2], wires=2) + qml.RX(p[2], wires=6) + qml.RX(p[2], wires=8) + + qml.RY(p[3], wires=0) + qml.RY(p[3], wires=2) + qml.RY(p[3], wires=6) + qml.RY(p[3], wires=8) + + # Edge single-qubit rotation + qml.RX(p[4], wires=1) + qml.RX(p[4], wires=3) + qml.RX(p[4], wires=5) + qml.RX(p[4], wires=7) + + qml.RY(p[5], wires=1) + qml.RY(p[5], wires=3) + qml.RY(p[5], wires=5) + qml.RY(p[5], wires=7) + + # Entagling two-qubit gates + # circling the edge of the board + qml.CRY(p[6], wires=[0, 1]) + qml.CRY(p[6], wires=[2, 1]) + qml.CRY(p[6], wires=[2, 5]) + qml.CRY(p[6], wires=[8, 5]) + qml.CRY(p[6], wires=[8, 7]) + qml.CRY(p[6], wires=[6, 7]) + qml.CRY(p[6], wires=[6, 3]) + qml.CRY(p[6], wires=[0, 3]) + + # To the corners from the centre + qml.CRY(p[7], wires=[4, 0]) + qml.CRY(p[7], wires=[4, 2]) + qml.CRY(p[7], wires=[4, 6]) + qml.CRY(p[7], wires=[4, 8]) + + # To the centre from the edges + qml.CRY(p[8], wires=[1, 4]) + qml.CRY(p[8], wires=[3, 4]) + qml.CRY(p[8], wires=[5, 4]) + qml.CRY(p[8], wires=[7, 4]) + + return [qml.expval(ob_center), qml.expval(ob_corner), qml.expval(ob_edge)] + + +fig, ax = qml.draw_mpl(circuit)([0] * 9, 18 * [0]) + +###################################################################### +# Let's also look at the same series of gates but this time they +# are applied independently from one another, so we won't be preserving +# the symmetries with our gate operations. Practically this also means +# more parameters, as previously groups of gates were updated together. + + +@qml.qnode(dev) +def circuit_no_sym(x, p): + + qml.RX(x[0], wires=0) + qml.RX(x[1], wires=1) + qml.RX(x[2], wires=2) + qml.RX(x[3], wires=3) + qml.RX(x[4], wires=4) + qml.RX(x[5], wires=5) + qml.RX(x[6], wires=6) + qml.RX(x[7], wires=7) + qml.RX(x[8], wires=8) + + # Centre single-qubit rotation + qml.RX(p[0], wires=4) + qml.RY(p[1], wires=4) + + # Note in this circuit the parameters aren't all the same. + # Previously they were identical to ensure they were applied + # as one combined gate. The fact they can all vary independently + # here means we aren't respecting the symmetry. + + # Corner single-qubit rotation + qml.RX(p[2], wires=0) + qml.RX(p[3], wires=2) + qml.RX(p[4], wires=6) + qml.RX(p[5], wires=8) + + qml.RY(p[6], wires=0) + qml.RY(p[7], wires=2) + qml.RY(p[8], wires=6) + qml.RY(p[9], wires=8) + + # Edge single-qubit rotation + qml.RX(p[10], wires=1) + qml.RX(p[11], wires=3) + qml.RX(p[12], wires=5) + qml.RX(p[13], wires=7) + + qml.RY(p[14], wires=1) + qml.RY(p[15], wires=3) + qml.RY(p[16], wires=5) + qml.RY(p[17], wires=7) + + # Entagling two-qubit gates + # circling the edge of the board + qml.CRY(p[18], wires=[0, 1]) + qml.CRY(p[19], wires=[2, 1]) + qml.CRY(p[20], wires=[2, 5]) + qml.CRY(p[21], wires=[8, 5]) + qml.CRY(p[22], wires=[8, 7]) + qml.CRY(p[23], wires=[6, 7]) + qml.CRY(p[24], wires=[6, 3]) + qml.CRY(p[25], wires=[0, 3]) + + # To the corners from the centre + qml.CRY(p[26], wires=[4, 0]) + qml.CRY(p[27], wires=[4, 2]) + qml.CRY(p[28], wires=[4, 6]) + qml.CRY(p[29], wires=[4, 8]) + + # To the centre from the edges + qml.CRY(p[30], wires=[1, 4]) + qml.CRY(p[31], wires=[3, 4]) + qml.CRY(p[32], wires=[5, 4]) + qml.CRY(p[33], wires=[7, 4]) + + return [qml.expval(ob_center), qml.expval(ob_corner), qml.expval(ob_edge)] + + +fig, ax = qml.draw_mpl(circuit_no_sym)([0] * 9, [0] * 34) + + +###################################################################### +# Note again how, though these circuits have a similar form to before, they are parameterised differently. +# We need to feed the vector :math:`\boldsymbol{y}` made up of the expectation value of these +# three operators into the loss function and use this to update our +# parameters. + + +import math + + +def encode_game(game): + board, res = game + x = board * (2 * math.pi) / 3 + if res == 1: + y = [-1, -1, 1] + elif res == -1: + y = [1, -1, -1] + else: + y = [-1, 1, -1] + return x, y + + +###################################################################### +# Recall that the loss function we're interested in is +# :math:`\mathcal{L}(\mathcal{D})=\frac{1}{|\mathcal{D}|} \sum_{(\boldsymbol{g}, \boldsymbol{y}) \in \mathcal{D}}\|\hat{\boldsymbol{y}}(\boldsymbol{g})-\boldsymbol{y}\|_{2}^{2}.` +# We need to define this and then we can begin our optimisation. + + +# calculate the mean square error for this classification problem +def cost_function(params, input, target): + output = torch.stack([torch.hstack(circuit(x, params)) for x in input]) + vec = output - target + sum_sqr = torch.sum(vec * vec, dim=1) + return torch.mean(sum_sqr) + + +###################################################################### +# Let's now train our symmetry-preserving circuit on the data. + + +from torch import optim +import numpy as np + +params = 0.01 * torch.randn(9) +params.requires_grad = True +opt = optim.Adam([params], lr=1e-2) + + +max_epoch = 15 +max_step = 30 +batch_size = 10 + +encoded_dataset = list(zip(*[encode_game(game) for game in dataset])) +encoded_dataset_val = list(zip(*[encode_game(game) for game in dataset_val])) + + +def accuracy(p, x_val, y_val): + with torch.no_grad(): + y_val = torch.tensor(y_val) + y_out = torch.stack([torch.hstack(circuit(x, p)) for x in x_val]) + acc = torch.sum(torch.argmax(y_out, axis=1) == torch.argmax(y_val, axis=1)) + return acc / len(x_val) + + +print(f"accuracy without training = {accuracy(params, *encoded_dataset_val)}") + +x_dataset = torch.stack(encoded_dataset[0]) +y_dataset = torch.tensor(encoded_dataset[1], requires_grad=False) + +saved_costs_sym = [] +saved_accs_sym = [] +for epoch in range(max_epoch): + rand_idx = torch.randperm(len(x_dataset)) + # Shuffled dataset + x_dataset = x_dataset[rand_idx] + y_dataset = y_dataset[rand_idx] + + costs = [] + + for step in range(max_step): + x_batch = x_dataset[step * batch_size : (step + 1) * batch_size] + y_batch = y_dataset[step * batch_size : (step + 1) * batch_size] + + def opt_func(): + opt.zero_grad() + loss = cost_function(params, x_batch, y_batch) + costs.append(loss.item()) + loss.backward() + return loss + + opt.step(opt_func) + + cost = np.mean(costs) + saved_costs_sym.append(cost) + + if (epoch + 1) % 1 == 0: + # Compute validation accuracy + acc_val = accuracy(params, *encoded_dataset_val) + saved_accs_sym.append(acc_val) + + res = [epoch + 1, cost, acc_val] + print("Epoch: {:2d} | Loss: {:3f} | Validation accuracy: {:3f}".format(*res)) + + +###################################################################### +# Now we train the non-symmetry preserving circuit. + + +params = 0.01 * torch.randn(34) +params.requires_grad = True +opt = optim.Adam([params], lr=1e-2) + +# calculate mean square error for this classification problem + + +def cost_function_no_sym(params, input, target): + output = torch.stack([torch.hstack(circuit_no_sym(x, params)) for x in input]) + vec = output - target + sum_sqr = torch.sum(vec * vec, dim=1) + return torch.mean(sum_sqr) + + +max_epoch = 15 +max_step = 30 +batch_size = 15 + +encoded_dataset = list(zip(*[encode_game(game) for game in dataset])) +encoded_dataset_val = list(zip(*[encode_game(game) for game in dataset_val])) + + +def accuracy_no_sym(p, x_val, y_val): + with torch.no_grad(): + y_val = torch.tensor(y_val) + y_out = torch.stack([torch.hstack(circuit_no_sym(x, p)) for x in x_val]) + acc = torch.sum(torch.argmax(y_out, axis=1) == torch.argmax(y_val, axis=1)) + return acc / len(x_val) + + +print(f"accuracy without training = {accuracy_no_sym(params, *encoded_dataset_val)}") + + +x_dataset = torch.stack(encoded_dataset[0]) +y_dataset = torch.tensor(encoded_dataset[1], requires_grad=False) + +saved_costs = [] +saved_accs = [] +for epoch in range(max_epoch): + rand_idx = torch.randperm(len(x_dataset)) + # Shuffled dataset + x_dataset = x_dataset[rand_idx] + y_dataset = y_dataset[rand_idx] + + costs = [] + + for step in range(max_step): + x_batch = x_dataset[step * batch_size : (step + 1) * batch_size] + y_batch = y_dataset[step * batch_size : (step + 1) * batch_size] + + def opt_func(): + opt.zero_grad() + loss = cost_function_no_sym(params, x_batch, y_batch) + costs.append(loss.item()) + loss.backward() + return loss + + opt.step(opt_func) + + cost = np.mean(costs) + saved_costs.append(costs) + + if (epoch + 1) % 1 == 0: + # Compute validation accuracy + acc_val = accuracy_no_sym(params, *encoded_dataset_val) + saved_accs.append(acc_val) + + res = [epoch + 1, cost, acc_val] + print("Epoch: {:2d} | Loss: {:3f} | Validation accuracy: {:3f}".format(*res)) + +###################################################################### +# Finally let's plot the results and see how the two training regimes differ. + + +from matplotlib import pyplot as plt + +plt.title("Validation accuracies") +plt.plot(saved_accs_sym, "b", label="Symmetric") +plt.plot(saved_accs, "g", label="Standard") + +plt.ylabel("Validation accuracy (%)") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +###################################################################### +# What we can see then is that by paying attention to the symmetries intrinsic +# to the learning problem and reflecting this in an equivariant gate set +# we have managed to improve our learning accuracies, while also using fewer parameters. +# While the symmetry-aware circuit clearly outperforms the naive one, it is notable however that +# the learning accuracies in both cases are hardly ideal given this is a solved game. +# So paying attention to symmetries definitely helps, but it also isn't a magic bullet! +# + +###################################################################### +# The use of symmetries in both quantum and classical machine learning is a developing field, so we +# can expect new results to emerge over the coming years. If you want to get +# involved, the references given below are a great place to start. + + +############################################################################## +# References +# ---------- +# +# .. [#Bronstein2021] +# +# Michael M. Bronstein, Joan Bruna, Taco Cohen, Petar Veličković (2021). +# Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges. +# `arXiv:2104.13478 `__ +# +# .. [#Nguyen2022] +# +# Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, +# Patrick J. Coles, Frédéric Sauvage, Martín Larocca, and M. Cerezo (2022). +# Theory for Equivariant Quantum Neural Networks. +# `arXiv:2210.08566 `__ +# +# .. [#Meyer2022] +# +# Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, +# Francesco Arzani, Alissa Wilms, Jens Eisert (2022). +# Exploiting symmetry in variational quantum machine learning. +# `arXiv:2205.06217 `__ +# + + +############################################################################## +# Acknowledgments +# --------------- +# +# The author would also like to acknowledge the helpful input of C.-Y. Park. +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_geometric_qml/metadata.json b/demonstrations_v2/tutorial_geometric_qml/metadata.json new file mode 100644 index 0000000000..63142ced4c --- /dev/null +++ b/demonstrations_v2/tutorial_geometric_qml/metadata.json @@ -0,0 +1,60 @@ +{ + "title": "Introduction to Geometric Quantum Machine Learning", + "authors": [ + { + "username": "reast" + } + ], + "dateOfPublication": "2022-10-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_intro_to_geometric_QML.png" + } + ], + "seoDescription": "Using the natural symmetries in a quantum learning problem can improve learning", + "doi": "", + "references": [ + { + "id": "Bronstein2021", + "type": "article", + "title": "Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges", + "authors": "Michael M. Bronstein, Joan Bruna, Taco Cohen, Petar Veli\u010dkovi\u0107", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2104.13478" + }, + { + "id": "Nguyen2022", + "type": "article", + "title": "Theory for Equivariant Quantum Neural Networks", + "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Fr\u00e9d\u00e9ric Sauvage, Mart\u00edn Larocca, and M. Cerezo", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.08566" + }, + { + "id": "Meyer2022", + "type": "article", + "title": "Exploiting symmetry in variational quantum machine learning", + "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2205.06217" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_equivariant_graph_embedding", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_geometric_qml/requirements.in b/demonstrations_v2/tutorial_geometric_qml/requirements.in new file mode 100644 index 0000000000..d8d1b51485 --- /dev/null +++ b/demonstrations_v2/tutorial_geometric_qml/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +torch diff --git a/demonstrations_v2/tutorial_givens_rotations/demo.py b/demonstrations_v2/tutorial_givens_rotations/demo.py new file mode 100644 index 0000000000..4da5630b53 --- /dev/null +++ b/demonstrations_v2/tutorial_givens_rotations/demo.py @@ -0,0 +1,495 @@ +r""" + +Givens rotations for quantum chemistry +====================================== + +.. meta:: + :property="og:description": Discover the building blocks of quantum circuits for + quantum chemistry + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Givens_rotations.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + + +*Author: Juan Miguel Arrazola — Posted: 30 June 2021. Last updated: 30 June 2021.* + +In the book `"Sophie's world" `_, the young +protagonist receives a white envelope containing a letter +with an intriguing question: "Why is Lego the most ingenious toy in the world?" At first baffled by +this curious message, she decides to reflect on the question. As told by the book's narrator, +she arrives at a conclusion: + +*The best thing about them was that with Lego she could construct any kind of object. And then +she could separate the blocks and construct something new. What more could one ask of a toy? +Sophie decided that Lego really could be called the most ingenious toy in the world.* + + + +.. figure:: ../_static/demonstration_assets/givens_rotations/lego-circuit.svg + :align: center + :width: 50% + + +In this tutorial, you will learn about the building blocks of quantum circuits for quantum +chemistry: Givens rotations. These are operations that can be used to construct any kind of +particle-conserving circuit. We discuss single and double excitation gates, which are particular +types of Givens rotations that play an important role in quantum chemistry. Notably, +controlled single excitation gates are universal for particle-conserving unitaries. You will also +learn how to use these gates to build arbitrary states of a fixed number of particles. + +Particle-conserving unitaries +----------------------------- + +Understanding the electronic structure of molecules is arguably the central problem in quantum chemistry. +Quantum computers tackle this problem by using systems of qubits to +represent the quantum states of the electrons. One method is to consider a +collection of `molecular orbitals `_, which +capture the three-dimensional region of space occupied by the electrons. Each orbital can be +occupied by at most two electrons, each with a different spin orientation. In this case we refer to +*spin orbitals* that can be occupied by a single electron. + +The state of electrons in a molecule can then be described by specifying how the +orbitals are occupied. The `Jordan-Wigner representation +`_ provides a +convenient way to do this: we associate a qubit with each spin orbital and +use its states to represent occupied :math:`|1\rangle` or unoccupied +:math:`|0\rangle` spin orbitals. + +An :math:`n`-qubit state with `Hamming weight `_ +:math:`k`, i.e., with :math:`k` qubits in state :math:`|1\rangle,` represents a state of +:math:`k` electrons in :math:`n` spin orbitals. For example :math:`|1010\rangle` is a state of +two electrons in two spin orbitals. More generally, superpositions over all basis states with a +fixed number of particles are valid states of the electrons in a molecule. These are states such as + +.. math:: + + |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + + c_5|0101\rangle + c_6|0011\rangle, + +for some coefficients :math:`c_i.` + +.. figure:: ../_static/demonstration_assets/givens_rotations/orbitals_states.png + :align: center + :width: 50% + + States of a system with six spin orbitals and three electrons. Orbitals are for illustration; + they correspond to carbon dioxide, which has more electrons and orbitals. + +Because the number of electrons in a molecule is +fixed, any transformation must conserve the number of particles. We refer to these as +**particle-conserving unitaries**. When designing quantum circuits and algorithms for quantum +chemistry, it is desirable to employ only particle-conserving gates that guarantee that the +states of the system remain valid. This raises the questions: what are the simplest +particle-conserving unitaries? Like Legos, how can they be used to construct any quantum circuit for +quantum chemistry applications? + +Givens rotations +---------------- + +Consider single-qubit gates. In their most general form, they perform the transformation + +.. math:: + + U|0\rangle &= a |0\rangle + b |1\rangle,\\ + U|1\rangle &= c |1\rangle + d |0\rangle, + +where :math:`|a|^2+|b|^2=|c|^2+|d|^2=1` and :math:`ab^* + cd^*=0.` This gate is +particle-conserving only if :math:`b=d=0,` which means that the only single-qubit gates that +preserve particle number are diagonal gates of the form + +.. math:: + + U = \begin{pmatrix} + e^{i\theta} & 0\\ + 0 & e^{i\phi} + \end{pmatrix}. + +On their own, these gates are not very interesting. They can only be used to change the +relative phases of states in a superposition; they cannot be used to create and control such +superpositions. So let's take a look at two-qubit gates. + +Basis states of two qubits can be categorized depending on +their number of particles. + +We have: + +- :math:`|00\rangle` with zero particles, +- :math:`|01\rangle,|10\rangle` with one particle, and +- :math:`|11\rangle` with two particles. + +We can now consider transformations that couple the states :math:`|01\rangle,|10\rangle.` These +are gates of the form + +.. math:: + + U|01\rangle &= a |01\rangle + b |10\rangle\\ + U|10\rangle &= c |10\rangle + d |01\rangle. + +This should be familiar: the unitary has the same form as a single-qubit gate, except that the +states :math:`|01\rangle, |10\rangle` respectively take the place of :math:`|0\rangle, +|1\rangle`. This correspondence has a name: the `dual-rail qubit +`_, where a two-level system is constructed +by specifying in which of two possible systems a single particle is located. The +difference compared to single-qubit gates is that any values of the parameters :math:`a, +b,c,d` give rise to a valid particle-conserving unitary. Take for instance the two-qubit gate + +.. math:: + + G(\theta)=\begin{pmatrix} + 1 & 0 & 0 & 0\\ + 0 & \cos (\theta/2) & -\sin (\theta/2) & 0\\ + 0 & \sin(\theta/2) & \cos(\theta/2) & 0\\ + 0 & 0 & 0 & 1 + \end{pmatrix}. + + +This is an example of a `Givens rotation `_: a +rotation in a two-dimensional subspace of a larger Hilbert space. In this case, we are performing a +Givens rotation in a two-dimensional subspace of the four-dimensional space of two-qubit states. +This gate allows us to create superpositions by exchanging the particle +between the two qubits. Such transformations can be interpreted as a **single excitation**, +where we view the exchange from :math:`|10\rangle` to :math:`|01\rangle` as exciting the electron +from the first to the second qubit. + +.. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_1.png + :align: center + :width: 35% + + A Givens rotation can be used to couple states that differ by a single excitation. + +This gate is implemented in PennyLane as the :class:`~.pennylane.SingleExcitation` operation. +We can use it to prepare an equal superposition of three-qubit states with a single particle +:math:`\frac{1}{\sqrt{3}}(|001\rangle + |010\rangle + |100\rangle).` We apply two single +excitation gates with parameters :math:`\theta, \phi` to the initial state :math:`|100\rangle.` +The excitation gates act on qubits 0,1 and 0,2 respectively. This yields the state + +.. math:: + + |\psi\rangle = \cos(\theta/2)\cos(\phi/2)|100\rangle - \sin(\theta/2)|010\rangle - + \cos(\theta/2)\sin(\phi/2)|001\rangle. + +Since the amplitude of :math:`|010\rangle` must be :math:`1/\sqrt{3},` we conclude that +:math:`-\sin(\theta/2)=1/\sqrt{3}.` This in turn implies that :math:`\cos(\theta/2)=\sqrt{2/3}` and +therefore :math:`-\sin(\phi/2)=1/\sqrt{2}.` Thus, to prepare an equal superposition state we +choose the angles of rotation to be + +.. math:: + + \theta &= - 2 \arcsin(1/\sqrt{3}),\\ + \phi &= - 2 \arcsin(1/\sqrt{2}). + +This can be implemented in PennyLane as follows: +""" + +import pennylane as qml +import numpy as np + +dev = qml.device('default.qubit', wires=3) + +@qml.qnode(dev, interface="autograd") +def circuit(x, y): + # prepares the reference state |100> + qml.BasisState(np.array([1, 0, 0]), wires=[0, 1, 2]) + # applies the single excitations + qml.SingleExcitation(x, wires=[0, 1]) + qml.SingleExcitation(y, wires=[0, 2]) + return qml.state() + +x = -2 * np.arcsin(np.sqrt(1/3)) +y = -2 * np.arcsin(np.sqrt(1/2)) +print(circuit(x, y)) + +############################################################################## +# The components of the output state are ordered according to their binary +# representation, so entry 1 is :math:`|001\rangle`, entry 2 is :math:`|010\rangle,` and entry 4 is +# :math:`|100\rangle,` meaning we indeed prepared the desired state. We can check this by +# reshaping the output state + +tensor_state = circuit(x, y).reshape(2, 2, 2) +print("Amplitude of state |001> = ", tensor_state[0, 0, 1]) +print("Amplitude of state |010> = ", tensor_state[0, 1, 0]) +print("Amplitude of state |100> = ", tensor_state[1, 0, 0]) + +############################################################################## +# We can also study **double excitations** involving the transfer of two particles. For example, +# consider a Givens rotation in the subspace spanned by the states +# :math:`|1100\rangle` and :math:`|0011\rangle.` These +# states differ by a double excitation since we can map :math:`|1100\rangle` to +# :math:`|0011\rangle` by exciting the particles from the first two qubits to the last two. +# Mathematically, this gate can be represented by a unitary :math:`G^{(2)}(\theta)` that performs +# the mapping +# +# .. math:: +# +# G^{(2)}|0011\rangle &= \cos (\theta/2)|0011\rangle + \sin (\theta/2)|1100\rangle\\ +# G^{(2)}|1100\rangle &= \cos (\theta/2)|1100\rangle - \sin (\theta/2)|0011\rangle, +# +# while leaving all other basis states unchanged. This gate is implemented in PennyLane as the +# :class:`~.pennylane.DoubleExcitation` operation. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/Givens_rotations_2.png +# :align: center +# :width: 35% +# +# A Givens rotation can also be used to couple states that differ by a double excitation. +# +# +# In the context of quantum chemistry, it is common to consider excitations on a fixed reference +# state and include only the excitations that preserve the spin orientation of the electron. +# For a system with :math:`n` qubits and :math:`k` particles, this reference state is typically +# chosen as the state with the first :math:`k` qubits in state :math:`|1\rangle,` and the remainder +# in state :math:`|0\rangle.` +# PennyLane allows you to obtain all such excitations using the function +# :func:`~.pennylane.qchem.excitations`. Let's employ it to build a circuit that includes +# all single and double excitations acting on a reference state of three particles in six qubits. +# We apply a random rotation for each gate: + +nr_particles = 3 +nr_qubits = 6 + +singles, doubles = qml.qchem.excitations(3, 6) +print(f"Single excitations = {singles}") +print(f"Double excitations = {doubles}") + +############################################################################## +# Now we continue to build the circuit: + +dev2 = qml.device('default.qubit', wires=6) + +@qml.qnode(dev2, interface="autograd") +def circuit2(x, y): + # prepares reference state + qml.BasisState(np.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) + # apply all single excitations + for i, s in enumerate(singles): + qml.SingleExcitation(x[i], wires=s) + # apply all double excitations + for j, d in enumerate(doubles): + qml.DoubleExcitation(y[j], wires=d) + return qml.state() + +# random angles of rotation +x = np.random.normal(0, 1, len(singles)) +y = np.random.normal(0, 1, len(doubles)) + +output = circuit2(x, y) + +############################################################################## +# We can check which basis states appear in the resulting superposition to confirm that they +# involve only states with three particles. + +# constructs binary representation of states with non-zero amplitude +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Besides these Givens rotations, there are other versions that have been +# reported in the literature and used to construct circuits for quantum chemistry. For instance, +# Ref. [#anselmetti]_ considers a different sign convention for single-excitation gates, +# +# .. math:: +# G(\theta)=\begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta/2) & \sin (\theta/2) & 0\\ +# 0 & -\sin(\theta/2) & \cos(\theta/2) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}, +# +# and Ref. [#barkoutsos]_ introduces the particle-conserving gates listed below, which are all +# Givens rotations +# +# .. math:: +# U_1(\theta, \phi) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (\theta) & e^{i\phi}\sin (\theta) & 0\\ +# 0 & e^{-i\phi}\sin(\theta) & -\cos(\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix},\\ +# +# U_2(\theta) &= \begin{pmatrix} +# 1 & 0 & 0 & 0\\ +# 0 & \cos (2\theta) & -i\sin (2\theta) & 0\\ +# 0 & -i\sin(2\theta) & \cos(2\theta) & 0\\ +# 0 & 0 & 0 & 1 +# \end{pmatrix}. +# +# Givens rotations are a powerful abstraction for understanding +# quantum circuits for quantum chemistry. Instead of thinking of single-qubit gates and CNOTs as the +# building-blocks of quantum circuits, we can be more clever and select two-dimensional subspaces +# spanned by states with an equal number of particles, and use Givens rotations in that subspace +# to construct the circuits. 🧠 +# +# Controlled excitation gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Single-qubit gates and CNOT gates are universal for quantum +# computing: they can be used to implement any conceivable quantum computation. If Givens +# rotations are analogous to single-qubit gates, then **controlled** Givens rotations are +# analogous to two-qubit gates. In universality constructions, the ability to control operations +# based on the states of other qubits is essential, so for this reason it's natural to study +# controlled Givens rotations. The simplest of these are controlled single-excitation gates, +# which are three-qubit gates that perform the mapping +# +# .. math:: +# CG(\theta) |101\rangle &= \cos (\theta/2)|101\rangle + \sin (\theta/2)|110\rangle,\\ +# CG(\theta) |110\rangle &= \cos (\theta/2)|110\rangle - \sin (\theta/2)|101\rangle, +# +# while leaving all other basis states unchanged. This gate only excites a particle +# from the second to third qubit, and vice versa, if the first (control) qubit is in state +# :math:`|1\rangle.` This is a useful property: as the name suggests, it provides us with better +# control over the transformations we want to apply. Suppose we aim to prepare the state +# +# .. math:: +# |\psi\rangle = \frac{1}{2}(|110000\rangle + |001100\rangle + |000011\rangle + |100100\rangle). +# +# Some inspection is enough to see that the states :math:`|001100\rangle` and :math:`|000011\rangle` +# differ by a double excitation from the reference state :math:`|110000\rangle.` Meanwhile, the state +# :math:`|100100\rangle` differs by a single excitation. It is thus tempting to think that applying +# two double-excitation gates and a single-excitation gate can be used to prepare the target state. +# It won't work! Applying the single-excitation gate on qubits 1 and 3 will also lead to an +# undesired contribution for the state :math:`|011000\rangle` through a coupling with +# :math:`|001100\rangle.` Let's check that this is the case: + +dev = qml.device('default.qubit', wires=6) + +@qml.qnode(dev, interface="autograd") +def circuit3(x, y, z): + qml.BasisState(np.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + qml.SingleExcitation(z, wires=[1, 3]) + return qml.state() + +x = -2 * np.arcsin(np.sqrt(1/4)) +y = -2 * np.arcsin(np.sqrt(1/3)) +z = -2 * np.arcsin(np.sqrt(1/2)) + +output = circuit3(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# Indeed, we have a non-zero coefficient for :math:`|011000\rangle.` To address this problem, +# we can instead apply the single-excitation gate controlled on the +# state of the first qubit. This ensures that there is no coupling with the state :math:`| +# 001100\rangle` since here the first qubit is in state :math:`|0\rangle.` Let's implement the circuit +# above, this time controlling on the state of the first qubit and verify that we can prepare the +# desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: + +@qml.qnode(dev, interface="autograd") +def circuit4(x, y, z): + qml.BasisState(np.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) + qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) + # single excitation controlled on qubit 0 + qml.ctrl(qml.SingleExcitation, control=0)(z, wires=[1, 3]) + return qml.state() + +output = circuit4(x, y, z) +states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] +print(states) + +############################################################################## +# It was proven in Ref. [#arrazola]_ that controlled single-excitation gates are universal for +# particle-conserving unitaries. With enough ingenuity, you can use these operations to construct +# any kind of circuit for quantum chemistry applications. What more could you ask from a gate? +# +# State preparation +# ----------------- +# +# We can bring all these pieces together and implement a circuit capable of preparing +# four-qubit states of two particles with real coefficients. The main idea is that we can +# perform the construction one basis state at a time by applying a suitable excitation gate, +# which may need to be controlled. +# +# +# .. figure:: ../_static/demonstration_assets/givens_rotations/circuit.png +# :align: center +# :width: 70% +# +# A circuit for preparing four-qubit states with two particles. +# +# +# Starting from the reference state :math:`|1100\rangle,` we create a superposition +# with the state :math:`|1010\rangle` by applying a single-excitation gate on qubits 1 and 2. +# Similarly, we create a superposition with the state :math:`|1001\rangle` with a single +# excitation between qubits 1 and 3. This leaves us with a state of the form +# +# .. math:: +# |\psi\rangle = a |1100\rangle + b |1010\rangle + c |1001\rangle. +# +# We can now perform two single excitations from qubit 0 to qubits 2 and 3. These will have +# to be controlled on the state of qubit 1. Finally, applying a double-excitation gate on all qubits +# can create a superposition of the form +# +# .. math:: +# +# |\psi\rangle = c_1|1100\rangle + c_2|1010\rangle + c_3|1001\rangle + c_4|0110\rangle + +# c_5|0101\rangle + c_6|0011\rangle, +# +# which is our intended outcome. Let's use this approach to create an equal superposition over +# all two-particle states on four qubits. We follow the same strategy as before, setting the angle +# of the :math:`k`-th Givens rotation as :math:`-2 \arcsin(1/\sqrt{n-k}),` where :math:`n` is the +# number of basis states in the superposition. + +dev = qml.device('default.qubit', wires=4) + +@qml.qnode(dev, interface="autograd") +def state_preparation(params): + qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) + qml.SingleExcitation(params[0], wires=[1, 2]) + qml.SingleExcitation(params[1], wires=[1, 3]) + # single excitations controlled on qubit 1 + qml.ctrl(qml.SingleExcitation, control=1)(params[2], wires=[0, 2]) + qml.ctrl(qml.SingleExcitation, control=1)(params[3], wires=[0, 3]) + qml.DoubleExcitation(params[4], wires=[0, 1, 2, 3]) + return qml.state() + +n = 6 +params = np.array([-2 * np.arcsin(1/np.sqrt(n-i)) for i in range(n-1)]) + +output = state_preparation(params) +# sets very small coefficients to zero +output[np.abs(output) < 1e-10] = 0 +states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] +print("Basis states = ", states) +print("Output state =", output) + +############################################################################## +# Success! This is the equal superposition state we wanted to prepare. 🚀 +# +# When it comes to quantum circuits for quantum chemistry, a wide variety of +# architectures have been proposed. Researchers in the field are faced with the apparent +# choice of making a selection among these circuits to conduct their computations and benchmark new +# algorithms. Like a kid in a toy store, it is challenging to pick just one. +# +# Ultimately, the aim of this tutorial is to provide you with the conceptual and software tools +# to implement any of these proposed circuits, and **also to design your own**. It's not only fun +# to play with toys; it's also fun to build them. +# +# +# References +# ---------- +# +# .. [#anselmetti] +# +# G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish, +# "Local, Expressive, Quantum-Number-Preserving VQE Ansatze +# for Fermionic Systems", arXiv:2104.05695, (2021). +# +# .. [#barkoutsos] +# +# P. KL. Barkoutsos, Panagiotis, et al., "Quantum algorithms for electronic structure +# calculations: Particle-hole Hamiltonian and optimized wave-function expansions", Physical +# Review A 98(2), 022322, (2018). +# +# .. [#arrazola] +# +# J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran, "Universal +# quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) +# +# diff --git a/demonstrations_v2/tutorial_givens_rotations/metadata.json b/demonstrations_v2/tutorial_givens_rotations/metadata.json new file mode 100644 index 0000000000..026702d6ef --- /dev/null +++ b/demonstrations_v2/tutorial_givens_rotations/metadata.json @@ -0,0 +1,65 @@ +{ + "title": "Givens rotations for quantum chemistry", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2021-06-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_Givens_rotations_for_QChem.png" + } + ], + "seoDescription": "Discover the building blocks of quantum circuits for quantum chemistry", + "doi": "", + "references": [ + { + "id": "anselmetti", + "type": "article", + "title": "Local, Expressive, Quantum-Number-Preserving VQE Ansatze for Fermionic Systems", + "authors": "G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish", + "year": "2021", + "journal": "", + "url": "" + }, + { + "id": "barkoutsos", + "type": "article", + "title": "Quantum algorithms for electronic structure calculations: Particle-hole Hamiltonian and optimized wave-function expansions", + "authors": "P. KL. Barkoutsos, Panagiotis, et al.", + "year": "2018", + "journal": "Physical Review A", + "url": "" + }, + { + "id": "arrazola", + "type": "article", + "title": "Universal quantum circuits for quantum chemistry", + "authors": "J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran", + "year": "2021", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_givens_rotations/requirements.in b/demonstrations_v2/tutorial_givens_rotations/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_givens_rotations/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_grovers_algorithm/demo.py b/demonstrations_v2/tutorial_grovers_algorithm/demo.py new file mode 100644 index 0000000000..4370816294 --- /dev/null +++ b/demonstrations_v2/tutorial_grovers_algorithm/demo.py @@ -0,0 +1,392 @@ +r""".. role:: html(raw) + :format: html + +Grover's Algorithm +================== + +.. meta:: + :property="og:description": Learn how to find an entry in a list using Grover's algorithm + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_grovers_algorithm.png + +.. related:: + + tutorial_qft_arithmetics Basic arithmetic with the quantum Fourier transform (QFT) + +*Author: Ludmila Botelho. — Posted: 3 July 2023.* + + +`Grover's algorithm `__ is an `oracle `__-based quantum +algorithm, proposed by Lov Grover [#Grover1996]_. In the original description, the author approaches the +following problem: suppose that we are searching for a specific phone number in a randomly-ordered +catalogue containing :math:`N` entries. To find such a number with a probability of +:math:`\frac{1}{2},` a classical algorithm will need to check the list on average +:math:`\frac{N}{2}` times. + + +In other words, the problem is defined by searching for an item on a list with :math:`N` items given +an Oracle access function :math:`f(x).` This function has the defining property that +:math:`f(x) = 1` if :math:`x` is the item we are looking for, and :math:`f(x) = 0` +otherwise. The solution to this black-box search problem is proposed as a quantum algorithm that +performs :math:`O(\sqrt{N})` oracular queries to the list with a high probability of finding the +answer, whereas any classical algorithm would require :math:`O(N)` queries. + +In this tutorial, we are going to implement a search for an n-bit string item using a quantum +circuit based on Grover's algorithm. + +The algorithm can be broken down into the following steps: + +1. Prepare the initial state +2. Implement the oracle +3. Apply the Grover diffusion operator +4. Repeat steps 2 and 3 approximately :math:`\frac{\pi}{4}\sqrt{N}` times +5. Measure + + +Let's import the usual PennyLane and Numpy libraries to load the necessary functions: + +""" + +import matplotlib.pyplot as plt +import pennylane as qml +import numpy as np + +###################################################################### +# Preparing the Initial State +# --------------------------- +# +# To perform the search, we are going to create an n-dimensional system, which has :math:`N = 2^n` +# computational basis states, represented via :math:`N` binary numbers. More specifically, +# bit strings with length :math:`n,` labelled as :math:`x_0,x_2,\cdots, x_{N-1}.` +# We initialize the system in the uniform superposition over all states, i.e., +# the amplitudes associated with each of the :math:`N` basis states are equal: +# +# .. math:: |s\rangle ={\frac {1}{\sqrt {N}}}\sum _{x=0}^{N-1}|x\rangle . +# +# +# This can be achieved by applying a Hadamard gate to all the wires. We can inspect the circuit using +# :class:`~.pennylane.Snapshot` to see how the states change on each step. Let us check the probability of finding +# a state in the computational basis for a 2-qubit circuit, writing the following functions and +# QNodes: + + +NUM_QUBITS = 2 +dev = qml.device("default.qubit", wires=NUM_QUBITS) +wires = list(range(NUM_QUBITS)) + + +def equal_superposition(wires): + for wire in wires: + qml.Hadamard(wires=wire) + + +@qml.qnode(dev) +def circuit(): + qml.Snapshot("Initial state") + equal_superposition(wires) + qml.Snapshot("After applying the Hadamard gates") + return qml.probs(wires=wires) # Probability of finding a computational basis state on the wires + + +results = qml.snapshots(circuit)() + +for k, result in results.items(): + print(f"{k}: {result}") + +###################################################################### +# Let's use a bar plot to better visualize the initial state amplitudes: + + +y = np.real(results["After applying the Hadamard gates"]) +bit_strings = [f"{x:0{NUM_QUBITS}b}" for x in range(len(y))] + +plt.bar(bit_strings, y, color = "#70CEFF") + +plt.xticks(rotation="vertical") +plt.xlabel("State label") +plt.ylabel("Probability Amplitude") +plt.title("States probabilities amplitudes") +plt.show() + +###################################################################### +# As expected, they are equally distributed. +# +# The Oracle and Grover's diffusion operator +# ------------------------------------------ +# +# Let's assume for now that only one index satisfies :math:`f(x) = 1`. We are going to call this index :math:`\omega.` +# To access :math:`f(x)` with an Oracle, we can formulate a unitary operator such that +# +# .. math:: +# \begin{cases} +# U_{\omega }|x\rangle =-|x\rangle &{\text{for }}x=\omega {\text{, that is, }}f(x)=1,\\U_{\omega }|x\rangle =|x\rangle &{\text{for }}x\neq \omega {\text{, that is, }}f(x)=0, +# \end{cases} +# +# where and :math:`U_\omega` acts by flipping the phase of the solution state while keeping the remaining states untouched. In other +# words, the unitary :math:`U_\omega` can be seen as a reflection around the set of orthogonal states +# to :math:`\vert \omega \rangle,` written as +# +# .. math:: U_\omega = \mathbb{I} - 2\vert \omega \rangle \langle \omega \vert. +# +# This can be easily implemented with :class:`~.pennylane.FlipSign`, which takes a binary array and flips the sign +# of the corresponding state. +# +# Let us take a look at an example. If we pass the array ``[0,0]``, the sign of the state +# :math:`\vert 00 \rangle = \begin{bmatrix} 1 \\0 \\0 \\0 \end{bmatrix}` will flip: + +dev = qml.device("default.qubit", wires=NUM_QUBITS) + +@qml.qnode(dev) +def circuit(): + qml.Snapshot("Initial state |00>") + # Flipping the marked state + qml.FlipSign([0, 0], wires=wires) + qml.Snapshot("After flipping it") + return qml.state() + +results = qml.snapshots(circuit)() + +for k, result in results.items(): + print(f"{k}: {result}") + +y1 = np.real(results["Initial state |00>"]) +y2 = np.real(results["After flipping it"]) + +bit_strings = [f"{x:0{NUM_QUBITS}b}" for x in range(len(y))] + +plt.bar(bit_strings, y1, color = "#70CEFF") +plt.bar(bit_strings, y2, color = "#C756B2") + +plt.xticks(rotation="vertical") +plt.xlabel("State label") +plt.ylabel("Probability Amplitude") +plt.title("States probabilities amplitudes") + +plt.legend(["Initial state |00>", "After flipping it"]) +plt.axhline(y=0.0, color="k", linestyle="-") +plt.show() + +###################################################################### +# We can see that the amplitude of the state :math:`\vert 01\rangle` flipped. Now, let us prepare +# the Oracle and inspect its action in the circuit. + +omega = np.zeros(NUM_QUBITS) + +def oracle(wires, omega): + qml.FlipSign(omega, wires=wires) + +dev = qml.device("default.qubit", wires=NUM_QUBITS) + +@qml.qnode(dev) +def circuit(): + equal_superposition(wires) + qml.Snapshot("Before querying the Oracle") + + oracle(wires, omega) + qml.Snapshot("After querying the Oracle") + + return qml.probs(wires=wires) + +results = qml.snapshots(circuit)() + +for k, result in results.items(): + print(f"{k}: {result}") +########################################## + +y1 = np.real(results["Before querying the Oracle"]) +y2 = np.real(results["After querying the Oracle"]) + +bit_strings = [f"{x:0{NUM_QUBITS}b}" for x in range(len(y1))] + +bar_width = 0.4 + +rect_1 = np.arange(0, len(y1)) +rect_2 = [x + bar_width for x in rect_1] + +plt.bar( + rect_1, + y1, + width=bar_width, + edgecolor="white", + color = "#70CEFF", + label="Before querying the Oracle", +) +plt.bar( + rect_2, + y2, + width=bar_width, + edgecolor="white", + color = "#C756B2", + label="After querying the Oracle", +) + +plt.xticks(rect_1 + 0.2, bit_strings, rotation="vertical") +plt.xlabel("State label") +plt.ylabel("Probability Amplitude") +plt.title("States probabilities amplitudes") + +plt.legend() +plt.show() + +###################################################################### +# We can see that the amplitude corresponding to the state :math:`\vert \omega \rangle` changed. +# However, we need an additional step to find the solution, since the probability of measuring any of +# the states remains equally distributed. This can be solved by applying the *Grover diffusion* +# operator, defined as +# +# .. math:: +# U_D = 2| s \rangle\langle s| - \mathbb{I}. +# +# The unitary :math:`U_D` also acts as a rotation, but this time through the uniform superposition :math:`\vert s \rangle.` +# Finally, the combination of :math:`U_{\omega}` with :math:`U_D` rotates the state +# :math:`\vert s \rangle` by an angle of +# :math:`\theta =2 \arcsin{\tfrac {1}{\sqrt {N}}}.` For more geometric insights +# about the oracle and the diffusion operator, please refer to this `PennyLane Codebook +# section `__. +# +# +# .. figure:: ../_static/demonstration_assets/grovers_algorithm/rotation.gif +# :align: center +# :width: 70% +# +# +# In a 2-qubit circuit, the diffusion operator has a specific shape: +# +# .. figure:: ../_static/demonstration_assets/grovers_algorithm/diffusion_2_qubits.svg +# :align: center +# :width: 90% +# +# +# Now, we have all the building blocks to implement a single-item search in a 2-qubit circuit. We can +# verify in the circuit below that applying the *Grover iterator* :math:`U_D U_\omega` once is enough +# to solve the problem. + + +dev = qml.device("default.qubit", wires=NUM_QUBITS) + + +def diffusion_operator(wires): + for wire in wires: + qml.Hadamard(wires=wire) + qml.PauliZ(wires=wire) + qml.ctrl(qml.PauliZ, 0)(wires=1) + for wire in wires: + qml.Hadamard(wires=wire) + + +@qml.qnode(dev) +def circuit(): + equal_superposition(wires) + qml.Snapshot("Uniform superposition |s>") + + oracle(wires, omega) + qml.Snapshot("State marked by Oracle") + diffusion_operator(wires) + + qml.Snapshot("Amplitude after diffusion") + return qml.probs(wires=wires) + + +results = qml.snapshots(circuit)() + +for k, result in results.items(): + print(f"{k}: {result}") +###################################################################### +# Searching for more items in a bigger list +# ----------------------------------------- +# +# Now, let us consider the generalized problem with large :math:`N,` accepting :math:`M` solutions, with +# :math:`1 \leq M \leq N.` In this case, the optimal number of Grover iterations to find the solution +# is given by :math:`r \approx \left \lceil \frac{\pi}{4} \sqrt{\frac{N}{M}} \right \rceil`\ [#NandC2000]_. +# +# For more qubits, we can use the same function for the Oracle to mark the desired states, and the +# diffusion operator takes a more general form: +# +# .. figure:: ../_static/demonstration_assets/grovers_algorithm/diffusion_n_qubits.svg +# :align: center +# :width: 90% +# +# which is easily implemented using :class:`~.pennylane.GroverOperator`. +# +# Finally, we have all the tools to build the circuit for Grover's algorithm, as we can see in the +# code below. For simplicity, we are going to search for the states +# :math:`\vert 0\rangle ^{\otimes n}` and :math:`\vert 1\rangle ^{\otimes n},` where +# :math:`n = \log_2 N` is the number of qubits. + +NUM_QUBITS = 5 + +omega = np.array([np.zeros(NUM_QUBITS), np.ones(NUM_QUBITS)]) + +M = len(omega) +N = 2**NUM_QUBITS +wires = list(range(NUM_QUBITS)) + +dev = qml.device("default.qubit", wires=NUM_QUBITS) + +@qml.qnode(dev) +def circuit(): + iterations = int(np.round(np.sqrt(N / M) * np.pi / 4)) + + # Initial state preparation + equal_superposition(wires) + + # Grover's iterator + for _ in range(iterations): + for omg in omega: + oracle(wires, omg) + qml.templates.GroverOperator(wires) + + return qml.probs(wires=wires) + + +results = qml.snapshots(circuit)() + +for k, result in results.items(): + print(f"{k}: {result}") +###################################################################### +# Let us use a bar plot to visualize the probability to find the correct bitstring. + +y = results["execution_results"] +bit_strings = [f"{x:0{NUM_QUBITS}b}" for x in range(len(y))] + +plt.bar(bit_strings, results["execution_results"], color = "#70CEFF") + +plt.xticks(rotation="vertical") +plt.xlabel("State label") +plt.ylabel("Probability") +plt.title("States probabilities") + +plt.show() + +###################################################################### +# Conclusion +# ----------- +# +# In conclusion, we have learned the basic steps of Grover's algorithm and how to implement it to search +# :math:`M` items in a list of size :math:`N` with high probability. +# +# Grover's algorithm in principle can be used to speed up more sophisticated computation, for +# instance, when used as a subroutine for problems that require extensive search +# and is the basis of a whole family of algorithms, such as the amplitude +# amplification technique. +# +# If you would like to learn more about Grover's algorithm, check out `this video `__! +# +# + +###################################################################### +# References +# ---------- +# +# .. [#Grover1996] +# +# L. K. Grover (1996) "A fast quantum mechanical algorithm for database search". `Proceedings of +# the Twenty-Eighth Annual ACM Symposium on Theory of Computing. STOC '96. Philadelphia, Pennsylvania, +# USA: Association for Computing Machinery: 212–219 +# `__. +# (`arXiv `__) +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# diff --git a/demonstrations_v2/tutorial_grovers_algorithm/metadata.json b/demonstrations_v2/tutorial_grovers_algorithm/metadata.json new file mode 100644 index 0000000000..84744be7f6 --- /dev/null +++ b/demonstrations_v2/tutorial_grovers_algorithm/metadata.json @@ -0,0 +1,39 @@ +{ + "title": "Grover's Algorithm", + "authors": [ + { + "username": "lbotelho" + } + ], + "dateOfPublication": "2023-07-03T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/grovers_algorithm/thumbnail_tutorial_grovers_algorithm.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_grovers_algorithm.png" + } + ], + "seoDescription": "Learn how to find an entry in a list using Grover's Algorithm", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/understanding-grover/3340" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_grovers_algorithm/requirements.in b/demonstrations_v2/tutorial_grovers_algorithm/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_grovers_algorithm/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py new file mode 100644 index 0000000000..0d667619e2 --- /dev/null +++ b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py @@ -0,0 +1,253 @@ +r"""Your guide to PennyLane if you know Qiskit +=================================================== + +Greetings, fellow quantum programmers 👋! Isn’t it such a wonderful world we live in, with so many +different quantum software development kits (SDKs) at our fingertips? I remember the days of (messy) +personalized code bases that took hours and hours to develop just *to be able* to start researching +👴. Nowadays, those tools are there for us to access for free, being developed and maintained by +`savvy open-source software developers `__ around the clock. Again, what a wonderful world! + +When it comes to quantum programming SDKs, `PennyLane `__ and `Qiskit `__ (``v1.0`` and ``<=v0.46``) are two +of the most widely-used by the community. PennyLane has a few staples that make it so: + +- **Hardware-agnostic**: PennyLane has no opinions on what hardware or simulator backends you want + to use for your research. You can program an emporium of real hardware and simulator backends all + from the easy-to-use PennyLane API. This includes IBM’s hardware with the `PennyLane-Qiskit + plugin `__. + +- **Everything is differentiable**: A quantum circuit in PennyLane is designed to behave like a + differentiable function, unlocking quantum differentiable programming and allowing to integrate + seamlessly with your favourite machine learning frameworks. + +- **Community-focused**: Let’s face it, you’re going to get stuck at some point when you’re + researching or learning. That’s why we have a mandate to make our `documentation `__ easy to navigate, + dedicated teams for creating :doc:`new demonstrations ` when we release new features, and an active + `discussion forum `__ to answer your questions. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_guide_to_pennylane_knowing_qiskit.png + :align: center + :width: 70% + +""" + +###################################################################### +# In this demo, we're going to demonstrate to you the fundamentals of PennyLane and all that makes it awesome, with +# the idea in mind that you're coming from Qiskit. If you want to follow along on your computer, all you’ll +# need to do is install the `PennyLane-Qiskit plugin `__: +# +# .. code-block:: +# +# pip install -U pennylane-qiskit +# +# Now, let’s get started. +# + +###################################################################### +# PennyLane 🤝 Qiskit +# ------------------- +# +# With the first stable release of Qiskit in February 2024 — `Qiskit 1.0 `__ — +# breaking changes were to be expected. Although learning a new language can be hard, PennyLane has very +# simple tools that will help via the PennyLane-Qiskit plugin. This is your gateway to the land of PennyLane +# that even lets you keep your existing Qiskit work, and you don't even have to know a ton about how PennyLane +# works to use it. +# +# There are two functions you need to know about: +# +# - :func:`~pennylane.from_qiskit`: converts an entire Qiskit ``QuantumCircuit`` — the whole thing — into +# PennyLane. It will faithfully convert Qiskit-side measurements (even mid-circuit measurements) or +# you can append PennyLane-side measurements directly to it. +# +# - :func:`~pennylane.from_qiskit_op`: converts a ``SparsePauliOp`` in Qiskit 1.0 to the equivalent operator in +# PennyLane. +# +# +# These two functions give you all that you need to access PennyLane’s features and user interface +# starting from the Qiskit side. As an example, let’s say you have the following code in Qiskit that +# prepares a Bell state. +# + +from qiskit import QuantumCircuit + +def qiskit_circuit(): + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + + return qc + +qc = qiskit_circuit() + +###################################################################### +# To convert this circuit to PennyLane, you just use ``qml.from_qiskit``: +# + +import pennylane as qml +import matplotlib.pyplot as plt + +pl_func = qml.from_qiskit(qc) +qml.draw_mpl(pl_func, style="pennylane")() +plt.show() + +###################################################################### +# Want to measure some expectation values of Pauli operators, as well? Use ``qml.from_qiskit_op`` to +# convert a ``SparsePauliOp`` into PennyLane’s equivalent operator. +# + +from qiskit.quantum_info import SparsePauliOp + +qiskit_pauli_op = SparsePauliOp("XY") +pl_pauli_op = qml.from_qiskit_op(qiskit_pauli_op) + +###################################################################### +# Then, you can *append* the expectation value measurement — done with ``qml.expval`` — to the PennyLane +# circuit when you create it with ``qml.from_qiskit``: +# + +pl_func = qml.from_qiskit(qc, measurements=[qml.expval(pl_pauli_op)]) +qml.draw_mpl(pl_func, style='pennylane')() +plt.show() + +###################################################################### +# And just like that, you’re in PennyLaneLand! ... *It's in your ears and in your eyes!* 🎶 . Now you might be asking: “What is ``pl_func`` and how +# could I use it further?” To answer those questions, we need to get to know PennyLane a little better. +# + +###################################################################### +# Get to Know PennyLane 🌞 +# ------------------------ +# +# Let’s go back to that Qiskit circuit (``qc``) that we created `earlier <#pennylane-qiskit>`_. If we want to execute that +# circuit in Qiskit and get some results, we can do this: +# + +from qiskit.primitives import StatevectorSampler + +qc.measure_all() + +sampler = StatevectorSampler() + +job_sampler = sampler.run([qc], shots=1024) +result_sampler = job_sampler.result()[0].data.meas.get_counts() + +print(result_sampler) + +###################################################################### +# When we use ``qml.from_qiskit`` on our Qiskit circuit, this is equivalent to creating this function +# in PennyLane. +# + +def pl_func(): + """ + Equivalent to doing: + pl_func = qml.from_qiskit(qc, measurements=qml.counts(wires=[0, 1])) + """ + qml.Hadamard(0) + qml.CNOT([0, 1]) + return qml.counts(wires=[0, 1]) + +###################################################################### +# .. note :: +# +# Qubits in PennyLane are called *wires*. Why? As was mentioned, PennyLane is a hardware-agnostic +# framework, and “wires” is a hardware-agnostic term for quantum degrees of freedom that +# quantum computers can be based on. +# +# + +###################################################################### +# A function like ``pl_func`` is called a **quantum function**. A quantum function in PennyLane just +# contains quantum gates and (optionally) returns a measurement. Measurements in PennyLane are quite +# different than in Qiskit 1.0 — we'll touch on how measurements work in PennyLane shortly. But, in our +# case, :func:`qml.counts(wires=[0, 1]) ` is the measurement, which counts the number +# of times each basis state is sampled. +# +# If we actually want to execute the circuit and see the result of our measurement, we need to define +# what the circuit runs on, just like how we defined a ``StatevectorSampler`` instance in Qiskit +# (a new `V2 primitive `__). PennyLane’s +# way of doing this is simple: (1) define a device with :func:`qml.device ` and (2) pair +# the device with the quantum function with :class:`~pennylane.QNode`. +# + +dev = qml.device("default.qubit", shots=1024) +pl_circuit = qml.QNode(pl_func, dev) + +print(pl_circuit()) + +###################################################################### +# Now that we have the full picture of how a circuit gets created and executed in PennyLane, let’s take +# a step back and summarize what’s going on. +# +# The first thing you’ll notice is that PennyLane’s primitives are Pythonic and array-like; quantum circuits +# are *functions*, returning measurements that behave like `NumPy arrays `__. The function ``pl_circuit`` is +# called a *quantum node* (QNode), which is the union of two things: +# +# - **A quantum function that contains quantum instructions**. This is ``pl_func``, which just contains +# quantum operations (gates) and returns a measurement. In this case, ``qml.counts(wires=1)`` is +# the measurement, which counts the number of times each basis state is sampled and returns a dictionary +# whose values are NumPy arrays. +# +# - **A device** (e.g., ``qml.device("default.qubit")``). PennyLane has `many devices you can choose from `__, +# but ``"default.qubit"`` is our battle-tested Python state vector simulator. +# +# +# As for measurements in PennyLane, they are quite different from Qiskit's V2 primitives. +# :doc:`PennyLane's measurement API ` comprises ergonomic functions that a QNode +# can return, including +# +# - :func:`~pennylane.state`: returns the quantum state vector, +# +# - :func:`~pennylane.probs`: returns the probability distribution of the quantum state, and +# +# - :func:`~pennylane.expval`: returns the expectation value of a provided observable. +# +# All of this allows for a QNode to be called like a regular Python function, executing on the device +# you specified and returning the measurement you asked for — as simple as that 🌈. +# +# Alternatively, wrapping a quantum +# function with :class:`qml.QNode ` is the same as *decorating* it with :func:`@qml.qnode(dev) `: +# + +@qml.qnode(dev) +def pl_circuit(): + """ + Equivalent to doing: + pl_circuit = qml.QNode(qml.from_qiskit(qc, measurements=qml.counts(wires=[0, 1])), dev) + """ + qml.Hadamard(0) + qml.CNOT([0, 1]) + return qml.counts(wires=[0, 1]) + +###################################################################### +# This is a minor point, but both approaches work. +# +# What's great about converting your work in Qiskit to PennyLane is that now you have access to all +# of `PennyLane's plugins `__, meaning you can run your Qiskit circuit on more than just IBM hardware! All +# you need to do is install the plugin of interest and change the name of the device in ``qml.device``. +# + +###################################################################### +# Further resources 📓 +# -------------------- +# +# There’s so much more to learn about what’s possible in PennyLane, and if you’re coming from Qiskit, +# you’re in good hands. The PennyLane-Qiskit plugin is your personal chaperone to the PennyLane +# ecosystem. You can dive deeper into what’s possible with the PennyLane-Qiskit plugin by visiting the +# `plugin homepage `__ or by checking out our +# :doc:`how-to guide for using Qiskit 1.0 with PennyLane `. +# +# Another great thing about the PennyLane ecosystem is that we have an `emporium of up-to-date +# demos `__ maintained by the +# same people that develop PennyLane. If you’re just starting out, I recommend reading our :doc:`qubit +# rotation tutorial `. +# +# Now that you’ve used PennyLane, every road in the wonderful world of quantum programming SDKs is +# open with no set speed limits 🏎️. If you have any questions about the PennyLane-Qiskit plugin, +# PennyLane, or even Qiskit, drop us a question on the `PennyLane Discussion +# Forum `__ and we’ll promptly respond. You can also keep exploring our website, +# `pennylane.ai `__, to see the latest and greatest PennyLane features, demos, +# and blogs, receive the `monthly Xanadu newsletter `__, or follow us on `LinkedIn `__ or `X +# (formerly Twitter) `__ to stay updated! +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json new file mode 100644 index 0000000000..538a4d3b49 --- /dev/null +++ b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json @@ -0,0 +1,46 @@ +{ + "title": "Your guide to PennyLane if you know Qiskit", + "authors": [ + { + "username": "isaacdevlugt" + } + ], + "dateOfPublication": "2024-07-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_guide_to_pennylane_knowing_qiskit.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_guide_to_pennylane_knowing_qiskit.png" + } + ], + "seoDescription": "This is your ultimate guide to PennyLane if you already know Qiskit.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "how_to_use_qiskit1_with_pennylane", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in new file mode 100644 index 0000000000..1d1ec52c06 --- /dev/null +++ b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in @@ -0,0 +1,4 @@ +matplotlib +pennylane +pennylane-qiskit +qiskit diff --git a/demonstrations_v2/tutorial_haar_measure/demo.py b/demonstrations_v2/tutorial_haar_measure/demo.py new file mode 100644 index 0000000000..5807b5ab34 --- /dev/null +++ b/demonstrations_v2/tutorial_haar_measure/demo.py @@ -0,0 +1,812 @@ +r""".. role:: html(raw) + :format: html + +Understanding the Haar measure +============================== + +.. meta:: + :property="og:description": Learn all about the Haar measure and how to randomly sample quantum states. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spherical_int_dtheta.png + +.. related:: + + tutorial_unitary_designs Unitary designs + quantum_volume Quantum volume + qsim_beyond_classical Beyond classical computing with qsim + tutorial_barren_plateaus Barren plateaus in quantum neural networks + + +*Author: Olivia Di Matteo — Posted: 22 March 2021. Last updated: 22 March 2021.* + +If you've ever dug into the literature about random quantum circuits, +variational ansatz structure, or anything related to the structure and +properties of unitary operations, you've likely come across a statement like the +following: "Assume that :math:`U` is sampled uniformly at random from the Haar +measure". In this demo, we're going to unravel this cryptic statement and take +an in-depth look at what it means. You'll gain an understanding of the general +concept of a *measure*, the Haar measure and its special properties, and you'll +learn how to sample from it using tools available in PennyLane and other +scientific computing frameworks. By the end of this demo, you'll be able to +include that important statement in your own work with confidence! + +.. note:: + + To get the most out of this demo, it is helpful if you are familiar with + `integration of multi-dimensional functions + `__, the `Bloch sphere + `__, and the conceptual ideas + behind `decompositions + `__ and factorizations of + unitary matrices (see, e.g., 4.5.1 and 4.5.2 of [#NandC2000]_). + +Measure +------- + +`Measure theory `__ is a +branch of mathematics that studies things that are measurable—think length, +area, or volume, but generalized to mathematical spaces and even higher +dimensions. Loosely, the measure tells you about how "stuff" is distributed and +concentrated in a mathematical set or space. An intuitive way to understand +the measure is to think about a sphere. An arbitrary point on a sphere can be +parametrized by three numbers—depending on what you're doing, you may use +Cartesian coordinates :math:`(x, y, z),` or it may be more convenient to use +spherical coordinates :math:`(\rho, \phi, \theta).` + +Suppose you wanted to compute the volume of a solid sphere with radius +:math:`r.` This can be done by integrating over the three coordinates +:math:`\rho, \phi,` and :math:`\theta.` Your first thought here may be to simply +integrate each parameter over its full range, like so: + +.. math:: + + V = \int_0^{r} \int_0^{2\pi} \int_0^{\pi} d\rho~ d\phi~ d\theta = 2\pi^2 r + +But, we know that the volume of a sphere of radius :math:`r` is +:math:`\frac{4}{3}\pi r^3,` so what we got from this integral is clearly wrong! +Taking the integral naively like this doesn't take into account the structure of +the sphere with respect to the parameters. For example, consider +two small, infinitesimal elements of area with the same difference in +:math:`\theta` and :math:`\phi,` but at different values of :math:`\theta:` + +.. figure:: /_static/demonstration_assets/haar_measure/spherical_int_dtheta.png + :align: center + :width: 50% + + +Even though the differences :math:`d\theta` and :math:`d\phi` themselves are the +same, there is way more "stuff" near the equator of the sphere than there is +near the poles. We must take into account the value of :math:`\theta` when +computing the integral! Specifically, we multiply by the function +:math:`\sin\theta`---the properties of the :math:`\sin` function mean that the +most weight will occur around the equator where :math:`\theta=\pi/2,` and the +least weight near the poles where :math:`\theta=0` and :math:`\theta=\pi.` + +Similar care must be taken for :math:`\rho.` The contribution to volume of +parts of the sphere with a large :math:`\rho` is far more than for a small +:math:`\rho`---we should expect the contribution to be proportional to +:math:`\rho^2,` given that the surface area of a sphere of radius :math:`r` is +:math:`4\pi r^2.` + +On the other hand, for a fixed :math:`\rho` and :math:`\theta,` the length of +the :math:`d\phi` is the same all around the circle. If put all these facts +together, we find that the actual expression for the integral should look like +this: + +.. math:: + + V = \int_0^r \int_0^{2\pi} \int_0^{\pi} \rho^2 \sin \theta~ d\rho~ d\phi~ + d\theta = \frac{4}{3}\pi r^3 + +These extra terms that we had to add to the integral, :math:`\rho^2 \sin +\theta`, constitute the *measure*. The measure weights portions of the sphere +differently depending on where they are in the space. While we need to know the +measure to properly integrate over the sphere, knowledge of the measure also +gives us the means to perform another important task, that of sampling points in +the space uniformly at random. We can't simply sample each parameter from the +uniform distribution over its domain—as we experienced already, this doesn't +take into account how the sphere is spread out over space. The measure describes +the distribution of each parameter and gives a recipe for sampling them in order +to obtain something properly uniform. + +The Haar measure +---------------- + +Operations in quantum computing are described by unitary matrices. +Unitary matrices, like points on a sphere, can be expressed in terms of a fixed +set of coordinates, or parameters. For example, the most general single-qubit rotation +implemented in PennyLane (:class:`~.pennylane.Rot`) is expressed in terms of three +parameters like so, + +.. math:: + + U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} + \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) + \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + + \omega)/2} \cos(\theta/2) \end{pmatrix}. + +For every dimension :math:`N,` the unitary matrices of size :math:`N \times N` +constitute the *unitary group* :math:`U(N).` We can perform operations on +elements of this group, such as apply functions to them, integrate over them, or +sample uniformly over them, just as we can do to points on a sphere. When we do +such tasks with respect to the sphere, we have to add the measure in order to +properly weight the different regions of space. The *Haar measure* provides the +analogous terms we need for working with the unitary group. + +For an :math:`N`-dimensional system, the Haar measure, often denoted by +:math:`\mu_N,` tells us how to weight the elements of :math:`U(N).` For +example, suppose :math:`f` is a function that acts on elements of :math:`U(N),` +and we would like to take its integral over the group. We must write this +integral with respect to the Haar measure, like so: + +.. math:: + + \int_{V \in U(N)} f(V) d\mu_N(V). + +As with the measure term of the sphere, :math:`d\mu_N` itself can be broken down +into components depending on individual parameters. While the Haar +measure can be defined for every dimension :math:`N,` the mathematical form gets +quite hairy for larger dimensions—in general, an :math:`N`-dimensional unitary +requires at least :math:`N^2 - 1` parameters, which is a lot to keep track of! +Therefore we'll start with the case of a single qubit :math:`(N=2),` then show +how things generalize. + +Single-qubit Haar measure +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The single-qubit case provides a particularly nice entry point because we can +continue our comparison to spheres by visualizing single-qubit states on the +Bloch sphere. As expressed above, the measure provides a recipe for sampling +elements of the unitary group in a properly uniform manner, given the structure +of the group. One useful consequence of this is that it provides a method to +sample quantum *states* uniformly at random—we simply generate Haar-random +unitaries, and apply them to a fixed basis state such as :math:`\vert 0\rangle.` + +We'll see how this works in good time. First, we'll take a look at what happens +when we ignore the measure and do things *wrong*. Suppose we sample quantum +states by applying unitaries obtained by the parametrization above, but sample +the angles :math:`\omega, \phi,` and :math:`\theta` from the flat uniform +distribution between :math:`[0, 2\pi)` (fun fact: there is a measure implicit in +this kind of sampling too! It just has a constant value, because each point is +equally likely to be sampled). + +""" + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +# set the random seed +np.random.seed(42) + +# Use the mixed state simulator to save some steps in plotting later +dev = qml.device('default.mixed', wires=1) + +@qml.qnode(dev) +def not_a_haar_random_unitary(): + # Sample all parameters from their flat uniform distribution + phi, theta, omega = 2 * np.pi * np.random.uniform(size=3) + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +num_samples = 2021 + +not_haar_samples = [not_a_haar_random_unitary() for _ in range(num_samples)] + +###################################################################### +# In order to plot these on the Bloch sphere, we'll need to do one more +# step, and convert the quantum states into Bloch vectors. +# + +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +# Used the mixed state simulator so we could have the density matrix for this part! +def convert_to_bloch_vector(rho): + """Convert a density matrix to a Bloch vector.""" + ax = np.trace(np.dot(rho, X)).real + ay = np.trace(np.dot(rho, Y)).real + az = np.trace(np.dot(rho, Z)).real + return [ax, ay, az] + +not_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in not_haar_samples]) + +###################################################################### +# With this done, let's find out where our "uniformly random" states ended up: + +def plot_bloch_sphere(bloch_vectors): + """ Helper function to plot vectors on a sphere.""" + fig = plt.figure(figsize=(6, 6)) + ax = fig.add_subplot(111, projection='3d') + fig.subplots_adjust(left=0, right=1, bottom=0, top=1) + + ax.grid(False) + ax.set_axis_off() + ax.view_init(30, 45) + + # Draw the axes (source: https://github.com/matplotlib/matplotlib/issues/13575) + x, y, z = np.array([[-1.5,0,0], [0,-1.5,0], [0,0,-1.5]]) + u, v, w = np.array([[3,0,0], [0,3,0], [0,0,3]]) + ax.quiver(x, y, z, u, v, w, arrow_length_ratio=0.05, color="black", linewidth=0.5) + + ax.text(0, 0, 1.7, r"|0⟩", color="black", fontsize=16) + ax.text(0, 0, -1.9, r"|1⟩", color="black", fontsize=16) + ax.text(1.9, 0, 0, r"|+⟩", color="black", fontsize=16) + ax.text(-1.7, 0, 0, r"|–⟩", color="black", fontsize=16) + ax.text(0, 1.7, 0, r"|i+⟩", color="black", fontsize=16) + ax.text(0,-1.9, 0, r"|i–⟩", color="black", fontsize=16) + + ax.scatter( + bloch_vectors[:,0], bloch_vectors[:,1], bloch_vectors[:, 2], c='#e29d9e', alpha=0.3 + ) + plt.show() + +plot_bloch_sphere(not_haar_bloch_vectors) + +###################################################################### +# You can see from this plot that even though our parameters were sampled from a +# uniform distribution, there is a noticeable amount of clustering around the poles +# of the sphere. Despite the input parameters being uniform, the output is very +# much *not* uniform. Just like the regular sphere, the measure is larger near +# the equator, and if we just sample uniformly, we won't end up populating that +# area as much. To take that into account we will need to sample from the proper +# Haar measure, and weight the different parameters appropriately. +# +# For a single qubit, the Haar measure looks much like the case of a sphere, +# minus the radial component. Intuitively, all qubit state vectors have length +# 1, so it makes sense that this wouldn't play a role here. The parameter that +# we will have to weight differently is :math:`\theta,` and in fact the +# adjustment in measure is identical to that we had to do with the polar axis of +# the sphere, i.e., :math:`\sin \theta.` In order to sample the :math:`\theta` +# uniformly at random in this context, we must sample from the distribution +# :math:`\hbox{Pr}(\theta) = \sin \theta.` We can accomplish this by setting up +# a custom probability distribution with +# `rv_continuous `__ +# in ``scipy``. + +from scipy.stats import rv_continuous + +class sin_prob_dist(rv_continuous): + def _pdf(self, theta): + # The 0.5 is so that the distribution is normalized + return 0.5 * np.sin(theta) + +# Samples of theta should be drawn from between 0 and pi +sin_sampler = sin_prob_dist(a=0, b=np.pi) + +@qml.qnode(dev) +def haar_random_unitary(): + phi, omega = 2 * np.pi * np.random.uniform(size=2) # Sample phi and omega as normal + theta = sin_sampler.rvs(size=1)[0] # Sample theta from our new distribution + qml.Rot(phi, theta, omega, wires=0) + return qml.state() + +haar_samples = [haar_random_unitary() for _ in range(num_samples)] +haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in haar_samples]) + +plot_bloch_sphere(haar_bloch_vectors) + +###################################################################### +# We see that when we use the correct measure, our qubit states are now +# much better distributed over the sphere. Putting this information together, +# we can now write the explicit form for the single-qubit Haar measure: +# +# .. math:: +# +# d\mu_2 = \sin \theta d\theta \cdot d\omega \cdot d\phi. +# +# Show me more math! +# ~~~~~~~~~~~~~~~~~~ +# +# While we can easily visualize the single-qubit case, this is no longer +# possible when we increase the number of qubits. Regardless, we can still +# obtain a mathematical expression for the Haar measure in arbitrary +# dimensions. In the previous section, we expressed the Haar measure in terms of +# a set of parameters that can be used to specify the unitary group +# :math:`U(2).` Such a parametrization is not unique, and in fact there are +# multiple ways to *factorize*, or decompose an :math:`N`-dimensional unitary +# operation into a set of parameters. +# +# Many of these parametrizations come to us from the study of photonics. Here, +# arbitrary operations are broken down into elementary operations involving only +# a few parameters which correspond directly to parameters of the physical +# apparatus used to implement them (beamsplitters and phase shifts). Rather than +# qubits, such operations act on modes, or *qumodes*. They are expressed as +# elements of the :math:`N`-dimensional `special unitary group +# `__. This group, written +# as :math:`SU(N),` is the continuous group consisting of all :math:`N \times N` +# unitary operations with determinant 1 (essentially like :math:`U(N),` minus +# a potential global phase). +# +# +# .. note:: +# +# Elements of :math:`SU(N)` and :math:`U(N)` can still be considered as +# multi-qubit operations in the cases where :math:`N` is a power of 2, but +# they must be translated from continuous-variable operations into qubit +# operations. (In PennyLane, this can be done by feeding the unitaries to +# the :class:`~.pennylane.QubitUnitary` operation directly. Alternatively, +# one can use *quantum compilation* to express the operations as a sequence +# of elementary gates such as Pauli rotations and CNOTs.) +# +# .. admonition:: Tip +# +# If you haven't had many opportunities to work in terms of qumodes, the +# `Strawberry Fields documentation +# `__ is a +# good starting point. +# +# For example, we saw already above that for :math:`N=2,` we can write +# +# .. math:: +# +# U(\phi, \theta, \omega) = \begin{pmatrix} e^{-i(\phi + \omega)/2} +# \cos(\theta/2) & -e^{i(\phi - \omega)/2} \sin(\theta/2) +# \\ e^{-i(\phi - \omega)/2} \sin(\theta/2) & e^{i(\phi + +# \omega)/2} \cos(\theta/2) \end{pmatrix}. +# +# +# This unitary can be factorized as follows: +# +# .. math:: +# +# U(\phi, \theta, \omega) = +# \begin{pmatrix} +# e^{-i\omega/2} & 0 \\ 0 & e^{i\omega/2} +# \end{pmatrix} +# \begin{pmatrix} +# \cos(\theta/2) & -\sin(\theta/2) \\ \sin(\theta/2) & \cos(\theta/2) +# \end{pmatrix} +# \begin{pmatrix} +# e^{-i\phi/2} & 0 \\ 0 & e^{i\phi/2} +# \end{pmatrix} +# +# The middle operation is a beamsplitter; the other two operations are phase +# shifts. We saw earlier that for :math:`N=2,` :math:`d\mu_2 = \sin\theta +# d\theta d\omega d\phi`---note how the parameter in the beamsplitter +# contributes to the measure in a different way than those of the phase +# shifts. As mentioned above, for larger values of :math:`N` there are multiple +# ways to decompose the unitary. Such decompositions rewrite elements in +# :math:`SU(N)` acting on :math:`N` modes as a sequence of operations acting +# only on 2 modes, :math:`SU(2),` and single-mode phase shifts. Shown below are +# three examples [#deGuise2018]_, [#Clements2016]_, [#Reck1994]_: +# +# .. figure:: /_static/demonstration_assets/haar_measure/unitaries.png +# :align: center +# :width: 95% +# +# +# In these graphics, every wire is a different mode. Every box represents an +# operation on one or more modes, and the number in the box indicates the number +# of parameters. The boxes containing a ``1`` are simply phase shifts on +# individual modes. The blocks containing a ``3`` are :math:`SU(2)` transforms +# with 3 parameters, such as the :math:`U(\phi, \theta, \omega)` above. Those +# containing a ``2`` are :math:`SU(2)` transforms on pairs of modes with 2 +# parameters, similar to the 3-parameter ones but with :math:`\omega = \phi.` +# +# Although the decompositions all produce the same set of operations, their +# structure and parametrization may have consequences in practice. The first [#deGuise2018]_ +# has a particularly convenient form that leads to a recursive definition +# of the Haar measure. The decomposition is formulated recursively such that an +# :math:`SU(N)` operation can be implemented by sandwiching an :math:`SU(2)` +# transformation between two :math:`SU(N-1)` transformations, like so: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/sun.svg +# :align: center +# :width: 80% +# +# | +# +# The Haar measure is then constructed recursively as a product of 3 +# terms. The first term depends on the parameters in the first :math:`SU(N-1)` +# transformation; the second depends on the parameters in the lone :math:`SU(2)` +# transformation; and the third term depends on the parameters in the other +# :math:`SU(N-1)` transformation. +# +# :math:`SU(2)` is the "base case" of the recursion—we simply have the Haar measure +# as expressed above. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su2_haar.svg +# :align: center +# :width: 25% +# +# | +# +# Moving on up, we can write elements of :math:`SU(3)` as a sequence of three +# :math:`SU(2)` transformations. The Haar measure :math:`d\mu_3` then consists +# of two copies of :math:`d\mu_2,` with an extra term in between to take into +# account the middle transformation. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su3_haar.svg +# :align: center +# :width: 80% +# +# | +# +# For :math:`SU(4)` and upwards, the form changes slightly, but still follows +# the pattern of two copies of :math:`d\mu_{N-1}` with a term in between. +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_premerge.svg +# :align: center +# :width: 90% +# +# | +# +# For larger systems, however, the recursive composition allows for some of the +# :math:`SU(2)` transformations on the lower modes to be grouped. We can take +# advantage of this and aggregate some of the parameters: +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_triangle_merge.svg +# :align: center +# :width: 100% +# +# | +# +# This leads to one copy of :math:`d\mu_{N-1},` which we'll denote as +# :math:`d\mu_{N-1}^\prime,` containing only a portion of the full set of terms +# (as detailed in [#deGuise2018]_, this is called a *coset measure*). +# +# | +# +# .. figure:: /_static/demonstration_assets/haar_measure/su4_haar.svg +# :align: center +# :width: 100% +# +# | +# +# Putting everything together, we have that +# +# .. math:: +# +# d\mu_N = d\mu_{N-1}^\prime \times \sin \theta_{N-1} +# \sin^{2(N-2)}\left(\frac{\theta_{N-1}}{2}\right) d\theta_{N-1} d\omega_{N-1} \times d\mu_{N-1} +# +# The middle portion depends on the value of :math:`N,` and the parameters +# :math:`\theta_{N-1}` and :math:`\omega_{N-1}` contained in the :math:`(N-1)`'th +# :math:`SU(N)` transformation. This is thus a convenient, systematic way to +# construct the :math:`N`-dimensional Haar measure for the unitary group. As a +# final note, even though unitaries can be parametrized in different ways, the +# underlying Haar measure is *unique*. This is a consequence of it being an +# invariant measure, as will be shown later. +# +# Haar-random matrices from the :math:`QR` decomposition +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Nice-looking math aside, sometimes you just need to generate a large number of +# high-dimensional Haar-random matrices. It would be very cumbersome to sample +# and keep track of the distributions of so many parameters; furthermore, the +# measure above requires you to parametrize your operations in a fixed way. +# There is a much quicker way to perform the sampling by taking a (slightly +# modified) `QR decomposition +# `__ of complex-valued +# matrices. This algorithm is detailed in [#Mezzadri2006]_, and consists of the +# following steps: +# +# 1. Generate an :math:`N \times N` matrix :math:`Z` with complex numbers :math:`a+bi` +# where both :math:`a` and :math:`b` are normally distributed with mean 0 and variance 1 +# (this is sampling from the distribution known as the *Ginibre ensemble*). +# 2. Compute a QR decomposition :math:`Z = QR.` +# 3. Compute the diagonal matrix :math:`\Lambda = \hbox{diag}(R_{ii}/|R_{ii}|).` +# 4. Compute :math:`Q^\prime = Q \Lambda,` which will be Haar-random. +# +# + +from numpy.linalg import qr + +def qr_haar(N): + """Generate a Haar-random matrix using the QR decomposition.""" + # Step 1 + A, B = np.random.normal(size=(N, N)), np.random.normal(size=(N, N)) + Z = A + 1j * B + + # Step 2 + Q, R = qr(Z) + + # Step 3 + Lambda = np.diag([R[i, i] / np.abs(R[i, i]) for i in range(N)]) + + # Step 4 + return np.dot(Q, Lambda) + +###################################################################### +# Let's check that this method actually generates Haar-random unitaries +# by trying it out for :math:`N=2` and plotting on the Bloch sphere. +# + +@qml.qnode(dev) +def qr_haar_random_unitary(): + qml.QubitUnitary(qr_haar(2), wires=0) + return qml.state() + +qr_haar_samples = [qr_haar_random_unitary() for _ in range(num_samples)] +qr_haar_bloch_vectors = np.array([convert_to_bloch_vector(s) for s in qr_haar_samples]) +plot_bloch_sphere(qr_haar_bloch_vectors) + +###################################################################### +# As expected, we find our qubit states are distributed uniformly over the +# sphere. This particular method is what's implemented in packages like +# ``scipy``'s `unitary_group +# `__ +# function. +# +# Now, it's clear that this method works, but it is also important to +# understand *why* it works. Step 1 is fairly straightforward—the base of our +# samples is a matrix full of complex values chosen from a typical +# distribution. This isn't enough by itself, since unitary matrices also +# have constraints—their rows and columns must be orthonormal. +# These constraints are where step 2 comes in—the outcome of a generic +# QR decomposition consists of an *orthonormal* matrix :math:`Q,` and and upper +# triangular matrix :math:`R.` Since our original matrix was complex-valued, we end +# up with a :math:`Q` that is in fact already unitary. But why not stop there? Why +# do we then perform steps 3 and 4? +# +# Steps 3 and 4 are needed because, while the QR decomposition yields a unitary, +# it is not a unitary that is properly Haar-random. In [#Mezzadri2006]_, it is +# explained that a uniform distribution over unitary matrices should also yield +# a uniform distribution over the *eigenvalues* of those matrices, i.e., every +# eigenvalue should be equally likely. Just using the QR decomposition out of +# the box produces an *uneven* distribution of eigenvalues of the unitaries! +# This discrepancy stems from the fact that the QR decomposition is not unique. +# We can take any unitary diagonal matrix :math:`\Lambda,` and re-express the decomposition +# as :math:`QR = Q\Lambda \Lambda^\dagger R = Q^\prime R^\prime.` Step 3 removes this +# redundancy by fixing a :math:`\Lambda` that depends on :math:`R,` leading to a unique +# value of :math:`Q^\prime = Q \Lambda,` and a uniform distribution of eigenvalues. +# +# .. admonition:: Try it! +# +# Use the ``qr_haar`` function above to generate random unitaries and construct +# a distribution of their eigenvalues. Then, comment out the lines for steps 3 and +# 4 and do the same—you'll find that the distribution is no longer uniform. +# Check out reference [#Mezzadri2006]_ for additional details and examples. + +###################################################################### +# Fun (and not-so-fun) facts +# -------------------------- +# +# We've now learned what the Haar measure is, and both an analytical and +# numerical means of sampling quantum states and unitary operations uniformly at +# random. The Haar measure also has many neat properties that play a role in +# quantum computing. +# +# Invariance of the Haar measure +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Earlier, we showed that the Haar measure is used when integrating functions over +# the unitary group: +# +# .. math:: +# +# \int_{V \in U(N)} f(V) d\mu_N(V). +# +# One of the defining features of the Haar measure is that it is both left and +# right *invariant* under unitary transformations. That is, +# +# .. math:: +# +# \int_{V \in U(N)} f(\color{red}{W}V) d\mu_N(V) = \int_{V \in U(N)} f(V\color{red}{W}) d\mu_N(V) = \int_{V \in U(N)} f(V) d\mu_N(V). +# +# This holds true for *any* other :math:`N\times N` unitary :math:`W!` A +# consequence of such invariance is that if :math:`V` is Haar-random, then so is +# :math:`V^T,` :math:`V^\dagger,` and any product of another unitary matrix and +# :math:`V` (where the product may be taken on either side). +# +# Another consequence of this invariance has to do with the structure of the entries +# themselves: they must all come from the same distribution. This is because the +# measure remains invariant under permutations, since permutations are unitary--- +# the whole thing still has to be Haar random no matter how the entries are ordered, +# so all distributions must be the same. The specific distribution is complex +# numbers :math:`a+bi` where both :math:`a` and :math:`b` has mean 0 and variance +# :math:`1/N` [#Meckes2014]_ (so, much like Ginibre ensemble we used in the QR decomposition +# above, but with a different variance and constraints due to orthonormality). +# +# Concentration of measure +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# An unfortunate (although interesting) property of the Haar measure is that it +# suffers from the phenomenon of `concentration of measure +# `__. Most of the +# "stuff" in the space concentrates around a certain area, and this gets worse +# as the size of the system increases. You can see the beginnings of by looking +# at the sphere. For the 3-dimensional sphere, we saw graphically how there is +# concentration around the equator, and how the measure takes that into account +# with the additional factor of :math:`\sin \theta.` This property becomes +# increasingly prominent for `higher-dimensional spheres +# `__. +# +# .. important:: +# +# The concentration described here is not referring to what we witnessed +# earlier on, when we sampled quantum states (points on the Bloch sphere) +# incorrectly and found that they clustered around the poles. However, that +# is not unrelated. Concentration of measure refers to where the measure +# itself is concentrated, and which parts of the space should be more heavily +# weighted. For the case of the sphere, it is the equatorial area, and when +# we didn't sample properly and take that concentration into account, we +# obtained an uneven distribution. +# +# Let's consider an :math:`N`-dimensional unit sphere. Points on the sphere, or +# vectors in this space, are parametrized by :math:`N-1` real coordinates. +# Suppose we have some function :math:`f` that maps points on that sphere to +# real numbers. Sample a point :math:`x` on that sphere from the uniform +# measure, and compute the value of :math:`f(x).` How close do you think the +# result will be to the mean value of the function, :math:`E[f],` over the +# entire sphere? +# +# A result called `Levy's lemma +# `__ +# [#Gerken2013]_, [#Hayden2006]_ expresses how likely it is that :math:`f(x)` is a specific +# distance away from the mean. It states that, for an :math:`x` selected +# uniformly at random, the probability that :math:`f(x)` deviates from +# :math:`E[f]` by some amount :math:`\epsilon` is bounded by: +# +# .. math:: +# +# \hbox{Pr}(|f(x) - E[f]| \ge \epsilon) \leq 2 \exp\left[-\frac{N\epsilon^2}{9\pi^3 \eta^2}\right]. +# +# A constraint on the function :math:`f` is that it must be `Lipschitz +# continuous `__, where +# :math:`\eta` is the *Lipschitz constant* of the function. The important aspect +# here is the likelihood of deviating significantly from the mean by an amount +# :math:`\epsilon` decreases exponentially with :math:`\epsilon.` Furthermore, +# increasing the dimension :math:`N` also makes the deviation exponentially less +# likely. +# +# Now, this result seems unrelated to quantum states—it concerns higher- +# dimensional spheres. However, recall that a quantum state vector is a complex +# vector whose squared values sum to 1, similar to vectors on a sphere. If you +# "unroll" a quantum state vector of dimension :math:`N = 2^n` by stacking its +# real and complex parts, you end with a vector of length :math:`2 \cdot 2^{n}` +# which ends up behaving just like a unit vector on the sphere in this +# dimension. Given that measure concentrates on spheres, and quantum state +# vectors can be converted to vectors on spheres, functions on random quantum +# states will also demonstrate concentration. +# +# This is bad news! To do useful things in quantum computing, we need a lot of +# qubits. But the more qubits we have, the more our randomly sampled states will +# look the same (specifically, random states will concentrate around the +# maximally entangled state [#Hayden2006]_). This has important consequences for +# near-term algorithms (as detailed in the next section), and any algorithm that +# involves uniform sampling of quantum states and operations. +# +# Haar measure and barren plateaus +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Suppose you are venturing out to solve a new problem using an algorithm such +# as the :doc:`variational quantum eigensolver `. A +# critical component of such methods is the choice of :doc:`variational ansatz +# `. Having now learned a bit about the properties of +# the Haar measure, you may think it would make sense to use this for the +# parametrization. Variational ansaetze are, after all, parametrized quantum +# circuits, so why not choose an ansatz that corresponds directly to a +# parametrization for Haar-random unitaries? The initial parameter selection +# will give you a state in the Hilbert space uniformly at random. Then, since +# this ansatz spans the entire Hilbert space, you're guaranteed to be able to +# represent the target ground state with your ansatz, and it should be able to +# find it with no issue ... right? +# +# Unfortunately, while such an ansatz is extremely *expressive* (i.e., it is +# capable of representing any possible state), these ansaetze actually suffer +# the most from the barren plateau problem [#McClean2018]_, [#Holmes2021]_. +# :doc:`Barren plateaus ` are regions in the +# cost landscape of a parametrized circuit where both the gradient and its +# variance approach 0, leading the optimizer to get stuck in a local minimum. +# This was explored recently in the work of [#Holmes2021]_, wherein closeness to +# the Haar measure was actually used as a metric for expressivity. The closer +# things are to the Haar measure, the more expressive they are, but they are +# also more prone to exhibiting barren plateaus. +# +# +# .. figure:: /_static/demonstration_assets/haar_measure/holmes-costlandscapes.png +# :align: center +# :width: 50% +# +# Image source: [#Holmes2021]_. A highly expressive ansatz that can access +# much of the space of possible unitaries (i.e., an ansatz capable of +# producing unitaries in something close to a Haar-random manner) is very +# likely to have flat cost landscapes and suffer from the barren plateau +# problem. +# +# It turns out that the types of ansaetze know as *hardware-efficient ansaetze* +# also suffer from this problem if they are "random enough" (this notion will be +# formalized in a future demo). It was shown in [#McClean2018]_ that this is a +# consequence of the concentration of measure phenomenon described above. The +# values of gradients and variances can be computed for classes of circuits on +# average by integrating with respect to the Haar measure, and it is shown that +# these values decrease exponentially in the number of qubits, and thus huge +# swaths of the cost landscape are simply and fundamentally flat. +# +# Conclusion +# ---------- +# +# The Haar measure plays an important role in quantum computing—anywhere +# you might be dealing with sampling random circuits, or averaging over +# all possible unitary operations, you'll want to do so with respect +# to the Haar measure. +# +# There are two important aspects of this that we have yet to touch upon, +# however. The first is whether it is efficient to sample from the Haar measure—given +# that the number of parameters to keep track of is exponential in the +# number of qubits, certainly not. But a more interesting question is do we +# *need* to always sample from the full Haar measure? The answer to this is +# "no" in a very interesting way. Depending on the task at hand, you may be able +# to take a shortcut using something called a *unitary design*. In an upcoming +# demo, we will explore the amazing world of unitary designs and their +# applications! +# +# References +# ---------- +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# +# .. [#deGuise2018] +# +# H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto. (2018) "Simple factorization +# of unitary transformations", `Phys. Rev. A 97 022328 +# `__. +# (`arXiv `__) +# +# .. [#Clements2016] +# +# W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and +# I. A. Walmsley (2016) “Optimal design for universal multiport +# interferometers”, \ `Optica 3, 1460–1465 +# `__. +# (`arXiv `__) +# +# .. [#Reck1994] +# +# M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani (1994) “Experimental +# realization of any discrete unitary operator”, `Phys. Rev. Lett.73, 58–61 +# `__. +# +# .. [#Mezzadri2006] +# +# F. Mezzadri (2006) "How to generate random matrices from the classical compact groups". +# (`arXiv `__) +# +# .. [#Meckes2014] +# +# E. Meckes (2019) `"The Random Matrix Theory of the Classical Compact Groups" +# `_, Cambridge University Press. +# +# .. [#Gerken2013] +# +# M. Gerken (2013) "Measure concentration: Levy's Lemma" +# (`lecture notes `__). +# +# +# .. [#Hayden2006] +# +# P. Hayden, D. W. Leung, and A. Winter (2006) "Aspects of generic +# entanglement", `Comm. Math. Phys. Vol. 265, No. 1, pp. 95-117 +# `__. +# (`arXiv `__) +# +# .. [#McClean2018] +# +# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven +# (2018) "Barren plateaus in quantum neural network training +# landscapes", `Nature Communications, 9(1) +# `__. +# (`arXiv `__) +# +# .. [#Holmes2021] +# +# Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles (2021) "Connecting ansatz +# expressibility to gradient magnitudes and barren plateaus". (`arXiv +# `__) +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_haar_measure/metadata.json b/demonstrations_v2/tutorial_haar_measure/metadata.json new file mode 100644 index 0000000000..9485f9df57 --- /dev/null +++ b/demonstrations_v2/tutorial_haar_measure/metadata.json @@ -0,0 +1,144 @@ +{ + "title": "Understanding the Haar measure", + "authors": [ + { + "username": "glassnotes" + } + ], + "dateOfPublication": "2021-03-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_understanding_Haar_measure.png" + } + ], + "seoDescription": "Learn all about the Haar measure and how to randomly sample quantum states.", + "doi": "", + "references": [ + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "journal": "", + "url": "" + }, + { + "id": "deGuise2018", + "type": "article", + "title": "Simple factorization of unitary transformations", + "authors": "H. de Guise, O. Di Matteo, and L. L. S\u00e1nchez-Soto", + "year": "2018", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.97.022328" + }, + { + "id": "Clements2016", + "type": "article", + "title": "Optimal design for universal multiport interferometers", + "authors": "W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and I. A. Walmsley", + "year": "2016", + "journal": "Optica", + "url": "https://www.osapublishing.org/optica/fulltext.cfm?uri=optica-3-12-1460&id=355743" + }, + { + "id": "Reck1994", + "type": "article", + "title": "Experimental realization of any discrete unitary operator", + "authors": "M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani", + "year": "1994", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58" + }, + { + "id": "Mezzadri2006", + "type": "article", + "title": "How to generate random matrices from the classical compact groups", + "authors": "F. Mezzadri", + "year": "2006", + "journal": "", + "url": "https://arxiv.org/abs/math-ph/0609050" + }, + { + "id": "Meckes2014", + "type": "book", + "title": "The Random Matrix Theory of the Classical Compact Groups", + "authors": "E. Meckes", + "year": "2019", + "publisher": "Cambridge University Press", + "url": "https://case.edu/artsci/math/esmeckes/Haar_book.pdf" + }, + { + "id": "Gerken2013", + "type": "article", + "title": "Measure concentration: Levy's Lemma", + "authors": "M. Gerken", + "year": "2013", + "url": "http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.679.2560" + }, + { + "id": "Hayden2006", + "type": "article", + "title": "Aspects of generic entanglement", + "authors": "P. Hayden, D. W. Leung, and A. Winter", + "year": "2006", + "journal": "Comm. Math. Phys.", + "volume": "265", + "number": "1", + "pages": "95-117", + "url": "https://link.springer.com/article/10.1007%2Fs00220-006-1535-6" + }, + { + "id": "McClean2018", + "type": "article", + "title": "Barren plateaus in quantum neural network training landscapes", + "authors": "J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven", + "year": "2018", + "journal": "Nature Communications", + "doi": "10.1038/s41467-018-07090-4", + "url": "" + }, + { + "id": "Holmes2021", + "type": "article", + "title": "Connecting ansatz expressibility to gradient magnitudes and barren plateaus", + "authors": "Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2101.02138" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qsim_beyond_classical", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/understanding-the-haar-measure-demo/7334" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_haar_measure/requirements.in b/demonstrations_v2/tutorial_haar_measure/requirements.in new file mode 100644 index 0000000000..8132c3977e --- /dev/null +++ b/demonstrations_v2/tutorial_haar_measure/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py new file mode 100644 index 0000000000..d573cc676f --- /dev/null +++ b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py @@ -0,0 +1,573 @@ +r""" + +Here comes the SU(N): multivariate quantum gates and gradients +============================================================== + +.. meta:: + :property="og:description": Learn about multivariate quantum gates for optimization + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_here_comes_the_sun.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_general_parshift General parameter-shift rules for quantum gradients + tutorial_unitary_designs Unitary designs and their uses in quantum computing + + +*Author: David Wierichs — Posted: 03 April 2023.* + +How do we choose an ansatz when designing a quantum circuit for a variational +quantum algorithm? And what happens if we do not start with elementary hardware-friendly +gates and compose them, but we instead use a more complex building block for local qubit +interactions and allow for multi-parameter gates from the start? +Can we differentiate such circuits, and how do they perform in optimization tasks? + +Let's find out! + +In this tutorial, you will learn about the :math:`\mathrm{SU}(N)` gate +:class:`~pennylane.SpecialUnitary`, a particular quantum gate which +can act like *any* gate on its qubits by choosing the parameters accordingly. +We will look at a custom derivative rule [#wiersema]_ for this gate and compare it to two +alternative differentiation strategies, namely finite differences and the `stochastic +parameter-shift rule `_. +Finally, we will compare the performance of +``qml.SpecialUnitary`` for a toy minimization problem to that of two other general +local gates. That is, we compare the trainability of equally expressive ansätze. + +Ansätze, so many ansätze +------------------------ + +Variational quantum algorithms have been promoted to be useful for many applications. +When designing these algorithms, a central task is to choose the quantum circuit ansatz, +which provides a parameterization of quantum states. In the course of a variational algorithm, +the circuit parameters are then optimized in order to minimize some cost function. +The choice of the ansatz can have a big impact on the quantum states that can be found +by the algorithm (expressivity) and on the optimization's behaviour (trainability). + +Typically, it also affects the +computational cost of executing the algorithm on quantum hardware and the strength of the noise +that enters the computation. Finally, the application itself influences, or +even fixes, the choice of ansatz for some variational quantum algorithms, +which can lead to constraints in the ansatz design. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_Ansatz.png + :align: center + :width: 90% + +While a number of best practices for ansatz design have been developed, +a lot is still unknown about the connection between circuit structures and the +resulting properties. Therefore, circuit design is often also based on intuition or heuristics; +an ansatz reported in the literature might just have turned out +to work particularly well for a given problem or might fall into a "standard" +category of circuits. + +If the application does not constrain the choice of ansatz, we may want to avoid choosing +somewhat arbitrary circuit ansätze that may introduce undesirable biases. +Instead, we will want to reflect the generic structure of the problem by performing a +fully general operation on the qubit register. +However, if we were to do so, the number of parameters required to produce such a general +operation would grow much too quickly. Instead, we want to consider fully general operations +*on a few qubits* and compose them into a fabric of local gates. For two-qubit operations, +the fabric could look like this: + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_SU4.png + :align: center + :width: 60% + +The general local operation can be implemented by composing a suitable combination +of elementary gates, like single-qubit rotations and CNOT gates. Alternatively, we may +choose a canonical parameterization of the group that contains all local operations, and we will +see that this is an advantageous approach for the trainability of the ansatz. + +.. figure:: ../_static/demonstration_assets/here_comes_the_sun/SUN_demo_optimization.png + :align: center + :width: 60% + +Before we can use the :math:`\mathrm{SU}(N)` gate in training, we will need to +learn how to differentiate it in a quantum circuit. But first things first: +let's start with a brief math intro — no really, just a *Liettle* bit. + +The special unitary group SU(N) and its Lie algebra +--------------------------------------------------- + +The gate we will look at is given by a specific parameterization of the +`special unitary group `__ +:math:`\mathrm{SU}(N),` where :math:`N=2^n` is the Hilbert space dimension of the gate +for :math:`n` qubits. Mathematically, the group can be defined as the set of operators +(or matrices) that can be inverted by taking their adjoint and that have +determinant :math:`1.` In general, all quantum gates acting on :math:`n` qubits are +elements of :math:`\mathrm{SU}(N)` up to a global phase. + +The group :math:`\mathrm{SU}(N)` is a `Lie group `__, +and its associated `Lie algebra `__ +is :math:`\mathfrak{su}(N).` For our purposes, it will be sufficient to look at a matrix +representation of the algebra and we may define it as + +.. math:: + + \mathfrak{su}(N) = + \{\Omega \in \mathbb{C}^{N\times N}: \Omega^\dagger=-\Omega, \operatorname{Tr}[\Omega]=0\}. + +The conditions are that the elements :math:`\Omega` are *skew-Hermitian* and that their trace vanishes. +We will use so-called canonical coordinates for the algebra which are simply the coefficients +in the Pauli basis. That is, we consider the Pauli basis elements multiplied with the +imaginary unit :math:`i,` except for the identity: + +.. math:: + + G_m \in \mathcal{P}^{(n)} = i \left\{I,X,Y,Z\right\}^n \setminus \{i I^n\}. + +A Lie algebra element :math:`\Omega` can be written as + +.. math:: + + \Omega = \sum_{m=1}^d \theta_m G_m,\quad \theta_m \in \mathbb{R} + +and those coefficients :math:`\theta` are precisely the canonical coordinates. +You may ask why we included the prefactor :math:`i` in the definition of :math:`G_m` and why we excluded +the identity (times :math:`i`). This was done to match the properties of :math:`\mathfrak{su}(N);` +the prefactor makes the basis elements skew-Hermitian and the identity would not have a +vanishing trace. Indeed, one can check that the dimension of :math:`\mathfrak{su}(N)` is +:math:`4^n-1` and that there are :math:`4^n` Pauli words, so that one Pauli word — the identity — had to go +in any case... We can use the canonical coordinates of the algebra to express a *group element* in +:math:`\mathrm{SU}(N)` as well, and the ``qml.SpecialUnitary`` gate we will use is defined as + +.. math:: + + U(\boldsymbol{\theta}) = \exp\left\{\sum_{m=1}^d \theta_m G_m \right\}. + +The number of coordinates and Pauli words in :math:`\mathcal{P}^{(n)}` is :math:`d=4^n-1.` +Therefore, this will be the number of parameters that a single ``qml.SpecialUnitary`` gate acting on +:math:`n` qubits will take. For example, it takes just three parameters for a single qubit, which +is why :class:`~pennylane.Rot` and :class:`~pennylane.U3` take three parameters and may +produce *any* single-qubit rotation. It takes a modest 15 parameters for two qubits, +but it already requires 63 parameters for three qubits. + +For unitaries generated by a single operator, i.e. of the form :math:`\exp(i\theta G),` +there is a plethora of differentiation techniques that allow us to compute its derivative. +However, a standard parameter-shift rule, for example, will not do the job if there are +non-commuting terms :math:`G_m` in the multi-parameter gate :math:`U(\boldsymbol{\theta})` above. +So how *do* we compute the derivative? + +Obtaining the gradient +---------------------- + +In variational quantum algorithms, we typically use the circuit to prepare a quantum state and +then we measure some observable :math:`H.` The resulting real-valued output is considered to be the +cost function :math:`C` that should be minimized. If we want to use gradient-based optimization for +this task, we need a method to compute the gradient :math:`\nabla C` in addition to the cost +function itself. As derived in the publication [#wiersema]_, this is possible on quantum hardware +for :math:`\mathrm{SU}(N)` gates as long as the gates themselves can be implemented. +The implementation in PennyLane follows the decomposition idea described in App. F3, but the +main text of [#wiersema]_ proposes an additional method that scales better in some scenarios +(the caveat being that this method requires additional gates to be available on the quantum hardware). +Here, we will focus on the former method. +We will not go through the entire derivation, but note the following key points: + +- The gradient with respect to all :math:`d` parameters of an :math:`\mathrm{SU}(N)` gate can be + computed using :math:`2d` auxiliary circuits. Each of the circuits contains one additional + operation compared to the original circuit, namely a ``qml.PauliRot`` gate with rotation + angles of :math:`\pm\frac{\pi}{2}.` Note that these Pauli rotations act on up to :math:`n` + qubits. +- This differentiation method uses automatic differentiation during compilation and + classical coprocessing steps, but is compatible with quantum hardware. For large :math:`n,` + the classical processing steps can quickly become prohibitively expensive. +- The computed gradient is not an approximative technique but allows for an exact computation + of the gradient on simulators. On quantum hardware, this leads to unbiased gradient + estimators. + +The implementation in PennyLane takes care of creating the additional circuits and evaluating +them, and with adequate post-processing we get the gradient :math:`\nabla C.` + +Comparing gradient methods +-------------------------- + +Before we dive into using ``qml.SpecialUnitary`` in an optimization task, let's compare +a few methods to compute the gradient with respect to the parameters of such a gate. +In particular, we will look at a finite difference (FD) approach, the stochastic parameter-shift +rule, and the custom gradient method we described above. + +For the first approach, we will use the standard central difference recipe given by + +.. math:: + + \partial_{\text{FD},\theta_j}C(\boldsymbol{\theta}) + =\left[C\left(\boldsymbol{\theta}+\frac{\delta}{2}\boldsymbol{e}_j\right) + -C\left(\boldsymbol{\theta}-\frac{\delta}{2}\boldsymbol{e}_j\right)\right] / \delta. + +Here, :math:`\delta` is a shift parameter that we need to choose and :math:`\boldsymbol{e}_j` is the +:math:`j`-th canonical basis vector, i.e. the all-zeros vector with a one in the +:math:`j`-th entry. This approach is agnostic to the differentiated function and does +not exploit its structure. + +In contrast, the stochastic parameter-shift rule is a differentiation recipe developed particularly +for multi-parameter gates like the :math:`\mathrm{SU}(N)` gates [#banchi]_. It involves the +approximate evaluation of an integral by sampling *splitting times* :math:`\tau` and +evaluating an expression close to the non-stochastic parameter-shift rule for each sample. +For more details, also consider the +:doc:`demo on the stochastic parameter-shift rule `. + +So, let's dive into a toy example and explore the three gradient methods! +We start by defining a simple one-qubit circuit that contains a single :math:`\mathrm{SU}(2)` +gate and measures the expectation value of :math:`H=\frac{3}{5} Z - \frac{4}{5} Y.` +As ``qml.SpecialUnitary`` requires automatic differentiation subroutines even for the +hardware-ready derivative recipe, we will make use of JAX. +""" + +import pennylane as qml +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") +jnp = jax.numpy + +dev = qml.device("default.qubit", wires=1) +H = 0.6 * qml.PauliZ(0) - 0.8 * qml.PauliY(0) + + +def qfunc(theta): + qml.SpecialUnitary(theta, wires=0) + return qml.expval(H) + + +circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") + +theta = jnp.array([0.4, 0.2, -0.5]) + +############################################################################## +# Now we need to set up the differentiation methods. For this demonstration, we will +# keep the first and last entry of ``theta`` fixed and only compute the gradient for the +# second parameter. This allows us to visualize the results easily and keeps the +# computational effort to a minimum. +# +# We start with the finite-difference +# recipe, using a shift scale of :math:`\delta=0.75.` This choice of :math:`\delta,` +# which is much larger than usual for numerical differentiation on classical computers, +# is adapted to the scenario of shot-based gradients (see App. F2 of [#wiersema]_). +# We compute the derivative with respect to the second entry of theta, so we will use +# the unit vector :math:`e_2:` + +unit_vector = np.array([0.0, 1.0, 0.0]) + + +def central_diff_grad(theta, delta): + plus_eval = circuit(theta + delta / 2 * unit_vector) + minus_eval = circuit(theta - delta / 2 * unit_vector) + return (plus_eval - minus_eval) / delta + + +delta = 0.75 +print(f"Central difference: {central_diff_grad(theta, delta):.5f}") + +############################################################################## +# Next up, we implement the stochastic parameter-shift rule. Of course we do not do +# so in full generality, but for the particular circuit in this example. We will +# sample ten splitting times to obtain the gradient entry. For each splitting time, +# we need to insert a Pauli-:math:`Y` rotation because :math:`\theta_2` belongs to +# the Pauli-:math:`Y` component of :math:`A(\boldsymbol{\theta}).` For this, we define +# an auxiliary circuit. + + +@jax.jit +@qml.qnode(dev, interface="jax") +def aux_circuit(theta, tau, sign): + qml.SpecialUnitary(tau * theta, wires=0) + # This corresponds to the parameter-shift evaluations of RY at 0 + qml.RY(-sign * np.pi / 2, wires=0) + qml.SpecialUnitary((1 - tau) * theta, wires=0) + return qml.expval(H) + + +def stochastic_parshift_grad(theta, num_samples): + grad = 0 + splitting_times = np.random.random(size=num_samples) + for tau in splitting_times: + # Evaluate the two-term parameter-shift rule of the auxiliar circuit + grad += aux_circuit(theta, tau, 1.0) - aux_circuit(theta, tau, -1.0) + return grad / num_samples + + +num_samples = 10 +print(f"Stochastic parameter-shift: {stochastic_parshift_grad(theta, num_samples):.5f}") + +############################################################################## +# Finally, we can make use of the custom parameter-shift rule introduced in +# [#wiersema]_, which is readily available in PennyLane. Due to the implementation +# chosen internally, the full gradient is returned; we need to pick the second +# gradient entry manually. For this small toy problem, this is +# not an issue. + +sun_grad = jax.grad(circuit) +print(f"Custom SU(N) gradient: {sun_grad(theta)[1]:.5f}") + +############################################################################## +# We obtained three values for the gradient of interest, and they do not agree. +# So what is going on here? First, let's use automatic differentiation to compute +# the exact value and see which method agrees with it (we again need to extract the +# corresponding entry from the full gradient). + +autodiff_circuit = qml.QNode(qfunc, dev, interface="jax", diff_method="parameter-shift") +exact_grad = jax.grad(autodiff_circuit)(theta)[1] +print(f"Exact gradient: {exact_grad:.5f}") + +############################################################################## +# As we can see, automatic differentiation confirmed that the custom differentiation method +# gave us the correct result. Why do the other methods disagree? +# This is because the finite difference recipe is an *approximate* gradient +# method. This means it has an error even if all circuit evaluations are +# made exact (up to numerical precision) like in the example above. +# As for the stochastic parameter-shift rule, you may already guess why there is +# a deviation: indeed, the *stochastic* nature of this method leads to derivative +# values that are scattered around the true value. It is an unbiased estimator, +# so the average will approach the exact value with increasingly many evaluations. +# To demonstrate this, let's compute the same derivative many times and plot +# a histogram of what we get. We'll do so for ``num_samples=2``, ``10`` and ``100``. + +import matplotlib.pyplot as plt + +plt.rcParams.update({"font.size": 12}) + +fig, ax = plt.subplots(1, 1, figsize=(6, 4)) +colors = ["#ACE3FF", "#FF87EB", "#FFE096"] +for num_samples, color in zip([2, 10, 100], colors): + grads = [stochastic_parshift_grad(theta, num_samples) for _ in range(1000)] + ax.hist(grads, label=f"{num_samples} samples", alpha=0.9, color=color) +ylim = ax.get_ylim() +ax.plot([exact_grad] * 2, ylim, ls="--", c="k", label="Exact") +ax.set(xlabel=r"$\partial_{SPS,\theta_2}C(\theta)$", ylabel="Frequency", ylim=ylim) +ax.legend(loc="upper left") +plt.tight_layout() +plt.show() + +############################################################################## +# As we can see, the stochastic parameter-shift rule comes with a variance +# that can be reduced at the additional cost of evaluating the auxiliary circuit +# for more splitting times. +# +# On quantum hardware, all measurement results are statistical in nature anyway. +# So how does this stochasticity combine with the +# three differentiation methods? We will not go into detail here, but refer +# to [#wiersema]_ to see how the custom differentiation rule proposed in the +# main text leads to the lowest mean squared error. For a single-qubit circuit +# similar to the one above, but with the single gate :math:`U(\boldsymbol{\theta})=\exp(iaX+ibY),` +# the derivative and its expected variance are shown in the following +# (recoloured) plot from the manuscript: +# +# .. figure:: ../_static/demonstration_assets/here_comes_the_sun/sampled_grad.png +# :align: center +# :width: 70% +# +# As we can see, the custom :math:`\mathrm{SU}(N)` parameter-shift rule produces the +# gradient estimates with the smallest variance. For small values of the parameter +# :math:`b,` which is fixed for each panel, the custom shift rule and the stochastic +# shift rule approach the standard two-term parameter-shift rule, which would be exact +# for :math:`b=0.` +# The finite difference gradient shown here was obtained using the shift +# scale :math:`\delta=0.75,` as well. As we can see, this suppresses the variance down to +# a level comparable to those of the shift rule derivatives and this shift scale is a +# reasonable trade-off between the variance and the systematic error we observed earlier. +# As shown in App. F3 of [#wiersema]_, this scale is indeed close to the optimal choice +# if we were to compute the gradient with 100 shots per circuit. +# +# Comparing ansatz structures +# --------------------------- +# +# We discussed above that there are many circuit architectures available and that choosing +# a suitable ansatz is important but can be difficult. Here, we will compare a simple ansatz +# based on the ``qml.SpecialUnitary`` gate discussed above to other approaches that fully +# parametrize the special unitary group for the respective number of qubits. +# In particular, we will compare ``qml.SpecialUnitary`` to standard decompositions from the +# literature that parametrize :math:`\mathrm{SU}(N)` with elementary gates, as well as to a +# sequence of Pauli rotation gates that also allows us to create any special unitary. +# Let us start by defining the decomposition of a two-qubit unitary. +# We choose the decomposition, which is optimal but not unique, from [#vatan]_. +# The Pauli rotation sequence is available in PennyLane +# via ``qml.ArbitraryUnitary`` and we will not need to implement it ourselves. + + +def two_qubit_decomp(params, wires): + """Implement an arbitrary SU(4) gate on two qubits + using the decomposition from Theorem 5 in + https://arxiv.org/pdf/quant-ph/0308006.pdf""" + i, j = wires + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[:3], wires=i) + qml.Rot(*params[3:6], wires=j) + qml.CNOT(wires=[j, i]) # First CNOT + qml.RZ(params[6], wires=i) + qml.RY(params[7], wires=j) + qml.CNOT(wires=[i, j]) # Second CNOT + qml.RY(params[8], wires=j) + qml.CNOT(wires=[j, i]) # Third CNOT + # Single U(2) parameterization on both qubits separately + qml.Rot(*params[9:12], wires=i) + qml.Rot(*params[12:15], wires=j) + + +# The three building blocks on two qubits we will compare are: +operations = { + ("Decomposition", "decomposition"): two_qubit_decomp, + ("PauliRot sequence",) * 2: qml.ArbitraryUnitary, + ("$\mathrm{SU}(N)$ gate", "SU(N) gate"): qml.SpecialUnitary, +} + +############################################################################## +# Now that we have the template for the composition approach in place, we construct a toy +# problem to solve using the ansätze. We will sample a random Hamiltonian in the Pauli basis +# (this time without the prefactor :math:`i,` as we want to construct a Hermitian operator) +# with independent coefficients that follow a normal distribution: +# +# .. math:: +# +# H = \sum_{m=1}^d h_m G_m,\quad h_m\sim \mathcal{N}(0,1). +# +# We will work with six qubits. + +num_wires = 6 +wires = list(range(num_wires)) +np.random.seed(62213) + +coefficients = np.random.randn(4**num_wires - 1) +# Create the matrices for the entire Pauli basis +basis = qml.ops.qubit.special_unitary.pauli_basis_matrices(num_wires) +# Construct the Hamiltonian from the normal random coefficients and the basis +H_matrix = qml.math.tensordot(coefficients, basis, axes=[[0], [0]]) +H = qml.Hermitian(H_matrix, wires=wires) +# Compute the ground state energy +E_min = min(qml.eigvals(H)) +print(f"Ground state energy: {E_min:.5f}") + +############################################################################## +# Using the toy problem Hamiltonian and the three ansätze for :math:`\mathrm{SU}(N)` operations +# from above, we create a circuit template that applies these operations in a brick-layer +# architecture with two blocks and each operation acting on ``loc=2`` qubits. +# For this we define a ``QNode``: + +loc = 2 +d = loc**4 - 1 # d = 15 for two-qubit operations +dev = qml.device("default.qubit", wires=num_wires) +# two blocks with two layers. Each layer contains three operations with d parameters +param_shape = (2, 2, 3, d) +init_params = np.zeros(param_shape) + + +def circuit(params, operation=None): + """Apply an operation in a brickwall-like pattern to a qubit register and measure H. + Parameters are assumed to have the dimensions (number of blocks, number of + wires per operation, number of operations per layer, and number of parameters + per operation), in that order. + """ + for params_block in params: + for i, params_layer in enumerate(params_block): + for j, params_op in enumerate(params_layer): + wires_op = [w % num_wires for w in range(loc * j + i, loc * (j + 1) + i)] + operation(params_op, wires_op) + return qml.expval(H) + + +qnode = qml.QNode(circuit, dev, interface="jax") +print(qml.draw(qnode)(init_params, qml.SpecialUnitary)) + +############################################################################## +# We can now proceed to prepare the optimization task using this circuit +# and an optimization routine of our choice. For simplicity, we run a vanilla gradient +# descent optimization with a fixed learning rate for 500 steps. Again, we use JAX + +# for auto-differentiation. + +learning_rate = 5e-4 +num_steps = 500 +init_params = jax.numpy.array(init_params) +grad_fn = jax.jit(jax.jacobian(qnode), static_argnums=1) +qnode = jax.jit(qnode, static_argnums=1) + +############################################################################## +# With this configuration, let's run the optimization! + +energies = {} +for (name, print_name), operation in operations.items(): + print(f"Running the optimization for the {print_name}") + params = init_params.copy() + energy = [] + for step in range(num_steps): + cost = qnode(params, operation) + params = params - learning_rate * grad_fn(params, operation) + energy.append(cost) # Store energy value + if step % 50 == 0: # Report current energy + print(f"{step:3d} Steps: {cost:.6f}") + + energy.append(qnode(params, operation)) # Final energy value + energies[name] = energy + +############################################################################## +# So, did it work? Judging from the intermediate energy values, it seems that the optimization +# outcomes differ notably. But let's take a look at the relative error in energy across the +# optimization process. + +fig, ax = plt.subplots(1, 1) +styles = [":", "--", "-"] +colors = ["#70CEFF", "#C756B2", "#FFE096"] +for (name, energy), c, ls in zip(energies.items(), colors, styles): + error = (energy - E_min) / abs(E_min) + ax.plot(list(range(len(error))), error, label=name, c=c, ls=ls, lw=2.5) + +ax.set(xlabel="Iteration", ylabel="Relative error") +ax.legend() +plt.show() + +############################################################################## +# We find that the optimization indeed performs significantly better for ``qml.SpecialUnitary`` +# than for the other two general unitaries, while using the same number of parameters and +# preserving the expressibility of the circuit ansatz. This +# means that we found a particularly well-trainable parameterization of the local unitaries which +# allows us to reduce the energy of the prepared quantum state more easily while maintaining the +# number of parameters. +# +# +# Conclusion +# ---------- +# +# To summarize, in this tutorial we introduced ``qml.SpecialUnitary``, a multi-parameter +# gate that can act like *any* gate on the qubits it is applied to and that is constructed +# with Lie theory in mind. We discussed three methods of differentiating quantum circuits +# that use this gate, showing that a new custom parameter-shift rule presented in +# [#wiersema]_ is particularly suitable to produce unbiased gradient estimates with the +# lowest variance. Afterwards, we used this differentiation technique when comparing +# the performance of ``qml.SpecialUnitary`` to that of other gates that can act +# like *any* gate locally. For this, we ran a gradient-based optimization for a toy model +# Hamiltonian and found that ``qml.SpecialUnitary`` is particularly well-trainable, achieving +# lower energies significantly quicker than the other tested gates. +# +# There are still exciting questions to answer about ``qml.SpecialUnitary``: How can the +# custom parameter-shift rule be used for other gates, and what does the so-called +# *Dynamical Lie algebra* of these gates have to do with it? How can we implement +# the ``qml.SpecialUnitary`` gate on hardware? Is the unitary time evolution implemented +# by this gate special in a physical sense? +# +# The answers to some, but not all, of these questions can be found in [#wiersema]_. +# We are certain that there are many more interesting aspects of this gate to be uncovered! +# If you want to learn more, consider the other literature references below, +# as well as the documentation of :class:`~pennylane.SpecialUnitary`. +# +# References +# ---------- +# +# .. [#vatan] +# +# Farrokh Vatan and Colin Williams, +# "Optimal Quantum Circuits for General Two-Qubit Gates", +# `arXiv:quant-ph/0308006 `__ (2003). +# +# .. [#wiersema] +# +# R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran. +# "Here comes the SU(N): multivariate quantum gates and gradients" +# `arXiv:2303.11355 `__ (2023). +# +# .. [#banchi] +# +# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of +# General Quantum Evolution with the Stochastic Parameter Shift Rule." +# `Quantum 5, 386 `__ (2021). +# diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/metadata.json b/demonstrations_v2/tutorial_here_comes_the_sun/metadata.json new file mode 100644 index 0000000000..971e6c8615 --- /dev/null +++ b/demonstrations_v2/tutorial_here_comes_the_sun/metadata.json @@ -0,0 +1,77 @@ +{ + "title": "Here comes the SU(N): multivariate quantum gates and gradients", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2023-04-03T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/here_comes_the_sun/thumbnail_tutorial_here_comes_the_sun.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_here_comes_the_sun.png" + } + ], + "seoDescription": "Learn about multivariate quantum gates for optimization", + "doi": "", + "references": [ + { + "id": "vatan", + "type": "article", + "title": "Optimal Quantum Circuits for General Two-Qubit Gates", + "authors": "Farrokh Vatan and Colin Williams", + "year": "2003", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0308006" + }, + { + "id": "wiersema", + "type": "article", + "title": "Here comes the SU(N): multivariate quantum gates and gradients", + "authors": "R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran", + "year": "2023", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2303.11355" + }, + { + "id": "banchi", + "type": "article", + "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule.", + "authors": "Leonardo Banchi and Gavin E. Crooks", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-01-25-386/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/requirements.in b/demonstrations_v2/tutorial_here_comes_the_sun/requirements.in new file mode 100644 index 0000000000..bb242ed39b --- /dev/null +++ b/demonstrations_v2/tutorial_here_comes_the_sun/requirements.in @@ -0,0 +1,5 @@ +jax +jaxlib +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py new file mode 100644 index 0000000000..668029bc10 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py @@ -0,0 +1,417 @@ +r"""How to build spin Hamiltonians +================================== +Systems of interacting spins provide simple but powerful models for studying problems in physics, +chemistry, and quantum computing. PennyLane offers a comprehensive set of tools that enables users +to intuitively construct a broad range of spin Hamiltonians. Here we show you how to use these tools +to easily construct spin Hamiltonians for the `Fermi–Hubbard model `__, +the `Heisenberg model `__, +the `transverse-field Ising model `__, +`Kitaev's honeycomb model `__, +the `Haldane model `__, +the `Emery model `__, +and more. And you can also already explore some of these models in detail using `PennyLane Spin Systems Datasets `__! + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_how_to_build_spin_hamiltonians.png + :align: center + :width: 70% + :target: javascript:void(0) +""" + +###################################################################### +# Hamiltonian templates +# --------------------- +# PennyLane provides a set of built-in +# `functions `__ +# in the `qml.spin `__ module for +# constructing spin Hamiltonians with minimal input needed from the user: we only need to specify +# the lattice that describes spin sites and the parameters that describe the interactions in our +# system. Let’s look at some examples for the models that are currently supported in PennyLane. +# +# Fermi–Hubbard model +# ^^^^^^^^^^^^^^^^^^^ +# The `Fermi–Hubbard model Hamiltonian `__ +# has a kinetic energy component, which is parameterized by a hopping parameter :math:`t`, and a +# potential energy component which is parameterized by the on-site interaction strength, :math:`U`: +# +# .. math:: +# +# H = -t\sum_{\left< i,j \right>, \sigma} c_{i\sigma}^{\dagger}c_{j\sigma} + U\sum_{i}n_{i \uparrow} n_{i\downarrow}. +# +# The terms :math:`c^{\dagger}, c` are the creation and annihilation operators, +# :math:`\left< i,j \right>` represents the indices of neighbouring spins, :math:`\sigma` is the +# spin degree of freedom, and :math:`n_{i \uparrow}, n_{i \downarrow}` are the number operators for +# the spin-up and spin-down fermions at site :math:`i`, denoted by :math:`0` and :math:`1` +# respectively. This model is often used as a simplified model to investigate superconductivity. +# +# The Fermi–Hubbard Hamiltonian can be +# constructed in PennyLane by passing the hopping and interaction parameters to the +# :func:`~.pennylane.spin.fermi_hubbard` function. We also need to specify the shape of the lattice +# that describes the positions of the spin sites. We will show an example here, and the full list of +# supported lattice shapes is +# provided in the :func:`~.pennylane.spin.generate_lattice` documentation. +# +# We can also define the +# number of lattice cells we would like to include in our Hamiltonian as a list of integers for +# :math:`x, y, z` directions, depending on the lattice shape. Here we generate the Fermi–Hubbard +# Hamiltonian on a ``square`` lattice of shape :math:`2 \times 2`. The ``square`` lattice is +# constructed from unit cells that contain only one site such that we will have +# :math:`2 \times 2 = 4` sites in total. We will provide more details on constructing lattices in +# the following sections. + +import pennylane as qml + +n_cells = [2, 2] +hopping = 0.2 +onsite = 0.3 + +hamiltonian = qml.spin.fermi_hubbard('square', n_cells, hopping, onsite) +print('Hamiltonian:\n') +hamiltonian + +###################################################################### +# Let's also visualize the square lattice we created. To do that, we need to +# create a simple plotting function, as well as the helper function +# :func:`~.pennylane.spin.generate_lattice`, which you will learn more about in the next sections. + +import matplotlib.pyplot as plt + +def plot(lattice, figsize=None, showlabel=True): + + # initialize the plot + if not figsize: + figsize = lattice.n_cells[::-1] + + plt.figure(figsize=figsize) + + # get lattice nodes and edges and plot them + nodes = lattice.lattice_points + + for edge in lattice.edges: + start_index, end_index, color = edge + start_pos, end_pos = nodes[start_index], nodes[end_index] + + x_axis = [start_pos[0], end_pos[0]] + y_axis = [start_pos[1], end_pos[1]] + plt.plot(x_axis, y_axis, color='gold') + + plt.scatter(nodes[:,0], nodes[:,1], color='dodgerblue', s=100) + + if showlabel: + for index, pos in enumerate(nodes): + plt.text(pos[0]-0.2, pos[1]+0.1, str(index), color='gray') + + plt.axis("off") + plt.show() + +lattice = qml.spin.generate_lattice('square', n_cells) +plot(lattice) + +###################################################################### +# We currently support the following in-built lattice shapes: ``chain``, ``square``, +# ``rectangle``, ``triangle``, ``honeycomb``, ``kagome``, ``lieb``, ``cubic``, ``bcc``, ``fcc`` +# and ``diamond``. More details are provided +# `here `__. +# +# Heisenberg model +# ^^^^^^^^^^^^^^^^ +# The `Heisenberg model Hamiltonian `__ +# is defined as +# +# .. math:: +# +# H = J\sum_{ < i, j >}(\sigma_i ^ x\sigma_j ^ x + \sigma_i ^ y\sigma_j ^ y + \sigma_i ^ z\sigma_j ^ z), +# +# where :math:`J` is the coupling constant and :math:`\sigma` is a Pauli operator. The Hamiltonian +# can be constructed on a ``triangle`` lattice as follows. + +coupling = [0.5, 0.5, 0.5] +hamiltonian = qml.spin.heisenberg('triangle', n_cells, coupling) + +lattice = qml.spin.generate_lattice('triangle', n_cells) +plot(lattice) + +###################################################################### +# Transverse-field Ising model +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# The `transverse-field Ising model (TFIM) Hamiltonian +# `__ +# is defined as +# +# .. math:: +# +# H = -J \sum_{} \sigma_i^{z} \sigma_j^{z} - h\sum_{i} \sigma_{i}^{x}, +# +# where :math:`J` is the coupling constant, :math:`h` is the strength of the transverse magnetic +# field and :math:`\sigma` is a Pauli operator. The Hamiltonian can be constructed on the +# ``honeycomb`` lattice as follows. + +coupling, h = 0.5, 1.0 +hamiltonian = qml.spin.transverse_ising('honeycomb', n_cells, coupling, h) + +lattice = qml.spin.generate_lattice('honeycomb', n_cells) +plot(lattice) + +###################################################################### +# Kitaev's honeycomb model +# ^^^^^^^^^^^^^^^^^^^^^^^^ +# The `Kitaev honeycomb model Hamiltonian `__ +# is defined on the honeycomb lattice, as +# +# .. math:: +# +# H = K_X \sum_{\langle i,j \rangle \in X}\sigma_i^x\sigma_j^x + +# \:\: K_Y \sum_{\langle i,j \rangle \in Y}\sigma_i^y\sigma_j^y + +# \:\: K_Z \sum_{\langle i,j \rangle \in Z}\sigma_i^z\sigma_j^z, +# +# where :math:`\sigma` is a Pauli operator and the parameters :math:`K_X`, :math:`K_Y`, :math:`K_Z` +# are the coupling constants in each direction. The Hamiltonian can be constructed as follows. + +coupling = [0.5, 0.6, 0.7] +hamiltonian = qml.spin.kitaev(n_cells, coupling) + +###################################################################### +# Haldane model +# ^^^^^^^^^^^^^ +# The `Haldane model Hamiltonian `__ +# is defined as +# +# .. math:: +# +# H = - t^{1} \sum_{\langle i,j \rangle, \sigma} +# c_{i\sigma}^\dagger c_{j\sigma} +# - t^{2} \sum_{\langle\langle i,j \rangle\rangle, \sigma} +# \left( e^{i\phi} c_{i\sigma}^\dagger c_{j\sigma} + e^{-i\phi} c_{j\sigma}^\dagger c_{i\sigma} \right), +# +# where :math:`t^{1}` is the hopping amplitude between neighbouring sites +# :math:`\langle i,j \rangle`, :math:`t^{2}` is the hopping amplitude between next-nearest neighbour +# sites :math:`\langle \langle i,j \rangle \rangle`, :math:`\phi` is the phase factor that breaks +# time-reversal symmetry in the system, and :math:`\sigma` is the spin degree of freedom. This +# function assumes two fermions with opposite spins on each lattice site. The Hamiltonian can be +# constructed on the ``kagome`` lattice using the following code. + +hopping = 0.5 +hopping_next = 1.0 +phi = 0.1 +hamiltonian = qml.spin.haldane('kagome', n_cells, hopping, hopping_next, phi) + +lattice = qml.spin.generate_lattice('kagome', n_cells) +plot(lattice) + +###################################################################### +# Emery model +# ^^^^^^^^^^^ +# The `Emery model Hamiltonian `__ +# is defined as +# +# .. math:: +# +# H = - t \sum_{\langle i,j \rangle, \sigma} c_{i\sigma}^{\dagger}c_{j\sigma} +# + U \sum_{i} n_{i \uparrow} n_{i\downarrow} + V \sum_{} (n_{i \uparrow} + +# n_{i \downarrow})(n_{j \uparrow} + n_{j \downarrow})\ , +# +# where :math:`t` is the hopping term representing the kinetic energy of electrons, +# :math:`U` is the on-site Coulomb interaction representing the repulsion between electrons, +# :math:`V` is the intersite coupling, +# :math:`\sigma` is the spin degree of freedom, and :math:`n_{k \uparrow}`, :math:`n_{k \downarrow}` +# are number operators for spin-up and spin-down fermions at site :math:`k`. This function assumes +# two fermions with opposite spins on each lattice site. The Hamiltonian can be +# constructed on the ``lieb`` lattice as follows. + +hopping = 0.5 +coulomb = 1.0 +intersite_coupling = 0.2 +hamiltonian = qml.spin.emery('lieb', n_cells, hopping, coulomb, intersite_coupling) + +lattice = qml.spin.generate_lattice('lieb', n_cells) +plot(lattice) + +###################################################################### +# Building Hamiltonians manually +# ------------------------------ +# The Hamiltonian template functions are great and simple tools for someone who just wants to build +# a Hamiltonian quickly. PennyLane also offers tools for building customized Hamiltonians. Let’s learn +# how to use these tools by constructing the Hamiltonian for the +# `transverse-field Ising model `__ +# on a two-dimensional lattice. +# +# The Hamiltonian is represented as: +# +# .. math:: +# +# H = -J \sum_{\left< i,j \right>} \sigma_i^{z} \sigma_j^{z} - h\sum_{i} \sigma_{i}^{x}, +# +# where :math:`J` is the coupling defined for the Hamiltonian, :math:`h` is the strength of +# transverse magnetic field, and :math:`\left< i,j \right>` represents the indices of neighbouring +# spins. +# +# Our approach for doing this is to construct a lattice that represents the spin sites and their +# connectivity. This is done by using the :class:`~.pennylane.spin.Lattice` class, which can be +# constructed either by calling the helper function :func:`~.pennylane.spin.generate_lattice` or by +# manually constructing the object. Let's see examples of both methods. First we use +# :func:`~.pennylane.spin.generate_lattice` to construct a square lattice containing +# :math:`3 \times 3 = 9` cells. Because each cell of the ``square`` lattice contains only one +# site, we get :math:`9` sites in total, which are all connected to their nearest neighbor. + +lattice = qml.spin.generate_lattice('square', [3, 3]) + +###################################################################### +# To visualize this lattice, we use the plotting function we created before. + +plot(lattice) + +###################################################################### +# Now, we construct a lattice manually by explicitly defining the positions of the sites in a +# unit cell, the primitive translation vectors defining the lattice and the number of cells in each +# direction [#ashcroft]_. Recall that a unit cell is the smallest repeating unit of a lattice. +# +# Let's create a square-octagon [#jovanovic]_ lattice manually. Our lattice +# can be constructed in a two-dimensional Cartesian coordinate system with two primitive +# translation vectors defined as unit vectors along the :math:`x` and :math:`y` directions, and four +# lattice point located inside the unit cell. We also assume that the lattice has three unit cells +# along each direction. + +from pennylane.spin import Lattice + +positions = [[0.2, 0.5], [0.5, 0.2], + [0.5, 0.8], [0.8, 0.5]] # coordinates of sites +vectors = [[1, 0], [0, 1]] # primitive translation vectors +n_cells = [3, 3] # number of unit cells in each direction + +lattice = Lattice(n_cells, vectors, positions, neighbour_order=2) + +plot(lattice, figsize = (5, 5), showlabel=False) + +###################################################################### +# Constructing the lattice manually is more flexible, while :func:`~.pennylane.spin.generate_lattice` +# only works for some +# `predefined lattice shapes `__. +# +# Now that we have the lattice, we can use its attributes, e.g., edges and vertices, to construct +# our transverse-field Ising model Hamiltonian. For instance, we can access the number of sites +# with ``lattice.n_sites`` and the indices that define each edge with ``lattice.edges_indices``. For +# the full list of attributes, please see the documentation of the :class:`~.pennylane.spin.Lattice` +# class. We also need to define the coupling, :math:`J`, and onsite parameters of the +# Hamiltonian, :math:`h`. + +from pennylane import X, Y, Z + +coupling, onsite = 0.25, 0.75 + +hamiltonian = 0.0 +# add the one-site terms +for vertex in range(lattice.n_sites): + hamiltonian += -onsite * X(vertex) + +# add the coupling terms +for edge in lattice.edges_indices: + i, j = edge[0], edge[1] + hamiltonian += - coupling * (Z(i) @ Z(j)) + +hamiltonian + +###################################################################### +# In this example, we just used the built-in attributes of our custom lattice without further +# customising them. The lattice can be constructed in a more flexible way that allows us to build +# fully general spin Hamiltonians. Let's look at an example. +# +# Building anisotropic Hamiltonians +# --------------------------------- +# Now we work on a more complicated Hamiltonian. We construct the anisotropic square-trigonal +# [#jovanovic]_ model, where the coupling parameters +# depend on the orientation of the bonds. We can construct the Hamiltonian by building the +# lattice manually and adding custom edges between the nodes. For instance, to define a custom +# ``XX`` edge with the coupling constant :math:`0.5` between nodes 0 and 1, we use the following. + +custom_edge = [(0, 1), ('XX', 0.5)] + +###################################################################### +# Let's now build our Hamiltonian. We first define the unit cell by specifying the positions of the +# nodes and the translation vectors and then define the number of unit cells in each +# direction [#jovanovic]_. + +positions = [[0.1830, 0.3169], + [0.3169, 0.8169], + [0.6830, 0.1830], + [0.8169, 0.6830]] + +vectors = [[1, 0], [0, 1]] + +n_cells = [3, 3] + +###################################################################### +# Let's plot the lattice to see what it looks like. + +plot(Lattice(n_cells, vectors, positions), figsize=(5, 5)) + +###################################################################### +# Now we add custom edges to the lattice. In our example, we define four types of custom +# edges: the first type is the one that connects node 0 to 1, the second type is defined to connect +# node 0 to 2, and the third and fourth types connect node 1 to 3 and 2 to 3, respectively. Note that +# this is an arbitrary selection. You can define any type of custom edge you would like. + +custom_edges = [[(0, 1), ('XX', 0.5)], + [(0, 2), ('YY', 0.6)], + [(1, 3), ('ZZ', 0.7)], + [(2, 3), ('ZZ', 0.7)]] + +lattice = Lattice(n_cells, vectors, positions, custom_edges=custom_edges) + +###################################################################### +# Let's print the lattice edges and check that our custom edge types are set correctly. + +print(lattice.edges) + +###################################################################### +# You can compare these edges with the lattice plotted above and verify the correct translation of +# the edges over the entire lattice sites. +# +# Now we pass the lattice object to the :func:`~.pennylane.spin.spin_hamiltonian` function, which is +# a helper function that constructs a Hamiltonian from a lattice object. + +hamiltonian = qml.spin.spin_hamiltonian(lattice=lattice) + +###################################################################### +# Alternatively, you can build the Hamiltonian manually by looping over the custom edges +# to build the Hamiltonian. + +opmap = {'X': X, 'Y': Y, 'Z': Z} + +hamiltonian = 0.0 +for edge in lattice.edges: + i, j = edge[0], edge[1] + k, l = edge[2][0][0], edge[2][0][1] + hamiltonian += opmap[k](i) @ opmap[l](j) * edge[2][1] + +hamiltonian + +###################################################################### +# You can see that it is easy and intuitive to construct this anisotropic Hamiltonian with the tools +# available in the `qml.spin `__ module. You can +# use these tools to construct custom Hamiltonians for other interesting systems. +# +# Conclusion +# ---------- +# The `spin module `__ in PennyLane provides +# a set of powerful tools for constructing spin Hamiltonians. +# Here we learned how to use these tools to construct predefined Hamiltonian templates such as the +# Fermi–Hubbard Hamiltonian. This can be done with our built-in functions that currently support +# several commonly used spin models and a variety of lattice shapes. More importantly, PennyLane +# provides easy-to-use function to manually build spin Hamiltonians on customized lattice structures +# with anisotropic interactions between the sites. This can be done intuitively using the +# :class:`~.pennylane.spin.Lattice` object and provided helper functions. The versatility of the new +# spin functionality allows you to construct any new spin Hamiltonian quickly and intuitively. +# +# References +# ---------- +# +# .. [#ashcroft] +# +# N. W. Ashcroft, D. N. Mermin, +# "Solid State Physics", Chapter 4, New York: Saunders College Publishing, 1976. +# +# .. [#jovanovic] +# +# D. Jovanovic, R. Gajic, K. Hingerl, +# "Refraction and band isotropy in 2D square-like Archimedean photonic crystal lattices", +# Opt. Express 16, 4048, 2008. +# diff --git a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json new file mode 100644 index 0000000000..7389b9ce35 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json @@ -0,0 +1,71 @@ +{ + "title": "How to build spin Hamiltonians", + "authors": [ + { + "username": "ddhawan" + }, + { + "username": "soran" + } + ], + "dateOfPublication": "2024-12-05T00:00:00+00:00", + "dateOfLastModification": "2024-12-05T00:00:00+00:00", + "categories": [ + "Getting Started", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_build_spin_hamiltonians.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_build_spin_hamiltonians.png" + } + ], + "seoDescription": "Learn how to build spin Hamiltonians with PennyLane.", + "doi": "", + "references": [ + { + "id": "ashcroft", + "type": "book", + "title": "Solid State Physics", + "authors": "N. W. Ashcroft, D. N. Mermin", + "year": "1976", + "publisher": "New York: Saunders College Publishing", + "url": "https://en.wikipedia.org/wiki/Ashcroft_and_Mermin" + }, + { + "id": "jovanovic", + "type": "article", + "title": "Refraction and band isotropy in 2D square-like Archimedean photonic crystal lattices", + "authors": "D. Jovanovic, R. Gajic, K. Hingerl", + "journal": "Opt. Express", + "volume": "16", + "pages": "4048--4058", + "year": "2008", + "url": "https://opg.optica.org/oe/fulltext.cfm?uri=oe-16-6-4048&id=154856" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_fermionic_operators", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_isingmodel_PyTorch", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py new file mode 100644 index 0000000000..8eed5c7337 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py @@ -0,0 +1,227 @@ +r"""How to collect statistics of mid-circuit measurements +========================================================= + +Measuring qubits in the middle of a quantum circuit execution can be useful in many ways. +From understanding the inner workings of a circuit, hardware characterization, +modeling and error mitigation, to error correction, algorithmic improvements and even up to full +computations encoded as measurements in measurement-based quantum computation (MBQC). + +Before turning to any of these advanced topics, it is worthwhile to familiarize ourselves with +the syntax and features around mid-circuit measurements (MCMs). In this how-to, we will focus on +extracting statistics about measurements that are performed while a quantum circuit is up and +running --- mid-circuit measurement statistics! + +.. figure:: ../_static/demonstration_assets/how_to_collect_mcm_stats/socialthumbnail_how_to_collect_mcm_stats.png + :align: center + :width: 50% + + +""" + +###################################################################### +# Defining the circuit ansatz +# --------------------------- +# +# We start by defining a quantum circuit ansatz that switches between a layer of simple rotation gates +# (:class:`~.pennylane.RX`), mid-circuit measurements(:func:`~.pennylane.measure`), and a layer +# of entangling two-qubit gates (:class:`~.pennylane.CNOT`) between the first and all other qubits. +# The ansatz then returns the list of four MCM values, so that we can process them further in a full quantum circuit +# As we will treat the first wire differently than all other wires, we define it as separate variable. +# +# Along the way, we perform some standard imports and set a randomness seed. +# + +import pennylane as qml +import numpy as np + +np.random.seed(511) + +first_wire = 0 +other_wires = [1, 2, 3] + + +def ansatz(x): + mcms = [] + + # Rotate all qubits + for w, x_ in enumerate(x): + qml.RX(x_, w) + + # Measure first qubit + mcms.append(qml.measure(first_wire)) + + # Entangle all qubits with first qubit + for w in other_wires: + qml.CNOT([first_wire, w]) + + # Measure and reset all qubits but the first + for w in other_wires: + mcms.append(qml.measure(w, reset=True)) + + return mcms + + +###################################################################### +# A quantum circuit with basic MCM statistics +# ------------------------------------------- +# +# Before we post-process the mid-circuit measurements in this ansatz or expand the ansatz itself, +# let's construct a simple :class:`~.pennylane.QNode` and look at the statistics of the four +# performed MCMs: +# +# 1. We compute the probability vector for the MCM on the first qubit, and +# +# 2. count the bit strings sampled from the other three MCMs. +# +# To implement the ``QNode``, we also define a shot-based qubit device. +# + +dev = qml.device("default.qubit", shots=100) + + +@qml.qnode(dev) +def simple_node(x): + # apply the ansatz, and collect mid-circuit measurements. mcm1 is the measurement + # of wire 0, and mcms2 is a list of measurements of the other wires. + mcm1, *mcms2 = ansatz(x) + return qml.probs(op=mcm1), qml.counts(mcms2) + + +###################################################################### +# Before executing the circuit, let's draw it! For this, we sample some random parameters, one +# for each qubit, and call the Matplotlib drawer :func:`~.pennylane.draw_mpl`. +# + +x = np.random.random(4) +fig, ax = qml.draw_mpl(simple_node)(x) + +###################################################################### +# Neat, let's move on to executing the circuit. We apply the ``defer_measurements`` transform to +# the ``QNode`` because it allows for fast evaluation even with many shots. + +probs, counts = qml.defer_measurements(simple_node)(x) +print(f"Probability vector of first qubit MCM: {np.round(probs, 5)}") +print(f"Bit string counts on other qubits: {counts}") + +###################################################################### +# We see that the first qubit has a probability of about :math:`20\%` to be in the state +# :math:`|1\rangle` after the rotation. We also observe that we only sampled bit strings from +# the other three qubits for which the second and third bit are identical. +# (Quiz question: Is this expected behaviour or did we just not sample often enough? +# Find the answer at the end of the how-to!) +# +# Post-processing mid-circuit measurements +# ---------------------------------------- +# We now set up a more interesting ``QNode``. It executes the ``ansatz`` from above twice and +# compares the obtained MCMs (note that we did not define ``comparing_function`` yet, we will +# get to that shortly): +# + + +@qml.qnode(dev) +def interesting_qnode(x): + first_mcms = ansatz(x) + second_mcms = ansatz(-x) + output = comparing_function(first_mcms, second_mcms) + return qml.counts(output) + + +###################################################################### +# Before we can run this more interesting ``QNode``, we need to actually specify the +# ``comparing_function``. We ask the following question: Is the measurement on the first qubit +# equal between the two sets of MCMs, and do the other three measured values summed together +# have the same parity, i.e. is the number of 1s odd in both sets or even in both sets? +# +# In contrast to quantum measurements at the end of a :class:`~.pennylane.QNode`, +# PennyLane supports a number of unary and binary operators for MCMs even *within* +# ``QNode``\ s. This enables us to phrase the question above as a boolean function. +# Consider the +# `introduction on measurements `_ +# and the documentation if you want to learn more about the supported operations. +# + + +def comparing_function(first_mcms, second_mcms): + """A function that compares two sets of MCM outcomes.""" + equal_first = first_mcms[0] == second_mcms[0] + # Computing the parity can be done with the bitwise "and" operator `&` + # with the number 1. Note that Python's and is not supported between MCMs! + first_parity = sum(first_mcms[1:]) & 1 + second_parity = sum(second_mcms[1:]) & 1 + equal_parity = first_parity == second_parity + return equal_first & equal_parity + + +###################################################################### +# We can again inspect this ``QNode`` by drawing it: +# + +fig, ax = qml.draw_mpl(interesting_qnode)(x) + +###################################################################### +# Note how all mid-circuit measurements feed into the classical output variable. +# +# Finally we may run the ``QNode`` and obtain the statistics for our comparison function: +# + +print(qml.defer_measurements(interesting_qnode)(x)) + +###################################################################### +# We find that our question is answered with "yes" in about :math:`2/3` of all samples. +# Turning up the number of shots lets us compute this ratio more precisely: +# + +num_shots = 10000 +counts = qml.defer_measurements(interesting_qnode)(x, shots=num_shots) +p_yes = counts[True] / num_shots +p_no = counts[False] / num_shots +print(f'The probability to answer with "yes" / "no" is {p_yes:.5f} / {p_no:.5f}') + +###################################################################### +# This concludes our how-to on statistics and post-processing of mid-circuit measurements. +# If you would like to explore mid-circuit measurement applications, be sure to check out +# our :doc:`MBQC demo ` and the +# :doc:`demo on quantum teleportation `. Or, see all available functionality in our +# `measurements quickstart page `_. +# +# For performance considerations, take a look at +# :func:`~.pennylane.defer_measurements` and :func:`~.pennylane.dynamic_one_shot`, +# two simulation techniques that PennyLane uses under the hood to run circuits +# like the ones in this how-to. +# +# And finally, the answer to our quiz question above: It's not expected that we +# never see bit strings with differing second and third bits. +# Sampling more shots eventually reveals this, even though they remain rare: + +probs, counts = qml.defer_measurements(simple_node)(x, shots=10000) +print(f"Bit string counts on last three qubits: {counts}") + +###################################################################### +# Supported MCM return types +# -------------------------- +# +# Before finishing, we discuss the return types that are supported for (postprocessed) MCMs. +# Depending on the processing applied to the MCM results, not all return types are supported. +# ``qml.probs(mcm0 * mcm1)``, for example, is not a valid return value, because it is not clear +# which probabilities are being requested. +# +# Furthermore, available return types depend on whether or not the device is +# shot-based (``qml.sample`` can not be returned if the device is not sampling). +# Overall, **all combinations of post-processing and all of** +# :func:`~.pennylane.expval`, +# :func:`~.pennylane.var`, +# :func:`~.pennylane.probs`, +# :func:`~.pennylane.sample`, **and** +# :func:`~.pennylane.counts`, +# **are supported** for mid-circuit measurements with the following exceptions: +# +# - ``qml.sample`` and ``qml.counts`` are not supported for ``shots=None``. +# - ``qml.probs`` is not supported for MCMs collected in arithmetic expressions. +# - ``qml.expval`` and ``qml.var`` are not supported for sequences of MCMs. +# ``qml.probs``, ``qml.sample``, and ``qml.counts`` are supported for sequences but +# only if they do not contain arithmetic expressions of these MCMs. +# +# For more details also consider the +# `measurements quickstart page `_ +# and the documentation of :func:`~.pennylane.measure`. +# diff --git a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json new file mode 100644 index 0000000000..7022601b2d --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json @@ -0,0 +1,50 @@ +{ + "title": "How to collect statistics of mid-circuit measurements", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_collect_mcm_stats.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_collect_mcm_stats.png" + } + ], + "seoDescription": "Learn how to collect statistics about measurements performed during a quantum circuit.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_teleportation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_create_dynamic_mcm_circuits", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py new file mode 100644 index 0000000000..82acd759d5 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py @@ -0,0 +1,252 @@ +r"""How to create dynamic circuits with mid-circuit measurements +================================================================ + +Measuring qubits in the middle of a quantum circuit execution can be useful in many ways. +From understanding the inner workings of a circuit, hardware characterization, +modeling and :doc:`error mitigation `, to error correction, +:doc:`quantum teleportation `, algorithmic improvements and even up to full +computations encoded as measurements in :doc:`measurement-based quantum computation (MBQC) `. + +Before turning to any of these advanced topics, it is worthwhile to familiarize ourselves with +the syntax and features around mid-circuit measurements (MCMs). In this how-to, we will focus on +dynamic quantum circuits that use control flow based on MCMs. +Most of the advanced concepts mentioned above incorporate MCMs in this way, making it a +key ingredient to scalable quantum computing. + +If you want to learn more or are looking for how to collect statistics of mid-circuit measurements, +have a read of the :doc:`how-to on MCM statistics `. + +.. figure:: ../_static/demonstration_assets/how_to_create_dynamic_mcm_circuits/socialthumbnail_how_to_create_dynamic_mcm_circuits.png + :align: center + :width: 50% + +""" + +###################################################################### +# Dynamic circuit with a single MCM and conditional +# ------------------------------------------------- +# +# We start with a minimal dynamic circuit on two qubits. +# +# - It rotates one qubit about the ``X``-axis and applies a phase to +# the other qubit using a :class:`~.pennylane.T` gate. +# +# - Both qubits are entangled with a :class:`~.pennylane.CNOT` gate. +# +# - The second qubit is measured with :func:`~.pennylane.measure`. +# +# - If we measured a ``1`` in the previous step, an :class:`~.pennylane.S` gate is applied +# to the first qubit. This conditioned operation is realized with :func:`~.pennylane.cond`. +# +# - Finally, the expectation value of the Pauli ``Y`` operator on the first qubit is returned. +# + +import pennylane as qml +import numpy as np + +dev = qml.device("lightning.qubit", wires=2) + + +@qml.qnode(dev, interface="numpy") +def circuit(x): + qml.RX(x, 0) + qml.T(1) + qml.CNOT(wires=[0, 1]) + mcm = qml.measure(1) + qml.cond(mcm, qml.S)(wires=0) + + return qml.expval(qml.Y(0)) + + +x = 1.361 +print(circuit(x)) + +###################################################################### +# +# After this minimal working example, we now construct a more complex circuit showcasing more +# features of MCMs and dynamic circuits in PennyLane. We start with some short preparatory +# definitions. +# +# How to dynamically prepare half-filled basis states +# --------------------------------------------------- +# +# We now turn to a more complex example of a dynamic circuit. We will build a circuit that +# non-deterministically initializes half-filled computational basis states, i.e., basis states +# with as many :math:`1`\ s as :math:`0`\ s. +# +# The procedure is as follows: +# +# - Single-qubit rotations and a layer of :class:`~.pennylane.CNOT` gates create an entangled state +# on the first three qubits. +# +# - The first three qubits are measured and for each measured :math:`0,` another qubit is excited +# from the :math:`|0\rangle` state to the :math:`|1\rangle` state. +# +# First, we define a quantum subprogram that creates the initial state: +# + + +def init_state(x): + # Rotate the first three qubits + for w in range(3): + qml.RX(x[w], w) + # Entangle the first three qubits + qml.CNOT([0, 1]) + qml.CNOT([1, 2]) + qml.CNOT([2, 0]) + + +###################################################################### +# With this subroutine in our hands, let's define the full :class:`~.pennylane.QNode`. +# For this, we also create a shot-based device. +# + +shots = 100 +dev = qml.device("default.qubit", shots=shots) + + +@qml.qnode(dev) +def create_half_filled_state(x): + init_state(x) + for w in range(3): + # Measure one qubit at a time and flip another, fresh qubit if measured 0 + mcm = qml.measure(w) + qml.cond(~mcm, qml.X)(w + 3) + + return qml.counts(wires=range(6)) + + +###################################################################### +# Before running this ``QNode``, let's sample some random input parameters and draw the circuit: +# + +np.random.seed(652) +x = np.random.random(3) * np.pi + +print(qml.draw(create_half_filled_state)(x)) + +###################################################################### +# We see an initial block of gates that prepares the starting state on the +# first three qubits, followed by pairs of measurements and conditional bit flips, +# applied to pairs of qubits. +# +# Great, now let's finally see if it works: +# + +counts = create_half_filled_state(x) +print(f"Sampled bit strings:\n{list(counts.keys())}") + +###################################################################### +# Indeed, we created half-filled computational basis states, each with its own probability: +# + +print("The probabilities for the bit strings are:") +for key, val in counts.items(): + print(f" {key}: {val/shots*100:4.1f} %") + +###################################################################### +# +# Postselecting dynamically prepared states +# ----------------------------------------- +# We can select only some of these half-filled states by postselecting on measurement outcomes: +# + + +@qml.qnode(dev) +def postselect_half_filled_state(x, selection): + init_state(x) + for w in range(3): + # Postselect the measured qubit to match the selection criterion + mcm = qml.measure(w, postselect=selection[w]) + qml.cond(~mcm, qml.X)(w + 3) + + return qml.counts(wires=range(6)) + + +###################################################################### +# As an example, suppose we wanted half-filled states that have a :math:`0` in the first and a +# :math:`1` in the third position. We do not postselect on the second qubit, which we can indicate +# by passing ``None`` to the ``postselect`` argument of :func:`~.pennylane.measure`. Again, before +# running the circuit, let's draw it first: +# + +selection = [0, None, 1] +print(qml.draw(postselect_half_filled_state)(x, selection)) + +###################################################################### +# Note the indicated postselection values next to the drawn MCMs. +# +# Time to run the postselecting circuit: +# + +counts = postselect_half_filled_state(x, selection) +postselected_shots = sum(counts.values()) + +print(f"Obtained {postselected_shots} out of {shots} samples after postselection.") +print("The probabilities for the postselected bit strings are:") +for key, val in counts.items(): + print(f" {key}: {val/postselected_shots*100:4.1f} %") + +###################################################################### +# We successfully postselected on the desired properties of the computational basis state. Note +# that the number of returned samples is reduced, because those samples that do not meet the +# postselection criteria are discarded entirely. +# +# Dynamically correct quantum states +# ---------------------------------- +# +# If we do not want to postselect the prepared states but still would like to guarantee some of +# the qubits to be in a selected state, we can instead flip the corresponding +# pairs of bits if we measured the undesired state: +# + + +@qml.qnode(dev) +def create_selected_half_filled_state(x, selection): + init_state(x) + all_mcms = [] + for w in range(3): + # Don't postselect on the selection criterion, but store the MCM for later + mcm = qml.measure(w) + qml.cond(~mcm, qml.X)(w + 3) + all_mcms.append(mcm) + + for w, sel, mcm in zip(range(3), selection, all_mcms): + # If the postselection criterion is not None, flip the corresponding pair + # of qubits conditioned on the mcm not satisfying the selection criterion + if sel is not None: + qml.cond(mcm != sel, qml.X)(w) + qml.cond(mcm != sel, qml.X)(w + 3) + + return qml.counts(wires=range(6)) + + +print(qml.draw(create_selected_half_filled_state)(x, selection)) + +###################################################################### +# We can see how the measured values are fed not only into the original conditioned operation, +# but also into the two additional bit flips for our "correction" procedure, as long as the +# selection criterion is not ``None``. Let's execute the circuit: +# + +counts = create_selected_half_filled_state(x, selection) +selected_shots = sum(counts.values()) + +print(f"Obtained all {selected_shots} of {shots} samples because we did not postselect") +print("The probabilities for the selected bit strings are:") +for key, val in counts.items(): + print(f" {key}: {val/selected_shots*100:4.1f} %") + +###################################################################### +# Note that we kept all samples because we did not postselect, and that (in this particular case) +# this evened out the probabilities of measuring either of the two possible bit strings. +# Also, note that we conditionally +# applied the bit flip operators ``qml.X`` by comparing an MCM result to +# the corresponding selection criterion (``mcm!=sel``). More generally, MCM +# results in PennyLane can be processed with standard arithmetic operations. For details, +# see the `introduction to MCMs +# `_ +# and the documentation of :func:`~.pennylane.measure`. +# +# And this is how to create dynamic circuits in PennyLane with mid-circuit measurements! +# diff --git a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json new file mode 100644 index 0000000000..eb097bbd12 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json @@ -0,0 +1,50 @@ +{ + "title": "How to create dynamic circuits with mid-circuit measurements", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-05-03T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_create_dynamic_mcm_circuits.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_create_dynamic_mcm_circuits.png" + } + ], + "seoDescription": "Learn how to create dynamic circuits with control flow based on mid-circuit measurements.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_teleportation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_collect_mcm_stats", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py new file mode 100644 index 0000000000..3d9b47e3da --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py @@ -0,0 +1,217 @@ +r"""How to import noise models from Qiskit +========================================== + +Noise models describe how a quantum system interacts with its environment. +These models are typically represented by a set of +`Kraus operators `_ +that encapsulates the probabilistic nature of quantum errors. ⚡ +Interestingly, different sets of Kraus operators can represent the same quantum noise process. +The non-unique nature of these representations allows quantum computing libraries to use +different approaches for storing and building Kraus operators to construct noise models. +In this how-to guide, we will first compare the construction of noise models in +`Qiskit `_ and +`PennyLane `_. Then, we will learn how to +convert a Qiskit noise model into an equivalent PennyLane one, allowing users to import any +custom user-defined or fake backend-based noise models. +""" + +###################################################################### +# Noise models in Qiskit and PennyLane +# ------------------------------------ +# +# The noise models in Qiskit are built using the tools available in the +# `noise module `_ +# of the ``Qiskit-Aer`` package. Each model is represented by a `NoiseModel +# `_ +# object that contains `QuantumError +# `_ +# to describe the errors encountered in gate operations. Optionally, it may also have a +# `ReadoutError `_ +# that describes the classical readout errors. +# +# Let's build a Qiskit noise model that inserts *depolarization* errors for single-qubit gates, +# *bit-flip* errors for the target qubit of the two-qubit gates, +# and *amplitude damping* errors for each measurement: +# + +import numpy as np +from qiskit_aer.noise import ( + amplitude_damping_error, depolarizing_error, pauli_error, NoiseModel +) + +# Building the Qiskit noise model +model_qk = NoiseModel() + +# Depolarization error for single-qubit gates +prob_depol = 0.2 +error_gate1 = depolarizing_error(prob_depol, 1) +model_qk.add_all_qubit_quantum_error(error_gate1, ["u1", "u2", "u3"]) + +# Bit flip errors for two-qubit gate +prob_bit_flip = 0.1 +error_gate2 = pauli_error([('X', prob_bit_flip), ('I', 1 - prob_bit_flip)]).tensor( + pauli_error([('I', 1)]) +) +model_qk.add_all_qubit_quantum_error(error_gate2, ["cx"]) + +# Amplitude damping error for measurements +n_qubits = 3 +exc_population = 0.2 +prob_ampl_damp = np.random.default_rng(42).uniform(0, 0.2, n_qubits) +for qubit in range(n_qubits): + error_meas = amplitude_damping_error(prob_ampl_damp[qubit], exc_population) + model_qk.add_quantum_error(error_meas, "measure", [qubit]) + +print(model_qk) + +###################################################################### +# In contrast, the noise models in PennyLane are :class:`~.pennylane.NoiseModel` +# objects with Boolean conditions that select the operation for which +# we want to apply noise. These conditions are mapped to noise functions +# that apply (or queue) the corresponding noise for the selected operation +# or measurement process based on user-provided metadata. This allows +# for a more functional construction, as we can see by recreating the +# above noise model as shown below. For more information on this, check out our +# :doc:`how-to for noise models in PennyLane `. 🧑‍🏫 +# + +import pennylane as qml + +# Depolarization error for single-qubit gates +gate1_fcond = qml.noise.op_in(["U1", "U2", "U3"]) & qml.noise.wires_in(range(n_qubits)) +gate1_noise = qml.noise.partial_wires(qml.DepolarizingChannel, prob_depol) + +# Bit flip errors for two-qubit gate +gate2_fcond = qml.noise.op_eq("CNOT") +def gate2_noise(op, **metadata): + qml.BitFlip(prob_bit_flip, op.wires[1]) + +# Readout errors for measurements +rmeas_fcond = qml.noise.meas_eq(qml.counts) +def rmeas_noise(op, **metadata): + for wire in op.wires: + qml.GeneralizedAmplitudeDamping(prob_ampl_damp[wire], 1 - exc_population, wire) + +# Building the PennyLane noise model +model_pl = qml.NoiseModel( + {gate1_fcond: gate1_noise, gate2_fcond: gate2_noise}, {rmeas_fcond: rmeas_noise}, +) + +print(model_pl) + +###################################################################### +# It is important to verify whether these noise models work the intended way. +# For this purpose, we will use them while simulating a +# `GHZ state `_ using the +# `default.mixed `_ +# and `qiskit.aer `_ +# devices. Note that we require :func:`~.pennylane.add_noise` transform for +# adding the PennyLane noise model but the Qiskit noise model is provided in +# the device definition itself: +# + +# Preparing the devices +n_shots = int(2e6) +dev_pl_ideal = qml.device("default.mixed", wires=n_qubits, shots=n_shots) +dev_qk_noisy = qml.device("qiskit.aer", wires=n_qubits, shots=n_shots, noise_model=model_qk) + +def GHZcircuit(): + qml.U2(0, np.pi, wires=[0]) + for wire in range(n_qubits-1): + qml.CNOT([wire, wire + 1]) + return qml.counts(wires=range(n_qubits), all_outcomes=True) + +# Preparing the circuits +pl_ideal_circ = qml.QNode(GHZcircuit, dev_pl_ideal) +pl_noisy_circ = qml.add_noise(pl_ideal_circ, noise_model=model_pl) +qk_noisy_circ = qml.QNode(GHZcircuit, dev_qk_noisy) + +# Preparing the results +pl_noisy_res, qk_noisy_res = pl_noisy_circ(), qk_noisy_circ() + +###################################################################### +# Now let's look at the results to compare the two noise models: +# + +pl_probs = np.array(list(pl_noisy_res.values())) / n_shots +qk_probs = np.array(list(qk_noisy_res.values())) / n_shots + +print("PennyLane Results: ", np.round(pl_probs, 3)) +print("Qiskit Results: ", np.round(qk_probs, 3)) +print("Are results equal? ", np.allclose(pl_probs, qk_probs, atol=1e-2)) + +###################################################################### +# As the results are equal within a targeted tolerance, +# we can confirm that the two noise models are equivalent. +# Note that this tolerance can be further suppressed by +# increasing the number of shots (``n_shots``) in the simulation. +# + +###################################################################### +# Importing Qiskit noise models +# ----------------------------- +# +# PennyLane provides the :func:`~.pennylane.from_qiskit_noise` function to +# easily convert a Qiskit noise model into an equivalent PennyLane noise model. +# Let's look at an example of a noise model based on the `GenericBackendV2 +# `_ +# backend that gets instantiated with the error data generated and sampled randomly from +# historical IBM backend data. +# + +from qiskit.providers.fake_provider import GenericBackendV2 + +backend = GenericBackendV2(num_qubits=2, seed=42) +qk_noise_model = NoiseModel.from_backend(backend) +print(qk_noise_model) + +###################################################################### +# To import this noise model as a PennyLane one, we simply do: +# + +pl_noise_model = qml.from_qiskit_noise(qk_noise_model) +print(pl_noise_model) + +###################################################################### +# This conversion leverages the standard Kraus representation of the errors +# stored in the ``qk_noise_model``. Internally, this is done in a smart three-step process: +# +# 1. First, all the basis gates from the noise model are mapped to the corresponding PennyLane +# gate `operations `_. +# 2. Next, the operations with noise are mapped to the corresponding error channels +# defined via :class:`~.pennylane.QubitChannel`. +# 3. Finally, the `Boolean conditionals `_ +# are constructed and combined based on their associated errors. +# +# This can be done for any noise model defined in Qiskit with a minor catch that +# the classical readout errors are not supported yet in PennyLane. +# However, we can easily re-insert quantum readout errors into our converted noise model. +# Here's an example that adds ``rmeas_fcond`` and ``rmeas_noise`` (defined earlier) to +# ``pl_noise_model``: +# + +pl_noise_model += {"meas_map": {rmeas_fcond: rmeas_noise}} +print(pl_noise_model.meas_map) + +###################################################################### +# Conclusion +# ---------- +# + +###################################################################### +# Qiskit provides noise models and tools that could be used to mirror the behaviour of quantum +# devices. Integrating them into PennyLane is a powerful way to enable users to perform +# differentiable noisy simulations that help them study the effects of noise on quantum circuits +# and develop noise-robust quantum algorithms. In this how-to guide, we learned how to construct +# PennyLane noise models from Qiskit ones by manually building one-to-one mappings +# for each kind of error and also by using the :func:`~.pennylane.from_qiskit_noise` function +# to convert the Qiskit noise model automatically. 💪 +# +# Should you have any questions about using noise models in PennyLane, you can consult the +# `noise module documentation `_, +# the `PennyLane Codebook module on Noisy Quantum Theory +# `_, +# or create a post on the `PennyLane Discussion Forum `_. +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json new file mode 100644 index 0000000000..4b054e79fe --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json @@ -0,0 +1,42 @@ +{ + "title": "How to import noise models from Qiskit", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2024-11-25T00:00:00+00:00", + "dateOfLastModification": "2024-11-25T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_import_qiskit_noise_models.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_import_qiskit_noise_models.png" + } + ], + "seoDescription": "Learn how noise models can be imported into PennyLane from Qiskit.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_how_to_use_noise_models", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in new file mode 100644 index 0000000000..5e86ccff7a --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in @@ -0,0 +1,4 @@ +numpy +pennylane +qiskit +qiskit_aer diff --git a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py new file mode 100644 index 0000000000..2f9f279910 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py @@ -0,0 +1,197 @@ +r"""How to quantum just-in-time compile VQE with Catalyst +===================================================== + +The `Variational Quantum Eigensolver `__ (VQE) is +a widely used quantum algorithm with applications in quantum chemistry and portfolio optimization +problems. It is an application of the `Ritz variational +principle `__, where a quantum computer is trained to +prepare the ground state of a given molecule. + +Here, we will implement the VQE algorithm for the trihydrogen cation :math:`H_3^{+}` (three hydrogen +atoms sharing two electrons) using `Catalyst `__, a +quantum just-in-time framework for PennyLane, that allows hybrid quantum-classical workflows to be +compiled, optimized, and executed with a significant performance boost. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_how-to-vqe-qjit_2024-04-23.png + :align: center + :width: 60% + :target: javascript:void(0) + +We will break the implementation into three steps: + +1. Finding the molecular Hamiltonian for :math:`H_3^{+}.` +2. Preparing trial ground step (ansatz). +3. Optimizing the circuit to minimize the expectation value of the Hamiltonian. +""" + +###################################################################### +# Simple VQE workflow +# ------------------- +# +# The VQE algorithm takes a molecular Hamiltonian and a parametrized circuit preparing the trial state +# of the molecule. The cost function is defined as the expectation value of the Hamiltonian computed +# in the trial state. With VQE, the lowest energy state of the Hamiltonian can be computed using an +# iterative optimization of the cost function. In PennyLane, this optimization is performed by a +# classical optimizer which (in principle) leverages a quantum computer to evaluate the cost function +# and calculate its gradient at each optimization step. +# +# Step 1: Create the Hamiltonian +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The first step is to specify the molecule we want to simulate. We can download the :math:`H_3^+` +# Hamiltonian from the `PennyLane Datasets +# service `__: +# + +import pennylane as qml +from pennylane import numpy as np + +dataset = qml.data.load('qchem', molname="H3+")[0] +H, qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print(f"qubits: {qubits}") + +###################################################################### +# Step 2: Create the cost function +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Here, we prepare the initial state of the quantum circuit using the Hartree-Fock state, and then use +# the :class:`~pennylane.DoubleExcitation` template to create our parametrized circuit ansatz. +# Finally, we measure the expectation value of our Hamiltonian. +# + +# The Hartree-Fock State +hf = dataset.hf_state + +# Define the device, using lightning.qubit device +dev = qml.device("lightning.qubit", wires=qubits) + +@qml.qnode(dev, diff_method="adjoint") +def cost(params): + qml.BasisState(hf, wires=range(qubits)) + qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3]) + qml.DoubleExcitation(params[1], wires=[0, 1, 4, 5]) + return qml.expval(H) + +###################################################################### +# Step 3: Optimize the circuit +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Since we are using AutoGrad, we can use PennyLane’s built-in +# :class:`~pennylane.GradientDescentOptimizer` to optimize the circuit. +# + +init_params = np.array([0.0, 0.0]) +opt = qml.GradientDescentOptimizer(stepsize=0.4) +steps = 10 + +params = init_params + +for n in range(10): + params, prev_energy = opt.step_and_cost(cost, params) + print(f"--- Step: {n}, Energy: {cost(params):.8f}") + +print(f"Final angle parameters: {params}") + +###################################################################### +# Quantum just-in-time compiling VQE +# ---------------------------------- +# +# To take advantage of quantum just-in-time compilation, we need to modify the above workflow in two +# ways: +# +# 1. Instead of AutoGrad/NumPy, We need to use `JAX `__, a machine +# learning framework that supports just-in-time compilation. +# +# 2. We need to decorate our workflow with the :func:`~pennylane.qjit` decorator, to indicate that +# we want to quantum just-in-time compile the workflow with Catalyst. +# +# When dealing with more complex workflows, we may also need to update/adjust other aspects, including +# how we write control flow. For more details, see the `Catalyst sharp bits +# documentation `__. +# +# Step 2: Create the QJIT-compiled cost function +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# When creating the cost function, we want to make sure that all parameters and arrays are created +# using JAX. We can now decorate the cost function with :func:`~pennylane.qjit`: +# +from jax import numpy as jnp + +hf = np.array(dataset.hf_state) + +@qml.qjit +@qml.qnode(dev) +def cost(params): + qml.BasisState.compute_decomposition(hf, wires=range(qubits)) + qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3]) + qml.DoubleExcitation(params[1], wires=[0, 1, 4, 5]) + return qml.expval(H) + +init_params = jnp.array([0.0, 0.0]) + +cost(init_params) + +###################################################################### +# Step 3: Optimize the QJIT-compiled circuit +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We can now optimize the circuit. Unlike before, we cannot use PennyLane’s built-in optimizers (such +# as :func:`~.GradientDescentOptimizer`) here, as they are designed to work with AutoGrad and are +# not JAX compatible. +# +# Instead, we can use `Optax `__, a library designed for +# optimization using JAX, as well as the :func:`~.catalyst.grad` function, which allows us to +# differentiate through quantum just-in-time compiled workflows. +# + +import catalyst +import optax + +opt = optax.sgd(learning_rate=0.4) + +@qml.qjit +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = catalyst.grad(cost)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + +loss_history = [] + +opt_state = opt.init(init_params) +params = init_params + +for i in range(10): + params, opt_state = update_step(i, params, opt_state) + loss_val = cost(params) + + print(f"--- Step: {i}, Energy: {loss_val:.8f}") + + loss_history.append(loss_val) + +###################################################################### +# Step 4: QJIT-compile the optimization +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# In the above example, we QJIT-compiled *just* the optimization update step, as well as the cost +# function. We can instead rewrite the above so that the *entire* optimization loop is QJIT-compiled, +# leading to further performance improvements: +# + +@qml.qjit +def optimization(params): + opt_state = opt.init(params) + (params, opt_state) = qml.for_loop(0, 10, 1)(update_step)(params, opt_state) + return params + +final_params = optimization(init_params) +print(f"Final angle parameters: {final_params}") + +###################################################################### +# Note that here we use the QJIT-compatible :func:`~pennylane.for_loop` function, which allows +# classical control flow in hybrid quantum-classical workflows to be compiled. +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json new file mode 100644 index 0000000000..d91caf09a6 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json @@ -0,0 +1,49 @@ +{ + "title": "How to quantum just-in-time compile VQE with Catalyst", + "authors": [ + { + "username": "maliasadi" + }, + { + "username": "josh" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "Quantum Chemistry", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how-to-vqe-qjit.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how-to-vqe-qjit.png" + } + ], + "seoDescription": "Learn how to find the ground state of a molecule with VQE using PennyLane, Catalyst, and just-in-time compilation.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in new file mode 100644 index 0000000000..fc745d6f41 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in @@ -0,0 +1,5 @@ +pennylane-catalyst +jax +jaxlib +optax +pennylane diff --git a/demonstrations_v2/tutorial_how_to_use_noise_models/demo.py b/demonstrations_v2/tutorial_how_to_use_noise_models/demo.py new file mode 100644 index 0000000000..94e7478404 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_noise_models/demo.py @@ -0,0 +1,234 @@ +r"""How to use noise models in PennyLane +------------------------------------ +""" + +###################################################################### +# Noise models are essential for understanding and describing the effects of physical errors +# in a quantum computation. They allow for simulating the imperfections in state evolution +# arising from environment-based `errors `__, +# state preparation routines, measurements, and more. +# +# Here, we show how to use the features provided in PennyLane's :mod:`~.pennylane.noise` +# module to construct and manipulate noise models, enabling noisy simulation (see the `noise module documentation `__ for more details). In PennyLane, +# noise models are constructed from two main components: +# +# 1. Boolean conditions (referred to as *conditionals*) that dictate whether noise is inserted into the circuit. +# 2. Callables (called noise *functions*) that apply noise operations when a corresponding condition is satisfied. +# +# The following figure is an example that shows how a noise model transforms a sample circuit by inserting +# amplitude and phase damping errors for :class:`~.pennylane.RX` +# and :class:`~.pennylane.RY` gates, respectively. +# + +###################################################################### +# .. figure:: ../_static/demonstration_assets/noise_models/noise_model_long.jpg +# :align: center +# :width: 85% +# +# .. +# + +###################################################################### +# In the upcoming sections, we will first cover the underlying components of +# noise models and learn how to use them to construct desired noise models. +# Finally, we will use our noise model to perform noisy simulations. +# + +###################################################################### +# Conditionals +# ~~~~~~~~~~~~ +# +# We implement conditions as Boolean functions that accept an operation and evaluate it +# to return a Boolean output. In PennyLane, such objects are referred to as **conditionals**. +# They are constructed as instances of :class:`~.pennylane.BooleanFn` and can be combined +# using standard bitwise operations such as ``&``, ``|``, ``^``, or ``~``. PennyLane supports +# the following types of conditionals: +# +# 1. **Operation-based conditionals:** They evaluate whether a gate operation is a +# specific type of operation or belongs to a specified set of operations. They are +# built using :func:`~.pennylane.noise.op_eq` and :func:`~.pennylane.noise.op_in`, respectively. +# 2. **Wire-based conditionals:** They evaluate whether a gate operation's wires are +# equal to or are contained in a specified set of wires. They are built using +# :func:`~.pennylane.noise.wires_eq` and :func:`~.pennylane.noise.wires_in`, respectively. +# 3. **Arbitrary conditionals:** Custom conditionals can be defined as a function wrapped +# with a :class:`~.pennylane.BooleanFn` decorator. The signature for such conditionals +# must be ``cond_fn(operation: Operation) -> bool``. +# +# For example, here's how we would define a conditional that checks for :math:`R_X(\phi)` +# gate operations with :math:`|\phi| < 1.0` and wires :math:`\in \{0, 1\}`: +# + +import pennylane as qml +import numpy as np + +@qml.BooleanFn +def rx_cond(op): + return isinstance(op, qml.RX) and np.abs(op.parameters[0]) < 1.0 + +# Combine this arbitrary conditional with a wire-based conditional +rx_and_wires_cond = rx_cond & qml.noise.wires_in([0, 1]) +for op in [qml.RX(0.05, wires=[0]), qml.RX(2.34, wires=[1])]: + print(f"Result for {op}: {rx_and_wires_cond(op)}") + +###################################################################### +# Noise functions +# ~~~~~~~~~~~~~~~ +# +# Callables that apply noise operations are referred to as **noise functions** and have the +# signature ``fn(op, **metadata) -> None``. Their definition has no return statement and +# contains the error operations that are *inserted* when a gate operation in the circuit +# satisfies corresponding conditional. There are a few ways to construct noise functions: +# +# 1. **Single-instruction noise functions:** To add a single-operation noise, we can use +# :func:`~pennylane.noise.partial_wires`. It performs a partial initialization of the +# noise operation and queues it on the ``wires`` of the gate operation. +# 2. **User-defined noise functions:** For adding more sophisticated and custom noise, +# we can define our own quantum function with the signature specified above. +# +# For example, one can use the following to insert a depolarization error and show +# the error that gets queued with an example gate operation: +# + +depol_error = qml.noise.partial_wires(qml.DepolarizingChannel, 0.01) + +op = qml.X('w1') # Example gate operation +print(f"Error for {op}: {depol_error(op)}") + +###################################################################### +# Creating a noise model +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# We can now create a PennyLane :class:`~.pennylane.NoiseModel` by stitching together +# multiple condition-callable pairs, where noise operations are inserted into the +# circuit when their corresponding condition is satisfied. For the first pair, we +# will use the previously constructed conditional and callable to insert a depolarization +# error after :class:`~.pennylane.RX` gates that satisfy :math:`|\phi| < 1.0` and that +# act on the wires :math:`\in \{0, 1\}`. +# + +fcond1, noise1 = rx_and_wires_cond, depol_error + +###################################################################### +# Next, we construct a pair to mimic thermal relaxation errors that are encountered +# during the state preparation via :class:`~.pennylane.ThermalRelaxationError`: + +fcond2 = qml.noise.op_eq(qml.StatePrep) + +def noise2(op, **kwargs): + for wire in op.wires: + qml.ThermalRelaxationError(0.1, kwargs["t1"], kwargs["t2"], kwargs["tg"], wire) + +###################################################################### +# By default, noise operations specified by a noise function will be inserted *after* the +# gate operation that satisfies the conditional. However, they can be inserted in a custom +# order by manually queuing the evaluated gate operation via :func:`~pennylane.apply` +# within the function definition. For example, we can add a sandwiching constant-valued +# rotation error for :class:`~.pennylane.Hadamard` gates on the wires :math:`\in \{0, 1\}`: +# + +fcond3 = qml.noise.op_eq("Hadamard") & qml.noise.wires_in([0, 1]) + +def noise3(op, **kwargs): + qml.RX(np.pi / 16, op.wires) + qml.apply(op) + qml.RY(np.pi / 8, op.wires) + +###################################################################### +# Finally, we can build the noise model with some required ``metadata`` for ``noise2``: +# + +metadata = dict(t1=0.02, t2=0.03, tg=0.001) # times unit: sec +noise_model = qml.NoiseModel( + {fcond1: noise1, fcond2: noise2, fcond3: noise3}, **metadata +) +print(noise_model) + +###################################################################### +# Adding noise models to your workflow +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now that we have built our noise model, we can learn how to use it. +# A noise model can be applied to a circuit or device via the +# :func:`~pennylane.add_noise` transform. For example, consider +# the following circuit that performs the evolution and de-evolution +# of a given initial state based on some parameters: +# + +from matplotlib import pyplot as plt + +qml.drawer.use_style("pennylane") +dev = qml.device("default.mixed", wires=3) +init_state = np.random.RandomState(42).rand(2 ** len(dev.wires)) +init_state /= np.linalg.norm(init_state) + +def circuit(theta, phi): + # State preparation + qml.StatePrep(init_state, wires=[0, 1, 2]) + + # Evolve state + qml.Hadamard(0) + qml.RX(theta, 1) + qml.RX(phi, 2) + qml.CNOT([1, 2]) + qml.CNOT([0, 1]) + + # De-evolve state + qml.CNOT([0, 1]) + qml.CNOT([1, 2]) + qml.RX(-phi, 2) + qml.RX(-theta, 1) + qml.Hadamard(0) + return qml.state() + +theta, phi = 0.21, 0.43 +ideal_circuit = qml.QNode(circuit, dev) +qml.draw_mpl(ideal_circuit)(theta, phi) +plt.show() + +###################################################################### +# To attach the ``noise_model`` to this quantum circuit, we use the +# :func:`~.pennylane.add_noise` transform: +# + +noisy_circuit = qml.add_noise(ideal_circuit, noise_model) +qml.draw_mpl(noisy_circuit)(theta, phi) +plt.show() + +###################################################################### +# We can then use the ``noisy_circuit`` to run noisy simulations as shown below: +# + +init_dm = np.outer(init_state, init_state) # density matrix for the init_state +ideal_res = np.round(qml.math.fidelity(ideal_circuit(theta, phi), init_dm), 8) +noisy_res = np.round(qml.math.fidelity(noisy_circuit(theta, phi), init_dm), 8) + +print(f"Ideal v/s Noisy: {ideal_res} and {noisy_res}") + +###################################################################### +# The fidelity for the state obtained from the ideal circuit is :math:`\approx 1.0`, +# which is expected since our circuit effectively does nothing to the initial state. +# We see that this is not the case for the result obtained from the noisy simulation, +# due to the error operations inserted in the circuit. +# + + +###################################################################### +# Conclusion +# ~~~~~~~~~~ +# +# Noise models provide a succinct way to describe the impact of the environment on +# quantum computation. In PennyLane, we define such models as mapping between conditionals +# that select the target operation and their corresponding noise operations. These +# can be constructed with utmost flexibility as we showed here. +# +# Should you have any questions about using noise models in PennyLane, you can consult the +# `noise module documentation `__, +# the PennyLane Codebook module on +# `Noisy Quantum Theory `__ +# on noise, or create a post on the `PennyLane Discussion Forum `__. +# You can also follow us on `X (formerly Twitter) `__ +# or `LinkedIn `__ to stay up-to-date with +# the latest and greatest from PennyLane! +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json b/demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json new file mode 100644 index 0000000000..b59906c472 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json @@ -0,0 +1,42 @@ +{ + "title": "How to use noise models in PennyLane", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2024-10-01T00:00:00+00:00", + "dateOfLastModification": "2024-11-22T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_noise_models.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_noise_models.png" + } + ], + "seoDescription": "Learn how noise models can be built and inserted into a quantum circuit in PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_import_qiskit_noise_models", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in b/demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py new file mode 100644 index 0000000000..c330573d79 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py @@ -0,0 +1,358 @@ +r""" +How to use quantum arithmetic operators +======================================= + +Classical computers handle arithmetic operations like addition, subtraction, multiplication, and exponentiation with ease. +For instance, you can multiply large numbers on your phone in milliseconds! +`Quantum computers `__ can handle these operations too, but their true value lies beyond basic calculations. + +Quantum arithmetic plays a crucial role in more advanced quantum algorithms, +serving as fundamental building blocks in their design and execution. For example: + +1. In `Shor's algorithm `__ quantum arithmetic is crucial for performing modular exponentiation [#shor_exp]_. + +2. :doc:`Grover's algorithm ` might need to use quantum arithmetic to construct oracles, as shown in `this related demo `_. + +3. Loading data or preparing initial states on a quantum computer often requires several quantum arithmetic operations [#sanders]_. + +With PennyLane, you will see how easy it is to build quantum arithmetic operations as subroutines for your algorithms. +Knowing your way around these operators could be just the thing to streamline your algorithm design! + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_how_to_use_quantum_arithmetic_operators.png + :align: center + :width: 70% + :target: javascript:void(0) + +In-place and out-place arithmetic operations +------------------------------------------ +Let's begin by defining the terms *in-place* and *out-place* in the context of arithmetic operators. +In-place operators, like the :class:`~.pennylane.Adder` and :class:`~.pennylane.Multiplier`, directly modify the original quantum state by updating a +specific register's state. In contrast, out-place operators, such as the :class:`~.pennylane.OutAdder` and :class:`~.pennylane.OutMultiplier`, +combine multiple states and store the result in a new register, leaving the original states unchanged. Both kinds of operators are +illustrated in the following figure. + +.. figure:: ../_static/demonstration_assets/how_to_use_quantum_arithmetic_operators/in_outplace.png + :align: center + :width: 90% + +In quantum computing, all arithmetic operations are inherently `modular `_. +The default behavior in PennyLane is to perform operations modulo :math:`2^n`, +where :math:`n` is the number of wires in the register. For example, if :math:`n=6`, we have :math:`(32 + 43) = 75 = 11 \mod 64`, (since :math:`2^6 = 64`). +That means that quantum registers of :math:`n` wires can represent numbers up to :math:`2^n`. +However, users can specify a custom value smaller than this default. It's important to keep this modular behavior +in mind when working with quantum arithmetic, as using +too few qubits in a quantum register could lead to overflow issues. We will come back to this point later. + +Addition operators +~~~~~~~~~~~~~~~~~~ + +There are two addition operators in PennyLane: :class:`~.pennylane.Adder` and :class:`~.pennylane.OutAdder`. + +The :class:`~.pennylane.Adder` operator performs an in-place operation, adding an integer value :math:`k` to the state of the wires :math:`|x \rangle`. It is defined as: + +.. math:: + + \text{Adder}(k) |x \rangle = | x+k \rangle. + +On the other hand, the :class:`~.pennylane.OutAdder` operator performs an out-place operation, where the states of two +wires, :math:`|x \rangle` and :math:`|y \rangle`, are +added together and the result is stored in a third register: + +.. math:: + + \text{OutAdder} |x \rangle |y \rangle |0 \rangle = |x \rangle |y \rangle |x + y \rangle. + +To implement these operators in PennyLane, the first step is to define the `registers of wires `_ +we will work with. We define wires for input registers, the output register, and also additional ``work_wires`` that will be important when we later discuss the :class:`~.pennylane.Multiplier` operator. +""" + +import pennylane as qml + +# we indicate the name of the registers and their number of qubits. +wires = qml.registers({"x": 4, "y":4, "output":6,"work_wires": 4}) + +###################################################################### +# Now, we write a circuit to prepare the state :math:`|x \rangle|y \rangle|0 \rangle`, which will be needed for the out-place +# operation, where we initialize specific values to :math:`x` and :math:`y`. Note that in this example we use computational basis states, but +# you could introduce any quantum state as input. + +def product_basis_state(x=0,y=0): + qml.BasisState(x, wires=wires["x"]) + qml.BasisState(y, wires=wires["y"]) + +dev = qml.device("default.qubit", shots=1) +@qml.qnode(dev) +def circuit(x,y): + product_basis_state(x, y) + return [qml.sample(wires=wires[name]) for name in ["x", "y", "output"]] + +###################################################################### +# Since the arithmetic operations are deterministic, a single shot is enough to sample +# from the circuit and extract the expected state in the output register. +# Next, for understandability, we will use an auxiliary function that will +# take one sample from the circuit and return the associated decimal number. + +def state_to_decimal(binary_array): + # Convert a binary array to a decimal number + return sum(bit * (2 ** idx) for idx, bit in enumerate(reversed(binary_array))) + +###################################################################### +# In this example we are setting :math:`x=1` and :math:`y=4` and checking that the results are as expected. + +output = circuit(x=1,y=4) +print("x register: ", output[0]," (binary) ---> ", state_to_decimal(output[0]), " (decimal)") +print("y register: ", output[1]," (binary) ---> ", state_to_decimal(output[1]), " (decimal)") +print("output register: ", output[2]," (binary) ---> ", state_to_decimal(output[2]), " (decimal)") + +###################################################################### +# Now we can implement an example for the :class:`~.pennylane.Adder` operator. We will add the integer :math:`5` to the ``wires["x"]`` register +# that stores the state :math:`|x \rangle=|1 \rangle`. + +@qml.qnode(dev) +def circuit(x): + + product_basis_state(x) # |x> + qml.Adder(5, wires["x"]) # |x+5> + + return qml.sample(wires=wires["x"]) + +print(circuit(x=1), " (binary) ---> ", state_to_decimal(circuit(x=1))," (decimal)") + +###################################################################### +# We obtained the result :math:`5+1=6`, as expected. At this point, it's worth taking a moment to look +# at the decomposition of the circuit into quantum gates and operators. + +fig, _ = qml.draw_mpl(circuit, decimals = 2, style = "pennylane", level='device')(x=1) +fig.show() + +###################################################################### +# From the :class:`~.pennylane.Adder` circuit we can see that the addition is performed +# in the Fourier basis. This includes a quantum Fourier transformation (QFT) followed by rotations to perform the addition, and +# concludes with an inverse QFT transformation. A more detailed explanation on the decomposition of arithmetic operators can be found in +# `the PennyLane Demo on quantum arithmetic with the QFT `_. +# +# Now, let's see an example for the :class:`~.pennylane.OutAdder` operator to add the states +# :math:`|x \rangle` and :math:`|y \rangle` to the output register. + +@qml.qnode(dev) +def circuit(x,y): + + product_basis_state(x, y) # |x> |y> |0> + qml.OutAdder(wires["x"], wires["y"], wires["output"]) # |x> |y> |x+y> + + return qml.sample(wires=wires["output"]) + +print(circuit(x=2,y=3), " (binary) ---> ", state_to_decimal(circuit(x=2,y=3)), " (decimal)") + +###################################################################### +# We obtained the result :math:`2+3=5`, as expected. +# +# Multiplication operators +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# There are two multiplication operators in PennyLane: the :class:`~.pennylane.Multiplier` and the :class:`~.pennylane.OutMultiplier`. +# The class :class:`~.pennylane.Multiplier` performs an in-place operation, multiplying the state of the wires :math:`|x \rangle` by an integer :math:`k`. It is defined as: +# +# .. math:: +# +# \text{Multiplier}(k) |x \rangle = | kx \rangle. +# +# The :class:`~.pennylane.OutMultiplier` performs an out-place operation, where the states of two +# registers, :math:`|x \rangle` and :math:`|y \rangle`, +# are multiplied together and the result is stored in a third register: +# +# .. math:: +# +# \text{OutMultiplier} |x \rangle |y \rangle |0 \rangle = |x \rangle |y \rangle |xy \rangle. +# +# We proceed to implement these operators in PennyLane. First, let's see an example for the +# :class:`~.pennylane.Multiplier` operator. We will multiply the state :math:`|x \rangle=|2 \rangle` by +# the integer :math:`k=3`: + +@qml.qnode(dev) +def circuit(x): + + product_basis_state(x) # |x> + qml.Multiplier(3, wires["x"], work_wires=wires["work_wires"]) # |3x> + + return qml.sample(wires=wires["x"]) + +print(circuit(x=2), " (binary) ---> ", state_to_decimal(circuit(x=2))," (decimal)") + +###################################################################### +# We got the expected result of :math:`3 \cdot 2 = 6`. +# +# Now, let's look at an example using the :class:`~.pennylane.OutMultiplier` operator to multiply the states :math:`|x \rangle` and +# :math:`|y \rangle`, storing the result in the output register. + +@qml.qnode(dev) +def circuit(x,y): + + product_basis_state(x, y) # |x> |y> |0> + qml.OutMultiplier(wires["x"], wires["y"], wires["output"]) # |x> |y> |xy> + + return qml.sample(wires=wires["output"]) + +print(circuit(x=4,y=2), " (binary) ---> ", state_to_decimal(circuit(x=4,y=2))," (decimal)") + +###################################################################### +# Nice! Modular subtraction and division can also be implemented as the inverses of addition and +# multiplication, respectively. The inverse of a quantum circuit can be implemented with the +# :func:`~.pennylane.adjoint` operator. Let's see an example of modular subtraction. + +@qml.qnode(dev) +def circuit(x): + + product_basis_state(x) # |x> + qml.adjoint(qml.Adder(3, wires["x"])) # |x-3> + + return qml.sample(wires=wires["x"]) + +print(circuit(x=6), " (binary) ---> ", state_to_decimal(circuit(x=6)), " (decimal)") + +###################################################################### +# As predicted, this gives us :math:`6-3=3`. +# +# Implementing a polynomial on a quantum computer +# -------------------------------------------- +# +# Now that you are familiar with these operations, let's take it a step further and see how we can use them for something more complicated. +# We will explore how to implement a polynomial function on a quantum computer using basic arithmetic. +# In particular, we will use :math:`f(x,y)= 4 + 3xy + 5 x+ 3 y` as an example, where the variables :math:`x` and +# :math:`y` are integer values. Therefore, the operator we want to build is: +# +# .. math:: +# +# U|x\rangle |y\rangle |0\rangle = |x\rangle |y\rangle |4 + 3xy + 5x + 3y\rangle. +# +# We will show how to implement this circuit in two different ways: first, by concatenating simple modular arithmetic operators, +# and finally, using the :class:`~.pennylane.OutPoly` operator. +# +# Concatenating arithmetic operations +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Let's start by defining the arithmetic operations to apply the function :math:`f(x,y) = 4 + 3xy + 5x + 3y` into a quantum state. +# +# First, we need to define a function that will add the term :math:`3xy` to the output register. We will use +# the :class:`~.pennylane.Multiplier` and :class:`~.pennylane.OutMultiplier` operators for this. Also, we will employ the +# adjoint function to undo certain multiplications, since :math:`U` does not modify the input registers. + +def adding_3xy(): + # |x> ---> |3x> + qml.Multiplier(3, wires["x"], work_wires=wires["work_wires"]) + + # |3x>|y>|0> ---> |3x>|y>|3xy> + qml.OutMultiplier(wires["x"], wires["y"], wires["output"]) + + # We return the x-register to its original value using the adjoint operation + # |3x>|y>|3xy> ---> |x>|y>|3xy> + qml.adjoint(qml.Multiplier)(3, wires["x"], work_wires=wires["work_wires"]) + +###################################################################### +# Then we need to add the term :math:`5x + 3y` to the output register, which can be done by using the +# :class:`~.pennylane.Multiplier` and :class:`~.pennylane.OutAdder` operators. + +def adding_5x_3y(): + + # |x>|y> ---> |5x>|3y> + qml.Multiplier(5, wires["x"], work_wires=wires["work_wires"]) + qml.Multiplier(3, wires["y"], work_wires=wires["work_wires"]) + + # |5x>|3y>|0> ---> |5x>|3y>|5x + 3y> + qml.OutAdder(wires["x"], wires["y"], wires["output"]) + + # We return the x and y registers to their original value using the adjoint operation + # |5x>|3y>|5x + 3y> ---> |x>|y>|5x + 3y> + qml.adjoint(qml.Multiplier)(5, wires["x"], work_wires=wires["work_wires"]) + qml.adjoint(qml.Multiplier)(3, wires["y"], work_wires=wires["work_wires"]) + +###################################################################### +# Now we can combine all these circuits to implement the transformation by the polynomial :math:`f(x,y)= 4 + 3xy + 5 x+ 3 y`. + +@qml.qnode(dev) +def circuit(x,y): + + product_basis_state(x, y) # |x> |y> |0> + qml.Adder(4, wires["output"]) # |x> |y> |4> + adding_3xy() # |x> |y> |4 + 3xy> + adding_5x_3y() # |x> |y> |4 + 3xy + 5x + 3y> + + return qml.sample(wires=wires["output"]) + +print(circuit(x=1,y=4), " (binary) ---> ", state_to_decimal(circuit(x=1,y=4)), " (decimal)") + +###################################################################### +# Cool, we get the correct result, :math:`f(1,4)=33`. +# +# At this point, it's interesting to consider what would happen if we had chosen a smaller number of wires for the output. +# For instance, if we had selected one wire fewer, we would have obtained the result :math:`33 \mod 2^5 = 1`. + +wires = qml.registers({"x": 4, "y": 4, "output": 5,"work_wires": 4}) + +print(circuit(x=1,y=4), " (binary) ---> ", state_to_decimal(circuit(x=1,y=4)), " (decimal)") + +###################################################################### +# With one fewer wire, we get :math:`1`, just like we predicted. Remember, we are working with modular arithmetic! +# +# Using the OutPoly function +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# There is a more direct method to apply polynomial transformations in PennyLane: +# using :class:`~.pennylane.OutPoly`. +# This operator automatically takes care of all the arithmetic under the hood. +# Let's check out how to apply a function like :math:`f(x, y)` using :class:`~.pennylane.OutPoly`. +# +# We will start by explicitly defining our function. + +def f(x, y): + return 4 + 3*x*y + 5*x + 3*y + +###################################################################### +# Now, we create a quantum circuit using :class:`~.pennylane.OutPoly`. + +###################################################################### + +wires = qml.registers({"x": 4, "y":4, "output":6}) +@qml.qnode(dev) +def circuit_with_Poly(x,y): + + product_basis_state(x, y) # |x> |y> |0> + qml.OutPoly( + f, + input_registers= [wires["x"], wires["y"]], + output_wires = wires["output"]) # |x> |y> |4 + 3xy + 5x + 3y> + + return qml.sample(wires = wires["output"]) + +print(circuit_with_Poly(x=1,y=4), " (binary) ---> ", state_to_decimal(circuit_with_Poly(x=1,y=4)), " (decimal)") + +###################################################################### +# You can decide, depending on the problem you are tackling, whether to go for the versatility +# of defining your own arithmetic operations or the convenience of using the :class:`~.pennylane.OutPoly` function. + +###################################################################### +# Conclusion +# ------------------------------------------ +# Understanding and implementing quantum arithmetic is a key step toward unlocking the full potential +# of quantum computing. By leveraging quantum arithmetic operations in PennyLane, you can streamline +# the coding of your quantum algorithms. So, +# whether you choose to customize your arithmetic operations or take advantage of the built-in +# convenience offered by PennyLane +# operators, you are now equipped to tackle the exciting quantum challenges ahead. +# +# References +# ---------- +# +# .. [#shor_exp] +# +# Robert L Singleton Jr +# "Shor's Factoring Algorithm and Modular Exponentiation Operators.", +# `arXiv:2306.09122 `__, 2023. +# +# .. [#sanders] +# +# Yuval R. Sanders, Guang Hao Low, Artur Scherer, Dominic W. Berry +# "Black-box quantum state preparation without arithmetic.", +# `arXiv:1807.03206 `__, 2018. +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json new file mode 100644 index 0000000000..d1ea6e5285 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json @@ -0,0 +1,65 @@ +{ + "title": "How to use quantum arithmetic operators", + "authors": [ + { + "username": "gmlejarza" + }, + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-11-05T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_quantum_arithmetic_operators.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_quantum_arithmetic_operators.png" + } + ], + "seoDescription": "Learn how to use the quantum arithmetic operators of PennyLane.", + "doi": "", + "references": [ + { + "id": "shor_exp", + "type": "article", + "title": "Shor's Factoring Algorithm and Modular Exponentiation Operators", + "authors": "Robert L Singleton Jr", + "year": "2023", + "publisher": "", + "url": "https://arxiv.org/abs/2306.09122/" + }, + { + "id": "sanders", + "type": "article", + "title": "Black-box quantum state preparation without arithmetic", + "authors": "Yuval R. Sanders, Guang Hao Low, Artur Scherer, Dominic W. Berry", + "year": "2018", + "publisher": "", + "url": "https://arxiv.org/abs/1807.03206/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_use_registers", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_how_to_use_registers/demo.py b/demonstrations_v2/tutorial_how_to_use_registers/demo.py new file mode 100644 index 0000000000..4f8d3c13c9 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_registers/demo.py @@ -0,0 +1,182 @@ +r"""How to use wire registers +==================================== +A register of wires represents a collection of wires that serve a purpose (e.g., an "estimation" register in :doc:`quantum phase estimation `) and abstract away the finer details of running quantum +algorithms. In this tutorial, we will explore how you can construct and use wire registers +in PennyLane. +""" + +###################################################################### +# How to create a wire register +# ----------------------------- +# +# The way to construct a wire register is simple --- just use :func:`~.pennylane.registers`. We need to +# pass a dictionary where the keys are the register names and the values are the number of +# wires in each register: +# + +import pennylane as qml + +register = qml.registers({"alice": 1, "bob": 2, "charlie": 3}) +print(register) + +###################################################################### +# The output is a dictionary where the keys are the names of the registers and the +# values are :class:`~.pennylane.wires.Wires` instances. +# +# You can also pass in a dictionary that has nested dictionaries as its values. + +nested_register = qml.registers( + { + "all_registers": { + "alice": 1, + "bob": {"bob1": {"bob1a": 1, "bob1b": 2}, "bob2": 1}, + "charlie": 1, + } + } +) +print(nested_register) + +###################################################################### +# Note that :func:`~.pennylane.registers` flattens any nested dictionaries, and the order of the +# elements is based on the order of appearance and the level of nestedness. For more details on ordering, refer +# to the documentation for :func:`~pennylane.registers`. +# +# Accessing a particular register is the same as accessing any element in a dictionary… + +print(nested_register["alice"], nested_register["bob1a"]) + +###################################################################### +# …and you can access a specific wire index via its index in a register. + +print(nested_register["all_registers"][2], nested_register["bob1a"][0]) + +###################################################################### +# You can also combine registers using set operations. Here, we use the pipe operator ``|`` to +# perform the union operation on the ``alice`` register and the ``charlie`` register. + +new_register = nested_register["alice"] | nested_register["charlie"] +print(new_register) + +###################################################################### +# For more details on what set operations are supported, refer to the documentation of +# :class:`~.pennylane.wires.Wires`. +# +# A simple example +# ---------------- +# +# In this example, we demonstrate how one can implement the swap test with registers. +# The `swap test `_ +# is an algorithm that calculates the squared inner +# product of two input states. It requires one auxiliary qubit and takes two input states :math:`|\psi\rangle` +# and :math:`|\phi\rangle.` We can think of these components as three registers. Suppose states +# :math:`|\psi\rangle` and :math:`|\phi\rangle` are each represented with 3 wires. In PennyLane +# code, that would be: + + +import numpy as np + +swap_register = qml.registers({"auxiliary": 1, "psi": 3, "phi": 3}) + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def swap_test(): + # Make "psi" and "phi" state orthogonal to each other + qml.RX(np.pi/2, swap_register["phi"][0]) + + qml.Hadamard(swap_register["auxiliary"]) + for i in range(len(swap_register["psi"])): + # We can use the union operation to assemble our registers on the fly + qml.CSWAP( + swap_register["auxiliary"] + | swap_register["psi"][i] + | swap_register["phi"][i] + ) + qml.Hadamard(swap_register["auxiliary"]) + return qml.expval(qml.Z(wires=swap_register["auxiliary"])) + + +print(swap_test()) + +###################################################################### +# An advanced example +# ------------------- +# +# Using registers can greatly streamline the process of modifying a workflow +# by simplifying wire management. In this example, we use :doc:`quantum phase estimation (QPE) ` to +# calculate the eigenvalues of a Hamiltonian. +# Generally, QPE is described with two sets of registers. One register is known as the +# "estimation" or "measurement" register, and the other is the state register where we apply our +# unitary operators :math:`U.` We can define these registers in PennyLane code: + +register = qml.registers({"state": 4, "estimation": 6}) + +###################################################################### +# To build our unitary operator :math:`U,` there are a variety of options. We can opt to use a +# straight-forward block encoding, or choose to use a subroutine like qubitization. Let's opt for +# :class:`~.pennylane.Qubitization`, which means we have to define another preparation register. +# Our registers now look like this: + +register = qml.registers({"state": 4, "estimation": 6, "prep": 4}) + +###################################################################### +# Finally, let's define our Hamiltonian. We'll use the `transverse-field Ising model `_ from +# `PennyLane Datasets `_, +# but feel free to try this with any other Hamiltonian. + + +[dataset] = qml.data.load( + "qspin", sysname="Ising", periodicity="open", lattice="chain", layout="1x4" +) +H = dataset.hamiltonians[0] +print(H) + +###################################################################### +# For QPE to work, we need to initialize the "state" register with an initial state that has good +# overlap with the eigenstate we want the eigenvalue of. + +initial_state = dataset.ground_states[0] + +###################################################################### +# With this, we can now define our QPE circuit: + +dev = qml.device("lightning.qubit", wires=14) + +@qml.qnode(dev) +def circuit(): + # Initialize state register to initial state + qml.StatePrep(initial_state, wires=register["state"]) + + # Apply Hadamard gate to all wires in estimation register + for wire in register["estimation"]: + qml.Hadamard(wires=wire) + + qml.ControlledSequence( + qml.Qubitization(H, register["prep"]), + control=register["estimation"], + ) + + qml.adjoint(qml.QFT)(wires=register["estimation"]) + + return qml.probs(wires=register["estimation"]) + +###################################################################### +# We'll run our circuit and do some post-processing to get the energy eigenvalue: + +output = circuit() +lamb = sum([abs(c) for c in H.terms()[0]]) +print("Eigenvalue: ", lamb * np.cos(2 * np.pi * (np.argmax(output)) / 2 ** len(register["estimation"]))) + +###################################################################### +# Changing the number of wires in your estimation register is very easy with registers. +# The complexity of wire management only gets more difficult +# as you start working with more and more registers. As you start building bigger and more complex +# algorithms, this can quickly become a serious issue! +# +# Conclusion +# ---------- +# In this demo, we showed how to construct wire registers and use them to implement quantum +# algorithms. Wire registers provide an elegant way of organizing and managing wires. Often, +# algorithms are described as acting upon registers and sub-registers; using wire registers +# can greatly streamline the implementation from theory to code. +# diff --git a/demonstrations_v2/tutorial_how_to_use_registers/metadata.json b/demonstrations_v2/tutorial_how_to_use_registers/metadata.json new file mode 100644 index 0000000000..0eaa840bab --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_registers/metadata.json @@ -0,0 +1,42 @@ +{ + "title": "How to use wire registers", + "authors": [ + { + "username": "austingmhuang" + } + ], + "dateOfPublication": "2024-09-05T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_registers.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_registers.png" + } + ], + "seoDescription": "Learn how wire registers can be built and used in PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qpe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_use_registers/requirements.in b/demonstrations_v2/tutorial_how_to_use_registers/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_how_to_use_registers/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py b/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py new file mode 100644 index 0000000000..6739c22c76 --- /dev/null +++ b/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py @@ -0,0 +1,654 @@ +r""" +.. _implicit_diff_susceptibility: + +Implicit differentiation of variational quantum algorithms +========================================================== + +.. meta:: + :property="og:description": Implicitly differentiating the solution of a VQA in PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/descartes.png + +.. related:: + tutorial_backprop Quantum gradients with backpropagation + tutorial_jax_transformations Using JAX with PennyLane + +*Authors: Shahnawaz Ahmed and Juan Felipe Carrasquilla Álvarez — Posted: 28 November 2022. Last updated: 28 November 2022.* + + +In 1638, René Descartes, intrigued by (then amateur) Pierre de Fermat's method +of computing tangents, challenged Fermat to find the tangent to +a complicated curve — now called the folium of Descartes: + +.. math:: + + x^3 + y^3 = 3axy. + +.. figure:: ../_static/demonstration_assets/implicit_diff/descartes.png + :scale: 65% + :alt: Representation of the folium of Descartes + :align: center + +With its cubic terms, this curve represents an implicit equation which cannot be +written as a simple expression :math:`y = f(x).` Therefore the task of calculating +the tangent function seemed formidable for the method Descartes had then, +except at the vertex. Fermat successfully provided the tangents at not just the +vertex but at any other point on the curve, baffling Descartes and legitimizing +the intellectual superiority of Fermat. The technique used by Fermat was +*implicit differentiation* [#Paradis2004]_. In the above equation, we can begin +by take derivatives on both sides and re-arrange the terms to obtain :math:`dy/dx.` + +.. math:: + + \frac{dy}{dx} = -\frac{x^2 - ay}{y^2 - ax}. + +Implicit differentiation can be used to compute gradients of such functions that +cannot be written down explicitly using simple elementary operations. It is a +basic technique of calculus that has recently found many applications in machine +learning — from hyperparameter optimization to the training of neural ordinary +differential equations (ODEs), and it has even led to the definition of a whole new class of architectures, +called Deep Equilibrium Models (DEQs) [#implicitlayers]_. + +Introduction +------------ + +The idea of implicit differentiation can be applied in quantum physics to extend +the power of automatic differentiation to situations where we are not able to +explicitly write down the solution to a problem. As a concrete example, consider +a variational quantum algorithm (VQA) that computes the ground-state solution of +a parameterized Hamiltonian :math:`H(a)` using a variational ansatz +:math:`|\psi_{z}\rangle,` where :math:`z` are the variational parameters. This leads to the solution + +.. math:: + + z^{*}(a) = \arg\,\min_{z} \langle \psi_{z}|H(a)|\psi_z\rangle. + +As we change :math:`H(a),` the solution also changes, but we do not obtain an +explicit function for :math:`z^{*}(a).` If we are interested in the properties of the +solution state, we could measure the expectation values of some operator +:math:`A` as + +.. math:: + + \langle A \rangle (a) = \langle \psi_{z^{*}(a)}| A | \psi_{z^{*}(a)}\rangle. + +With a VQA, we can find a solution to the optimization for a fixed :math:`H(a).` +However, just like with the folium of Descartes, we do not have an explicit solution, +so the gradient :math:`\partial_a \langle A \rangle (a)` is not easy to compute. +The solution is only implicitly defined. + +Automatic differentiation techniques that construct an explicit computational +graph and differentiate through it by applying the chain rule for gradient +computation cannot be applied here easily. A brute-force application of automatic +differentiation that finds :math:`z^{*}(a)` throughout the full optimization +would require us to keep track of all intermediate variables and steps in the optimization +and differentiate through them. This could quickly become computationally +expensive and memory-intensive for quantum algorithms. Implicit differentiation +provides an alternative way to efficiently compute such a gradient. + +Similarly, there exist various other +interesting quantities that can be written as gradients of a ground-state solution, +e.g., nuclear forces in quantum chemistry, permanent electric dipolestatic +polarizability, the static hyperpolarizabilities of various orders, +fidelity susceptibilities, and geometric tensors. All such +quantities could possibly be computed using implicit differentiation on quantum +devices. In our recent work we present a unified way to implement such +computations and other applications of implicit differentiation through +variational quantum algorithms [#Ahmed2022]_. + +In this demo we show how implicit gradients can be computed using a variational +algorithm written in *PennyLane* and powered by a modular implicit differentiation +implementation provided by the tool *JAXOpt* [#Blondel2021]_. We compute the generalized +susceptibility for a spin system by using a variational ansatz to obtain a +ground-state and implicitly differentiating through it. In order to compare +the implicit solution, we find the exact ground-state through eigendecomposition +and determine gradients using automatic differentiation. Even though +eigendecomposition may become unfeasible for larger systems, for a small number +of spins, it suffices for a comparison with our implicit differentiation approach. + +Implicit Differentiation +------------------------ + +We consider the differentiation of a solution of the root-finding problem, defined by + +.. math:: + + f(z, a) = 0. + +A function :math:`z^{*}(a)` that satisfies :math:`f(z^{*}(a), a) = 0` gives a +solution map for fixed values of :math:`a.` An explicit analytical solution +is, however, difficult to obtain in general. This means that the direct differentiation of +:math:`\partial_a z^{*}(a)` is not always possible. Despite that, some iterative +algorithms may be able to compute the solution by starting from an initial set of values +for :math:`z,` e.g., using a fixed-point solver. The optimality condition +:math:`f(z^{*}(a), a) = 0` tells the solver when a solution is found. + +Implicit differentiation can be used to compute :math:`\partial_a z^{*}(a)` +more efficiently than brute-force automatic differentiation, using only the +solution :math:`z^{*}(a)` and partial derivatives at the solution point. We do +not have to care about how the solution is obtained and, therefore, do not need +to differentiate through the solution-finding algorithm [#Blondel2021]_. + +The `Implicit function theorem `__ +is a statement about how the set of zeros of a system of equations is locally +given by the graph of a function under some conditions. It can be extended to +the complex domain and we state the theorem (informally) below [#Chang2003]_. + +.. topic:: Implicit function theorem (IFT) (informal) + + If :math:`f(z, a)` is some analytic function where in a local neighbourhood + around :math:`(z_0, a_0)` we have :math:`f(z_0, a_0) = 0,` there exists an + analytic solution :math:`z^{*}(a)` that satisfies :math:`f(z^{*}(a), a) = 0.` + + +.. figure:: ../_static/demonstration_assets/implicit_diff/implicit_diff.png + :scale: 65% + :alt: Graph of the implicit function with its solution. + :align: center + +In the figure above we can see solutions to the optimality condition +:math:`f(z, a) = 0 ` (red stars), which defines a curve :math:`z^{*}(a).` +According to the IFT, the solution function is analytic, which means it can be +differentiated at the solution points by simply differentiating +the above equation with respect to :math:`a,` as + +.. math:: + + \partial_a f(z_0, a_0) + \partial_{z} f(z_0, a_0) \partial_{a} z^{*}(a) = 0, + +which leads to + +.. math:: + + \partial_{a} z^{*}(a) = - (\partial_{z} f(z_0, a_0) )^{-1}\partial_a f(z_0, a_0). + + +This shows that implicit differentiation can be used in situations where we can +phrase our optimization problem in terms of an optimality condition +or a fixed point equation that can be solved. In case of optimization tasks, +such an optimality condition would be that, at the minima, the gradient of the +cost function is zero — i.e., + +.. math:: + + f(z, a) = \partial_z g(z, a) = 0. + +Then, as long as we have the solution, :math:`z^{*}(a),` and the partial derivatives at the solution (in this case the Hessian of the +cost function :math:`g(z, a)`), :math:`(\partial_a f, \partial_z f),` we can compute implicit gradients. Note that, +for a multivariate function, the inversion +:math:`(\partial_{z} f(z_0, a_0) )^{-1}` needs to be defined and easy to +compute. It is possible to approximate this inversion in a clever way by constructing +a linear problem that can be solved approximately [#Blondel2021]_, [#implicitlayers]_. + + +Implicit differentiation through a variational quantum algorithm +---------------------------------------------------------------- + +.. figure:: ../_static/demonstration_assets/implicit_diff/VQA.png + :scale: 65% + :alt: Application of implicit differentiation in variational quantum algorithm. + :align: center + +We now discuss how the idea of implicit differentiation can be applied to +variational quantum algorithms. Let us take a parameterized Hamiltonian +:math:`H(a),` where :math:`a` is a parameter that can be continuously varied. +If :math:`|\psi_{z}\rangle` is a variational solution to the ground state of :math:`H(a),` +then we can find a :math:`z^*(a)` that minimizes the ground state energy, i.e., + +.. math:: + + z^*(a) = \arg\, \min_{z} \langle \psi_{z}| H(a) | \psi_{z}\rangle = \arg \min_{z} E(z, a), + + +where :math:`E(z, a)` is the energy function. We consider the following +Hamiltonian + +.. math:: + + H(a) = -J \sum_{i} \sigma^{z}_i \sigma^{z}_{i+1} - \gamma \sum_{i} \sigma^{x}_i + \delta \sum_{i} \sigma^z_i - a A, + +where :math:`J` is the interaction, :math:`\sigma^{x}` and :math:`\sigma^{z}` +are the spin-:math:`\frac{1}{2}` operators and :math:`\gamma` is the magnetic +field strength (which is taken to be the same for all spins). +The term :math:`A = \frac{1}{N}\sum_i \sigma^{z}_i` +is the magnetization and a small non-zero magnetization :math:`\delta` is added +for numerical stability. We have assumed a circular chain such that in the +interaction term the last spin (:math:`i = N-1`) interacts with the first +(:math:`i=0`). + +Now we could find the ground state of this Hamiltonian by simply taking the +eigendecomposition and applying automatic differentiation through the +eigendecomposition to compute gradients. We will compare this exact computation +for a small system to the gradients given by implicit differentiation through a +variationally obtained solution. + +We define the following optimality condition at the solution point: + +.. math:: + + f(z, a) = \partial_z E(z, a) = 0. + +In addition, if the conditions of the implicit function theorem +are also satisfied, i.e., :math:`f` is continuously differentiable +with a non-singular Jacobian at the solution, then we can apply the chain rule +and determine the implicit gradients easily. + +At this stage, any other complicated function that depends on the variational ground state +can be easily differentiated using automatic differentiation by plugging in the +value of :math:`partial_a z^{*}(a)` where it is required. The +expectation value of the operator :math:`A` for the ground state +is + +.. math:: + + \langle A\rangle = \langle \psi_{z^*}| A| \psi_{z^*}\rangle. + +In the case where :math:`A` is just the energy, i.e., :math:`A = H(a),` the +Hellmann–Feynman theorem allows us to easily compute the gradient. However, for +a general operator we need the gradients :math:`\partial_a z^{*}(a),` which means that implicit differentiation is a very elegant way to go beyond the +Hellmann–Feynman theorem for arbitrary expectation values. + +Let us now dive into the code and implementation. + +*Talk is cheap. Show me the code.* - Linus Torvalds +""" + +############################################################################## +# Implicit differentiation of ground states in PennyLane +# ------------------------------------------------------ +# + +from functools import reduce + +from operator import add + +import jax +from jax import jit +import jax.numpy as jnp + +import pennylane as qml +import numpy as np + +import jaxopt + +import matplotlib.pyplot as plt + +jax.config.update("jax_platform_name", "cpu") + +# Use double precision numbers +jax.config.update("jax_enable_x64", True) + +############################################################################## +# Defining the Hamiltonian and measurement operator +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We define the Hamiltonian by building the non-parametric part separately and +# adding the parametric part to it as a separate term for efficiency. Note that, for +# the example of generalized susceptibility, we are measuring expectation values +# of the operator :math:`A` that also defines the parametric part of the +# Hamiltonian. However, this is not necessary and we could compute gradients for +# any other operator using implicit differentiation, as we have access to the +# gradients :math:`\partial_a z^{*}(a).` + +N = 4 +J = 1.0 +gamma = 1.0 + + +def build_H0(N, J, gamma): + """Builds the non-parametric part of the Hamiltonian of a spin system. + + Args: + N (int): Number of qubits/spins. + J (float): Interaction strength. + gamma (float): Interaction strength. + + Returns: + qml.Hamiltonian: The Hamiltonian of the system. + """ + H = qml.Hamiltonian([], []) + + for i in range(N - 1): + H += -J * qml.PauliZ(i) @ qml.PauliZ(i + 1) + + H += -J * qml.PauliZ(N - 1) @ qml.PauliZ(0) + + # Transverse + for i in range(N): + H += -gamma * qml.PauliX(i) + + # Small magnetization for numerical stability + for i in range(N): + H += -1e-1 * qml.PauliZ(i) + + return H + + +H0 = build_H0(N, J, gamma) +H0_matrix = qml.matrix(H0) +A = reduce(add, ((1 / N) * qml.PauliZ(i) for i in range(N))) +A_matrix = qml.matrix(A) + +############################################################################### +# Computing the exact ground state through eigendecomposition +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We now define a function that computes the exact ground state using +# eigendecomposition. Ideally, we would like to take gradients of this function. +# It is possible to simply apply automatic differentiation through this exact +# ground-state computation. JAX has an implementation of differentiation +# through eigendecomposition. +# +# Note that we have some points in this plot that are ``nan``, where the gradient +# computation through the eigendecomposition does not work. We will see later that +# the computation through the VQA is more stable. + + +@jit +def ground_state_solution_map_exact(a: float) -> jnp.array: + """The ground state solution map that we want to differentiate + through, computed from an eigendecomposition. + + Args: + a (float): The parameter in the Hamiltonian, H(a). + + Returns: + jnp.array: The ground state solution for the H(a). + """ + H = H0_matrix + a * A_matrix + eval, eigenstates = jnp.linalg.eigh(H) + z_star = eigenstates[:, 0] + return z_star + + +a = jnp.array(np.random.uniform(0, 1.0)) +z_star_exact = ground_state_solution_map_exact(a) + +################################################################# +# Susceptibility computation through the ground state solution map +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Let us now compute the susceptibility function by taking gradients of the +# expectation value of our operator :math:`A` w.r.t `a.` We can use `jax.vmap` +# to vectorize the computation over different values of `a`. + + +@jit +def expval_A_exact(a): + """Expectation value of ``A`` as a function of ``a`` where we use the + ``ground_state_solution_map_exact`` function to find the ground state. + + Args: + a (float): The parameter defining the Hamiltonian, H(a). + + Returns: + float: The expectation value of A calculated using the variational state + that should be the ground state of H(a). + """ + z_star = ground_state_solution_map_exact(a) + eval = jnp.conj(z_star.T) @ A_matrix @ z_star + return eval.real + + +# the susceptibility is the gradient of the expectation value +_susceptibility_exact = jax.grad(expval_A_exact) +susceptibility_exact = jax.vmap(_susceptibility_exact) + +alist = jnp.linspace(0, 3, 1000) +susvals_exact = susceptibility_exact(alist) + +plt.plot(alist, susvals_exact) +plt.xlabel("a") +plt.ylabel(r"$\partial_{a}\langle A \rangle$") +plt.show() + +############################################################################### +# Computing susceptibility through implicit differentiation +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We use PennyLane to find a variational ground state for the Hamiltonian +# :math:`H(a)` and compute implicit gradients through the variational +# optimization procedure. We use the ``jaxopt`` library which contains an +# implementation of gradient descent that automatically comes with implicit +# differentiation capabilities. We are going to use that to obtain +# susceptibility by taking gradients through the ground-state minimization. +# +# Defining the variational state +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# In PennyLane, we can implement a variational state in different ways, by +# defining a quantum circuit. There are also useful template circuits available, such as +# :class:`~pennylane.SimplifiedTwoDesign`, which implements the :doc:`two-design ansatz `. +# The ansatz consists of layers of Pauli-Y rotations with +# controlled-Z gates. In each layer there are ``N - 1`` parameters for the Pauli-Y gates. +# Therefore, the ansatz is efficient as long as we have enough layers for it +# so that is expressive enough to represent the ground-state. +# +# We set ``n_layers = 5``, but you can redo this example with fewer layers to see +# how a less expressive ansatz leads to error in the susceptibility computation. +# +# .. note:: +# +# The setting ``shots=None`` makes for the computation of gradients using reverse-mode +# autodifferentiation (backpropagation). It allows us to just-in-time (JIT) +# compile the functions that compute expectation values and gradients. +# In a real device we would use a finite number of shots and the gradients would be computed +# using the parameter-shift rule. However, this may be slower. + +variational_ansatz = qml.SimplifiedTwoDesign +n_layers = 5 +weights_shape = variational_ansatz.shape(n_layers, N) + +dev = qml.device("default.qubit", wires=N, shots=None) + + +@jax.jit +@qml.qnode(dev, interface="jax") +def energy(z, a): + """Computes the energy for a Hamiltonian H(a) using a measurement on the + variational state U(z)|0> with U(z) being any circuit ansatz. + + Args: + z (jnp.array): The variational parameters for the ansatz (circuit) + a (jnp.array): The Hamiltonian parameters. + + Returns: + float: The expectation value (energy). + """ + variational_ansatz(*z, wires=range(N)) + + return qml.expval(H0 + a * A) + + +z_init = [jnp.array(2 * np.pi * np.random.random(s)) for s in weights_shape] +a = jnp.array([0.5]) +print("Energy", energy(z_init, a)) + +############################################################################### +# Computing ground states using a variational quantum algorithm (VQA) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We construct a loss function that defines a ground-state minimization +# task. We are looking for variational parameters ``z`` that minimize the energy +# function. Once we find a set of parameters ``z``, we wish to compute the +# gradient of any function of the ground state w.r.t. ``a``. +# +# For the implicit differentiation we will use the tool ``jaxopt``, which implements +# modular implicit differentiation for various cases; e.g., for fixed-point +# functions or optimization. We can directly use ``jaxopt`` to optimize our loss +# function and then compute implicit gradients through it. +# It all works due to :doc:`PennyLane's integration with JAX `. +# +# The implicit differentiation formulas can even be `implemented manually with JAX `__. +# These formulas are implemented in a modular way, using the +# ``jaxopt.GradientDescent`` optimizer with ``implicit_diff=True``. +# We use the seamless integration between PennyLane, JAX +# and JAXOpt to compute the susceptibility. +# +# Since everything is written in JAX, simply calling the +# ``jax.grad`` function works as ``jaxopt`` computes the implicit gradients and +# plugs it any computation used by ``jax.grad``. We can also just-in-time (JIT) +# compile all functions although the compilation may take some time as the +# number of spins or variational ansatz becomes more complicated. Once compiled, +# all computes run very fast for any parameters. + + +def ground_state_solution_map_variational(a, z_init): + """The ground state solution map that we want to differentiate + through. + + Args: + a (float): The parameter in the Hamiltonian, H(a). + z_init [jnp.array(jnp.float)]: The initial guess for the variational + parameters. + + Returns: + z_star (jnp.array [jnp.float]): The parameters that define the + ground-state solution. + """ + + @jax.jit + def loss(z, a): + """Loss function for the ground-state minimization with regularization. + + Args: + z (jnp.array): The variational parameters for the ansatz (circuit) + a (jnp.array): The Hamiltonian parameters. + + Returns: + float: The loss value (energy + regularization) + """ + return energy(z, a) + 0.001 * jnp.sum(jnp.abs(z[0])) + 0.001 * jnp.sum(jnp.abs(z[1])) + + gd = jaxopt.GradientDescent( + fun=loss, + stepsize=1e-2, + acceleration=True, + maxiter=1000, + implicit_diff=True, + tol=1e-15, + ) + z_star = gd.run(z_init, a=a).params + return z_star + + +a = jnp.array(np.random.uniform(0, 1.0)) # A random ``a`` +z_star_variational = ground_state_solution_map_variational(a, z_init) + +############################################################################### +# Computing gradients through the VQA simply by calling ``jax.grad`` +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We can compute the susceptibility values by simply using ``jax.grad``. After the +# first call, the function is compiled and subsequent calls become +# much faster. + + +@jax.jit +@qml.qnode(dev, interface="jax") +def expval_A_variational(z: float) -> float: + """Expectation value of $A$ as a function of $a$ where we use the + a variational ground state solution map. + + Args: + a (float): The parameter in the Hamiltonian, H(a). + + Returns: + float: The expectation value of M on the ground state of H(a) + """ + variational_ansatz(*z, wires=range(N)) + return qml.expval(A) + + +@jax.jit +def groundstate_expval_variational(a, z_init) -> float: + """Computes ground state and calculates the expectation value of the operator M. + + Args: + a (float): The parameter in the Hamiltonian, H(a). + z_init [jnp.array(jnp.float)]: The initial guess for the variational parameters. + H0 (qml.Hamiltonian): The static part of the Hamiltonian + """ + z_star = ground_state_solution_map_variational(a, z_init) + return expval_A_variational(z_star) + + +susceptibility_variational = jax.jit(jax.grad(groundstate_expval_variational, argnums=0)) +z_init = [jnp.array(2 * np.pi * np.random.random(s)) for s in weights_shape] +print("Susceptibility", susceptibility_variational(alist[0], z_init)) + +susvals_variational = [] + +for i in range(len(alist)): + susvals_variational.append(susceptibility_variational(alist[i], z_init)) + +plt.plot(alist, susvals_variational, label="Implicit diff through VQA") +plt.plot(alist, susvals_exact, "--", c="k", label="Automatic diff through eigendecomposition") +plt.xlabel("a") +plt.ylabel(r"$\partial_{a}\langle A \rangle$") +plt.legend() +plt.show() + +############################################################################## +# PennyLane version and details + +print(qml.about()) + +############################################################################## +# Conclusion +# ---------- +# We have shown how a combination of JAX, PennyLane and JAXOpt can be used to +# compute implicit gradients through a VQA. The ability to compute such +# gradients opens up new possibilities, e.g., the design of a Hamiltonian such that +# its ground-state has certain properties. It is also possible to perhaps look +# at this inverse-design of the Hamiltonian as a control problem. Implicit +# differentiation in the classical setting allows defining a new type of +# neural network layer — implicit layers such as neural ODEs. In a similar +# way, we hope this demo the inspires creation of new architectures for quantum +# neural networks, perhaps a quantum version of neural ODEs or quantum implicit +# layers. +# +# In future works, it would be important to assess the cost of running implicit +# differentiation through an actual quantum computer and determine the quality +# of such gradients as a function of noise as explored in a related recent work +# [#Matteo2021]_. +# +# References +# ---------- +# +# .. [#Paradis2004] +# +# Jaume Paradís, Josep Pla & Pelegrí Viader +# "Fermat and the Quadrature of the Folium of Descartes" +# The American Mathematical Monthly, 111:3, 216-229 +# `10.1080/00029890.2004.11920067 `__, 2004. +# +# .. [#Ahmed2022] +# +# Shahnawaz Ahmed, Nathan Killoran, Juan Felipe Carrasquilla Álvarez +# "Implicit differentiation of variational quantum algorithms +# `arXiv:2211.13765 `__, 2022. +# +# .. [#Blondel2021] +# +# Mathieu Blondel, Quentin Berthet, Marco Cuturi, Roy Frostig, Stephan Hoyer, Felipe Llinares-López, Fabian Pedregosa, Jean-Philippe Vert +# "Efficient and modular implicit differentiation" +# `arXiv:2105.15183 `__, 2021. +# +# .. [#implicitlayers] +# +# Zico Kolter, David Duvenaud, Matt Johnson. +# "Deep Implicit Layers - Neural ODEs, Deep Equilibirum Models, and Beyond" +# `http://implicit-layers-tutorial.org `__, 2021. +# +# .. [#Matteo2021] +# +# Olivia Di Matteo, R. M. Woloshyn +# "Quantum computing fidelity susceptibility using automatic differentiation" +# `arXiv:2207.06526 `__, 2022. +# +# .. [#Chang2003] +# +# Chang, Hung-Chieh, Wei He, and Nagabhushana Prabhu. +# "The analytic domain in the implicit function theorem." +# `JIPAM. J. Inequal. Pure Appl. Math 4.1 `__, (2003). +# diff --git a/demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json b/demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json new file mode 100644 index 0000000000..59f2b6fbc6 --- /dev/null +++ b/demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json @@ -0,0 +1,98 @@ +{ + "title": "Implicit differentiation of variational quantum algorithms", + "authors": [ + { + "username": "quantshah" + }, + { + "username": "jfcalvarez" + } + ], + "dateOfPublication": "2022-11-28T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_implicit_differentiation_variational_quantum_algo.png" + } + ], + "seoDescription": "Implicitly differentiating the the solution of a VQA in PennyLane.", + "doi": "", + "references": [ + { + "id": "Paradis2004", + "type": "article", + "title": "Fermat and the Quadrature of the Folium of Descartes", + "authors": "Jaume Parad\u00eds, Josep Pla & Pelegr\u00ed Viader", + "year": "2004", + "journal": "The American Mathematical Monthly", + "doi": "10.1080/00029890.2004.11920067", + "url": "" + }, + { + "id": "Ahmed2022", + "type": "article", + "title": "Implicit differentiation of variational quantum algorithms", + "authors": "Shahnawaz Ahmed, Nathan Killoran, Juan Felipe Carrasquilla \u00c1lvarez", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2211.13765" + }, + { + "id": "Blondel2021", + "type": "article", + "title": "Efficient and modular implicit differentiation", + "authors": "Mathieu Blondel, Quentin Berthet, Marco Cuturi, Roy Frostig, Stephan Hoyer, Felipe Llinares-L\u00f3pez, Fabian Pedregosa, Jean-Philippe Vert ", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2105.15183" + }, + { + "id": "implicitlayers", + "type": "webpage", + "title": "Deep Implicit Layers - Neural ODEs, Deep Equilibirum Models, and Beyond", + "authors": "Zico Kolter, David Duvenaud, Matt Johnson", + "year": "2021", + "journal": "", + "url": "http://implicit-layers-tutorial.org" + }, + { + "id": "Matteo2021", + "type": "article", + "title": "Quantum computing fidelity susceptibility using automatic differentiation", + "authors": "Olivia Di Matteo, R. M. Woloshyn", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2207.06526" + }, + { + "id": "Chang2003", + "type": "article", + "title": "The analytic domain in the implicit function theorem.", + "authors": "Chang, Hung-Chieh, Wei He, and Nagabhushana Prabhu", + "year": "2003", + "journal": "J. Inequal. Pure Appl. Math", + "url": "http://emis.icm.edu.pl/journals/JIPAM/v4n1/061_02_www.pdf" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2211.13765" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in b/demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in new file mode 100644 index 0000000000..075509d294 --- /dev/null +++ b/demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in @@ -0,0 +1,6 @@ +jax +jaxlib +jaxopt +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_initial_state_preparation/demo.py b/demonstrations_v2/tutorial_initial_state_preparation/demo.py new file mode 100644 index 0000000000..62aa1b2ad9 --- /dev/null +++ b/demonstrations_v2/tutorial_initial_state_preparation/demo.py @@ -0,0 +1,339 @@ +r""" + +Initial state preparation for quantum chemistry +=============================================== + +A high-quality initial state can significantly reduce the runtime of many quantum algorithms. From +the variational quantum eigensolver (VQE) to quantum phase estimation (QPE) and even the recent +`intermediate-scale quantum (ISQ) `_ algorithms, obtaining the ground state of a chemical system requires +a good initial state. For instance, in the case of VQE, a good initial state directly translates into fewer +optimization steps. In QPE, the probability of measuring the ground-state energy is directly +proportional to the overlap squared of the initial and ground states. Even beyond quantum chemistry, +good initial guesses are important for algorithms like quantum approximate optimization (QAOA) +and Grover search. + +Much like searching for a needle in a haystack, there are a lot of things you might try +to prepare a good guess for the ground state in the high-dimensional Hilbert space. In this +tutorial, we show how to use traditional computational chemistry techniques to +get a good initial state. Such an initial state will not be exactly +the ground state, but it will certainly be better than the standard guess of a computational +basis state :math:`|0\rangle^{\otimes N}` or the Hartree-Fock state. + +.. figure:: ../_static/demonstration_assets/initial_state_preparation/qchem_input_states.png + :align: center + :width: 65% + :target: javascript:void(0) + +Importing initial states +------------------------ +We can import initial states obtained from several post-Hartree-Fock quantum chemistry methods +to PennyLane. These methods are incredibly diverse in terms of their outputs, not always returning +an object that is easy to turn into a PennyLane state vector. + +We have already done this hard conversion work: all that you need to do is run these methods and +pass their outputs to PennyLane's :func:`~.pennylane.qchem.import_state` function. The currently +supported methods are configuration interaction with singles and doubles (CISD), coupled cluster +(CCSD), density-matrix renormalization group (DMRG) and semistochastic heat-bath configuration +interaction (SHCI). + +We now show how this works on the linear :math:`\text{H}_3^+` molecule as an example. + + +CISD states +~~~~~~~~~~~ +The first line of attack for initial state preparation is often a CISD calculation, performed with the `PySCF `_ +library. CISD is unsophisticated, but it is fast. It will not be of much help for strongly correlated molecules, +but it is better than Hartree-Fock. Here is the code example using the restricted Hartree-Fock +orbitals (:func:`~.pennylane.qchem.import_state` works for unrestricted orbitals, too). +""" + +from pyscf import gto, scf, ci +from pennylane.qchem import import_state +from pennylane import numpy as np + +R = 1.2 +# create the H3+ molecule +mol = gto.M(atom=[["H", (0, 0, 0)], + ["H", (0, 0, R)], + ["H", (0, 0, 2 * R)]], charge=1) +# perfrom restricted Hartree-Fock and then CISD +myhf = scf.RHF(mol).run() +myci = ci.CISD(myhf).run() +wf_cisd = import_state(myci, tol=1e-1) +print(f"CISD-based state vector: \n{np.round(wf_cisd.real, 4)}") + +############################################################################## +# The final object, PennyLane's state vector ``wf_cisd``, is ready to be used as an +# initial state in a quantum circuit in PennyLane--we will showcase this below for VQE. +# +# Conversion for CISD to a state vector is straightforward: simply assign the PySCF-stored +# CI coefficients to appropriate entries in the state vector based on the determinant they correspond to. +# The second attribute passed to ``import_state()``, ``tol``, specifies the cutoff beyond +# which contributions to the wavefunctions are neglected. Internally, wavefunctions are +# stored in their Slater determinant representation. If their prefactor coefficient +# is below ``tol``, those determinants are dropped from the expression. + +############################################################################## +# CCSD states +# ~~~~~~~~~~~ +# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can +# automatically detect the input type and apply the appropriate conversion protocol. + +from pyscf import cc + +mycc = cc.CCSD(myhf).run() +wf_ccsd = import_state(mycc, tol=1e-1) +print(f"CCSD-based state vector: \n{np.round(wf_ccsd.real, 4)}") + +############################################################################## +# For CCSD conversion, at present the exponential form is expanded and terms are collected **to +# second order** to obtain the CI coefficients. +# +# DMRG states +# ~~~~~~~~~~~ +# For more complex or more correlated molecules, initial states from DMRG or +# SHCI will be better options. DMRG calculations involve using the library `Block2 `_, +# which can be installed with ``pip``: +# +# .. code-block:: bash +# +# pip install block2 +# +# The DMRG calculation is run on top of the molecular orbitals obtained by Hartree-Fock, +# stored in the ``myhf`` object, which we can reuse from before. +# +# .. code-block:: python +# +# from pyscf import mcscf +# from pyblock2.driver.core import DMRGDriver, SymmetryTypes +# from pyblock2._pyscf.ao2mo import integrals as itg +# +# # obtain molecular integrals and other parameters for DMRG +# mc = mcscf.CASCI(myhf, mol.nao, mol.nelectron) +# ncas, n_elec, spin, ecore, h1e, g2e, orb_sym = \ +# itg.get_rhf_integrals(myhf, mc.ncore, mc.ncas, g2e_symm=8) +# +# # initialize the DMRG solver, Hamiltonian (as matrix-product operator, MPO) and +# # state (as matrix-product state, MPS) +# driver = DMRGDriver(scratch="./dmrg_temp", symm_type=SymmetryTypes.SZ) +# driver.initialize_system(n_sites=ncas, n_elec=n_elec, spin=spin, orb_sym=orb_sym) +# mpo = driver.get_qc_mpo(h1e=h1e, g2e=g2e, ecore=ecore, iprint=0) +# ket = driver.get_random_mps(tag="GS") +# +# # execute DMRG by modifying the ket state in-place to minimize the energy +# driver.dmrg(mpo, ket, n_sweeps=30,bond_dims=[100,200],\ +# noises=[1e-3,1e-5],thrds=[1e-6,1e-7],tol=1e-6) +# +# # post-process the MPS to get an initial state +# dets, coeffs = driver.get_csf_coefficients(ket, iprint=0) +# dets = dets.tolist() +# wf_dmrg = import_state((dets, coeffs), tol=1e-1) +# print(f"DMRG-based state vector: \n {np.round(wf_dmrg, 4)}") +# +# .. code-block:: bash +# +# DMRG-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. -0.2243 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The crucial part is calling ``get_csf_coefficients()`` on the solution stored in +# MPS form in the ``ket``. This triggers an internal reconstruction calculation that +# converts the MPS to the sum of Slater determinants form, returning the output +# as a tuple ``(array([int]), array(float]))``. The first element expresses a given Slater +# determinant using Fock occupation vectors of length equal to the number of spatial +# orbitals. In Block2 notation, the entries can be ``0`` (orbital unoccupied), ``1`` (orbital occupied with spin-up +# electron), ``2`` (occupied with spin-down), and ``3`` (doubly occupied). The first +# element, ``array([int])``, must be converted to ``list`` +# for :func:`~.pennylane.qchem.import_state` to accept it. +# The second element stores the CI coefficients. +# +# In principle, this functionality can be used to generate any initial state, provided +# the user specifies a list of Slater determinants and their coefficients in this form. +# Let's take this opportunity to create the Hartree-Fock initial state, to compare the +# other states against it later on. + +from pennylane import numpy as np + +hf_primer = ([[3, 0, 0]], np.array([1.0])) +wf_hf = import_state(hf_primer) + +############################################################################## +# SHCI states +# ~~~~~~~~~~~ +# +# The SHCI calculations utilize the library `Dice `_, and can be run +# using PySCF through the interface module `SHCI-SCF `_. +# For Dice, the execution process is similar to that of DMRG: +# +# .. code-block:: python +# +# from pyscf.shciscf import shci +# +# # prepare PySCF CASCI object, whose solver will be the SHCI method +# ncas, nelecas_a, nelecas_b = mol.nao, mol.nelectron // 2, mol.nelectron // 2 +# myshci = mcscf.CASCI(myhf, ncas, (nelecas_a, nelecas_b)) +# +# # set up essentials for the SHCI solver +# output_file = f"shci_output.out" +# myshci.fcisolver = shci.SHCI(myhf.mol) +# myshci.fcisolver.outputFile = output_file +# +# # execute SHCI through the PySCF interface +# e_tot, e_ci, ss, mo_coeff, mo_energies = myshci.kernel(verbose=5) +# +# # post-process the shci_output.out to extract the wave function +# # results and create the tuple of dets (list([str])) and coeffs (array([float])) +# # shci_data = (dets, coeffs) +# wf_shci = import_state(shci_data, tol=1e-1) +# print(f"SHCI-based state vector\n{wf_shci}") +# +# .. code-block:: bash +# +# SHCI-based state vector +# [ 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0.2243 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. 0. 0. -0.9745 0. 0. 0. 0. 0. +# 0. 0. 0. 0. 0. 0. 0. 0. 0. +# 0. ] + +############################################################################## +# The Dice output file prints determinants using symbols ``0`` (unoccupied orbital), +# ``a`` and ``b`` (orbital occupied with spin-up and spin-down electron, respectively), +# and ``2`` (doubly occupied orbital). These determinant outputs, and corresponding +# coefficients, should be extracted and arranged as ``(list([str]), array(float]))``, +# where each string combines all the determinant symbols ``0, a, b, 2`` for a single +# determinant with no spaces. For example, for the HF state we created in the DMRG section, +# the SHCI output should read ``([["200"]], np.array([1.]))`` + +############################################################################## +# Application: speed up VQE +# ------------------------- +# Let us now demonstrate how the choice of a better initial state shortens the runtime +# of VQE for obtaining the ground-state energy of a molecule. As a first step, create our +# linear :math:`\text{H}_3^+` molecule, a device, and a simple VQE circuit with single and double excitations: + +import pennylane as qml +from pennylane import qchem + +# generate the molecular Hamiltonian for H3+ +symbols = ["H", "H", "H"] +geometry = np.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) +molecule = qchem.Molecule(symbols, geometry, charge=1) + +H2mol, qubits = qchem.molecular_hamiltonian(molecule) +wires = list(range(qubits)) +dev = qml.device("default.qubit", wires=qubits) + +# create all possible excitations in H3+ +singles, doubles = qchem.excitations(2, qubits) +excitations = singles + doubles + +############################################################################## +# Now let's run VQE with the Hartree-Fock initial state. We first build the VQE circuit: + + +@qml.qnode(dev) +def circuit_VQE(theta, initial_state): + qml.StatePrep(initial_state, wires=wires) + for i, excitation in enumerate(excitations): + if len(excitation) == 4: + qml.DoubleExcitation(theta[i], wires=excitation) + else: + qml.SingleExcitation(theta[i], wires=excitation) + return qml.expval(H2mol) + +############################################################################## +# Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. + +opt = qml.GradientDescentOptimizer(stepsize=0.4) +theta = np.array(np.zeros(len(excitations)), requires_grad=True) +delta_E, iteration = 10, 0 +results_hf = [] + +# run the VQE optimization loop until convergence threshold is reached +while abs(delta_E) > 1e-5: + theta, prev_energy = opt.step_and_cost(circuit_VQE, theta, initial_state=wf_hf) + new_energy = circuit_VQE(theta, initial_state=wf_hf) + delta_E = new_energy - prev_energy + results_hf.append(new_energy) + if len(results_hf) % 5 == 0: + print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") +print(f"Starting with HF state took {len(results_hf)} iterations until convergence.") + +############################################################################## +# And compare with how things go when you run it with the CISD initial state: + +theta = np.array(np.zeros(len(excitations)), requires_grad=True) +delta_E, iteration = 10, 0 +results_cisd = [] + +while abs(delta_E) > 1e-5: + theta, prev_energy = opt.step_and_cost(circuit_VQE, theta, initial_state=wf_cisd) + new_energy = circuit_VQE(theta, initial_state=wf_cisd) + delta_E = new_energy - prev_energy + results_cisd.append(new_energy) + if len(results_cisd) % 5 == 0: + print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") +print(f"Starting with CISD state took {len(results_cisd)} iterations until convergence.") + +############################################################################## +# Let's visualize the comparison between the two initial states, and see that indeed +# we get to the ground state much faster by starting with the CISD state. + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() +ax.plot(range(len(results_hf)), results_hf, color="r", marker="o", label="wf_hf") +ax.plot(range(len(results_cisd)), results_cisd, color="b", marker="o", label="wf_cisd") +ax.legend(fontsize=16) +ax.tick_params(axis="both", labelsize=16) +ax.set_xlabel("Iteration", fontsize=20) +ax.set_ylabel("Energy, Ha", fontsize=20) +plt.tight_layout() +plt.show() + +############################################################################## +# Indeed, the CISD state significantly shortens the VQE runtime. +# +# It is sometimes possible to foresee the extent of this speed-up of a particular initial state +# by computing its overlap with the ground state--a traditional metric of success for initial +# states in quantum algorithms. Because in our examples the states are regular arrays, computing an +# overlap between different states is as easy as computing a dot product + +print(np.dot(wf_cisd, wf_hf).real) +print(np.dot(wf_ccsd, wf_hf).real) +print(np.dot(wf_cisd, wf_ccsd).real) + +############################################################################## +# In this particular case of :math:`\text{H}_3^+,` even CISD gives the exact wavefunction, hence both overlaps +# with the HF state are identical. In more correlated molecules, overlaps will show that the more +# multireference methods DMRG and SHCI are farther away from the Hartree-Fock state, +# allowing them to perform better (you can check this by printing the overlaps with +# DMRG and SHCI in a more correlated molecule). If a ground state in such a case was known, +# the overlap to it could tell us directly the quality of the initial state. + +############################################################################## +# Conclusion +# ----------- +# This demo shows how to import initial states from outputs of traditional quantum chemistry methods +# for use in PennyLane. We showcased simple workflows for how to run +# a variety of state-of-the-art post-Hartree-Fock methods, from libraries such as +# `PySCF `_, +# `Block2 `_ and +# `Dice `_, to generate outputs that can then be +# converted to PennyLane's state vector format with a single line of code. With these +# initial states, we use the example of VQE to demonstrate how a better choice +# of initial state can lead to improved algorithmic performance. For the molecule +# used in our example, the CISD state was sufficient: however, in more correlated +# molecules, DMRG and SHCI initial states typically provide the best speed-ups. +# diff --git a/demonstrations_v2/tutorial_initial_state_preparation/metadata.json b/demonstrations_v2/tutorial_initial_state_preparation/metadata.json new file mode 100644 index 0000000000..147738ea35 --- /dev/null +++ b/demonstrations_v2/tutorial_initial_state_preparation/metadata.json @@ -0,0 +1,41 @@ +{ + "title": "Initial State Preparation for Quantum Chemistry", + "authors": [ + { + "username": "chiffa" + } + ], + "dateOfPublication": "2023-10-20T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/initial_state_preparation/thumbnail_initial_state_preparation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_initial_state_preparation.png" + } + ], + "seoDescription": "Prepare initial states for quantum algorithms from output of traditional quantum chemistry methods.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_initial_state_preparation/requirements.in b/demonstrations_v2/tutorial_initial_state_preparation/requirements.in new file mode 100644 index 0000000000..1dc2016a8b --- /dev/null +++ b/demonstrations_v2/tutorial_initial_state_preparation/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +pyscf diff --git a/demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py b/demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py new file mode 100644 index 0000000000..0937cf048d --- /dev/null +++ b/demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py @@ -0,0 +1,375 @@ +r"""Intro to Amplitude Amplification +======================================================================= + +`Grover's algorithm `_ is one of the most important +developments in quantum computing. This technique is a special case of a quantum algorithm called +**Amplitude Amplification** (Amp Amp). In this demo, you will learn its basic principles and +how to implement it in PennyLane using the new :class:`~.pennylane.AmplitudeAmplification` template. We also discuss +a useful extension of the algorithm called fixed-point amplitude amplification. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_AmplitudeAmplification_2024-04-29.png + :align: center + :width: 50% + :target: javascript:void(0) + + +Amplitude Amplification +------------------------- + +Our goal is to prepare an unknown state :math:`|\phi\rangle` knowing certain property of the state. +A first approach is to design a quantum circuit described by a unitary :math:`U` that generates an initial state :math:`|\Psi\rangle= U|0\rangle` +that has a non-zero overlap with the target state :math:`|\phi\rangle.` +Any state can be represented in the computational basis as: + +.. math:: + |\Psi\rangle = \sum_i c_i |i\rangle \quad \text{where} \quad c_i \in \mathbb{C}. + +But we can find better representations 😈. One choice is to make :math:`|\phi\rangle` an element of the basis. We can then write + +.. math:: + |\Psi\rangle = \alpha |\phi\rangle + \beta |\phi^{\perp}\rangle, + +where :math:`|\phi^{\perp}\rangle` is some state orthogonal +to :math:`|\phi\rangle,` and :math:`\alpha, \beta \in \mathbb{R}.` This allows us to represent +the initial state in a two-dimensional space --- a crucial advantage that we will exploit repeatedly. Notice that this representation is even simpler than a Bloch +sphere since the amplitudes are real numbers, so we can visualize all operations inside a circle, as shown in the image below: + +.. figure:: ../_static/demonstration_assets/intro_amplitude_amplification/ampamp1.jpeg + :align: center + :width: 60% + :target: javascript:void(0) + + +The two axes correspond to the states :math:`|\phi\rangle` and :math:`|\phi^{\perp}\rangle.` In the figure we also show the initial state :math:`|\Psi\rangle,` which +forms an angle of :math:`\theta=\arcsin(\alpha)` with the x-axis. + +Our aim is to **amplify** the amplitude :math:`\alpha` to get closer +to :math:`|\phi\rangle,` hence the name Amplitude Amplification [#ampamp]_ 😏. We will use this geometric picture to identify a sequence of operators that moves +the initial vector :math:`|\Psi\rangle` as close to :math:`|\phi\rangle` as possible. + +The algorithm +~~~~~~~~~~~~~~~ + +To obtain the state :math:`|\phi\rangle,` we could just rotate the initial state counterclockwise +by an angle :math:`\pi/2 -\theta.` However, we don't explicitly know :math:`|\phi\rangle,` +so it's unclear how this could be done. This is where a great idea is born: **what if instead of rotations we think of reflections?** + +The main insight of the Amp Amp algorithm is that there is a sequence of **two reflections** that allow us to effectively perform a rotation towards the target state. The first is the reflection with respect to :math:`|\phi^{\perp}\rangle` and the second one is the reflection with respect to :math:`|\Psi\rangle.` + +Let's go step by step. First we apply the reflection around :math:`|\phi^{\perp}\rangle:` + +.. figure:: ../_static/demonstration_assets/intro_amplitude_amplification/ampamp2.jpeg + :align: center + :width: 60% + :target: javascript:void(0) + + +This reflection may seem challenging to implement since we do not explicitly know :math:`|\phi^{\perp}\rangle.` However, the operator performing the reflection is well-defined: + +.. math:: + \begin{cases} + R_{\phi^{\perp}}|\phi\rangle = -|\phi\rangle, \\ + R_{\phi^{\perp}}|\phi^{\perp}\rangle = |\phi^{\perp}\rangle. + \end{cases} + +Amp Amp usually assumes access to an oracle implementing this reflection. +For example, in a search problem, this is the usual Grover oracle that "marks" the target state with a phase flip. +In practice, the way to build the oracle is just a classic check: +if the given state meets the known property, we change its sign. This will become clearer later with an example. + + +After applying the first reflection we are moving away from :math:`|\phi\rangle` --- why do that? +Well, sometimes it's necessary to take a step backward to take two steps +forward, and that is exactly what we will do. For this purpose, we use a second reflection with respect to :math:`|\Psi\rangle.` This is easier to build since +we know the operator :math:`U` that generates :math:`|\Psi\rangle.` + +.. figure:: ../_static/demonstration_assets/intro_amplitude_amplification/ampamp3.jpeg + :align: center + :width: 60% + :target: javascript:void(0) + + Reflection with respect to :math:`|\Psi\rangle.` + + +Together, these two reflections are equivalent to rotating the state by :math:`2\theta` degrees from its original position, +where :math:`\theta` is the angle that defines the initial state. To amplify the amplitude and +approach the target state, we perform this sequence of rotations multiple times, i.e. :math:`\dots R_{\Psi}R_{\phi^{\perp}}R_{\Psi}R_{\phi^{\perp}}.` More precisely, we repeat them :math:`k` times, with :math:`k` given by: + +.. math:: + k = \frac{\pi}{4 \theta}-\frac{1}{2}. + +This expression is derived by recognizing that the angle of the resulting state after :math:`k` iterations is :math:`(2k + 1)\theta,` +and we aim for this value to be equal to :math:`\frac{\pi}{2}` radians (i.e. :math:`90º`). + +As we will see below, Amp Amp can be applied to unstructured dataset searching problems. Let's suppose that in a set +of N elements we are looking for a single one, and we begin with an equal superposition state such that :math:`\alpha=\frac{1}{\sqrt{N}}.` +The number of iterations required in this case is :math:`k \sim \frac{\pi \sqrt{N}}{4},` making the complexity +of the algorithm :math:`\mathcal{O}(\sqrt{N}).` This provides a quadratic speedup compared to the +classical :math:`\mathcal{O}(N)` brute-force approach. + +Amplitude Amplification in PennyLane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let's take a look at a practical example using PennyLane: solving the `zero-sum problem `_. +In this task, we are given a list of :math:`n` integers and our goal is to find all subsets of numbers +whose sum is equal :math:`0.` In this example, we use the following set of integers: +""" + + +values = [1, -2, 3, 4, 5, -6] +n = len(values) + +############################################################################## +# Can you find all the subsets that add to zero? 🤔 +# +# We will use Amplitude Amplification to solve the problem. +# First we define a binary variable :math:`x_i` that takes the value :math:`1` if we include the :math:`i`-th element in the +# subset and takes the value :math:`0` otherwise. +# We encode the :math:`i`-th variable in the :math:`i`-th qubit of a quantum state. For instance, :math:`|110001\rangle` +# represents the subset :math:`[1,-2,-6]` consisting of the first, second, and sixth element in the set. +# Later on, we will see how to solve it directly with :class:`~.pennylane.AmplitudeAmplification`, but it is worthwhile to go +# step by step showing each part of the algorithm. +# +# We can now define the initial state: +# +# .. math:: +# |\Psi\rangle = \frac{1}{\sqrt{2^n}}\sum_{i=0}^{2^n-1}|i\rangle. +# +# This is a uniform superposition of all possible subsets so solutions are guaranteed to have non-zero amplitudes +# in :math:`|\Psi\rangle.` Let's generate the state and visualize it. + +import pennylane as qml +import matplotlib.pyplot as plt +plt.style.use('pennylane.drawer.plot') + +@qml.prod +# This decorator converts the quantum function U into an operator. +# It is useful for later use in the AmplitudeAmplification template. +def U(wires): + # create the generator U, such that U|0⟩ = |Ψ⟩ + for wire in wires: + qml.Hadamard(wires=wire) + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def circuit(): + U(wires = range(n)) + return qml.state() + +output = circuit()[:64].real + +plt.bar(range(len(output)), output) +plt.ylim(-0.4, 0.6) +plt.ylabel("Amplitude") +plt.xlabel("|i⟩") +plt.axhline(0, color='black', linewidth=1) +plt.show() + + + +############################################################################## +# Initially, the probability of getting any basis state is the same. +# The next step is to define the oracle that marks the elements that satisfy our property --- that the sum of the subset is zero. +# To do this we first create an operator that stores the sum of the selected subset in some auxiliary qubits. +# This operator, which we call :math:`\text{Sum}` , is defined as: +# +# .. math:: +# \text{Sum}|x\rangle|0\rangle = |x\rangle|\sum v_ix_i\rangle, +# +# where :math:`v_i` is the :math:`i`-th integer in the input set. For more details of how we build this operation take a +# look at `Basic arithmetic with the Quantum Fourier Transform `_. +# + +import numpy as np + +def Sum(wires_subset, wires_sum): + qml.QFT(wires = wires_sum) + for i, value in enumerate(values): + for j in range(len(wires_sum)): + qml.CRZ(value * np.pi / (2 ** j), wires=[wires_subset[i], wires_sum[j]]) + qml.adjoint(qml.QFT)(wires=wires_sum) + +############################################################################## +# To create the oracle that performs the reflection around :math:`|\phi^{\perp}\rangle,` we apply the :math:`\text{Sum}` operator to the +# state and then flip the sign of those states whose sum is :math:`0.` +# This allows us to mark the searched elements. Then we apply the inverse of the sum to clean the auxiliary qubits. +# + +@qml.prod +def oracle(wires_subset, wires_sum): + # Reflection on |ϕ⟂⟩ + Sum(wires_subset, wires_sum) + qml.FlipSign(0, wires=wires_sum) + qml.adjoint(Sum)(wires_subset, wires_sum) + + +@qml.qnode(dev) +def circuit(): + U(wires=range(n)) # Generate initial state + oracle(range(n), range(n, n+5)) # Apply the reflection on |ϕ⟂⟩ + return qml.state() + + +output = circuit()[0::2 ** 5].real +plt.bar(range(len(output)), output) +plt.ylim(-0.4, 0.6) +plt.ylabel("Amplitude") +plt.xlabel("|i⟩") +plt.axhline(0, color='black', linewidth=1) +plt.show() + +############################################################################## +# Great, we have flipped the sign of the solution states. Note that in this example we can check every possible subset explicitly, but this becomes exponentially hard for larger input sets. Still, this operator would correctly flip the sign of any solution. +# The next step is to reflect on the :math:`|\Psi\rangle` state. +# This reflection operator can be built directly in PennyLane with :class:`~.pennylane.Reflection`. +# The final circuit is then: +# +# .. figure:: ../_static/demonstration_assets/intro_amplitude_amplification/sum_zero.jpeg +# :align: center +# :width: 88% +# :target: javascript:void(0) +# + + +@qml.qnode(dev) +def circuit(): + U(wires=range(n)) # Generate initial state + oracle(range(n), range(n, n + 5)) # Apply the reflection on |ϕ⟂⟩ + qml.Reflection(U(wires=range(n))) # Reflect on |Ψ⟩ + return qml.state() + +############################################################################## +# Let's now look at the state :math:`|\Psi\rangle` and see how it is changed. + +output = circuit()[0::2 ** 5].real +plt.bar(range(len(output)), output) +plt.ylim(-0.4, 0.6) +plt.ylabel("Amplitude") +plt.xlabel("|i⟩") +plt.axhline(0, color='black', linewidth=1) +plt.show() + +############################################################################## +# We have now amplified the amplitude of all the states that represent a solution to our problem. +# The four peaks are obtained in :math:`0`, :math:`27`, :math:`35` and :math:`61,` whose binary +# representation corresponds with :math:`|000000\rangle`, :math:`|011011\rangle,` :math:`|100011\rangle` and :math:`|111101\rangle` respectively. +# These states satisfy the property that the sum of the subset is :math:`0.` +# +# Let's use the :class:`~.pennylane.AmplitudeAmplification` to code the problem in a more compact way. +# This template expects as input, the unitary :math:`U` that prepares :math:`|\Psi\rangle,` the reflection with respect +# to :math:`|\phi^{\perp}\rangle` (i.e. the oracle), and the number of iterations. +# We increase the number of iterations in order to study the evolution of the initial state: + +@qml.qnode(dev) +def circuit(iters): + U(wires=range(n)) + qml.AmplitudeAmplification(U = U(wires = range(n)), + O = oracle(range(n), range(n, n + 5)), + iters = iters) + + return qml.probs(wires = range(n)) + +fig, axs = plt.subplots(2, 2, figsize=(14, 10)) +for i in range(1,9,2): + output = circuit(iters=i) + ax = axs[i // 4, i //2 % 2] + ax.bar(range(len(output)), output) + ax.set_ylim(0, 0.6) + ax.set_title(f"Iteration {i}") + +plt.tight_layout() +plt.subplots_adjust(bottom=0.1) +plt.axhline(0, color='black', linewidth=1) +plt.show() + +############################################################################## +# We can see that as the number of iterations increases, the probability of success increases as well. But we should be careful to not overdo it: after 5 iterations in our example, the amplitudes have in fact decreased. This phenomenon is known as "overcooking" and is a +# consequence of rotating the state too much. It can be addressed using fixed-point amplitude amplification, which we discuss below. +# +# .. figure:: ../_static/demonstration_assets/intro_amplitude_amplification/overcook.gif +# :align: center +# :width: 50% +# +# +# +# Fixed-point Amplitude Amplification +# ------------------------------------- +# In the above example, we have a problem: we do not know the number of solutions. Because of this we cannot +# calculate the exact number of iterations needed, so we do not know when to stop and might overcook the state. However, there is a variant +# of Amplitude Amplification that solves this issue: the fixed-point quantum search variant [#fixedpoint]_. +# +# The idea behind this technique is to avoid overcooking by gradually reducing the intensity of the resulting rotation with +# the help of an auxiliary qubit. +# The speed at which we decrease this intensity is carefully studied +# in reference [#fixedpoint]_ and has a very interesting interpretation related to the approximation of the +# sign function [#unification]_. +# +# To use this variant we simply set ``fixed_point = True`` and indicate the auxiliary qubit. +# Let's see what happens with the same example as before: + +@qml.qnode(dev) +def circuit(iters): + U(wires=range(n)) + qml.AmplitudeAmplification(U = U(wires = range(n)), + O = oracle(range(n), range(n, n + 5)), + iters = iters, + fixed_point=True, + work_wire = n + 5) + + return qml.probs(wires = range(n)) + +fig, axs = plt.subplots(2, 2, figsize=(14, 10)) +for i in range(1,9,2): + output = circuit(iters=i) + ax = axs[i // 4, i //2 % 2] + ax.bar(range(len(output)), output) + ax.set_ylim(0, 0.6) + ax.set_title(f"Iteration {i}") + +plt.tight_layout() +plt.subplots_adjust(bottom=0.1) +plt.axhline(0, color='black', linewidth=1) +plt.show() + +############################################################################## +# Unlike before, we can see that the probability of success does not decrease for a large number of iterations. +# +# Conclusion +# ----------- +# +# In this demo we have shown the process of finding unknown states with Amplitude Amplification. +# We discussed some of its limitations and learned how to overcome them with the fixed-point version. +# This algorithm is important in a variety of workflows such as molecular simulation with QPE. This shouldn't be surprising, as it is generally helpful to rapidly amplify the amplitude of a target state. Moreover, the idea of using +# the reflections can be extrapolated to algorithms such as Qubitization or QSVT. +# PennyLane supports the Amplitude Amplification algorithm and its variants such as fixed-point and Oblivious Amplitude Amplification [#oblivious]_. +# We encourage you to explore them and see how they can help you in your quantum algorithms. +# +# +# References +# ---------- +# +# .. [#ampamp] +# +# Gilles Brassard, Peter Hoyer, Michele Mosca and Alain Tapp. +# "Quantum Amplitude Amplification and Estimation", +# `arXiv:quant-ph/0005055 `__, 2000. +# +# .. [#fixedpoint] +# +# Theodore J. Yoder, Guang Hao Low and Isaac L. Chuang. +# "Fixed-point quantum search with an optimal number of queries", +# `arXiv:1409.3305 `__, 2014. +# +# .. [#unification] +# +# John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang. +# “A Grand Unification of Quantum Algorithms”, +# `PRX Quantum 2,040203 `__, 2021. +# +# .. [#oblivious] +# +# Dominic W. Berry, et al. +# "Simulating Hamiltonian dynamics with a truncated Taylor series", +# `arXiv:1412.4687 `__, 2014 +# diff --git a/demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json b/demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json new file mode 100644 index 0000000000..f49538ef9d --- /dev/null +++ b/demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json @@ -0,0 +1,85 @@ +{ + "title": "Intro to Amplitude Amplification", + "authors": [ + { + "username": "KetPuntoG" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2024-05-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/intro_amplitude_amplification/thumbnail_AmplitudeAmplification_2024-04-29.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_AmplitudeAmplification_2024-04-29.png" + } + ], + "seoDescription": "Learn Amplitude Amplification from scratch and how to use fixed-point quantum search", + "doi": "", + "references": [ + { + "id": "ampamp", + "type": "article", + "title": "Quantum Amplitude Amplification and Estimation", + "authors": "Gilles Brassard, Peter Hoyer, Michele Mosca, Alain Tapp", + "year": "2000", + "journal": "arXiv", + "url": "https://arxiv.org/abs/quant-ph/0005055" + }, + { + "id": "fixedpoint", + "type": "article", + "title": "Fixed-point quantum search with an optimal number of queries", + "authors": "Theodore J. Yoder, Guang Hao Low, Isaac L. Chuang", + "year": "2014", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/abs/1409.3305" + }, + { + "id": "unification", + "type": "article", + "title": "A Grand Unification of Quantum Algorithms", + "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang", + "year": "2021", + "publisher": "PRX Quantum", + "journal": "arXiv", + "url": "https://arxiv.org/abs/2105.02859" + }, + { + "id": "oblivious", + "type": "article", + "title": "Simulating Hamiltonian dynamics with a truncated Taylor series", + "authors": "Dominic W. Berry, et al.", + "year": "2014", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/pdf/1412.4687.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_grovers_algorithm", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in b/demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_intro_qrom/demo.py b/demonstrations_v2/tutorial_intro_qrom/demo.py new file mode 100644 index 0000000000..007109cd6f --- /dev/null +++ b/demonstrations_v2/tutorial_intro_qrom/demo.py @@ -0,0 +1,352 @@ +r"""Intro to quantum read-only memory (QROM) +============================================================= + +Managing data is a crucial task, and quantum computers are no exception: efficient data management is vital in `quantum machine learning `__, search algorithms, and :doc:`state preparation `. +In this demonstration, we will discuss the concept of a quantum read-only memory (QROM), a data structure designed to load classical data into a quantum computer. +This is a valuable tool in quantum machine learning or for preparing quantum states among others. +We also explain how to use this operator in PennyLane using the :class:`~.pennylane.QROM` template. + + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_qrom.png + :align: center + :width: 70% + :target: javascript:void(0) + +QROM +----- + +Quantum read-only memory (QROM) is an operator that allows us to load classical data into a quantum computer. Data are represented as a collection of bitstrings (lists composed of 0s and 1s) that we denote by :math:`b_0, b_1, \ldots, b_{N-1}.` The QROM operator is then defined as: + +.. math:: + + \text{QROM}|i\rangle|0^{\otimes m}\rangle = |i\rangle|b_i\rangle, + +where :math:`|b_i\rangle` is the bitstring associated with the :math:`i`-th computational basis state, and :math:`m` is the length of the bitstrings. We have assumed all the bitstrings are of equal length. + +For example, suppose our data consists of eight bitstrings, each with two bits: :math:`[01, 11, 11, 00, 01, 11, 11, 00].` Then, the index register will consist of three +qubits (:math:`3 = \log_2 8`) and the target register of two qubits (:math:`m = 2`). For instance, for the +first four indices, the QROM operator acts as: + +.. math:: + \begin{align} + \text{QROM}|000\rangle|00\rangle &= |000\rangle|01\rangle \\ + \text{QROM}|001\rangle|00\rangle &= |001\rangle|11\rangle \\ + \text{QROM}|010\rangle|00\rangle &= |010\rangle|11\rangle \\ + \text{QROM}|011\rangle|00\rangle &= |011\rangle|00\rangle. + \end{align} + +We will now explain three different implementations of QROM: *Select*, *SelectSwap*, and an extension of *SelectSwap*. + +Select +~~~~~~~ + +:class:`~.pennylane.Select` is an operator that prepares quantum states associated with indices. It is defined as: + +.. math:: + + \text{Select}|i\rangle|0\rangle = |i\rangle U_i|0\rangle =|i\rangle|\phi_i\rangle, + +where :math:`|\phi_i\rangle` is the :math:`i`-th state we want to encode, generated by a known unitary :math:`U_i.` +QROM can be considered a special case of the *Select* operator where the encoded states are computational basis states. +Then the unitaries :math:`U_i` can be simply products of :math:`X` gates satisfying: + +.. math:: + + U_i|0\rangle =|b_i\rangle. + +We use :class:`~.pennylane.BasisState` as a useful template for implementing the gates :math:`U_i.` Let's see how it could be written in code: + +""" + +import pennylane as qml +import numpy as np +from functools import partial +import matplotlib.pyplot as plt + + +bitstrings = ["01", "11", "11", "00", "01", "11", "11", "00"] + +control_wires = [0, 1, 2] +target_wires = [3, 4] + +Ui = [qml.BasisState(int(bitstring, 2), target_wires) for bitstring in bitstrings] + +dev = qml.device("default.qubit", shots=1) + + +# This line is included for drawing purposes only. +@partial(qml.devices.preprocess.decompose, stopping_condition=lambda obj: False, max_expansion=1) + +@qml.qnode(dev) +def circuit(index): + qml.BasisState(index, wires=control_wires) + qml.Select(Ui, control=control_wires) + return qml.sample(wires=target_wires) + + +qml.draw_mpl(circuit, style="pennylane")(3) +plt.show() + +############################################################################## +# Now we can check that all the outputs are as expected: + +for i in range(8): + print(f"The bitstring stored in index {i} is: {circuit(i)}") + + +############################################################################## +# The outputs match the elements of our initial data list: :math:`[01, 11, 11, 00, 01, 11, 11, 00].` Nice! +# +# The :class:`~.pennylane.QROM` template can be used to implement the previous circuit using directly the bitstring +# without having to calculate the :math:`U_i` gates: + +import warnings +# This line will suppress ComplexWarnings for output visibility +warnings.filterwarnings(action="ignore", category=np.ComplexWarning) + +bitstrings = ["01", "11", "11", "00", "01", "11", "11", "00"] + +control_wires = [0, 1, 2] +target_wires = [3, 4] + + +@partial(qml.compile, basis_set="CNOT") # Line added for resource estimation purposes only. +@qml.qnode(dev) +def circuit(index): + qml.BasisState(index, wires=control_wires) + qml.QROM(bitstrings, control_wires, target_wires, work_wires=None) + return qml.sample(wires=target_wires) + + +for i in range(8): + print(f"The bitstring stored in index {i} is: {circuit(i)}") + +############################################################################## +# Although this approach works correctly, the number of multicontrol gates is high — gates with a costly decomposition. +# Here we show the number of 1 and 2 qubit gates we use when decomposing the circuit: + +print("Number of qubits: ", len(control_wires + target_wires)) +print("One-qubit gates: ", qml.specs(circuit)(0)["resources"].gate_sizes[1]) +print("Two-qubit gates: ", qml.specs(circuit)(0)["resources"].gate_sizes[2]) + +############################################################################## +# You can learn more about these resource estimation methods in +# the `PennyLane documentation `__. +# There are numerous works that attempt to simplify this, of which +# we highlight reference [#unary]_, which introduces an efficient technique using measurements in the middle +# of the circuit. Another clever approach was introduced in [#selectSwap]_ , with a smart structure known as *SelectSwap*, +# which we describe below. +# +# SelectSwap +# ~~~~~~~~~~ +# The goal of the *SelectSwap* construction is to trade depth of the circuit for width. That is, using multiple auxiliary qubits, +# we reduce the circuit depth required to build the QROM. Before we get into how it works, let's show you how easy it is to use: +# we simply add ``work_wires`` to the code we had previously. +# + +bitstrings = ["01", "11", "11", "00", "01", "11", "11", "00"] + +control_wires = [0, 1, 2] +target_wires = [3, 4] +work_wires = [5, 6] + + +@partial(qml.compile, basis_set="CNOT") +@qml.qnode(dev) +def circuit(index): + qml.BasisState(index, wires=control_wires) + # added work wires below + qml.QROM(bitstrings, control_wires, target_wires, work_wires, clean=False) + return qml.sample(wires=control_wires + target_wires + work_wires) + +print("Number of qubits: ", len(control_wires + target_wires + work_wires)) +print("One-qubit gates: ", qml.specs(circuit)(0)["resources"].gate_sizes[1]) +print("Two-qubit gates: ", qml.specs(circuit)(0)["resources"].gate_sizes[2]) + +############################################################################## +# The number of 1 and 2 qubit gates is significantly reduced! +# +# Internally, the main idea of this approach is to organize the :math:`U_i` operators into two dimensions, +# whose positions will be determined by a column index :math:`c` and a row index :math:`r.` +# +# .. figure:: ../_static/demonstration_assets/qrom/select_swap.jpeg +# :align: center +# :width: 70% +# :target: javascript:void(0) +# +# Following this structure, for instance, the :math:`U_5` operator (or :math:`101` in binary) is in column :math:`2` and row :math:`1` (zero-based indexing): +# +# .. figure:: ../_static/demonstration_assets/qrom/indixes_qrom.jpeg +# :align: center +# :width: 70% +# :target: javascript:void(0) +# +# In order to load the desired bitstring in the target wires, we use two building blocks in the construction: +# +# - **Select block**: Loads the :math:`c`-th column in the target and work wires. +# - **Swap block**: Swaps the :math:`r`-th row to the target wires. +# +# +# Let's look at an example by assuming we want to load in the target wires the bitstring with +# the index :math:`5,` i.e., :math:`U_5|0\rangle = |b_5\rangle.` +# +# .. figure:: ../_static/demonstration_assets/qrom/example_selectswap.jpeg +# :align: center +# :width: 85% +# :target: javascript:void(0) +# +# Now we run the circuit with our initial data list: :math:`[01, 11, 11, 00, 01, 11, 11, 00].` + +index = 5 +output = circuit(index) +print(f"control wires: {output[:3]}") +print(f"target wires: {output[3:5]}") +print(f"work wires: {output[5:7]}") + + +############################################################################## +# As expected, :math:`|b_5\rangle = |11\rangle` is loaded in the target wires. +# Note that with more auxiliary qubits we could make larger groupings of bitstrings reducing the depth of the +# *Select* operator. Below we show an example with two columns and four rows: +# +# .. figure:: ../_static/demonstration_assets/qrom/select_swap_4.jpeg +# :align: center +# :width: 70% +# :target: javascript:void(0) +# +# The QROM template will put as many rows as possible using the ``work_wires`` we pass. +# Let's check how it looks in PennyLane: + +bitstrings = ["01", "11", "11", "00", "01", "11", "11", "00"] + +control_wires = [0, 1, 2] +target_wires = [3, 4] +work_wires = [5, 6, 7, 8, 9, 10, 11, 12] + + +# Line added for drawing purposes only +@partial(qml.devices.preprocess.decompose, stopping_condition=lambda obj: False, max_expansion=2) +@qml.qnode(qml.device("default.qubit", shots=1)) +def circuit(index): + qml.BasisState(index, wires=control_wires) + qml.QROM(bitstrings, control_wires, target_wires, work_wires, clean=False) + return qml.sample(wires=target_wires), qml.sample(wires=target_wires) + + +qml.draw_mpl(circuit, style="pennylane")(0) +plt.show() + + +############################################################################## +# The circuit matches the one described above. +# +# Reusable qubits +# ~~~~~~~~~~~~~~~~ +# +# The above approach has a drawback. The work wires have been altered, i.e., after applying the operator they have not +# been returned to state :math:`|00\rangle.` This could cause unwanted behaviors, but in PennyLane it can be easily solved +# by setting the parameter ``clean = True``. + + +bitstrings = ["01", "11", "11", "00", "01", "11", "11", "00"] + +control_wires = [0, 1, 2] +target_wires = [3, 4] +work_wires = [5, 6] + + +@qml.qnode(dev) +def circuit(index): + qml.BasisState(index, wires=control_wires) + qml.QROM(bitstrings, control_wires, target_wires, work_wires, clean=True) + return qml.sample(wires=target_wires + work_wires) + + +for i in range(8): + print(f"The bitstring stored in index {i} is: {circuit(i)[:2]}") + print(f"The work wires for that index are in the state: {circuit(i)[2:4]}\n") + + +############################################################################## +# All the work wires have been reset to the zero state. +# +# To achieve this, we follow the technique shown in [#cleanQROM]_, where the proposed circuit (with :math:`R` rows) is as follows: +# +# .. figure:: ../_static/demonstration_assets/qrom/clean_version_2.jpeg +# :align: center +# :width: 90% +# :target: javascript:void(0) +# +# To see how this circuit works, let's suppose we want to load the bitstring :math:`b_{cr}` in the target wires, where :math:`b_{cr}` +# is the bitstring whose operator :math:`U` is placed in the :math:`c`-th column and :math:`r`-th row in the two-dimensional representation shown in the *Select* block. +# We can summarize the idea in a few simple steps. +# +# 0. **Initialize the state.** We create the state: +# +# .. math:: +# |c\rangle |r\rangle |0\rangle |0\rangle \dots |0\rangle. +# +# 1. **A uniform superposition is created in the r-th register of the work wires**. To do this, we put the Hadamards in the target wires and move it to the :math:`r`-th row with the *Swap* block: +# +# .. math:: +# |c\rangle |r\rangle |0\rangle |0\rangle \dots |+\rangle_r \dots |0\rangle. +# +# 2. **The Select block is applied.** This loads the whole :math:`c`-th column in the registers. Note that in the :math:`r`-th position, the *Select* has no effect since the state :math:`|+\rangle` is not modified by :math:`X` gates: +# +# .. math:: +# |c\rangle |r\rangle |b_{c0}\rangle |b_{c1}\rangle \dots |+\rangle_r \dots |b_{c(R-1)}\rangle. +# +# +# 3. **The Hadamard gate is applied to the r-th register of the work wires.** This returns that register to the zero state. The two *Swap* blocks and the Hadamard gate applied to the target wires achieve this: +# +# .. math:: +# |c\rangle |r\rangle |b_{c0}\rangle |b_{c1}\rangle \dots |0\rangle_r \dots |b_{c(R-1)}\rangle. +# +# 4. **Select block is applied.** Thanks to this, we clean the used registers. That is because loading the bitstring twice in the same register leaves the state as :math:`|0\rangle` since :math:`X^2 = \mathbb{I}.` On the other hand, the bitstring :math:`|b_{cr}\rangle` is loaded in the :math:`r` register: +# +# .. math:: +# |c\rangle |r\rangle |0\rangle |0\rangle \dots |b_{cr}\rangle_r \dots |0\rangle. +# +# 5. **Swap block is applied.** With this, we move :math:`|b_{cr}\rangle` that is encoded in the r-th row to the target wires: +# +# .. math:: +# |c\rangle |r\rangle |b_{cr}\rangle |0\rangle \dots |0\rangle_r \dots |0\rangle. +# +# The desired bitstring has been encoded in the target wires and the rest of the qubits have been left in the zero state. +# +# Conclusion +# ---------- +# +# By implementing various versions of the QROM operator, such as *Select* and *SelectSwap*, we can optimize quantum circuits +# for enhanced performance and scalability. These methods improve the efficiency of +# state preparation [#StatePrep]_ techniques. After all, state preparation is a special case of data encoding, where the data are the coefficients that define the state. +# QROM methods are particularly attractive for large-scale quantum computing due to their superior asymptotic efficiency. +# This makes them an indispensable tool for developing new algorithms. +# +# References +# ---------- +# +# .. [#unary] +# +# Ryan Babbush, Craig Gidney, Dominic W. Berry, Nathan Wiebe, Jarrod McClean, Alexandru Paler, Austin Fowler, and Hartmut Neven, +# "Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity," +# `Physical Review X, 8(4), 041015 (2018). `__, 2018 +# +# .. [#selectSwap] +# +# Guang Hao Low, Vadym Kliuchnikov, and Luke Schaeffer, +# "Trading T-gates for dirty qubits in state preparation and unitary synthesis", +# `arXiv:1812.00954 `__, 2018 +# +# .. [#cleanQROM] +# +# Dominic W. Berry, Craig Gidney, Mario Motta, Jarrod R. McClean, and Ryan Babbush, +# "Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization", +# `Quantum 3, 208 `__, 2019 +# +# .. [#StatePrep] +# +# Lov Grover and Terry Rudolph, +# "Creating superpositions that correspond to efficiently integrable probability distributions", +# `arXiv:quant-ph/0208112 `__, 2002 +# diff --git a/demonstrations_v2/tutorial_intro_qrom/metadata.json b/demonstrations_v2/tutorial_intro_qrom/metadata.json new file mode 100644 index 0000000000..8a0b69e144 --- /dev/null +++ b/demonstrations_v2/tutorial_intro_qrom/metadata.json @@ -0,0 +1,81 @@ +{ + "title": "Intro to quantum read-only memory (QROM)", + "authors": [ + { + "username": "KetPuntoG" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2024-09-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qrom.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qrom.png" + } + ], + "seoDescription": "Learn how to enter data into a quantum computer with a quantum read-only memory (QROM) approach.", + "doi": "", + "references": [ + { + "id": "unary", + "type": "article", + "title": "Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity", + "authors": "Ryan Babbush, Craig Gidney, Dominic W. Berry, Nathan Wiebe, Jarrod McClean, Alexandru Paler, Austin Fowler, Hartmut Neven", + "year": "2018", + "publisher": "", + "journal": "Physical Review X", + "url": "http://dx.doi.org/10.1103/PhysRevX.8.041015" + }, + { + "id": "selectSwap", + "type": "article", + "title": "Trading T-gates for dirty qubits in state preparation and unitary synthesis", + "authors": "Guang Hao Low, Vadym Kliuchnikov, Luke Schaeffer", + "year": "2018", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/abs/1812.00954" + }, + { + "id": "cleanQROM", + "type": "article", + "title": "Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization", + "authors": "Dominic W. Berry, Craig Gidney, Mario Motta, Jarrod R. McClean, Ryan Babbush", + "year": "2019", + "publisher": "", + "journal": "Quantum", + "url": "http://dx.doi.org/10.22331/q-2019-12-02-208" + }, + { + "id": "StatePrep", + "type": "article", + "title": "Creating superpositions that correspond to efficiently integrable probability distributions", + "authors": "Lov Grover, Terry Rudolph", + "year": "2002", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/abs/quant-ph/0208112" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_initial_state_preparation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_intro_qrom/requirements.in b/demonstrations_v2/tutorial_intro_qrom/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_intro_qrom/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_intro_qsvt/demo.py b/demonstrations_v2/tutorial_intro_qsvt/demo.py new file mode 100644 index 0000000000..0989db9829 --- /dev/null +++ b/demonstrations_v2/tutorial_intro_qsvt/demo.py @@ -0,0 +1,314 @@ +r"""Intro to QSVT +============================================================= + +.. meta:: + :property="og:description": Introduction to the Quantum Singular Value Transformation algorithm + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_intro_qsvt.png + +.. related:: + + function_fitting_qsp Function Fitting using Quantum Signal Processing + +*Author: Juan Miguel Arrazola — Posted: May 23, 2023.* + +Few quantum algorithms deserve to be placed in a hall of fame: Shor's algorithm, Grover's algorithm, quantum phase estimation; +maybe even HHL and VQE. There is now a new technique with prospects of achieving such celebrity status: +the quantum singular value transformation (QSVT). Since you're reading this, chances are you have at least heard of QSVT and its broad +applicability. + +This tutorial introduces the fundamental principles of QSVT with example code from PennyLane. We focus on the basics; +while these techniques may appear intimidating in the literature, the fundamentals are relatively easy to grasp. Teaching +you these core principles is the purpose of this tutorial. + +| + +.. figure:: ../_static/demonstration_assets/intro_qsvt/thumbnail_intro_qsvt.png + :align: center + :width: 50% + :target: javascript:void(0) + +| + +Transforming scalars encoded in matrices +----------------------------------------- +My personal perspective on QSVT is that it is really a result in linear algebra that tells us how to +transform matrices that are inside larger unitary matrices. + +Let's start with the simplest example: +we encode a scalar :math:`a` inside a 2x2 matrix :math:`U(a).` By encoding we mean that the +matrix depends explicitly on :math:`a.` This encoding can be achieved in +multiple ways, for example: + +.. math:: U(a) = \begin{pmatrix} a & \sqrt{1-a^2}\\ + \sqrt{1-a^2} & -a + \end{pmatrix}. + +The parameter :math:`a` must lie between -1 and 1 to ensure the matrix is unitary, but this is just a matter of rescaling. +We want the matrix to be unitary so that it can be implemented on a quantum computer. + +We now ask the crucial question that will get everything started: +what happens if we repeatedly alternate multiplication of this matrix by some other matrix? 🤔 +There are multiple choices for the "other matrix", for example + +.. math:: S(\phi) = \begin{pmatrix} e^{i\phi} & 0\\ + 0 & e^{-i\phi} + \end{pmatrix}, + +which has the advantage of being diagonal. This unitary is known as the **signal-processing** operator. +It depends on a choice of angle :math:`\phi` that will play an important role. + +The answer to our question is encapsulated in a method known as **quantum signal processing** (QSP). +If we alternate products of :math:`U(a)` and :math:`S(\phi)`, keeping :math:`a` fixed and varying :math:`\phi,` +the top-left corner of the resulting matrix is a polynomial transformation of :math:`a.` Mathematically, + +.. math:: S(\phi_0)\prod_{k=1}^d U(a) S(\phi_k) = \begin{pmatrix} + P(a) & *\\ + * & * + \end{pmatrix}. + +The asterisk :math:`*` is used to indicate that we are not interested in these entries. + +The intuition behind this result is that every time we multiply by :math:`U(a),` its entries are +transformed to a polynomial of higher degree, and by interleaving signal-processing operators, +it's possible to tune the coefficients of the polynomial. For example + +.. math:: S(-\pi/2) U(a) S(\pi/2) U(a) S(0) = \begin{pmatrix} + 2a^2-1 & 0\\ + 0 & 2a^2 -1 + \end{pmatrix}. + +The main quantum signal processing theorem states that it is possible to find +angles that implement a large class of complex polynomial transformations :math:`P(a)` with maximum degree and +parity determined by the number of angles [#unification]_. + +The results of the +theorem can then be extended to show that using additional rotations, it is possible to +find :math:`d+1` angles that implement any real polynomial of parity :math:`d \mod 2` and maximum degree :math:`d.` +Multiple QSP sequences can then be used to implement real polynomials of indefinite parity. +Finding the desired angles can be done efficiently in practice, but identifying the best +methods is an active area of research. You can learn more in our `QSP demo `_ +and in Ref. [#unification]_. + +For now, let's look at a simple example of how quantum signal processing can be implemented using +PennyLane. We aim to perform a transformation by the Legendre polynomial +:math:`(5 x^3 - 3x)/2,` for which we use pre-computed optimal angles. +As you will soon learn, QSP can be viewed as a special case of QSVT. We thus use the :func:`~.pennylane.qsvt` +operation to construct the output matrix and compare the resulting transformation to +the target polynomial. + +""" + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + + +def target_poly(a): + return 0.5 * (5 * a**3 - 3 * a) + + +# pre-optimized angles +angles = [-0.20409113, -0.91173829, 0.91173829, 0.20409113] + + +def qsvt_output(a): + # output matrix + out = qml.matrix(qml.qsvt(a, angles, wires=[0])) + return out[0, 0] # top-left entry + + +a_vals = np.linspace(-1, 1, 50) +qsvt = [np.real(qsvt_output(a)) for a in a_vals] # neglect small imaginary part +target = [target_poly(a) for a in a_vals] + + +plt.plot(a_vals, target, label="target") +plt.plot(a_vals, qsvt, "*", label="qsvt") + +plt.legend() +plt.show() + + +############################################################################## +# It works! 🎉 💃 +# +# Quantum signal processing is a result about multiplication of 2x2 matrices, yet it is the core principle +# underlying the QSVT algorithm. If you've made it this far, you're in great shape for the rest to come 🥇 +# +# +# Transforming matrices encoded in matrices +# ------------------------------------------ +# +# Time to ask another key question: what if instead of encoding a scalar, we encode an entire matrix :math:`A?` 🧠 +# This is trickier since we need to ensure that the larger operator remains unitary. A way to achieve this +# is to use a similar construction as in the scalar case: +# +# .. math:: U(A) = \begin{pmatrix} A & \sqrt{I-A A^\dagger}\\ +# \sqrt{I-A^\dagger A} & -A^\dagger +# \end{pmatrix}. +# +# This operator is a valid unitary regardless of the form of :math:`A;` it doesn't even have to be a square matrix. We just +# need to ensure that :math:`A` is properly normalized such that its largest singular value is bounded by 1. +# +# Any such method of encoding a matrix inside a larger unitary is known as a **block encoding**. In our construction, +# the matrix :math:`A` is encoded in the top-left block, hence the name. PennyLane supports +# the :class:`~pennylane.BlockEncode` operation that follows the construction above. Let's test +# it out with an example encoding first a square matrix: + +# square matrix +A = [[0.1, 0.2], [0.3, 0.4]] +U1 = qml.BlockEncode(A, wires=range(2)) +print("U(A):") +print(np.round(qml.matrix(U1), 2)) + +############################################################################## +# And also a rectangular matrix +B = [[0.5, -0.5, 0.5]] +U2 = qml.BlockEncode(B, wires=range(2)) +print("U(B):") +print(np.round(qml.matrix(U2), 2)) + + +############################################################################## +# Notice that we haven't really made a reference to quantum computing; everything is just linear algebra. +# Told you so! 😈 +# +# Quantum kicks in when we construct circuits that implement +# a block-encoding unitary. Arguably the hardest thing about QSVT is +# implementing block encodings. We don't cover such +# methods in detail here, but for reference, a popular approach is to express :math:`A` as a linear combination of unitaries +# and define associated :math:`\text{PREPARE}` and :math:`\text{SELECT}` operators. +# Then the operator +# +# .. math:: U=\text{PREP}^\dagger\cdot\text{SEL}\cdot\text{PREP}, +# +# is a block-encoding of :math:`A` up to a constant factor. +# +# Time for a third key question: Can we use the same strategy as in QSP to +# polynomially transform a block-encoded matrix? Because that would be fantastic 😎 +# +# For this to be possible, we need to generalize the signal-processing operator +# :math:`S(\phi)` to higher dimensions. This can be done by using a diagonal unitary where we apply the phase +# :math:`e^{i\phi}` to the subspace determined by the block, +# and the phase :math:`e^{-i \phi}` everywhere else. For example, revisiting the square matrix :math:`A` in +# the code above, where :math:`A` is encoded in a two-dimensional subspace, the corresponding operator is +# +# .. math:: \begin{pmatrix} e^{i\phi} & 0 & 0 & 0\\ +# 0 & e^{i\phi} & 0 & 0 \\ +# 0 & 0 & e^{-i\phi} & 0\\ +# 0 & 0 & 0 & e^{-i\phi} \\ +# \end{pmatrix}. +# +# These are known as **projector-controlled phase gates**, for which we use the symbol :math:`\Pi(\phi).` +# +# When :math:`A` is not square, +# we have to be careful and define two operators: one acting on the row subspace and another on the +# column subspace. Projector-controlled phase gates are implemented in PennyLane using the +# :class:`~pennylane.PCPhase` operation. Here's a simple example: + +dim = 2 +phi = np.pi / 2 +pcp = qml.PCPhase(phi, dim, wires=range(2)) +print("Pi:") +print(np.round(qml.matrix(pcp), 2)) + + +############################################################################## +# As you may have guessed, this generalization of QSP does the trick. By cleverly alternating a block-encoding unitary +# and the appropriate projector-controlled phase gates, we can polynomially transform the encoded matrix. +# The result is the QSVT algorithm. +# +# Mathematically, when the polynomial degree :math:`d` is even (number of angles is :math:`d + 1`), the QSVT algorithm +# states that +# +# .. math:: \left[\prod_{k=1}^{d/2}\Pi_{\phi_{2k-1}}U(A)^\dagger \tilde{\Pi}_{\phi_{2k}} U(A)\right]\Pi_{\phi_{d+1}}= +# \begin{pmatrix} P(A) & *\\ +# * & * +# \end{pmatrix}. +# +# The tilde is used in the projector-controlled phase gates to distinguish whether they act on the column or row +# subspaces. The polynomial transformation of :math:`A` is defined in terms of its singular value decomposition +# +# .. math:: P(A) = \sum_k P(\sigma_k)|w_k\rangle \langle v_k|, +# +# where we use braket notation to denote the left and right singular vectors. +# For technical reasons, the sequence looks slightly different when the polynomial degree is odd: +# +# .. math:: \tilde{\Pi}_{\phi_1}\left[\prod_{k=1}^{(d-1)/2}\Pi_{\phi_{2k}}U(A)^\dagger \tilde{\Pi}_{\phi_{2k+1}} U(A)\right]\Pi_{\phi_{d+1}}= +# \begin{pmatrix} +# P(A) & *\\ +# * & * +# \end{pmatrix}. +# +# As with QSP, it is possible to use the QSVT sequence to apply any real polynomial transformation up to degree +# :math:`d` when using :math:`d+1` angles. In fact, as long as we're careful with conventions, +# we can use the same angles regardless of the dimensions of :math:`A.` +# +# The QSVT construction is a beautiful result. By using a number of operations that grows linearly with the +# degree of the target polynomial, we can transform the singular values of arbitrary block-encoded matrices +# without ever having to actually perform singular value decompositions! If the block encoding circuits can be +# implemented in polynomial time in the number of qubits, the resulting +# quantum algorithm will also run in polynomial time. This is very powerful. +# +# In PennyLane, implementing the QSVT transformation is as simple as using :func:`~.pennylane.qsvt`. Let's revisit +# our previous example and transform a matrix according to the same Legendre polynomial. We'll use a diagonal matrix +# with eigenvalues evenly distributed between -1 and 1, allowing us to easily check the transformation. + + +eigvals = np.linspace(-1, 1, 16) +A = np.diag(eigvals) # 16-dim matrix +wire_order = list(range(5)) +U_A = qml.matrix(qml.qsvt, wire_order=wire_order)(A, angles, wires=wire_order) # block-encoded in 5-qubit system + +qsvt_A = np.real(np.diagonal(U_A))[:16] # retrieve transformed eigenvalues + +plt.plot(a_vals, target, label="target") +plt.plot(eigvals, qsvt_A, "*", label="qsvt") + +plt.legend() +plt.show() + + +############################################################################### +# The :func:`~pennylane.qsvt` operation is tailored for use in simulators and employs standard forms for block encodings +# and projector-controlled phase shifts. Advanced users can define their own version of these operators +# with explicit quantum circuits, and construct the resulting QSVT algorithm using the :class:`~pennylane.QSVT` template. +# +# Final thoughts +# -------------- +# The original paper introducing the QSVT algorithm [#qsvt]_ described a series of potential applications, +# notably Hamiltonian simulation and solving linear systems of equations. QSVT has also been used as +# a unifying framework for different quantum algorithms [#unification]_. +# One of my favourite uses of QSVT is to transform a molecular Hamiltonian by a polynomial approximation of a step function [#lintong]_. +# This sets large eigenvalues to zero, effectively performing a projection onto a low-energy subspace. +# +# The PennyLane team is motivated to create tools that can empower researchers +# worldwide to develop the innovations that will define the present and future of quantum computing. +# We have designed QSVT support to help you master the concepts and perform rapid prototyping of new ideas. +# We look forward to seeing the innovations that will result from your journey. +# +# References +# ---------- +# +# .. [#qsvt] +# +# András Gilyén, Yuan Su, Guang Hao Low, Nathan Wiebe, +# "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", +# `Proceedings of the 51st Annual ACM SIGACT Symposium on the Theory of Computing `__, 2019 +# +# +# .. [#lintong] +# +# Lin, Lin, and Yu Tong, "Near-optimal ground state preparation", +# `Quantum 4, 372 `__, 2020 +# +# +# .. [#unification] +# +# John M. Martyn, Zane M. Rossi, Andrew K. Tan, and Isaac L. Chuang, +# "Grand Unification of Quantum Algorithms", +# `PRX Quantum 2, 040203 `__, 2021 +# +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_intro_qsvt/metadata.json b/demonstrations_v2/tutorial_intro_qsvt/metadata.json new file mode 100644 index 0000000000..21feae8866 --- /dev/null +++ b/demonstrations_v2/tutorial_intro_qsvt/metadata.json @@ -0,0 +1,69 @@ +{ + "title": "Intro to QSVT", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2023-05-23T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/intro_qsvt/thumbnail_intro_qsvt.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_intro_qsvt.png" + } + ], + "seoDescription": "Master the basics of the quantum singular value transformation", + "doi": "", + "references": [ + { + "id": "qsvt", + "type": "article", + "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", + "authors": "Andr\u00e1s Gily\u00e9n, Yuan Su, Guang Hao Low, Nathan Wiebe", + "year": "2019", + "publisher": "", + "journal": "", + "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" + }, + { + "id": "lintong", + "type": "article", + "title": "Near-optimal ground state preparation", + "authors": "Lin, Lin, and Yu Tong", + "year": "2020", + "publisher": "", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2020-12-14-372/" + }, + { + "id": "unification", + "type": "article", + "title": "Grand Unification of Quantum Algorithms", + "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, and Isaac L. Chuang", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.040203" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "function_fitting_qsp", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/qsvt-algorithm-computing-the-angles-for-projector-controlled-phase-gate/3203" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_intro_qsvt/requirements.in b/demonstrations_v2/tutorial_intro_qsvt/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_intro_qsvt/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py b/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py new file mode 100644 index 0000000000..ce9eeb3d5a --- /dev/null +++ b/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py @@ -0,0 +1,251 @@ +r""" +.. _isingmodel_PyTorch: + +3-qubit Ising model in PyTorch +============================== + +.. meta:: + :property="og:description": This demonstration uses the PyTorch interface of + PennyLane to optimize a 3-qubit Ising model. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/isingspins.png + +.. related:: + + tutorial_state_preparation Training a quantum circuit with PyTorch + pytorch_noise PyTorch and noisy devices + +*Author: Aroosa Ijaz — Posted: 16 October 2019. Last updated: 26 October 2020.* + +The interacting spins with variable coupling strengths of an `Ising model `__ +can be used to simulate various machine learning concepts like `Hopfield networks `__ +and `Boltzmann machines `__ +(`Schuld & Petruccione (2018) `_). +They also closely imitate the underlying mathematics of a subclass of computational problems called +`Quadratic Unconstrained Binary Optimization (QUBO) `__ problems. + +Ising models are commonly encountered in the subject area of adiabatic quantum computing. Quantum annealing algorithms +(for example, as performed on a D-wave system) are often used to find low-energy configurations of Ising problems. +The optimization landscape of the Ising model is non-convex, which can make finding global minima challenging. +In this tutorial, we get a closer look at this phenomenon by applying gradient descent techniques to a toy Ising model.  + +PennyLane implementation +------------------------ + +This basic tutorial optimizes a 3-qubit Ising model using the PennyLane ``default.qubit`` +device with PyTorch. In the absence of external fields, the Hamiltonian for this system is given by: + +.. math:: H=-\sum_{} J_{ij} \sigma_i \sigma_{j}, + +where each spin can be in the +1 or -1 spin state and :math:`J_{ij}` are the nearest-neighbour coupling strengths. + +For simplicity, the first spin can be assumed +to be in the "up" state (+1 eigenstate of Pauli-Z operator) and the coupling matrix can be set to :math:`J = [1,-1].` The rotation angles for the other two spins are then optimized +so that the energy of the system is minimized for the given couplings. +""" + +import torch +from torch.autograd import Variable +import pennylane as qml +from pennylane import numpy as np + +############################################################################### +# A three-qubit quantum circuit is initialized to represent the three spins: + +dev = qml.device("default.qubit", wires=3) + +@qml.qnode(dev, interface="torch") +def circuit(p1, p2): + # We use the general Rot(phi,theta,omega,wires) single-qubit operation + qml.Rot(p1[0], p1[1], p1[2], wires=1) + qml.Rot(p2[0], p2[1], p2[2], wires=2) + return [qml.expval(qml.PauliZ(i)) for i in range(3)] + +############################################################################### +# The cost function to be minimized is defined as the energy of the spin configuration: + +def cost(var1, var2): + # the circuit function returns a numpy array of Pauli-Z expectation values + spins = circuit(var1, var2) + + # the expectation value of Pauli-Z is +1 for spin up and -1 for spin down + energy = -(1 * spins[0] * spins[1]) - (-1 * spins[1] * spins[2]) + return energy + +############################################################################### +# Sanity check +# ------------ +# Let's test the functions above using the :math:`[s_1, s_2, s_3] = [1, -1, -1]` spin +# configuration and the given coupling matrix. The total energy for this Ising model +# should be: +# +# .. math:: H = -1(J_1 s_1 \otimes s_2 + J_2 s_2 \otimes s3) = 2 +# + +test1 = torch.tensor([0, np.pi, 0]) +test2 = torch.tensor([0, np.pi, 0]) + +cost_check = cost(test1, test2) +print("Energy for [1, -1, -1] spin configuration:", cost_check) + +############################################################################### +# Random initialization +# --------------------- + +torch.manual_seed(56) +p1 = Variable((np.pi * torch.rand(3, dtype=torch.float64)), requires_grad=True) +p2 = Variable((np.pi * torch.rand(3, dtype=torch.float64)), requires_grad=True) + +var_init = [p1, p2] +cost_init = cost(p1, p2) + +print("Randomly initialized angles:") +print(p1) +print(p2) +print("Corresponding cost before optimization:") +print(cost_init) + +############################################################################### +# Optimization +# ------------ +# Now we use the PyTorch gradient descent optimizer to minimize the cost: + +opt = torch.optim.SGD(var_init, lr=0.1) + +def closure(): + opt.zero_grad() + loss = cost(p1, p2) + loss.backward() + return loss + +var_pt = [var_init] +cost_pt = [cost_init] +x = [0] + +for i in range(100): + opt.step(closure) + if (i + 1) % 5 == 0: + x.append(i) + p1n, p2n = opt.param_groups[0]["params"] + costn = cost(p1n, p2n) + var_pt.append([p1n, p2n]) + cost_pt.append(costn) + + # for clarity, the angles are printed as numpy arrays + print("Energy after step {:5d}: {: .7f} | Angles: {}".format( + i+1, costn, [p1n.detach().numpy(), p2n.detach().numpy()]),"\n" + ) + + +############################################################################### +# +# .. note:: +# When using the *PyTorch* optimizer, keep in mind that: +# +# 1. ``loss.backward()`` computes the gradient of the cost function with respect to all parameters with ``requires_grad=True``. +# 2. ``opt.step()`` performs the parameter update based on this *current* gradient and the learning rate. +# 3. ``opt.zero_grad()`` sets all the gradients back to zero. It's important to call this before ``loss.backward()`` to avoid the accumulation of gradients from multiple passes. +# +# Hence, its standard practice to define the ``closure()`` function that clears up the old gradient, +# evaluates the new gradient and passes it onto the optimizer in each step. +# +# The minimum energy is -2 for the spin configuration :math:`[s_1, s_2, s_3] = [1, 1, -1]` +# which corresponds to +# :math:`(\phi, \theta, \omega) = (0, 0, 0)` for the second spin and :math:`(\phi, \theta, \omega) = (0, \pi, 0)` for +# the third spin. Note that gradient descent optimization might not find this global minimum due to the non-convex cost function, as is shown in the next section. + +p1_final, p2_final = opt.param_groups[0]["params"] +print("Optimized angles:") +print(p1_final) +print(p2_final) +print("Final cost after optimization:") +print(cost(p1_final, p2_final)) + +############################################################################### + +import matplotlib +import matplotlib.pyplot as plt + +fig = plt.figure(figsize=(6, 4)) + +# Enable processing the Torch trainable tensors +with torch.no_grad(): + plt.plot(x, cost_pt, label = 'global minimum') + plt.xlabel("Optimization steps") + plt.ylabel("Cost / Energy") + plt.legend() + plt.show() + +############################################################################### +# Local minimum +# ------------- +# If the spins are initialized close to the local minimum of zero energy, the optimizer is +# likely to get stuck here and never find the global minimum at -2. + +torch.manual_seed(9) +p3 = Variable((np.pi*torch.rand(3, dtype = torch.float64)), requires_grad = True) +p4 = Variable((np.pi*torch.rand(3, dtype = torch.float64)), requires_grad = True) + +var_init_loc = [p3, p4] +cost_init_loc = cost(p3, p4) + +print("Corresponding cost before optimization:") +print(cost_init_loc) + + +############################################################################### + +opt = torch.optim.SGD(var_init_loc, lr = 0.1) + +def closure(): + opt.zero_grad() + loss = cost(p3, p4) + loss.backward() + return loss + +var_pt_loc = [var_init_loc] +cost_pt_loc = [cost_init_loc] + +for j in range(100): + opt.step(closure) + if (j + 1) % 5 == 0: + p3n, p4n = opt.param_groups[0]['params'] + costn = cost(p3n, p4n) + var_pt_loc.append([p3n, p4n]) + cost_pt_loc.append(costn) + + # for clarity, the angles are printed as numpy arrays + print('Energy after step {:5d}: {: .7f} | Angles: {}'.format( + j+1, costn, [p3n.detach().numpy(), p4n.detach().numpy()]),"\n" + ) + +############################################################################### + +fig = plt.figure(figsize=(6, 4)) + +# Enable processing the Torch trainable tensors +with torch.no_grad(): + plt.plot(x, cost_pt_loc, 'r', label = 'local minimum') + plt.xlabel("Optimization steps") + plt.ylabel("Cost / Energy") + plt.legend() + plt.show() + +############################################################################### +# | +# +# Try it yourself! Download and run this file with different +# initialization parameters and see how the results change. +# +# Further reading +# --------------- +# +# 1. Maria Schuld and Francesco Petruccione. "Supervised Learning with Quantum Computers." +# Springer, 2018. +# +# 2. Andrew Lucas. "Ising formulations of many NP problems." +# `arXiv:1302.5843 `__, 2014. +# +# 3. Gary Kochenberger et al. "The Unconstrained Binary Quadratic Programming Problem: A Survey." +# `Journal of Combinatorial Optimization `__, 2014. +# +# diff --git a/demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json b/demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json new file mode 100644 index 0000000000..2c3c42db3a --- /dev/null +++ b/demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json @@ -0,0 +1,66 @@ +{ + "title": "3-qubit Ising model in PyTorch", + "authors": [ + { + "username": "aijaz" + } + ], + "dateOfPublication": "2019-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_3qubit_Ising_model_PyTorch.png" + } + ], + "seoDescription": "This demonstration uses the PyTorch interface of PennyLane to optimize a 3-qubit Ising model.", + "doi": "", + "references": [ + { + "id": "Schuld2018", + "type": "article", + "title": "Supervised Learning with Quantum Computers.", + "authors": "Maria Schuld and Francesco Petruccione", + "year": "2018", + "journal": "", + "publisher": "Springer", + "url": "" + }, + { + "id": "Lucas2014", + "type": "article", + "title": "Ising formulations of many NP problems.", + "authors": "Andrew Lucas", + "year": "2014", + "journal": "", + "url": "https://arxiv.org/pdf/1302.5843" + }, + { + "id": "Kochenberger2014", + "type": "article", + "title": "The Unconstrained Binary Quadratic Programming Problem: A Survey.", + "authors": "Gary Kochenberger et al.", + "year": "2014", + "journal": "Journal of Combinatorial Optimization", + "url": "https://link.springer.com/article/10.1007/s10878-014-9734-0" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_state_preparation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in b/demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in new file mode 100644 index 0000000000..929357a2b9 --- /dev/null +++ b/demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +torch diff --git a/demonstrations_v2/tutorial_jax_transformations/demo.py b/demonstrations_v2/tutorial_jax_transformations/demo.py new file mode 100644 index 0000000000..846e9a8587 --- /dev/null +++ b/demonstrations_v2/tutorial_jax_transformations/demo.py @@ -0,0 +1,308 @@ +r""" +Using JAX with PennyLane +======================== + +.. meta:: + :property="og:description": Learn how to use JAX with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/jax.png + +.. related:: + + tutorial_qubit_rotation Basic tutorial: qubit rotation + tutorial_vqe A brief overview of VQE + tutorial_vqt Variational Quantum Thermalizer + +*Author: Chase Roberts — Posted: 12 April 2021. Last updated: 12 April 2021.* + +JAX is an incredibly powerful scientific computing library that has been gaining traction in +both the physics and deep learning communities. While JAX was originally designed for +classical machine learning (ML), many of its transformations are also useful +for quantum machine learning (QML), and can be used directly with PennyLane. +""" + +############################################################################## +# .. figure:: ../_static/demonstration_assets/jax_logo/jax.png +# :width: 50% +# :align: center +# +# In this tutorial, we'll go over a number of JAX transformations and show how you can +# use them to build and optimize quantum circuits. We'll show examples of how to +# do gradient descent with ``jax.grad``, run quantum circuits in parallel +# using ``jax.vmap``, compile and optimize simulations with ``jax.jit``, +# and control and seed the random nature of quantum computer simulations +# with ``jax.random``. By the end of this tutorial you should feel just as comfortable +# transforming quantum computing programs with JAX as you do transforming your +# neural networks. +# +# If this is your first time reading PennyLane code, we recommend going through +# the :doc:`basic tutorial ` +# first. It's all in vanilla NumPy, so you should be able to +# easily transfer what you learn to JAX when you come back. +# +# With that said, we begin by importing PennyLane, JAX, the JAX-provided version of NumPy and +# set up a two-qubit device for computations. We'll be using the ``default.qubit`` device +# for the first part of this tutorial. + +import jax +import jax.numpy as jnp +import pennylane as qml + +# Added to silence some warnings. +jax.config.update("jax_enable_x64", True) + +dev = qml.device("default.qubit", wires=2) + +############################################################################## +# Let's start with a simple example circuit that generates a two-qubit entangled state, +# then evaluates the expectation value of the Pauli-Z operator on the first wire. + + +@qml.qnode(dev, interface="jax") +def circuit(param): + # These two gates represent our QML model. + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + + # The expval here will be the "cost function" we try to minimize. + # Usually, this would be defined by the problem we want to solve, + # but for this example we'll just use a single PauliZ. + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# We can now execute the circuit just like any other python function. +print(f"Result: {repr(circuit(0.123))}") + +############################################################################## +# Notice that the output of the circuit is a JAX ``DeviceArray``. +# In fact, when we use the ``default.qubit`` device, the entire computation +# is done in JAX, so we can use all of the JAX tools out of the box! +# +# Now let's move on to an example of a transformation. The code we wrote above is entirely +# differentiable, so let's calculate its gradient with ``jax.grad``. +print("\nGradient Descent") +print("---------------") + +# We use jax.grad here to transform our circuit method into one +# that calcuates the gradient of the output relative to the input. + +grad_circuit = jax.grad(circuit) +print(f"grad_circuit(jnp.pi / 2): {grad_circuit(jnp.pi / 2):0.3f}") + +# We can then use this grad_circuit function to optimize the parameter value +# via gradient descent. +param = 0.123 # Some initial value. + +print(f"Initial param: {param:0.3f}") +print(f"Initial cost: {circuit(param):0.3f}") + +for _ in range(100): # Run for 100 steps. + param -= grad_circuit(param) # Gradient-descent update. + +print(f"Tuned param: {param:0.3f}") +print(f"Tuned cost: {circuit(param):0.3f}") + +############################################################################# +# And that's QML in a nutshell! If you've done classical machine learning before, +# the above training loop should feel very familiar to you. The only difference is +# that we used a quantum computer (or rather, a simulation of one) as part of our +# model and cost calculation. In the end, almost all QML problems involve tuning some +# parameters and minimizing some cost function, just like classical ML. +# While classical ML focuses on learning classical systems like language or vision, +# QML is most useful for learning about quantum systems. For example, +# :doc:`finding chemical ground states ` +# or learning to :doc:`sample thermal energy states `. + + +############################################################################## +# Batching and Evolutionary Strategies +# ------------------------------------- +# +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxvmap.png +# :width: 50% +# :align: center +# +# We just showed how we can use gradient methods to learn a parameter value, +# but on real quantum computing hardware, calculating gradients can be really expensive and noisy. +# Another approach is to use `evolutionary strategies `__ +# (ES) to learn these parameters. +# Here, we will be using the ``jax.vmap`` `transform `__ +# to make running batches of circuits much easier. ``vmap`` essentially transforms a single quantum computer into +# multiple running in parallel! + +print("\n\nBatching and Evolutionary Strategies") +print("------------------------------------") + +# Create a vectorized version of our original circuit. +vcircuit = jax.vmap(circuit) + +# Now, we call the ``vcircuit`` with multiple parameters at once and get back a +# batch of expectations. +# This examples runs 3 quantum circuits in parallel. +batch_params = jnp.array([1.02, 0.123, -0.571]) + +batched_results = vcircuit(batch_params) +print(f"Batched result: {batched_results}") + +############################################################################## +# Let's now set up our ES training loop. The idea is pretty simple. First, we +# calculate the expected values of each of our parameters. The cost values +# then determine the "weight" of that example. The lower the cost, the larger the weight. +# These batches are then used to generate a new set of parameters. + +# Needed to do randomness with JAX. +# For more info on how JAX handles randomness, see the documentation. +# https://jax.readthedocs.io/en/latest/jax.random.html +key = jax.random.PRNGKey(0) + +# Generate our first set of samples. +params = jax.random.normal(key, (100,)) +mean = jnp.average(params) +var = 1.0 +print(f"Initial value: {mean:0.3f}") +print(f"Initial cost: {circuit(mean):0.3f}") + +for _ in range(200): + # In this line, we run all 100 circuits in parallel. + costs = vcircuit(params) + + # Use exp(-x) here since the costs could be negative. + weights = jnp.exp(-costs) + mean = jnp.average(params, weights=weights) + + # We decrease the variance as we converge to a solution. + var = var * 0.97 + + # Split the PRNGKey to generate a new set of random samples. + key, split = jax.random.split(key) + params = jax.random.normal(split, (100,)) * var + mean + +print(f"Final value: {mean:0.3f}") +print(f"Final cost: {circuit(mean):0.3f}") + + +############################################################################# +# How to use jax.jit: Compiling Circuit Execution +# ----------------------------------------------- +# .. figure:: ../_static/demonstration_assets/jax_logo/jaxjit.png +# :width: 50% +# :align: center +# +# JAX is built on top of `XLA `__, a powerful +# numerics library that can optimize and cross compile computations to different hardware, +# including CPUs, GPUs, etc. JAX can compile its computation to XLA via the ``jax.jit`` +# `transform. `__ +# +# When compiling an XLA program, the compiler will do several rounds of optimization +# passes to enhance the performance of the computation. Because of this compilation overhead, +# you'll generally find the first time calling the function to be slow, but all subsequent +# calls are much, much faster. You'll likely want to do it if you're running +# the same circuit over and over but with different parameters, like you would find in almost +# all variational quantum algorithms. + + +print("\n\nJit Example") +print("-----------") + + +@qml.qnode(dev, interface="jax") +def circuit(param): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + +# Compiling your circuit with JAX is very easy, just add jax.jit! +jit_circuit = jax.jit(circuit) + +import time + +# No jit. +start = time.time() +# JAX runs async, so .block_until_ready() blocks until the computation +# is actually finished. You'll only need to use this if you're doing benchmarking. +circuit(0.123).block_until_ready() +no_jit_time = time.time() - start + +# First call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +first_time = time.time() - start + +# Second call with jit. +start = time.time() +jit_circuit(0.123).block_until_ready() +second_time = time.time() - start + + +print(f"No jit time: {no_jit_time:0.8f} seconds") +# Compilation overhead will make the first call slower than without jit... +print(f"First run time: {first_time:0.8f} seconds") +# ... but the second run time is >100x faster than the first! +print(f"Second run time: {second_time:0.8f} seconds") + + +# You can see that for the cost of some compilation overhead, we can +# greatly increase our performance of our simulation by orders of magnitude. + +############################################################################## +# Shots and Sampling with JAX +# ---------------------------- +# +# JAX was designed to enable experiments to be as repeatable as possible. Because of this, +# JAX requires us to seed all randomly generated values (as you saw in the above +# batching example). Sadly, the universe doesn't allow us to seed real quantum computers, +# so if we want our JAX to mimic a real device, we'll have to handle randomness ourselves. +# +# To learn more about how JAX handles randomness, visit their +# `documentation site. `__ +# +# .. note:: +# This example only applies if you are using ``jax.jit``. Otherwise, PennyLane +# automatically seeds and resets the random-number-generator for you on each call. +# +# To set the random number generating key, you'll have to pass the ``jax.random.PRNGKey`` +# when constructing the device. Because of this, if you want to use ``jax.jit`` with randomness, +# the device construction will have to happen within that jitted method. + +print("\n\nRandomness") +print("----------") + + +# Let's create our circuit with randomness and compile it with jax.jit. +@jax.jit +def circuit(key, param): + # Notice how the device construction now happens within the jitted method. + dev = qml.device("default.qubit", wires=2, shots=10, seed=key) + + # Now we can create our qnode within the circuit function. + @qml.qnode(dev, interface="jax", diff_method=None) + def my_circuit(): + qml.RX(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.sample(qml.PauliZ(0)) + + return my_circuit() + + +key1 = jax.random.PRNGKey(0) +key2 = jax.random.PRNGKey(1) + +# Notice that the first two runs return exactly the same results, +print(f"key1: {circuit(key1, jnp.pi/2)}") +print(f"key1: {circuit(key1, jnp.pi/2)}") + +# The second run has different results. +print(f"key2: {circuit(key2, jnp.pi/2)}") + +################################################ +# Closing Remarks +# ---------------- +# By now, using JAX with PennyLane should feel very natural. They +# complement each other very nicely; JAX with its powerful transforms, and PennyLane +# with its easy access to quantum computers. We're still in early days of +# development, but we hope to continue to grow our ecosystem around JAX, +# and by extension, grow JAX into quantum computing and quantum machine learning. +# The future looks bright for this field, and we're excited to see what you build! +# +# diff --git a/demonstrations_v2/tutorial_jax_transformations/metadata.json b/demonstrations_v2/tutorial_jax_transformations/metadata.json new file mode 100644 index 0000000000..0a217da7b4 --- /dev/null +++ b/demonstrations_v2/tutorial_jax_transformations/metadata.json @@ -0,0 +1,42 @@ +{ + "title": "Using JAX with PennyLane", + "authors": [ + { + "username": "croberts" + } + ], + "dateOfPublication": "2021-04-12T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_jax_with_pl.png" + } + ], + "seoDescription": "Learn how to use JAX with PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqt", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_jax_transformations/requirements.in b/demonstrations_v2/tutorial_jax_transformations/requirements.in new file mode 100644 index 0000000000..d630fe270f --- /dev/null +++ b/demonstrations_v2/tutorial_jax_transformations/requirements.in @@ -0,0 +1,3 @@ +jax +jaxlib +pennylane diff --git a/demonstrations_v2/tutorial_kak_theorem/demo.py b/demonstrations_v2/tutorial_kak_theorem/demo.py new file mode 100644 index 0000000000..b491b59817 --- /dev/null +++ b/demonstrations_v2/tutorial_kak_theorem/demo.py @@ -0,0 +1,986 @@ +r"""The KAK theorem +=================== + +The KAK theorem is a beautiful mathematical result from Lie theory, with +particular relevance for quantum computing. It can be seen as a +generalization of the singular value decomposition (SVD), as it decomposes a group +element :math:`U` (think: unitary operator) into :math:`U=K_1AK_2,` where +:math:`K_{1,2}` and :math:`A` belong to special subgroups that we will introduce. +This means the KAK theorem falls under the large umbrella of matrix factorizations, +and it allows us to break down arbitrary quantum circuits into smaller building blocks, +i.e., we can use it for quantum circuit decompositions. + +In this demo, we will dive into Lie algebras and their groups. Then, we will discuss +so-called symmetric spaces, which arise from certain subgroups of those Lie groups. +With these tools in our hands, we will then learn about the KAK theorem itself, which +exploits a so-called Cartan decomposition to break up such a Lie group. + +We will make all steps explicit on a toy example on paper and in code, which splits +single-qubit gates into a well-known sequence of rotations. +Finally, we will get to know a handy decomposition of arbitrary +two-qubit unitaries into rotation gates as another application of the KAK theorem. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_kak_theorem.png + :align: center + :width: 70% + :target: javascript:void(0) + +Along the way, we will put some non-essential mathematical details +as well as a few gotchas regarding the nomenclature into boxes such as this one: + +.. admonition:: Prerequisites + :class: note + + In the following we will assume a basic understanding of vector spaces, + linear maps, and Lie algebras. To review those topics, we recommend a look + at your favourite linear algebra material. For the latter, also see our + :doc:`introduction to (dynamical) Lie algebras `. + +Without further ado, let's get started! + +Lie algebras and their groups +----------------------------- + +We start with Lie algebras, their Lie groups, +and a particular interaction between the two, the *adjoint action*. + +Lie algebras +~~~~~~~~~~~~ + +As mentioned above, we will assume a basic understanding of the mathematical objects +we will use. To warm up, however, let us briefly talk about Lie algebras (for details +see our :doc:`intro to (dynamical) Lie algebras `). + +A *Lie algebra* :math:`\mathfrak{g}` is a vector space with an additional operation +that takes two vectors to a new vector, the *Lie bracket*. To form an algebra, :math:`\mathfrak{g}` must be +closed under the Lie bracket. +For our purposes, the vectors will always be matrices and the Lie bracket will be the matrix +commutator. + +**Example** + +Our working example in this demo will be the *special unitary* algebra in two dimensions, +:math:`\mathfrak{su}(2).` +It consists of traceless complex-valued skew-Hermitian :math:`2\times 2` matrices, which we +can conveniently describe using the Pauli matrices: + +.. math:: + + \mathfrak{su}(2) + &= \left\{i\left(\begin{array}{cc} a & b-ic \\ b+ic & -a \end{array}\right) + {\large |} a, b, c \in \mathbb{R}\right\}\\ + &= \left\{i(a Z + b X + c Y)| a, b, c \in \mathbb{R}\right\}. + +We will also look at a more involved example at the end of the demo. + +.. admonition:: Math detail: our Lie algebras are real + :class: note + + The algebra :math:`\mathfrak{su}(n)` is a *real* Lie algebra, i.e., it is a vector space over + real numbers, :math:`\mathbb{R}.` This means that scalar-vector multiplication is + only valid between vectors (complex-valued matrices) and real scalars. + + There is a simple way to see this. Multiplying a skew-Hermitian matrix + :math:`x\in\mathfrak{su}(n)` by a complex number :math:`c\in\mathbb{C}` will yield + :math:`(cx)^\dagger=\overline{c} x^\dagger=-\overline{c} x,` so that + the result might no longer be skew-Hermitian, i.e. no longer in the algebra! If we keep it to real scalars + :math:`c\in\mathbb{R}` only, we have :math:`\overline{c}=c,` so that + :math:`(cx)^\dagger=-cx` and we're fine. + + We will only consider real Lie algebras here. + +Let us set up :math:`\mathfrak{su}(2)` in code. +Note that the algebra itself consists of *skew*-Hermitian matrices, but we will work +with the Hermitian counterparts as inputs, i.e., we will skip the factor :math:`i.` +We can check that :math:`\mathfrak{su}(2)` is closed under commutators by +computing all nested commutators, the so-called *Lie closure*, and observing +that the closure is not larger than :math:`\mathfrak{su}(2)` itself. +Of course, we could also check the closure manually for this small example. +""" + +from itertools import product, combinations +import pennylane as qml +from pennylane import X, Y, Z +import numpy as np + +su2 = [X(0), Y(0), Z(0)] +print(f"su(2) is {len(su2)}-dimensional") + +all_hermitian = all(qml.equal(qml.adjoint(op).simplify(), op) for op in su2) +print(f"The operators are all Hermitian: {all_hermitian}") + +su2_lie_closed = qml.lie_closure(su2) +print(f"The Lie closure of su(2) is {len(su2_lie_closed)}-dimensional.") + +traces = [op.pauli_rep.trace() for op in su2] +print(f"All operators are traceless: {np.allclose(traces, 0.)}") + +###################################################################### +# We find that :math:`\mathfrak{su}(2)` is indeed closed, and that it is a 3-dimensional +# space, as expected from the explicit expression above. +# We also picked a correct representation with traceless operators. +# +# .. admonition:: Math detail: (semi)simple Lie algebras +# :class: note +# +# Our main result for this demo will be the KAK theorem, which applies to so-called +# *semisimple* Lie algebras, which are in turn composed of *simple* Lie algebras as building +# blocks. Without going into detail, it often is sufficient to think of these building +# blocks as (1) special orthogonal algebras :math:`\mathfrak{so}(n),` (2) unitary symplectic +# algebras :math:`\mathfrak{sp}(n),` and (3) special unitary algebras :math:`\mathfrak{su}(n).` +# In particular, our example here is of the latter type, so it is not only semisimple, +# but even simple. +# +# Lie group from a Lie algebra +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The topic of Lie groups and Lie algebras is a large field of study and there are many +# things we could talk about in this section. For the sake of brevity, however, we will +# only list a few important properties that are needed further below. For more details +# and proofs, refer to your favourite Lie theory book, which could be [#hall]_ or [#tu]_. +# +# The Lie group :math:`\mathcal{G}` associated to a Lie algebra :math:`\mathfrak{g}` is given +# by the exponential map applied to the algebra: +# +# .. math:: +# +# \mathcal{G}=\exp(\mathfrak{g}). +# +# We will only consider Lie groups :math:`\exp(\mathfrak{g})` arising from a Lie algebra +# :math:`\mathfrak{g}` here. +# As we usually think about the unitary algebras :math:`\mathfrak{u}` and their +# subalgebras, the correspondence is well-known to quantum practitioners: Exponentiate +# a skew-Hermitian matrix to obtain a unitary operation, i.e., a quantum gate. +# +# Interaction between Lie groups and algebras +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We will make use of a particular interaction between the algebra :math:`\mathfrak{g}` and +# its group :math:`\mathcal{G},` called the *adjoint action* of :math:`\mathcal{G}` on +# :math:`\mathfrak{g}.` It is given by +# +# .. math:: +# +# \text{Ad}: \mathcal{G} \times \mathfrak{g} \to \mathfrak{g}, +# \ (\exp(x),y)\mapsto \text{Ad}_{\exp(x)}(y) = \exp(x) y\exp(-x). +# +# Similarly, we can interpret the Lie bracket as a map of :math:`\mathfrak{g}` acting on itself, +# which is called the *adjoint representation* of :math:`\mathfrak{g}` on itself: +# +# .. math:: +# +# \text{ad}: \mathfrak{g} \times \mathfrak{g} \to \mathfrak{g}, +# \ (x, y) \mapsto \text{ad}_x(y) = [x, y]. +# +# The adjoint group action and adjoint algebra representation do not only carry a very +# similar name, they are intimately related: +# +# .. math:: +# +# \text{Ad}_{\exp(x)}(y) = \exp(\text{ad}_x) (y), +# +# where we applied the exponential map to :math:`\text{ad}_x,` which maps from :math:`\mathfrak{g}` +# to itself, via its series representation. +# We will refer to this relationship as the *adjoint identity*. +# We talk about :math:`\text{Ad}` and :math:`\text{ad}` in more detail in the box below, and refer to our demo +# :doc:`g-sim: Lie algebraic classical simulations ` for +# further discussion. +# +# .. admonition:: Derivation: adjoint representations +# :class: note +# +# We begin this derivation with the *adjoint action* of :math:`\mathcal{G}` on itself, +# given by +# +# .. math:: +# +# \Psi: \mathcal{G} \times \mathcal{G} \to \mathcal{G}, +# \ (\exp(x),\exp(y))\mapsto \Psi_{\exp(x)}(\exp(y)) = \exp(x) \exp(y)\exp(-x). +# +# The map :math:`\Psi_{\exp(x)}` (with fixed subscript) is a smooth map from the Lie group +# :math:`\mathcal{G}` to itself, so that we may differentiate it. This leads to the +# differential :math:`\text{Ad}_{\exp(x)}=d\Psi_{\exp(x)},` which maps the tangent spaces of +# :math:`\mathcal{G}` to itself. At the identity, where +# the algebra :math:`\mathfrak{g}` forms the tangent space, we find +# +# .. math:: +# +# \text{Ad} : \mathcal{G} \times\mathfrak{g} \to \mathfrak{g}, +# \ (\exp(x), y)\mapsto \exp(x) y \exp(-x). +# +# This is the adjoint action of :math:`\mathcal{G}` on :math:`\mathfrak{g}` as we +# introduced above. +# +# Now that we have the adjoint action of :math:`\mathcal{G}` on :math:`\mathfrak{g},` +# we can differentiate it with respect to the subscript argument: +# +# .. math:: +# +# \text{ad}_{\circ}(y)&=d\text{Ad}_\circ(y),\\ +# \text{ad}: \mathfrak{g}\times \mathfrak{g}&\to\mathfrak{g}, +# \ (x, y)\mapsto \text{ad}_x(y) = [x, y]. +# +# It is a non-trivial observation that this differential equals the commutator! +# With :math:`\text{ad}` we arrived at a map that *represents* the action of an algebra element +# :math:`x` on the vector space that is the algebra itself. That is, we found the +# *adjoint representation* of :math:`\mathfrak{g}.` +# +# Finally, note that the adjoint identity can be proven with similar tools as above, +# i.e., chaining derivatives and exponentiation suitably. +# +# Symmetric spaces +# ---------------- +# +# Symmetric spaces are a popular field of study both in physics and mathematics. +# We will not go into depth regarding their interpretation or classification, but refer the +# interested reader to the broad existing literature, including [#arvanitogeorgos]_ and +# [#helgason]_. +# In the following, we mostly care about the algebraic structure of symmetric spaces. +# +# Subalgebras and Cartan decompositions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# A *subalgebra* :math:`\mathfrak{k}` of a Lie algebra :math:`\mathfrak{g}` is a +# vector subspace that is closed under the Lie bracket. Overall, this means that +# :math:`\mathfrak{k}` is closed under addition, scalar multiplication, and the Lie bracket. +# The latter often is simply written as :math:`[\mathfrak{k}, \mathfrak{k}]\subset \mathfrak{k}.` +# +# The algebras we are interested in come with an *inner product* between its elements. +# For our purposes, it is sufficient to assume that it is +# +# .. math:: +# +# \langle x, y\rangle = \text{tr}[x^\dagger y]. +# +# Let's implement the inner product and an orthogonality check based on it: +# + + +def inner_product(op1, op2): + """Compute the trace inner product between two operators.""" + # Use two wires to reuse it in the second example on two qubits later on + return qml.math.trace(qml.matrix(qml.adjoint(op1) @ op2, wire_order=[0, 1])) + + +def is_orthogonal(op, basis): + """Check whether an operator is orthogonal to a space given by some basis.""" + return np.allclose([inner_product(op, basis_op) for basis_op in basis], 0) + + +###################################################################### +# Given a subalgebra :math:`\mathfrak{k}\subset \mathfrak{g},` the inner product allows +# us to define an orthogonal complement +# +# .. math:: +# +# \mathfrak{p} = \{x\in\mathfrak{g} | \langle x, y\rangle=0 \ \forall \ y\in\mathfrak{k}\}. +# +# In this context, :math:`\mathfrak{k}` is commonly called the *vertical space*, +# :math:`\mathfrak{p}` accordingly is the *horizontal space*. +# The KAK theorem will apply to scenarios in which these spaces satisfy additional +# commutation relations, which do not hold for all subalgebras: +# +# .. math:: +# +# [\mathfrak{k}, \mathfrak{p}] \subset& \mathfrak{p} \qquad \text{(Reductive property)},\\ +# [\mathfrak{p}, \mathfrak{p}] \subset& \mathfrak{k} \qquad \text{(Symmetric property)}. +# +# The first property tells us that :math:`\mathfrak{p}` is left intact by the adjoint action of +# :math:`\mathfrak{k}.` The second property suggests that :math:`\mathfrak{p}` behaves like the +# *opposite* of a subalgebra, i.e., all commutators lie in its complement, the subalgebra +# :math:`\mathfrak{k}.` Due to the adjoint identity from above, the first property also holds for +# group elements acting on algebra elements; for all :math:`x\in\mathfrak{p}` and +# :math:`K\in\mathcal{K}=\exp(\mathfrak{k}),` we have +# +# .. math:: +# +# K x K^\dagger +# = \exp(y) x \exp(-y) +# = \text{Ad}_{\exp(y)}(x) +# = \exp(\text{ad}_y) (x) +# = \sum_{n=0}^\infty \frac{1}{n!} \underset{\in\mathfrak{p}}{\underbrace{(\text{ad}_y)^n (x)}} +# \in \mathfrak{p}. +# +# If the reductive property holds, the quotient space :math:`\mathcal{G}/\mathcal{K}` of the groups +# of :math:`\mathfrak{g}` and :math:`\mathfrak{k}` (see detail box below) is called a +# *reductive homogeneous space*. If both properties hold, :math:`(\mathfrak{k}, \mathfrak{p})` is +# called a *Cartan pair* and we call :math:`\mathfrak{g}=\mathfrak{k} \oplus \mathfrak{p}` a +# *Cartan decomposition*. :math:`(\mathfrak{g}, \mathfrak{k})` is named a *symmetric pair* +# and the quotient :math:`\mathcal{G}/\mathcal{K}` is a *symmetric space*. +# Symmetric spaces are relevant for a wide range of applications in physics +# and have been studied a lot throughout the last hundred years. +# +# .. admonition:: Nomenclature: Cartan decomposition/pair +# :class: warning +# +# Depending on context and field, there sometimes is an additional requirement +# for :math:`\mathfrak{g}=\mathfrak{k}\oplus\mathfrak{p}` to be called a Cartan decomposition. +# Without going into detail, this requirement is that the so-called *Killing form* must be +# negative definite on :math:`\mathfrak{k}` and positive definite on :math:`\mathfrak{p}` +# [#helgason]_. +# +# .. admonition:: Math detail: quotient space +# :class: note +# +# The *quotient space* of a Lie group :math:`\mathcal{G}` and a subgroup :math:`\mathcal{K}` +# is the space of cosets of :math:`\mathcal{K},` i.e., +# :math:`\mathcal{G}/\mathcal{K} = \{g\mathcal{K} | g\in G\}.` In this space, two elements are +# equal if they just differ by multiplying an element from :math:`\mathcal{K}` from the left +# to one of them. The quotient space is a manifold like the two groups :math:`\mathcal{G}` and +# :math:`\mathcal{K},` but in general it will *not* be a group itself. For example, a product +# of two elements is +# :math:`(g'\mathcal{K})(g\mathcal{K})=g'g(g^{-1} \mathcal{K} g) \mathcal{K},` which only is +# a coset again if :math:`g^{-1} \mathcal{K} g\subset \mathcal{K}.` Subgroups for which this +# condition holds for any :math:`g\in \mathcal{G}` are called *normal subgroups*. +# We are interested in cases where the symmetric property +# :math:`[\mathfrak{p}, \mathfrak{p}] \subset \mathfrak{k}` holds, which excludes (non-Abelian) +# normal subgroups, and thus our quotient space :math:`\mathcal{G}/\mathcal{K}` will not be +# a group. +# +# **Example** +# +# For our example, we consider the subalgebra :math:`\mathfrak{k}=\mathfrak{u}(1)` +# of :math:`\mathfrak{su}(2)` that generates Pauli-:math:`Z` rotations: +# +# .. math:: +# +# \mathfrak{k} = \text{span}_{\mathbb{R}} \{iZ\}. +# +# Let us define it in code, and check whether it gives rise to a Cartan decomposition. +# As we want to look at another example later, we wrap everything in a function. +# + + +def check_cartan_decomposition(g, k, space_name): + """Given an algebra g and an operator subspace k, verify that k is a subalgebra + and gives rise to a Cartan decomposition.""" + # Check Lie closure of k + k_lie_closure = qml.pauli.dla.lie_closure(k) + k_is_closed = len(k_lie_closure) == len(k) + print(f"The Lie closure of k is as big as k itself: {k_is_closed}.") + + # Orthogonal complement of k, assuming that everything is given in the same basis. + p = [g_op for g_op in g if is_orthogonal(g_op, k)] + print( + f"k has dimension {len(k)}, p has dimension {len(p)}, which combine to " + f"the dimension {len(g)} of g: {len(k)+len(p)==len(g)}" + ) + + # Check reductive property + k_p_commutators = [qml.commutator(k_op, p_op) for k_op, p_op in product(k, p)] + k_p_coms_in_p = all([is_orthogonal(com, k) for com in k_p_commutators]) + + print(f"All commutators in [k, p] are in p (orthogonal to k): {k_p_coms_in_p}.") + if k_p_coms_in_p: + print(f"{space_name} is a reductive homogeneous space.") + + # Check symmetric property + p_p_commutators = [qml.commutator(*ops) for ops in combinations(p, r=2)] + p_p_coms_in_k = all([is_orthogonal(com, p) for com in p_p_commutators]) + + print(f"All commutators in [p, p] are in k (orthogonal to p): {p_p_coms_in_k}.") + if p_p_coms_in_k: + print(f"{space_name} is a symmetric space.") + + return p + + +u1 = [Z(0)] +space_name = "SU(2)/U(1)" +p = check_cartan_decomposition(su2, u1, space_name) + +###################################################################### +# Cartan subalgebras +# ~~~~~~~~~~~~~~~~~~ +# +# The symmetric property of a Cartan decomposition +# :math:`([\mathfrak{p}, \mathfrak{p}]\subset\mathfrak{k})` tells us that :math:`\mathfrak{p}` +# is *very far* from being a subalgebra (commutators never end up in :math:`\mathfrak{p}` again). +# This also gives us information about potential subalgebras *within* :math:`\ \mathfrak{p}.` +# Assume we have a subalgebra :math:`\mathfrak{a}\subset\mathfrak{p}.` Then the commutator +# between any two elements :math:`x, y\in\mathfrak{a}` must satisfy +# +# .. math:: +# +# [x, y] \in \mathfrak{a} \subset \mathfrak{p} +# &\Rightarrow [x, y]\in\mathfrak{p} \text{(subalgebra property)}, \\ +# [x, y] \in [\mathfrak{a}, \mathfrak{a}] \subset [\mathfrak{p}, \mathfrak{p}] +# \subset \mathfrak{k} &\Rightarrow [x, y]\in\mathfrak{k}\ \text{(symmetric property)}. +# +# That is, the commutator must lie in *both* orthogonal complements :math:`\mathfrak{k}` and +# :math:`\mathfrak{p},` which only have the zero vector in common. This tells us that *all* +# commutators in :math:`\mathfrak{a}` vanish, making it an *Abelian* subalgebra: +# +# .. math:: +# +# [\mathfrak{a}, \mathfrak{a}] = \{0\}. +# +# Such an Abelian subalgebra is a (horizontal) *Cartan subalgebra (CSA)* if it is *maximal*, +# i.e., if it can not be made any larger (higher-dimensional) without leaving :math:`\mathfrak{p}.` +# +# .. admonition:: Nomenclature: Cartan subalgebra +# :class: warning +# +# Depending on context and field, there are inequivalent notions of Cartan subalgebras. +# In particular, there is a common notion of Cartan subalgebras which are not contained +# in a horizontal space. Throughout this demo, we always mean a *horizontal* +# maximal Abelian subalgebra :math:`\mathfrak{a}\subset\mathfrak{p}.` The two notions +# can be made compatible by being precise about the space of which the subalgebra is a CSA. +# +# How many different CSAs are there? Given a CSA :math:`\mathfrak{a},` we can pick a vertical +# element :math:`y\in\mathfrak{k}` and apply the corresponding group element :math:`K=\exp(y)` to +# all elements of the CSA, using the adjoint action we studied above. This will yield a valid +# CSA again: First, :math:`K\mathfrak{a} K^\dagger` remains in :math:`\mathfrak{p}` +# due to the reductive property, as we discussed when introducing the Cartan decomposition. +# Second, the adjoint action will not change the Abelian property because +# +# .. math:: +# +# [K x_1 K^\dagger, K x_2 K^\dagger] = K [x_1, x_2] K^\dagger = K 0 K^\dagger = 0 +# \quad \forall\ x_{1, 2}\in\mathfrak{a}. +# +# Finally, we are guaranteed that :math:`K\mathfrak{a} K^\dagger` remains maximal: +# +# .. admonition:: Math detail: CSAs remain maximal +# :class: note +# +# The reason that :math:`K\mathfrak{a} K^\dagger` is maximal if :math:`\mathfrak{a}` was, is +# that we assume :math:`\mathfrak{g}` to be a semisimple Lie algebra, for which the +# adjoint representation is faithful. This in turn implies that linearly +# independent elements of :math:`\mathfrak{g}` will not be mapped to linearly dependent +# elements by the adjoint action of :math:`K.` +# +# For most :math:`y\in\mathfrak{k},` applying :math:`K=\exp(y)` in this way will yield a +# *different* CSA, so that we find a whole continuum of them. +# It turns out that they *all* can be found by starting with *any* +# :math:`\mathfrak{a}` and applying all of :math:`\mathcal{K}` to it. +# +# *This is what powers the KAK theorem.* +# +# **Example** +# +# For our example, we established the decomposition +# :math:`\mathfrak{su}(2)=\mathfrak{u}(1)\oplus \mathfrak{p}` with the two-dimensional horizontal +# space :math:`\mathfrak{p} = \text{span}_{\mathbb{R}}\{iX, iY\}.` Starting with the subspace +# :math:`\mathfrak{a}=\text{span}_{\mathbb{R}} \{iY\},` we see that we immediately reach a maximal Abelian +# subalgebra (a CSA), because :math:`[Y, X]\neq 0.` Applying a rotation +# :math:`\exp(i\eta Z)\in\mathcal{K}` to this CSA gives us a new CSA via +# +# .. math:: +# +# \mathfrak{a}'=\{\exp(i\eta Z) (c iY) \exp(-i\eta Z) | c\in\mathbb{R}\} +# =\{c\cos(2\eta) iY + c\sin(2\eta) iX | c\in\mathbb{R}\} . +# +# The vertical group element :math:`\exp(i\eta Z)` simply rotates the CSA within +# :math:`\mathfrak{p}.` Let us not forget to define the CSA in code. + +# CSA generator: iY +a = p[1] + +# Rotate CSA by applying some vertical group element exp(i eta Z) +eta = 0.6 +# The factor -2 compensates the convention -1/2 in the RZ gate +a_prime = qml.RZ(-2 * eta, 0) @ a @ qml.RZ(2 * eta, 0) + +# Expectation from our theoretical calculation +a_prime_expected = np.cos(2 * eta) * a + np.sin(2 * eta) * p[0] +a_primes_equal = np.allclose(qml.matrix(a_prime_expected), qml.matrix(a_prime)) +print(f"The rotated CSAs match between numerics and theory: {a_primes_equal}") + +###################################################################### +# Cartan involutions +# ~~~~~~~~~~~~~~~~~~ +# +# In practice, there often is a more convenient way to obtain a Cartan decomposition +# than by specifying the subalgebra :math:`\mathfrak{k}` or its horizontal counterpart +# :math:`\mathfrak{p}` manually. It goes as follows. +# +# We will look at a map :math:`\theta` from the total Lie algebra :math:`\mathfrak{g}` +# to itself. We demand that :math:`\theta` has the following properties, for +# :math:`x, y\in\mathfrak{g}` and :math:`c\in\mathbb{R}.` +# +# #. It is linear, i.e., :math:`\theta(x + cy)=\theta(x) +c \theta(y),` +# #. It is compatible with the commutator, i.e., :math:`\theta([x, y])=[\theta(x),\theta(y)],` and +# #. It is an *involution*, i.e., :math:`\theta(\theta(x)) = x,` +# or :math:`\theta^2=\mathbb{I}_{\mathfrak{g}}.` +# +# In short, we demand that :math:`\theta` be an *involutive automorphism* of :math:`\mathfrak{g}.` +# +# As an involution, :math:`\theta` only can have the eigenvalues :math:`\pm 1,` with associated +# eigenspaces :math:`\mathfrak{g}_\pm.` Let's see what happens when we compute commutators between +# elements :math:`x_\pm\in\mathfrak{g}_\pm \Leftrightarrow \theta(x_\pm) = \pm x_\pm:` +# +# .. math:: +# +# &\theta([x_+, x_+]) = [\theta(x_+), \theta(x_+)] = [x_+, x_+] +# &\ \Rightarrow\ [x_+, x_+]\in\mathfrak{g}_+ ,\\ +# &\theta([x_+, x_-]) = [\theta(x_+), \theta(x_-)] = -[x_+, x_-] +# &\ \Rightarrow\ [x_+, x_-]\in\mathfrak{g}_- ,\\ +# &\theta([x_-, x_-]) = [\theta(x_-), \theta(x_-)] = (-1)^2 [x_-, x_-] +# &\ \Rightarrow\ [x_-, x_-]\in\mathfrak{g}_+. +# +# Or, in other words, +# :math:`[\mathfrak{g}_+, \mathfrak{g}_+] \subset \mathfrak{g}_+,` +# :math:`[\mathfrak{g}_+, \mathfrak{g}_-] \subset \mathfrak{g}_-,` +# and :math:`[\mathfrak{g}_-, \mathfrak{g}_-] \subset \mathfrak{g}_+.` +# So an involution is enough to find us a Cartan decomposition, with +# :math:`\mathfrak{k}=\mathfrak{g}_+` and :math:`\mathfrak{p}=\mathfrak{g}_-.` +# +# 🤯 +# +# We might want to call such a :math:`\theta` a *Cartan involution*. +# +# .. admonition:: Nomenclature: Cartan involution +# :class: warning +# +# Some people do so, some people again require more properties for such an +# involution to be called Cartan involution. +# For our purposes, let's go with the more general definition and call all +# involutions with the properties above Cartan involutions. +# +# Conversely, if we have a Cartan decomposition based on a subalgebra :math:`\mathfrak{k},` +# we can define the map +# +# .. math:: +# +# \theta_{\mathfrak{k}}(x) = \Pi_{\mathfrak{k}}(x)-\Pi_{\mathfrak{p}}(x), +# +# where :math:`\Pi_{\mathfrak{k},\mathfrak{p}}` are the projectors onto the two vector +# subspaces. Clearly, :math:`\theta_{\mathfrak{k}}` is linear because projectors are. +# It is also compatible with the commutator due to the commutation relations +# between :math:`\mathfrak{k}` and :math:`\mathfrak{p}` (see box below). +# Finally, :math:`\theta_{\mathfrak{k}}` is an involution because +# +# .. math:: +# +# \theta_{\mathfrak{k}}^2=(\Pi_{\mathfrak{k}}-\Pi_{\mathfrak{p}})^2 +# = \Pi_{\mathfrak{k}}^2-\Pi_{\mathfrak{k}}\Pi_{\mathfrak{p}} +# -\Pi_{\mathfrak{p}}\Pi_{\mathfrak{k}}+\Pi_{\mathfrak{p}}^2 +# =\Pi_{\mathfrak{k}}+\Pi_{\mathfrak{p}} +# = \mathbb{I}_{\mathfrak{g}}, +# +# where we used the projectors' property :math:`\Pi_{\mathfrak{k}}^2=\Pi_{\mathfrak{k}}` and +# :math:`\Pi_{\mathfrak{p}}^2=\Pi_{\mathfrak{p}},` as well as the fact that +# :math:`\Pi_{\mathfrak{k}}\Pi_{\mathfrak{p}}=\Pi_{\mathfrak{p}}\Pi_{\mathfrak{k}}=0` because +# the spaces :math:`\mathfrak{k}` and :math:`\mathfrak{p}` are orthogonal to each other. +# +# .. admonition:: Math detail: :math:`\theta_{\mathfrak{k}}` is a homomorphism +# :class: note +# +# To see that :math:`\theta_{\mathfrak{k}}` is compatible with the commutator, i.e., +# an algebra homomorphism, see how a commutator :math:`[x, y]` splits: +# +# .. math:: +# +# [x, y] +# &= [\Pi_{\mathfrak{k}}(x) + \Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y) + \Pi_{\mathfrak{p}}(y)] \\ +# &= \underset{\in \mathfrak{k}}{\underbrace{[\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{k}}(y)]}} +# +\underset{\in \mathfrak{p}}{\underbrace{[\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{p}}(y)]}} +# +\underset{\in \mathfrak{p}}{\underbrace{[\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y)]}} +# +\underset{\in \mathfrak{k}}{\underbrace{[\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{p}}(y)]}}, +# +# where we used :math:`\mathbb{I}_{\mathfrak{g}} = \Pi_{\mathfrak{k}} + \Pi_{\mathfrak{p}}` and the +# commutation relations between :math:`\mathfrak{k}` and :math:`\mathfrak{p}.` +# We can use this split to compute +# +# .. math:: +# +# \theta_{\mathfrak{k}} ([x, y]) +# &=\Pi_{\mathfrak{k}}([x, y]) - \Pi_{\mathfrak{p}}([x, y])\\ +# &=[\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{k}}(y)] + [\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{p}}(y)] +# - [\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{p}}(y)] - [\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y)]\\ +# &=[\Pi_{\mathfrak{k}}(x) -\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y)-\Pi_{\mathfrak{p}}(y)]\\ +# &=[\theta_{\mathfrak{k}} (x),\theta_{\mathfrak{k}} (y)]. +# +# Thus, :math:`\theta_{\mathfrak{k}}` indeed is compatible with the commutator. +# +# This shows us that we can easily switch between a Cartan involution and a Cartan +# decomposition, in either direction! +# +# **Example** +# +# In our example, an involution that reproduces our choice +# :math:`\mathfrak{k}=\text{span}_{\mathbb{R}} \{iZ\}` is :math:`\theta_Z(x) = Z x Z` +# (convince yourself that it is an involution that respects commutators, or verify that +# it matches :math:`\theta_{\mathfrak{k}}` from above). + + +def theta_Z(x): + return qml.simplify(Z(0) @ x @ Z(0)) + + +theta_of_u1 = [theta_Z(x) for x in u1] +u1_is_su2_plus = all(qml.equal(x, theta_of_x) for x, theta_of_x in zip(u1, theta_of_u1)) +print(f"U(1) is the +1 eigenspace: {u1_is_su2_plus}") + +theta_of_p = [theta_Z(x) for x in p] +p_is_su2_minus = all(qml.equal(-x, theta_of_x) for x, theta_of_x in zip(p, theta_of_p)) +print(f"p is the -1 eigenspace: {p_is_su2_minus}") + +###################################################################### +# We can easily get a new subalgebra by modifying the involution, say, to +# :math:`\theta_Y(x) = Y x Y,` expecting that +# :math:`\mathfrak{k}_Y=\text{span}_{\mathbb{R}} \{iY\}` becomes the new subalgebra. + + +def theta_Y(x): + return qml.simplify(Y(0) @ x @ Y(0)) + + +eigvals = [] +for x in su2: + if qml.equal(theta_Y(x), x): + eigvals.append(1) + elif qml.equal(theta_Y(x), -x): + eigvals.append(-1) + else: + raise ValueError("Operator not purely in either eigenspace.") + +print(f"Under theta_Y, the operators\n{su2}\nhave the eigenvalues\n{eigvals}") + +###################################################################### +# This worked! A new involution gave us a new subalgebra and Cartan decomposition. +# +# .. admonition:: Math detail: classification of Cartan decompositions +# :class: note +# +# You might already see that the two different decompositions created by :math:`\theta_Z` +# and :math:`\theta_Y` are very similar. There is a whole field of study that +# characterizes---and even fully classifies---the possible Cartan decompositions +# of semisimple Lie algebras. This classification +# plays a big role when talking about decompositions without getting stuck on details +# like the choice of basis or the representation of the algebra as matrices. +# For example, there are only three types of Cartan decompositions of the special unitary +# algebra :math:`\mathfrak{su}(n),` called AI, AII, and AIII. The subalgebras +# :math:`\mathfrak{k}` for these decompositions are the special orthogonal algebra +# :math:`\mathfrak{so}(n)` (AI), the unitary symplectic algebra :math:`\mathfrak{sp}(n)` (AII), +# and a sum of (special) unitary algebras +# :math:`\mathfrak{su}(p)\oplus\mathfrak{su}(q)\oplus\mathfrak{u}(1)` (AIII, :math:`p+q=n`). +# For a quick overview, see for example the `Wikipedia entry on symmetric spaces +# `__. +# Their involutions are usually represented by complex conjugation (AI), by the adjoint +# action with a Pauli operator (AIII, for qubits, :math:`p=q=2^{N-1}`), or by both +# (AII). It is instructive to try and see why those three are *not* equivalent +# under a unitary basis change! +# +# The KAK theorem +# --------------- +# +# Now that we covered all prerequisites, we are ready for our main result. It consists of two +# steps that are good to know individually, so we will look at both of them in sequence. +# We will not conduct formal proofs but leave those to the literature references. +# In the following, let :math:`\mathfrak{g}` be a compact real semisimple Lie algebra and +# :math:`\mathfrak{k}` a subalgebra such that :math:`\mathfrak{g}=\mathfrak{k}\oplus \mathfrak{p}` +# is a Cartan decomposition. +# +# The first step is a decomposition of the Lie group :math:`\mathcal{G}=\exp(\mathfrak{g})` +# into the Lie subgroup +# :math:`\mathcal{K}=\exp(\mathfrak{k})` and the exponential of the horizontal space, +# :math:`\mathcal{P}=\exp(\mathfrak{p}),` *which is not a group* (see box on quotient spaces). +# The decomposition is a simple product within :math:`\mathcal{G}:` +# +# .. math:: +# +# \mathcal{G} &= \mathcal{K}\mathcal{P}, \text{ or }\\ +# \forall\ G\in\mathcal{G}\ \ \exists K\in\mathcal{K}, x\in\mathfrak{p}: \ G &= K \exp(x). +# +# This *KP* decomposition can be seen as the *group version* of +# :math:`\mathfrak{g} = \mathfrak{k} \oplus\mathfrak{p}` and is known as a *global* Cartan +# decomposition of :math:`\mathcal{G}.` +# +# The second step is the further decomposition of the space :math:`\mathcal{P}=\exp(\mathfrak{p}).` +# For this we first need to fix a Cartan subalgebra (CSA) :math:`\mathfrak{a}\subset\mathfrak{p}.` +# The CSA might be given through some application or from context, but there is no +# canonical choice. +# Given a horizontal vector :math:`x\in\mathfrak{p},` we can always construct a second CSA +# :math:`\mathfrak{a}_x\subset\mathfrak{p}` that contains :math:`x.` As any two CSAs can be mapped +# to each other by some subalgebra element :math:`y\in\mathfrak{k}` using the adjoint action :math:`\text{Ad},` +# we know that a :math:`y` exists such that +# +# .. math:: +# +# \exp(y)\mathfrak{a}_x\exp(-y)=\mathfrak{a} +# \quad\Rightarrow\quad x\in(\exp(-y) \mathfrak{a}\exp(y). +# +# Generalizing this statement across all horizontal elements :math:`x\in\mathfrak{p},` we find +# +# .. math:: +# +# \mathfrak{p} \subset \{\exp(-y) \mathfrak{a} \exp(y) | y\in\mathfrak{k}\}. +# +# As we discussed, the converse inclusion also must hold for a reductive space, so that we +# may even replace :math:`\subset` by an equality. +# Now we can use :math:`\exp(\text{Ad}_{K} x)=\text{Ad}_{K}\exp(x)` to move +# this statement to the group level, +# +# .. math:: +# +# \mathcal{P} +# =\exp(\mathfrak{p}) +# = \{\exp(\exp(-y) \mathfrak{a} \exp(y)) | y\in\mathfrak{k}\} +# = \{\exp(K^{-1} \mathfrak{a} K) | K\in\mathcal{K}\} +# = \{K^{-1} \mathcal{A} K | K\in\mathcal{K}\}, +# +# where we abbreviated :math:`\mathcal{A} = \exp(\mathfrak{a}).` +# +# Chaining the two steps together and combining the left factor :math:`K^{-1}` with the group +# :math:`\mathcal{K}` in the *KP* decomposition, we obtain the *KAK theorem* +# +# .. math:: +# +# \mathcal{G} +# =\mathcal{K}\mathcal{P} +# = \mathcal{K}\{K^{-1} \mathcal{A} K | K\in\mathcal{K}\}, +# = \{K_1 \mathcal{A} K_2 | K_{1,2}\in\mathcal{K}\}, +# = \mathcal{K} \mathcal{A} \mathcal{K} \qquad\textbf{(KAK Theorem).} +# +# It teaches us that any group element can be decomposed into two factors from the Lie subgroup and +# the exponential of a CSA element, i.e., of commuting elements from the horizontal subspace +# :math:`\mathfrak{p}.` This may already hint at the usefulness of the KAK theorem for matrix +# factorizations in general, and for quantum circuit decompositions in particular. +# Given a group operation :math:`G=\exp(x)` with :math:`x\in\mathfrak{g},` there are two +# subalgebra elements :math:`y_{1,2}\in\mathfrak{k}` (or subgroup elements +# :math:`K_{1,2}=\exp(y_{1,2})\in \mathcal{K}`) and a Cartan subgalgebra element +# :math:`a\in\mathfrak{a}` so that +# +# .. math:: +# +# G\in\mathcal{G} \quad\Rightarrow\quad G=K_1 \exp(a) K_2. +# +# If :math:`x` happens to be from the horizontal subspace :math:`\mathfrak{p},` so that +# :math:`G\in \mathcal{P}\subset\mathcal{G},` we know that the two subgroup elements :math:`K_1` +# and :math:`K_2` will in fact be related, namely +# +# .. math:: +# +# G\in\mathcal{P} \quad\Rightarrow\quad G=K\exp(a)K^\dagger. +# +# **Example** +# +# Applying what we just learned to our example on :math:`\mathfrak{su}(2),` we can state that +# any single-qubit gate can be implemented by running a gate from +# :math:`\mathcal{K}=\{\exp(i\eta Z) | \eta\in\mathbb{R}\},` a CSA gate +# :math:`\mathcal{A}=\{\exp(i\varphi Y) | \eta\in\mathbb{R}\},` and another gate from +# :math:`\mathcal{K}.` We rediscovered a standard decomposition of an arbitrary +# :math:`SU(2)` gate! PennyLane produces it with :func:`~.pennylane.ops.one_qubit_decomposition`: + +x = 0.2j * su2[0] - 0.1j * su2[1] - 0.2j * su2[2] +G = qml.math.linalg.expm(qml.matrix(x)) +print(qml.ops.one_qubit_decomposition(G, 0, rotations="ZYZ")) + +###################################################################### +# If we pick a *horizontal gate*, i.e., a gate :math:`G\in\mathcal{P}`, we obtain the same +# rotation angle for the initial and final :math:`R_Z` rotations, up to the expected sign, and +# a shift by some multiple of :math:`2\pi.` + +horizontal_x = -0.1j * p[0] - 4.1j * p[1] +print(horizontal_x) +P = qml.math.linalg.expm(qml.matrix(horizontal_x)) +decomp = qml.ops.one_qubit_decomposition(P, 0, rotations="ZYZ") +print(decomp) +angle_match = np.isclose((decomp[0].data[0] + decomp[-1].data[0]) % (2 * np.pi), 0.0) +print(f"First and last rotation angle match up to sign and shift by 2kπ: {angle_match}") + +###################################################################### +# Other choices for involutions or---equivalently---subalgebras :math:`\mathfrak{k}` will +# lead to other decompositions of ``Rot``. For example, using :math:`\theta_Y` from above +# together with the CSA :math:`\mathfrak{a}_Y=\text{span}_{\mathbb{R}} \{iX\},` we find the +# decomposition +# +# .. math:: +# +# \text{Rot}(\phi, \theta, \omega) = R_Y(\eta_1) R_X(\vartheta) R_Y(\eta_2). +# +# And that's it for our main discussion. We conclude this demo by applying the +# KAK theorem to the group of arbitrary two-qubit gates. +# +# Application: Two-qubit gate decomposition +# ----------------------------------------- +# +# Two-qubit operations are described by the special unitary group :math:`SU(4)` and +# here we will use a decomposition of its algebra :math:`\mathfrak{su}(4)` to decompose +# such gates. +# Specifically, we use the subalgebra that generates single-qubit operations independently +# on either qubit, :math:`\mathfrak{su}(2)\oplus\mathfrak{su}(2).` Let's set it up with our +# tool from earlier: + +# Define su(4). Skip first entry of Pauli group, which is the identity +su4 = list(qml.pauli.pauli_group(2))[1:] +print(f"su(4) is {len(su4)}-dimensional") + +# Define subalgebra su(2) ⊕ su(2) +su2_su2 = [X(0), Y(0), Z(0), X(1), Y(1), Z(1)] +space_name = "SU(4)/(SU(2)xSU(2))" +p = check_cartan_decomposition(su4, su2_su2, space_name) + +###################################################################### +# .. admonition:: Math detail: involution for two-qubit decomposition +# :class: note +# +# The accompanying involution sorts operators by the number of qubits on which they are +# supported (:math:`\mathfrak{k}` is supported on one, :math:`\mathfrak{p}` on two). +# This can be realized with the operation +# +# .. math:: +# +# \theta(x) = -Y_0Y_1 x^T Y_0Y_1. +# +# Intuitively, the conjugation by :math:`Y_0Y_1` adds a minus +# sign for each :math:`X` and :math:`Z` factor in :math:`x,` and the transposition +# adds a minus sign for each :math:`Y.` Taken together, each Pauli operator contributes +# a minus sign. Finally, as we want the single-qubit operators to receive no sign in total +# (:math:`\mathfrak{k}` is the :math:`+1` eigenspace), we add a minus sign overall. +# +# Now we can pick a Cartan subalgebra within :math:`\mathfrak{p},` the vector space +# of all two-qubit Paulis. A common choice is +# +# .. math:: +# +# \mathfrak{a} = \text{span}_{\mathbb{R}}\{iX_0X_1, iY_0Y_1, iZ_0Z_1\}. +# +# These three operators commute, making :math:`\mathfrak{a}` Abelian. +# They also form a *maximal* Abelian algebra within :math:`\mathfrak{p},` which is less obvious. +# +# The KAK theorem now tells us that any two-qubit gate :math:`U,` being part of +# :math:`SU(4),` can be implemented by a sequence +# +# .. math:: +# +# U &= \exp(y_1) \exp(a)\exp(y_2)\\ +# &= \exp(i[\varphi^x_0 X_0 + \varphi^y_0 Y_0 + \varphi^z_0 Z_0]) +# \exp(i[\varphi^x_1 X_1 + \varphi^y_1 Y_1 + \varphi^z_1 Z_1])\\ +# &\times \exp(i [\eta^x X_0X_1 + \eta^y Y_0Y_1 + \eta^z Z_0Z_1])\\ +# &\times \exp(i[\vartheta^x_0 X_0 + \vartheta^y_0 Y_0 + \vartheta^z_0 Z_0]) +# \exp(i[\vartheta^x_1 X_1 + \vartheta^y_1 Y_1 + \vartheta^z_1 Z_1]). +# +# Here we decomposed the exponentials of the vertical elements :math:`y_{1,2}` further by +# splitting them into exponentials acting on the first and second qubit, respectively. +# +# The three parameters :math:`\eta^{x, y, z}` sometimes are called the *Cartan coordinates* +# of :math:`U,` and they can be used, e.g., to assess the smallest-possible duration to +# implement the gate in hardware. +# +# With this result, we can implement a template that can create any two-qubit gate. +# We'll use :class:`~.pennylane.Rot` for the single-qubit exponentials (which changes +# the meaning of the angles, but maintains the coverage) and are allowed to +# split the Cartan subalgebra term :math:`\exp(a)` into three exponentials, as its +# terms commute. +# + + +def su4_gate(params): + phi0, phi1, eta, theta0, theta1 = np.split(params, range(3, 15, 3)) + qml.Rot(*phi0, wires=0) + qml.Rot(*phi1, wires=1) + qml.IsingXX(eta[0], wires=[0, 1]) + qml.IsingYY(eta[1], wires=[0, 1]) + qml.IsingZZ(eta[2], wires=[0, 1]) + qml.Rot(*theta0, wires=0) + qml.Rot(*theta1, wires=1) + + +params = np.random.random(15) +fig, ax = qml.draw_mpl(su4_gate, wire_order=[0, 1])(params) + +###################################################################### +# And that's a wrap on our KAK theorem application for two-qubit gates! +# +# You may have noticed that the theorem only states the existence of a +# decomposition, but does not provide a constructive way of finding +# :math:`y_{1,2}` and :math:`a` for a given gate :math:`U.` For this, +# some additional work is required, as explained in [#kokcu_fdhs]_, for example. +# +# Conclusion +# ---------- +# +# In this demo we learned about the KAK theorem and how it uses a Cartan +# decomposition of a Lie algebra to decompose its Lie group. +# This allows us to break down arbitrary quantum gates from that group, +# as we implemented in code for the groups of single-qubit and two-qubit gates, +# :math:`SU(2)` and :math:`SU(4).` +# +# If you are interested in other applications of Lie theory in the field of +# quantum computing, you are in luck! It has been a handy tool throughout the last +# decades, e.g., for the simulation of quantum circuits [#somma]_ [#goh]_ and their +# compression [#kokcu_comp]_ [#gu]_, in quantum optimal control [#dirr]_, and for trainability +# analyses [#fontana]_ [#ragone]_. For Lie algebraic classical simulation of quantum circuits, +# also take a look at the :doc:`g-sim ` and +# :doc:`(g+P)-sim ` demos. +# +# References +# ---------- +# +# .. [#hall] +# +# Brian C. Hall +# "Lie Groups, Lie Algebras, and Representations. An Elementary Introduction" +# `Graduate Texts in Mathematics, Springer `__, 2015. +# +# .. [#tu] +# +# Loring W. Tu +# "An Introduction to Manifolds" +# `Universitext, Springer `__, 2011. +# +# .. [#arvanitogeorgos] +# +# Andreas Arvanitogeorgos +# "An Introduction to Lie Groups and the Geometry of Homogeneous Spaces" +# `Student Mathematical Library **22** `__, 2003 +# +# .. [#helgason] +# +# Sigurdur Helgason +# "Differential geometry, Lie groups, and symmetric spaces" +# `Graduate Studies in Mathematics **34** `__, 2001 +# +# .. [#goh] +# +# Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage +# "Lie-algebraic classical simulations for variational quantum computing" +# `arXiv:2308.01432 `__, 2023. +# +# .. [#somma] +# +# Rolando D. Somma +# "Quantum Computation, Complexity, and Many-Body Physics" +# `arXiv:quant-ph/0512209 `__, 2005. +# +# .. [#kokcu_fdhs] +# +# Efekan Kökcü, Thomas Steckmann, Yan Wang, J. K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper +# "Fixed Depth Hamiltonian Simulation via Cartan Decomposition" +# `arXiv:2104.00728 `__, 2021. +# `PRL (closed access) `__, 2022. +# +# .. [#kokcu_comp] +# +# Efekan Kökcü, Daan Camps, Lindsay Bassman, James K. Freericks, Wibe A. de Jong, Roel Van Beeumen, Alexander F. Kemper +# "Algebraic Compression of Quantum Circuits for Hamiltonian Evolution" +# `arXiv:2108.03282 `__, 2021. +# `PRA (closed access) `__, 2022. +# +# .. [#gu] +# +# Shouzhen Gu, Rolando D. Somma, Burak Şahinoğlu +# "Fast-forwarding quantum evolution" +# `Quantum **5** `__, 2021. +# +# .. [#dirr] +# +# G. Dirr, U. Helmke +# "Lie Theory for Quantum Control" +# `GAMM-Mitteilungen **31** `__, 2008. +# +# .. [#fontana] +# +# Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia +# "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ansätze" +# `Nat. Commun. **15** `__, 2024. +# +# .. [#ragone] +# +# Michael Ragone, Bojko N. Bakalov, Frédéric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo +# "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits" +# `Nat. Commun. **15** `__, 2024. +# diff --git a/demonstrations_v2/tutorial_kak_theorem/metadata.json b/demonstrations_v2/tutorial_kak_theorem/metadata.json new file mode 100644 index 0000000000..12c1b6d0eb --- /dev/null +++ b/demonstrations_v2/tutorial_kak_theorem/metadata.json @@ -0,0 +1,174 @@ +{ + "title": "The KAK theorem", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-11-25T00:00:00+00:00", + "dateOfLastModification": "2024-11-25T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_kak_theorem.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_kak_theorem.png" + } + ], + "seoDescription": "Learn about the KAK theorem and how it powers circuit decompositions.", + "doi": "", + "references": [ + { + "id": "hall", + "type": "book", + "title": "Lie Groups, Lie Algebras, and Representations. An Elementary Introduction", + "authors": "Brian C. Hall", + "year": "2015", + "publisher": "Springer", + "journal": "Graduate Texts in Mathematics", + "doi": "10.1007/978-3-319-13467-3", + "url": "https://link.springer.com/book/10.1007/978-3-319-13467-3" + }, + { + "id": "tu", + "type": "book", + "title": "An Introduction to Manifolds", + "authors": "Loring W. Tu", + "year": "2011", + "publisher": "Springer", + "journal": "Universitext", + "doi": "10.1007/978-1-4419-7400-6", + "url": "https://link.springer.com/book/10.1007/978-1-4419-7400-6" + }, + { + "id": "arvanitogeorgos", + "type": "book", + "title": "An Introduction to Lie Groups and the Geometry of Homogeneous Spaces", + "authors": "Andreas Arvanitogeorgos", + "year": "2003", + "publisher": "American Mathematical Society", + "journal": "Student Mathematical Library", + "url": "https://bookstore.ams.org/stml-22" + }, + { + "id": "helgason", + "type": "book", + "title": "Differential geometry, Lie groups, and symmetric spaces", + "authors": "Sigurdur Helgason", + "year": "2001", + "publisher": "American Mathematical Society", + "journal": "Graduate Studies in Mathematics", + "doi": "10.1090/gsm/034", + "url": "https://bookstore.ams.org/gsm-34" + }, + { + "id": "goh", + "type": "preprint", + "title": "lie-algebraic classical simulations for variational quantum computing", + "authors": "matthew l. goh, martin larocca, lukasz cincio, m. cerezo, fr\u00e9d\u00e9ric sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "somma", + "type": "preprint", + "title": "quantum computation, complexity, and many-body physics", + "authors": "rolando d. somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "kokcu_fdhs", + "type": "article", + "title": "Fixed Depth Hamiltonian Simulation via Cartan Decomposition", + "authors": "Efekan K\u00f6kc\u00fc, Thomas Steckmann, Yan Wang, J.\u2009K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper", + "year": "2022", + "publisher": "American Physical Society", + "journal": "", + "doi": "10.1103/PhysRevLett.129.070501", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.129.070501" + }, + { + "id": "kokcu_comp", + "type": "article", + "title": "Algebraic Compression of Quantum Circuits for Hamiltonian Evolution", + "authors": "Efekan K\u00f6kc\u00fc, Daan Camps, Lindsay Bassman, James K. Freericks, Wibe A. de Jong, Roel Van Beeumen, Alexander F. Kemper", + "year": "2022", + "publisher": "American Physical Society", + "journal": "", + "doi": "10.1103/PhysRevA.105.032420", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.105.032420" + }, + { + "id": "gu", + "type": "article", + "title": "Fast-forwarding quantum evolution", + "authors": "Shouzhen Gu, Rolando D. Somma, Burak \u015eahino\u011flu", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "doi": "10.22331/q-2021-11-15-577", + "url": "https://quantum-journal.org/papers/q-2021-11-15-577/#" + }, + { + "id": "dirr", + "type": "article", + "title": "Lie Theory for Quantum Control", + "authors": "G. Dirr, U. Helmke", + "year": "2008", + "publisher": "Gesellschaft f\u00fcr Angewandte Mathematik und Mechanik", + "journal": "Surveys for Applied Mathematics and Mechanics", + "doi": "10.1002/gamm.200890003", + "url": "https://onlinelibrary.wiley.com/doi/abs/10.1002/gamm.200890003" + }, + { + "id": "fontana", + "type": "article", + "title": "the adjoint is all you need: characterizing barren plateaus in quantum ans\u00e4tze", + "authors": "enrico fontana, dylan herman, shouvanik chakrabarti, niraj kumar, romina yalovetzky, jamie heredge, shree hari sureshbabu, marco pistoia", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2309.07902", + "url": "https://arxiv.org/abs/2309.07902" + }, + { + "id": "ragone", + "type": "preprint", + "title": "a unified theory of barren plateaus for deep parametrized quantum circuits", + "authors": "michael ragone, bojko n. bakalov, fr\u00e9d\u00e9ric sauvage, alexander f. kemper, carlos ortiz marrero, martin larocca, m. cerezo", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2309.09342", + "url": "https://arxiv.org/abs/2309.09342" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_kak_theorem/requirements.in b/demonstrations_v2/tutorial_kak_theorem/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_kak_theorem/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_kernel_based_training/demo.py b/demonstrations_v2/tutorial_kernel_based_training/demo.py new file mode 100644 index 0000000000..66a5e8bb59 --- /dev/null +++ b/demonstrations_v2/tutorial_kernel_based_training/demo.py @@ -0,0 +1,687 @@ +""" +.. _kernel_based_training: + +.. role:: html(raw) + :format: html + +Kernel-based training of quantum models with scikit-learn +========================================================= + +.. meta:: + :property="og:description": Train a quantum machine learning model based on the idea of quantum kernels. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/kernel_based_scaling.png + +.. related:: + + tutorial_variational_classifier Variational classifier + +*Author: Maria Schuld — Posted: 03 February 2021. Last updated: 3 February 2021.* + +Over the last few years, quantum machine learning research has provided a lot of insights on +how we can understand and train quantum circuits as machine learning models. +While many connections to neural networks have been made, it becomes increasingly clear that +their mathematical foundation is intimately related to so-called *kernel methods*, the most famous +of which is the `support vector machine (SVM) `__ +(see for example `Schuld and Killoran (2018) `__, +`Havlicek et al. (2018) `__, +`Liu et al. (2020) `__, +`Huang et al. (2020) `__, +and, for a systematic summary which we will follow here, +`Schuld (2021) `__). + +The link between quantum models and kernel methods has important practical implications: +we can replace the common `variational approach `__ +to quantum machine learning with a classical kernel method where the kernel—a small building block +of the overall algorithm—is computed by a quantum device. In many situations there are +guarantees that we get better or at least equally good results. + +This demonstration explores how kernel-based training compares with +`variational training `__ in terms of the number of quantum +circuits that have to be evaluated. For this we train a quantum machine +learning model with a kernel-based approach using a combination of PennyLane +and the `scikit-learn `__ machine +learning library. We compare this strategy with a variational +quantum circuit trained via stochastic gradient descent using +`PyTorch `__. + +We will see that in a typical small-scale example, kernel-based training requires only a fraction of the number of +quantum circuit evaluations used by variational circuit training, while each +evaluation runs a much shorter circuit. +In general, the relative efficiency of kernel-based methods compared to variational circuits +depends on the number of parameters used in the variational model. + +.. figure:: ../_static/demonstration_assets/kernel_based_training/scaling.png + :align: center + :scale: 100% + :alt: Scaling of kernel-based vs. variational learning + +If the number of variational parameters remains small, e.g., there is a square-root-like scaling with the number +of data samples (green line), variational circuits are almost as efficient as neural networks (blue line), +and require much fewer circuit evaluations +than the quadratic scaling of kernel methods (red line). +However, with current hardware-compatible training strategies, +kernel methods scale much better than variational circuits that require a number of parameters of the +order of the training set size (orange line). + +In conclusion, **for quantum machine learning applications with many parameters, kernel-based training can be a great +alternative to the variational approach to quantum machine learning**. + +After working through this demo, you will: + +* be able to use a support vector machine with a quantum kernel computed with PennyLane, and + +* be able to compare the scaling of quantum circuit evaluations required in kernel-based versus + variational training. + + +""" + +###################################################################### +# Background +# ---------- +# +# Let us consider a *quantum model* of the form +# +# .. math:: f(x) = \langle \phi(x) | \mathcal{M} | \phi(x)\rangle, +# +# where :math:`| \phi(x)\rangle` is prepared +# by a fixed `embedding +# circuit `__ that +# encodes data inputs :math:`x,` +# and :math:`\mathcal{M}` is an arbitrary observable. This model includes variational +# quantum machine learning models, since the observable can +# effectively be implemented by a simple measurement that is preceded by a +# variational circuit: +# +# +# .. figure:: ../_static/demonstration_assets/kernel_based_training/quantum_model.png +# :align: center +# :scale: 20% +# :alt: quantum-model +# +# | +# +# For example, applying a circuit :math:`G(\theta)` and then +# measuring the Pauli-Z observable :math:`\sigma^0_z` of the first qubit +# implements the trainable measurement +# :math:`\mathcal{M}(\theta) = G^{\dagger}(\theta) \sigma^0_z G(\theta).` +# +# The main practical consequence of approaching quantum machine learning with a +# kernel approach is that instead of training :math:`f` variationally, +# we can often train an equivalent classical kernel method with a kernel executed on a +# quantum device. This *quantum kernel* +# is given by the mutual overlap of two data-encoding quantum states, +# +# .. math:: \kappa(x, x') = | \langle \phi(x') | \phi(x)\rangle|^2. +# +# Kernel-based training therefore bypasses the processing and measurement +# parts of common variational circuits, and only depends on the +# data encoding. +# +# If the loss function :math:`L` is the `hinge +# loss `__, the kernel method +# corresponds to a standard `support vector +# machine `__ (SVM) +# in the sense of a maximum-margin classifier. Other convex loss functions +# lead to more general variations of support vector machines. +# +# .. note:: +# +# More precisely, we can replace variational with kernel-based +# training if the optimisation +# problem can be written as minimizing a cost of the form +# +# .. math:: \min_f \lambda\; \mathrm{tr}\{\mathcal{M}^2\} + \frac{1}{M}\sum_{m=1}^M L(f(x^m), y^m), +# +# which is a regularized empirical risk with training data samples :math:`(x^m, y^m)_{m=1\dots M},` +# regularization strength :math:`\lambda \in \mathbb{R},` and loss function :math:`L.` +# +# Theory predicts that kernel-based training will always find better or equally good +# minima of this risk. However, to show this here we would have +# to either regularize the variational training by the trace of the squared observable, or switch off +# regularization in the classical SVM, which removes a lot of its strength. The kernel-based and the variational +# training in this demonstration therefore optimize slightly different cost +# functions, and it is out of our scope to establish whether one training method finds a better minimum than +# the other. +# + + +###################################################################### +# Kernel-based training +# --------------------- +# +# First, we will turn to kernel-based training of quantum models. +# As stated above, an example implementation is a standard support vector +# machine with a kernel computed by a quantum circuit. +# + + +###################################################################### +# We begin by importing all sorts of useful methods: +# + +import numpy as np +import torch +from torch.nn.functional import relu + +from sklearn.svm import SVC +from sklearn.datasets import load_iris +from sklearn.preprocessing import StandardScaler +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score + +import pennylane as qml +from pennylane.templates import AngleEmbedding, StronglyEntanglingLayers + +import matplotlib.pyplot as plt + +np.random.seed(42) + + +###################################################################### +# The second step is to define a data set. Since the performance +# of the models is not the focus of this demo, we can just use +# the first two classes of the famous `Iris data set `__. +# Dating back to as far as 1936, +# this toy data set consists of 100 samples of four features each, +# and gives rise to a very simple classification problem. +# + +X, y = load_iris(return_X_y=True) + +# pick inputs and labels from the first two classes only, +# corresponding to the first 100 samples +X = X[:100] +y = y[:100] + +# scaling the inputs is important since the embedding we use is periodic +scaler = StandardScaler().fit(X) +X_scaled = scaler.transform(X) + +# scaling the labels to -1, 1 is important for the SVM and the +# definition of a hinge loss +y_scaled = 2 * (y - 0.5) + +X_train, X_test, y_train, y_test = train_test_split(X_scaled, y_scaled) + + +###################################################################### +# We use the `angle-embedding +# template `__ +# which needs as many qubits as there are features: +# + +n_qubits = len(X_train[0]) +n_qubits + + +###################################################################### +# To implement the kernel we could prepare the two states :math:`| \phi(x) \rangle,` :math:`| \phi(x') \rangle` +# on different sets of qubits with angle-embedding routines :math:`S(x), S(x'),` +# and measure their overlap with a small routine called a `SWAP test `__. +# +# However, we need only half the number of qubits if we prepare +# :math:`| \phi(x)\rangle` and then apply the inverse embedding +# with :math:`x'` on the same qubits. We then measure the projector onto +# the initial state :math:`|0..0\rangle \langle 0..0|.` +# +# .. figure:: ../_static/demonstration_assets/kernel_based_training/kernel_circuit.png +# :align: center +# :scale: 80% +# :alt: Kernel evaluation circuit +# +# To verify that this gives us the kernel: +# +# .. math:: +# +# \begin{align*} +# \langle 0..0 |S(x') S(x)^{\dagger} \mathcal{M} S(x')^{\dagger} S(x) | 0..0\rangle &= \langle 0..0 |S(x') S(x)^{\dagger} |0..0\rangle \langle 0..0| S(x')^{\dagger} S(x) | 0..0\rangle \\ +# &= |\langle 0..0| S(x')^{\dagger} S(x) | 0..0\rangle |^2\\ +# &= | \langle \phi(x') | \phi(x)\rangle|^2 \\ +# &= \kappa(x, x'). +# \end{align*} +# +# Note that a projector :math:`|0..0 \rangle \langle 0..0|` can be constructed +# using the ``qml.Hermitian`` observable in PennyLane. +# +# Altogether, we use the following quantum node as a *quantum kernel +# evaluator*: +# + +dev_kernel = qml.device("lightning.qubit", wires=n_qubits) + +projector = np.zeros((2 ** n_qubits, 2 ** n_qubits)) +projector[0, 0] = 1 + +@qml.qnode(dev_kernel) +def kernel(x1, x2): + """The quantum kernel.""" + AngleEmbedding(x1, wires=range(n_qubits)) + qml.adjoint(AngleEmbedding)(x2, wires=range(n_qubits)) + return qml.expval(qml.Hermitian(projector, wires=range(n_qubits))) + + +###################################################################### +# A good sanity check is whether evaluating the kernel of a data point and +# itself returns 1: +# + +kernel(X_train[0], X_train[0]) + + +###################################################################### +# The way an SVM with a custom kernel is implemented in scikit-learn +# requires us to pass a function that computes a matrix of kernel +# evaluations for samples in two different datasets A, B. If A=B, +# this is the `Gram matrix `__. +# + + +def kernel_matrix(A, B): + """Compute the matrix whose entries are the kernel + evaluated on pairwise data from sets A and B.""" + return np.array([[kernel(a, b) for b in B] for a in A]) + + +###################################################################### +# Training the SVM optimizes internal parameters that basically +# weigh kernel functions. +# It is a breeze in scikit-learn, which is designed +# as a high-level machine learning library: +# + +svm = SVC(kernel=kernel_matrix).fit(X_train, y_train) + + +###################################################################### +# Let’s compute the accuracy on the test set. +# + +with dev_kernel.tracker: + predictions = svm.predict(X_test) + accuracy_score(predictions, y_test) + +###################################################################### +# The SVM predicted all test points correctly. +# How many times was the quantum device evaluated? +# + +dev_kernel.tracker.totals['executions'] + + +###################################################################### +# This number can be derived as follows: For :math:`M` training samples, +# the SVM must construct the :math:`M \times M` dimensional kernel gram +# matrix for training. To classify :math:`M_{\rm pred}` new samples, the +# SVM needs to evaluate the kernel at most :math:`M_{\rm pred}M` times to get the +# pairwise distances between training vectors and test samples. +# +# .. note:: +# +# Depending on the implementation of the SVM, only :math:`S \leq M_{\rm pred}` +# *support vectors* are needed. +# +# Let us formulate this as a function, which can be used at the end of the demo +# to construct the scaling plot shown in the introduction. +# + + +def circuit_evals_kernel(n_data, split): + """Compute how many circuit evaluations one needs for kernel-based + training and prediction.""" + + M = int(np.ceil(split * n_data)) + Mpred = n_data - M + + n_training = M * M + n_prediction = M * Mpred + + return n_training + n_prediction + + +###################################################################### +# With :math:`M = 75` and :math:`M_{\rm pred} = 25,` the number of kernel evaluations +# can therefore be estimated as: +# + +circuit_evals_kernel(n_data=len(X), split=len(X_train) / (len(X_train) + len(X_test))) + + +###################################################################### +# The single additional evaluation can be attributed to evaluating the kernel once above +# as a sanity check. +# + +###################################################################### +# A similar example using variational training +# -------------------------------------------- +# + + +###################################################################### +# Using the variational principle of training, we can propose an *ansatz* +# for the variational circuit and train it directly. By +# increasing the number of layers of the ansatz, its expressivity +# increases. Depending on the ansatz, we may only +# search through a subspace of all measurements for the best +# candidate. +# +# Remember from above, the variational training does not optimize +# *exactly* the same cost as the SVM, but we try to match them as closely +# as possible. For this we use a bias term in the quantum model, and train +# on the hinge loss. +# +# We also explicitly use the `parameter-shift `__ +# differentiation method in the quantum node, since this is a method which works on hardware as well. +# While ``diff_method='backprop'`` or ``diff_method='adjoint'`` would reduce the number of +# circuit evaluations significantly, they are based on tricks that are only suitable for simulators, +# and can therefore not scale to more than a few dozen qubits. +# + +dev_var = qml.device("lightning.qubit", wires=n_qubits) + +@qml.qnode(dev_var, diff_method="parameter-shift") +def quantum_model(x, params): + """A variational quantum model.""" + + # embedding + AngleEmbedding(x, wires=range(n_qubits)) + + # trainable measurement + StronglyEntanglingLayers(params, wires=range(n_qubits)) + return qml.expval(qml.PauliZ(0)) + +def quantum_model_plus_bias(x, params, bias): + """Adding a bias.""" + return quantum_model(x, params) + bias + +def hinge_loss(predictions, targets): + """Implements the hinge loss.""" + all_ones = torch.ones_like(targets) + hinge_loss = all_ones - predictions * targets + # trick: since the max(0,x) function is not differentiable, + # use the mathematically equivalent relu instead + hinge_loss = relu(hinge_loss) + return hinge_loss + + +###################################################################### +# We now summarize the usual training and prediction steps into two +# functions similar to scikit-learn's ``fit()`` and ``predict()``. While +# it feels cumbersome compared to the one-liner used to train the kernel method, +# PennyLane—like other differentiable programming libraries—provides a lot more +# control over the particulars of training. +# +# In our case, most of the work is to convert between numpy and torch, +# which we need for the differentiable ``relu`` function used in the hinge loss. +# + + +def quantum_model_train(n_layers, steps, batch_size): + """Train the quantum model defined above.""" + + params = np.random.random((n_layers, n_qubits, 3)) + params_torch = torch.tensor(params, requires_grad=True) + bias_torch = torch.tensor(0.0) + + opt = torch.optim.Adam([params_torch, bias_torch], lr=0.1) + + loss_history = [] + for i in range(steps): + + batch_ids = np.random.choice(len(X_train), batch_size) + + X_batch = X_train[batch_ids] + y_batch = y_train[batch_ids] + + X_batch_torch = torch.tensor(X_batch, requires_grad=False) + y_batch_torch = torch.tensor(y_batch, requires_grad=False) + + def closure(): + opt.zero_grad() + preds = torch.stack( + [quantum_model_plus_bias(x, params_torch, bias_torch) for x in X_batch_torch] + ) + loss = torch.mean(hinge_loss(preds, y_batch_torch)) + + # bookkeeping + current_loss = loss.detach().numpy().item() + loss_history.append(current_loss) + if i % 10 == 0: + print("step", i, ", loss", current_loss) + + loss.backward() + return loss + + opt.step(closure) + + return params_torch, bias_torch, loss_history + + +def quantum_model_predict(X_pred, trained_params, trained_bias): + """Predict using the quantum model defined above.""" + + p = [] + for x in X_pred: + + x_torch = torch.tensor(x) + pred_torch = quantum_model_plus_bias(x_torch, trained_params, trained_bias) + pred = pred_torch.detach().numpy().item() + if pred > 0: + pred = 1 + else: + pred = -1 + + p.append(pred) + return p + + +###################################################################### +# Let’s train the variational model and see how well we are doing on the +# test set. +# + +n_layers = 2 +batch_size = 20 +steps = 100 + +with dev_var.tracker: + trained_params, trained_bias, loss_history = quantum_model_train(n_layers, steps, batch_size) + pred_test = quantum_model_predict(X_test, trained_params, trained_bias) + +print("accuracy on test set:", accuracy_score(pred_test, y_test)) + +plt.plot(loss_history) +plt.ylim((0, 1)) +plt.xlabel("steps") +plt.ylabel("cost") +plt.show() + + +###################################################################### +# The variational circuit has a slightly lower +# accuracy than the SVM—but this depends very much on the training settings +# we used. Different random parameter initializations, more layers, or more steps may indeed get +# perfect test accuracy. +# +# How often was the device executed? +# + +dev_var.tracker.totals['executions'] + + +###################################################################### +# That is a lot more than the kernel method took! +# +# Let’s try to understand this value. In each optimization step, the variational +# circuit needs to compute the partial derivative of all +# trainable parameters for each sample in a batch. Using parameter-shift +# rules, we require roughly two circuit +# evaluations per partial derivative. Prediction uses only one circuit +# evaluation per sample. +# +# We can formulate this as another function that will be used in the scaling plot below. +# + + +def circuit_evals_variational(n_data, n_params, n_steps, shift_terms, split, batch_size): + """Compute how many circuit evaluations are needed for + variational training and prediction.""" + + M = int(np.ceil(split * n_data)) + Mpred = n_data - M + + n_training = n_params * n_steps * batch_size * shift_terms + n_prediction = Mpred + + return n_training + n_prediction + + +###################################################################### +# This estimates the circuit evaluations in variational training as: +# + +circuit_evals_variational( + n_data=len(X), + n_params=len(trained_params.flatten()), + n_steps=steps, + shift_terms=2, + split=len(X_train) / (len(X_train) + len(X_test)), + batch_size=batch_size, +) + + +###################################################################### +# The estimate is a bit higher because it does not account for some optimizations +# that PennyLane performs under the hood. +# +# It is important to note that while they are trained in a similar manner, +# the number of variational circuit evaluations differs from the number of +# neural network model evaluations in classical machine learning, which would be given by: +# + +def model_evals_nn(n_data, n_params, n_steps, split, batch_size): + """Compute how many model evaluations are needed for neural + network training and prediction.""" + + M = int(np.ceil(split * n_data)) + Mpred = n_data - M + + n_training = n_steps * batch_size + n_prediction = Mpred + + return n_training + n_prediction + +###################################################################### +# In each step of neural network training, and due to the clever implementations of automatic differentiation, +# the backpropagation algorithm can compute a +# gradient for all parameters in (more-or-less) a single run. +# For all we know at this stage, the no-cloning principle prevents variational circuits from using these tricks, +# which leads to ``n_training`` in ``circuit_evals_variational`` depending on the number of parameters, but not in +# ``model_evals_nn``. +# +# For the same example as used here, a neural network would therefore +# have far fewer model evaluations than both variational and kernel-based training: +# + +model_evals_nn( + n_data=len(X), + n_params=len(trained_params.flatten()), + n_steps=steps, + split=len(X_train) / (len(X_train) + len(X_test)), + batch_size=batch_size, +) + + +###################################################################### +# Which method scales best? +# ------------------------- +# + + +###################################################################### +# The answer to this question depends on how the variational model +# is set up, and we need to make a few assumptions: +# +# 1. Even if we use single-batch stochastic gradient descent, in which every training step uses +# exactly one training sample, we would want to see every training sample at least once on average. +# Therefore, the number of steps should scale at least linearly with the number of training data samples. +# +# 2. Modern neural networks often have many more parameters than training +# samples. But we do not know yet whether variational circuits really need that many parameters as well. +# We will therefore use two cases for comparison: +# +# 2a) the number of parameters grows linearly with the training data, or ``n_params = M``, +# +# 2b) the number of parameters saturates at some point, which we model by setting ``n_params = sqrt(M)``. +# +# Note that compared to the example above with 75 training samples and 24 parameters, a) overestimates the number of evaluations, while b) +# underestimates it. +# + + +###################################################################### +# This is how the three methods compare: +# + +variational_training1 = [] +variational_training2 = [] +kernelbased_training = [] +nn_training = [] +x_axis = range(0, 2000, 100) + +for M in x_axis: + var1 = circuit_evals_variational( + n_data=M, n_params=M, n_steps=M, shift_terms=2, split=0.75, batch_size=1 + ) + variational_training1.append(var1) + + var2 = circuit_evals_variational( + n_data=M, n_params=round(np.sqrt(M)), n_steps=M, + shift_terms=2, split=0.75, batch_size=1 + ) + variational_training2.append(var2) + + kernel = circuit_evals_kernel(n_data=M, split=0.75) + kernelbased_training.append(kernel) + + nn = model_evals_nn( + n_data=M, n_params=M, n_steps=M, split=0.75, batch_size=1 + ) + nn_training.append(nn) + + +plt.plot(x_axis, nn_training, linestyle='--', label="neural net") +plt.plot(x_axis, variational_training1, label="var. circuit (linear param scaling)") +plt.plot(x_axis, variational_training2, label="var. circuit (srqt param scaling)") +plt.plot(x_axis, kernelbased_training, label="(quantum) kernel") +plt.xlabel("size of data set") +plt.ylabel("number of evaluations") +plt.legend() +plt.tight_layout() +plt.show() + + +###################################################################### +# This is the plot we saw at the beginning. +# With current hardware-compatible training methods, whether kernel-based training +# requires more or fewer quantum circuit evaluations +# than variational training depends on how many parameters the latter needs. +# If variational circuits turn out to be as parameter-hungry as neural networks, +# kernel-based training will outperform them for common machine learning tasks. However, +# if variational learning only turns out to require few parameters (or if more efficient training methods are found), +# variational circuits could in principle match the linear scaling of neural networks trained with backpropagation. +# +# The practical take-away from this demo is that unless your variational circuit has significantly fewer +# parameters than training data, kernel methods could be a much faster alternative! +# +# Finally, it is important to note that fault-tolerant quantum computers may change the picture +# for both quantum and classical machine learning. +# As mentioned in `Schuld (2021) `__, +# early results from the quantum machine learning literature show that +# larger quantum computers will most likely enable us to reduce +# the quadratic scaling of kernel methods to linear scaling, which may make classical as well as quantum kernel methods a +# strong alternative to neural networks for big data processing one day. +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_kernel_based_training/metadata.json b/demonstrations_v2/tutorial_kernel_based_training/metadata.json new file mode 100644 index 0000000000..aeb9c5d2ed --- /dev/null +++ b/demonstrations_v2/tutorial_kernel_based_training/metadata.json @@ -0,0 +1,33 @@ +{ + "title": "Kernel-based training of quantum models with scikit-learn", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2021-02-03T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_kernal-based_training_of_quantum_models.png" + } + ], + "seoDescription": "Train a quantum machine learning model based on the idea of quantum kernels.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/kernel-based-training-demonstration/1017" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_kernel_based_training/requirements.in b/demonstrations_v2/tutorial_kernel_based_training/requirements.in new file mode 100644 index 0000000000..375ecbc231 --- /dev/null +++ b/demonstrations_v2/tutorial_kernel_based_training/requirements.in @@ -0,0 +1,5 @@ +matplotlib +numpy +pennylane +scikit-learn +torch diff --git a/demonstrations_v2/tutorial_kernels_module/demo.py b/demonstrations_v2/tutorial_kernels_module/demo.py new file mode 100644 index 0000000000..fc3a5e67fa --- /dev/null +++ b/demonstrations_v2/tutorial_kernels_module/demo.py @@ -0,0 +1,630 @@ +r"""Training and evaluating quantum kernels +=========================================== + +.. meta:: + :property="og:description": Kernels and alignment training with Pennylane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/QEK_thumbnail.png + +.. related:: + + tutorial_kernel_based_training Kernel-based training with scikit-learn + tutorial_data_reuploading_classifier Data-reuploading classifier + +*Authors: Peter-Jan Derks, Paul K. Faehrmann, Elies Gil-Fuster, Tom +Hubregtsen, Johannes Jakob Meyer and David Wierichs — Posted: 24 June 2021. Last updated: 18 November 2021.* + +Kernel methods are one of the cornerstones of classical machine learning. +Here we are concerned with kernels that can be evaluated on quantum computers, +*quantum kernels* for short. +In this tutorial you will learn how to evaluate kernels, use them for classification +and train them with gradient-based optimization, and all that using the +functionality of PennyLane's +`kernels module `__. +The demo is based on Ref. [#Training_QEKs]_, a project from Xanadu's own +`QHack `__ hackathon. + +What are kernel methods? +------------------------ + +To understand what a kernel method does, let's first revisit +one of the simplest methods to assign binary labels to datapoints: +linear classification. + +Imagine we want to discern two different classes of points that lie in +different corners of the plane. A linear classifier corresponds to +drawing a line and assigning different labels to the regions on opposing +sides of the line: + +.. figure:: ../_static/demonstration_assets/kernels_module/linear_classification.png + :align: center + :width: 30% + +We can mathematically formalize this by assigning the label :math:`y` +via + +.. math:: + y(\boldsymbol{x}) = \operatorname{sgn}(\langle \boldsymbol{w}, \boldsymbol{x}\rangle + b). + +The vector :math:`\boldsymbol{w}` points perpendicular to the line and +thus determine its slope. The independent term :math:`b` specifies the +position on the plane. In this form, linear classification can also be +extended to higher dimensional vectors :math:`\boldsymbol{x},` where a +line does not divide the entire space into two regions anymore. Instead +one needs a *hyperplane*. It is immediately clear that this method is +not very powerful, as datasets that are not separable by a hyperplane +can't be classified without error. + +We can actually sneak around this limitation by performing a neat trick: +if we define some map :math:`\phi(\boldsymbol{x})` that *embeds* our +datapoints into a larger *feature space* and then perform linear +classification there, we could actually realise non-linear +classification in our original space! + +.. figure:: ../_static/demonstration_assets/kernels_module/embedding_nonlinear_classification.png + :align: center + :width: 65% + +If we go back to the expression for our prediction and include the +embedding, we get + +.. math:: + y(\boldsymbol{x}) = \operatorname{sgn}(\langle \boldsymbol{w}, \phi(\boldsymbol{x})\rangle + b). + +We will forgo one tiny step, but it can be shown that for the purpose +of optimal classification, we can choose the vector defining the +decision boundary as a linear combination of the embedded datapoints +:math:`\boldsymbol{w} = \sum_i \alpha_i \phi(\boldsymbol{x}_i).` Putting +this into the formula yields + +.. math:: + y(\boldsymbol{x}) = \operatorname{sgn}\left(\sum_i \alpha_i \langle \phi(\boldsymbol{x}_i), \phi(\boldsymbol{x})\rangle + b\right). + +This rewriting might not seem useful at first, but notice the above +formula only contains inner products between vectors in the embedding +space: + +.. math:: + k(\boldsymbol{x}_i, \boldsymbol{x}_j) = \langle \phi(\boldsymbol{x}_i), \phi(\boldsymbol{x}_j)\rangle. + +We call this function the *kernel*. It provides the advantage that we can often +find an explicit formula for the kernel :math:`k` that makes it +superfluous to actually perform the (potentially expensive) embedding +:math:`\phi.` Consider for example the following embedding and the +associated kernel: + +.. math:: + \phi((x_1, x_2)) &= (x_1^2, \sqrt{2} x_1 x_2, x_2^2) \\ + k(\boldsymbol{x}, \boldsymbol{y}) &= x_1^2 y_1^2 + 2 x_1 x_2 y_1 y_2 + x_2^2 y_2^2 = \langle \boldsymbol{x}, \boldsymbol{y} \rangle^2. + +This means by just replacing the regular scalar product in our linear +classification with the map :math:`k,` we can actually express much more +intricate decision boundaries! + +This is very important, because in many interesting cases the embedding :math:`\phi` +will be much costlier to compute than the kernel :math:`k.` + +In this demo, we will explore one particular kind of kernel +that can be realized on near-term quantum computers, namely *Quantum +Embedding Kernels (QEKs)*. These are kernels that arise from embedding +data into the space of quantum states. We formalize this by considering +a parameterised quantum circuit :math:`U(\boldsymbol{x})` that maps +a datapoint :math:`\boldsymbol{x}` to the state + +.. math:: + |\psi(\boldsymbol{x})\rangle = U(\boldsymbol{x}) |0 \rangle. + +The kernel value is then given by the *overlap* of the associated +embedded quantum states + +.. math:: + k(\boldsymbol{x}_i, \boldsymbol{x}_j) = | \langle\psi(\boldsymbol{x}_i)|\psi(\boldsymbol{x}_j)\rangle|^2. + +""" + +############################################################################## +# A toy problem +# ------------- +# In this demo, we will treat a toy problem that showcases the +# inner workings of classification with quantum embedding kernels, +# training variational embedding kernels and the available functionalities +# to do both in PennyLane. We of course need to start with some imports: + +from pennylane import numpy as np +import matplotlib as mpl + +np.random.seed(1359) + +############################################################################## +# And we proceed right away to create a dataset to work with, the +# ``DoubleCake`` dataset. Firstly, we define two functions to enable us to +# generate the data. +# The details of these functions are not essential for understanding the demo, +# so don't mind them if they are confusing. + + +def _make_circular_data(num_sectors): + """Generate datapoints arranged in an even circle.""" + center_indices = np.array(range(0, num_sectors)) + sector_angle = 2 * np.pi / num_sectors + angles = (center_indices + 0.5) * sector_angle + x = 0.7 * np.cos(angles) + y = 0.7 * np.sin(angles) + labels = 2 * np.remainder(np.floor_divide(angles, sector_angle), 2) - 1 + + return x, y, labels + + +def make_double_cake_data(num_sectors): + x1, y1, labels1 = _make_circular_data(num_sectors) + x2, y2, labels2 = _make_circular_data(num_sectors) + + # x and y coordinates of the datapoints + x = np.hstack([x1, 0.5 * x2]) + y = np.hstack([y1, 0.5 * y2]) + + # Canonical form of dataset + X = np.vstack([x, y]).T + + labels = np.hstack([labels1, -1 * labels2]) + + # Canonical form of labels + Y = labels.astype(int) + + return X, Y + +############################################################################## +# Next, we define a function to help plot the ``DoubleCake`` data: + +def plot_double_cake_data(X, Y, ax, num_sectors=None): + """Plot double cake data and corresponding sectors.""" + x, y = X.T + cmap = mpl.colors.ListedColormap(["#FF0000", "#0000FF"]) + ax.scatter(x, y, c=Y, cmap=cmap, s=25, marker="s") + + if num_sectors is not None: + sector_angle = 360 / num_sectors + for i in range(num_sectors): + color = ["#FF0000", "#0000FF"][(i % 2)] + other_color = ["#FF0000", "#0000FF"][((i + 1) % 2)] + ax.add_artist( + mpl.patches.Wedge( + (0, 0), + 1, + i * sector_angle, + (i + 1) * sector_angle, + lw=0, + color=color, + alpha=0.1, + width=0.5, + ) + ) + ax.add_artist( + mpl.patches.Wedge( + (0, 0), + 0.5, + i * sector_angle, + (i + 1) * sector_angle, + lw=0, + color=other_color, + alpha=0.1, + ) + ) + ax.set_xlim(-1, 1) + + ax.set_ylim(-1, 1) + ax.set_aspect("equal") + ax.axis("off") + + return ax + + +############################################################################## +# Let's now have a look at our dataset. In our example, we will work with +# 3 sectors: + +import matplotlib.pyplot as plt + +num_sectors = 3 +X, Y = make_double_cake_data(num_sectors) + +ax = plot_double_cake_data(X, Y, plt.gca(), num_sectors=num_sectors) + +############################################################################## +# Defining a Quantum Embedding Kernel +# ----------------------------------- +# PennyLane's `kernels module `__ +# allows for a particularly simple +# implementation of Quantum Embedding Kernels. The first ingredient we +# need for this is an *ansatz*, which we will construct by repeating a +# layer as building block. Let's start by defining this layer: + +import pennylane as qml + + +def layer(x, params, wires, i0=0, inc=1): + """Building block of the embedding ansatz""" + i = i0 + for j, wire in enumerate(wires): + qml.Hadamard(wires=[wire]) + qml.RZ(x[i % len(x)], wires=[wire]) + i += inc + qml.RY(params[0, j], wires=[wire]) + + n_wires = len(wires) + for p, w in zip(params[1], wires): + qml.CRZ(p, wires=[w % n_wires, (w + 1) % n_wires]) + + +############################################################################## +# To construct the ansatz, this layer is repeated multiple times, reusing +# the datapoint ``x`` but feeding different variational +# parameters ``params`` into each of them. +# Together, the datapoint and the variational parameters fully determine +# the embedding ansatz :math:`U(\boldsymbol{x}).` +# In order to construct the full kernel circuit, we also require its adjoint +# :math:`U(\boldsymbol{x})^\dagger,` which we can obtain via ``qml.adjoint`.` + + +def ansatz(x, params, wires): + """The embedding ansatz""" + for j, layer_params in enumerate(params): + layer(x, layer_params, wires, i0=j * len(wires)) + + +adjoint_ansatz = qml.adjoint(ansatz) + + +def random_params(num_wires, num_layers): + """Generate random variational parameters in the shape for the ansatz.""" + return np.random.uniform(0, 2 * np.pi, (num_layers, 2, num_wires), requires_grad=True) + + +############################################################################## +# Together with the ansatz we only need a device to run the quantum circuit on. +# For the purpose of this tutorial we will use PennyLane's ``default.qubit`` +# device with 5 wires in analytic mode. + +dev = qml.device("default.qubit", wires=5, shots=None) +wires = dev.wires.tolist() + +############################################################################## +# Let us now define the quantum circuit that realizes the kernel. We will compute +# the overlap of the quantum states by first applying the embedding of the first +# datapoint and then the adjoint of the embedding of the second datapoint. We +# finally extract the probabilities of observing each basis state. + + +@qml.qnode(dev) +def kernel_circuit(x1, x2, params): + ansatz(x1, params, wires=wires) + adjoint_ansatz(x2, params, wires=wires) + return qml.probs(wires=wires) + + +############################################################################## +# The kernel function itself is now obtained by looking at the probability +# of observing the all-zero state at the end of the kernel circuit – because +# of the ordering in ``qml.probs``, this is the first entry: + + +def kernel(x1, x2, params): + return kernel_circuit(x1, x2, params)[0] + + +############################################################################## +# +# .. note:: +# An alternative way to set up the kernel circuit in PennyLane would be +# to use the observable type +# `Projector `__. +# This is shown in the +# `demo on kernel-based training of quantum models `__, where you will also find more +# background information on the kernel circuit structure itself. +# +# Before focusing on the kernel values we have to provide values for the +# variational parameters. At this point we fix the number of layers in the +# ansatz circuit to :math:`6.` + +init_params = random_params(num_wires=5, num_layers=6) + +############################################################################## +# Now we can have a look at the kernel value between the first and the +# second datapoint: + +kernel_value = kernel(X[0], X[1], init_params) +print(f"The kernel value between the first and second datapoint is {kernel_value:.3f}") + +############################################################################## +# The mutual kernel values between all elements of the dataset form the +# *kernel matrix*. We can inspect it via the ``qml.kernels.square_kernel_matrix`` +# method, which makes use of symmetry of the kernel, +# :math:`k(\boldsymbol{x}_i,\boldsymbol{x}_j) = k(\boldsymbol{x}_j, \boldsymbol{x}_i).` +# In addition, the option ``assume_normalized_kernel=True`` ensures that we do not +# calculate the entries between the same datapoints, as we know them to be 1 +# for our noiseless simulation. Overall this means that we compute +# :math:`\frac{1}{2}(N^2-N)` kernel values for :math:`N` datapoints. +# To include the variational parameters, we construct a ``lambda`` function that +# fixes them to the values we sampled above. + +init_kernel = lambda x1, x2: kernel(x1, x2, init_params) +K_init = qml.kernels.square_kernel_matrix(X, init_kernel, assume_normalized_kernel=True) + +with np.printoptions(precision=3, suppress=True): + print(K_init) + +############################################################################## +# Using the Quantum Embedding Kernel for predictions +# -------------------------------------------------- +# The quantum kernel alone can not be used to make predictions on a +# dataset, becaues it is essentially just a tool to measure the similarity +# between two datapoints. To perform an actual prediction we will make use +# of scikit-learn's Support Vector Classifier (SVC). + +from sklearn.svm import SVC + +############################################################################## +# To construct the SVM, we need to supply ``sklearn.svm.SVC`` with a function +# that takes two sets of datapoints and returns the associated kernel matrix. +# We can make use of the function ``qml.kernels.kernel_matrix`` that provides +# this functionality. It expects the kernel to not have additional parameters +# besides the datapoints, which is why we again supply the variational +# parameters via the ``lambda`` function from above. +# Once we have this, we can let scikit-learn adjust the SVM from our Quantum +# Embedding Kernel. +# +# .. note:: +# This step does *not* modify the variational parameters in our circuit +# ansatz. What it does is solving a different optimization task for the +# :math:`\alpha` and :math:`b` vectors we introduced in the beginning. + +svm = SVC(kernel=lambda X1, X2: qml.kernels.kernel_matrix(X1, X2, init_kernel)).fit(X, Y) + +############################################################################## +# To see how well our classifier performs we will measure which percentage +# of the dataset it classifies correctly. + + +def accuracy(classifier, X, Y_target): + return 1 - np.count_nonzero(classifier.predict(X) - Y_target) / len(Y_target) + + +accuracy_init = accuracy(svm, X, Y) +print(f"The accuracy of the kernel with random parameters is {accuracy_init:.3f}") + +############################################################################## +# We are also interested in seeing what the decision boundaries in this +# classification look like. This could help us spotting overfitting issues +# visually in more complex data sets. To this end we will introduce a +# second helper method. + + +def plot_decision_boundaries(classifier, ax, N_gridpoints=14): + _xx, _yy = np.meshgrid(np.linspace(-1, 1, N_gridpoints), np.linspace(-1, 1, N_gridpoints)) + + _zz = np.zeros_like(_xx) + for idx in np.ndindex(*_xx.shape): + _zz[idx] = classifier.predict(np.array([_xx[idx], _yy[idx]])[np.newaxis, :]) + + plot_data = {"_xx": _xx, "_yy": _yy, "_zz": _zz} + ax.contourf( + _xx, + _yy, + _zz, + cmap=mpl.colors.ListedColormap(["#FF0000", "#0000FF"]), + alpha=0.2, + levels=[-1, 0, 1], + ) + plot_double_cake_data(X, Y, ax) + + return plot_data + + +############################################################################## +# With that done, let's have a look at the decision boundaries for our +# initial classifier: + +init_plot_data = plot_decision_boundaries(svm, plt.gca()) + +############################################################################## +# We see the outer points in the dataset can be correctly classified, but +# we still struggle with the inner circle. But remember we have a circuit +# with many free parameters! It is reasonable to believe we can give +# values to those variational parameters which improve the overall accuracy +# of our SVC. +# +# Training the Quantum Embedding Kernel +# ------------------------------------- +# +# To be able to train the Quantum Embedding Kernel we need some measure of +# how well it fits the dataset in question. Performing an exhaustive +# search in parameter space is not a good solution because it is very +# resource intensive, and since the accuracy is a discrete quantity we +# would not be able to detect small improvements. +# +# We can, however, resort to a more specialized measure, the +# *kernel-target alignment* [#Alignment]_. The kernel-target alignment compares the +# similarity predicted by the quantum kernel to the actual labels of the +# training data. It is based on *kernel alignment*, a similiarity measure +# between two kernels with given kernel matrices :math:`K_1` and +# :math:`K_2:` +# +# .. math:: +# \operatorname{KA}(K_1, K_2) = \frac{\operatorname{Tr}(K_1 K_2)}{\sqrt{\operatorname{Tr}(K_1^2)\operatorname{Tr}(K_2^2)}}. +# +# .. note:: +# Seen from a more theoretical side, :math:`\operatorname{KA}` +# is nothing else than the cosine of the angle between the kernel +# matrices :math:`K_1` and :math:`K_2` if we see them as vectors +# in the space of matrices with the Hilbert-Schmidt (or +# Frobenius) scalar product +# :math:`\langle A, B \rangle = \operatorname{Tr}(A^T B).` This +# reinforces the geometric picture of how this measure relates +# to objects, namely two kernels, being aligned in a vector space. +# +# The training data enters the picture by defining an *ideal* kernel +# function that expresses the original labelling in the vector +# :math:`\boldsymbol{y}` by assigning to two datapoints the product +# of the corresponding labels: +# +# .. math:: +# k_{\boldsymbol{y}}(\boldsymbol{x}_i, \boldsymbol{x}_j) = y_i y_j. +# +# The assigned kernel is thus :math:`+1` if both datapoints lie in the +# same class and :math:`-1` otherwise and its kernel matrix is simply +# given by the outer product :math:`\boldsymbol{y}\boldsymbol{y}^T.` +# The kernel-target alignment is then defined as the kernel alignment +# of the kernel matrix :math:`K` generated by the +# quantum kernel and :math:`\boldsymbol{y}\boldsymbol{y}^T:` +# +# .. math:: +# \operatorname{KTA}_{\boldsymbol{y}}(K) +# = \frac{\operatorname{Tr}(K \boldsymbol{y}\boldsymbol{y}^T)}{\sqrt{\operatorname{Tr}(K^2)\operatorname{Tr}((\boldsymbol{y}\boldsymbol{y}^T)^2)}} +# = \frac{\boldsymbol{y}^T K \boldsymbol{y}}{\sqrt{\operatorname{Tr}(K^2)} N} +# +# where :math:`N` is the number of elements in :math:`\boldsymbol{y},` +# that is the number of datapoints in the dataset. +# +# In summary, the kernel-target alignment effectively captures how well +# the kernel you chose reproduces the actual similarities of the data. It +# does have one drawback, however: having a high kernel-target alignment +# is only a necessary but not a sufficient condition for a good +# performance of the kernel [#Alignment]_. This means having good alignment is +# guaranteed for good performance, but optimal alignment will not always +# bring optimal training accuracy with it. +# +# Let's now come back to the actual implementation. PennyLane's +# ``kernels`` module allows you to easily evaluate the kernel +# target alignment: + +kta_init = qml.kernels.target_alignment(X, Y, init_kernel, assume_normalized_kernel=True) + +print(f"The kernel-target alignment for our dataset and random parameters is {kta_init:.3f}") + +############################################################################## +# Now let's code up an optimization loop and improve the kernel-target alignment! +# +# We will make use of regular gradient descent optimization. To speed up +# the optimization we will not use the entire training set to compute +# :math:`\operatorname{KTA}` but rather +# sample smaller subsets of the data at each step, we choose :math:`4` +# datapoints at random. Remember that PennyLane's built-in optimizer works +# to *minimize* the cost function that is given to it, which is why we +# have to multiply the kernel target alignment by :math:`-1` to actually +# *maximize* it in the process. +# +# .. note:: +# Currently, the function ``qml.kernels.target_alignment`` is not +# differentiable yet, making it unfit for gradient descent optimization. +# We therefore first define a differentiable version of this function. + + +def target_alignment( + X, + Y, + kernel, + assume_normalized_kernel=False, + rescale_class_labels=True, +): + """Kernel-target alignment between kernel and labels.""" + + K = qml.kernels.square_kernel_matrix( + X, + kernel, + assume_normalized_kernel=assume_normalized_kernel, + ) + + if rescale_class_labels: + nplus = np.count_nonzero(np.array(Y) == 1) + nminus = len(Y) - nplus + _Y = np.array([y / nplus if y == 1 else y / nminus for y in Y]) + else: + _Y = np.array(Y) + + T = np.outer(_Y, _Y) + inner_product = np.sum(K * T) + norm = np.sqrt(np.sum(K * K) * np.sum(T * T)) + inner_product = inner_product / norm + + return inner_product + + +params = init_params +opt = qml.GradientDescentOptimizer(0.2) + +for i in range(500): + # Choose subset of datapoints to compute the KTA on. + subset = np.random.choice(list(range(len(X))), 4) + # Define the cost function for optimization + cost = lambda _params: -target_alignment( + X[subset], + Y[subset], + lambda x1, x2: kernel(x1, x2, _params), + assume_normalized_kernel=True, + ) + # Optimization step + params = opt.step(cost, params) + + # Report the alignment on the full dataset every 50 steps. + if (i + 1) % 50 == 0: + current_alignment = target_alignment( + X, + Y, + lambda x1, x2: kernel(x1, x2, params), + assume_normalized_kernel=True, + ) + print(f"Step {i+1} - Alignment = {current_alignment:.3f}") + +############################################################################## +# We want to assess the impact of training the parameters of the quantum +# kernel. Thus, let's build a second support vector classifier with the +# trained kernel: + +# First create a kernel with the trained parameter baked into it. +trained_kernel = lambda x1, x2: kernel(x1, x2, params) + +# Second create a kernel matrix function using the trained kernel. +trained_kernel_matrix = lambda X1, X2: qml.kernels.kernel_matrix(X1, X2, trained_kernel) + +# Note that SVC expects the kernel argument to be a kernel matrix function. +svm_trained = SVC(kernel=trained_kernel_matrix).fit(X, Y) + +############################################################################## +# We expect to see an accuracy improvement vs. the SVM with random +# parameters: + +accuracy_trained = accuracy(svm_trained, X, Y) +print(f"The accuracy of a kernel with trained parameters is {accuracy_trained:.3f}") + +############################################################################## +# We have now achieved perfect classification! 🎆 +# +# Following on the results that SVM's have proven good generalisation +# behavior, it will be interesting to inspect the decision boundaries of +# our classifier: + +trained_plot_data = plot_decision_boundaries(svm_trained, plt.gca()) + +############################################################################## +# Indeed, we see that now not only every data instance falls within the +# correct class, but also that there are no strong artifacts that would make us +# distrust the model. In this sense, our approach benefits from both: on +# one hand it can adjust itself to the dataset, and on the other hand +# is not expected to suffer from bad generalisation. +# +# References +# ---------- +# +# .. [#Training_QEKs] +# +# Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan H. S. Derks, +# Paul K. Faehrmann, and Johannes Jakob Meyer. +# "Training Quantum Embedding Kernels on Near-Term Quantum Computers." +# `arXiv:2105.02276 `__, 2021. +# +# .. [#Alignment] +# +# Wang, Tinghua, Dongyan Zhao, and Shengfeng Tian. +# "An overview of kernel alignment and its applications." +# `Artificial Intelligence Review 43.2: 179-192 `__, 2015. +# +# diff --git a/demonstrations_v2/tutorial_kernels_module/metadata.json b/demonstrations_v2/tutorial_kernels_module/metadata.json new file mode 100644 index 0000000000..3983a5324e --- /dev/null +++ b/demonstrations_v2/tutorial_kernels_module/metadata.json @@ -0,0 +1,71 @@ +{ + "title": "Training and evaluating quantum kernels", + "authors": [ + { + "username": "pjderks" + }, + { + "username": "pkfaehrmann" + }, + { + "username": "egfuster" + }, + { + "username": "thubregtsen" + }, + { + "username": "jjmeyer" + }, + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2021-06-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_training_and_evaluating_quantum_kernels.png" + } + ], + "seoDescription": "Kernels and alignment training with Pennylane.", + "doi": "", + "references": [ + { + "id": "Training_QEKs", + "type": "article", + "title": "Training Quantum Embedding Kernels on Near-Term Quantum Computers.", + "authors": "Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan H. S. Derks, Paul K. Faehrmann, and Johannes Jakob Meyer", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2105.02276" + }, + { + "id": "Alignment", + "type": "article", + "title": "An overview of kernel alignment and its applications.", + "authors": "Wang, Tinghua, Dongyan Zhao, and Shengfeng Tian", + "year": "2015", + "journal": "Artificial Intelligence Review", + "url": "https://link.springer.com/article/10.1007/s10462-012-9369-4" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_kernels_module/requirements.in b/demonstrations_v2/tutorial_kernels_module/requirements.in new file mode 100644 index 0000000000..166e26691f --- /dev/null +++ b/demonstrations_v2/tutorial_kernels_module/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +scikit-learn diff --git a/demonstrations_v2/tutorial_lcu_blockencoding/demo.py b/demonstrations_v2/tutorial_lcu_blockencoding/demo.py new file mode 100644 index 0000000000..81c0f15fcd --- /dev/null +++ b/demonstrations_v2/tutorial_lcu_blockencoding/demo.py @@ -0,0 +1,277 @@ +r"""Linear combination of unitaries and block encodings +============================================================= + +If I (Juan Miguel) had to summarize quantum computing in one sentence, it would be this: information is +encoded in quantum states and processed using `unitary operations `_. +The challenge of quantum algorithms is to design and build these unitaries to perform interesting and +useful tasks with the encoded information. My colleague `Nathan Wiebe `_ +once told me that some of his early research was motivated by a simple +question: Quantum computers can implement products of unitaries --- after all, +that's how we build circuits from a `universal gate set `_. +But what about **sums of unitaries**? 🤔 + +In this tutorial, we will teach you the basics of one of the most versatile tools in quantum algorithms: +*linear combinations of unitaries*, or LCUs for short. You will also understand how to +use LCUs to create another powerful building block of quantum algorithms: block encodings. +Among their many uses, block encodings allow us to transform quantum states by non-unitary +operators, and they are useful in a variety of contexts, perhaps most famously in +`qubitization `_ and the `quantum +singular value transformation (QSVT) `_. + +| + +.. figure:: ../_static/demonstration_assets/lcu_blockencoding/thumbnail_lcu_blockencoding.png + :align: center + :width: 50% + :target: javascript:void(0) + +| + +LCUs +---- +Linear combinations of unitaries are straightforward --- it’s already explained in the name: we +decompose operators into a weighted sum of unitaries. Mathematically, this means expressing +an operator :math:`A` in terms of coefficients :math:`\alpha_{k}` and unitaries :math:`U_{k}` as + +.. math:: A = \sum_{k=0}^{N-1} \alpha_k U_k. + +A general way to build LCUs is to employ properties of the **Pauli basis**. +This is the set of all products of Pauli matrices :math:`\{I, X, Y, Z\}.` For the space of operators +acting on :math:`n` qubits, this set forms a complete basis. Thus, any operator can be expressed in the Pauli basis, +which immediately gives an LCU decomposition. PennyLane allows you to decompose any matrix into the Pauli basis using the +:func:`~.pennylane.pauli_decompose` function. The coefficients :math:`\alpha_k` and the unitaries +:math:`U_k` from the decomposition can be accessed directly from the result. We show how to do this +in the code below for a simple example. + +""" +import numpy as np +import pennylane as qml + +a = 0.25 +b = 0.75 + +# matrix to be decomposed +A = np.array( + [[a, 0, 0, b], + [0, -a, b, 0], + [0, b, a, 0], + [b, 0, 0, -a]] +) + +LCU = qml.pauli_decompose(A) +LCU_coeffs, LCU_ops = LCU.terms() + +print(f"LCU decomposition:\n {LCU}") +print(f"Coefficients:\n {LCU_coeffs}") +print(f"Unitaries:\n {LCU_ops}") + + +############################################################################## +# PennyLane uses a smart Pauli decomposition based on vectorizing the matrix and exploiting properties of +# the `Walsh-Hadamard transform `_, +# but the cost still scales as ~ :math:`O(n 4^n)` for :math:`n` qubits, so be careful. +# +# It's good to remember that many types of Hamiltonians are already compactly expressed +# in the Pauli basis, for example in various `Ising models `_ +# and molecular Hamiltonians using the `Jordan-Wigner transformation `_. +# This is very useful since we get an LCU decomposition for free. +# +# Block Encodings +# --------------- +# Going from an LCU to a quantum circuit that applies the associated operator is also straightforward +# once you know the trick: to prepare, select, and unprepare. +# +# Starting from the LCU decomposition :math:`A = \sum_{k=0}^{N-1} \alpha_k U_k` with positive, real +# coefficients, we define the prepare (PREP) operator: +# +# .. math:: \text{PREP}|0\rangle = \sum_k \sqrt{\frac{|\alpha_k|}{\lambda}}|k\rangle, +# +# where :math:`\lambda` is a normalization constant defined as :math:`\lambda = \sum_k |\alpha_k|,` +# and the select (SEL) operator: +# +# .. math:: \text{SEL}|k\rangle |\psi\rangle = |k\rangle U_k |\psi\rangle. +# +# They are aptly named: PREP prepares a state whose amplitudes +# are determined by the coefficients of the LCU, and SEL selects which unitary is applied. +# +# .. note:: +# +# Some important details about the equations above: +# +# * :math:`SEL` acts this way on any state :math:`|\psi\rangle` +# * We are using :math:`|0\rangle` as shorthand to denote the all-zero state for multiple qubits. +# +# The final trick is to combine PREP and SEL to make :math:`A` appear 🪄: +# +# .. math:: \langle 0| \text{PREP}^\dagger \cdot \text{SEL} \cdot \text{PREP} |0\rangle|\psi\rangle = \frac{A}{\lambda} |\psi\rangle. +# +# If you're up for it, it's illuminating to go through the math and show how :math:`A` comes out on the right +# side of the equation. +# (Tip: calculate the action of :math:`\text{PREP}^\dagger` on :math:`\langle 0|,` not on the output +# state after :math:`\text{SEL} \cdot \text{PREP}`). +# +# Otherwise, the intuitive way to understand this equation is that we apply PREP, SEL, and then invert PREP. If +# we measure :math:`|0\rangle` in the auxiliary qubits, the input state :math:`|\psi\rangle` will be transformed by +# :math:`A` (up to normalization). The figure below shows this as a circuit with four unitaries in SEL. +# +# | +# +# .. figure:: ../_static/demonstration_assets/lcu_blockencoding/schematic.png +# :align: center +# :width: 50% +# :target: javascript:void(0) +# +# | +# +# The circuit +# +# .. math:: U = \text{PREP}^\dagger \cdot \text{SEL} \cdot \text{PREP} +# +# is a **block encoding** of :math:`A,` up to normalization. The reason for this name is that if we write :math:`U` +# as a matrix, the operator :math:`A` is encoded inside a block of :math:`U` as +# +# .. math:: U = \begin{bmatrix} A & \cdot \\ \cdot & \cdot \end{bmatrix}. +# +# This block is defined by the subspace of all states where the auxiliary qubits are in state +# :math:`|0\rangle.` +# +# +# PennyLane supports the direct implementation of `prepare `_ +# and `select `_ +# operators. We'll go through them individually and use them to construct a block encoding circuit. +# Prepare circuits can be constructed using the :class:`~.pennylane.StatePrep` operation, which takes +# the normalized target state as input: + +dev1 = qml.device("default.qubit", wires=1) + +# normalized square roots of coefficients +alphas = (np.sqrt(LCU_coeffs) / np.linalg.norm(np.sqrt(LCU_coeffs))) + + +@qml.qnode(dev1) +def prep_circuit(): + qml.StatePrep(alphas, wires=0) + return qml.state() + + +print("Target state: ", alphas) +print("Output state: ", np.real(prep_circuit())) + +############################################################################## +# Similarly, select circuits can be implemented using :class:`~.pennylane.Select`, which takes the +# target unitaries as input. We specify the control wires directly, but the system wires are inherited +# from the unitaries. Since :func:`~.pennylane.pauli_decompose` uses a canonical wire ordering, we +# first map the wires to those used for the system register in our circuit: + +import matplotlib.pyplot as plt + +dev2 = qml.device("default.qubit", wires=3) + +# unitaries +ops = LCU_ops +# relabeling wires: 0 → 1, and 1 → 2 +unitaries = [qml.map_wires(op, {0: 1, 1: 2}) for op in ops] + + +@qml.qnode(dev2) +def sel_circuit(qubit_value): + qml.BasisState(qubit_value, wires=0) + qml.Select(unitaries, control=0) + return qml.expval(qml.PauliZ(2)) + +qml.draw_mpl(sel_circuit, style='pennylane')([0]) +plt.show() +############################################################################## +# Based on the controlled operations, the circuit above will flip the measured qubit +# if the input is :math:`|1\rangle` and leave it unchanged if the +# input is :math:`|0\rangle.` The output expectation values correspond to these states: + +print('Expectation value for input |0>:', sel_circuit([0])) +print('Expectation value for input |1>:', sel_circuit([1])) + +############################################################################## +# We can now combine these to construct a full LCU circuit. Here we make use of the +# :func:`~.pennylane.adjoint` function as a convenient way to invert the prepare circuit. We have +# chosen an input matrix that is already normalized, so it can be seen appearing directly in the +# top-left block of the unitary describing the full circuit --- the mark of a successful block +# encoding. + + +@qml.qnode(dev2) +def lcu_circuit(): # block_encode + # PREP + qml.StatePrep(alphas, wires=0) + + # SEL + qml.Select(unitaries, control=0) + + # PREP_dagger + qml.adjoint(qml.StatePrep(alphas, wires=0)) + return qml.state() + + +output_matrix = qml.matrix(lcu_circuit)() +print("A:\n", A, "\n") +print("Block-encoded A:\n") +print(np.real(np.round(output_matrix,2))) + +############################################################################## +# Application: Projectors +# ----------------------- +# Suppose we wanted to project our quantum state :math:`|\psi\rangle` onto the state +# :math:`|\phi\rangle.` We could accomplish this by applying the projector +# :math:`| \phi \rangle\langle \phi |` to :math:`|\psi\rangle.` However, we cannot directly apply +# projectors as gates in our quantum circuits because they are **not** unitary operations. +# We can instead use a simple LCU decomposition which holds for any projector: +# +# .. math:: +# | \phi \rangle\langle \phi | = \frac{1}{2} \cdot \mathbb{I} + \frac{1}{2} \cdot (2 \cdot | \phi \rangle\langle \phi | - \mathbb{I}) +# +# Both terms in the expression above are unitary (try proving it for yourself). We can now use this +# LCU decomposition to block-encode the projector! As an example, let's block-encode the projector +# :math:`| 0 \rangle\langle 0 |` that projects a state to the :math:`|0\rangle` state: +# +# .. math:: | 0 \rangle\langle 0 | = \begin{bmatrix} +# 1 & 0 \\ +# 0 & 0 \\ +# \end{bmatrix}. +# + +coeffs = np.array([1/2, 1/2]) +alphas = np.sqrt(coeffs) / np.linalg.norm(np.sqrt(coeffs)) + +proj_unitaries = [qml.Identity(0), qml.PauliZ(0)] + +############################################################################## +# Note that the second term in our LCU simplifies to a Pauli :math:`Z` operation. We can now +# construct a full LCU circuit and verify that :math:`| 0 \rangle\langle 0 |` is block-encoded in +# the top left block of the matrix. + +def lcu_circuit(): # block_encode + # PREP + qml.StatePrep(alphas, wires="ancilla") + + # SEL + qml.Select(proj_unitaries, control="ancilla") + + # PREP_dagger + qml.adjoint(qml.StatePrep(alphas, wires="ancilla")) + return qml.state() + + +output_matrix = qml.matrix(lcu_circuit, wire_order=["ancilla", 0])() +print("Block-encoded projector:\n") +print(np.real(np.round(output_matrix,2))) + + +############################################################################## +# Final thoughts +# ------------------- +# LCUs and block encodings are often associated with advanced algorithms that require the full power +# of fault-tolerant quantum computers. The truth is that they are basic constructions with +# broad applicability that can be useful for all kinds of hardware and simulators. If you're working +# on quantum algorithms and applications in any capacity, these are techniques that you should +# master, and PennyLane is equipped with the tools to help you get there. + + +############################################################################## diff --git a/demonstrations_v2/tutorial_lcu_blockencoding/metadata.json b/demonstrations_v2/tutorial_lcu_blockencoding/metadata.json new file mode 100644 index 0000000000..b3cc50ad14 --- /dev/null +++ b/demonstrations_v2/tutorial_lcu_blockencoding/metadata.json @@ -0,0 +1,60 @@ +{ + "title": "Linear combination of unitaries and block encodings", + "authors": [ + { + "username": "ixfoduap" + }, + { + "username": "Diego" + }, + { + "username": "Jay" + } + ], + "dateOfPublication": "2023-10-25T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/lcu_blockencoding/thumbnail_lcu_blockencoding.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_lcu_blockencoding.png" + } + ], + "seoDescription": "Master the basics of LCUs and their applications", + "doi": "", + "references": [ + { + "id": "qsvt", + "type": "article", + "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", + "authors": "Andr\u00e1s Gily\u00e9n, Yuan Su, Guang Hao Low, Nathan Wiebe", + "year": "2019", + "publisher": "", + "journal": "", + "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_apply_qsvt", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/lcu-demo-comment/7316" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_lcu_blockencoding/requirements.in b/demonstrations_v2/tutorial_lcu_blockencoding/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_lcu_blockencoding/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py b/demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py new file mode 100644 index 0000000000..b7d456a4ba --- /dev/null +++ b/demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py @@ -0,0 +1,432 @@ +r"""Learning quantum dynamics incoherently: Variational learning using classical shadows +======================================================================================== + +How can we recreate and simulate an unknown quantum process with a quantum circuit? One approach is +to learn the dynamics of this process incoherently, as done by Jerbi et al. [#Jerbi]_. +Here, we'll reproduce the numerical simulations of [#Jerbi]_ using the authors' data, as provided +in the +`Learning Dynamics Incoherently PennyLane Dataset `__. + +This approach differs from learning the quantum process *coherently* [#Huang]_ because it does not +require the model circuit to be connected to the target quantum process. That is, the model circuit +does not receive quantum information from the target process directly. Instead, we train the model +circuit using classical information from the classical shadow measurements. This works well for +low-entangling processes but can require an exponential number of classical shadow measurements, +depending on the unknown quantum process [#Jerbi]_. This is useful because +it's not always possible to port the quantum output of a system directly to hardware without +first measuring it. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_LearningDynamicsIncoherently.png + :align: center + :width: 80% + +In simple terms, learning dynamics incoherently consists of two steps. First, we measure the output +of the unknown process for many different inputs. In this tutorial, we do this by measuring +:doc:`classical shadows ` of the target process output. + +Then, we adjust a variational quantum circuit +until it produces the same input-output combinations as the unknown process. +Here, we will +simulate the model circuit output and use the classical shadow measurements to estimate the +overlap between the model output states and the unknown process output states. +""" + +###################################################################### +# 1. Creating an unknown target quantum process +# ---------------------------------------------- +# +# For our unknown quantum process, we will use the +# time evolution of a Hamiltonian: +# +# .. math:: U(H, t) = e^{-i H t / \hbar} . +# +# For the Hamiltonian, :math:`H,` we choose a transverse-field Ising Hamiltonian (as in +# [#Jerbi]_): +# +# .. math:: H = \sum_{i=0}^{n-1} Z_iZ_{i+1} + \sum_{i=0}^{n}\alpha_iX_i, +# +# where :math:`n` is the number of qubits and :math:`\alpha` are randomly generated weights. +# +# More specifically, we will approximate :math:`U(H, t)` via +# `Trotterization `_. +# We first create the Hamiltonian and Trotterize later with :class:`~pennylane.TrotterProduct`. +# + +import pennylane as qml +from pennylane import numpy as pnp +import numpy as np +import matplotlib.pyplot as plt + +# number of qubits for the Hamiltonian +n_qubits = 2 + +# set random seed for reproducibility +pnp.random.seed(7) +np.random.seed(7) + +# generate random weights +alphas = np.random.normal(0, 0.5, size=n_qubits) + +# create the Hamiltonian +hamiltonian = qml.sum( + *[qml.PauliZ(wires=i) @ qml.PauliZ(wires=i + 1) for i in range(n_qubits - 1)] +) + qml.dot(alphas, [qml.PauliX(wires=i) for i in range(n_qubits)]) + +###################################################################### +# 2. Creating random initial states +# ----------------------------------- +# +# The next step is to prepare a set of initial states. We will then apply the +# unknown quantum process to each of these states and measure the output to create input-output +# pairs. Later, we will train a model circuit to generate the same input-output pairs, thus +# reproducing the unknown quantum process. +# +# Ideally, our input states should be uniformly distributed over the state space. If they are all +# clustered together, our model circuit will not learn to approximate the unknown quantum process +# behavior for states that are very different from our training set. +# +# For quantum systems, this means we want to sample +# :doc:`Haar-random states `, as done below. +# + +from scipy.stats import unitary_group + +n_random_states = 100 + +# Generate several random unitaries +random_unitaries = unitary_group.rvs(2**n_qubits, n_random_states) +# Take the first column of each unitary as a random state +random_states = [random_unitary[:, 0] for random_unitary in random_unitaries] + +###################################################################### +# .. note :: +# +# On a personal computer, this method becomes slow (>1 second) around 10 qubits. +# + + +###################################################################### +# 3. Time evolution and classical shadow measurements +# ---------------------------------------------------- +# +# Now we can evolve the initial states using a Trotterized version of the +# `Hamiltonian above <#creating-an-unknown-target-quantum-process>`_. This +# will approximate the time evolution of the transverse-field Ising system. +# + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def target_circuit(input_state): + # prepare training state + qml.StatePrep(input_state, wires=range(n_qubits)) + + # evolve the Hamiltonian for time=2 in n=1 steps with the order 1 formula + qml.TrotterProduct(hamiltonian, time=2, n=1, order=1) + return qml.classical_shadow(wires=range(n_qubits)) + + +qml.draw_mpl(target_circuit)(random_states[0]) +plt.show() + +###################################################################### +# +# Since ``target_circuit`` returns :func:`~pennylane.classical_shadow`, running the circuit with a +# ``shot`` value gives the desired number of classical shadow measurements. +# We use this to create a set of shadows for each initial state. +# + +n_measurements = 10000 + +shadows = [] +for random_state in random_states: + bits, recipes = target_circuit(random_state, shots=n_measurements) + shadow = qml.ClassicalShadow(bits, recipes) + shadows.append(shadow) + + +###################################################################### +# 4. Creating a model circuit that will learn the target process +# ---------------------------------------------------------------- +# +# Now that we have the classical shadow measurements, we need to create a model circuit that +# learns to produce the same output as the target circuit. +# +# As done in [#Jerbi]_, we create a model circuit with the same gate structure as the target +# circuit. If the target quantum process were truly unknown, then we would choose a general +# variational quantum circuit like in the +# :doc:`Variational classifier demo `. +# +# .. note :: +# +# We use *local* measurements to keep the computational complexity low and +# because classical shadows are well-suited to estimating local observables [#Jerbi]_. +# For this reason, the following circuit returns local density matrices for each qubit. In +# hardware, the density matrix is obtained via state tomography using Pauli measurements or classical shadows. + + +@qml.qnode(dev) +def model_circuit(params, random_state): + qml.StatePrep(random_state, wires=range(n_qubits)) + # parameterized quantum circuit with the same gate structure as the target + for i in range(n_qubits): + qml.RX(params[i], wires=i) + + for i in reversed(range(n_qubits - 1)): + qml.IsingZZ(params[n_qubits + i], wires=[i, i + 1]) + return [qml.density_matrix(i) for i in range(n_qubits)] + + +initial_params = pnp.random.random(size=n_qubits*2-1, requires_grad=True) + +qml.draw_mpl(model_circuit)(initial_params, random_states[0]) +plt.show() + +###################################################################### +# 5. Training using classical shadows in a cost function +# ------------------------------------------------------ +# +# We now have to find the optimal parameters for ``model_circuit`` to mirror the ``target_circuit``. +# We can estimate the similarity between the circuits according to this cost function (see +# Appendix B of [#Jerbi]_): +# +# .. math:: C^l_N(\theta) = 1 - \frac{1}{nN}\sum^N_{j=1}\sum^n_{i=1}Tr[U|\psi^{(j)}\rangle\langle\psi^{(j)}|U^\dagger O^{(j)}_i(\theta)], +# +# where :math:`n` is the number of qubits, :math:`N` is the number of initial states, :math:`\psi^{(j)}` +# are random states, :math:`U` is our target unitary operation, and :math:`O_i` is the local density +# matrix for qubit :math:`i` after applying the ``model_circuit`.` That is, the local states +# :math:`\rho_{i}^{(j)}` are used as the observables: +# +# .. math:: O_{i}^{(j)}(\theta) := \rho_{i}^{(j)}. +# +# We can calculate this cost for our system by using the +# `shadow measurements <#time-evolution-and-classical-shadow-measurements>`_ to estimate +# the expectation value of :math:`O_i.` Roughly, this cost function measures the fidelity between +# the model circuit and the target circuit, by proxy of the single-qubit reduced states +# :math:`\rho_{i}^{(j)}` of the model over a variety of input-output pairs. + + +def cost(params): + cost = 0.0 + for idx, random_state in enumerate(random_states): + # obtain the density matrices for each qubit + observable_mats = model_circuit(params, random_state) + # convert to a PauliSentence + observable_pauli = [ + qml.pauli_decompose(observable_mat, wire_order=[qubit]) + for qubit, observable_mat in enumerate(observable_mats) + ] + # estimate the overlap for each qubit + cost = cost + qml.math.sum(shadows[idx].expval(observable_pauli)) + cost = 1 - cost / n_qubits / n_random_states + return cost + + +params = initial_params + +optimizer = qml.GradientDescentOptimizer(stepsize=5) +steps = 50 + +costs = [None]*(steps+1) +params_list = [None]*(steps+1) + +params_list[0]=initial_params +for i in range(steps): + params_list[i + 1], costs[i] = optimizer.step_and_cost(cost, params_list[i]) + +costs[-1] = cost(params_list[-1]) + +print("Initial cost:", costs[0]) +print("Final cost:", costs[-1]) + +###################################################################### +# +# We can plot the cost over the iterations and compare it to the ideal cost. +# + + +# find the ideal parameters from the original Trotterized Hamiltonian +ideal_parameters = [ + op.decomposition()[0].parameters[0] + for op in qml.TrotterProduct(hamiltonian, 2, 1, 1).decomposition() +] +ideal_parameters = ideal_parameters[:n_qubits][::-1] + ideal_parameters[n_qubits:] + +ideal_cost = cost(ideal_parameters) + +plt.plot(costs, label="Training") +plt.plot([0, steps], [ideal_cost, ideal_cost], "r--", label="Ideal parameters") +plt.ylabel("Cost") +plt.xlabel("Training iterations") +plt.legend() +plt.show() + +###################################################################### +# In this case, we see +# that the ideal cost is greater than 0. This is because for the ideal parameters, the model outputs +# and target outputs are equal: +# +# .. math:: \rho_{i}^{(j)} := O_i = U|\psi^{(j)}\rangle\langle\psi^{(j)}|U^\dagger. +# +# Since the single-qubit reduced states used in the +# cost function are mixed states, the trace of their square is less than one: +# +# .. math:: Tr[(\rho_{i}^{(j)})^2] < 1. +# +# The ideal cost :math:`C^l_N(\theta)` is therefore greater than 0. +# +# We can also look at the :func:`trace_distance ` between the unitary +# matrix of the target circuit and the model circuit. As the circuits become more similar with each +# training iteration, we should see the trace distance decrease and reach a low value. +# + +import scipy + +target_matrix = qml.matrix( + qml.TrotterProduct(hamiltonian, 2, 1, 1), + wire_order=range(n_qubits), +) + +zero_state = [1] + [0]*(2**n_qubits-1) + +# model matrix using the all-|0> state to negate state preparation effects +model_matrices = [qml.matrix(model_circuit, wire_order=range(n_qubits))(params, zero_state) for params in params_list] +trace_distances = [qml.math.trace_distance(target_matrix, model_matrix) for model_matrix in model_matrices] + +plt.plot(trace_distances) +plt.ylabel("Trace distance") +plt.xlabel("Training iterations") +plt.show() + +print("The final trace distance is: \n", trace_distances[-1]) + + +###################################################################### +# Using the Learning Dynamics Incoherently PennyLane Dataset +# ---------------------------------------------------------- +# +# In Jerbi et al. [#Jerbi]_, the authors perform this procedure to learn dynamics incoherently on a +# larger, 16-qubit transverse-field Ising +# Hamiltonian, and use classical shadow samples from quantum hardware to estimate the cost function. +# The corresponding `Learning Dynamics Incoherently PennyLane Dataset `__ +# can be downloaded via the :mod:`qml.data` module. + +[ds] = qml.data.load("other", name="learning-dynamics-incoherently") + +# print the available data +print(ds.list_attributes()) + +# print more information about the hamiltonian +print(ds.attr_info["hamiltonian"]["doc"]) + +###################################################################### +# +# The unknown target Hamiltonian, Haar-random initial states, and resulting classical shadow +# measurements are all available in the dataset. +# +# .. note :: +# +# We use few shadows to keep the computational time low and the dataset contains only two +# training states. + +random_states = ds.training_states + +n_measurements = 10000 +shadows = [qml.ClassicalShadow(shadow_meas[:n_measurements], shadow_bases[:n_measurements]) for shadow_meas, shadow_bases in zip(ds.shadow_meas,ds.shadow_bases)] + +###################################################################### +# +# We only need to create the model circuit, cost function, and train. +# For these we use the same model circuit as +# `above <#creating-a-model-circuit-that-will-learn-the-target-process>`_, updated to reflect +# the increased number of qubits. +# + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def model_circuit(params, random_state): + # this is a parameterized quantum circuit with the same gate structure as the target unitary + qml.StatePrep(random_state, wires=range(16)) + for i in range(16): + qml.RX(params[i], wires=i) + + for i in reversed(range(15)): + qml.IsingZZ(params[16 + i], wires=[i, i + 1]) + return [qml.density_matrix(i) for i in range(16)] + + +initial_params = pnp.random.random(size=31) + +qml.draw_mpl(model_circuit)(initial_params, random_states[0]) +plt.show() + +###################################################################### +# +# We can then minimize the cost to train the model to output the same states as the target circuit. +# For this, we can use the cost function from +# `before <#training-using-classical-shadows-in-a-cost-function>`_, +# as long as we update the number of qubits and the number of random states. +# + +n_qubits = 16 +n_random_states = len(ds.training_states) + +optimizer = qml.GradientDescentOptimizer(stepsize=5) +steps = 50 + +costs = [None]*(steps+1) +params_list = [None]*(steps+1) + +params_list[0]=initial_params +for i in range(steps): + params_list[i + 1], costs[i] = optimizer.step_and_cost(cost, params_list[i]) + +costs[-1] = cost(params_list[-1]) + +print("Initial cost:", cost(initial_params)) +print("Final cost:", costs[-1]) + +###################################################################### +# As a quick check, we can take a look at the density matrices +# to see whether the training was successful: +# + +original_matrices = model_circuit(initial_params, random_states[0]) +learned_matrices = model_circuit(params_list[-1], random_states[0]) +target_matrices_shadow = np.mean(shadows[0].local_snapshots(), axis=0) + +print("Untrained example output state\n", original_matrices[0]) +print("Trained example output state\n", learned_matrices[0]) +print("Target output state\n", target_matrices_shadow[0]) + +###################################################################### +# +# After training, the model outputs are closer to the target outputs, but not quite the same. +# This is due to the limitations of this learning method. Even for a simple circuit like the +# short-time evolution of a first order single Trotter step, it requires a large number of +# shadow measurements and training states to faithfully reproduce the underlying quantum process. +# The results can be improved by increasing the number of training states and +# :doc:`classical shadow measurements `. +# + + +############################################################################## +# +# References +# ------------ +# +# .. [#Jerbi] +# +# Sofiene Jerbi, Joe Gibbs, Manuel S. Rudolph, Matthias C. Caro, Patrick J. Coles, Hsin-Yuan Huang, Zoë Holmes +# "The power and limitations of learning quantum dynamics incoherently" +# `arXiv:2303.12834 `__, 2005. +# +# .. [#Huang] +# +# Hsin-Yuan Huang, Michael Broughton, Jordan Cotler, Sitan Chen, Jerry Li, Masoud Mohseni, Hartmut Neven, Ryan Babbush, Richard Kueng, John Preskill, and Jarrod R. McClean +# "Quantum advantage in learning from experiments" +# `Science `__, 2022 +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json b/demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json new file mode 100644 index 0000000000..43f2329a80 --- /dev/null +++ b/demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json @@ -0,0 +1,67 @@ +{ + "title": "Learning dynamics incoherently: Variational learning using classical shadows", + "authors": [ + { + "username": "Diego" + } + ], + "dateOfPublication": "2024-08-15T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_learning_dynamics_incoherently.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_learning_dynamics_incoherently.png" + } + ], + "seoDescription": "Learn how to reproduce an unknown quantum process with classical shadow measurements", + "doi": "", + "references": [ + { + "id": "jerbi2023power", + "type": "article", + "title": "The power and limitations of learning quantum dynamics incoherently", + "authors": "Sofiene Jerbi, Joe Gibbs, Manuel S. Rudolph, Matthias C. Caro, Patrick J. Coles, Hsin-Yuan Huang, and Zo\u00eb Holmes", + "year": "2023", + "url": "https://arxiv.org/abs/2303.12834" + }, + { + "id": "Huang2022Quantum", + "type": "article", + "title": "Quantum advantage in learning from experiments", + "journal": "Science", + "authors": "Hsin-Yuan Huang, Michael Broughton, Jordan Cotler, Sitan Chen, Jerry Li, Masoud Mohseni, Hartmut Neven, Ryan Babbush, Richard Kueng, John Preskill, and Jarrod R. McClean", + "year": "2022", + "url": "http://dx.doi.org/10.1126/science.abn7293" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2303.12834" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_haar_measure", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in b/demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in new file mode 100644 index 0000000000..8132c3977e --- /dev/null +++ b/demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_learning_few_data/demo.py b/demonstrations_v2/tutorial_learning_few_data/demo.py new file mode 100644 index 0000000000..7733739d43 --- /dev/null +++ b/demonstrations_v2/tutorial_learning_few_data/demo.py @@ -0,0 +1,598 @@ +r""" +.. _learning_few_data: + +Generalization in QML from few training data +============================================ + +.. meta:: + :property="og:description": Generalization of quantum machine learning models. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/few_data_thumbnail.png + +.. related:: + + tutorial_local_cost_functions Alleviating barren plateaus with local cost functions + +*Authors: Korbinian Kottmann, Luis Mantilla Calderon, Maurice Weber — Posted: 29 August 2022* + +In this tutorial, we dive into the generalization capabilities of quantum machine learning models. +For the example of a `Quantum Convolutional Neural Network (QCNN) `_, we show how its generalization error behaves as a +function of the number of training samples. This demo is based on the paper +*"Generalization in quantum machine learning from few training data"*. by Caro et al. [#CaroGeneralization]_. + +What is generalization in (Q)ML? +--------------------------------- +When optimizing a machine learning model, be it classical or quantum, we aim to maximize its performance over the data +distribution of interest (e.g., images of cats and dogs). However, in practice, we are limited to a finite amount of +data, which is why it is necessary to reason about how our model performs on new, previously unseen data. The difference +between the model's performance on the true data distribution and the performance estimated from our training data is +called the *generalization error*, and it indicates how well the model has learned to generalize to unseen data. +Generalization can be seen as a manifestation of the bias-variance trade-off; models that +perfectly fit the training data admit a low bias at the cost of a higher variance, and hence typically perform poorly on unseen +test data. In the classical machine learning community, this trade-off has been extensively +studied and has led to optimization techniques that favour generalization, for example, by regularizing models via +their variance [#NamkoongVariance]_. +Below, we see a canoncial example of this trade-off, with a model having low bias, but high variance +and therefore high generalization error. The low variance model, on the other hand, has a higher +bias but generalizes better. + +.. figure:: /_static/demonstration_assets/learning_few_data/overfitting.png + :width: 65% + :align: center + + + +Let us now dive deeper into generalization properties of quantum machine learning (QML) models. We start by describing +the typical data processing pipeline of a QML model. A classical data input :math:`x` is first encoded in a quantum +state via a mapping :math:`x \mapsto \rho(x).` This encoded state is then processed through a quantum +channel :math:`\rho(x) \mapsto \mathcal{E}_\alpha(\rho(x))` with learnable parameters :math:`\alpha.` Finally, a measurement is performed on the resulting state +to get the final prediction. Now, the goal is to minimize the expected loss over the data-generating distribution +:math:`P`, indicating how well our model performs on new data. Mathematically, for a loss function :math:`\ell,` the +expected loss, denoted by :math:`R,` is given by + +.. math:: R(\alpha) = \mathbb{E}_{(x,y)\sim P}[\ell(\alpha;\,x,\,y)] + +where :math:`x` are the features, :math:`y` are the labels, and :math:`P` is their joint distribution. +In practice, as the joint distribution :math:`P` is generally unknown, this quantity has to be +estimated from a finite amount of data. Given a training set :math:`S = \{(x_i,\,y_i)\}_{i=1}^N` +with :math:`N` samples, we estimate the performance of our QML model by calculating the +average loss over the training set + +.. math:: R_S(\alpha) = \frac{1}{N}\sum_{i=1}^N \ell(\alpha;\,x_i,\,y_i), + +which is referred to as the training loss and is an unbiased estimate of :math:`R(\alpha).` This is only a proxy +to the true quantity of interest :math:`R(\alpha),` and their difference is called the generalization error + +.. math:: \mathrm{gen}(\alpha) = R(\alpha) - \hat{R}_S(\alpha), + +which is the quantity that we explore in this tutorial. Keeping in mind the bias-variance trade-off, one would expect +that more complex models, i.e. models with a larger number of parameters, achieve a lower error on the training data +but a higher generalization error. Having more training data, on the other hand, leads to a better approximation of the +true expected loss and hence a lower generalization error. This intuition is made precise in Ref. [#CaroGeneralization]_, +where it is shown that :math:`\mathrm{gen}(\alpha)` roughly scales as :math:`\mathcal{O}(\sqrt{T / N}),` where :math:`T` +is the number of parametrized gates and :math:`N` is the number of training samples. +""" + +############################################################################## +# Generalization bounds for QML models +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# As hinted at earlier, we expect the generalization error to depend both on the richness of the model class, as well as +# on the amount of training data available. As a first result, the authors of Ref. [#CaroGeneralization]_ found that for +# a QML model with at most :math:`T` parametrized local quantum channels, the generalization error depends on :math:`T` +# and :math:`N` according to +# +# .. math:: \mathrm{gen}(\alpha) \sim \mathcal{O}\left(\sqrt{\frac{T\log T}{N}}\right). +# +# We see that this scaling is in line with our intuition that the generalization error scales inversely with the number +# of training samples and increases with the number of parametrized gates. However, as is the case for +# `quantum convolutional neural networks (QCNNs) `_, it is possible to get a more fine-grained bound by including knowledge on the number of gates :math:`M` which have been reused (i.e. whose parameters are shared across wires). Naively, one could suspect that the generalization error scales as +# :math:`\tilde{\mathcal{O}}(\sqrt{MT/N})` by directly applying the above result (and where +# :math:`\tilde{\mathcal{O}}` includes logarithmic factors). However, the authors of Ref. [#CaroGeneralization]_ found +# that such models actually adhere to the better scaling +# +# .. math:: \mathrm{gen}(\alpha) \sim \mathcal{O}\left(\sqrt{\frac{T\log MT}{N}}\right). +# +# With this, we see that for QCNNs to have a generalization error :math:`\mathrm{gen}(\alpha)\leq\epsilon,` we need a +# training set of size :math:`N \sim T \log MT / \epsilon^2.` For the special case of QCNNs, we can explicitly connect +# the number of samples needed for good generalization to the system size :math:`n` since these models +# use :math:`\mathcal{O}(\log(n))` independently parametrized gates, each of which is used at most :math:`n` times [#CongQuantumCNN]_. +# Putting the pieces together, we find that a training set of size +# +# .. math:: N \sim \mathcal{O}(\mathrm{poly}(\log n)) +# +# is sufficient for the generalization error to be bounded by :math:`\mathrm{gen}(\alpha) \leq \epsilon.` +# In the next part of this tutorial, we will illustrate this result by implementing a QCNN to classify different +# digits in the classical ``digits`` dataset. Before that, we set up our QCNN. + +############################################################################## +# Quantum convolutional neural networks +# ------------------------------------ +# Before we start building a QCNN, let us briefly review the idea of classical CNNs, which have shown +# tremendous success in tasks like image recognition, recommender systems, and sound classification, to name a few. +# For a more in-depth explanation of CNNs, we highly recommend `chapter 9 `_ +# in [#DLBook]_. +# Classical CNNs are a family of neural networks which make use of convolutions and pooling operations to +# insert an inductive bias, favouring invariances to spatial transformations like translations, rotations, and scaling. +# A *convolutional layer* consists of a small kernel (a window) that sweeps over a 2D array representation of an image and extracts local +# information while sharing parameters across the spatial dimensions. In addition to the convolutional layers, +# one typically uses pooling layers to reduce the size of the input and to provide a mechanism for summarizing +# information from a neighbourhood of values in the input. On top of reducing dimensionality, these types of layers have the advantage +# of making the model more agnostic to certain transformations like scaling and rotations. +# These two types of layers are applied repeatedly in an alternating manner as shown in the figure below. +# +# .. figure:: /_static/demonstration_assets/learning_few_data/cnn_pic.png +# :width: 75% +# :align: center +# +# A graphical representation of a CNN. Obtained using Ref. [#LeNailNNSVG]_. +# +# We want to build something similar for a quantum circuit. First, we import the necessary +# libraries we will need in this demo and set a random seed for reproducibility: + +import matplotlib as mpl +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from sklearn import datasets +import seaborn as sns +import jax; + +jax.config.update('jax_platform_name', 'cpu') +jax.config.update("jax_enable_x64", True) +import jax.numpy as jnp + +import optax # optimization using jax + +import pennylane as qml +import pennylane.numpy as pnp + +sns.set() + +seed = 0 +rng = np.random.default_rng(seed=seed) + + +############################################################################## +# To construct a convolutional and pooling layer in a quantum circuit, we will +# follow the QCNN construction proposed in [#CongQuantumCNN]_. The former layer +# will extract local correlations, while the latter allows reducing the dimensionality +# of the feature vector. In a quantum circuit, the convolutional layer, consisting of a kernel swept +# along the entire image, is a two-qubit unitary that correlates neighbouring +# qubits. As for the pooling layer, we will use a conditioned single-qubit unitary that depends +# on the measurement of a neighboring qubit. Finally, we use a *dense layer* that entangles all +# qubits of the final state using an all-to-all unitary gate as shown in the figure below. +# +# .. figure:: /_static/demonstration_assets/learning_few_data/qcnn-architecture.png +# :width: 75% +# :align: center +# +# QCNN architecture. Taken from Ref. [#CongQuantumCNN]_. +# +# Breaking down the layers +# -------------------------- +# +# The convolutional layer should have the weights of the two-qubit unitary as an input, which are +# updated at every training step. In PennyLane, we model this arbitrary two-qubit unitary +# with a particular sequence of gates: two single-qubit :class:`~.pennylane.U3` gates (parametrized by three +# parameters, each), three Ising interactions between both qubits (each interaction is +# parametrized by one parameter), and two additional :class:`~.pennylane.U3` gates on each of the two +# qubits. + +def convolutional_layer(weights, wires, skip_first_layer=True): + """Adds a convolutional layer to a circuit. + Args: + weights (np.array): 1D array with 15 weights of the parametrized gates. + wires (list[int]): Wires where the convolutional layer acts on. + skip_first_layer (bool): Skips the first two U3 gates of a layer. + """ + n_wires = len(wires) + assert n_wires >= 3, "this circuit is too small!" + + for p in [0, 1]: + for indx, w in enumerate(wires): + if indx % 2 == p and indx < n_wires - 1: + if indx % 2 == 0 and not skip_first_layer: + qml.U3(*weights[:3], wires=[w]) + qml.U3(*weights[3:6], wires=[wires[indx + 1]]) + qml.IsingXX(weights[6], wires=[w, wires[indx + 1]]) + qml.IsingYY(weights[7], wires=[w, wires[indx + 1]]) + qml.IsingZZ(weights[8], wires=[w, wires[indx + 1]]) + qml.U3(*weights[9:12], wires=[w]) + qml.U3(*weights[12:], wires=[wires[indx + 1]]) + + +############################################################################## +# The pooling layer's inputs are the weights of the single-qubit conditional unitaries, which in +# this case are :class:`~.pennylane.U3` gates. Then, we apply these conditional measurements to half of the +# unmeasured wires, reducing our system size by a factor of 2. + + +def pooling_layer(weights, wires): + """Adds a pooling layer to a circuit. + Args: + weights (np.array): Array with the weights of the conditional U3 gate. + wires (list[int]): List of wires to apply the pooling layer on. + """ + n_wires = len(wires) + assert len(wires) >= 2, "this circuit is too small!" + + for indx, w in enumerate(wires): + if indx % 2 == 1 and indx < n_wires: + m_outcome = qml.measure(w) + qml.cond(m_outcome, qml.U3)(*weights, wires=wires[indx - 1]) + + +############################################################################## +# We can construct a QCNN by combining both layers and using an arbitrary unitary to model +# a dense layer. It will take a set of features — the image — as input, encode these features using +# an embedding map, apply rounds of convolutional and pooling layers, and eventually output the +# desired measurement statistics of the circuit. + + +def conv_and_pooling(kernel_weights, n_wires, skip_first_layer=True): + """Apply both the convolutional and pooling layer.""" + convolutional_layer(kernel_weights[:15], n_wires, skip_first_layer=skip_first_layer) + pooling_layer(kernel_weights[15:], n_wires) + + +def dense_layer(weights, wires): + """Apply an arbitrary unitary gate to a specified set of wires.""" + qml.ArbitraryUnitary(weights, wires) + + +num_wires = 6 +device = qml.device("default.qubit", wires=num_wires) + + +@qml.qnode(device) +def conv_net(weights, last_layer_weights, features): + """Define the QCNN circuit + Args: + weights (np.array): Parameters of the convolution and pool layers. + last_layer_weights (np.array): Parameters of the last dense layer. + features (np.array): Input data to be embedded using AmplitudEmbedding.""" + + layers = weights.shape[1] + wires = list(range(num_wires)) + + # inputs the state input_state + qml.AmplitudeEmbedding(features=features, wires=wires, pad_with=0.5) + qml.Barrier(wires=wires, only_visual=True) + + # adds convolutional and pooling layers + for j in range(layers): + conv_and_pooling(weights[:, j], wires, skip_first_layer=(not j == 0)) + wires = wires[::2] + qml.Barrier(wires=wires, only_visual=True) + + assert last_layer_weights.size == 4 ** (len(wires)) - 1, ( + "The size of the last layer weights vector is incorrect!" + f" \n Expected {4 ** (len(wires)) - 1}, Given {last_layer_weights.size}" + ) + dense_layer(last_layer_weights, wires) + return qml.probs(wires=(0)) + + +fig, ax = qml.draw_mpl(conv_net)( + np.random.rand(18, 2), np.random.rand(4 ** 2 - 1), np.random.rand(2 ** num_wires) +) +plt.show() + +############################################################################## +# In the problem we will address, we need to encode 64 features +# in our quantum state. Thus, we require six qubits (:math:`2^6 = 64`) to encode +# each feature value in the amplitude of each computational basis state. +# +# Training the QCNN on the digits dataset +# --------------------------------------- +# In this demo, we are going to classify the digits ``0`` and ``1`` from the classical ``digits`` dataset. +# Each hand-written digit image is represented as an :math:`8 \times 8` array of pixels as shown below: + +digits = datasets.load_digits() +images, labels = digits.data, digits.target + +images = images[np.where((labels == 0) | (labels == 1))] +labels = labels[np.where((labels == 0) | (labels == 1))] + +fig, axes = plt.subplots(nrows=1, ncols=12, figsize=(3, 1)) + +for i, ax in enumerate(axes.flatten()): + ax.imshow(images[i].reshape((8, 8)), cmap="gray") + ax.axis("off") + +plt.tight_layout() +plt.subplots_adjust(wspace=0, hspace=0) +plt.show() + + +############################################################################## +# For convenience, we create a ``load_digits_data`` function that will make random training and +# testing sets from the ``digits`` dataset from ``sklearn.dataset``: + + +def load_digits_data(num_train, num_test, rng): + """Return training and testing data of digits dataset.""" + digits = datasets.load_digits() + features, labels = digits.data, digits.target + + # only use first two classes + features = features[np.where((labels == 0) | (labels == 1))] + labels = labels[np.where((labels == 0) | (labels == 1))] + + # normalize data + features = features / np.linalg.norm(features, axis=1).reshape((-1, 1)) + + # subsample train and test split + train_indices = rng.choice(len(labels), num_train, replace=False) + test_indices = rng.choice( + np.setdiff1d(range(len(labels)), train_indices), num_test, replace=False + ) + + x_train, y_train = features[train_indices], labels[train_indices] + x_test, y_test = features[test_indices], labels[test_indices] + + return ( + jnp.asarray(x_train), + jnp.asarray(y_train), + jnp.asarray(x_test), + jnp.asarray(y_test), + ) + + +############################################################################## +# To optimize the weights of our variational model, we define the cost and accuracy functions +# to train and quantify the performance on the classification task of the previously described QCNN: + + +@jax.jit +def compute_out(weights, weights_last, features, labels): + """Computes the output of the corresponding label in the qcnn""" + cost = lambda weights, weights_last, feature, label: conv_net(weights, weights_last, feature)[ + label + ] + return jax.vmap(cost, in_axes=(None, None, 0, 0), out_axes=0)( + weights, weights_last, features, labels + ) + + +def compute_accuracy(weights, weights_last, features, labels): + """Computes the accuracy over the provided features and labels""" + out = compute_out(weights, weights_last, features, labels) + return jnp.sum(out > 0.5) / len(out) + + +def compute_cost(weights, weights_last, features, labels): + """Computes the cost over the provided features and labels""" + out = compute_out(weights, weights_last, features, labels) + return 1.0 - jnp.sum(out) / len(labels) + + +def init_weights(): + """Initializes random weights for the QCNN model.""" + weights = pnp.random.normal(loc=0, scale=1, size=(18, 2), requires_grad=True) + weights_last = pnp.random.normal(loc=0, scale=1, size=4 ** 2 - 1, requires_grad=True) + return jnp.array(weights), jnp.array(weights_last) + + +value_and_grad = jax.jit(jax.value_and_grad(compute_cost, argnums=[0, 1])) + + +############################################################################## +# We are going to perform the classification for training sets with different values of :math:`N.` Therefore, we +# define the classification procedure once and then perform it for different datasets. +# Finally, we update the weights using the :class:`pennylane.AdamOptimizer` and use these updated weights to +# calculate the cost and accuracy on the testing and training set: + + +def train_qcnn(n_train, n_test, n_epochs): + """ + Args: + n_train (int): number of training examples + n_test (int): number of test examples + n_epochs (int): number of training epochs + desc (string): displayed string during optimization + + Returns: + dict: n_train, + steps, + train_cost_epochs, + train_acc_epochs, + test_cost_epochs, + test_acc_epochs + + """ + # load data + x_train, y_train, x_test, y_test = load_digits_data(n_train, n_test, rng) + + # init weights and optimizer + weights, weights_last = init_weights() + + # learning rate decay + cosine_decay_scheduler = optax.cosine_decay_schedule(0.1, decay_steps=n_epochs, alpha=0.95) + optimizer = optax.adam(learning_rate=cosine_decay_scheduler) + opt_state = optimizer.init((weights, weights_last)) + + # data containers + train_cost_epochs, test_cost_epochs, train_acc_epochs, test_acc_epochs = [], [], [], [] + + for step in range(n_epochs): + # Training step with (adam) optimizer + train_cost, grad_circuit = value_and_grad(weights, weights_last, x_train, y_train) + updates, opt_state = optimizer.update(grad_circuit, opt_state) + weights, weights_last = optax.apply_updates((weights, weights_last), updates) + + train_cost_epochs.append(train_cost) + + # compute accuracy on training data + train_acc = compute_accuracy(weights, weights_last, x_train, y_train) + train_acc_epochs.append(train_acc) + + # compute accuracy and cost on testing data + test_out = compute_out(weights, weights_last, x_test, y_test) + test_acc = jnp.sum(test_out > 0.5) / len(test_out) + test_acc_epochs.append(test_acc) + test_cost = 1.0 - jnp.sum(test_out) / len(test_out) + test_cost_epochs.append(test_cost) + + return dict( + n_train=[n_train] * n_epochs, + step=np.arange(1, n_epochs + 1, dtype=int), + train_cost=train_cost_epochs, + train_acc=train_acc_epochs, + test_cost=test_cost_epochs, + test_acc=test_acc_epochs, + ) + + +############################################################################## +# .. note:: +# +# There are some small intricacies for speeding up this code that are worth mentioning. We are using ``jax`` for our training +# because it allows for `just-in-time `_ (``jit``) compilation. A function decorated with ``@jax.jit`` will be compiled upon its first execution +# and cached for future executions. This means the first execution will take longer, but all subsequent executions are substantially faster. +# Further, we use ``jax.vmap`` to vectorize the execution of the QCNN over all input states, as opposed to looping through the training and test set at every execution. + +############################################################################## +# Training for different training set sizes yields different accuracies, as seen below. As we increase the training data size, the overall test accuracy, +# a proxy for the models' generalization capabilities, increases: + +n_test = 100 +n_epochs = 100 +n_reps = 100 + + +def run_iterations(n_train): + results_df = pd.DataFrame( + columns=["train_acc", "train_cost", "test_acc", "test_cost", "step", "n_train"] + ) + + for _ in range(n_reps): + results = train_qcnn(n_train=n_train, n_test=n_test, n_epochs=n_epochs) + results_df = pd.concat( + [results_df, pd.DataFrame.from_dict(results)], axis=0, ignore_index=True + ) + + return results_df + + +# run training for multiple sizes +train_sizes = [2, 5, 10, 20, 40, 80] +results_df = run_iterations(n_train=2) +for n_train in train_sizes[1:]: + results_df = pd.concat([results_df, run_iterations(n_train=n_train)]) + +############################################################################## +# Finally, we plot the loss and accuracy for both the training and testing set +# for all training epochs, and compare the test and train accuracy of the model: + +# aggregate dataframe +df_agg = results_df.groupby(["n_train", "step"]).agg(["mean", "std"]) +df_agg = df_agg.reset_index() + +sns.set_style('whitegrid') +colors = sns.color_palette() +fig, axes = plt.subplots(ncols=3, figsize=(16.5, 5)) + +generalization_errors = [] + +# plot losses and accuracies +for i, n_train in enumerate(train_sizes): + df = df_agg[df_agg.n_train == n_train] + + dfs = [df.train_cost["mean"], df.test_cost["mean"], df.train_acc["mean"], df.test_acc["mean"]] + lines = ["o-", "x--", "o-", "x--"] + labels = [fr"$N={n_train}$", None, fr"$N={n_train}$", None] + axs = [0,0,2,2] + + for k in range(4): + ax = axes[axs[k]] + ax.plot(df.step, dfs[k], lines[k], label=labels[k], markevery=10, color=colors[i], alpha=0.8) + + + # plot final loss difference + dif = df[df.step == 100].test_cost["mean"] - df[df.step == 100].train_cost["mean"] + generalization_errors.append(dif) + +# format loss plot +ax = axes[0] +ax.set_title('Train and Test Losses', fontsize=14) +ax.set_xlabel('Epoch') +ax.set_ylabel('Loss') + +# format generalization error plot +ax = axes[1] +ax.plot(train_sizes, generalization_errors, "o-", label=r"$gen(\alpha)$") +ax.set_xscale('log') +ax.set_xticks(train_sizes) +ax.set_xticklabels(train_sizes) +ax.set_title(r'Generalization Error $gen(\alpha) = R(\alpha) - \hat{R}_N(\alpha)$', fontsize=14) +ax.set_xlabel('Training Set Size') + +# format loss plot +ax = axes[2] +ax.set_title('Train and Test Accuracies', fontsize=14) +ax.set_xlabel('Epoch') +ax.set_ylabel('Accuracy') +ax.set_ylim(0.5, 1.05) + +legend_elements = [ + mpl.lines.Line2D([0], [0], label=f'N={n}', color=colors[i]) for i, n in enumerate(train_sizes) + ] + [ + mpl.lines.Line2D([0], [0], marker='o', ls='-', label='Train', color='Black'), + mpl.lines.Line2D([0], [0], marker='x', ls='--', label='Test', color='Black') + ] + +axes[0].legend(handles=legend_elements, ncol=3) +axes[2].legend(handles=legend_elements, ncol=3) + +axes[1].set_yscale('log', base=2) +plt.show() + +############################################################################## +# ------------ +# +# +# The key takeaway of this work is that some quantum learning +# models can achieve high-fidelity predictions using a few training data points. +# We implemented a model known as the quantum convolutional neural network (QCNN) using PennyLane +# for a binary classification task. Using six qubits, we have trained the QCNN to distinguish +# between handwritten digits of :math:`0`'s and :math:`1's`. With :math:`80` samples, we have +# achieved a model with accuracy greater than :math:`97\%` in :math:`100` training epochs. +# Furthermore, we have compared the test and train accuracy of this model for a different number of +# training samples and found the scaling of the generalization error agrees with the theoretical +# bounds obtained in [#CaroGeneralization]_. +# +# +# References +# ---------- +# +# .. [#CaroGeneralization] +# +# Matthias C. Caro, Hsin-Yuan Huang, M. Cerezo, Kunal Sharma, Andrew Sornborger, Lukasz Cincio, Patrick J. Coles. +# "Generalization in quantum machine learning from few training data" +# `arxiv:2111.05292 `__, 2021. +# +# .. [#DLBook] +# +# Ian Goodfellow, Yoshua Bengio and Aaron Courville. +# `"Deep Learning"2 `__, 2016. +# +# .. [#NamkoongVariance] +# +# Hongseok Namkoong and John C. Duchi. +# "Variance-based regularization with convex objectives." +# `Advances in Neural Information Processing Systems +# `__, 2017. +# +# .. [#CongQuantumCNN] +# +# Iris Cong, Soonwon Choi, Mikhail D. Lukin. +# "Quantum Convolutional Neural Networks" +# `arxiv:1810.03787 `__, 2018. +# +# .. [#LeNailNNSVG] +# +# Alexander LeNail. +# "NN-SVG: Publication-Ready Neural Network Architecture Schematics" +# `Journal of Open Source Software `__, 2019. +# +# diff --git a/demonstrations_v2/tutorial_learning_few_data/metadata.json b/demonstrations_v2/tutorial_learning_few_data/metadata.json new file mode 100644 index 0000000000..0afe124f43 --- /dev/null +++ b/demonstrations_v2/tutorial_learning_few_data/metadata.json @@ -0,0 +1,87 @@ +{ + "title": "Generalization in QML from few training data", + "authors": [ + { + "username": "Qottmann" + }, + { + "username": "lmcalderon" + }, + { + "username": "mauriceweber" + } + ], + "dateOfPublication": "2022-08-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generalization_QML.png" + } + ], + "seoDescription": "Generalization of quantum machine learning models.", + "doi": "", + "references": [ + { + "id": "CaroGeneralization", + "type": "article", + "title": "Generalization in quantum machine learning from few training data", + "authors": "Matthias C. Caro, Hsin-Yuan Huang, M. Cerezo, Kunal Sharma, Andrew Sornborger, Lukasz Cincio, Patrick J. Coles", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2111.05292" + }, + { + "id": "DLBook", + "type": "book", + "title": "Deep Learning", + "authors": "Ian Goodfellow, Yoshua Bengio and Aaron Courville", + "year": "2016", + "journal": "", + "url": "http://www.deeplearningbook.org" + }, + { + "id": "NamkoongVariance", + "type": "article", + "title": "Variance-based regularization with convex objectives.", + "authors": "Hongseok Namkoong and John C. Duchi", + "year": "2017", + "journal": "Advances in Neural Information Processing Systems", + "url": "https://proceedings.neurips.cc/paper/2017/file/5a142a55461d5fef016acfb927fee0bd-Paper.pdf" + }, + { + "id": "CongQuantumCNN", + "type": "article", + "title": "Quantum Convolutional Neural Networks", + "authors": "Iris Cong, Soonwon Choi, Mikhail D. Lukin", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1810.03787" + }, + { + "id": "LeNailNNSVG", + "type": "article", + "title": "NN-SVG: Publication-Ready Neural Network Architecture Schematics", + "authors": "Alexander LeNail", + "year": "2019", + "journal": "Journal of Open Source Software", + "doi": "10.21105/joss.00747", + "url": "" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2111.05292" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_learning_few_data/requirements.in b/demonstrations_v2/tutorial_learning_few_data/requirements.in new file mode 100644 index 0000000000..75f3252d06 --- /dev/null +++ b/demonstrations_v2/tutorial_learning_few_data/requirements.in @@ -0,0 +1,9 @@ +jax +jaxlib +matplotlib +numpy +optax +pandas +pennylane +seaborn +scikit-learn diff --git a/demonstrations_v2/tutorial_learning_from_experiments/demo.py b/demonstrations_v2/tutorial_learning_from_experiments/demo.py new file mode 100644 index 0000000000..e719804eb3 --- /dev/null +++ b/demonstrations_v2/tutorial_learning_from_experiments/demo.py @@ -0,0 +1,539 @@ +r""" +Quantum advantage in learning from experiments +============================================== + +.. meta:: + :property="og:description": Learn how quantum memory can boost quantum machine learning algorithms + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/learning_from_exp_thumbnail.png + +*Author: Joseph Bowles — Posted: 18 April 2022. Last updated: 30 June 2022.* + +This demo is based on the article `Quantum advantage in learning from +experiments `__ `[1] <#ref1>`__ by +Hsin-Yuan Huang and co-authors. The article investigates the following +question: + +*How useful is access to quantum memory for quantum machine learning?* + +They show that access to quantum memory can make a big difference, and +prove that there exist learning problems for which algorithms with +quantum memory require *exponentially less resources* than those +without. We look at one learning task studied in `[1] <#ref1>`__ for +which this is the case. + +The learning task +----------------- + +The learning task we focus on involves deciding if a unitary is +time-reversal symmetric (we’ll call them T-symmetric) or not. +Mathematically, time-reversal symmetry in quantum mechanics involves +reversing the sense of :math:`i` so that :math:`i \rightarrow -i.` +Hence, a unitary :math:`U` is T-symmetric if + +.. math:: U^*=U. + +Now for the learning task. Let’s say we have a bunch of quantum circuits +:math:`U_1, \cdots, U_n,` some of which are T-symmetric and some not, +but we are not told which ones are which. + +""" + + +############################################################################## +# .. figure:: ../_static/demonstration_assets/learning_from_experiments/fig1b.png +# :align: center +# :width: 50% + + +###################################################################### +# The task is to design an algorithm to determine which of the +# :math:`U`\ ’s are T-symmetric and which are not, given query access to +# the unitaries. Note that we do not have any labels here, so this is an +# unsupervised learning task. To make things concrete, let’s consider +# unitaries acting on 8 qubits. We will also limit the number of times we +# can use each unitary: +# + +qubits = 8 # the number of qubits on which the unitaries act +n_shots = 100 # the number of times we can use each unitary + + +###################################################################### +# Experiments with and without a quantum memory +# --------------------------------------------- +# + + +###################################################################### +# To tackle this task we consider experiments with and without quantum +# memory. We also assume that we have access to a single physical +# realization of each unitary; in other words, we do not have multiple +# copies of the devices that implement :math:`U_i.` +# +# An experiment without quantum memory can therefore only make use of a +# single query to :math:`U_i` in each circuit, since querying :math:`U_i` +# again would require storing the state of the first query in memory and +# re-using the unitary. In the paper these experiments are called +# **conventional experiments**. +# +# Experiments with quantum memory do not have the limitations of +# conventional experiments. This means that multiple queries can be made +# to :math:`U_i` in a single circuit, which can be realized in practice by +# using a quantum memory. These experiments are called **quantum-enhanced +# experiments**. +# +# Note that we are not comparing classical and quantum algorithms here, +# but rather two classes of quantum algorithms. +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/learning_from_experiments/experiments.png +# :align: center +# :width: 60% + + +###################################################################### +# The conventional way +# -------------------- +# + + +###################################################################### +# First, we will try to solve the task with a conventional experiment. Our +# strategy will be as follows: +# +# - For each :math:`U_i,` we prepare ``n_shots`` copies of the state +# :math:`U_i\vert0\rangle` and measure each state to generate +# classical measurement data. +# - Use an unsupervised classical machine learning algorithm (kernel +# PCA), to try and separate the data into two clusters corresponding to +# T-symmetric unitaries vs. the rest. +# +# If we succeed in clustering the data then we have successfully managed +# to discriminate the two classes! +# + + +############################################################################## +# .. figure:: ../_static/demonstration_assets/learning_from_experiments/fig2b.png +# :align: center +# :width: 70% + + +###################################################################### +# To generate the measurement data, we will measure the states +# :math:`U_i\vert0\rangle` in the :math:`y` basis. The local expectation +# values take the form +# +# .. math:: E_i = \langle 0\vert U^{\dagger}\sigma_y^{(i)} U \vert 0 \rangle. +# +# where :math:`\sigma_y^{(i)}` acts on the :math:`i^{\text{th}}` qubit. +# +# Using the fact that :math:`\sigma_y^*=-\sigma_y` and the property +# :math:`U^*=U` for T-symmetric unitaries, one finds +# +# .. math:: E_i^*=\langle 0\vert (U^{\dagger})^*(\sigma_y^{(i)})^* (U)^* \vert 0 \rangle = - \langle 0\vert U^{\dagger}\sigma_y^{(i)} U \vert 0 \rangle = - E_i. +# +# Since :math:`E_i` is a real number, the only solution to this is +# :math:`E_i=0,` which implies that all local expectations values are 0 +# for this class. +# +# For general unitaries it is not the case that :math:`E_i=0,` and so it +# seems as though this will allow us to discriminate the two classes of +# circuits easily. However, for general random unitaries the local +# expectation values approach zero exponentially with the number of +# qubits: from finite measurement data it can still be very hard to see +# any difference! In fact, in the article `exponential separations between +# learning with and without quantum +# memory `__ `[2] <#ref2>`__ it is +# proven that using conventional experiments, any successful algorithm +# *must* use the unitaries an exponential number of times. +# + + +###################################################################### +# Let’s see how this looks in practice. First we define a function to +# generate random unitaries, making use of Pennylane’s +# `RandomLayers `__ +# template. For the time-symmetric case we will only allow for Y +# rotations, since these unitaries contain only real numbers, and +# therefore result in T-symmetric unitaries. For the other unitaries, we +# will allow rotations about X,Y, and Z. +# + +import pennylane as qml +from pennylane.templates.layers import RandomLayers +import numpy as np + +np.random.seed(234087) + +layers, gates = 10, 10 # the number of layers and gates used in RandomLayers + + +def generate_circuit(shots): + """ + generate a random circuit that returns a number of measuement samples + given by shots + """ + dev = qml.device("lightning.qubit", wires=qubits, shots=shots) + + @qml.qnode(dev) + def circuit(ts=False): + + if ts == True: + ops = [qml.RY] # time-symmetric unitaries + else: + ops = [qml.RX, qml.RY, qml.RZ] # general unitaries + + weights = np.random.rand(layers, gates) * np.pi + RandomLayers(weights, wires=range(qubits), rotations=ops, seed=np.random.randint(0, 10000)) + + return [qml.sample(op=qml.PauliY(q)) for q in range(qubits)] + + return circuit + + +###################################################################### +# let’s check if that worked: +# + +# the measurement outcomes for the first 3 shots +circuit = generate_circuit(n_shots) +print(np.array(circuit(ts=True))[:, 0:3]) +print("\n") +print(np.array(circuit(ts=False))[:, 0:3]) + + +###################################################################### +# Now we can generate some data. The first 30 circuits in the data set are +# T-symmetric and the second 30 circuits are not. Since we are in an +# unsupervised setting, the algorithm will not know this information. +# + +circuits = 30 # the number of circuits in each data set + +raw_data = [] + +for ts in [True, False]: + for __ in range(circuits): + circuit = generate_circuit(n_shots) + raw_data.append(circuit(ts=ts)) + + +###################################################################### +# Before feeding the data to a clustering algorithm, we will process it a +# little. For each circuit, we calculate the mean and the variance of each +# output bit and store this in a vector of size ``2*qubits``. These +# vectors make up our classical data set. +# + + +def process_data(raw_data): + "convert raw data to vectors of means and variances of each qubit" + + raw_data = np.array(raw_data) + nc = len(raw_data) # the number of circuits used to generate the data + nq = len(raw_data[0]) # the number of qubits in each circuit + new_data = np.zeros([nc, 2 * nq]) + + for k, outcomes in enumerate(raw_data): + means = [np.mean(outcomes[q, :]) for q in range(nq)] + variances = [np.var(outcomes[q, :]) for q in range(nq)] + new_data[k] = np.array(means + variances) + + return new_data + + +data = process_data(raw_data) + + +###################################################################### +# Now we use scikit-learn’s `kernel +# PCA `__ +# package to try and cluster the data. This performs principal component +# analysis in a high dimensional feature space defined by a kernel (below +# we use the radial basis function kernel). +# + +from sklearn.decomposition import KernelPCA +from sklearn import preprocessing + +kernel_pca = KernelPCA( + n_components=None, kernel="rbf", gamma=None, fit_inverse_transform=True, alpha=0.1 +) + +# rescale the data so it has unit standard deviation and zero mean. +scaler = preprocessing.StandardScaler().fit(data) +data = scaler.transform(data) +# try to cluster the data +fit = kernel_pca.fit(data).transform(data) + + +###################################################################### +# Let’s plot the result. Here we look at the first two principal +# components. +# + +import matplotlib.pyplot as plt + +# make a colour map for the points +c = np.array([0 for __ in range(circuits)] + [1 for __ in range(circuits)]) + +plt.scatter(fit[:, 0], fit[:, 1], c=c) +plt.show() + + +###################################################################### +# Looks like the algorithm failed to cluster the data. We can try to get a +# separation by increasing the number of shots. Let’s increase the number +# of shots by 100 and see what happens. +# + +n_shots = 10000 # 100 x more shots + +raw_data = [] + +for ts in [True, False]: + for __ in range(circuits): + circuit = generate_circuit(n_shots) + raw_data.append(circuit(ts=ts)) + +data = process_data(raw_data) +scaler = preprocessing.StandardScaler().fit(data) +data = scaler.transform(data) + +fit = kernel_pca.fit(data).transform(data) + +plt.scatter(fit[:, 0], fit[:, 1], c=c) +plt.show() + +###################################################################### +# Now we have a separation, however we required a lot of shots from the +# quantum circuit. As we increase the number of qubits, the number of +# shots we need will scale exponentially (as shown in `[2] <#ref2>`__), +# and so conventional strategies cannot learn to separate the data +# efficiently. +# + + +###################################################################### +# The quantum-enhanced way +# ------------------------ +# +# Now let’s see what difference having a quantum memory can make. Instead +# of using a single unitary to generate measurement data, we will make use +# of twice the number of qubits, and apply the unitary twice: +# + + +############################################################################## +# .. figure:: ../_static/demonstration_assets/learning_from_experiments/fig3b.png +# :align: center +# :width: 70% + +###################################################################### +# In practice, this could be done by storing the output state from the +# first unitary in quantum memory and preparing the same state by using +# the unitary again. Let’s define a function ``enhanced_circuit()`` to +# implement that. Note that since we now have twice as many qubits, we use +# half the number of shots as before so that the total number of uses of +# the unitary is unchanged. +# + +n_shots = 50 +qubits = 8 + +dev = qml.device("lightning.qubit", wires=qubits * 2, shots=n_shots) + + +def CNOT_sequence(control_wires, target_wires): + """Apply CNOTs in sequence using the provided control and target wires""" + for c_wire, t_wire in zip(control_wires, target_wires): + qml.CNOT([c_wire, t_wire]) + + +@qml.qnode(dev) +def enhanced_circuit(ts=False): + "implement the enhanced circuit, using a random unitary" + + if ts == True: + ops = [qml.RY] + else: + ops = [qml.RX, qml.RY, qml.RZ] + + weights = np.random.rand(layers, n_shots) * np.pi + seed = np.random.randint(0, 10000) + + for q in range(qubits): + qml.Hadamard(wires=q) + + CNOT_sequence(control_wires=range(qubits), target_wires=range(qubits, 2 * qubits)) + RandomLayers(weights, wires=range(0, qubits), rotations=ops, seed=seed) + RandomLayers(weights, wires=range(qubits, 2 * qubits), rotations=ops, seed=seed) + CNOT_sequence(control_wires=range(qubits), target_wires=range(qubits, 2 * qubits)) + + for q in range(qubits): + qml.Hadamard(wires=q) + + return [qml.sample(op=qml.PauliZ(q)) for q in range(2 * qubits)] + + +###################################################################### +# Now we generate some raw measurement data, and calculate the mean and +# variance of each qubit as before. Our data vectors are now twice as long +# since we have twice the number of qubits. +# + +raw_data = [] + +for ts in [True, False]: + for __ in range(circuits): + raw_data.append(enhanced_circuit(ts)) + +data = process_data(raw_data) + + +###################################################################### +# Let’s throw that into Kernel PCA again and plot the result. +# + +kernel_pca = KernelPCA( + n_components=None, kernel="rbf", gamma=None, fit_inverse_transform=True, alpha=0.1 +) + +scaler = preprocessing.StandardScaler().fit(data) +data = scaler.transform(data) + +fit = kernel_pca.fit(data).transform(data) + +c = np.array([0 for __ in range(circuits)] + [1 for __ in range(circuits)]) +plt.scatter(fit[:, 0], fit[:, 1], c=c) +plt.show() + + +###################################################################### +# Kernel PCA has perfectly separated the two classes! In fact, all the +# T-symmetric unitaries have been mapped to the same point. This is +# because the circuit is actually equivalent to performing +# :math:`U^TU\otimes \mathbb{I}\vert 0 \rangle,` which for T-symmetric +# unitaries is just the identity operation. +# +# To see this, note that the Hadamard and CNOT gates before +# :math:`U_i\otimes U_i` map the :math:`\vert0\rangle` state to the +# maximally entanged state +# :math:`\vert \Phi^+\rangle = \frac{1}{\sqrt{2}}(\vert 00...0\rangle+ \vert11...1\rangle,` +# and the gates after :math:`U_i\otimes U_i` are just the inverse +# transformation. The probability that all measurement outcomes give the +# result :math:`+1` is therefore. +# +# .. math:: p(11\cdots 1) = \langle \Phi^+ \vert U_i \otimes U_i \vert\Phi^+ \rangle. +# +# A well known fact about the maximally entanged state is that +# :math:`U\otimes \mathbb{I}\vert\Phi^+\rangle= \mathbb{I}\otimes U^T\vert\Phi^+\rangle.` +# The probabilty is therefore +# +# .. math:: p(11\cdots 1) = \langle \Phi^+ \vert U_i^T U_i \otimes \mathbb{I} \vert\Phi^+ \rangle. +# +# For T-symmetric unitaries :math:`U_i^T=U_i^\dagger,` so this probability +# is equal to one: the :math:`11\cdots 1` outcome is always obtained. +# +# If we look at the raw measurement data for the T-symmetric unitaries: +# + +np.array(raw_data[0])[:, 0:5] # outcomes of first 5 shots of the first T-symmetric circuit + + +###################################################################### +# We see that indeed this is the only measurement outcome. +# +# To make things a bit more interesting, let’s add some noise to the +# circuit. We will define a function ``noise_layer(epsilon)`` that adds +# some random single qubit rotations, where the maximum rotation angle is +# ``epsilon``. +# + + +def noise_layer(epsilon): + "apply a random rotation to each qubit" + for q in range(2 * qubits): + angles = (2 * np.random.rand(3) - 1) * epsilon + qml.Rot(angles[0], angles[1], angles[2], wires=q) + + +###################################################################### +# We redefine our ``enhanced_circuit()`` function with a noise layer +# applied after the unitaries +# + + +@qml.qnode(dev) +def enhanced_circuit(ts=False): + "implement the enhanced circuit, using a random unitary with a noise layer" + + if ts == True: + ops = [qml.RY] + else: + ops = [qml.RX, qml.RY, qml.RZ] + + weights = np.random.rand(layers, n_shots) * np.pi + seed = np.random.randint(0, 10000) + + for q in range(qubits): + qml.Hadamard(wires=q) + + CNOT_sequence(control_wires=range(qubits), target_wires=range(qubits, 2 * qubits)) + RandomLayers(weights, wires=range(0, qubits), rotations=ops, seed=seed) + RandomLayers(weights, wires=range(qubits, 2 * qubits), rotations=ops, seed=seed) + noise_layer(np.pi / 4) # added noise layer + CNOT_sequence(control_wires=range(qubits, 2 * qubits), target_wires=range(qubits)) + + for q in range(qubits): + qml.Hadamard(wires=qubits + q) + + return [qml.sample(op=qml.PauliZ(q)) for q in range(2 * qubits)] + + +###################################################################### +# Now we generate the data and feed it to kernel PCA again. +# + +raw_data = [] + +for ts in [True, False]: + for __ in range(circuits): + raw_data.append(enhanced_circuit(ts)) + +data = process_data(raw_data) + +kernel_pca = KernelPCA( + n_components=None, kernel="rbf", gamma=None, fit_inverse_transform=True, alpha=0.1 +) +scaler = preprocessing.StandardScaler().fit(data) +data = scaler.transform(data) +fit = kernel_pca.fit(data).transform(data) + +c = np.array([0 for __ in range(circuits)] + [1 for __ in range(circuits)]) +plt.scatter(fit[:, 0], fit[:, 1], c=c) +plt.show() + + +###################################################################### +# Nice! Even in the presence of noise we still have a clean separation of +# the two classes. This shows that using entanglement can make a big +# difference to learning. +# + + +###################################################################### +# References +# ---------- +# +# [1] *Quantum advantage in learning from experiments*, Hsin-Yuan Huang +# et. al., `arxiv:2112.00778 `__ +# (2021) +# +# [2] *Exponential separations between learning with and without quantum +# memory*, Sitan Chen, Jordan Cotler, Hsin-Yuan Huang, Jerry Li, +# `arxiv:2111.05881 `__ (2021) +# +# diff --git a/demonstrations_v2/tutorial_learning_from_experiments/metadata.json b/demonstrations_v2/tutorial_learning_from_experiments/metadata.json new file mode 100644 index 0000000000..5d83de727e --- /dev/null +++ b/demonstrations_v2/tutorial_learning_from_experiments/metadata.json @@ -0,0 +1,48 @@ +{ + "title": "Quantum advantage in learning from experiments", + "authors": [ + { + "username": "josephbowles" + } + ], + "dateOfPublication": "2022-04-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_advantage_learning_from_experiments.png" + } + ], + "seoDescription": "Learn how quantum memory can boost quantum machine learning algorithms", + "doi": "", + "references": [ + { + "id": "Huang2021", + "type": "article", + "title": "Quantum advantage in learning from experiments", + "authors": "Hsin-Yuan Huang et al.", + "year": "2021", + "journal": "", + "doi": "10.1126/science.abn7293", + "url": "https://arxiv.org/pdf/2112.00778.pdf" + }, + { + "id": "Chen2021", + "type": "article", + "title": "Exponential separations between learning with and without quantum memory", + "authors": "Sitan Chen, Jordan Cotler, Hsin-Yuan Huang, Jerry Li", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2111.05881" + } + ], + "basedOnPapers": [ + "10.1126/science.abn7293" + ], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_learning_from_experiments/requirements.in b/demonstrations_v2/tutorial_learning_from_experiments/requirements.in new file mode 100644 index 0000000000..c18ba892e9 --- /dev/null +++ b/demonstrations_v2/tutorial_learning_from_experiments/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scikit-learn diff --git a/demonstrations_v2/tutorial_learningshallow/demo.py b/demonstrations_v2/tutorial_learningshallow/demo.py new file mode 100644 index 0000000000..38cd6d014a --- /dev/null +++ b/demonstrations_v2/tutorial_learningshallow/demo.py @@ -0,0 +1,465 @@ +r"""Learning shallow quantum circuits with local inversions and circuit sewing +============================================================================== + +In a recent paper `Learning shallow quantum circuits `_ [#Huang]_, Huang et al +introduce and prove performance bounds on efficient algorithms to learn constant depth circuits. +At the heart of the paper lie local inversions that locally undo a quantum circuit, as well as a circuit "sewing" +technique that lets one construct a global inversion from those. +We are going to review these new concepts and showcase them with an implementation in PennyLane. + +Introduction +------------ + +Shallow, constant depth quantum circuits are provably powerful [#Bravyi]_. +At the same time, they are known to be difficult to train [#Anschuetz]_. +The authors of [#Huang]_ tackle the question of whether or not shallow circuits are efficiently learnable. + + +Given some unknown unitary circuit :math:`U,` learning the circuit constitutes finding a unitary :math:`V` that faithfully resembles :math:`U's` action. +This can be either fully performing the same operation (:math:`U V^\dagger = 1`) or resembling the action on a fixed input state +(:math:`U |\phi\rangle = V |\phi\rangle,` where often :math:`|\phi\rangle = |0 \rangle^{\otimes n}`). +The authors go through both scenarios with different levels of restrictions on the allowed gate set and locality of the target circuit :math:`U.` +In this demo, we are mainly going to focus on learning the action on :math:`|0 \rangle^{\otimes n},` i.e. :math:`U |0\rangle^{\otimes n} = V |0\rangle^{\otimes n}.` + + +At the heart of the solutions to all these scenarios lies the use of local inversions that undo the effect of the unitary, +and sewing them together to form a global inversion. + +Local Inversions +---------------- + +A local inversion is a unitary circuit that locally disentangles one qubit after a previous, different unitary entangled them. Let us +make an explicit example in PennyLane after some boilerplate imports. Let us look at a very shallow unitary circuit +:math:`U^\text{test} = \text{CNOT}_{(0, 1)}\text{CNOT}_{(2, 3)}\text{CNOT}_{(1, 2)} H^{\otimes n}.` +""" +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +def U_test(): + for i in range(4): + qml.Hadamard(i) + qml.CNOT((1, 2)) + qml.CNOT((2, 3)) + qml.CNOT((0, 1)) + +qml.draw_mpl(U_test)() +plt.show() + + +############################################################################## +# We now want to locally invert it. That is, we want to apply a second unitary +# :math:`V_0` such that :math:`\text{tr}_{\neq 0} \left[V_0 U (|0 \rangle \langle 0|)^{\otimes n} U^\dagger V^\dagger_0\right] = |0 \rangle \langle 0|_0,` +# where we trace out all but wire ``0``. +# For that, we just follow the light-cone of the qubit that we want to invert +# and perform the inverse operations in reverse order in :math:`V_0.` + +def V_0(): + qml.CNOT((0, 1)) + qml.CNOT((1, 2)) + qml.Hadamard(0) + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def local_inversion(): + U_test() # some shallow unitary circuit + V_0() # supposed to disentangle qubit 0 + return qml.density_matrix(wires=[0]) + +print(np.allclose(local_inversion(), np.array([[1., 0.], [0., 0.]]))) + +############################################################################## +# After performing :math:`U^\text{test}` and the inversion of qubit +# :math:`0`, :math:`V_0,` we find the reduced state :math:`|0 \rangle \langle 0|` on the correct qubit. +# +# Local inversions are not unique and finding one is easier than finding global inversions. +# But constructing a global inversion from all possible local inversions is highly non-trivial +# (as it constitutes a variant of the `quantum marginal problem `_). +# However, the circuit sewing technique introduced in [#Huang]_ lets us circumvent that problem and construct +# a global inversion from just a single local inversion per qubit. +# +# To do that, we construct the other local inversions in the same way as before by just following +# back the light-cones of the respective qubits. In general these would have to be learned, more on that later. Here we just +# reverse-engineer them from knowing :math:`U^\text{test}.` + +def V_1(): + qml.CNOT((0, 1)) + qml.CNOT((1, 2)) + qml.Hadamard(1) + +def V_2(): + qml.CNOT((2, 3)) + qml.CNOT((1, 2)) + qml.Hadamard(2) + +def V_3(): + qml.CNOT((2, 3)) + qml.CNOT((1, 2)) + qml.Hadamard(3) + + +############################################################################## +# Circuit Sewing +# -------------- +# +# So how does knowing local inversions :math:`\{V_0, V_1, V_2, V_3\}` help us with solving the original goal of finding a global inversion :math:`U V = \mathbb{1}?` +# The authors introduce a clever trick that they coin `circuit sewing`. It works by swapping out the decoupled qubit with an ancilla register and restoring ("repairing") the unitary on the remaining wires. +# Let us walk through this process step by step. +# +# We already saw how to decouple qubit :math:`0` in ``local_inversion()`` above. We continue by swapping out +# the decoupled wire with an ancilla qubit and "repairing" the circuit by applying :math:`V^\dagger_0.` +# (This is called "repairing" because :math:`V_1` can now decouple qubit 1, which would not be possible in general without that step.) +# We label all ancilla wires by ``[n + 0, n + 1, .. 2n-1]`` to have an easy 1-to-1 correspondence and we see that qubit ``1`` is successfully decoupled. +# For completeness, we also check that the swapped out qubit (now moved to wire ``n + 0``) is decoupled still. +# + +n = 4 # number of qubits + +@qml.qnode(dev) +def sewing_1(): + U_test() # some shallow unitary circuit + qml.Barrier() + V_0() # disentangle qubit 0 + qml.Barrier() + qml.SWAP((0, n)) # swap out disentangled qubit 0 and n+0 + qml.Barrier() + qml.adjoint(V_0)() # repair circuit from V_0 + qml.Barrier() + V_1() # disentangle qubit 1 + return qml.density_matrix(wires=[1]), qml.density_matrix(wires=[n]) + +# The Barriers are to see which part of the circuit corresponds to which gate +qml.draw_mpl(sewing_1)() +plt.show() + +r1, rn = sewing_1() +print(f"Sewing qubit 1") +print(f"rho_1 = |0⟩⟨0| {np.allclose(r1, np.array([[1, 0], [0, 0]]))}") +print(f"rho_0+n = |0⟩⟨0| {np.allclose(rn, np.array([[1, 0], [0, 0]]))}") + +############################################################################## +# We can continue this process for all qubits. Let us be tedious and do all steps one by one. + +@qml.qnode(dev) +def sewing_2(): + U_test() # some shallow unitary circuit + V_0() # disentangle qubit 0 + qml.SWAP((0, n)) # swap out disentangled qubit 0 and n+0 + qml.adjoint(V_0)() # repair circuit from V_0 + V_1() # disentangle qubit 1 + qml.SWAP((1, n + 1)) # swap out disentangled qubit 1 to n+1 + qml.adjoint(V_1)() # repair circuit from V_1 + V_2() # disentangle qubit 2 + return qml.density_matrix(wires=[2]), qml.density_matrix(wires=[n]), qml.density_matrix(wires=[n + 1]) + +r2, rn, rn1 = sewing_2() +print(f"Sewing qubit 2") +print(f"rho_2 = |0⟩⟨0| {np.allclose(r2, np.array([[1, 0], [0, 0]]))}") +print(f"rho_0+n = |0⟩⟨0| {np.allclose(rn, np.array([[1, 0], [0, 0]]))}") +print(f"rho_1+n = |0⟩⟨0| {np.allclose(rn1, np.array([[1, 0], [0, 0]]))}") + +############################################################################## +# We continue to show that the swapped out wires remain decoupled, as well as the qubit we are currently decoupling. + +@qml.qnode(dev) +def sewing_3(): + U_test() # some shallow unitary circuit + V_0() # disentangle qubit 0 + qml.SWAP((0, n)) # swap out disentangled qubit 0 and n+0 + qml.adjoint(V_0)() # repair circuit from V_0 + V_1() # disentangle qubit 1 + qml.SWAP((1, n + 1)) # swap out disentangled qubit 1 to n+1 + qml.adjoint(V_1)() # repair circuit from V_1 + V_2() # disentangle qubit 2 + qml.SWAP((2, n + 2)) # swap out disentangled qubit 2 to n+2 + qml.adjoint(V_2)() # repair circuit from V_2 + V_3() # disentangle qubit 3 + return qml.density_matrix(wires=[3]), qml.density_matrix(wires=[n]), qml.density_matrix(wires=[n + 1]), qml.density_matrix(wires=[n + 2]) + +r3, rn, rn1, rn2 = sewing_3() +print(f"Sewing qubit 3") +print(f"rho_3 = |0⟩⟨0| {np.allclose(r3, np.array([[1, 0], [0, 0]]))}") +print(f"rho_0+n = |0⟩⟨0| {np.allclose(rn, np.array([[1, 0], [0, 0]]))}") +print(f"rho_1+n = |0⟩⟨0| {np.allclose(rn1, np.array([[1, 0], [0, 0]]))}") +print(f"rho_2+n = |0⟩⟨0| {np.allclose(rn2, np.array([[1, 0], [0, 0]]))}") + +############################################################################## +# After one final swap and repair, we arrive at a state where all original qubits are decoupled. We just need to move them back to their original position +# with a global SWAP. +# But not just that, we also now know that, globally, the original :math:`U^\text{test}` is inverted. + +@qml.qnode(dev) +def V_dagger_test(): + U_test() # some shallow unitary circuit + V_0() # disentangle qubit 0 + qml.SWAP((0, n)) # swap out disentangled qubit 0 and n+0 + qml.adjoint(V_0)() # repair circuit from V_0 + V_1() # disentangle qubit 1 + qml.SWAP((1, n + 1)) # swap out disentangled qubit 1 to n+1 + qml.adjoint(V_1)() # repair circuit from V_1 + V_2() # disentangle qubit 2 + qml.SWAP((2, n + 2)) # swap out disentangled qubit 2 to n+2 + qml.adjoint(V_2)() # repair circuit from V_2 + V_3() # disentangle qubit 3 + qml.SWAP((3, n + 3)) # swap out disentangled qubit 3 to n+3 + qml.adjoint(V_3)() # repair circuit from V_3 + for i in range(n): # swap back all decoupled wires to their original registers + qml.SWAP((i + n, i)) + return qml.density_matrix([0, 1, 2, 3]) + +psi0 = np.eye(2**4)[0] # |0>^n +np.allclose(V_dagger_test(), np.outer(psi0, psi0)) + +############################################################################## +# +# Everything after ``U_test()`` in ``V_dagger_test`` constitutes :math:`(V^\text{sew})^\dagger.` + +def V_dagger(): + V_0() # disentangle qubit 0 + qml.SWAP((0, n)) # swap out disentangled qubit 0 and n+0 + qml.adjoint(V_0)() # repair circuit from V_0 + V_1() # disentangle qubit 1 + qml.SWAP((1, n + 1)) # swap out disentangled qubit 1 to n+1 + qml.adjoint(V_1)() # repair circuit from V_1 + V_2() # disentangle qubit 2 + qml.SWAP((2, n + 2)) # swap out disentangled qubit 2 to n+2 + qml.adjoint(V_2)() # repair circuit from V_2 + V_3() # disentangle qubit 3 + qml.SWAP((3, n + 3)) # swap out disentangled qubit 3 to n+3 + qml.adjoint(V_3)() # repair circuit from V_3 + for i in range(n): # swap back all decoupled wires to their original registers + qml.SWAP((i + n, i)) + +############################################################################## +# It is such that the action of :math:`U^\text{test}` on :math:`|0 \rangle^{\otimes n}` is reverted when tracing out the ancilla qubits. +# From the paper we know that, in fact, the action of the sewn :math:`V^\text{sew}` overall is +# +# .. math:: V^\text{sew} |0^{\otimes 2n}\rangle = U \otimes U^\dagger |0^{\otimes 2n}\rangle. +# +# :math:`U` acts on the first ``n`` qubits, whereas :math:`U^\dagger` acts on the ``n`` ancilla qubits. + + +############################################################################## +# +# Numerical Experiment +# -------------------- +# +# The actual learning in this procedure happens in obtaining the local inversions :math:`\{V_0, V_1, V_2, V_3\}.` +# The paper relies on existence proofs from gate synthesis [#Shende]_ and suggests brute-force searching via enumerate-and-test to find suitable :math:`\{V_i\}.` +# The idea is to essentially take a big enough set of possible :math:`\{V_i\}` and post-select those that fulfill +# :math:`||V^\dagger_i U^\dagger P_i U V_i - P_i|| < \epsilon` for :math:`P_i \in \{X, Y, Z\},` which the authors +# show suffices as a criterium to have an approximate local inversion. +# +# Instead of doing that, let us here look at an explicit example by constructing a target unitary of some structure and a variational Ansatz for +# the local inversions that has a different structure. There are many ways to obtain local inversions, this is just one we find more convenient. +# +# First, let us construct the target unitary + +import jax +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") + +n = 4 +wires = range(n) +U_params = jax.random.normal(jax.random.PRNGKey(0), shape=(2, n//2), dtype=float) + +def U_target(wires): + for i in range(n): + qml.Hadamard(wires=wires[i]) + # brick-wall ansatz + for i in range(0, n, 2): + qml.IsingXX(U_params[0, i], wires=(wires[i], wires[(i+1)%len(wires)])) + for i in range(1, n, 2): + qml.IsingXX(U_params[1, i], wires=(wires[i], wires[(i+1)%len(wires)])) + +qml.draw_mpl(U_target)(wires) +plt.show() + +############################################################################## +# Putting on blindfolds and assuming we don't know the circuit structure of :math:`U^\text{target},` we set up a variational Ansatz for the local inversions :math:`V_i` with the following structure. + +n_layers = 2 + +def V_i(params, wires): + + for i in range(n): + qml.RX(params[0, 0, i], i) + for i in range(n): + qml.RY(params[0, 1, i], i) + + for ll in range(n_layers): + for i in range(0, n, 2): + qml.CNOT((wires[i], wires[i+1])) + for i in range(1, n, 2): + qml.CNOT((wires[i], wires[(i+1)%n])) + for i in range(n): + qml.RX(params[ll+1, 0, i], i) + for i in range(n): + qml.RY(params[ll+1, 1, i], i) + +params = jax.random.normal(jax.random.PRNGKey(10), shape=(n_layers+1, 2, n), dtype=float) + +qml.draw_mpl(V_i)(params, wires) +plt.show() + +############################################################################## +# Next, we are going to run optimizations for each :math:`V_i` to find a local inversion. +# For that we need some boilerplate code, see our :doc:`demo ` +# on optimizing quantum circuits in jax. + +import optax +from datetime import datetime +from functools import partial + +X, Y, Z = qml.PauliX, qml.PauliY, qml.PauliZ + +def run_opt(value_and_grad, theta, n_epochs=100, lr=0.1, b1=0.9, b2=0.999): + + optimizer = optax.adam(learning_rate=lr, b1=b1, b2=b2) + opt_state = optimizer.init(theta) + + energy = np.zeros(n_epochs) + thetas = [] + + @jax.jit + def step(opt_state, theta): + val, grad_circuit = value_and_grad(theta) + updates, opt_state = optimizer.update(grad_circuit, opt_state) + theta = optax.apply_updates(theta, updates) + + return opt_state, theta, val + + t0 = datetime.now() + ## Optimization loop + for n in range(n_epochs): + opt_state, theta, val = step(opt_state, theta) + + energy[n] = val + thetas.append( + theta + ) + t1 = datetime.now() + print(f"final loss: {val}; min loss: {np.min(energy)}; after {t1 - t0}") + + return thetas, energy + +dev = qml.device("default.qubit") + +############################################################################## +# As a cost function, we perform state tomography after applying :math:`U^\text{target}` and our Ansatz :math:`V_i.` +# Our aim is to bring the state on qubit ``i`` back to the north pole of the Bloch sphere, and we specify our cost function accordingly. + +@qml.qnode(dev, interface="jax") +def qnode_i(params, i): + U_target(wires) + V_i(params, wires) + return [qml.expval(P(i)) for P in [X, Y, Z]] + +@partial(jax.jit, static_argnums=1) +@jax.value_and_grad +def cost_i(params, i): + x, y, z = qnode_i(params, i) + return x**2 + y**2 + (1-z)**2 + +############################################################################## +# We can now run the optimization. We see that in that case it suffices to use the random initial values from above for each optimization. + +params_i = [] +for i in range(n): + cost = partial(cost_i, i=i) + thetas, _ = run_opt(cost, params) + params_i.append(thetas[-1]) + +############################################################################## +# For consistency, we check the resulting coordinates of qubit ``i`` on the Bloch sphere. + +for i in range(n): + X_res, Y_res, Z_res = qnode_i(params_i[i], i) + print(f"Bloch sphere coordinates of qubit {i} after inversion: {X_res:.5f}, {Y_res:.5f} {Z_res:.5f}") + +############################################################################## +# We see that they are all approximately inverting the circuit as the resulting state is close to :math:`|0\rangle` (associated with coordinates :math:`(x, y, z) = (0, 0, 1)`). +# With these local inversions, we can sew together again a unitary that globally inverts the circuit. + +def V_sew(): + for i in range(n): + # local sewing: inversion, exchange, heal + V_i(params_i[i], range(n)) + qml.SWAP((i, i+n)) + qml.adjoint(V_i)(params_i[i], range(n)) + + # global SWAP + for i in range(n): + qml.SWAP((i, i+n)) + +@qml.qnode(dev, interface="jax") +def sewing_test(): + U_target(range(n)) + V_sew() + return qml.density_matrix(range(4)) + +print(np.allclose(sewing_test(), np.outer(psi0, psi0), atol=1e-1)) + +############################################################################## +# The final test confirms that :math:`V^\text{sew}` approximately inverts :math:`U^\text{target}` on the system wires. +# +# Conclusion +# ---------- +# +# We saw how one can construct a global inversion from sewing together local inversions. This is a powerful new technique +# that may find applications in different domains of quantum computing. +# The technique cleverly circumvents the +# quantum marginal problem of constructing a global inversion from local ones compatible with each other. +# +# The authors use this technique to prove that constant depth quantum circuits are learnable (i.e. can be reconstructed) in a variety of different scenarios. +# +# .. note:: +# We mainly focussed on the case of constructing :math:`V^\text{sew}` such that :math:`V^\text{sew} U |0 \rangle^{\otimes n} = |0 \rangle^{\otimes n}` as it already +# nicely captures the main technical method that is circuit sewing. This is different to learning the full unitary, i.e. :math:`V` such that :math:`U V = 1.` +# +# For this, the circuit sewing works in the exact same way. The main difference is that the local inversions are now full inversions in the sense of +# :math:`\text{tr}_{\neq 0}\left[V_i U\right] = \mathbb{1}_i` (whereas before we just had :math:`V_i U |0 \rangle^{\otimes n} = |0 \rangle^{\otimes n},` which is a simpler case). +# The authors show that a sufficient condition for full inversion is achieved by minimizing +# +# .. math:: \sum_{P\in \{X, Y, Z\}} ||V^\dagger_i U^\dagger P_i U V_i - P_i ||. +# +# In the paper, the authors suggest to brute-force search the whole space of possible :math:`V_i` and post-select those for which the distance to :math:`P_i` is small. +# The terms are evaluated by randomly sampling input (product) states :math:`|\phi_j\rangle` and computing expectation values of :math:`\langle \phi_j | V^\dagger_i U^\dagger P_i U V_i |\phi_j\rangle.` +# In particular, samples for all possible candidates of :math:`V_i` are generated. +# Another possibility is to perform state tomography of the single qubit states and compare that with the input state. +# Either way, the circuit sewing after obtaining the learned local inversions is the same as described above. + + + +############################################################################## +# +# References +# ---------- +# +# .. [#Huang] +# +# Hsin-Yuan Huang, Yunchao Liu, Michael Broughton, Isaac Kim, Anurag Anshu, Zeph Landau, Jarrod R. McClean +# "Learning shallow quantum circuits" +# `arXiv:2401.10095 `__, 2024. +# +# .. [#Bravyi] +# +# Sergey Bravyi, David Gosset, Robert Koenig +# "Quantum advantage with shallow circuits" +# `arXiv:1704.00690 `__, 2017. +# +# .. [#Anschuetz] +# +# Eric R. Anschuetz, Bobak T. Kiani +# "Beyond Barren Plateaus: Quantum Variational Algorithms Are Swamped With Traps" +# `arXiv:2205.05786 `__, 2022. +# +# .. [#Shende] +# +# Vivek V. Shende, Stephen S. Bullock, Igor L. Markov +# "Synthesis of Quantum Logic Circuits" +# `arXiv:quant-ph/0406176 `__, 2004. +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_learningshallow/metadata.json b/demonstrations_v2/tutorial_learningshallow/metadata.json new file mode 100644 index 0000000000..5d97f2e6c4 --- /dev/null +++ b/demonstrations_v2/tutorial_learningshallow/metadata.json @@ -0,0 +1,85 @@ +{ + "title": "Learning shallow quantum circuits with local inversions and circuit sewing", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-01-24T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Computing", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/learningshallow/thumbnail_learningshallow.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_learningshallow.png" + } + ], + "seoDescription": "Learn how to do circuit sewing with local inversions, which play a crucial role in learning shallow quantum circuits", + "doi": "", + "references": [ + { + "id": "Huang", + "type": "preprint", + "title": "Learning shallow quantum circuits", + "authors": "Hsin-Yuan Huang, Yunchao Liu, Michael Broughton, Isaac Kim, Anurag Anshu, Zeph Landau, Jarrod R. McClean", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2401.10095", + "url": "https://arxiv.org/abs/2401.10095" + }, + { + "id": "Bravyi", + "type": "preprint", + "title": "Sergey Bravyi, David Gosset, Robert Koenig", + "authors": "Quantum advantage with shallow circuits", + "year": "2017", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1704.00690", + "url": "https://arxiv.org/abs/1704.00690" + }, + { + "id": "Anschuetz", + "type": "preprint", + "title": "Beyond Barren Plateaus: Quantum Variational Algorithms Are Swamped With Traps", + "authors": "Eric R. Anschuetz, Bobak T. Kiani", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2205.05786", + "url": "https://arxiv.org/abs/2205.05786" + }, + { + "id": "Shende", + "type": "article", + "title": "Synthesis of Quantum Logic Circuits", + "authors": "Vivek V. Shende, Stephen S. Bullock, Igor L. Markov", + "year": "2004", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0406176", + "url": "https://arxiv.org/abs/quant-ph/0406176" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2401.10095" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_learningshallow/requirements.in b/demonstrations_v2/tutorial_learningshallow/requirements.in new file mode 100644 index 0000000000..ed2cc33de0 --- /dev/null +++ b/demonstrations_v2/tutorial_learningshallow/requirements.in @@ -0,0 +1,7 @@ +datetime +jax +jaxlib +matplotlib +numpy +optax +pennylane diff --git a/demonstrations_v2/tutorial_liealgebra/demo.py b/demonstrations_v2/tutorial_liealgebra/demo.py new file mode 100644 index 0000000000..b0615d8e1d --- /dev/null +++ b/demonstrations_v2/tutorial_liealgebra/demo.py @@ -0,0 +1,458 @@ +r"""Introducing (Dynamical) Lie Algebras for quantum practitioners +================================================================== + +Are you a quantum practitioner that has so far successfully avoided learning about Lie groups and Lie algebras, +but find yourself in a situation where the pressure is steadily growing? Did you have little success in quick +online search attempts and found yourself overwhelmed by high energy physics literature? +Then this demo might be for you, as we are going to introduce the basic concepts of Lie theory from a perspective that is amenable to +quantum scientists, engineers, and practitioners. + +Introduction +------------ + +Lie algebras, pronounced like the name "Lee", offer a fresh perspective on some of the established ideas in quantum physics and become more and more important +in quantum computing. Let us recap some of the key concepts of quantum mechanics and how they relate to and give rise to Lie +algebras. + +Most physicists know quantum physics in terms of wavefunctions :math:`|\psi\rangle` +that live in a `Hilbert space `_ :math:`\mathcal{H},` +as well as (bounded) linear operators :math:`\hat{O}` that live in the space of linear operators on +that Hilbert space, :math:`\mathcal{L}(\mathcal{H}).` For finite dimensional systems (think, :math:`n` number of qubits) +we have complex valued state vectors (wavefunctions) in :math:`\mathcal{H} = \mathbb{C}^{2^n}` with norm 1 and +square matrices (linear operators) in :math:`\mathcal{L}(\mathcal{H}) = \mathbb{C}^{2^n \times 2^n}.` + +Two very important sub-classes of linear operators in quantum mechanics are unitary and Hermitian operators. +Hermitian operators :math:`H` are self-adjoint, :math:`H^\dagger = H,` and describe observables that can be measured. Unitary operators are norm-preserving +such that :math:`\langle \psi | U^\dagger U | \psi \rangle = \langle \psi | \psi \rangle,` in particular we have +:math:`U^{-1} = U^\dagger.` They describe how quantum states are transformed and ensure that their norm is preserved. + +A unitary operator can always be written as + +.. math:: U = e^{-i H}, + +where we say that :math:`H` is the generator of :math:`U.` Take for example a single qubit rotation +:math:`U(\phi) = e^{-i \frac{\phi}{2} X}.` :math:`U(\phi)` is the unitary evolution that rotates a quantum +state in Hilbert space around the x-axis on the `Bloch sphere `_, +and is generated by the Pauli :math:`X` `matrix `_. + +The space of all such unitary operators +forms the so-called special unitary group :math:`SU(N),` where for qubit systems we have :math:`N=2^n` with :math:`N` the dimension of the group +and `n` the number of qubits. +In quantum computing, we are typically dealing with the Hilbert space :math:`\mathcal{H} = \mathbb{C}^{2^n}` and for full +universality we require the available gates to span all of :math:`SU(2^n).` That means when we have all unitaries of :math:`SU(2^n)` +available to us, we can reach any state in Hilbert space from any other state. + +The Lie group :math:`SU(2^n)` has an associated Lie algebra to it, called :math:`\mathfrak{su}(2^n)` (more on that later). +In some cases, it is more convenient to work with the associated Lie algebra rather than the Lie group. + +So if you are familiar with quantum computing but knew nothing about Lie algebras and Lie groups before this demo, +the good news is that you actually already know the elements of both. Roughly speaking, the relevant Lie group in quantum computing +is the space of unitaries, and the relevant Lie algebra is the space of Hermitian matrices. Further, they are related to each other: The Lie algebra (Hermitian matrices) +generates the Lie group (unitaries) via the exponential map. There are, however, some subtleties if we want to be mathematically precise, as we will explore more in depth now. + +Lie algebras +------------ + +After some motivation and connections to concepts we are already familiar with, let us formally introduce Lie algebras. +An `algebra `_ is a vector space equipped with a bilinear operation. +A `Lie algebra `_ :math:`\mathfrak{g}` is a special case where the bilinear operation behaves like a commutator. +In particular, the bilinear operation :math:`[\bullet, \bullet]: \mathfrak{g} \times \mathfrak{g} \rightarrow \mathfrak{g}` needs to satisfy + +* :math:`[x, x] = 0 \ \forall x \in \mathfrak{g}` (alternativity) +* :math:`[x, [y, z]] + [y, [z, x]] + [z, [x, y]] = 0 \ \forall x,y,z \in \mathfrak{g}` (Jacobi identity) +* :math:`[x, y] = - [y, x] \ \forall x,y \in \mathfrak{g}` (anti-commutativity) + +The last one, anti-commutativity, technically is not an axiom but follows from bilinearity and alternativity, but is so crucial that it is worth highlighting. +These properties generally define the so-called Lie bracket, where the commutator is just one +special case thereof. A different example would be the `cross-product `_ between vectors in :math:`\mathbb{R}^3.` +Note also that we are talking about a **vector** space in the mathematical sense, and the elements ("vectors") in :math:`\mathfrak{g}` are actually operators (matrices) in our case looking at quantum physics. + +One very relevant Lie algebra for us is the special unitary algebra :math:`\mathfrak{su}(N),` the space of :math:`N \times N` skew-Hermitian matrices with trace zero. +The fact that we look at skew-Hermitian (:math:`H^\dagger = - H`) instead of Hermitian (:math:`H^\dagger = H`) matrices is a technical detail (see note below). For all practical purposes you +can just think of Hermitian operators with an imaginary factor and note that linear combinations are strictly over the reals. In fact, you may sometimes +find references to :math:`\mathfrak{su}(N)` being the Hermitian matrices in physics literature (see `Wikipedia `_). + +.. note:: + + The result of a commutator between two Hermitian operators :math:`H_1` and :math:`H_2` is always skew-Hermitian due to the commutator's anti-commutativity, i.e. + + .. math:: [H_1, H_2]^\dagger = [H_2^\dagger, H_1^\dagger] = - [H_1, H_2]. + + This means that Hermitian operators are not closed under commutation, and thus do not form a Lie algebra + (because the commutator maps outside the set of Hermitian matrices). But instead, skew-Hermitian operators do. + Note that the algebra of :math:`N \times N` skew-Hermitian matrices is called the unitary algebra :math:`\mathfrak{u}(N),` whereas + the additional property of the trace being zero making it the `special` unitary algebra :math:`\mathfrak{su}(N).` They generate + the unitary group :math:`U(N)` and the special unitary group :math:`SU(N)` with determinant 1, respectively. + + +The Pauli matrices :math:`\{iX, iY, iZ\}` span the :math:`\mathfrak{su}(2)` algebra that we can associate with single qubit dynamics. +For multiple qubits we have + +.. math:: \mathfrak{su}(2^n) = \text{span}_{\mathbb{R}}\left(\{iX_0, .., iY_0, .., iZ_0, .., iX_0 X_1, .. iY_0 Y_1, .., iZ_0 Z_1, ..\}\right), + +where the span is over the reals :math:`\mathbb{R}.` In particular, we cannot do a complex span over Paulis, since this could destroy the anti-commutativity again. Another way +of thinking about this is that Lie algebra elements "live" in the exponent of a unitary operator, and having that exponent become Hermitian instead of skew-Hermitian +destroys the unitary property. + +Let us briefly test some of these properties numerically. +First, let us do a linear combination of :math:`\{iX, iY, iZ\}` with some real values and check unitarity after putting them in the exponent. +""" +import numpy as np +import pennylane as qml +from pennylane import X, Y, Z + +su2 = [1j * X(0), 1j * Y(0), 1j * Z(0)] + +coeffs = [1., 2., 3.] # some real coefficients +exponent = qml.dot(coeffs, su2) # linear combination of operators +U = qml.math.expm(exponent.matrix()) # compute matrix exponent of lin. comb. +print(np.allclose(U.conj().T @ U, np.eye(2))) # check that result is unitary UU* = 1 + +############################################################################## +# If we throw complex values in the mix, the resulting matrix is not unitary anymore. + +coeffs = [1., 2.+ 1j, 3.] # some complex coefficients +exponent = qml.dot(coeffs, su2) +U = qml.math.expm(exponent.matrix()) +print(np.allclose(U.conj().T @ U, np.eye(2))) # result is not unitary anymore + +############################################################################## +# +# Relation to Lie groups +# ---------------------- +# We said earlier that the Lie group :math:`SU(N)` is generated by the Lie algebra :math:`\mathfrak{su}(N).` +# But what do we actually mean by that? +# Essentially, for every unitary matrix :math:`U \in SU(N)` there is a (real) linear combination of elements :math:`iP_j \in \mathfrak{su}(N)` such that +# +# .. math:: U = e^{i \sum_{j=1}^N \lambda_j P_j} +# +# for some real coefficients :math:`\lambda_j \in \mathbb{R}.` +# +# In quantum computing, we are interested in unitary gates that, when composed together, +# realize a complicated unitary evolution :math:`U.` That could, for example, be +# a unitary that prepares the ground state of a Hamiltonian from the +# :math:`|0\rangle^{\otimes n}` state or perform a sub-routine like the quantum +# Fourier transform. In particular, we are not composing quantum circuits via creating +# superpositions of Lie algebra elements as is done in the last equation. +# +# Luckily, beyond the relation above, we also know that any unitary matrix :math:`U \in SU(2^n)` +# can be decomposed in a finite product of elements from a universal gate set :math:`\mathcal{U},` +# +# .. math:: U = \prod_j U_j +# +# for :math:`U_j \in \mathcal{U}.` A universal gate set is formed exactly when the generators of its elements +# form :math:`\mathfrak{su}(2^n).` Note that in this equation the product may feature a large number of gates :math:`U_j,` +# so universality does not guarantee an efficient decomposition but rather just a finite one. +# +# Dynamical Lie Algebras +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# A different way of looking at this is taking a set of generators :math:`\{iG_j\}` and asking what kind of +# unitary evolutions they can generate. This naturally introduces the so-called Dynamical Lie Algebra (DLA), +# originally proposed in quantum optimal control theory and recently re-emerging in the quantum computing literature. +# The DLA :math:`i\mathfrak{g}` is given by all possible nested commutators between the generators :math:`\{iG_j\},` +# until no new and linearly independent skew-Hermitian operator is generated. This is called the Lie-closure and is +# written like +# +# .. math:: i \mathfrak{g} = \langle iG_1, iG_2, iG_3,.. \rangle_\text{Lie}. +# +# Let us do a quick example and compute the Lie closure of :math:`\{iX, iY\}` (more examples later). + +print(qml.commutator(1j * X(0), 1j * Y(0))) + +############################################################################## +# We know that the commutator between :math:`iX` and :math:`iY` yields a new operator :math:`\propto iZ.` +# Note that we do not care for scalar coefficients, just the operators (technically, we care for linear independence, and :math:`2i Z` is of course linearly dependent on :math:`iZ`). +# So we add :math:`iZ` to our list of operators and continue to take commutators between them. + +list_ops = [1j * X(0), 1j * Y(0), 1j * Z(0)] +for op1 in list_ops: + for op2 in list_ops: + print(qml.commutator(op1, op2)) + +############################################################################## +# Since no new operators have been created we know the lie closure is complete and our dynamical Lie algebra +# is :math:`\langle\{iX, iY\}\rangle_\text{Lie} = \{iX, iY, iZ\}( = \mathfrak{su}(2)).` +# +# PennyLane provides some dedicated functionality for Lie algebras. We can compute the Lie closure of the generators using ``qml.lie_closure``. + +dla = qml.lie_closure([X(0), Y(0)]) +dla + +############################################################################## +# On one hand, the Lie closure ensures that the DLA is closed under commutation. +# But you can also think of the Lie closure as filling the missing operators to describe the possible dynamics in terms of its Lie algebra. +# Let us stick to the example above and imagine for a second that we dont take the Lie closure but just take the two generators :math:`\{iX, iY\}.` +# These two generators suffice for universality (for a single qubit) in that we can write any evolution in the Dynamical Lie Group :math:`SU(2)` as a finite product of +# these :math:`X` and :math:`Y` rotations :math:`e^{-i \phi X}` and :math:`e^{-i \phi Y}.` +# For example, let us write a Pauli-Z rotation at non-trivial angle :math:`0.5` as a product of them. + +U_target = qml.matrix(qml.RZ(-0.5, 0)) +decomp = qml.ops.one_qubit_decomposition(U_target, 0, rotations="XYX") +print(decomp) + +############################################################################## +# We can check that this is indeed a valid decomposition by computing the trace distance to the target. + +U = qml.prod(*decomp).matrix() +print(1 - np.real(np.trace(U_target @ U))/2) + +############################################################################## +# So we see that a finite set of generators :math:`iX` and :math:`iY` suffice to express the target unitary. However, we cannot write +# :math:`U = e^{-i(\lambda_1 X + \lambda_2 Y)}` since we are missing the :math:`iZ` from the DLA +# :math:`i\mathfrak{g} = \langle iX, iY \rangle_\text{Lie} = \{iX, iY, iZ\}.` +# +# Ising-type Lie algebras +# ~~~~~~~~~~~~~~~~~~~~~~~ +# Let us work through another example as an exercise. Let us look at the generators :math:`\{iX_0 X_1, iZ_0, iZ_1\}.` +# You may recognize them as the terms in the transverse field Ising model (here for the simple case of :math:`n=2`) +# +# .. math:: H_\text{Ising} = \sum_{\langle i, j \rangle} X_i X_j + \sum_{j=1}^n Z_j +# +# where :math:`\langle i, j \rangle` indicates a sum over nearest neighbors in the system's topology. +# Let us compute the first set of commutators for those generators. + +generators = [1j * (X(0) @ X(1)), 1j * Z(0), 1j * Z(1)] + +# collection of linearly independent basis vectors, automatically discards linearly dependent ones +dla = qml.pauli.PauliVSpace(generators, dtype=complex) +for i, op1 in enumerate(generators): + for op2 in generators[i+1:]: + res = qml.commutator(op1, op2)/2 + res = res.simplify() # ensures all products of scalars are executed + print(f"[{op1}, {op2}] = {res}") + + if res.scalar != 0. and dla.is_independent(res.pauli_rep): + # Note that the previous is_independent check is just for pedagocical purposes + # as dla.add already checks linear independence below + print(f"Appending {res}") + dla.add(res) + +############################################################################## +# We obtain two new operators :math:`iY_0 X_1` and :math:`iX_0 Y_1` and append the list of operators of the DLA. +# We then continue with depth-1 nested commutators ("nested" as :math:`iY_0 X_1 \propto [iX_0 X_1, iZ_0]`). + +for i, op1 in enumerate(dla.basis.copy()): + for op2 in dla.basis.copy()[i+1:]: + res = qml.commutator(op1, op2)/2 + res = res.simplify() + print(f"[{op1}, {op2}] = {res}") + + if res.scalar != 0. and dla.is_independent(res.pauli_rep): + print(f"Appending {res}") + dla.add(res) + +############################################################################## +# The only new operator here is :math:`iY_0 Y_1,` which we add to the list of the DLA. +# We could continue this process with a second nesting layer but will find that no new operators are added past this point. +# We finally end up with the DLA :math:`\{X_0 X_1, Z_0, Z_1, iY_0 X_1, iX_0 Y_1, iY_0 Y_1\}` + +for op in dla.basis: + print(op) + +############################################################################## +# Curiously, even though both :math:`iZ_0` and :math:`iZ_1` are in the DLA, :math:`iZ_0 Z_1` is not. +# Hence, products of generators are not necessarily in the DLA. +# +# We have constructed the DLA by hand to showcase the process. We can use the PennyLane function :func:`~lie_closure` for convenience. +# In that case, we omit the explicit use of the imgaginary factor. + +dla2 = qml.lie_closure([X(0) @ X(1), Z(0), Z(1)]) +for op in dla2: + print(op) + +############################################################### +# The DLA obtained from the Ising generators form the so-called special orthogonal Lie algebra +# :math:`\mathfrak{so}(4),` which has the dimension :math:`4*3/2 = 6` (see table below), equal to the number of operators we obtain from computing the Lie closure. +# For more qubits :math:`n,` the associated DLA for the transverse field Ising model is :math:`\mathfrak{so}(2n)` for open boundary conditions +# and :math:`\mathfrak{so}(2n)^{\oplus 2}` for cyclic boundary conditions in 1D. +# +# We can easily verify this using :func:`~lie_closure`. + +def IsingGenerators(n, bc="open"): + gens = [X(i) @ X(i+1) for i in range(n-1)] + gens += [Z(i) for i in range(n)] + if bc == "periodic": + gens += [X(n-1) @ X(0)] + return gens + +for n in range(2, 5): + open_ = qml.lie_closure(IsingGenerators(n, "open")) + periodic_ = qml.lie_closure(IsingGenerators(n, "periodic")) + print(f"Ising for n = {n}") + print(f"open: {len(open_)} = {n*(2*n-1)} = 2n * (2n - 1)/2") + print(f"open: {len(periodic_)} = {2*n*(2*n-1)} = 2 * 2n * (2n - 1)/2") + +############################################################### +# This Ising-type Lie algebra is one of only a few handful DLAs that have polynomial scaling, see [#Wiersma]_ for a full classification in 1D +# and are thus efficiently simulatable [#Somma]_ [#Goh]_. Less common but also relevant +# is the `symplectic algebra `_ :math:`\mathfrak{sp}(2N).` +# +# +# In the table below we provide the dimensions of some of the common simple Lie algebras. +# +# .. list-table:: +# :widths: 40 40 +# :header-rows: 1 +# +# * - Lie algebra +# - dimension +# * - :math:`\mathfrak{su}(N)` +# - :math:`N^2-1` +# * - :math:`\mathfrak{so}(N)` +# - :math:`N(N-1)/2` +# * - :math:`\mathfrak{sp}(N)` +# - :math:`N(N+1)/2` +# +# +# Hamiltonian Symmetries +# ---------------------- +# +# With this new knowledge we are now able to understand what is meant when some Hamiltonian models are said to be symmetric under some symmetry group. +# Specifically, let us look at the spin-1/2 Heisenberg model Hamiltonian in 1D with nearest neighbor interactions, +# +# .. math:: H_\text{Heis} = \sum_{j=1}^{n-1} J_j \left(X_j X_{j+1} + Y_j Y_{j+1} + Z_j Z_{j+1} \right) +# +# with some coupling constants :math:`J_j \in \mathbb{R}.` First it is important to understand that the generators here are made up of the whole +# sum of operators :math:`X_j X_{j+1} + Y_j Y_{j+1} + Z_j Z_{j+1}`, and not each individual term :math:`X_j X_{j+1}`, :math:`Y_j Y_{j+1},` and :math:`Z_j Z_{j+1}.` +# This Hamiltonian is said to be :math:`SU(2)` invariant, but what does that mean? +# +# First, let us identify total spin components +# +# .. math:: S_\text{tot}^{x} = \sum_{j=1}^n X_j ; \ S_\text{tot}^{y} = \sum_{j=1}^n Y_j ; \ S_\text{tot}^{z} = \sum_{j=1}^n Z_j. +# +# Together, they span a representation of :math:`\mathfrak{su}(2)` (more on that below). These total spin components each commute with the system Hamiltonian, i.e. +# +# .. math:: [S_\text{tot}^{x}, H_\text{Heis}] = 0 ; [S_\text{tot}^{y}, H_\text{Heis}] = 0 ; [S_\text{tot}^{z}, H_\text{Heis}] = 0 +# +# Let us briefly verify this for a small example for ``n = 3`` qubits that readily generalizes to arbitrary sizes. + +n = 3 +H = qml.sum(*(P(i) @ P(i+1) for i in range(n-1) for P in [X, Y, Z])) + +SX = qml.sum(*(X(i) for i in range(n))) +SY = qml.sum(*(Y(i) for i in range(n))) +SZ = qml.sum(*(Z(i) for i in range(n))) + +print(qml.commutator(H, SX)) +print(qml.commutator(H, SY)) +print(qml.commutator(H, SZ)) + +############################################################################## +# +# Now that we know that the Heisenberg model Hamiltonian commutes with any :math:`S_\text{tot}^{\alpha}` for :math:`\alpha \in \{x, y, z\},` we also know that any observable +# composed of the total spin components +# +# .. math:: \hat{O} = c_x S^x_\text{tot} + c_x S^y_\text{tot} + c_x S^z_\text{tot} +# +# commutes with the Hamiltonian, +# +# .. math:: [\hat{O}, H_\text{Heis}] = 0. +# +# An immediate consequence of this is that also :math:`[e^{-i\hat{O}}, H_\text{Heis}] = 0.` +# Hence, :math:`H_\text{Heis}` is invariant under any action of :math:`e^{-i \hat{O}} \in SU(2),` +# +# .. math:: e^{i\hat{O}} H_\text{Heis} e^{-i\hat{O}} = H_\text{Heis}. +# +# Thus, :math:`H_\text{Heis}` is said to be :math:`SU(2)` symmetric. +# +# There are several things to note: +# We have so far been sloppy in equating Lie algebras with one of many +# possible representations (e.g. :math:`\text{span}_{\mathbb{R}} \{iX, iY, iZ\} = \mathfrak{su}(2)` above). +# The total spin component operators :math:`S_\text{tot}^{x}, S_\text{tot}^{y}, S_\text{tot}^{z}` span another representation of :math:`\mathfrak{su}(2)` and, therefore, generate :math:`SU(2).` +# This is easily verified by looking at the commutation relation between these operators that match :math:`[\hat{O}_i, \hat{O}_j] = 2i \varepsilon_{ij\ell} \hat{O}_\ell,` the defining +# property of :math:`\mathfrak{su}(2).` + +print(qml.commutator(SX, SY) == (2j*SZ).simplify()) +print(qml.commutator(SZ, SX) == (2j*SY).simplify()) +print(qml.commutator(SY, SZ) == (2j*SX).simplify()) + +############################################################################## +# +# Another perspective on the inherent :math:`SU(2)` symmetry of :math:`H_\text{Heis}` is that the expectation +# value of :math:`\hat{O}` with respect to any state :math:`|\psi\rangle` is invariant under evolution of :math:`H_\text{Heis}.` +# This can be seen by looking at +# +# .. math:: \langle \psi(t) | \hat{O} |\psi(t)\rangle = \langle \psi | e^{i t H_\text{Heis}} \hat{O} e^{-i t H_\text{Heis}} |\psi\rangle = \langle \psi | e^{i t H_\text{Heis}} e^{-i t H_\text{Heis}} \hat{O} |\psi\rangle = \langle \psi | \hat{O} |\psi\rangle +# +# where :math:`|\psi(t)\rangle = e^{-i t H_\text{Heis}} |\psi\rangle` is the evolved state under :math:`H_\text{Heis}.` In that sense, :math:`\hat{O}` is a conserved quantity of the system. +# One often associates a so-called `quantum number `_ with each generator of the symmetry, +# here :math:`\{S_\text{tot}^{x}, S_\text{tot}^{y}, S_\text{tot}^{z}\},` the total spin numbers. +# +# Overall, we saw that :math:`H_\text{Heis}` is invariant under action of :math:`SU(2)` and how this gives rise to conserved quantities. +# +# .. note:: +# Symmetries also play a big role in quantum phase transitions: +# Imagine preparing the ground state at zero temperature of a system that has a symmetry. Accordingly, the ground state must +# be invariant under that symmetry. I.e., the expectation value of the conserved quantities must not change by adiabatically (very slowly) changing the system parameters +# while staying at zero temperature. +# However, there may be critical point in the parameter space of the Hamiltonian where a conserved quantity does, in fact, change. +# That is what is called the `spontaneous breaking of the symmetry` +# and it is associated with a quantum phase transition. +# + + +############################################################################## +# +# Conclusion +# ---------- +# +# With this introduction, we hope to clarify some terminology, introduce the basic concepts of Lie theory and motivate their relevance in quantum physics by touching on universality and symmetries. +# While Lie theory and symmetries are playing a central role in established fields such as quantum phase transitions (see note above) and `high energy physics `_, +# they have recently also emerged in quantum machine learning with the onset of geometric quantum machine learning [#Meyer]_ [#Nguyen]_ +# (see our recent :doc:`introduction to geometric quantum machine learning `). +# Further, DLAs have recently become instrumental in classifying criteria for barren plateaus [#Fontana]_ [#Ragone]_ and designing simulators based on them [#Goh]_. +# + + + +############################################################################## +# +# References +# ---------- +# +# .. [#Wiersma] +# +# Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov +# "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension" +# `arXiv:2309.05690 `__, 2023. +# +# .. [#Meyer] +# +# Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert +# "Exploiting symmetry in variational quantum machine learning" +# `arXiv:2205.06217 `__, 2022. +# +# .. [#Nguyen] +# +# Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Frederic Sauvage, Martin Larocca, M. Cerezo +# "Theory for Equivariant Quantum Neural Networks" +# `arXiv:2210.08566 `__, 2022. +# +# .. [#Fontana] +# +# Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia +# "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ansätze" +# `arXiv:2309.07902 `__, 2023. +# +# .. [#Ragone] +# +# Michael Ragone, Bojko N. Bakalov, Frédéric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo +# "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits" +# `arXiv:2309.09342 `__, 2023. +# +# .. [#Goh] +# +# Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage +# "Lie-algebraic classical simulations for variational quantum computing" +# `arXiv:2308.01432 `__, 2023. +# +# .. [#Somma] +# +# Rolando D. Somma +# "Quantum Computation, Complexity, and Many-Body Physics" +# `arXiv:quant-ph/0512209 `__, 2005. +# +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_liealgebra/metadata.json b/demonstrations_v2/tutorial_liealgebra/metadata.json new file mode 100644 index 0000000000..379aac25a0 --- /dev/null +++ b/demonstrations_v2/tutorial_liealgebra/metadata.json @@ -0,0 +1,115 @@ +{ + "title": "Introducing (Dynamical) Lie Algebras for quantum practitioners", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-02-27T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/liealgebra/thumbnail_liealgebra.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_liealgebra.png" + } + ], + "seoDescription": "A gentle introduction to Lie theory covering the basics of Lie algebras and Lie groups in the context of quantum computing.", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Meyer", + "type": "article", + "title": "Exploiting symmetry in variational quantum machine learning", + "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2205.06217", + "url": "https://arxiv.org/abs/2205.06217" + }, + { + "id": "Nguyen", + "type": "preprint", + "title": "Theory for Equivariant Quantum Neural Networks", + "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Frederic Sauvage, Martin Larocca, M. Cerezo", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2210.08566", + "url": "https://arxiv.org/abs/2210.08566" + }, + { + "id": "Fontana", + "type": "article", + "title": "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ans\u00e4tze", + "authors": "Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.07902", + "url": "https://arxiv.org/abs/2309.07902" + }, + { + "id": "Ragone", + "type": "preprint", + "title": "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits", + "authors": "Michael Ragone, Bojko N. Bakalov, Fr\u00e9d\u00e9ric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.09342", + "url": "https://arxiv.org/abs/2309.09342" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_liealgebra/requirements.in b/demonstrations_v2/tutorial_liealgebra/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_liealgebra/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_liesim/demo.py b/demonstrations_v2/tutorial_liesim/demo.py new file mode 100644 index 0000000000..2fee5f3463 --- /dev/null +++ b/demonstrations_v2/tutorial_liesim/demo.py @@ -0,0 +1,483 @@ +r"""g-sim: Lie-algebraic classical simulations for variational quantum computing +================================================================================ + +For the most part, we now know the phenomenon of :doc:`barren plateaus ` +can be reduced to the dimension of the circuit's :doc:`dynamical Lie algebra (DLA) `. +In particular, exponentially sized DLAs lead to exponentially vanishing gradients (barren plateaus). +Conversely, it has been realized that circuits with polynomially sized DLAs can be efficiently simulated using a technique called :math:`\mathfrak{g}`-sim, +leading to discussions on whether all trainable parametrized circuits are also efficiently classically simulable. + +So what is all the fuss about? How does :math:`\mathfrak{g}`-sim work? What are its restrictions? How can I run it in PennyLane? +We are going to try to answer all these questions in the demo below. + +Introduction +------------ + +Lie algebras are tightly connected to quantum physics [#Kottmann]_. +While Lie algebra theory is an integral part of high energy and condensed matter physics, +recent developments have shown connections to quantum simulation and quantum computing. +In particular, the infamous :doc:`barren plateau problem ` has been fully characterized by the underlying +:doc:`dynamical Lie algebra (DLA) ` [#Fontana]_ [#Ragone]_. +The main result of these works is that the dimension of the circuit's DLA is inversely proportional to the variance of the mean of the gradient +(over a uniform parameter distribution), leading to exponentially vanishing gradients in the uniform average case whenever the +DLA scales exponentially in system size. + +At the same time, there exist Lie algebraic techniques with which one can classically simulate expectation values of circuits with a complexity polynomial +in the dimension of the circuit's DLA [#Somma]_ [#Somma2]_ [#Galitski]_ [#Goh]_. +Hence, circuits with guaranteed non-exponentially vanishing gradients in the uniform average case are classically simulable, +leading to some debate on whether the field of variational quantum computing is doomed or not [#Cerezo]_. +The majority of DLAs are in fact exponentially sized [#Wiersema]_, shifting this debate towards the question of whether or not uniform average case results +are relevant in practice for variational methods [#Mazzola]_, with some arguing for better initialization methods [#Park]_. + +In this demo, we want to focus on those cases where efficient classical simulation is possible due to polynomially sized DLAs. +These instances are rather limited as it mainly concerns DLAs of non-interacting systems as well as the transverse-field Ising model and variations thereof (see [#Wiersema]_ for details). + +Lie algebra basics +------------------ + +Before going into the specifics of Lie algebra simulation (:math:`\mathfrak{g}`-sim), +we want to briefly recap the most important concepts of Lie algebra theory that are relevant for us. +More info can be found in our +:doc:`Intro to (Dynamical) Lie Algebras for quantum practitioners `. + +Given Hermitian operators :math:`G = \{h_i\}` (think Hermitian observables like terms of a Hamiltonian), +the dynamical Lie algebra :math:`\mathfrak{g}` +can be computed via the Lie closure :math:`\langle \cdot \rangle_\text{Lie}` (see :func:`~pennylane.lie_closure`), + +.. math:: \mathfrak{g} = \langle \{h_i\} \rangle_\text{Lie} \subseteq \mathfrak{su}(2^n). + +That is, by computing all possible nested commutators until no new operators emerge. This leads to a set of operators that is closed under commutation, +hence the name. +In particular, the result of the commutator between any two elements in :math:`\mathfrak{g}` +can be decomposed as a linear combination of other elements in :math:`\mathfrak{g},` + +.. math:: [h_\alpha, h_\beta] = \sum_\gamma f^\gamma_{\alpha \beta} h_\gamma. + +The coefficients :math:`f^\gamma_{\alpha \beta}` are called the structure constants of the DLA and can be computed via the standard +projection in vector spaces (as is :math:`\mathfrak{g}`), + +.. math:: f^\gamma_{\alpha \beta} = \frac{\langle h_\gamma, [h_\alpha, h_\beta]\rangle}{\langle h_\gamma, h_\gamma\rangle}. + +The main difference from the usual vector spaces like :math:`\mathbb{R}^N` or :math:`\mathbb{C}^N` is that here we +use the trace inner product between operators :math:`\langle h_\alpha, h_\beta \rangle = \text{tr}\left[h_\alpha^\dagger h_\beta \right]` + +.. note:: + + Technically, the (dynamical) Lie algebra is formed by skew-Hermitian operators :math:`\{i h_i\}.` + We avoid this distinction here since for all practical purposes one can also look at Hermitian + operators and explicitly add imaginary units in the exponents where appropriate. + For more details, see the note in the "Lie algebras" section of our `Intro to (dynamical) Lie algebras for quantum practitioners `__. + +:math:`\mathfrak{g}`-sim theory +------------------------------- + +In Lie algebra simulation, :math:`\mathfrak{g}`-sim, we are interested in how expectation values of Lie algebra elements are transformed under unitary evolution. +We start from an initial expectation value vector of the input state :math:`\rho^0` with respect to each DLA element, + +.. math:: (\vec{e}^0)_\alpha = \text{tr}\left[h_\alpha \rho^0 \right]. + +Graphically, we can represent this as a tensor with one leg. + +.. figure:: ../_static/demonstration_assets/liesim/e.png + :align: center + :width: 33% + +When we transform the state :math:`\rho^0` with a unitary evolution :math:`U,` we can use the cyclic property of the trace to shift the +evolution onto the DLA element, + +.. math:: (\vec{e}^1)_\alpha = \text{tr}\left[ h_\alpha U \rho^0 U^\dagger \right] = \text{tr}\left[ U^\dagger h_\alpha U \rho^0 \right]. + +In the context of :math:`\mathfrak{g}`-sim, we assume the unitary operator to be generated by DLA elements :math:`h_\mu \in \mathfrak{g};` in particular, we have + +.. math:: U = e^{-i \theta h_\mu} + +with some real parameter :math:`\theta \in \mathbb{R}.` + +As a consequence of the `Baker–Campbell–Hausdorff formula `__, +we know that any :math:`h_\alpha \in \mathfrak{g}` transformed under such a :math:`U` is again in :math:`\mathfrak{g}` +(because it leads to a sum of nested commutators between DLA elements, and the DLA is closed under commutation). +In fact, it is a well-known result that the resulting operator is given by the exponential of the structure constants + +.. math:: e^{i \theta h_\mu} h_\alpha e^{-i \theta h_\mu} = \sum_\beta e^{-i \theta f^\mu_{\alpha \beta}} h_\beta. + +This is the identity connecting the adjoint representations of a Lie group, :math:`\text{Ad}_{e^{-ih_\mu}}(x) = e^{ih_\mu} x e^{-ih_\mu},` +and the adjoint representation of the associated Lie algebra, :math:`\left(\text{ad}_{h_\mu}\right)_{\alpha \beta} = f^\mu_{\alpha \beta}.` +It can be summarized as + +.. math:: \text{Ad}_{e^{-ih_\mu}} = e^{-i \text{ad}_{h_\mu}}. + +To the best of our knowledge there is no universally accepted name for this identity +(see, e.g. `Adjoint representation (Wikipedia) `__ +or `Lemma 3.14 in Introduction to Lie Groups and Lie Algebras `__), +so we shall refer to it as the "adjoint identity" from here on. + +With this, we can see how the initial expectation value vector is transformed under unitary evolution, + +.. math:: (\vec{e}^1)_\alpha = \sum_\beta e^{-i \theta f^\mu_{\alpha \beta}} \text{tr}\left[h_\beta \rho^0 \right]. + +This is simply the matrix-vector product between the adjoint representation of the unitary gate and the initial expectation value vector. +For a unitary circuit composed of multiple gates, + +.. math:: \mathcal{U} = \prod_j e^{-i \theta_j h_j}, + +this becomes the product of multiple adjoint representations of said gates, + +.. math:: \tilde{U} = \prod_j e^{-i \theta_j \text{ad}_{h_j}}. + +So overall, the evolution can be summarized graphically as the following. + +.. figure:: ../_static/demonstration_assets/liesim/Ue.png + :align: center + :width: 33% + +We are typically interested in expectation values of observables composed of DLA elements, +:math:`\langle \hat{O} \rangle = \sum_\alpha w_\alpha h_\alpha.` +Overall, the computation in :math:`\mathfrak{g}`-sim is a vector-matrix-vector product, + +.. math:: \langle \hat{O} \rangle = \text{tr}\left[\hat{O} \mathcal{U} \rho^0 \mathcal{U}^\dagger \right] = \sum_{\alpha \beta} w_\alpha \tilde{U}_{\alpha \beta} e_\beta = \vec{w} \cdot \tilde{U} \cdot \vec{e}. + +Or, graphically: + +.. figure:: ../_static/demonstration_assets/liesim/wUe.png + :align: center + :width: 33% + +The dimension of :math:`\left(\text{ad}_{h_j}\right)_{\alpha \beta} = f^j_{\alpha \beta}` is +:math:`\text{dim}(\mathfrak{g}) \times \text{dim}(\mathfrak{g}).` So while we evolve a :math:`2^n`-dimensional +complex vector in state vector simulators, we evolve a :math:`\text{dim}(\mathfrak{g})`-dimensional expectation +vector in :math:`\mathfrak{g}`-sim, which is more efficient whenever :math:`\text{dim}(\mathfrak{g}) < 2^n.` In general, it is efficient +whenever :math:`\text{dim}(\mathfrak{g}) = O\left(\text{poly}(n)\right).` + +:math:`\mathfrak{g}`-sim in PennyLane +------------------------------------- + +Let us put this into practice and write a differentiable :math:`\mathfrak{g}`-simulator in PennyLane. +We start with some boilerplate PennyLane imports. + +""" + +import pennylane as qml +from pennylane import X, Z, I +import numpy as np + +import jax +import jax.numpy as jnp +from jax.scipy.linalg import expm + +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") + +############################################################################## +# +# System DLA +# ~~~~~~~~~~ +# +# As mentioned before, polynomially sized DLAs are rare, with the transverse-field Ising model (TFIM) with nearest neighbors being one of them. +# We take, for simplicity, the one-dimensional variant with open boundary conditions, +# +# .. math:: H_\text{TFIM} = \sum_{j=1}^{n-1} J X_j X_{j+1} + \sum_{i=1}^{n} h Z_j. +# +# We define its generators and compute the :func:`~pennylane.lie_closure`. + +n = 10 # number of qubits. +generators = [X(i) @ X(i+1) for i in range(n-1)] +generators += [Z(i) for i in range(n)] + +# work with PauliSentence instances for efficiency +generators = [op.pauli_rep for op in generators] + +dla = qml.pauli.lie_closure(generators, pauli=True) +dim_g = len(dla) + +############################################################################## +# We are using the :class:`~pennylane.pauli.PauliSentence` representation of the operators via the ``op.pauli_rep`` attribute for more efficient arithmetic and processing. +# +# Initial expectation value vector +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# With that, we can compute the initial expectation value vector for the :math:`\rho_0 = |0 \rangle \langle 0 |` initial state for every DLA element. +# We are doing a trick of representing the initial state as a Pauli operator, :math:`|0 \rangle \langle 0 |^{\otimes n} = \prod_{i=1}^n (I_i + Z_i)/2.` +# We take advantage of the locality of the DLA elements +# and use the analytic, normalized trace method :meth:`~.pennylane.pauli.PauliSentence.trace`, all to avoid having to go to the full Hilbert space. + +# compute initial expectation value vector +e_in = np.zeros(dim_g, dtype=float) + +for i, h_i in enumerate(dla): + # initial state |0x0| = (I + Z)/2, note that trace function + # below already normalizes by the dimension, + # so we can ommit the explicit factor /2 + rho_in = qml.prod(*(I(i) + Z(i) for i in h_i.wires)) + rho_in = rho_in.pauli_rep + + e_in[i] = (h_i @ rho_in).trace() + +e_in = jnp.array(e_in) +e_in + + +############################################################################## +# Observable +# ~~~~~~~~~~ +# +# We can compute the expectation value of any linear combination of DLA elements. We choose the TFIM Hamiltonian itself, +# +# .. math:: \hat{O} = H_\text{TFIM} = \sum_j J X_j X_{j+1} + h Z_j. +# +# So just the generators with some coefficients. Here we choose :math:`J=h=0.5` for simplicity. +# We generate the :math:`\vec{w}` vector by setting the appropriate coefficients to ``0.5`.` + +w = np.zeros(dim_g, dtype=float) +w[:len(generators)] = 0.5 +w = jnp.array(w) + +############################################################################## +# Forward and backward pass +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Together with the structure constants computed via :func:`~pennylane.structure_constants` we now have all ingredients to define +# the forward pass of the expectation value computation. For demonstration purposes, +# we choose a random subset of ``depth=10`` generators for gates from the DLA. + +adjoint_repr = qml.pauli.structure_constants(dla) + +depth = 10 +gate_choice = np.random.choice(dim_g, size=depth) +gates = adjoint_repr[gate_choice] + +def forward(theta): + # simulation + e_t = e_in + for i in range(depth): + e_t = expm(theta[i] * gates[i]) @ e_t + + # final expectation value + result_g_sim = w @ e_t + + return result_g_sim.real + +theta = jax.random.normal(jax.random.PRNGKey(0), shape=(10,)) + +gsim_forward, gsim_backward = forward(theta), jax.grad(forward)(theta) +gsim_forward, gsim_backward + +############################################################################## +# As a sanity check, we compare the computation with the full state vector equivalent circuit. + +H = 0.5 * qml.sum(*[op.operation() for op in generators]) + +@qml.qnode(qml.device("default.qubit"), interface="jax") +def qnode(theta): + for i, mu in enumerate(gate_choice): + qml.exp(-1j * theta[i] * dla[mu].operation()) + return qml.expval(H) + +statevec_forward, statevec_backward = qnode(theta), jax.grad(qnode)(theta) +statevec_forward, statevec_backward + + +############################################################################## +# We see that both simulations yield the same results, while full state vector simulation is done with a +# :math:`2^n = 1024` dimensional state vector, and :math:`\mathfrak{g}`-sim with a :math:`\text{dim}(g) = 2n (2n-1)/2 = 190` dimensional +# expectation value vector. + +print( + qml.math.allclose(statevec_forward, gsim_forward), + qml.math.allclose(statevec_backward, gsim_backward), +) + +############################################################################## +# Beyond 6 qubits, :math:`\mathfrak{g}`-sim is more efficient in simulating circuits generated by the TFIM Hamiltonian. +# + +import matplotlib.pyplot as plt +ns = np.arange(2, 17) + +plt.plot(ns, 2*ns*(2*ns-1)/2, "x-", label="dim(g)") +plt.plot(ns, 2**ns, ".-", label="2^n") +plt.yscale("log") +plt.legend() +plt.xlabel("n qubits") +plt.show() + +############################################################################## +# +# VQE +# ~~~ +# +# Let us do a quick run of the :doc:`variational quantum eigensolver (VQE) ` on the system at hand. +# +# First, we define our optimization loop in jax. Consider this boilerplate code and see our demo +# on :doc:`how to optimize a QML model using JAX and Optax ` +# for details. + +import optax +from datetime import datetime + +def run_opt(value_and_grad, theta, n_epochs=100, lr=0.1, b1=0.9, b2=0.999, E_exact=0., verbose=True): + + optimizer = optax.adam(learning_rate=lr, b1=b1, b2=b2) + opt_state = optimizer.init(theta) + + energy = np.zeros(n_epochs) + gradients = [] + thetas = [] + + @jax.jit + def step(opt_state, theta): + val, grad_circuit = value_and_grad(theta) + updates, opt_state = optimizer.update(grad_circuit, opt_state) + theta = optax.apply_updates(theta, updates) + + return opt_state, theta, val + + + t0 = datetime.now() + + ## Optimization loop + for n in range(n_epochs): + opt_state, theta, val = step(opt_state, theta) + + energy[n] = val + thetas.append(theta) + t1 = datetime.now() + if verbose: + print(f"final loss: {val - E_exact}; min loss: {np.min(energy) - E_exact}; after {t1 - t0}") + + return thetas, energy, gradients + +############################################################################## +# We can use the Hamiltonian variational ansatz as a natural parametrization of an ansatz circuit to obtain +# the ground-state energy. +# +# In particular, we use the full Hamiltonian generator with a trainable parameter for each term, +# +# .. math:: \prod_{\ell=1}^{10} e^{-i \sum_j \theta^X_j X_j X_{j+1} + \theta^Z_j Z_j}, +# +# and repeat that over ``depth=10`` layers. + +# Pick the adjoint repr of only the Hamiltonian generators +ham_terms = adjoint_repr[:len(generators)] + +def forward(theta): + # simulation + e_t = jnp.array(e_in) + + for i in range(depth): + e_t = expm(jnp.einsum("j,jkl->kl", theta[i], ham_terms)) @ e_t + + # final expectation values + result_g_sim = w @ e_t + + return result_g_sim.real + +############################################################################## +# Now we can run the optimization to find the ground-state energy. + +theta = jax.random.normal(jax.random.PRNGKey(0), shape=(depth, len(generators),)) + +value_and_grad = jax.jit(jax.value_and_grad(forward)) + +value_and_grad(theta) # jit-compile first + +E_exact = H.eigvals().min() + +_, energies, _ = run_opt(value_and_grad, theta, E_exact=E_exact, verbose=True) + +import matplotlib.pyplot as plt +plt.plot(energies-E_exact) +plt.yscale("log") +plt.ylabel("$E - E_{exact}$") +plt.xlabel("epochs") +plt.show() + +############################################################################## +# We see good convergence to the true ground-state energy after ``100`` epochs. + +############################################################################## +# +# Conclusion +# ---------- +# +# We learned about the conceptually intriguing connection between unitary evolution and the adjoint representation of the system DLA via the adjoint identity, +# and saw how this connection can be used for classical simulation. In particular, for specific systems like the TFIM we can efficiently simulate circuit +# expectation values. +# +# In case you are more curious about Lie algebraic simulation techniques, check out the follow-up demo on :doc:`(g+P)-sim ` +# an extension of :math:`\mathfrak{g}`-sim. + + + +############################################################################## +# +# References +# ---------- +# +# .. [#Kottmann] +# +# Korbinian Kottmann +# "Introducing (Dynamical) Lie Algebras for quantum practitioners" +# `PennyLane Demos `__, 2024. +# +# .. [#Fontana] +# +# Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia +# "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ansätze" +# `arXiv:2309.07902 `__, 2023. +# +# .. [#Ragone] +# +# Michael Ragone, Bojko N. Bakalov, Frédéric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo +# "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits" +# `arXiv:2309.09342 `__, 2023. +# +# .. [#Somma] +# +# Rolando D. Somma +# "Quantum Computation, Complexity, and Many-Body Physics" +# `arXiv:quant-ph/0512209 `__, 2005. +# +# .. [#Somma2] +# +# Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill +# "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models" +# `arXiv:quant-ph/0601030 `__, 2006. +# +# .. [#Galitski] +# +# Victor Galitski +# "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach" +# `arXiv:1012.2873 `__, 2010. +# +# .. [#Goh] +# +# Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage +# "Lie-algebraic classical simulations for variational quantum computing" +# `arXiv:2308.01432 `__, 2023. +# +# .. [#Cerezo] +# +# M. Cerezo, Martin Larocca, Diego García-Martín, N. L. Diaz, Paolo Braccia, Enrico Fontana, Manuel S. Rudolph, Pablo Bermejo, Aroosa Ijaz, Supanut Thanasilp, Eric R. Anschuetz, Zoë Holmes +# "Does provable absence of barren plateaus imply classical simulability? Or, why we need to rethink variational quantum computing" +# `arXiv:2312.09121 `__, 2023. +# +# .. [#Wiersema] +# +# Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov +# "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension" +# `arXiv:2309.05690 `__, 2023. +# +# .. [#Mazzola] +# +# Guglielmo Mazzola +# "Quantum computing for chemistry and physics applications from a Monte Carlo perspective" +# `arXiv:2308.07964 `__, 2023. +# +# .. [#Park] +# +# Chae-Yeun Park, Minhyeok Kang, Joonsuk Huh +# "Hardware-efficient ansatz without barren plateaus in any depth" +# `arXiv:2403.04844 `__, 2024. +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_liesim/metadata.json b/demonstrations_v2/tutorial_liesim/metadata.json new file mode 100644 index 0000000000..decf240cce --- /dev/null +++ b/demonstrations_v2/tutorial_liesim/metadata.json @@ -0,0 +1,165 @@ +{ + "title": "g-sim: Lie-algebraic classical simulations for variational quantum computing", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-06-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/liesim/thumbnail_gsim.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_gsim.png" + } + ], + "seoDescription": "A differentiable implementation of g-sim in PennyLane", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Fontana", + "type": "article", + "title": "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ans\u00e4tze", + "authors": "Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.07902", + "url": "https://arxiv.org/abs/2309.07902" + }, + { + "id": "Ragone", + "type": "preprint", + "title": "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits", + "authors": "Michael Ragone, Bojko N. Bakalov, Fr\u00e9d\u00e9ric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.09342", + "url": "https://arxiv.org/abs/2309.09342" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "Somma2", + "type": "preprint", + "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", + "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", + "year": "2006", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0601030", + "url": "https://arxiv.org/abs/quant-ph/0601030" + }, + { + "id": "Galitski", + "type": "preprint", + "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", + "authors": "Victor Galitski", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1012.2873", + "url": "https://arxiv.org/abs/1012.2873" + }, + { + "id": "Cerezo", + "type": "preprint", + "title": "Does provable absence of barren plateaus imply classical simulability? Or, why we need to rethink variational quantum computing", + "authors": "M. Cerezo, Martin Larocca, Diego Garc\u00eda-Mart\u00edn, N. L. Diaz, Paolo Braccia, Enrico Fontana, Manuel S. Rudolph, Pablo Bermejo, Aroosa Ijaz, Supanut Thanasilp, Eric R. Anschuetz, Zo\u00eb Holmes", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2312.09121", + "url": "https://arxiv.org/abs/2312.09121" + }, + { + "id": "Mazzola", + "type": "preprint", + "title": "Quantum computing for chemistry and physics applications from a Monte Carlo perspective", + "authors": "Guglielmo Mazzola", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.07964", + "url": "https://arxiv.org/abs/2308.07964" + }, + { + "id": "Park", + "type": "preprint", + "title": "Hardware-efficient ansatz without barren plateaus in any depth", + "authors": "Chae-Yeun Park, Minhyeok Kang, Joonsuk Huh", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2403.04844", + "url": "https://arxiv.org/abs/2403.04844" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2308.01432" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim_extension", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_liesim/requirements.in b/demonstrations_v2/tutorial_liesim/requirements.in new file mode 100644 index 0000000000..ed2cc33de0 --- /dev/null +++ b/demonstrations_v2/tutorial_liesim/requirements.in @@ -0,0 +1,7 @@ +datetime +jax +jaxlib +matplotlib +numpy +optax +pennylane diff --git a/demonstrations_v2/tutorial_liesim_extension/demo.py b/demonstrations_v2/tutorial_liesim_extension/demo.py new file mode 100644 index 0000000000..b7f2942bef --- /dev/null +++ b/demonstrations_v2/tutorial_liesim_extension/demo.py @@ -0,0 +1,536 @@ +r"""(g + P)-sim: Extending g-sim by non-DLA observables and gates +================================================================= + +In a :doc:`previous demo `, we introduced the core concepts of +Lie-algebraic simulation techniques [#Somma]_ [#Somma2]_ [#Galitski]_, such as :math:`\mathfrak{g}`-sim [#Goh]_. +With that, we can compute quantum circuit expectation values using the so-called :doc:`dynamical Lie algebra (DLA) ` of the circuit. +The complexity of :math:`\mathfrak{g}`-sim is determined by the dimension of the corresponding Lie algebra, :math:`\mathfrak{g}.` +Adding operators to :math:`\mathfrak{g}` can transform a polynomially sized DLA to an exponentially sized, but we show here that +when one is using only a few of a specific kind of non-DLA gates, the increase in size is polynomial. + +.. note:: + + The contents of this demo are self-contained. However, familiarity with + :doc:`dynamical Lie algebras ` and :doc:`g-sim in PennyLane ` is advised. + +Introduction +------------ + +Lie-algebraic simulation techniques such as :math:`\mathfrak{g}`-sim can be handy in the niche cases where the +:doc:`dynamical Lie algebra (DLA) ` scales polynomially with +the number of qubits. Because those cases essentially boil down to the transverse field Ising model (TFIM) and variants thereof in 1D [#Wiersema]_, +we will do a case study on its DLA specifically. + +We are interested in the case where we want to extend the DLA :math:`\mathfrak{g}` by a few additional gates +that are outside the DLA. For :math:`n` qubits we get a DLA dimension of +:math:`\text{dim}(\mathfrak{g}) = 2n(2n-1)/2` for the TFIM (see :doc:`here `). +Suppose we want to expand the DLA by a single operator :math:`p` in order to use it as a gate, +and let us assume that :math:`p` is the product of two DLA operators that, itself, is not part of the DLA. +Adding product operators to the TFIM DLA and computing their new Lie closure can lead to an exponential increase with a new dimension up to :math:`2(2^{2n-2}-1).` +In that worst case, we get the so-called `associative algebra` of :math:`\mathfrak{g};` that is, the algebra from the closure over multiplication, +i.e. which looks at all possible products of operators. This is also a Lie algebra. + +Here, we show how to extend the DLA by such a :math:`p` gate without going to the exponentially large associative algebra, but instead make use of the fact that :math:`p` is +a product of DLA elements. We do so by looking at `moments` of :math:`\mathfrak{g}` instead. The :math:`m`-th order moments are products of :math:`(m+1)` DLA elements. +E.g. :math:`p = h_{\alpha_1} h_{\alpha_2} \notin \mathfrak{g}` is a first order moment. Depending on their order, every non-DLA moment gate increases +the highest moment order considered in the computation, :math:`m_\text{comp}`. The overall cost scales with the maximum order :math:`\text{dim}(\mathfrak{g})^{m_\text{comp}}.` + +In the worst case, each moment expands the space of operators by a factor :math:`\text{dim}(\mathfrak{g}),` such that for :math:`m` moments, +we are dealing with a :math:`\text{dim}(\mathfrak{g})^{m+2}` dimensional space. In that sense, this is similar to +:doc:`Clifford+T simulators ` where +expensive :math:`T` gates come with an exponential cost. +A key difference is that for a finite dimensional DLA, there is a maximum moment :math:`m_\text{max}.` This corresponds to simply constructing the full associative algebra again. +In the case that the required :math:`m_\text{comp} = m_\text{max},` we can just perform regular :math:`\mathfrak{g}`-sim with the associative algebra. Here we will consider +the case :math:`m_\text{comp} < m_\text{max}.` + +In [#Goh]_, the authors already hint at the possibility of extending :math:`\mathfrak{g}`-sim by expectation values +of products of DLA elements. In this demo, we extend this notion to `gates` generated by moments of the DLA. + +:math:`\mathfrak{g}`-sim +------------------------ + +Let us briefly recap the core principles of :math:`\mathfrak{g}`-sim. We consider a Lie algebra :math:`\mathfrak{g} = \{h_1, .., h_d\},` which is closed +under commutation (see :func:`~pennylane.lie_closure`). We know that gates :math:`e^{-i \theta h_\alpha}` transform Lie algebra elements into Lie +algebra elements, + +.. math:: e^{i \theta h_\mu} h_\alpha e^{-i \theta h_\mu} = \sum_\beta \left(e^{-i \theta \text{ad}_{h_\mu}}\right)_{\alpha \beta} h_\beta. + +This is the adjoint identity with the adjoint representation of the Lie algebra given by the :func:`~pennylane.structure_constants`, +:math:`f^\mu_{\alpha \beta} = -i \left(\text{ad}_{h_\mu}\right)_{\alpha \beta}.` + +This lets us evolve any expectation value of DLA elements using the adjoint representation of the DLA. +For that, we define the expectation value vector :math:`(\boldsymbol{e})_\alpha = \text{tr}[h_\alpha \rho].` + +Also, let us write :math:`U = e^{-i \theta \text{ad}_{h_\mu}}` corresponding to a unitary :math:`\mathcal{U} = e^{-i \theta h_\mu}.` +Using the adjoint identity above and the cyclic property of the trace, we can write an evolved expectation value vector as + +.. math:: \text{tr}\left[h_\alpha \mathcal{U} \rho \mathcal{U}^\dagger\right] = \sum_\beta U_{\alpha \beta} \text{tr}\left[h_\beta \rho \right]. + +Hence, the expectation value vector is simply transformed by matrix multiplication with :math:`U` and we have + +.. math:: \boldsymbol{e}^\text{out} = U \boldsymbol{e}^\text{in} + +for some input expectation value vector :math:`\boldsymbol{e}^\text{in}.` + +A circuit comprised of multiple unitaries :math:`\mathcal{U}` then simply corresponds to evolving the expectation value vector +with :math:`U.` + +We are going to concretely use the DLA of the transverse field Ising model, + +.. math:: H_\text{Ising} = J \sum_{i=1}^{n-1} X_i X_{i+1} + h \sum_{i=1}^n Z_i. + +This is one of the few systems that yield a polynomially sized DLA. +Let us construct its DLA via :func:`~pennylane.lie_closure`. + +""" + +import pennylane as qml +import numpy as np + +from pennylane import X, Y, Z, I +from pennylane.pauli import PauliSentence, PauliWord +from scipy.linalg import expm + +import copy + +# TFIM generators +def TFIM(n): + generators = [X(i) @ X(i+1) for i in range(n-1)] + generators += [Z(i) for i in range(n)] + generators = [op.pauli_rep for op in generators] + + dla = qml.pauli.lie_closure(generators, pauli=True) + dim_dla = len(dla) + return generators, dla, dim_dla + +generators, dla, dim_g = TFIM(n=4) + +############################################################################## +# In regular :math:`\mathfrak{g}`-sim, the unitary evolution :math:`\mathcal{U}` of the expectation vector +# is simply generated by the adjoint representation :math:`U.` + +adjoint_repr = qml.structure_constants(dla) +gate = adjoint_repr[-1] +theta = 0.5 + +U = expm(theta * gate) + +############################################################################## +# +# :math:`(\mathfrak{g}+P)`-sim +# ---------------------------- +# +# We now want to extend :math:`\mathfrak{g}`-sim by operators that are not in the DLA, but a product +# of DLA operators. Note that while the DLA is closed under commutation, it is not closed under multiplication, +# such that products of DLA elements are in general not in :math:`\mathfrak{g}.` +# +# Let us look at the adjoint action of a gate generated by :math:`p = h_{\mu_1} h_{\mu_2} .. \notin \mathfrak{g},` +# +# .. math:: e^{i \theta h_{\mu_1} h_{\mu_2} ..} h_\alpha e^{-i \theta h_{\mu_1} h_{\mu_2} ..} = \sum_\beta \boldsymbol{P}^0_{\alpha \beta} h_\beta + \sum_{\beta_1 \beta_2} \boldsymbol{P}^1_{\alpha \beta_1 \beta_2} h_{\beta_1} h_{\beta_2} + ... +# +# Here, :math:`\boldsymbol{P}^m` correspond to the contributions of the :math:`m`-th moments in :math:`\mathfrak{g}.` +# Let us look at the case where we use a first order product, :math:`\mathcal{P} = e^{-i \theta h_{\mu_1} h_{\mu_2}},` and only DLA operators and first order moments contribute to the adjoint action. +# +# For that, let us construct a concrete example. First we pick two elements from :math:`\mathfrak{g}` such that their product is not in :math:`\mathfrak{g}.` + +p = dla[-5] @ dla[-2] +p = next(iter(p)) # strip any scalar coefficients +dla_vspace = qml.pauli.PauliVSpace(dla, dtype=complex) +dla_vspace.is_independent(p.pauli_rep) + + +############################################################################## +# +# .. note:: +# +# For DLAs consisting of Pauli words — as is the case for the TFIM — we can simply remove any scalar factors to avoid having additional imaginary factors in the exponent of gates. +# +# Now, we compute :math:`e^{i \theta h_{\mu_2} h_{\mu_1}} h_\alpha e^{-i \theta h_{\mu_1} h_{\mu_2}}` and decompose it in the DLA and first moments. +# +# Note that since the product basis is overcomplete, we only keep track of the linearly independent elements and ignore the rest. + +def exppw(theta, ps): + # assert that it is indeed a pure Pauli word, not a sentence + assert (len(ps) == 1 and isinstance(ps, PauliSentence)) or isinstance(ps, PauliWord) + return np.cos(theta) * PauliWord({}) + 1j * np.sin(theta) * ps + +theta = 0.5 + +P = exppw(theta, p) +P_dagger = exppw(-theta, p) # complex conjugate with p just being a hermitian pauli word + +P0 = np.zeros((dim_g, dim_g), dtype=float) + +for i, h1 in enumerate(dla): + res = P @ h1 @ P_dagger + for j, h2 in enumerate(dla): + # decompose the result in terms of DLA elements + # res = ∑ (res · h_j / ||h_j||^2) * h_j + value = (res @ h2).trace().real + value = value / (h2 @ h2).trace() + P0[i, j] = value + +P1 = np.zeros((dim_g, dim_g, dim_g), dtype=float) + +for i, h1 in enumerate(dla): + res = P @ h1 @ P_dagger + dla_and_M1_vspace = copy.deepcopy(dla_vspace) + for j, h2 in enumerate(dla): + for l, h3 in enumerate(dla): + prod = h2 @ h3 + + if not dla_and_M1_vspace.is_independent(prod): + continue + + # decompose the result in terms of products of DLA elements + # res = ∑ (res · p_j / ||p_j||^2) * p_j + value = (res @ prod).trace().real + value = value / (prod @ prod).trace().real + P1[i, j, l] = value + dla_and_M1_vspace.add(prod) + +############################################################################## +# We want to confirm that the adjoint action of :math:`\mathcal{P}` is indeed fully described by the zeroth and first moments. +# +# For that, we reconstruct the transformed DLA elements and compare them with the decomposition. + +for i, h1 in enumerate(dla): + res = P @ h1 @ P_dagger + res.simplify() + + reconstruct = sum([P0[i, j] * dla[j] for j in range(dim_g)]) + reconstruct += sum([P1[i, j, l] * dla[j] @ dla[l] for j in range(dim_g) for l in range(dim_g)]) + reconstruct.simplify() + + assert res == reconstruct + + +############################################################################## +# Now that we have successfully constructed a :math:`\mathcal{P}` gate, +# let us look how entering it in a circuit transforms DLA elements (and therefore expectation value vector elements). +# +# .. math:: +# +# \begin{align*} +# (\boldsymbol{e})_\alpha & = \text{tr}\left[h_\alpha \mathcal{P} \rho \mathcal{P}^\dagger \right] = \text{tr}\left[\mathcal{P}^\dagger h_\alpha \mathcal{P} \rho \right] \\ +# \ & = \sum_\beta \boldsymbol{P}^0_{\alpha \beta} \text{tr}\left[ h_\beta \rho \right] + \sum_{\beta_1 \beta_2} \boldsymbol{P}^1_{\alpha \beta_1 \beta_2} \text{tr}\left[ h_{\beta_1} h_{\beta_2} \rho \right] \\ +# \ & = \sum_\beta \boldsymbol{P}^0_{\alpha \beta} \boldsymbol{E}^0_\beta + \sum_{\beta_1 \beta_2} \boldsymbol{P}^1_{\alpha \beta_1 \beta_2} \boldsymbol{E}^1_{\beta_1 \beta_2} +# \end{align*} +# +# Here we have defined the expectation tensor +# :math:`(\boldsymbol{E}^m)_{\beta_1 , .. , \beta_{m+1}} = \text{tr}\left[ h_{\beta_1} .. h_{\beta_{m+1}} \rho \right]` for the :math:`m`-th moment. +# Note that :math:`\boldsymbol{e} = \boldsymbol{E}^0` is the expectation value vector for regular :math:`\mathfrak{g}`-sim. +# +# +# Such a computation corresponds to the branching off from the original diagram, with an extra contribution coming from the higher moments. +# +# .. figure:: /_static/demonstration_assets/liesim_extension/first_split.png +# :width: 45% +# :align: center +# +# When inserting an arbitrary DLA gate :math:`U` before and :math:`V` after the :math:`\mathcal{P}` gate, +# we obtain the following diagram. Note that :math:`U` and :math:`V` can be compositions of multiple DLA gates again. +# +# .. figure:: /_static/demonstration_assets/liesim_extension/first_order_diagram.png +# :width: 45% +# :align: center +# +# Note that in one vertical column the :math:`U` correspond to the same matrix. +# +# +# Example +# ~~~~~~~ +# +# Let us compute an example. For that we start by computing the initial expectation vector and tensor. + +E0_in = np.zeros((dim_g), dtype=float) +E1_in = np.zeros((dim_g, dim_g), dtype=float) +E_in = [E0_in, E1_in] + +for i, hi in enumerate(dla): + rho_in = qml.prod(*(I(i) + Z(i) for i in hi.wires)) + rho_in = rho_in.pauli_rep + + E_in[0][i] = (hi @ rho_in).trace() + +for i, hi in enumerate(dla): + for j, hj in enumerate(dla): + prod = hi @ hj + if prod.wires != qml.wires.Wires([]): + rho_in = qml.prod(*(I(i) + Z(i) for i in prod.wires)) + else: + rho_in = PauliWord({}) # identity + rho_in = rho_in.pauli_rep + + E_in[1][i, j] = (prod @ rho_in).trace().real + +############################################################################## +# +# Now we need to compute the two branches from the diagram above. +# +# .. figure:: /_static/demonstration_assets/liesim_extension/first_order_diagram.png +# :width: 45% +# :align: center +# + +# some other DLA gate V +V = expm(0.5 * adjoint_repr[-2]) + +# contract first branch +# V - P - U - E^0_in +res0 = U @ E_in[0] +res0 = P0 @ res0 +res0 = V @ res0 + +# contract second branch + +# --U-==-+--------+ -+------+ +# | E^1_in | = | res | +# --U-==-+--------+ -+------+ +res = np.einsum("ij,jl->il", U, E_in[1]) +res = np.einsum("kl,il->ik", U, res) + +# +-----+-==-+------+ +# -V-==-| P^1 | | res | +# +-----+-==-+------+ +res = np.einsum("ijl,jl->i", P1, res) +res = V @ res + +res = res + res0 + +############################################################################## +# As a sanity check, let us compare this to the same circuit but using our default state vector simulator in PennyLane. + +@qml.qnode(qml.device("default.qubit")) +def true(): + qml.exp(-1j * theta * dla[-1].operation()) + qml.exp(-1j * 0.5 * p.operation()) + qml.exp(-1j * 0.5 * dla[-2].operation()) + return [qml.expval(op.operation()) for op in dla] + +true_res = np.array(true()) + +np.allclose(true_res, res) + +############################################################################## +# We find that indeed both results coincide and expectation value vectors are correctly transformed in :math:`(\mathfrak{g}+P)`-sim. + +############################################################################## +# Higher moments +# ~~~~~~~~~~~~~~ +# +# We can extend the above approach by a second :math:`P` gate in the circuit. +# This leads to contributions from up to the third order. The diagram for a circuit :math:`P V P U` has the following five branches. +# +# First, the zeroth- and first-order contribution. This can be seen as the branching off from the first previous diagram. +# +# .. figure:: /_static/demonstration_assets/liesim_extension/2P_first_second.png +# :width: 35% +# :align: center +# +# We also obtain the third-order diagram containing both :math:`\boldsymbol{P}^1` tensors. +# +# .. figure:: /_static/demonstration_assets/liesim_extension/2P_fourth.png +# :width: 35% +# :align: center +# +# To get the correct results, we also obtain intermediate second-order diagrams. +# +# .. figure:: /_static/demonstration_assets/liesim_extension/2P_thirds.png +# :width: 90% +# :align: center +# + +############################################################################## +# Moment vector space +# ------------------- +# +# +# The above diagrams are handy to understand the maximum moment order required for adding :math:`P` gates of a certain order. +# There is, however, a lot of redundancy due to the overcompleteness of naively looking at moments as `all` possible products between DLA elements. +# +# Instead, we can also work in the vector space of unique moments +# +# .. math:: \mathcal{M}^m := \{p | p= h_{\alpha_1} .. h_{\alpha_m+1} \notin \mathcal{M}^{m-1} \} \cup \mathcal{M}^{m-1} +# +# that is iteratively built from :math:`\mathcal{M}^0 = \mathfrak{g}.` +# +# Even though these spaces in general do `not` form +# Lie algebras, we can still compute their (pseudo) adjoint representations and use them for :math:`\mathfrak{g}`-sim as long as +# we work in a large enough space with the correct maximum moment order. +# +# We now set up the moment vector spaces starting from the DLA and keep adding linearly independent product operators. + +def Moment_step(ops, dla): + MomentX = qml.pauli.PauliVSpace(ops.copy()) + for i, op1 in enumerate(dla): + for op2 in ops[i+1:]: + prod = op1 @ op2 + # ignore scalar coefficient + pw = next(iter(prod.keys())) + + MomentX.add(pw) + + return MomentX.basis + +Moment0 = dla.copy() +Moment = [Moment0] +dim = [len(Moment0)] +for i in range(1, 5): + Moment.append(Moment_step(Moment[-1], dla)) + dim.append(len(Moment[-1])) + +dim + +############################################################################## +# +# We see that for the considered example of :math:`n = 4` we reach the +# maximum moment already for the second order +# (the additional operator in the third moment space is just the identity). +# +# We can repeat our original computation for the first moments using the +# :math:`98`-dimensional first-order moment vector space :math:`\mathcal{M}^1.` +# +# The recipe follows the exact same steps as :math:`\mathfrak{g}`-sim but using :math:`\mathcal{M}^1` instead. +# First, we compute the input expectation value vector. + +comp_moment = 1 + +e_in = np.zeros((dim[comp_moment]), dtype=float) + +for i, hi in enumerate(Moment[comp_moment]): + rho_in = qml.prod(*(I(i) + Z(i) for i in hi.wires)) + rho_in = rho_in.pauli_rep + + e_in[i] = (hi @ rho_in).trace() + +############################################################################## +# +# Next, we compute the (pseudo-)adjoint representation of :math:`\mathcal{M}^1.` + +adjoint_repr = qml.structure_constants(Moment[comp_moment]) + +############################################################################## +# +# We can now choose arbitrary DLA gates and a maximum of `one` :math:`P` gate to evolve the expectation value vector. + +e_t = e_in +e_t = expm(0.5 * adjoint_repr[dim_g-1]) @ e_t # the same U gate +e_t = expm(0.5 * adjoint_repr[74]) @ e_t # the same P gate +e_t = expm(0.5 * adjoint_repr[dim_g-2]) @ e_t # the same V gate + +############################################################################## +# The final result matches the state vector result again + +np.allclose(e_t[:dim_g], true_res) + +############################################################################## +# Limitations +# ----------- +# +# We saw how we can make use of moment vector spaces to extend :math:`\frak{g}`-sim by non-DLA elements under certain conditions. +# The upside is that while the Lie closure or construction of the associative algebra leads to an exponential DLA in :math:`n,` we +# get away with a polynomial cost in :math:`n,` as we have :math:`O(\text{dim}(\mathfrak{g})^{m_{\text{comp}}})`-sized objects +# for some fixed maximum moment order :math:`m_{\text{comp}}` in the computation, with some additional reductions due to the redundancies in the moment spaces. +# +# However, we argue that while this is interesting in theory, there is little practical utility. +# To show that, we plot the dimensions of the first and second moments against the associative algebra dimension. + +dims_dla = [] +dims_moment = [] +dims_tempdla = [] + +ns = np.arange(2, 6) + +for n in ns: + _, dla, dim_g = TFIM(n) + + Moment0 = dla.copy() + Moment = [Moment0] + dim = [len(Moment0)] + for i in range(1, 5): + Moment.append(Moment_step(Moment[-1], dla)) + dim.append(len(Moment[-1])) + + tempdla = qml.lie_closure(dla + [Moment[1][-1]], pauli=True) + + dims_dla.append(dim_g) + dims_moment.append(dim) + dims_tempdla.append(len(tempdla)) + +import matplotlib.pyplot as plt + +plt.title("Dimensions of $\\langle g + P \\rangle_{{Lie}}$ vs $\mathcal{M}^m$") + +plt.plot(ns, dims_tempdla, "o--", label="${{dim}}(\\langle g + P \\rangle_{{Lie}})$", color="tab:blue") +plt.plot(ns, 2 * (2**(2*ns - 2) - 1), "-", label="$2(2^{{2n-2}}-1)$", color="tab:blue") + +color = ["tab:orange", "tab:green", "tab:cyan"] +dims_moment = np.array(dims_moment) +for i in range(3): + plt.plot(ns, dims_moment[:, i], "x--", label=f"$dim(\mathcal{{M}}^{i})$", color=color[i]) + fitcoeff = np.polyfit(ns, dims_moment[:, i], i+2) # polynomial fit of order m+1 + plt.plot(ns, np.poly1d(fitcoeff)(ns), "-", label=f"$O(n^{{{i}+2}})$", color=color[i]) + +plt.xticks(ns) +plt.yscale("log") + +plt.legend() +plt.xlabel("n") +plt.show() + +############################################################################## +# +# First, we note that the maximum moment is quickly reached for small system sizes. In that case we have :math:`m_\text{comp} = m_\text{max}` +# so we might as well look at the associative algebra and perform regular :math:`\mathfrak{g}`-sim. +# Secondly, we also note that the dimensions quickly explode and become hard to handle in reasonable times. +# +# For example, in all cases here there are no non-trivial third-order moments. We would have to go to :math:`n = 6,` +# for which there are :math:`1980` elements in :math:`\mathcal{M}^3,` which corresponds to iterating over +# :math:`1980^3 \approx 2^{32}` commutators to compute the (pseudo-)adjoint representation. This is +# already a stretch to accomplish with the available tools. +# +# Hence, this method is effectively restricted to very few non-DLA gates of Ising-type DLAs, +# rendering the method overall niche for practical applications. On the other hand, we gained some new theoretical insights +# into the relationship between the simulability of a quantum circuit and its DLA. In particular, we constructed a polynomial +# algorithm in the number of qubits :math:`n,` for simulating DLA circuits with up to :math:`m_\text{comp}` moments in :math:`\mathcal{O}(\text{dim}\left(\mathfrak{g}(n)\right)^{m_\text{comp} + 2}) = \mathcal{O}(\text{poly}(n)).` + + + +############################################################################## +# +# References +# ---------- +# +# .. [#Somma] +# +# Rolando D. Somma +# "Quantum Computation, Complexity, and Many-Body Physics" +# `arXiv:quant-ph/0512209 `__, 2005. +# +# .. [#Somma2] +# +# Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill +# "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models" +# `arXiv:quant-ph/0601030 `__, 2006. +# +# .. [#Galitski] +# +# Victor Galitski +# "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach" +# `arXiv:1012.2873 `__, 2010. +# +# .. [#Goh] +# +# Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage +# "Lie-algebraic classical simulations for variational quantum computing" +# `arXiv:2308.01432 `__, 2023. +# +# .. [#Wiersema] +# +# Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov +# "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension" +# `arXiv:2309.05690 `__, 2023. +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_liesim_extension/metadata.json b/demonstrations_v2/tutorial_liesim_extension/metadata.json new file mode 100644 index 0000000000..cb14378ab7 --- /dev/null +++ b/demonstrations_v2/tutorial_liesim_extension/metadata.json @@ -0,0 +1,100 @@ +{ + "title": "(g + P)-sim: Extending g-sim by non-DLA observables and gates", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-06-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/liesim_extension/thumbnail_liesim_extension.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_liesim_extension.png" + } + ], + "seoDescription": "(g + P)-sim: Extending g-sim by non-DLA observables and gates", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "Somma2", + "type": "preprint", + "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", + "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", + "year": "2006", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0601030", + "url": "https://arxiv.org/abs/quant-ph/0601030" + }, + { + "id": "Galitski", + "type": "preprint", + "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", + "authors": "Victor Galitski", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1012.2873", + "url": "https://arxiv.org/abs/1012.2873" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2308.01432" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_liesim_extension/requirements.in b/demonstrations_v2/tutorial_liesim_extension/requirements.in new file mode 100644 index 0000000000..8132c3977e --- /dev/null +++ b/demonstrations_v2/tutorial_liesim_extension/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_local_cost_functions/demo.py b/demonstrations_v2/tutorial_local_cost_functions/demo.py new file mode 100644 index 0000000000..5d5d8db870 --- /dev/null +++ b/demonstrations_v2/tutorial_local_cost_functions/demo.py @@ -0,0 +1,534 @@ +""" +Alleviating barren plateaus with local cost functions +===================================================== + +.. meta:: + :property="og:description": Local cost functions are cost formulations for variational quantum circuits that are more robust to barren plateaus. + :property="og:image": ../_static/demonstration_assets/local_cost_functions/Cerezo_et_al_local_cost_functions.png + +.. related:: + + tutorial_barren_plateaus Barren plateaus in quantum neural networks + +*Author: Thomas Storwick — Posted: 09 September 2020. Last updated: 28 January 2021.* + +Barren Plateaus +--------------- + +:doc:`Barren plateaus ` are large regions of the cost function's parameter space +where the variance of the gradient is almost 0; or, put another way, the +cost function landscape is flat. This means that a variational circuit +initialized in one of these areas will be untrainable using any gradient-based +algorithm. + + + +In `"Cost-Function-Dependent Barren Plateaus in Shallow Quantum Neural +Networks" `__ [#Cerezo2020]_, Cerezo et al. demonstrate the +idea that the barren plateau +phenomenon can, under some circumstances, be avoided by using cost functions that only have +information from part of the circuit. These *local* cost functions can be +more robust against noise, and may have better-behaved gradients with no +plateaus for shallow circuits. + + +.. figure:: ../_static/demonstration_assets/local_cost_functions/Cerezo_et_al_local_cost_functions.png + :align: center + :width: 50% + + Taken from Cerezo et al. [#Cerezo2020]_. + +Many variational quantum algorithms are constructed to use global cost functions. +Information from the entire measurement is used to analyze the result of the +circuit, and a cost function is calculated from this to quantify the circuit's +performance. A local cost function only considers information from a few qubits, +and attempts to analyze the behavior of the entire circuit from this limited scope. + + +Cerezo et al. also handily prove that these local cost functions are bounded by the +global ones, i.e., if a global cost function is formulated in the manner described +by Cerezo et al., then the value of its corresponding local cost function will always be +less than or equal to the value of the global cost function. + +In this notebook, we investigate the effect of barren plateaus in +variational quantum algorithms, and how they can be mitigated using +local cost functions. + + +We first need to import the following modules. +""" + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt +from matplotlib.ticker import LinearLocator, FormatStrFormatter + +np.random.seed(42) + +###################################################################### +# Visualizing the problem +# ----------------------- +# +# To start, let's look at the task of learning the identity gate +# across multiple qubits. This will help us visualize the problem and get +# a sense of what is happening in the cost landscape. +# +# First we define a number of wires we want to train on. The work by +# Cerezo et al. shows that circuits are trainable under certain regimes, so +# how many qubits we train on will effect our results. + +wires = 6 +dev = qml.device("lightning.qubit", wires=wires, shots=10000) + + +###################################################################### +# Next, we want to define our QNodes and our circuit ansatz. For this +# simple example, an ansatz that works well is simply a rotation along X, +# and a rotation along Y, repeated across all the qubits. +# +# We will also define our cost functions here. Since we are trying to +# learn the identity gate, a natural cost function is 1 minus the probability of measuring the +# zero state, denoted here as :math:`1 - p_{|0\rangle}.` +# +# .. math:: C = \langle \psi(\theta) | \left(I - |0\rangle \langle 0|\right) | \psi(\theta) \rangle =1-p_{|0\rangle} +# +# We will apply this across all qubits for our global cost function, i.e., +# +# .. math:: C_{G} = \langle \psi(\theta) | \left(I - |00 \ldots 0\rangle \langle 00 \ldots 0|\right) | \psi(\theta) \rangle = 1-p_{|00 \ldots 0\rangle} +# +# and for the local cost function, we will sum the individual contributions from each qubit: +# +# .. math:: C_L = \langle \psi(\theta) | \left(I - \frac{1}{n} \sum_j |0\rangle \langle 0|_j\right)|\psi(\theta)\rangle = 1 - \sum_j p_{|0\rangle_j}. +# +# It might be clear to some readers now why this function can perform better. +# By formatting our local cost function in this way, we have essentially divided +# the problem up into multiple single-qubit terms, and summed all +# the results up. +# +# To implement this, we will define a separate QNode for the local cost +# function and the global cost function. +# +# + + +def global_cost_simple(rotations): + for i in range(wires): + qml.RX(rotations[0][i], wires=i) + qml.RY(rotations[1][i], wires=i) + return qml.probs(wires=range(wires)) + +def local_cost_simple(rotations): + for i in range(wires): + qml.RX(rotations[0][i], wires=i) + qml.RY(rotations[1][i], wires=i) + return [qml.probs(wires=i) for i in range(wires)] + +global_circuit = qml.QNode(global_cost_simple, dev, interface="autograd") + +local_circuit = qml.QNode(local_cost_simple, dev, interface="autograd") + +def cost_local(rotations): + return 1 - np.sum([i for (i, _) in local_circuit(rotations)]) / wires + +def cost_global(rotations): + return 1 - global_circuit(rotations)[0] + + + +###################################################################### +# To analyze each of the circuits, we provide some random initial +# parameters for each rotation. +# + +RX = np.random.uniform(low=-np.pi, high=np.pi) +RY = np.random.uniform(low=-np.pi, high=np.pi) +rotations = [[RX for i in range(wires)], [RY for i in range(wires)]] + + +###################################################################### +# Examining the results: +# + +print("Global Cost: {: .7f}".format(cost_global(rotations))) +print("Local Cost: {: .7f}".format(cost_local(rotations))) + +qml.drawer.use_style('black_white') +fig1, ax1 = qml.draw_mpl(global_circuit, decimals=2)(rotations) +fig1.suptitle("Global Cost", fontsize='xx-large') +plt.show() + +fig2, ax2 = qml.draw_mpl(local_circuit, decimals=2)(rotations) +fig2.suptitle("Local Cost", fontsize='xx-large') +plt.show() + + +###################################################################### +# With this simple example, we can visualize the cost function, and see +# the barren plateau effect graphically. Although there are :math:`2n` (where :math:`n` is the +# number of qubits) parameters, in order to plot the cost landscape +# we must constrain ourselves. We will consider the case where all X rotations +# have the same value, and all the Y rotations have the same value. +# +# Firstly, we look at the global cost function. When plotting the cost +# function across 6 qubits, much of the cost landscape is flat, and +# difficult to train (even with a circuit depth of only 2!). This effect +# will worsen as the number of qubits increases. +# + +def generate_surface(cost_function): + Z = [] + Z_assembler = [] + + X = np.arange(-np.pi, np.pi, 0.25) + Y = np.arange(-np.pi, np.pi, 0.25) + X, Y = np.meshgrid(X, Y) + + for x in X[0, :]: + for y in Y[:, 0]: + rotations = [[x for i in range(wires)], [y for i in range(wires)]] + Z_assembler.append(cost_function(rotations)) + Z.append(Z_assembler) + Z_assembler = [] + + Z = np.asarray(Z) + return Z + +def plot_surface(surface): + X = np.arange(-np.pi, np.pi, 0.25) + Y = np.arange(-np.pi, np.pi, 0.25) + X, Y = np.meshgrid(X, Y) + fig = plt.figure() + ax = fig.add_subplot(111, projection="3d") + surf = ax.plot_surface(X, Y, surface, cmap="viridis", linewidth=0, antialiased=False) + ax.set_zlim(0, 1) + ax.zaxis.set_major_locator(LinearLocator(10)) + ax.zaxis.set_major_formatter(FormatStrFormatter("%.02f")) + plt.show() + + +global_surface = generate_surface(cost_global) +plot_surface(global_surface) + +###################################################################### +# However, when we change to the local cost function, the cost landscape +# becomes much more trainable as the size of the barren plateau decreases. +# +# + +local_surface = generate_surface(cost_local) +plot_surface(local_surface) + + + +###################################################################### +# Those are some nice pictures, but how do they reflect actual +# trainability? Let us try training both the local and global cost +# functions. +# To simplify this model, let's modify our cost function from +# +# .. math:: C_{L} = 1-\sum p_{|0\rangle}, +# +# where we sum the marginal probabilities of each qubit, to +# +# .. math:: C_{L} = 1-p_{|0\rangle}, +# +# where we only consider the probability of a single qubit to be in the 0 state. +# +# While we're at it, let us make our ansatz a little more like one we would encounter while +# trying to solve a VQE problem, and add entanglement. + +def global_cost_simple(rotations): + for i in range(wires): + qml.RX(rotations[0][i], wires=i) + qml.RY(rotations[1][i], wires=i) + for i in range(wires - 1): + qml.CNOT([i, i + 1]) + return qml.probs(wires=range(wires)) + +def local_cost_simple(rotations): + for i in range(wires): + qml.RX(rotations[0][i], wires=i) + qml.RY(rotations[1][i], wires=i) + for i in range(wires - 1): + qml.CNOT([i, i + 1]) + return qml.probs(wires=[0]) + +global_circuit = qml.QNode(global_cost_simple, dev, interface="autograd") + +local_circuit = qml.QNode(local_cost_simple, dev, interface="autograd") + +def cost_local(rotations): + return 1 - local_circuit(rotations)[0] + +def cost_global(rotations): + return 1 - global_circuit(rotations)[0] + + + +###################################################################### +# Of course, now that we've changed both our cost function and our circuit, +# we will need to scan the cost landscape again. + + +global_surface = generate_surface(cost_global) +plot_surface(global_surface) + +local_surface = generate_surface(cost_local) +plot_surface(local_surface) + + +###################################################################### +# It seems our changes didn't significantly alter the overall cost landscape. +# This probably isn't a general trend, but it is a nice surprise. +# Now, let us get back to training the local and global cost functions. +# Because we have a visualization of the total cost landscape, +# let's pick a point to exaggerate the problem. One of the worst points in the +# landscape is :math:`(\pi,0)` as it is in the middle of the plateau, so let's use that. + + +rotations = np.array([[3.] * len(range(wires)), [0.] * len(range(wires))], requires_grad=True) +opt = qml.GradientDescentOptimizer(stepsize=0.2) +steps = 100 +params_global = rotations +for i in range(steps): + # update the circuit parameters + params_global = opt.step(cost_global, params_global) + + if (i + 1) % 5 == 0: + print("Cost after step {:5d}: {: .7f}".format(i + 1, cost_global(params_global))) + if cost_global(params_global) < 0.1: + break +fig, ax = qml.draw_mpl(global_circuit, decimals=2)(params_global) +plt.show() + + +###################################################################### +# After 100 steps, the cost function is still exactly 1. Clearly we are in +# an "untrainable" area. Now, let us limit ourselves to the local cost +# function and see how it performs. +# + +rotations = np.array([[3. for i in range(wires)], [0. for i in range(wires)]], requires_grad=True) +opt = qml.GradientDescentOptimizer(stepsize=0.2) +steps = 100 +params_local = rotations +for i in range(steps): + # update the circuit parameters + params_local = opt.step(cost_local, params_local) + + if (i + 1) % 5 == 0: + print("Cost after step {:5d}: {: .7f}".format(i + 1, cost_local(params_local))) + if cost_local(params_local) < 0.05: + break + +fig, ax = qml.draw_mpl(local_circuit, decimals=2)(params_local) +plt.show() + + +###################################################################### +# It trained! And much faster than the global case. However, we know our +# local cost function is bounded by the global one, but just how much +# have we trained it? +# + +cost_global(params_local) + + +###################################################################### +# Interestingly, the global cost function is still 1. If we trained the +# local cost function, why hasn't the global cost function changed? +# +# The answer is that we have trained the global cost a *little bit*, but +# not enough to see a change with only 10000 shots. To see the effect, +# we'll need to increase the number of shots to an unreasonable amount. +# Instead, making the backend analytic by setting shots to ``None``, gives +# us the exact representation. +# + +_dev = qml.device("lightning.qubit", wires=wires, shots=None) +global_circuit = qml.QNode(global_cost_simple, _dev, interface="autograd") +print( + "Current cost: " + + str(cost_global(params_local)) + + ".\nInitial cost: " + + str(cost_global([[3.0 for i in range(wires)], [0 for i in range(wires)]])) + + ".\nDifference: " + + str( + cost_global([[3.0 for i in range(wires)], [0 for i in range(wires)]]) + - cost_global(params_local) + ) +) + + +###################################################################### +# Our circuit has definitely been trained, but not a useful amount. If we +# attempt to use this circuit, it would act the same as if we never trained at all. +# Furthermore, if we now attempt to train the global cost function, we are +# still firmly in the plateau region. In order to fully train the global +# circuit, we will need to increase the locality gradually as we train. +# + + +def tunable_cost_simple(rotations): + for i in range(wires): + qml.RX(rotations[0][i], wires=i) + qml.RY(rotations[1][i], wires=i) + for i in range(wires - 1): + qml.CNOT([i, i + 1]) + return qml.probs(range(locality)) + +def cost_tunable(rotations): + return 1 - tunable_circuit(rotations)[0] + +tunable_circuit = qml.QNode(tunable_cost_simple, dev, interface="autograd") +locality = 2 +params_tunable = params_local +fig, ax = qml.draw_mpl(tunable_circuit, decimals=2)(params_tunable) +plt.show() +print(cost_tunable(params_tunable)) + +locality = 2 +opt = qml.GradientDescentOptimizer(stepsize=0.1) +steps = 600 +for i in range(steps): + # update the circuit parameters + params_tunable = opt.step(cost_tunable, params_tunable) + + runCost = cost_tunable(params_tunable) + if (i + 1) % 10 == 0: + print( + "Cost after step {:5d}: {: .7f}".format(i + 1, runCost) + + ". Locality: " + + str(locality) + ) + + if runCost < 0.1 and locality < wires: + print("---Switching Locality---") + locality += 1 + continue + elif runCost < 0.1 and locality >= wires: + break +fig, ax = qml.draw_mpl(tunable_circuit, decimals=2)(params_tunable) +plt.show() + + +###################################################################### +# A more thorough analysis +# ------------------------ +# +# Now the circuit can be trained, even though we started from a place +# where the global function has a barren plateau. The significance of this +# is that we can now train from every starting location in this example. +# +# But, how often does this problem occur? If we wanted to train this +# circuit from a random starting point, how often would we be stuck in a +# plateau? To investigate this, let's attempt to train the global cost +# function using random starting positions and count how many times we run +# into a barren plateau. +# +# Let's use a number of qubits we are more likely to use in a real variational +# circuit: n=10. We will say that after +# 400 steps, any run with a cost function of less than 0.9 (chosen +# arbitrarily) will probably be trainable given more time. Any run with a +# greater cost function will probably be in a plateau. +# +# This may take up to 15 minutes. +# + +samples = 10 +plateau = 0 +trained = 0 +opt = qml.GradientDescentOptimizer(stepsize=0.2) +steps = 400 +wires = 8 + +dev = qml.device("lightning.qubit", wires=wires, shots=10000) +global_circuit = qml.QNode(global_cost_simple, dev, interface="autograd") + +for runs in range(samples): + print("--- New run! ---") + has_been_trained = False + + params_global = np.random.uniform(-np.pi, np.pi, (2, wires), requires_grad=True) + + for i in range(steps): + # update the circuit parameters + params_global = opt.step(cost_global, params_global) + + if (i + 1) % 20 == 0: + print("Cost after step {:5d}: {: .7f}".format(i + 1, cost_global(params_global))) + if cost_global(params_global) < 0.9: + has_been_trained = True + break + if has_been_trained: + trained = trained + 1 + else: + plateau = plateau + 1 + print("Trained: {:5d}".format(trained)) + print("Plateau'd: {:5d}".format(plateau)) + + +samples = 10 +plateau = 0 +trained = 0 +opt = qml.GradientDescentOptimizer(stepsize=0.2) +steps = 400 +wires = 8 + +dev = qml.device("lightning.qubit", wires=wires, shots=10000) +tunable_circuit = qml.QNode(tunable_cost_simple, dev, interface="autograd") + +for runs in range(samples): + locality = 1 + print("--- New run! ---") + has_been_trained = False + + params_tunable = np.random.uniform(-np.pi, np.pi, (2, wires), requires_grad=True) + for i in range(steps): + # update the circuit parameters + params_tunable = opt.step(cost_tunable, params_tunable) + + runCost = cost_tunable(params_tunable) + if (i + 1) % 10 == 0: + print( + "Cost after step {:5d}: {: .7f}".format(i + 1, runCost) + + ". Locality: " + + str(locality) + ) + + if runCost < 0.5 and locality < wires: + print("---Switching Locality---") + locality += 1 + continue + elif runCost < 0.1 and locality >= wires: + trained = trained + 1 + has_been_trained = True + break + if not has_been_trained: + plateau = plateau + 1 + print("Trained: {:5d}".format(trained)) + print("Plateau'd: {:5d}".format(plateau)) + + +###################################################################### +# In the global case, anywhere between 70-80% of starting positions are +# untrainable, a significant number. It is likely that, as the complexity of our +# ansatz—and the number of qubits—increases, this factor will increase. +# +# We can compare that to our local cost function, where every single area trained, +# and most even trained in less time. While these examples are simple, +# this local-vs-global cost behaviour has been shown to extend to more +# complex problems. + + +############################################################################## +# References +# ---------- +# +# .. [#Cerezo2020] +# +# Cerezo, M., Sone, A., Volkoff, T., Cincio, L., and Coles, P. (2020). +# Cost-Function-Dependent Barren Plateaus in Shallow Quantum Neural Networks. +# `arXiv:2001.00550 `__ +# +# diff --git a/demonstrations_v2/tutorial_local_cost_functions/metadata.json b/demonstrations_v2/tutorial_local_cost_functions/metadata.json new file mode 100644 index 0000000000..39db0e05a9 --- /dev/null +++ b/demonstrations_v2/tutorial_local_cost_functions/metadata.json @@ -0,0 +1,44 @@ +{ + "title": "Alleviating barren plateaus with local cost functions", + "authors": [ + { + "username": "tstorwick" + } + ], + "dateOfPublication": "2020-09-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_alleviating_barren_plateaus.png" + } + ], + "seoDescription": "Local cost functions are cost formulations for variational quantum circuits that are more robust to barren plateaus.", + "doi": "", + "references": [ + { + "id": "Cerezo2020", + "type": "article", + "title": "Cost-Function-Dependent Barren Plateaus in Shallow Quantum Neural Networks", + "authors": "Cerezo, M., Sone, A., Volkoff, T., Cincio, L., and Coles, P.", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2001.00550" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2001.00550" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_local_cost_functions/requirements.in b/demonstrations_v2/tutorial_local_cost_functions/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_local_cost_functions/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_magic_state_distillation/demo.py b/demonstrations_v2/tutorial_magic_state_distillation/demo.py new file mode 100644 index 0000000000..8532c500a8 --- /dev/null +++ b/demonstrations_v2/tutorial_magic_state_distillation/demo.py @@ -0,0 +1,335 @@ +r""" +Magic state distillation +======================== + +While many typical quantum algorithms can be described via simple quantum circuits, others require a +more expressive programming model. Beyond the description of unitary operators, an algorithm may +require the probabilistic execution of quantum instructions, real-time feedback from quantum +measurements, real-time classical computation, and unbounded repetitions of program segments. Such +programs are generally also called **hybrid quantum-classical programs**. + +`Catalyst `__ for PennyLane brings this powerful +programming model to PennyLane to develop and compile hybrid quantum programs in Python. + +One such algorithm that goes beyond the circuit model is the **magic state distillation** routine, +developed to enable practical universal gate sets on fault-tolerant hardware architectures. In this +tutorial, we will see how we can use Catalyst’s tight integration of quantum and classical code, +both within the language and during execution, to develop a magic state distillation routine. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_magic-state-distillation_2024-04-23.png + :align: center + :width: 60% + :target: javascript:void(0) + +Introduction +------------ + +The idea behind this algorithm is based on the availability of a quantum computer that is only +capable of running gates from the :doc:`Clifford group `, which is generated by the operators +:math:`\{H, S, CNOT\}.` This computer alone is provably not a universal quantum computer, meaning +that there are quantum algorithms it would not be capable of running. + +In order to achieve universal quantum computing, only a single additional “non-Clifford” gate is +required, which cannot be constructed from Clifford gates alone. As demonstrated by `Bravyi and +Kitaev in 2005 `__, certain noisy qubit states can be +purified into so-called *magic states*, which can in turn be used to implement non-Clifford gates by +consuming the magic state. + +In practice, it is not necessarily easy to generate magic states. However, provided we have a method +of generating (noisy) quantum states that are just “close enough” to magic states, we can purify the +noisy states to be arbitrarily close to ideal magic states. This is the procedure performed by the +magic state distillation algorithm. + +T-Type Magic States +------------------- + +In this tutorial we will produce T-type magic states via purification. T-type states are a +particular type of magic states that are defined as the eigenvectors of the :math:`e^{i\pi/4}SH` +operator (an :math:`H` gate followed by an :math:`S` gate): + +.. math:: \frac{e^{i\pi/4}}{\sqrt{2}}\begin{bmatrix} 1 & 1 \\ i & -i \end{bmatrix} + +In the computational basis, these eigenvectors can be expressed as: + +.. math:: | T_0\rangle = \cos(\beta) | 0\rangle + e^{i\pi/4}\sin(\beta) | 1\rangle, + +.. math:: | T_1\rangle = \sin(\beta)| 0\rangle - e^{i\pi/4}\cos(\beta)| 1\rangle, + +with :math:`\beta = \frac{1}{2}\arccos(\frac{1}{\sqrt{3}}).` + +First, let’s write a function to generate a pure :math:`|T_0\rangle` state via a Pauli-Y rotation +and a phase shift: +""" + +import pennylane as qml + +import jax; jax.config.update('jax_platform_name', 'cpu') +from jax import numpy as jnp +from jax import random + +b = 0.5 * jnp.arccos(1 / jnp.sqrt(3)) + +def generate_T0(wire): + qml.RY(2 * b, wires=wire) + qml.PhaseShift(jnp.pi / 4, wires=wire) + +###################################################################### +# Now, let’s create up a *faulty* :math:`| T_0\rangle` state-generating function. We can do this by +# perturbing the :math:`|T_0\rangle` state by a random component of scale :math:`r.` +# +# Note that since this is a random function, we need to pass it a PRNG key to satisfy `JAX’s stateless +# implementation `__. +# + +def faulty_generate_T0(wire, key, r): + key, subkey = random.split(key) + disturbance = random.uniform(subkey, minval=-r, maxval=r) + qml.RY(2 * (b + disturbance), wires=wire) + + key, subkey = random.split(key) + disturbance = random.uniform(subkey, minval=-r, maxval=r) + qml.PhaseShift(jnp.pi / 4 + disturbance, wires=wire) + return key + +###################################################################### +# Purification algorithm +# ---------------------- +# +# The purification algorithm for magic states was first introduced by Bravyi and +# Kitaev [#bravyi2005]_, although we will be using the implementation +# described in `A study of the robustness of magic state distillation against Clifford gate +# faults `__ +# by Tomas Jochym-O’Connor [#tomas2012]_. The process is as follows: +# +# - Prepare five copies of noisy :math:`| T_0\rangle` states; +# +# - Apply the decoding circuit of the 5-qubit error correction code (refer to the sources above for +# more details on how this works); +# +# - Perform a “syndrome” measurement on the first four wires (this is error correction terminology, +# normally meaning a measurement to detect the presence of errors). +# +# Remarkably, if all the measurements come out as 0, we will have obtained a noisy +# :math:`| T_1\rangle` state of provably higher fidelity than our input states. We can then convert +# the :math:`| T_1\rangle` state into a :math:`| T_0\rangle` state using our Clifford gate set, namely +# a Hadamard gate followed by a Pauli-Y gate. This process can then be repeated to achieve even higher +# fidelities. +# +# Note that if any of the measurements produced a 1, the algorithm failed and we need to restart the +# process. This is where the hybrid quantum programming features come in: we need to obtain real-time +# measurement results, and based on those decide on the next quantum instructions to execute. +# +# First, let’s define the error correction decoding circuit. +# + +def error_correction_decoder(wires): + """Error correction decoder for the 5-qubit error correction code generated + by stabilizers XZZXI, IXZZX, XIXZZ, ZXIXZ. + """ + w0, w1, w2, w3, w4 = wires + + qml.CNOT(wires=[w1, w0]) + qml.CZ(wires=[w1, w0]) + qml.CZ(wires=[w1, w2]) + qml.CZ(wires=[w1, w4]) + + qml.CNOT(wires=[w2, w0]) + qml.CZ(wires=[w2, w3]) + qml.CZ(wires=[w2, w4]) + + qml.CNOT(wires=[w3, w0]) + + qml.CNOT(wires=[w4, w0]) + qml.CZ(wires=[w4, w0]) + + qml.PauliZ(wires=w0) + qml.PauliZ(wires=w1) + qml.PauliZ(wires=w4) + + qml.Hadamard(wires=w1) + qml.Hadamard(wires=w2) + qml.Hadamard(wires=w3) + qml.Hadamard(wires=w4) + +print(qml.draw(error_correction_decoder)(range(5))) + +###################################################################### +# We’ll also define some helper functions to perform a conditional reset of a qubit into the +# :math:`| 0\rangle` state, which we will use whenever the algorithm needs to restart. +# +# Here we use a mid-circuit measurement and a classically-controlled Pauli-X gate: +# + +from catalyst import measure + +def measure_and_reset(wire): + """Measure a wire and then reset it back to the |0⟩ state.""" + + m = measure(wire) + + if m: + qml.PauliX(wires=wire) + + return m + +###################################################################### +# Note in the above: +# +# - we import and use :func:`catalyst.measure`, rather than using :func:`pennylane.measure`. +# +# - we use standard Python ``if`` statements. When we quantum just-in-time compile the entire +# algorithm, we will utilize the :doc:`AutoGraph ` feature of Catalyst to automatically capture Python +# control flow around quantum operations. +# +# Now we come to the main part of the algorithm, which we will JIT-compile using Catalyst and +# :func:`~pennylane.qjit`! +# +# The structure of the algorithm consists of a *repeat-until-success* loop. This means we execute a +# piece of code with a probabilistic outcome, and if the outcome is not the desired result, we go back +# and repeat the code until we do get the desired result. In our case the desired result is a syndrome +# measurement of 0. We’ll encode this repeat-until-success loop with a while loop around quantum +# operations. +# + +dev = qml.device("lightning.qubit", wires=5) + +@qml.qjit(autograph=True) +@qml.qnode(dev) +def state_distillation(random_key, r): + key = random_key + syndrome = True + + while syndrome: + # generate 5 faulty T0 states + key = faulty_generate_T0(0, key, r) + key = faulty_generate_T0(1, key, r) + key = faulty_generate_T0(2, key, r) + key = faulty_generate_T0(3, key, r) + key = faulty_generate_T0(4, key, r) + + # run the error correction decoding algorithm + # on the generated faulty states + error_correction_decoder(wires=(0, 1, 2, 3, 4)) + + # measure and reset all wires + m1 = measure_and_reset(1) + m2 = measure_and_reset(2) + m3 = measure_and_reset(3) + m4 = measure_and_reset(4) + + syndrome = m1 + m2 + m3 + m4 > 0 + + if syndrome: + # reset wire 0 and return to repeat the loop + measure_and_reset(0) + + # if all measurements were 0, then the loop + # has exited, and we know that wire 0 is in an approximate T1 state. + + # We can convert the T1 state back to T0 + # by applying a Hadamard and Pauli-Y rotation + qml.Hadamard(wires=0) + qml.PauliY(wires=0) + + return qml.state() + +###################################################################### +# Benchmark +# --------- +# +# To confirm that we are, in fact, successfully distilling T-type magic states, we will measure the +# fidelity of a purified magic state compared to the fidelity of the original noisy state (with +# respect to an ideal :math:`| T_0\rangle` state). The results are averaged over a number of runs to +# account for the randomness in the noise. +# + +import matplotlib.pyplot as plt +import os + +dev_5_qubits = qml.device("default.qubit", wires=5) + +@jax.jit +@qml.qnode(dev_5_qubits, interface="jax") +def T0_state(): + generate_T0(0) + return qml.state() + +@jax.jit +@qml.qnode(dev_5_qubits, interface="jax") +def faulty_T0_state(random_key, r): + faulty_generate_T0(0, random_key, r) + return qml.state() + +exact_T0_state = T0_state() +perturbations = jnp.linspace(0, 1, 20) +repeats = 200 + +pres = [] +posts = [] + +for r in perturbations: + + pre_total = 0. + post_total = 0. + + for i in range(repeats): + key = random.PRNGKey(i) + + # generate a faulty T0 state + faulty_qubit_state = faulty_T0_state(key, r) + # perform magic state distillation on the faulty T0 state + distilled_qubit_state = state_distillation(key, r) + + # compute the fidelity of the faulty and exact T0 state + base_fidelity = jnp.abs(jnp.dot(faulty_qubit_state, jnp.conj(exact_T0_state))) ** 2 + pre_total += base_fidelity + + # compute the fidelity of the distilled/purified T0 state and the exact T0 state + distilled_fidelity = jnp.abs(jnp.dot(distilled_qubit_state, jnp.conj(exact_T0_state))) ** 2 + post_total += distilled_fidelity + + pres.append(pre_total / repeats) + posts.append(post_total / repeats) + +plt.style.use("bmh") +plt.figure(figsize=(10, 6)) +plt.plot(perturbations, pres, label="pre-distillation") +plt.plot(perturbations, posts, label="post-distillation") +plt.title("Average Fidelity for T-Type Magic States", fontsize=18) +plt.legend(fontsize=12) +plt.xlabel("noisy perturbation", fontsize=14) +plt.ylabel(r"fidelity w.r.t $\left|T_0\right\rangle$", fontsize=14) +plt.show() + +###################################################################### +# From the plot we can see that the distillation procedure can significantly improve the fidelity of +# the magic state, provided that the input state has at least ~82% fidelity. +# +# Conclusion +# ---------- +# +# In this tutorial, we have implemented the magic state distillation algorithm to distill a noisy +# T-type magic state into one of higher fidelity using PennyLane and Catalyst. This was done by +# decoding the :math:`| T_0\rangle` state with the 5-qubit error correction code, and selecting for +# the desired measurement outcomes. +# +# This algorithm can be repeated many times, in order to obtain :math:`| T_0\rangle` states of +# arbitrary fidelity. Non-Clifford gates can then be implemented on a fault-tolerant architecture, +# achieving universal quantum computation. +# +# References +# ---------- +# +# .. [#bravyi2005] +# +# Sergey Bravyi and Alexei Kitaev. "Universal quantum computation with +# ideal Clifford gates and noisy ancillas." `Physical Review A 71.2 (2005). +# `__ +# +# .. [#tomas2012] +# +# Tomas Jochym-O'Connor, et al. "The robustness of magic state +# distillation against errors in Clifford gates." +# `arXiv preprint arXiv:1205.6715 (2012) `__. + +###################################################################### diff --git a/demonstrations_v2/tutorial_magic_state_distillation/metadata.json b/demonstrations_v2/tutorial_magic_state_distillation/metadata.json new file mode 100644 index 0000000000..4ff025ae03 --- /dev/null +++ b/demonstrations_v2/tutorial_magic_state_distillation/metadata.json @@ -0,0 +1,52 @@ +{ + "title": "Magic state distillation", + "authors": [ + { + "username": "david" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_magic-state-distillation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_magic-state-distillation.png" + } + ], + "seoDescription": "Use PennyLane, Catalyst, and QJIT to improve the fidelity of magic T-states.", + "doi": "", + "references": [ + { + "id": "bravyi2005", + "type": "article", + "title": "Universal quantum computation with ideal Clifford gates and noisy ancillas.", + "authors": "Sergey Bravyi, Alexei Kitaev", + "year": "2005", + "journal": "Physical Review A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.71.022316" + }, + { + "id": "tomas2012", + "type": "article", + "title": "The robustness of magic state distillation against errors in Clifford gates.", + "authors": "Tomas Jochym-O'Connor", + "year": "2012", + "journal": "arXiv", + "url": "https://arxiv.org/abs/1205.6715" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1205.6715" + ], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_magic_state_distillation/requirements.in b/demonstrations_v2/tutorial_magic_state_distillation/requirements.in new file mode 100644 index 0000000000..21024b5740 --- /dev/null +++ b/demonstrations_v2/tutorial_magic_state_distillation/requirements.in @@ -0,0 +1,5 @@ +pennylane-catalyst +jax +jaxlib +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_mapping/demo.py b/demonstrations_v2/tutorial_mapping/demo.py new file mode 100644 index 0000000000..be1ce23b5d --- /dev/null +++ b/demonstrations_v2/tutorial_mapping/demo.py @@ -0,0 +1,343 @@ +r""" + +Mapping fermionic operators to qubit operators +============================================== + +Simulating quantum systems stands as one of the most anticipated applications of quantum +chemistry with the potential to transform our understanding of chemical and physical systems. These +simulations typically require mapping schemes that transform fermionic representations into qubit +representations. There are a variety of mapping schemes used in quantum computing but the +conventional ones are the Jordan-Wigner, Parity, and Bravyi-Kitaev transformations [#Tranter]_. In +this demo, you will learn about these mapping schemes and their implementation in PennyLane. You +will also learn how to use these mappings in the context of computing the ground state energy +of a molecular system. + +.. figure:: ../_static/demonstration_assets/mapping/long_image.png + :align: center + :width: 80% + :target: javascript:void(0) + +Jordan-Wigner Mapping +--------------------- +The state of a quantum system in the `second quantized `__ +formalism is typically represented in the occupation-number basis. For fermions, the occupation +number is either :math:`0` or :math:`1` as a result of the Pauli exclusion principle. The +occupation-number basis states can be represented by a vector that is constructed by +applying the fermionic creation operators, :math:`a^\dagger,` to a vacuum state: + +.. math:: + + a^\dagger | 0 \rangle = | 1 \rangle. + +Similarly, electrons can be removed from a state by applying the fermionic annihilation +operators :math:`a:` + +.. math:: + + a | 1 \rangle = | 0 \rangle. + +An intuitive way to represent a fermionic system in the qubit basis is to store the fermionic +occupation numbers in qubit states. This requires constructing qubit creation and annihilation +operators that can be applied to an initial state to provide the desired occupation number state. +These operators are defined as + +.. math:: + + Q^{\dagger}_j = \frac{1}{2}(X_j - iY_j), + +and + +.. math:: + + Q_j = \frac{1}{2}(X_j + iY_j), + +where :math:`X` and :math:`Y` are Pauli operators. However, an important property of fermionic +creation and annihilation operators is the anti-commutation relations between them, which is not +preserved by directly using the analogous qubit operators. + +.. math:: + + [a^{\dagger}_j, a^{\dagger}_k]_+ = 0, \:\:\:\:\:\:\: [a_j, a_k]_+=0, \:\:\:\:\:\:\: [a_j, a^{\dagger}_k]_+ = \delta_{jk} I, + +where :math:`I` is the identity operator. These relations are essential for capturing the Pauli +exclusion principle which requires the fermionic wave function to be antisymmetric. The +anti-commutation relations between fermionic operators can be incorporated by adding a sequence of +Pauli :math:`Z` operators to the qubit operators (can you verify that?). According to these, +the fermionic creation and annihilation operators can be represented in the basis of Pauli operators: + +.. math:: + + a_{j}^{\dagger} = \frac{1}{2}(X_j - iY_j) \otimes_{k`__, +:math:`a_{5}^{\dagger},` which creates an electron in the fifth orbital. One +way to do this in PennyLane is to use :func:`~.pennylane.fermi.from_string`. We +then map the operator using :func:`~.pennylane.fermi.jordan_wigner`. +""" + +import pennylane as qml +from pennylane.fermi import from_string, jordan_wigner + +qubits = 10 +fermi_op = from_string("5+") +pauli_jw = jordan_wigner(fermi_op, ps=True) +pauli_jw + +############################################################################### +# The long sequence of the :math:`Z` operations can significantly increase the +# resources needed to implement the operator on quantum hardware, as it may require using entangling +# operations across multiple qubits, which can be challenging to implement efficiently. One way to +# avoid having such long tails of :math:`Z` operations is to work in the parity basis where the +# fermionic state stores the parity instead of the occupation number. +# +# Parity Mapping +# -------------- +# In the Parity representation, the state of a fermionic system is represented with a binary +# vector that corresponds to the parity of the orbitals. Note that parity for a set of orbitals +# is defined as the sum (mod 2) of the occupation numbers. Let's look at an example using the +# PennyLane function :func:`~.pennylane.qchem.hf_state` to obtain the state of a system with +# :math:`10` orbitals and :math:`5` electrons in both the occupation-number and parity bases. + +orbitals = 10 +electrons = 5 +state_number = qml.qchem.hf_state(electrons, orbitals) +state_parity = qml.qchem.hf_state(electrons, orbitals, basis="parity") + +print("State in occupation number basis:\n", state_number) +print("State in parity basis:\n", state_parity) + +############################################################################## +# Note that Parity mapping solves the non-locality problem of the parity information by storing +# the parity of orbital :math:`j` in qubit :math:`j` while the occupation information for the +# orbital is stored non-locally. In the parity basis, we cannot represent the creation or +# annihilation of a particle in orbital +# :math:`j` by simply operating with qubit creation or annihilation operators. In fact, the state of +# the :math:`(j − 1)`-th qubit provides information about the occupation state of qubit :math:`j` +# and whether we need to act with a creation or annihilation operator. Similarly, the creation or +# annihilation of a particle in qubit :math:`j` changes the parity of all qubits following it. +# As a result, the operator that is equivalent to creation and annihilation operators in +# the parity basis is a two-qubit operator acting on qubits :math:`j` and :math:`j − 1,` and +# an update operator which updates the parity of all qubits with index larger than j: +# +# .. math:: +# +# a_{j}^{\dagger} = \frac{1}{2}(Z_{j-1} \otimes X_j - iY_j) \otimes_{k>j} X_{k} +# +# and +# +# .. math:: +# +# a_{j} = \frac{1}{2}(Z_{j-1} \otimes X_j + iY_j) \otimes_{k>j} X_{k} +# +# Let's now look at an example where we map our fermionic operator :math:`a_{5}^{\dagger}` in a +# :math:`10` qubit system with +# Parity mapping using :func:`~.pennylane.fermi.parity_transform` in PennyLane. + +qubits = 10 +pauli_pr = qml.parity_transform(fermi_op, qubits, ps=True) +pauli_pr + +############################################################################## +# It is evident from this example that the Parity mapping doesn't improve upon the +# the Jordan-Wigner mapping as the long :math:`Z` strings are now replaced by :math:`X` +# strings. However, a very important advantage of using parity mapping is the ability to taper two +# qubits by leveraging symmetries of molecular Hamiltonians. You can find +# more information about this in our +# `qubit tapering `__ demo. +# Let's look at an example. + +generators = [qml.prod(*[qml.Z(i) for i in range(qubits-1)]), qml.Z(qubits-1)] +paulixops = qml.paulix_ops(generators, qubits) +paulix_sector = [1, 1] +taper_op = qml.taper(pauli_pr, generators, paulixops, paulix_sector) +qml.simplify(taper_op) + +############################################################################### +# Note that the tapered operator doesn't include qubit :math:`8` and :math:`9.` +# +# Bravyi-Kitaev Mapping +# --------------------- +# The Bravyi-Kitaev mapping aims to improve the linear scaling of the Jordan-Wigner and Parity +# mappings, in the number of qubits, +# by storing both the occupation number and the parity non-locally. In this formalism, even-labelled +# qubits store the occupation number of orbitals and odd-labelled qubits store parity +# through partial sums of occupation numbers. The corresponding creation and annihilation operators +# are defined `here `__. +# Let's use the :func:`~.pennylane.fermi.bravyi_kitaev` function to map our :math:`a_{5}^{\dagger}` +# operator. + +pauli_bk = qml.bravyi_kitaev(fermi_op, qubits, ps=True) +pauli_bk + +############################################################################## +# It is clear that the local nature of the transformation in the Bravyi-Kitaev mapping helps to +# improve the number of qubits. This advantage becomes even more clear if you +# work with a larger qubit +# system. We now use the Bravyi-Kitaev mapping to construct a qubit Hamiltonian and +# compute its ground state energy with the `VQE `__ +# method. +# +# Energy Calculation +# ------------------ +# To perform a VQE calculation for a desired Hamiltonian, we need an initial state typically set to +# a Hartree-Fock state, and a set of excitation operators to build an ansatz that allows us to +# obtain the ground state and then compute the expectation value of the Hamiltonian. It is important +# to note that the initial state and the excitation operators should be consistent with the +# mapping scheme used for obtaining the qubit Hamiltonian. Let's now build these three components +# for :math:`H_2` and compute its ground state energy. For this example, we will use the +# Bravyi-Kitaev transformation but similar calculations can be run with the other mappings. +# +# Molecular Hamiltonian +# ^^^^^^^^^^^^^^^^^^^^^ +# First, let's build the molecular Hamiltonian. This requires defining the atomic symbols and +# coordinates. + +from pennylane import qchem +from pennylane import numpy as np + +symbols = ['H', 'H'] +geometry = np.array([[0.0, 0.0, -0.69434785], + [0.0, 0.0, 0.69434785]], requires_grad = False) + +mol = qchem.Molecule(symbols, geometry) + +############################################################################## +# We then use the :func:`~pennylane.qchem.fermionic_hamiltonian` function to build +# the fermionic Hamiltonian for our molecule. + +h_fermi = qchem.fermionic_hamiltonian(mol)() + +############################################################################## +# We now use :func:`~pennylane.fermi.bravyi_kitaev` to transform the fermionic Hamiltonian to its +# qubit representation. + +electrons = 2 +qubits = len(h_fermi.wires) +h_pauli = qml.bravyi_kitaev(h_fermi, qubits, tol=1e-16) + +############################################################################## +# Initial state +# ^^^^^^^^^^^^^ +# We now need the initial state that has the correct number of electrons. We use the Hartree-Fock +# state which can be obtained in a user-defined basis by using :func:`~.pennylane.qchem.hf_state` in +# PennyLane. For that, we need to specify the number of electrons, the number of orbitals and the +# desired mapping. + +hf_state = qchem.hf_state(electrons, qubits, basis="bravyi_kitaev") + +############################################################################## +# Excitation operators +# ^^^^^^^^^^^^^^^^^^^^ +# We now build our quantum circuit with the :class:`~.pennylane.UCCSD` ansatz. This ansatz is +# constructed with a set of single and double excitation operators. In PennyLane, we have +# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operators which +# are very efficient, but they are only compatible with the Jordan-Wigner mapping. Here we construct +# the excitation operators manually. We start from the fermionic single and double excitation +# operators defined as [#Yordanov]_ +# +# .. math:: +# +# T_i^k(\theta) = \theta(a_k^{\dagger}a_i - a_i^{\dagger}a_k) +# +# and +# +# .. math:: +# +# T_{ij}^{kl}(\theta) = \theta(a_k^{\dagger}a_l^{\dagger}a_i a_j - +# a_i^{\dagger}a_j^{\dagger}a_k a_l), +# +# where :math:`\theta` is an adjustable parameter. We can easily construct these fermionic +# excitation operators in PennyLane and then map them to the qubit basis with +# :func:`~pennylane.fermi.bravyi_kitaev`, similar to the way we transformed the fermionic +# Hamiltonian. + +from pennylane.fermi import from_string + +singles, doubles = qchem.excitations(electrons, qubits) + +singles_fermi = [] +for ex in singles: + singles_fermi.append(from_string(f"{ex[1]}+ {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}-")) + +doubles_fermi = [] +for ex in doubles: + doubles_fermi.append(from_string(f"{ex[3]}+ {ex[2]}+ {ex[1]}- {ex[0]}-") + - from_string(f"{ex[0]}+ {ex[1]}+ {ex[2]}- {ex[3]}-")) + +############################################################################## +# The fermionic operators are now mapped to qubit operators. + +singles_pauli = [] +for op in singles_fermi: + singles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +doubles_pauli = [] +for op in doubles_fermi: + doubles_pauli.append(qml.bravyi_kitaev(op, qubits, ps=True)) + +############################################################################## +# Note that we need to exponentiate these operators to be able to use them in the circuit +# [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. + +params = np.array([0.22347661, 0.0, 0.0]) + +dev = qml.device("default.qubit", wires=qubits) + +@qml.qnode(dev) +def circuit(params): + qml.BasisState(hf_state, wires=range(qubits)) + + for i, excitation in enumerate(doubles_pauli): + qml.exp((excitation * params[i] / 2).operation()), range(qubits) + + for j, excitation in enumerate(singles_pauli): + qml.exp((excitation * params[i + j + 1] / 2).operation()), range(qubits) + + return qml.expval(h_pauli) + +print('Energy =', circuit(params)) + +############################################################################## +# Using the above circuit, we produce the ground state energy of :math:`H_2` molecule. +# +# Conclusion +# --------------- +# In this demo, we learned about various mapping schemes available in PennyLane and how +# they can be used to convert fermionic operators to qubits operators. We also learned +# the pros and cons associated with each scheme. The Jordan-Wigner mapping provides an intuitive +# approach while parity mapping allows tapering qubits in molecular systems. However, these two +# methods usually give qubit operators with a long chain of Pauli operators, which makes them +# challenging to implement on quantum hardware. The Bravyi-Kitaev mapping, on the other hand, +# emphasizes locality and resource efficiency, making it an attractive option for certain +# applications. Through this demonstration, we recognize the importance of choosing an appropriate +# mapping scheme tailored to the specific problem at hand and the available quantum +# resources. Lastly, we showed how a user can employ these different mappings in ground state energy +# calculations through an example. We would like to encourage the interested readers to run +# calculations for different molecular systems and observe how the scaling in the number of qubits +# is influenced by the selected mapping techniques. +# +# References +# ---------- +# +# .. [#Tranter] +# +# A. Tranter, S. Sofia, *et al.*, "The Bravyi–Kitaev Transformation: +# Properties and Applications". `International Journal of Quantum Chemistry 115.19 (2015). +# `__ +# +# .. [#Yordanov] +# +# Y. S. Yordanov, *et al.*, "Efficient quantum circuits for quantum computational chemistry". +# `Physical Review A 102.6 (2020). +# `__ +# diff --git a/demonstrations_v2/tutorial_mapping/metadata.json b/demonstrations_v2/tutorial_mapping/metadata.json new file mode 100644 index 0000000000..8ac1d86029 --- /dev/null +++ b/demonstrations_v2/tutorial_mapping/metadata.json @@ -0,0 +1,51 @@ +{ + "title": "Mapping fermionic Hamiltonians to qubit Hamiltonians", + "authors": [ + { + "username": "ddhawan" + } + ], + "dateOfPublication": "2024-05-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing", + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/mapping/thumbnail_mapping_2024-06-20.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mapping_2024-06-20.png" + } + ], + "seoDescription": "Learn how to map fermionic operators to qubit operators", + "doi": "", + "references": [ + { + "id": "Tranter", + "type": "article", + "title": "The Bravyi\u2013Kitaev Transformation: Properties and Applications", + "authors": "A. Tranter, S. Sofia et al.", + "year": "2015", + "publisher": "International Journal of Quantum Chemistry", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/qua.24969" + }, + { + "id": "Yordanov", + "type": "article", + "title": "Efficient quantum circuits for quantum computational chemistry", + "authors": "Y. S. Yordanov et al.", + "year": "2020", + "publisher": "Physical Review A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.102.062612" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_mapping/requirements.in b/demonstrations_v2/tutorial_mapping/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_mapping/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_mbqc/demo.py b/demonstrations_v2/tutorial_mbqc/demo.py new file mode 100644 index 0000000000..f21d800114 --- /dev/null +++ b/demonstrations_v2/tutorial_mbqc/demo.py @@ -0,0 +1,809 @@ +r""".. _mbqc: + +Measurement-based quantum computation +===================================== + +.. meta:: + :property="og:description": Learn about measurement-based quantum computation + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_mbqc.png + +.. related:: + + tutorial_toric_code Toric code + +*Authors: Joost Bus and Radoica Draškić — Posted: 05 December 2022. Last updated: 05 December 2022.* + +""" + +############################################################################## +# +# **Measurement-based quantum computing (MBQC)**, also known as one-way quantum computing, is an +# inventive approach to quantum computing that makes use of *off-line* entanglement as a resource +# for computation. A one-way quantum computer starts out with an entangled state, a so-called +# *cluster state*, and applies particular single-qubit measurements that correspond to the desired quantum circuit. In this context, +# off-line means that the entanglement is created independently from the rest of the +# computation, like how a blank sheet of paper is made separately from the text of a book. Coming +# from the gate-based model, this method might seem unintuitive to you at first, but the approaches +# can be proven to be equally powerful. In MBQC, the measurements *are* the computation and the +# entanglement of the cluster state is used as a resource. +# +# The structure of this demo will be as follows. First, we introduce the concept of a cluster +# state, the substrate for measurement-based quantum computation. Then, we will move on to explain +# how to implement arbitrary quantum circuits, thus proving that MBQC is universal. Lastly, we will +# briefly touch upon how quantum error correction (QEC) is done in this scheme. +# +# Throughout this tutorial, we will explain the underlying concepts with the help of some code +# snippets using `PennyLane `_. In the section about QEC, +# we will also use Xanadu's quantum error correction simulation software +# `FlamingPy `_ developed by our architecture team +# [#XanaduPassiveArchitecture]_. +# +# +# +# .. figure:: ../_static/demonstration_assets/mbqc/DALLE-mbqc.png +# :align: center +# :alt: DALLE representation of Measurement-based quantum computation +# :width: 60% +# +# .. +# +# In MBQC, seeing is computing! +# + +############################################################################## +# +# Cluster states and graph states +# ---------------- +# +# *Cluster states* are the universal substrate for measurement-based quantum computation +# [#OneWay2001]_. They are a special instance of *graph states* [#EntanglementGraphStates]_, a +# class of entangled multi-qubit states that can be represented by an undirected graph +# :math:`G = (V,E)` whose vertices :math:`V` are associated with qubits and the edges :math:`E` with entanglement +# between them. The associated quantum state reads as follows +# +# .. math:: |\Phi\rangle=\Pi_{(i,j)\in E}CZ_{ij}|+⟩^{\otimes n}. +# +# where :math:`n` is the number of qubits, :math:`CZ_{ij}` is the controlled-:math:`Z`` gate between +# qubits :math:`i` and :math:`j,` and :math:`|+\rangle = \frac{1}{\sqrt{2}}\big(|0\rangle + |1\rangle\big)` +# is the :math:`+1`` eigenstate of the Pauli-:math:`X` operator. The distinction between graph +# states and a cluster states is rather technical and details can be found in Ref. +# [#PersistentEntanglement]_. For now, suffice to say that cluster states are a subset of graph +# states with some additional conditions. +# +# We can also describe the creation of a cluster state in the gate-based model. Let us first +# define a graph we want to look at, and then construct a circuit in PennyLane to create the +# corresponding graph state. +# + +import networkx as nx +import matplotlib.pyplot as plt + +a, b = 1, 5 # dimensions of the graph (lattice) +G = nx.grid_graph(dim=[a, b]) # there are a * b qubits + +plt.figure(figsize=(5, 1)) +nx.draw(G, pos={node: node for node in G}, node_size=500, node_color="black") + +############################################################################## +# +# This is a fairly simple cluster state, but we will later see how even +# this simple graph is useful for logical operations. Now that we have defined a graph, we can go ahead +# and define a circuit to prepare the cluster state. +# + +import pennylane as qml + +qubits = [str(node) for node in G.nodes] +dev = qml.device("lightning.qubit", wires=qubits) + + +@qml.qnode(dev, interface="autograd") +def cluster_state(): + for node in qubits: + qml.Hadamard(wires=[node]) + + for edge in G.edges: + i, j = edge + qml.CZ(wires=[str(i), str(j)]) + + return qml.state() + + +print(qml.draw(cluster_state)()) + +############################################################################## +# +# Observe that the structure of the circuit is fairly simple. It only requires Hadamard +# gates on each qubit and then a controlled-:math:`Z` gate between connected qubits. This part of +# the computation is not actually computing anything. In fact, aside from the width and depth of +# the desired quantum circuit, the cluster state generation is essentially independent of the +# calculation. If you have a reliable way of applying these two operations (Hadamard and +# controlled-:math:`Z`), you are ready for the next step: worrying about conditional single-qubit +# measurements. +# + +############################################################################## +# Information propagation and teleportation +# ------------------------------------------ +# +# Measurement-based quantum computation heavily relies on the idea of information propagation. In +# particular, we make use of a protocol called *quantum teleportation*, one of the driving concepts behind MBQC. Despite its Sci-Fi name, quantum +# teleportation is very real and has been experimentally demonstrated multiple times in the last few decades +# [#Hermans2022]_, [#Furusawa1998]_, [#Riebe2004]_, [#Nielsen1998]_. Moreover, it has related applications +# in safe communication protocols that are impossible with classical communication so it's certainly +# worth learning about. In this protocol, we transport *information*, not matter, between systems. Admittedly, it has a +# somewhat misleading name because it is not instantaneous: it requires communication of +# additional classical information, which is still limited by the speed of light. +# +# One-qubit Teleportation +# ````````````````````` +# Let's take a deeper look at the principles behind quantum teleportation using a simple example of one-qubit +# teleportation. We start with one qubit in the state :math:`|\psi\rangle` that we want to transfer +# to the second qubit initially in the state :math:`|0\rangle.` The figure below represents the +# protocol. The green box represents the creation of a cluster state, while +# the red box represents the measurement of a qubit with the appropriate correction applied to +# the second qubit based on the measurement outcome. +# +# .. figure:: ../_static/demonstration_assets/mbqc/one-bit-teleportation.png +# :align: center +# :alt: Teleportation protocol +# :width: 75% +# +# .. +# +# Let's implement one-qubit teleportation in PennyLane. + +import pennylane as qml +import pennylane.numpy as np + +dev = qml.device("lightning.qubit", wires=2) + + +@qml.qnode(dev, interface="autograd") +def one_bit_teleportation(input_state): + # Prepare the input state + qml.StatePrep(input_state, wires=0) + + # Prepare the cluster state + qml.Hadamard(wires=1) + qml.CZ(wires=[0, 1]) + + # Measure the first qubit in the Pauli-X basis + # and apply an X-gate conditioned on the outcome + qml.Hadamard(wires=0) + m = qml.measure(wires=[0]) + qml.cond(m == 1, qml.PauliX)(wires=1) + qml.Hadamard(wires=1) + + # Return the density matrix of the output state + return qml.density_matrix(wires=[1]) + + +############################################################################## +# +# Note that we return a `density matrix `_ in the +# function above. +# This allows for us to describe operations beyond unitaries, +# such as the teleportation protocol. +# +# Now, let's prepare a random qubit state and see if the teleportation protocol is working as +# expected. To do so, we'll generate a random normalized state :math:`|\psi\rangle = \alpha |0\rangle + \beta |1\rangle` +# and apply the teleportation protocol to see if the resulting density matrix +# describing the second qubit is the same as our input state :math:`|\psi\rangle.` + +# Define helper function for random input state on n qubits +def generate_random_state(n=1): + input_state = np.random.random(2 ** n) + 1j * np.random.random(2 ** n) + return input_state / np.linalg.norm(input_state) + + +# Generate a random input state |psi> for n=1 qubit +input_state = generate_random_state() + +density_matrix = np.outer(input_state, np.conj(input_state)) +density_matrix_mbqc = one_bit_teleportation(input_state) + +np.allclose(density_matrix, density_matrix_mbqc) + +############################################################################## +# +# As we can see, :math:`|\psi\rangle,` originally the state of the first qubit, has been transported to the second qubit! +# +# This protocol is one of the main ingredients of one-way quantum computing. Essentially, we +# propagate the information in one end of our cluster state to the other end through +# successive teleportations. In addition, we can "write" our circuit onto the cluster state by +# choosing the measurements adaptively. In the next section, we will see how we can actually do this. +# + +############################################################################## +# Universality of MBQC +# ---------------------- +# How do we know if this measurement-based scheme is just as powerful as its gate-based counterpart? We +# have to prove it! In particular, we want to show that a measurement-based quantum computer is a +# `quantum Turing machine (QTM) `_. To do this, we +# need to show 4 things [#OneWay2001]_: +# +# 1. How **information propagates** through the cluster state. +# +# 2. How arbitrary **single-qubit rotations** can be implemented. +# +# 3. How a **two-qubit gate** can be implemented in this scheme. +# +# 4. How to implement **arbitrary quantum circuits**. +# +# In the previous section, we have already seen how the quantum information propagates from one +# side of the cluster to the other. In this +# section, we will tackle the remaining parts concerning logical operations. Throughout, we will +# assume the ability to measure in arbitrary bases. +# + +############################################################################## +# +# .. _single-qubit-rotations: +# +# Single-qubit rotations +# ``````````````````````` +# Arbitrary single-qubit rotations are essential operations for a universal quantum computer. In +# MBQC, we can implement these rotations by using the entanglement of the cluster state. Any +# single-qubit gate can be represented as a composition of three rotations along two different axes, +# for example :math:`U(\alpha, \beta, \gamma) = R_x(\gamma)R_z(\beta)R_x(\alpha)` where +# :math:`R_x` and :math:`R_z` represent rotations around the :math:`X` and :math:`Z` axis, +# respectively. +# +# We will see that in our measurement-based scheme, this operation can be implemented using a linear +# chain of 5 qubits prepared in a cluster state, as shown in the figure below [#MBQCRealization]_. The first qubit +# :math:`t_\mathrm{in}` is prepared in some input state :math:`|\psi_\mathrm{in}\rangle,` +# and we are interested in the final state of the output qubit :math:`t_\mathrm{out}.` +# +# .. figure:: ../_static/demonstration_assets/mbqc/single-qubit-rotation.png +# :align: center +# :alt: Measurement-based single qubit rotation +# :width: 75% +# +# .. +# +# The input qubit :math:`t_\mathrm{in}`, together with the intermediate qubits :math:`a_1,` +# :math:`a_2,` and :math:`a_3` are then measured in the bases +# +# .. math:: +# \mathcal{B}_j(\theta_j) \equiv \left\{\frac{|0\rangle + e^{i\theta_j}|1\rangle}{\sqrt{2}}, +# \frac{|0\rangle - e^{i\theta_j}|1\rangle}{\sqrt{2}}\right\}, +# +# where the angles :math:`\theta_j` depend on prior measurement outcomes and +# are given by +# +# .. math:: \theta_{\mathrm{in}} = 0, \qquad \theta_{1} = (-1)^{m_{\mathrm{in}} + 1} \alpha, \qquad +# \theta_{2} = (-1)^{m_1} \beta, \quad \text{and} \quad \theta_{3} = (-1)^{m_{\mathrm{in}} + m_2} \gamma +# +# with :math:`m_{\mathrm{in}}, m_1, m_2 \in \{0, 1\}` being the measurement outcomes on nodes +# :math:`t_\mathrm{in}`, :math:`a_1` and :math:`a_2,` respectively. Note that the +# measurement basis is adaptive; the measurement on :math:`a_3,` for example, depends on the outcome +# of earlier measurements in the chain. After these operations, the state of qubit +# :math:`t_\mathrm{out}` is given by +# +# .. math:: |\psi_{\mathrm{out}}\rangle = \tilde{U}(\alpha, \beta, \gamma)|\psi_{\mathrm{in}}\rangle +# = X^{m_1 + m_3}Z^{m_{\mathrm{in}} + m_2}U(\alpha, \beta, \gamma) +# |\psi_{\mathrm{in}}\rangle. +# +# with :math:`m_3` being the measurement outcome on node :math:`a_3.` Now note that this unitary +# :math:`\tilde{U}` is related to our desired unitary :math:`U` up to +# the first two Pauli terms. Luckily, we can correct for these additional Pauli gates by +# choosing the measurement basis of qubit :math:`t_\mathrm{out}` appropriately or correcting for them classically after +# the quantum computation. +# +# To demonstrate that this actually works, we will use PennyLane. For simplicity, we will just +# show the ability will to perform single-axis rotations :math:`R_z(\theta)` and +# :math:`R_x(\theta)` for arbitrary :math:`\theta \in [0, 2 \pi).` Note that these two operations +# plus the CNOT also constitute a universal gate set. +# +# To start off, we define the :math:`R_z(\theta)` gate using two qubits with the gate-based approach +# so we can later compare our MBQC approach to it. + +dev = qml.device("lightning.qubit", wires=1) + + +@qml.qnode(dev, interface="autograd") +def RZ(theta, input_state): + # Prepare the input state + qml.StatePrep(input_state, wires=0) + + # Perform the Rz rotation + qml.RZ(theta, wires=0) + + # Return the density matrix of the output state + return qml.density_matrix(wires=[0]) + + +############################################################################## +# +# Let's now implement an :math:`R_z` gate on an arbitrary state +# in the MBQC formalism. +# + +mbqc_dev = qml.device("lightning.qubit", wires=2) + + +@qml.qnode(mbqc_dev, interface="autograd") +def RZ_MBQC(theta, input_state): + # Prepare the input state + qml.StatePrep(input_state, wires=0) + + # Prepare the cluster state + qml.Hadamard(wires=1) + qml.CZ(wires=[0, 1]) + + # Measure the first qubit and correct the state + qml.RZ(theta, wires=0) + qml.Hadamard(wires=0) + m = qml.measure(wires=[0]) + + qml.cond(m == 1, qml.PauliX)(wires=1) + qml.Hadamard(wires=1) + + # Return the density matrix of the output state + return qml.density_matrix(wires=[1]) + + +############################################################################## +# +# Next, we will prepare a random input state and compare the two approaches. +# + +# Generate a random input state +input_state = generate_random_state() +theta = 2 * np.pi * np.random.random() + +np.allclose(RZ(theta, input_state), RZ_MBQC(theta, input_state)) + +############################################################################## +# +# Seems good! As we can see, the resulting states are practically the same. +# For the :math:`R_x(\theta)` gate we take a similar approach. +# + +dev = qml.device("lightning.qubit", wires=1) + + +@qml.qnode(dev, interface="autograd") +def RX(theta, input_state): + # Prepare the input state + qml.StatePrep(input_state, wires=0) + + # Perform the Rz rotation + qml.RX(theta, wires=0) + + # Return the density matrix of the output state + return qml.density_matrix(wires=[0]) + + +mbqc_dev = qml.device("lightning.qubit", wires=3) + + +@qml.qnode(mbqc_dev, interface="autograd") +def RX_MBQC(theta, input_state): + # Prepare the input state + qml.StatePrep(input_state, wires=0) + + # Prepare the cluster state + qml.Hadamard(wires=1) + qml.Hadamard(wires=2) + qml.CZ(wires=[0, 1]) + qml.CZ(wires=[1, 2]) + + # Measure the qubits and perform corrections + qml.Hadamard(wires=0) + m1 = qml.measure(wires=[0]) + + qml.RZ(theta, wires=1) + qml.cond(m1 == 1, qml.RX)(-2 * theta, wires=2) + qml.Hadamard(wires=1) + m2 = qml.measure(wires=[1]) + + qml.cond(m2 == 1, qml.PauliX)(wires=2) + qml.cond(m1 == 1, qml.PauliZ)(wires=2) + + # Return the density matrix of the output state + return qml.density_matrix(wires=[2]) + + +############################################################################## +# +# Finally, we again compare the two implementations with a random state as an input. +# + +# Generate a random input state +input_state = generate_random_state() +theta = 2 * np.pi * np.random.random() + +np.allclose(RX(theta, input_state), RX_MBQC(theta, input_state)) + +############################################################################## +# +# Perfect! We have shown that we can implement any single-axis rotation on an arbitrary state in the +# MBQC formalism. In the following section we will look at a two-qubit gate to complete our +# universal gate set. + +############################################################################## +# The two-qubit gate: CNOT +# `````````````````````````` +# The second ingredient for a universal quantum computing scheme is the two-qubit gate. Here, we will +# show how to perform a CNOT operation in the measurement-based framework. The input state is given on two qubits, +# control qubit :math:`c` and target qubit :math:`t_\mathrm{in}.` Preparing the cluster state shown in +# the figure below, and measuring qubits :math:`t_\mathrm{in}` and :math:`a` in the :math:`X`-basis, +# we implement the CNOT gate between qubits :math:`c` and :math:`t_\mathrm{out}` up to Pauli corrections [#MBQCRealization]_. +# +# .. figure:: ../_static/demonstration_assets/mbqc/cnot.png +# :align: center +# :alt: Measurement-based CNOT +# :width: 50% +# +# .. +# +# Let's see how one can do this in PennyLane. + +dev = qml.device("lightning.qubit", wires=2) + + +@qml.qnode(dev, interface="autograd") +def CNOT(input_state): + # Prepare the input state + qml.StatePrep(input_state, wires=[0, 1]) + qml.CNOT(wires=[0, 1]) + + return qml.density_matrix(wires=[0, 1]) + + +mbqc_dev = qml.device("lightning.qubit", wires=4) + + +@qml.qnode(mbqc_dev, interface="autograd") +def CNOT_MBQC(input_state): + # Prepare the input state + qml.StatePrep(input_state, wires=[0, 1]) + + # Prepare the cluster state + qml.Hadamard(wires=2) + qml.Hadamard(wires=3) + qml.CZ(wires=[2, 0]) + qml.CZ(wires=[2, 1]) + qml.CZ(wires=[2, 3]) + + # Measure the qubits in the appropriate bases + qml.Hadamard(wires=1) + m1 = qml.measure(wires=[1]) + qml.Hadamard(wires=2) + m2 = qml.measure(wires=[2]) + + # Correct the state + qml.cond(m1 == 1, qml.PauliZ)(wires=0) + qml.cond(m2 == 1, qml.PauliX)(wires=3) + qml.cond(m1 == 1, qml.PauliZ)(wires=3) + + # Return the density matrix of the output state + return qml.density_matrix(wires=[0, 3]) + + +############################################################################## +# Now let's prepare a random input state and check our implementation. + +# Generate a random 2-qubit state +input_state = generate_random_state(n=2) + +np.allclose(CNOT(input_state), CNOT_MBQC(input_state)) + +############################################################################## +# Arbitrary quantum circuits +# ``````````````````````````` +# Once we have established the ability to implement arbitrary single-qubit rotations and a two-qubit +# gate, the final step is to show that we can implement arbitrary quantum circuits. To do so, +# we simply have to note that we have a *universal gate set* [#DiVincenzo]_. The complete computation +# can be performed as shown in the figure below. The qubits are teleported along the arrows in the +# cluster and single-qubit gates are applied through a selection of measurement bases along these arrays. +# Two-qubit gates are implemented along vertical arrows, and the rest of the qubits are measured in the +# :math:`Z`-basis, effectively taking them out of the cluster without affecting the neighboring nodes. +# +# .. figure:: ../_static/demonstration_assets/mbqc/mbqc_info_flow.png +# :align: center +# :alt: measurement-based quantum computation information flow +# :width: 75% +# +# .. +# +# A complete measurement-based quantum computation. Circles :math:`\odot` symbolize measurements +# of Pauli-:math:`Z`, vertical arrows :math:`\uparrow` are measurements of Pauli-:math:`X,` while +# tilted arrows :math:`\nwarrow` or :math:`\nearrow` refer to +# measurements in the :math:`xy`-plane. [#OneWay2001]_ +# +# However, you might wonder: Is it even feasible to construct the large cluster states that +# one-way quantum computation requires? The number of qubits needed to construct a circuit can grow +# to be very large, as it not only depends on the number of logical qubits, but also on the depth +# of the circuit. At this point, it's good to reiterate that the entanglement of the cluster +# state is created *off-line*. +# +# *...the entanglement is created independently from the rest of the computation, like how a blank +# sheet of paper is made separately from the text of a book.* +# +# Interestingly enough, we do not have to prepare all of the entanglement at once. Just like we can +# already start printing text upon the first few pages, we can apply measurements to one end of the +# cluster while growing it at the same time, as shown in the figure below. That is, we can start +# printing the text on the first few pages while at the same time reloading the printer's paper +# tray! +# +# .. figure:: ../_static/demonstration_assets/mbqc/measure_entangle.jpeg +# :align: center +# :alt: entanglement measurement +# :width: 75% +# +# .. +# +# Schematic showing how we can also consume the cluster state while we grow it. The blue qubits are in a cluster state, +# where the bonds between them represent entanglement. The gray qubits have been measured, +# destroying the entanglement and removing them from the cluster. At the same time, the green +# qubits are being added to the cluster by entangling them with it. Prior measurement outcomes +# determine the basis for future measurements [#OpticalQuantumComputing]_. +# +# This feature makes it particularly attractive for photonic quantum computers: we can use +# expendable qubits that can't stick around for the full calculation. If we can find a +# reliable way to produce qubits and stitch them together through entanglement, we can use it to +# produce our cluster state resource! Essentially, we need some kind of qubit factory and a +# stitching mechanism that puts it all together. The stitching mechanism depends on the physical +# platform; for example, it can be implemented with an Ising interaction [#OneWay2001]_ or by +# interfering two optical modes with a beamsplitter [#XanaduPassiveArchitecture]_. +# + +############################################################################## +# Quantum error correction +# ------------------------- +# +# To mitigate the physical errors that can (and will) happen during a quantum computation, we +# require some kind of error correction scheme. Error correction is a technique for detecting errors and +# reconstructing the logical data with as little information loss as possible. It is not exclusive +# to quantum computing; it is also used in "classical" information processing such as computation, +# data storage, and communication where one also has to deal `with +# noise coming from the environment `_. However, it is +# a stringent requirement in the quantum realm as the systems one works with are much more +# precarious and therefore prone to environmental factors, causing errors. +# +# Due to the peculiarities of quantum physics, we have to be careful when implementing error +# correction. First of all, we can not simply look inside our quantum computer and see if an error occurred; this would collapse the +# wavefunction, which carries valuable information. Secondly, we can not make copies of a quantum +# state to create redundancy because of the *no-cloning theorem*. Lastly, there are infinitely many +# more errors in quantum computing, whereas the only errors in classical computing are bit flips: a 1 +# being flipped to a 0 or vice versa. +# +# A whole research field devoted to combating these challenges has formed since Peter Shor published his +# seminal paper in 1995 [#ShorQEC1995]_. The main idea in QEC is using redundancy to encode +# information, just like classical error correction. However, to +# overcome the quantum-specific problems, we must measure groups of qubits and observe correlations +# between rather than measuring individual qubits. More technically, we measure +# operators that involve multiple qubits, called *stabilizers*. Based on the outcome of these stabilizer +# measurements, we can apply a correction and recover our information. +# Full coverage of this topic is beyond the scope of this tutorial, but a good place to start is +# `Daniel Gottesman's thesis `_ or `this blog post by +# Arthur Pesah `_ for a more compact +# introduction. Instead, we will give you the gist of quantum error correction in the +# MBQC framework. We will do so by using the surface code [#FowlerSurfaceCode]_ [#FowlerPolyestimate]_ [#GoogleQEC2022]_ as an example. This code makes use of stabilizers of the form :math:`\bigotimes_i X_i` or +# :math:`\bigotimes_j Z_j,` as depicted below. +# +# .. _fig-surfacecode: +# +# .. figure:: ../_static/demonstration_assets/mbqc/surface_code_d3.png +# :align: center +# :alt: surface code +# :width: 50% +# +# .. +# +# A distance :math:`d=3` surface code. Circles represent qubits and bubbles represent operators, called +# stabilizers, used to detect errors. The stabilizers are tensor products of Pauli-:math:`X` or Pauli-:math:`Z` +# operators and each is associated with its own ancilla qubit. The combined system encodes one +# logical qubit and can correct any combination of :math:`\lfloor (d-1)/2 \rfloor` errors. +# [#FowlerPolyestimate]_ +# +# In the measurement-based picture, quantum error correction requires cluster states that are at +# least 3-dimensional [#XanaduBlueprint]_, contrary to the 2-dimensional cluster states required for +# universal quantum computation discussed in the previous section. The error correcting code that you want to implement +# dictates the structure of the cluster state. The cluster state that is associated with the surface code is known as the RHG lattice, +# named after its architects Raussendorf, Harrington, and Goyal. We can visualize this cluster +# state with FlamingPy. +# + +from flamingpy.codes import SurfaceCode + +code_distance = 3 +RHG = SurfaceCode(code_distance) + +fig, _ = RHG.draw(backend="matplotlib", showbackground=True) +plt.show() + +############################################################################## +# +# For the sake of intuition, you can think of the graph shown above as having two spatial dimensions (:math:`x` +# and :math:`y`) and one temporal dimension (:math:`z`). The cluster state alternates between *primal* and *dual sheets*, shown below in more detail. +# In principle, any quantum error correction stabilizer code can be `foliated `_ into +# a graph state for measurement-based QEC [#FoliatedQuantumCodes]_, [#UniversalFTMBQC]_. However, the foliations are particularly nice for `CSS +# codes `_, named after Calderbank, Shor, and Steane. CSS codes have stabilizers that exclusively contain +# :math:`X`-stabilizers *or* :math:`Z`-stabilizers, and include the *surface code* and *colour code* families. +# For these CSS codes, you can roughly view the primal and dual sheets as measuring the +# :math:`Z`-stabilizers and :math:`X`-stabilizers, respectively. We encourage you to have another +# look at the :ref:`figure ` with the distance-3 surface code and try to link it +# with the dual and primal sheets shown here! +# +# .. figure:: ../_static/demonstration_assets/mbqc/primal_dual.png +# :align: center +# :alt: primal and dual +# :width: 70% +# +# The computation and error correction are again performed with single-qubit measurements, as illustrated below. +# At each timestep, we measure all the qubits on one sheet of the lattice. The binary +# outcomes of these measurements determine the measurement bases for future measurements, and the +# last sheet of the lattice contains the encoded result of the computation which can be read out by yet another measurement. +# +# .. figure:: ../_static/demonstration_assets/mbqc/gif_measuring.gif +# :align: center +# :alt: error corrected computation with measurements using the RHG lattice +# :width: 75% +# +# .. +# +# Performing an error corrected computation with measurements using the RHG lattice. [#XanaduBlueprint]_ +# + + +############################################################################## +# +# Conclusion +# ------------------------------- +# +# We have learned that a one-way quantum computer capable of cluster state +# entanglement together with adaptive arbitrary single-qubit measurements allows for universal +# quantum computation. The MBQC framework is a powerful quantum computing approach, particularly +# useful in platforms that allow for many expendable flying qubits and easy physical entangling +# gates. It circumvents the need for applying in-line entangling gates that are often the most +# noisy operations in gate-based quantum computers with trapped-ions or superconducting circuits. +# Instead, the required entanglement is created off-line which is often simpler to implement. +# Furthermore, it's advantageous for photonics because the depth of the optical circuit can remain +# constant. This means that it does not grow with the depth of the logical circuit, preventing +# intolerable losses. +# +# In this demo, we assumed that the system is capable of performing arbitrary +# single-qubit measurements. This is not a strict requirement, as one can acquire the same +# capabilities by sprinkling *magic states* into the cluster state. A discussion of this topic is +# beyond the scope of this tutorial, but a good place to start is `this +# paper `_ [#MagicStates]_. +# +# Xanadu's approach toward a universal quantum computer involves *continuous-variable* cluster states +# [#CV-MBQC]_. If you would like to learn more about the architecture, you can read our blueprint +# papers [#XanaduBlueprint]_ and [#XanaduPassiveArchitecture]_. We also highly recommend watching `this +# video `_ outlining the main ideas! +# + +############################################################################## +# References +# ------------ +# +# +# .. [#OneWay2001] +# +# Robert Raussendorf and Hans J. Briegel (2001) *A One-Way Quantum Computer*, +# `Phys. Rev. Lett. 86, 5188 +# `_. +# +# .. [#MBQCRealization] +# +# Swapnil Nitin Shah (2021) *Realizations of Measurement Based Quantum Computing*, +# `arXiv `_. +# +# .. [#XanaduBlueprint] +# +# J. Eli Bourassa, Rafael N. Alexander, Michael Vasmer, Ashlesha Patil, Ilan Tzitrin, +# Takaya Matsuura, Daiqin Su, Ben Q. Baragiola, Saikat Guha, Guillaume Dauphinais, Krishna K. +# Sabapathy, Nicolas C. Menicucci, and Ish Dhand (2021) *Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer*, +# `Quantum 5, 392 `_. +# +# .. [#XanaduPassiveArchitecture] +# +# Ilan Tzitrin, Takaya Matsuura, Rafael N. Alexander, Guillaume Dauphinais, J. Eli Bourassa, +# Krishna K. Sabapathy, Nicolas C. Menicucci, and Ish Dhand (2021) *Fault-Tolerant Quantum Computation with Static Linear Optics*, +# `PRX Quantum, Vol. 2, No. 4 +# `_. +# +# .. [#ShorQEC1995] +# +# Peter W. Shor (1995) *Scheme for reducing decoherence in quantum computer memory*, +# `Physical Review A, Vol. 52, Iss. 4 +# `_. +# +# .. [#LatticeSurgeryRaussendorf2018] +# +# Daniel Herr, Alexandru Paler, Simon J. Devitt and Franco Nori (2018) *Lattice Surgery on the Raussendorf Lattice*, +# `IOP Publishing 3, 3 +# `_. +# +# .. [#FowlerSurfaceCode] +# +# Austin G. Fowler, Matteo Mariantoni, John M. Martinis, Andrew N. Cleland (2012) +# *Surface codes: Towards practical large-scale quantum computation*, `arXiv `_. +# +# .. [#GoogleQEC2022] +# +# Google Quantum AI (2022) *Suppressing quantum errors by scaling a surface code logical qubit*, `arXiv `_. +# +# .. [#CV-MBQC] +# +# Nicolas C. Menicucci, Peter van Loock, Mile Gu, Christian Weedbrook, Timothy C. Ralph, and +# Michael A. Nielsen (2006) *Universal Quantum Computation with Continuous-Variable Cluster States*, +# `arXiv `_. +# +# .. [#DiVincenzo] +# +# David P. DiVincenzo. (2000) *The Physical Implementation of Quantum Computation*, +# `arXiv `_. +# +# .. [#Furusawa1998] +# +# A. Furusawa, J. L. Sørensen, S. L. Braunstein, C. A. Fuchs,H. J. Kimble, E. S. Polzik. (1998) +# *Unconditional Quantum Teleportation*, `Science Vol 282, Issue 5389 `_. +# +# +# .. [#Nielsen1998] +# +# M. A. Nielsen, E. Knill & R. Laflamme. (1998) *Complete quantum teleportation using nuclear +# magnetic resonance*, `Nature volume 396, 52–55 `_. +# +# .. [#Hermans2022] +# +# S. L. N. Hermans, M. Pompili, H. K. C. Beukers, S. Baier, J. Borregaard & R. Hanson. (2022) +# *Qubit teleportation between non-neighbouring nodes in a quantum network*, +# `Nature 605, 663–668 `_. +# +# .. [#Riebe2004] +# +# M. Riebe, H. Häffner, C. F. Roos, W. Hänsel, J. Benhelm, G. P. T. Lancaster, T. W. Körber, +# C. Becher, F. Schmidt-Kaler, D. F. V. James & R. Blatt. (2002) *Deterministic quantum +# teleportation with atoms*, `Nature 429, 734-737 `_. +# +# .. [#FoliatedQuantumCodes] +# +# A. Bolt, G. Duclos-Cianci, D. Poulin, T. M. Stace. (2016) *Foliated Quantum Codes*, +# `arXiv `_. +# +# .. [#MagicStates] +# +# Sergey Bravyi and Alexei Kitaev. (2004) *Universal quantum computation with ideal Clifford gates and noisy ancillas*, +# `arXiv `_. +# +# .. [#QuantumTeleportation] +# +# M. Hein, J. Eisert and H.J. Briegel. (2003) *Multi-party entanglement in graph states*, +# `arXiv `_. +# +# .. [#OpticalQuantumComputing] +# +# Jeremy L. O'Brien. (2007) *Optical quantum computing*, `Science Vol. 318, Issue 5856, 1567-1570 +# `_. +# +# .. [#FowlerPolyestimate] +# +# Austin G. Fowler. (2013) *Polyestimate: instantaneous open source surface code analysis*, `arXiv +# `_. +# +# .. [#EntanglementGraphStates] +# +# M. Hein, W. Dür, J. Eisert, R. Raussendorf, M. Van den Nest, H.J. Briegel. (2006) *Entanglement +# in Graph States and its Applications*, `arXiv `_. +# +# .. [#PersistentEntanglement] +# +# Hans J. Briegel and Robert Raussendorf (2001) *Persistent Entanglement in Arrays of +# Interacting Particles*, `Phys. Rev. Lett. 86, 910 +# `_. +# +# .. [#UniversalFTMBQC] +# +# Benjamin J. Brown, Sam Roberts. (2018) *Universal fault-tolerant measurement-based quantum computation*, `arXiv +# `_. +# + +############################################################################## +# diff --git a/demonstrations_v2/tutorial_mbqc/metadata.json b/demonstrations_v2/tutorial_mbqc/metadata.json new file mode 100644 index 0000000000..5e3d5b118c --- /dev/null +++ b/demonstrations_v2/tutorial_mbqc/metadata.json @@ -0,0 +1,245 @@ +{ + "title": "Measurement-based quantum computation", + "authors": [ + { + "username": "jbus" + }, + { + "username": "rdraskic" + } + ], + "dateOfPublication": "2022-12-05T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_measurement-based_QC.png" + } + ], + "seoDescription": "Learn about measurement-based quantum computation", + "doi": "", + "references": [ + { + "id": "OneWay2001", + "type": "article", + "title": "A One-Way Quantum Computer", + "authors": "Robert Raussendorf and Hans J. Briegel", + "year": "2001", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.5188" + }, + { + "id": "MBQCRealization", + "type": "article", + "title": "Realizations of Measurement Based Quantum Computing", + "authors": "Swapnil Nitin Shah", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/pdf/2112.11601.pdf" + }, + { + "id": "XanaduBlueprint", + "type": "article", + "title": "Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer", + "authors": "J. Eli Bourassa, Rafael N. Alexander, Michael Vasmer, Ashlesha Patil, Ilan Tzitrin, Takaya Matsuura, Daiqin Su, Ben Q. Baragiola, Saikat Guha, Guillaume Dauphinais, Krishna K. Sabapathy, Nicolas C. Menicucci, and Ish Dhand", + "year": "2021", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-02-04-392/" + }, + { + "id": "XanaduPassiveArchitecture", + "type": "article", + "title": "Fault-Tolerant Quantum Computation with Static Linear Optics", + "authors": "Ilan Tzitrin, Takaya Matsuura, Rafael N. Alexander, Guillaume Dauphinais, J. Eli Bourassa, Krishna K. Sabapathy, Nicolas C. Menicucci, and Ish Dhand", + "year": "2021", + "journal": "PRX Quantum", + "volume": "2", + "number": "4", + "doi": "10.1103/PRXQuantum.2.040353", + "url": "" + }, + { + "id": "ShorQEC1995", + "type": "article", + "title": "Scheme for reducing decoherence in quantum computer memory", + "authors": "Peter W. Shor", + "year": "1995", + "journal": "Physical Review A", + "volume": "52", + "issue": "4", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.52.R2493" + }, + { + "id": "LatticeSurgeryRaussendorf2018", + "type": "article", + "title": "Lattice Surgery on the Raussendorf Lattice", + "authors": "Daniel Herr, Alexandru Paler, Simon J. Devitt and Franco Nori", + "year": "2018", + "journal": "IOP Publishing", + "url": "https://arxiv.org/abs/1711.04921" + }, + { + "id": "FowlerSurfaceCode", + "type": "article", + "title": "Surface codes: Towards practical large-scale quantum computation", + "authors": "Austin G. Fowler, Matteo Mariantoni, John M. Martinis, Andrew N. Cleland", + "year": "2012", + "journal": "", + "url": "https://arxiv.org/abs/1208.0928" + }, + { + "id": "GoogleQEC2022", + "type": "article", + "title": "Suppressing quantum errors by scaling a surface code logical qubit", + "authors": "Google Quantum AI", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/pdf/2207.06431.pdf" + }, + { + "id": "CV-MBQC", + "type": "article", + "title": "Universal Quantum Computation with Continuous-Variable Cluster States", + "authors": "Nicolas C. Menicucci, Peter van Loock, Mile Gu, Christian Weedbrook, Timothy C. Ralph, and Michael A. Nielsen", + "year": "2006", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0605198" + }, + { + "id": "DiVincenzo", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "David P. DiVincenzo", + "year": "2000", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0002077" + }, + { + "id": "Furusawa1998", + "type": "article", + "title": "Unconditional Quantum Teleportation", + "authors": "A. Furusawa, J. L. S\u00f8rensen, S. L. Braunstein, C. A. Fuchs,H. J. Kimble, E. S. Polzik", + "year": "1998", + "journal": "Science", + "volume": "282", + "issue": "5389", + "url": "https://www.science.org/doi/10.1126/science.282.5389.706" + }, + { + "id": "Nielsen1998", + "type": "article", + "title": "Complete quantum teleportation using nuclear magnetic resonance", + "authors": "M. A. Nielsen, E. Knill & R. Laflamme", + "year": "1998", + "journal": "Nature", + "volume": "396", + "url": "https://www.nature.com/articles/23891" + }, + { + "id": "Hermans2022", + "type": "article", + "title": "Qubit teleportation between non-neighbouring nodes in a quantum network", + "authors": "S. L. N. Hermans, M. Pompili, H. K. C. Beukers, S. Baier, J. Borregaard & R. Hanson", + "year": "2022", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-022-04697-y" + }, + { + "id": "Riebe2004", + "type": "article", + "title": "Deterministic quantum teleportation with atoms", + "authors": "M. Riebe, H. H\u00e4ffner, C. F. Roos, W. H\u00e4nsel, J. Benhelm, G. P. T. Lancaster, T. W. K\u00f6rber, C. Becher, F. Schmidt-Kaler, D. F. V. James & R. Blatt", + "year": "2002", + "journal": "Nature", + "url": "https://www.nature.com/articles/nature02570" + }, + { + "id": "FoliatedQuantumCodes", + "type": "article", + "title": "Foliated Quantum Codes", + "authors": "A. Bolt, G. Duclos-Cianci, D. Poulin, T. M. Stace", + "year": "2016", + "journal": "", + "url": "https://arxiv.org/abs/1607.02579" + }, + { + "id": "MagicStates", + "type": "article", + "title": "Universal quantum computation with ideal Clifford gates and noisy ancillas", + "authors": "Sergey Bravyi and Alexei Kitaev", + "year": "2004", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0403025" + }, + { + "id": "QuantumTeleportation", + "type": "article", + "title": "Multi-party entanglement in graph states", + "authors": "M. Hein, J. Eisert and H.J. Briegel", + "year": "2003", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0307130" + }, + { + "id": "OpticalQuantumComputing", + "type": "article", + "title": "Optical quantum computing", + "authors": "Jeremy L. O'Brien", + "year": "2007", + "journal": "Science", + "volume": "318", + "issue": "5856", + "url": "https://www.science.org/doi/10.1126/science.1142892" + }, + { + "id": "FowlerPolyestimate", + "type": "article", + "title": "Polyestimate: instantaneous open source surface code analysis", + "authors": "Austin G. Fowler", + "year": "2013", + "journal": "", + "url": "https://arxiv.org/abs/1307.0689" + }, + { + "id": "EntanglementGraphStates", + "type": "article", + "title": "Entanglement in Graph States and its Applications", + "authors": "M. Hein, W. D\u00fcr, J. Eisert, R. Raussendorf, M. Van den Nest, H.J. Briegel", + "year": "2006", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0602096" + }, + { + "id": "PersistentEntanglement", + "type": "article", + "title": "Persistent Entanglement in Arrays of Interacting Particles", + "authors": "Hans J. Briegel and Robert Raussendorf", + "year": "2001", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.910" + }, + { + "id": "UniversalFTMBQC", + "type": "article", + "title": "Universal fault-tolerant measurement-based quantum computation", + "authors": "Benjamin J. Brown, Sam Roberts", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1811.11780" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_toric_code", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_mbqc/requirements.in b/demonstrations_v2/tutorial_mbqc/requirements.in new file mode 100644 index 0000000000..373f510e32 --- /dev/null +++ b/demonstrations_v2/tutorial_mbqc/requirements.in @@ -0,0 +1,4 @@ +flamingpy +matplotlib +networkx +pennylane diff --git a/demonstrations_v2/tutorial_mcm_introduction/demo.py b/demonstrations_v2/tutorial_mcm_introduction/demo.py new file mode 100644 index 0000000000..e0232a62eb --- /dev/null +++ b/demonstrations_v2/tutorial_mcm_introduction/demo.py @@ -0,0 +1,469 @@ +r"""Introduction to mid-circuit measurements +============================================ + +Mid-circuit measurements are an important building block in quantum algorithms +and quantum error correction, and with :doc:`measurement-based quantum computing +`, they even power a complete quantum computing paradigm. +In this tutorial, we will dive into the basics of mid-circuit measurements with +PennyLane. You will learn about + +- basic measurement processes in quantum mechanics, + +- the impact of a measurement on one- and two-qubit systems, + +- postselection and qubit reset, and + +- dynamic quantum circuits powered by conditional operations. + +.. figure:: ../_static/demonstration_assets/mcm_introduction/socialthumbnail_mcm_introduction.png + :align: center + :width: 50% + +We also have dedicated learning material if you want to know :doc:`how to collect +statistics of mid-circuit measurements ` or +:doc:`how to create dynamic circuits with mid-circuit measurements +`. +""" + +###################################################################### +# Measurements in quantum mechanics +# --------------------------------- +# +# Measurements are the subject of important questions in quantum mechanics: +# What is a measurement? How does it affect the measured system? And how can we +# describe a measurement process mathematically? +# Given how fundamental those question are, there is a plethora of learning +# resources on this topic, from textbooks [#mike_n_ike]_ and (video) lectures +# [#feynman]_, [#zwiebach]_ to interactive studying material like +# `the PennyLane Codebook `__ +# or Ref. [#van_der_Sar]_. +# Furthermore, discussing measurements quickly +# leads to questions about the interpretation of quantum mechanics and philosophical, +# if not metaphysical, discourse. +# For these reasons, we will not aim at discussing those deep question in great detail, +# but focus on understanding the basics that will help us understand measurements +# performed within quantum circuits, i.e., mid-circuit measurements, and how to realize +# them in PennyLane. +# +# Bear with us, we will briefly look at a mathematicaly definition for +# measurements but then turn to practical examples and hands-on calculations. +# +# Mathematical description +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We will go with the following definition: A measurement :math:`M` is a process that +# maps a valid quantum state :math:`\rho` to a classical probabilistic mixture +# +# .. math:: M[\rho]=\sum_{i=1}^n p_i \rho_i +# +# of post-measurement quantum states :math:`\rho_i` that are specified by :math:`M.` +# Here, :math:`n` is the number of possible measurement outcomes and :math:`p_i` +# is the probability to measure the outcome :math:`i` associated to :math:`\rho_i,` +# given the input state :math:`\rho.` For a qubit in the +# :math:`|+\rangle=(|0\rangle + |1\rangle)/\sqrt{2}` state measured in the :math:`Z` basis, we find +# +# .. math:: +# +# M[|+\rangle\langle +|]=\frac{1}{2}|0\rangle\langle 0|+\frac{1}{2}|1\rangle\langle 1|, +# +# because the probability to measure :math:`0` or :math:`1` is :math:`50\%` each. We +# will explore this example in more detail below. +# +# The expression above describes the probabilistic mixture after the quantum mechanical +# measurement if we do *not* record the measurement outcome. +# If we do record the measurement outcome and only keep those samples +# that match a specific postselection rule, +# we no longer have a probabilistic mixture, but find the state :math:`\rho_i` for +# the filtered outcome :math:`i.` +# +# For the rest of this tutorial, we will restrict ourselves to standard measurements +# commonly found in mid-circuit measurements, using so-called projective measurements. +# In this setting, the measurement comes with one projector :math:`\Pi_i` per +# measurement outcome, and all projectors sum to the identity. +# The post-measurement states are given by +# +# .. math:: \rho_i = \frac{\Pi_i \rho \Pi_i}{\operatorname{tr}[\Pi_i \rho]} +# +# and the probabilities are dictated by the Born rule, +# :math:`p_i=\operatorname{tr}[\Pi_i \rho].` +# This means that if we do not record the measurement outcome, the system simply +# ends up in the state +# +# .. math:: M[\rho] = \sum_{i=1}^n \Pi_i \rho \Pi_i. +# +# To understand this abstract description better, let's look at three simple examples; +# measuring a single qubit, measuring a Bell state, and resetting qubits. +# +# Measuring a single qubit +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Consider a single qubit in the state :math:`|+\rangle,` i.e., in the equal +# superposition of :math:`|0\rangle` and :math:`|1\rangle.` +# To get started, let's first implement this state in PennyLane and compute some +# expectation values that will be insightful later on. We follow these steps: +# +# - Import PennyLane and define a device using :func:`~.pennylane.device`. +# The built-in ``"default.qubit"`` Python statevector simulator will suffice for our +# purposes, however PennyLane provides a wide array of additional +# `high-performance and hardware devices `__. +# +# - Write a quantum function that first creates the :math:`|+\rangle` state using +# a :class:`~.pennylane.Hadamard` gate, and then measures the expectation values +# :math:`\langle X\rangle` and :math:`\langle Z\rangle` in this state, using +# :func:`~.pennylane.expval`. +# +# - Specify the device on which the quantum function should be executed, +# using the :func:`~.pennylane.qnode` decorator. +# +# - Run the quantum node and show the computed expectation values! +# +# If you'd like more guidance on any of these steps, also have a look at +# :doc:`our tutorial on qubit rotation ` explaining +# them in detail. +# + +import pennylane as qml + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def before(): + qml.Hadamard(0) # Create |+> state + return qml.expval(qml.X(0)), qml.expval(qml.Z(0)) + +b = before() +print(f"Expectation values before any measurement: {b[0]:.1f}, {b[1]:.1f}") + +###################################################################### +# The result is not surprising: :math:`|+\rangle` is the eigenstate of :math:`X` for +# the eigenvalue :math:`+1,` and it has the well-known expectation value +# :math:`\langle +|Z|+\rangle=0.` +# +# Now we bring in a mid-circuit measurement in the computational, or Pauli-:math:`Z,` +# basis. It comes with the projections :math:`\Pi_i=|i\rangle\langle i|,` +# :math:`i\in\{0, 1\},` onto the computational basis states. +# If we execute the measurement process but do not record the outcome, we find the state +# +# .. math:: +# +# M[\rho] +# &= \Pi_0 \rho_0 \Pi_0 + \Pi_1\rho_1 \Pi_1\\ +# &= |0\rangle\langle 0|+\rangle\langle +|0\rangle\langle 0| +# \ +\ |1\rangle\langle 1|+\rangle\langle +|1\rangle\langle 1|\\ +# &= \frac{1}{2}\mathbb{I}. +# +# where we used the overlaps :math:`\langle +|i\rangle=1/\sqrt{2}` and the decomposition +# :math:`\mathbb{I} = |0\rangle\langle 0| + |1\rangle\langle 1|` of the identity. +# This means that the measurement sends the qubit from a pure state into a mixed state, +# i.e., it not only affects the state but even the *class* of states it is in. And this +# is *because* we did not even record the measurement outcome! +# +# Let's look at this example in PennyLane. We repeat the steps from above but +# additionally include a mid-circuit measurement, calling :func:`~.pennylane.measure` +# on the qubit ``0``. Note that we just perform the measurement and do not assign any +# variable to its outcome. +# + +@qml.qnode(dev) +def after(): + qml.Hadamard(0) # Create |+> state + qml.measure(0) # Measure without recording the outcome + return qml.expval(qml.X(0)), qml.expval(qml.Z(0)) + +a = after() +print(f"Expectation value after the measurement: {a[0]:.1f}, {a[1]:.1f}") + +###################################################################### +# The measurement moved the qubit from the :math:`|+\rangle` eigenstate of +# the Pauli-:math:`X` operator into a mixed state with expectation value zero for all +# Pauli operators, explaining the values we just observed. +# +# Now if we filter for one measurement outcome, say :math:`0,` we find the state +# +# .. math:: +# +# M[\rho]=\rho_0 +# =\frac{|0\rangle\langle 0|+\rangle\langle +|0\rangle\langle 0|}{\operatorname{tr}[|0\rangle\langle 0|+\rangle\langle +|]} +# =|0\rangle\langle 0|, +# +# that is, the qubit is in a new, pure state. In PennyLane, we can postselect on the case +# where we measured a :math:`0` using the ``postselect`` keyword argument of +# ``qml.measure``: +# + +@qml.qnode(dev) +def after(): + qml.Hadamard(0) # Create |+> state + qml.measure(0, postselect=0) # Measure and only accept 0 as outcome + return qml.expval(qml.X(0)), qml.expval(qml.Z(0)) + +a = after() +print(f"Expectation value after the postselected measurement: {a[0]:.1f}, {a[1]:.1f}") + +###################################################################### +# As expected, we find the that the measured, postselected qubit is in the +# :math:`|0\rangle` eigenstate of the Pauli-:math:`Z` operator with eigenvalue +# :math:`+1,` yielding :math:`\langle X\rangle=0` and :math:`\langle Z\rangle=1.` For +# ``postselect=1``, we would have obtained the :math:`|1\rangle` eigenstate of :math:`Z` +# with eigenvalue :math:`-1,` instead. +# +# Measuring a Bell pair +# ~~~~~~~~~~~~~~~~~~~~~ +# Next, we consider a pair of qubits, entangled in a Bell state: +# +# .. math:: |\phi\rangle = \frac{1}{\sqrt{2}} (|00\rangle + |11\rangle). +# +# This is a pure state with density matrix +# +# .. math:: +# +# |\phi\rangle\langle \phi | = \frac{1}{2}\left(|00\rangle\langle 00| +# + |00\rangle\langle 11| + |11\rangle\langle 00| + |11\rangle\langle 11|\right). +# +# We will again measure only the first qubit. +# We code this circuit up similar to the one above, using an additional ``CNOT`` gate +# to create the Bell state. We also include optional hyperparameters such as +# ``postselect`` as keyword arguments to our quantum function and pass them on to +# ``qml.measure``. Note that we can't complete the quantum function yet, because we still +# need to discuss what to return from it! +# + +def bell_pair_preparation(**kwargs): + qml.Hadamard(0) + qml.CNOT([0, 1]) # Create a Bell pair + qml.measure(0, **kwargs) # Measure first qubit, using keyword arguments + +###################################################################### +# Without recording the outcome, i.e., ``postselect=None``, we obtain the state +# +# .. math:: +# +# M[\rho] = \frac{1}{2}\left(|00\rangle\langle 00| + |11\rangle\langle 11|\right), +# +# which again could be described by a classical mixture as well. If we instead postselect +# on measuring, say, a :math:`1,` we find :math:`M[\rho] = |11\rangle\langle 11|.` +# +# There are two striking differences between whether we record the measurement outcome +# or not: the state of the qubits changes from a mixed to a pure state, as witnessed +# by the state's *purity*; and its entanglement changes, too, as witnessed by the +# *von Neumann entanglement entropy*. We can compute both quantities easily in PennyLane, +# using :func:`~.pennylane.purity` and :func:`~.pennylane.vn_entropy`, respectively. +# And those will be the return types to complete our quantum function: +# + +@qml.qnode(dev) +def bell_pair(postselect): + bell_pair_preparation(postselect=postselect) + return qml.purity([0, 1]), qml.vn_entropy(0) + +###################################################################### +# So let's compare the purities and von Neumann entropies of the Bell state +# after measurement: +# + +without_ps = bell_pair(None) +with_ps = bell_pair(1) +print(f" | without ps | with ps ") +print(f"Purity | {without_ps[0]:.1f} | {with_ps[0]:.1f}") +print(f"Entanglement entropy | {without_ps[1]:.2f} | {with_ps[1]:.1f}") + +###################################################################### +# We indeed see a change in the purity and entanglement entropy based on postselection. +# +# Qubit reset +# ~~~~~~~~~~~ +# +# Another commonly used feature with mid-circuit measurements is to reset the measured +# qubit, i.e., if we measured a :math:`1,` we flip it back into to the :math:`|0\rangle` +# state with a Pauli :math:`X` operation. If there is just one qubit, this is the same +# as if we never measured it but reset it directly to the initial state :math:`|0\rangle,` +# as long as we do not use the measurement outcome for anything. +# For the Bell pair example from above, resetting the measured qubit means that +# we flip the first bit if it is a :math:`1.` Alternatively, we can trace out the +# first qubit and re-initialize it in the state :math:`|0\rangle.` Denoting the reset +# step explicitly as :math:`R,` this leads to the post-measurement state +# +# .. math:: +# +# R[M[\rho]] +# &=|0\rangle\langle 0|\otimes \operatorname{tr}_1[M[\rho]]\\ +# &=|0\rangle\langle 0|\otimes +# \left[\frac{1}{2}\left(|0\rangle\langle 0| + |1\rangle\langle 1|\right)\right]\\ +# &= |0\rangle\langle 0|\otimes \frac{1}{2}\mathbb{I}. +# +# We see that the qubits are no longer entangled, even if we do not postselect. +# Let's compute some exemplary expectation values in this state with PennyLane. +# We recycle the state preparation subroutine from above, to which we +# can simply pass the keyword argument ``reset`` to activate the qubit reset: +# + +@qml.qnode(dev) +def bell_pair_with_reset(reset): + bell_pair_preparation(reset=reset) + return qml.expval(qml.Z(0)), qml.expval(qml.Z(1)), qml.expval(qml.Z(0) @ qml.Z(1)) + +no_reset = bell_pair_with_reset(reset=False) +reset = bell_pair_with_reset(reset=True) + +print(f" | | | ") +print(f"Without reset | {no_reset[0]:.1f} | {no_reset[1]:.1f} | {no_reset[2]:.1f}") +print(f"With reset | {reset[0]:.1f} | {reset[1]:.1f} | {reset[2]:.1f}") + +###################################################################### +# Resetting the qubit changed the expectation values of the local observable :math:`Z_0` +# and the global observable :math:`Z_0Z_1.` +# +# Dynamically controlling a quantum circuit +# ----------------------------------------- +# So far we've only talked about mid-circuit measurements that directly affect the state of +# qubits, about postselection, and about qubit reset as an additional step after +# performing the measurement. However, the outcomes of a measurement can not only be used +# to decide whether or not to discard a circuit execution. More importantly, as +# mid-circuit measurements are performed while the quantum circuit is up and running, their +# outcomes can be used to modify the circuit structure itself *dynamically*. +# +# This technique is widely used to improve quantum algorithms or to trade off classical +# and quantum computing resources. It also is an elementary building block for quantum +# error correction, as the corrections need to happen while the circuit is running. +# +# Here we look at a simple yet instructive example subroutine called a *T-gadget*, +# a technique related to :doc:`quantum teleportation `. +# +# T-gadget in PennyLane +# ~~~~~~~~~~~~~~~~~~~~~ +# In fault-tolerant quantum computing, a standard way to describe a quantum circuit is to +# separate Clifford gates (which map Pauli operators to Pauli operators) from +# :class:`~.pennylane.T` gates. Clifford gates, including :class:`~.pennylane.X`, +# :class:`~.pennylane.Hadamard`, :class:`~.pennylane.S`, and +# :class:`~.pennylane.CNOT`, alone can not express arbitrary quantum circuits, but it's +# enough to add the ``T`` gate to this set [#gottesman]_! +# +# Applying a ``T`` gate on an error-corrected quantum computer is usually hard. +# A *T-gadget* [#zhou]_ allows us to replace a ``T`` gate by Clifford gates, provided +# we have an auxiliary qubit in the right initial state, a so-called magic state. +# The gadget then consists of the following steps: +# +# - Prepare an auxiliary qubit in a magic state +# :math:`(|0\rangle + e^{i\pi/4} |1\rangle)/\sqrt{2},` for example using :doc:`magic +# state distillation `; +# +# - Entangle the auxiliary and target qubit with a ``CNOT``; +# +# - Measure the auxiliary qubit with ``measure`` and record the outcome; +# +# - If the measurement outcome was :math:`1,` apply an ``S`` gate to the target qubit. +# The conditional is realized with :func:`~.pennylane.cond`. +# + +import numpy as np + +magic_state = np.array([1, np.exp(1j * np.pi / 4)]) / np.sqrt(2) + +def t_gadget(wire, aux_wire): + qml.StatePrep(magic_state, aux_wire) + qml.CNOT([wire, aux_wire]) + mcm = qml.measure(aux_wire, reset=True) # Resetting disentangles aux qubit + qml.cond(mcm, qml.S)(wire) # Apply qml.S(wire) if mcm was 1 + +###################################################################### +# We will not derive why this works (see, e.g., [#zhou]_ instead), but +# illustrate that this gadget implements a ``T`` gate by combining it with an adjoint +# ``T†`` gate and looking at the resulting action on the +# eigenstates of ``X``. For this, we +# +# - prepare a :math:`|+\rangle` or :math:`|-\rangle` state, chosen by an input; +# +# - apply the T-gadget from above; +# +# - apply ``T†``, using :func:`~.pennylane.adjoint`; +# +# - return the expectation value :math:`\langle X_0\rangle.` +# + +@qml.qnode(dev) +def test_t_gadget(init_state): + qml.Hadamard(0) # Create |+> state + if init_state == "-": + qml.Z(0) # Flip to |-> state + + t_gadget(0, 1) # Apply T-gadget + qml.adjoint(qml.T)(0) # Apply T^† to undo the gadget + + return qml.expval(qml.X(0)) + +print(f" with initial state |+>: {test_t_gadget('+'):4.1f}") +print(f" with initial state |->: {test_t_gadget('-'):4.1f}") + +###################################################################### +# The T-gadget indeed performs a ``T`` gate, which is being reversed by +# ``T†``. As a result, the expectation values match those of the initial +# states :math:`|\pm\rangle.` +# +# How can we understand the above circuit intuitively? We did not postselect +# the measurement outcome, but we did record (and use) it to modify the +# circuit structure. For a single measurement, or shot, this would have +# led to exactly *one* of the events "measure :math:`0,` do not apply ``S``" +# or "measure :math:`1,` apply ``S``", with equal probability for either one. +# The state on wire ``0`` is :math:`T|\pm\rangle` in either case! +# +# For scenarios in which the different events lead to *distinct* states, +# one has to pay attention to whether a single shot or a collection of +# shots is used, and to the computed measurement statistics. +# +# Conclusion +# ---------- +# +# This concludes our introduction to mid-circuit measurements. We saw how +# quantum mechanical measurements affect qubit systems and how postselection +# affects the state after measurement and validated the theoretical examples +# with short PennyLane examples. Then we looked into dynamic circuits powered +# by operations conditioned on mid-circuit measurements. +# +# For more detailed material also check out the dedicated how-tos +# on :doc:`mid-circuit measurement statistics ` +# and :doc:`dynamic circuits `, +# as well as the `measurements quickstart page +# `_ +# and the documentation of :func:`~.pennylane.measure`. +# +# Happy measuring! +# +# References +# ---------- +# .. [#mike_n_ike] +# +# Michael Nielsen, Isaac Chuang +# "Quantum computation and quantum information", Cambridge university press, +# `Book website `__, 2010. +# +# .. [#feynman] +# +# Richard P. Feynman +# "Feynman lectures on physics", volume 3, +# `open access at Caltech `__, 1963. +# +# .. [#zwiebach] +# +# Barton Zwiebach +# "Quantum Physics II", +# `MIT OpenCourseWare `__, 2013. +# +# .. [#van_der_Sar] +# +# Toeno van der Sar, Gary Steele +# "Open Quantum Sensing and Measurement", +# `open access at TUDelft `__, 2023. +# +# .. [#gottesman] +# +# Daniel Gottesman +# "Theory of fault-tolerant quantum computation", Physical Review A, **57**, 127, +# `open acces at Caltech `__, 1998. +# +# .. [#zhou] +# +# Xinlan Zhou, Debbie W. Leung, Isaac L. Chuang +# "Methodology for quantum logic gate constructions", Physical Review A, **62**, 052316, +# `arXiv quant-ph/0002039 `__, 2000 +# +# diff --git a/demonstrations_v2/tutorial_mcm_introduction/metadata.json b/demonstrations_v2/tutorial_mcm_introduction/metadata.json new file mode 100644 index 0000000000..a651290d70 --- /dev/null +++ b/demonstrations_v2/tutorial_mcm_introduction/metadata.json @@ -0,0 +1,52 @@ +{ + "title": "Introduction to mid-circuit measurements", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-05-10T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mcm_introduction.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mcm_introduction.png" + } + ], + "seoDescription": "Learn the basics of mid-circuit measurements and how to use them in PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_how_to_create_dynamic_mcm_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_collect_mcm_stats", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_teleportation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_mcm_introduction/requirements.in b/demonstrations_v2/tutorial_mcm_introduction/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_mcm_introduction/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_measurement_optimize/demo.py b/demonstrations_v2/tutorial_measurement_optimize/demo.py new file mode 100644 index 0000000000..31d3913e46 --- /dev/null +++ b/demonstrations_v2/tutorial_measurement_optimize/demo.py @@ -0,0 +1,836 @@ +r""" +Measurement optimization +======================== + +.. meta:: + :property="og:description": Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/grouping.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_qaoa_intro Intro to QAOA + +*Author: Josh Izaac — Posted: 18 January 2021. Last updated: 29 August 2023.* + +The variational quantum eigensolver (VQE) is the OG variational quantum algorithm. Harnessing +near-term quantum hardware to solve for the electronic structure of molecules, VQE is *the* +algorithm that sparked the variational circuit craze of the last 5 years, and holds great +promise for showcasing a quantum advantage on near-term quantum hardware. It has also inspired +other quantum algorithms such as the :doc:`Quantum Approximate Optimization Algorithm (QAOA) +`. + +To scale VQE beyond the regime of classical computation, however, we need to solve for the +ground state of increasingly larger molecules. A consequence is that the number of +measurements we need to make on the quantum hardware also grows polynomially—a huge bottleneck, +especially when quantum hardware access is limited and expensive. + +To mitigate this 'measurement problem', a plethora of recent research dropped over the course of +2019 and 2020 [#yen2020]_ [#izmaylov2019]_ [#huggins2019]_ [#gokhale2020]_ [#verteletskyi2020]_ , +exploring potential strategies to minimize the number of measurements required. In fact, by grouping +commuting terms of the Hamiltonian, we can significantly reduce the number of +measurements needed—in some cases, reducing the number of measurements by up to 90%! + +.. figure:: /_static/demonstration_assets/measurement_optimize/grouping.png + :width: 90% + :align: center + +In this demonstration, we revisit the VQE algorithm, see first-hand how the required number of +measurements scales as molecule size increases, and finally use these measurement optimization +strategies to minimize the number of measurements we need to make. These techniques are valuable +beyond just VQE, allowing you to add measurement optimization to your toolkit of techniques to +perform variational algorithms more efficiently. + +Revisiting VQE +-------------- + +The study of :doc:`variational quantum algorithms ` was spearheaded +by the introduction of the :doc:`variational quantum eigensolver ` (VQE) algorithm in +2014 [#peruzzo2014]_. While classical variational techniques have been known for decades to estimate +the ground state energy of a molecule, VQE allowed this variational technique to be applied using +quantum computers. Since then, the field of variational quantum algorithms has evolved +significantly, with larger and more complex models being proposed (such as +:doc:`quantum neural networks `, :doc:`QGANs `, and +:doc:`variational classifiers `). However, quantum chemistry +remains one of the flagship use-cases for variational quantum algorithms, and VQE the standard-bearer. + +Part of the appeal of VQE lies within its simplicity. A circuit ansatz :math:`U(\theta)` is chosen +(typically the Unitary Coupled-Cluster Singles and Doubles +(:func:`~pennylane.templates.subroutines.UCCSD`) ansatz), and the qubit representation of the +molecular Hamiltonian is computed: + +.. math:: H = \sum_i c_i h_i, + +where :math:`h_i` are the terms of the Hamiltonian written as a tensor product of Pauli operators or the identity +acting on wire :math:`n,` :math:`P_n \in \{I, \sigma_x, \sigma_y, \sigma_z\}:` + +.. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. + +(The :math:`h_i` product of Pauli terms is often referred to as a 'Pauli word' in the literature.) The cost +function of the VQE is then simply the expectation value of this Hamiltonian on the state obtained +after running the variational quantum circuit: + +.. math:: \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger H U(\theta) | 0 \rangle. + +By using a classical optimizer to *minimize* this quantity, we can estimate +the ground state energy of the Hamiltonian :math:`H:` + +.. math:: H U(\theta_{min}) |0\rangle = E_{min} U(\theta_{min}) |0\rangle. + +In practice, when we are using quantum hardware to compute these expectation values we expand out +the Hamiltonian as its summation, resulting in separate expectation values that need to be calculated for each term: + +.. math:: + + \text{cost}(\theta) = \langle 0 | U(\theta)^\dagger \left(\sum_i c_i h_i\right) U(\theta) | 0 \rangle + = \sum_i c_i \langle 0 | U(\theta)^\dagger h_i U(\theta) | 0 \rangle. + +.. note:: + + How do we compute the qubit representation of the molecular Hamiltonian? This is a more + complicated story that involves applying a self-consistent field method (such as Hartree-Fock), + and then performing a fermionic-to-qubit mapping such as the Jordan-Wigner or Bravyi-Kitaev + transformations. + + For more details on this process, check out the :doc:`/demos/tutorial_quantum_chemistry` + tutorial. + +The measurement problem +----------------------- + +For small molecules, the VQE algorithm scales and performs exceedingly well. For example, for the +Hydrogen molecule :math:`\text{H}_2,` the final Hamiltonian in its qubit representation +has 15 terms that need to be measured. Let's obtain the Hamiltonian from +`PennyLane's dataset library `__ +to verify the number of terms. In this tutorial, we use the :func:`~.pennylane.data.load` +function to download the dataset of the molecule. + +""" + +import functools +import warnings +from pennylane import numpy as np +import pennylane as qml + + +dataset = qml.data.load('qchem', molname="H2", bondlength=0.7)[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print(H) + +############################################################################### +# Here, we can see that the Hamiltonian involves 15 terms, so we expect to compute 15 expectation values +# on hardware. Let's generate the cost function to check this. + +# Create a 4 qubit simulator +dev = qml.device("default.qubit", shots=1000, seed=904932) + +# number of electrons +electrons = 2 + +# Define the Hartree-Fock initial state for our variational circuit +initial_state = qml.qchem.hf_state(electrons, num_qubits) + +# Construct the UCCSD ansatz +singles, doubles = qml.qchem.excitations(electrons, num_qubits) +s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles) +ansatz = functools.partial( + qml.UCCSD, init_state=initial_state, s_wires=s_wires, d_wires=d_wires +) + +# generate the cost function +@qml.qnode(dev, interface="autograd") +def cost_circuit(params): + ansatz(params, wires=range(num_qubits)) + return qml.expval(H) + +############################################################################## +# If we evaluate this cost function, we can see that it corresponds to 15 different +# executions under the hood—one per expectation value: + +params = np.random.normal(0, np.pi, len(singles) + len(doubles)) +with qml.Tracker(dev) as tracker: # track the number of executions + print("Cost function value:", cost_circuit(params)) + +print("Number of quantum evaluations:", tracker.totals['executions']) + +############################################################################## +# How about a larger molecule? Let's try the +# `water molecule `__: + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) + +print("Required number of qubits:", num_qubits) +print("Number of Hamiltonian terms/required measurements:", len(H.terms()[0])) + +print("\n", H) + + +############################################################################## +# Simply going from two atoms in :math:`\text{H}_2` to three in :math:`\text{H}_2 \text{O}` +# resulted in over triple the number of qubits required and 1086 measurements that must be made! +# +# We can see that as the size of our molecule increases, we run into a problem: larger molecules +# result in Hamiltonians that not only require a larger number of qubits :math:`N` in their +# representation, but the number of terms in the Hamiltonian scales like +# :math:`\mathcal{O}(N^4)!` 😱😱😱 +# +# We can mitigate this somewhat by choosing smaller `basis sets +# `__ to represent the electronic structure +# wavefunction, however this would be done at the cost of solution accuracy, and doesn't reduce the number of +# measurements significantly enough to allow us to scale to classically intractable problems. +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/n4.png +# :width: 70% +# :align: center +# +# The number of qubit Hamiltonian terms required to represent various molecules in the specified +# basis sets (adapted from `Minimizing State Preparations for VQE by Gokhale et al. +# `__) + + +############################################################################## +# Simultaneously measuring observables +# ------------------------------------ +# +# One of the assumptions we made above was that every term in the Hamiltonian must be measured independently. +# However, this might not be the case. From the `Heisenberg uncertainty relationship +# `__ for two +# observables :math:`\hat{A}` and :math:`\hat{B},` we know that +# +# .. math:: \sigma_A^2 \sigma_B^2 \geq \frac{1}{2}\left|\left\langle [\hat{A}, \hat{B}] \right\rangle\right|, +# +# where :math:`\sigma^2_A` and :math:`\sigma^2_B` are the variances of measuring the expectation value of the +# associated observables, and +# +# .. math:: [\hat{A}, \hat{B}] = \hat{A}\hat{B}-\hat{B}\hat{A} +# +# is the commutator. Therefore, +# +# - If the two observables :math:`\hat{A}` and :math:`\hat{B}` do not commute (:math:`[\hat{A}, +# \hat{B}] \neq 0`), then :math:`\sigma_A^2 +# \sigma_B^2 > 0` and we cannot simultaneously measure the expectation values of the two +# observables. +# +# .. +# +# - If :math:`\hat{A}` and :math:`\hat{B}` **do** commute (:math:`[\hat{A}, +# \hat{B}] = 0`), then :math:`\sigma_A^2 +# \sigma_B^2 \geq 0` and there exists a measurement basis where we can **simultaneously measure** the +# expectation value of both observables on the same state. +# +# To explore why commutativity and simultaneous measurement are related, let's assume that there +# is a complete, orthonormal eigenbasis :math:`|\phi_n\rangle` that *simultaneously +# diagonalizes* both :math:`\hat{A}` and :math:`\hat{B}:` +# +# .. math:: +# +# ① ~~ \hat{A} |\phi_n\rangle &= \lambda_{A,n} |\phi_n\rangle,\\ +# ② ~~ \hat{B} |\phi_n\rangle &= \lambda_{B,n} |\phi_n\rangle. +# +# where :math:`\lambda_{A,n}` and :math:`\lambda_{B,n}` are the corresponding eigenvalues. +# If we pre-multiply the first equation by :math:`\hat{B},` and the second by :math:`\hat{A}` +# (both denoted in blue): +# +# .. math:: +# +# \color{blue}{\hat{B}}\hat{A} |\phi_n\rangle &= \lambda_{A,n} \color{blue}{\hat{B}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle,\\ +# \color{blue}{\hat{A}}\hat{B} |\phi_n\rangle &= \lambda_{B,n} \color{blue}{\hat{A}} +# |\phi_n\rangle = \lambda_{A,n} \color{blue}{\lambda_{B,n}} |\phi_n\rangle. +# +# We can see that assuming a simultaneous eigenbasis requires that +# :math:`\hat{A}\hat{B}|\phi_n\rangle = \hat{B}\hat{A}|\phi_n\rangle.` Or, rearranging, +# +# .. math:: (\hat{A}\hat{B} - \hat{B}\hat{A}) |\phi_n\rangle = [\hat{A}, \hat{B}]|\phi_n\rangle = 0. +# +# Our assumption that :math:`|\phi_n\rangle` simultaneously diagonalizes both :math:`\hat{A}` and +# :math:`\hat{B}` only holds true if the two observables commute. +# +# So far, this seems awfully theoretical. What does this mean in practice? +# +# In the realm of variational circuits, we typically want to compute expectation values of an +# observable on a given state :math:`|\psi\rangle.` If we have two commuting observables, we now know that +# they share a simultaneous eigenbasis: +# +# .. math:: +# +# \hat{A} &= \sum_n \lambda_{A, n} |\phi_n\rangle\langle \phi_n|,\\ +# \hat{B} &= \sum_n \lambda_{B, n} |\phi_n\rangle\langle \phi_n|. +# +# Substituting this into the expression for the expectation values: +# +# .. math:: +# +# \langle\hat{A}\rangle &= \langle \psi | \hat{A} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{A, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{A,n} +# |\langle \phi_n|\psi\rangle|^2,\\ +# \langle\hat{B}\rangle &= \langle \psi | \hat{B} | \psi \rangle = \langle \psi | \left( \sum_n +# \lambda_{B, n} |\phi_n\rangle\langle \phi_n| \right) | \psi \rangle = \sum_n \lambda_{B,n} +# |\langle \phi_n|\psi\rangle|^2. +# +# So, assuming we know the eigenvalues of the commuting observables in advance, if we perform a +# measurement in their shared eigenbasis (the :math:`|\phi_n\rangle`), we only need to perform a **single measurement** of the +# probabilities :math:`|\langle \phi_n|\psi\rangle|^2` in order to recover both expectation values! 😍 +# +# Fantastic! But, can we use this to reduce the number of measurements we need to perform in the VQE algorithm? +# To do so, we must find the answer to two questions: +# +# 1. How do we determine which terms of the cost Hamiltonian commute? +# +# 2. How do we rotate the circuit into the shared eigenbasis prior to measurement? +# +# The answers to these questions aren't necessarily easy nor straightforward. Thankfully, there are +# some recent techniques we can harness to address both. + +############################################################################## +# Qubit-wise commuting Pauli terms +# -------------------------------- +# +# Back when we summarized the VQE algorithm, we saw that each term of the Hamiltonian is generally represented +# as a tensor product of Pauli operators: +# +# .. math:: h_i = \bigotimes_{n=0}^{N-1} P_n. +# +# Luckily, this tensor product structure allows us to take a bit of a shortcut. Rather than consider +# **full commutativity**, we can consider a more strict condition known as **qubit-wise +# commutativity** (QWC). +# +# To start with, let's consider single-qubit Pauli operators and the identity. We know that the Pauli operators +# commute with themselves as well as the identity, but they do *not* commute with +# each other: +# +# .. math:: +# +# [\sigma_i, I] = 0, ~~~ [\sigma_i, \sigma_i] = 0, ~~~ [\sigma_i, \sigma_j] = c \sigma_k \delta_{ij}. +# +# Now consider two tensor products of Pauli terms, for example :math:`X\otimes Y \otimes I` and +# :math:`X\otimes I \otimes Z.` We say that these two terms are qubit-wise commuting, since, if +# we compare each subsystem in the tensor product, we see that every one commutes: +# +# .. math:: +# +# \begin{array}{ | *1c | *1c | *1c | *1c | *1c |} +# X &\otimes &Y &\otimes &I\\ +# X &\otimes &I &\otimes &Z +# \end{array} ~~~~ \Rightarrow ~~~~ [X, X] = 0, ~~~ [Y, I] = 0, ~~~ [I, Z] = 0. +# +# As a consequence, both terms must commute: +# +# .. math:: [X\otimes Y \otimes I, X\otimes I \otimes Z] = 0. +# +# .. important:: +# +# Qubit-wise commutativity is a **sufficient** but not **necessary** condition +# for full commutativity. For example, the two Pauli terms :math:`Y\otimes Y` and +# :math:`X\otimes X` are not qubit-wise commuting, but do commute (have a go verifying this!). +# +# Once we have identified a qubit-wise commuting pair of Pauli terms, it is also straightforward to +# find the gates to rotate the circuit into the shared eigenbasis. To do so, we simply rotate +# each qubit one-by-one depending on the Pauli operator we are measuring on that wire: +# +# .. raw:: html +# +# +#
+# +# .. rst-class:: docstable +# +# +------------------+-------------------------------+ +# | Observable | Rotation gate | +# +==================+===============================+ +# | :math:`X` | :math:`RY(-\pi/2) = H` | +# +------------------+-------------------------------+ +# | :math:`Y` | :math:`RX(\pi/2)=HS^{-1}=HSZ` | +# +------------------+-------------------------------+ +# | :math:`Z` | :math:`I` | +# +------------------+-------------------------------+ +# | :math:`I` | :math:`I` | +# +------------------+-------------------------------+ +# +# .. raw:: html +# +#
+# +# Therefore, in this particular example: +# +# * Wire 0: we are measuring both terms in the :math:`X` basis, apply the Hadamard gate +# * Wire 1: we are measuring both terms in the :math:`Y` basis, apply a :math:`RX(\pi/2)` gate +# * Wire 2: we are measuring both terms in the :math:`Z` basis (the computational basis), no gate needs to be applied. +# +# Let's use PennyLane to verify this. + + +obs = [ + qml.PauliX(0) @ qml.PauliY(1), + qml.PauliX(0) @ qml.PauliZ(2) +] + + +############################################################################## +# First, let's naively use two separate circuit evaluations to measure +# the two QWC terms. + + +dev = qml.device("default.qubit", wires=3) + +@qml.qnode(dev, interface="autograd") +def circuit1(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[0]) + + +@qml.qnode(dev, interface="autograd") +def circuit2(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(obs[1]) + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) +rng = np.random.default_rng(192933) +weights = rng.normal(scale=0.1, size=param_shape) + +print("Expectation value of XYI = ", circuit1(weights)) +print("Expectation value of XIZ = ", circuit2(weights)) + +############################################################################## +# Now, let's use our QWC approach to reduce this down to a *single* measurement +# of the probabilities in the shared eigenbasis of both QWC observables: + +@qml.qnode(dev, interface="autograd") +def circuit_qwc(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + + # rotate wire 0 into the shared eigenbasis + qml.RY(-np.pi / 2, wires=0) + + # rotate wire 1 into the shared eigenbasis + qml.RX(np.pi / 2, wires=1) + + # wire 2 does not require a rotation + + # measure probabilities in the computational basis + return qml.probs(wires=range(3)) + + +rotated_probs = circuit_qwc(weights) +print(rotated_probs) + + +############################################################################## +# We're not quite there yet; we have only calculated the probabilities of the variational circuit +# rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the +# *expectation values* of the two QWC observables from the probabilities, recall that we need one +# final piece of information: their eigenvalues :math:`\lambda_{A, n}` and :math:`\lambda_{B, n}.` +# +# We know that the single-qubit Pauli operators each have eigenvalues :math:`(1, -1),` while the identity +# operator has eigenvalues :math:`(1, 1).` We can make use of ``np.kron`` to quickly +# generate the eigenvalues of the full Pauli terms, making sure that the order +# of the eigenvalues in the Kronecker product corresponds to the tensor product. + +eigenvalues_XYI = np.kron(np.kron([1, -1], [1, -1]), [1, 1]) +eigenvalues_XIZ = np.kron(np.kron([1, -1], [1, 1]), [1, -1]) + +# Taking the linear combination of the eigenvalues and the probabilities +print("Expectation value of XYI = ", np.dot(eigenvalues_XYI, rotated_probs)) +print("Expectation value of XIZ = ", np.dot(eigenvalues_XIZ, rotated_probs)) + + +############################################################################## +# Compare this to the result when we used two circuit evaluations. We have successfully used a +# single circuit evaluation to recover both expectation values! +# +# Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply +# return the two QWC Pauli terms from the QNode: + +@qml.qnode(dev, interface="autograd") +def circuit(weights): + qml.StronglyEntanglingLayers(weights, wires=range(3)) + return [ + qml.expval(qml.PauliX(0) @ qml.PauliY(1)), + qml.expval(qml.PauliX(0) @ qml.PauliZ(2)) + ] + + +print(circuit(weights)) + + +############################################################################## +# Behind the scenes, PennyLane is making use of our built-in +# :mod:`qml.pauli ` module, which contains functions for diagonalizing QWC +# terms: + +rotations, new_obs = qml.pauli.diagonalize_qwc_pauli_words(obs) + +print(rotations) +print(new_obs) + + +############################################################################## +# Here, the first line corresponds to the basis rotations that were discussed above, written in +# terms of ``RX`` and ``RY`` rotations. Check out the :mod:`qml.pauli ` +# documentation for more details on its provided functionality and how it works. +# +# Given a Hamiltonian containing a large number of Pauli terms, +# there is a high likelihood of there being a significant number of terms that qubit-wise commute. Can +# we somehow partition the terms into **fewest** number of QWC groups to minimize the number of measurements +# we need to take? + +############################################################################## +# Grouping QWC terms +# ------------------ +# +# A nice example is provided in [#verteletskyi2020]_ showing how we might tackle this. Say we have +# the following Hamiltonian defined over four qubits: +# +# .. math:: H = Z_0 + Z_0 Z_1 + Z_0 Z_1 Z_2 + Z_0 Z_1 Z_2 Z_3 + X_2 X_3 + Y_0 X_2 X_3 + Y_0 Y_1 X_2 X_3, +# +# where we are using the shorthand :math:`P_0 P_2 = P\otimes I \otimes P \otimes I` for brevity. +# If we go through and work out which Pauli terms are qubit-wise commuting, we can represent +# this in a neat way using a graph: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph1.png +# :width: 70% +# :align: center +# +# In the above graph, every node represents an individual Pauli term of the Hamiltonian, with +# edges connecting terms that are qubit-wise commuting. Groups of qubit-wise commuting terms are +# represented as **complete subgraphs**. Straight away, we can make an observation: +# there is no unique solution for partitioning the Hamiltonian into groups of qubit-wise commuting +# terms! In fact, there are several solutions: +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph2.png +# :width: 90% +# :align: center +# +# Of course, of the potential solutions above, there is one that is more optimal than the others --- +# on the bottom left, we have partitioned the graph into *two* complete subgraphs, as opposed to the +# other solutions that require three complete subgraphs. If we were to go with this solution, +# we would be able to measure the expectation value of the Hamiltonian using two circuit evaluations. +# +# This problem—finding the minimum number of complete subgraphs of a graph—is actually quite well +# known in graph theory, where it is referred to as the `minimum clique cover problem +# `__ (with 'clique' being another term for a complete subgraph). +# +# Unfortunately, that's where our good fortune ends—the minimum clique cover problem is known to +# be `NP-hard `__, meaning there is no known (classical) +# solution to finding the optimum/minimum clique cover in polynomial time. +# +# Thankfully, there is a silver lining: we know of polynomial-time algorithms for finding +# *approximate* solutions to the minimum clique cover problem. These heuristic approaches, while +# not guaranteed to find the optimum solution, scale quadratically with the number of nodes in the +# graph/terms in the Hamiltonian [#yen2020]_, so work reasonably well in practice. +# +# Many of these heuristic approaches have roots in another graph problem known as `graph +# colouring `__; the assignment of colours to +# the graph's vertices such that no adjacent vertices have the same colour. How is this related +# to the minimum clique cover problem, though? If we take our QWC graph above, and generate the +# `complement graph `__ by drawing edges +# between all *non*-adjacent nodes, +# +# .. figure:: /_static/demonstration_assets/measurement_optimize/graph3.png +# :width: 100% +# :align: center +# +# we see that solving the minimum clique cover problem on the QWC graph is equivalent to solving the +# graph colouring problem on the complement graph using the minimum possible number of colours. +# While there are various different heuristic algorithms, a common one is `greedy colouring +# `__; in fact, the open-source graph +# package `NetworkX even provides a function for greedy colouring +# `__, +# ``nx.greedy_color``. +# +# Let's give this a go, using NetworkX to solve the minimum clique problem for observable grouping. +# First, we'll need to generate the QWC graph (with each node corresponding to a Hamiltonian +# term, and edges indicating two terms that are QWC). + +import networkx as nx +from matplotlib import pyplot as plt + +terms = [ + qml.PauliZ(0), + qml.PauliZ(0) @ qml.PauliZ(1), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2), + qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2) @ qml.PauliZ(3), + qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliX(2) @ qml.PauliX(3), + qml.PauliY(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliX(3) +] + +def format_pauli_word(term): + """Convenience function that nicely formats a PennyLane + tensor observable as a Pauli word""" + if isinstance(term, qml.ops.Prod): + return " ".join([format_pauli_word(t) for t in term]) + + return f"{term.name[-1]}{term.wires.tolist()[0]}" + +G = nx.Graph() + +with warnings.catch_warnings(): + # Muting irrelevant warnings + warnings.filterwarnings( + "ignore", + message="The behaviour of operator ", + category=UserWarning, + ) + + # add the terms to the graph + G.add_nodes_from(terms) + + # add QWC edges + G.add_edges_from([ + [terms[0], terms[1]], # Z0 <--> Z0 Z1 + [terms[0], terms[2]], # Z0 <--> Z0 Z1 Z2 + [terms[0], terms[3]], # Z0 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[2]], # Z0 Z1 <--> Z0 Z1 Z2 + [terms[2], terms[3]], # Z0 Z1 Z2 <--> Z0 Z1 Z2 Z3 + [terms[1], terms[3]], # Z0 Z1 <--> Z0 Z1 Z2 Z3 + [terms[0], terms[4]], # Z0 <--> X2 X3 + [terms[1], terms[4]], # Z0 Z1 <--> X2 X3 + [terms[4], terms[5]], # X2 X3 <--> Y0 X2 X3 + [terms[4], terms[6]], # X2 X3 <--> Y0 Y1 X2 X3 + [terms[5], terms[6]], # Y0 X2 X3 <--> Y0 Y1 X2 X3 + ]) + + plt.margins(x=0.1) + nx.draw( + G, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1" + ) + + ############################################################################## + # We can now generate the complement graph (compare this to our handdrawn + # version above!): + + C = nx.complement(G) + coords = nx.spring_layout(C) + + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color="#9eded1", + edge_color="#c1c1c1" + ) + + + ############################################################################## + # Now that we have the complement graph, we can perform a greedy coloring to + # determine the minimum number of QWC groups: + + groups = nx.coloring.greedy_color(C, strategy="largest_first") + + # plot the complement graph with the greedy colouring + nx.draw( + C, + coords, + labels={node: format_pauli_word(node) for node in terms}, + with_labels=True, + node_size=500, + font_size=8, + node_color=[("#9eded1", "#aad4f0")[groups[node]] for node in C], + edge_color="#c1c1c1" + ) + + +num_groups = len(set(groups.values())) +print("Minimum number of QWC groupings found:", num_groups) + + +for i in range(num_groups): + print(f"\nGroup {i}:") + + for term, group_id in groups.items(): + if group_id == i: + print(format_pauli_word(term)) + +############################################################################## +# Putting it all together +# ----------------------- +# +# So, we now have a strategy for minimizing the number of measurements we need to perform +# for our VQE problem: +# +# 1. Determine which terms of the Hamiltonian are qubit-wise commuting, and use +# this to construct a graph representing the QWC relationship. +# +# 2. Construct the complement QWC graph. +# +# 3. Use a graph colouring heuristic algorithm to determine a graph colouring for the complement graph +# with a minimum number of colours. Each coloured vertex set corresponds to a +# qubit-wise commuting group of Hamiltonian terms. +# +# 4. Generate and evaluate the circuit ansatz (with additional rotations) per +# QWC grouping, extracting probability distributions. +# +# 5. Finally, post-process the probability distributions with the observable eigenvalues +# to recover the Hamiltonian expectation value. +# +# Luckily, the PennyLane ``pauli`` module makes this relatively easy. Let's walk through +# the entire process using the provided grouping functions. +# +# Steps 1-3 (finding and grouping QWC terms in the Hamiltonian) can be done via the +# :func:`qml.pauli.group_observables ` function: + +obs_groupings = qml.pauli.group_observables(terms, grouping_type='qwc', method='rlf') + + +############################################################################## +# The ``grouping_type`` argument allows us to choose how the commuting terms +# are determined (more on that later!) whereas ``method`` determines the colouring +# heuristic (in this case, ``"rlf"`` refers to Recursive Largest First, a variant of largest first colouring heuristic). +# +# If we want to see what the required rotations and measurements are, we can use the +# :func:`qml.pauli.diagonalize_qwc_groupings ` +# function: + +rotations, measurements = qml.pauli.diagonalize_qwc_groupings(obs_groupings) + +############################################################################## +# However, this isn't strictly necessary—recall previously that the QNode +# has the capability to *automatically* measure qubit-wise commuting observables! + +dev = qml.device("default.qubit", wires=4) + +@qml.qnode(dev, interface="autograd") +def circuit(weights, group=None, **kwargs): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return [qml.expval(o) for o in group] + +param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) +weights = np.random.normal(scale=0.1, size=param_shape) +result = [circuit(weights, group=g) for g in obs_groupings] + +print("Term expectation values:") +for group, expvals in enumerate(result): + print(f"Group {group} expectation values:", expvals) + +# Since all the coefficients of the Hamiltonian are unity, +# we can simply sum the expectation values. +print(" = ", np.sum(np.hstack(result))) + + +############################################################################## +# Finally, we don't need to go through this process manually every time; if our cost function can be +# written in the form of an expectation value of a Hamiltonian (as is the case for most VQE and QAOA +# problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to +# automatically optimize the measurements. + +H = qml.Hamiltonian(coeffs=np.ones(len(terms)), observables=terms, grouping_type="qwc") +_, H_ops = H.terms() +@qml.qnode(dev, interface="autograd") +def cost_fn(weights): + qml.StronglyEntanglingLayers(weights, wires=range(4)) + return qml.expval(H) +print(cost_fn(weights)) + +############################################################################## +# Beyond VQE +# ---------- +# +# Wait, hang on. We dove so deeply into measurement grouping and optimization, we forgot to check +# how this affects the number of measurements required to perform the VQE on :math:`\text{H}_2 \text{O}!` +# Let's use our new-found knowledge to see what happens. + +dataset = qml.data.load('qchem', molname="H2O")[0] +H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) +print("Number of Hamiltonian terms/required measurements:", len(H_ops)) + +# grouping +groups = qml.pauli.group_observables(H_ops, grouping_type='qwc', method='rlf') +print("Number of required measurements after optimization:", len(groups)) + +############################################################################## +# We went from 1086 required measurements/circuit evaluations to 320 (just over *one thousand* +# down to *three hundred* 😱😱😱). +# +# As impressive as this is, however, this is just the beginning of the optimization. +# +# While finding qubit-wise commutating terms is relatively straightforward, with a little +# extra computation we can push this number down even further. Recent work has explored +# the savings that can be made by considering *full* commutativity [#yen2020]_, unitary +# partitioning [#izmaylov2019]_, and Fermionic basis rotation grouping [#huggins2019]_. +# Work has also been performed to reduce the classical overhead associated with measurement +# optimization, allowing the classical measurement grouping to be performed in linear time +# [#gokhale2020]_. For example, recall that qubit-wise commutativity is only a subset of +# full commutativity; if we consider full commutativity instead, we can further reduce the +# number of groups required. +# +# Finally, it is worth pointing out that, as the field of variational quantum algorithms grows, this +# problem of measurement optimization no longer just applies to the VQE algorithm (the algorithm it +# was born from). Instead, there are a multitude of algorithms that could benefit from these +# measurement optimization techniques (QAOA being a prime example). +# +# So the next time you are working on a variational quantum algorithm and the number +# of measurements required begins to explode—stop, take a deep breath 😤, and consider grouping +# and optimizing your measurements. +# +# .. note:: +# +# Qubit-wise commuting group information for a wide variety of molecules has been +# pre-computed, and is available for download in +# in the `PennyLane Datasets library `__. + +############################################################################## +# References +# ---------- +# +# .. [#peruzzo2014] +# +# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# .. [#yen2020] +# +# Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov. "Measuring all compatible +# operators in one series of single-qubit measurements using unitary transformations." `Journal of +# Chemical Theory and Computation 16.4 (2020): 2400-2409. +# `__ +# +# .. [#izmaylov2019] +# +# Artur F. Izmaylov, *et al.* "Unitary partitioning approach to the measurement problem in the +# variational quantum eigensolver method." `Journal of Chemical Theory and Computation 16.1 (2019): +# 190-195. `__ +# +# .. [#huggins2019] +# +# William J. Huggins, *et al.* "Efficient and noise resilient measurements for quantum chemistry +# on near-term quantum computers." `arXiv preprint arXiv:1907.13117 (2019). +# `__ +# +# .. [#gokhale2020] +# +# Pranav Gokhale, *et al.* "Minimizing state preparations in variational quantum eigensolver by +# partitioning into commuting families." `arXiv preprint arXiv:1907.13623 (2019). +# `__ +# +# .. [#verteletskyi2020] +# +# Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov. "Measurement optimization in the +# variational quantum eigensolver using a minimum clique cover." `The Journal of Chemical Physics +# 152.12 (2020): 124114. `__ +# +# diff --git a/demonstrations_v2/tutorial_measurement_optimize/metadata.json b/demonstrations_v2/tutorial_measurement_optimize/metadata.json new file mode 100644 index 0000000000..96fa55f33b --- /dev/null +++ b/demonstrations_v2/tutorial_measurement_optimize/metadata.json @@ -0,0 +1,97 @@ +{ + "title": "Measurement optimization", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2021-01-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_measurement_optimization.png" + } + ], + "seoDescription": "Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function.", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean, et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "yen2020", + "type": "article", + "title": "Measuring all compatible operators in one series of single-qubit measurements using unitary transformations.", + "authors": "Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov", + "year": "2020", + "journal": "Journal of Chemical Theory and Computation", + "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.0c00008" + }, + { + "id": "izmaylov2019", + "type": "article", + "title": "Unitary partitioning approach to the measurement problem in the variational quantum eigensolver method.", + "authors": "Artur F. Izmaylov et al.", + "year": "2019", + "journal": "Journal of Chemical Theory and Computation", + "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.9b00791" + }, + { + "id": "huggins2019", + "type": "article", + "title": "Efficient and noise resilient measurements for quantum chemistry on near-term quantum computers.", + "authors": "William J. Huggins et al.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1907.13117" + }, + { + "id": "gokhale2020", + "type": "article", + "title": "Minimizing state preparations in variational quantum eigensolver by partitioning into commuting families.", + "authors": "Pranav Gokhale et al.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1907.13623" + }, + { + "id": "verteletskyi2020", + "type": "article", + "title": "Measurement optimization in the variational quantum eigensolver using a minimum clique cover.", + "authors": "Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov", + "year": "2020", + "journal": "The Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/10.1063/1.5141458" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_measurement_optimize/requirements.in b/demonstrations_v2/tutorial_measurement_optimize/requirements.in new file mode 100644 index 0000000000..f84259cda4 --- /dev/null +++ b/demonstrations_v2/tutorial_measurement_optimize/requirements.in @@ -0,0 +1,3 @@ +matplotlib +networkx +pennylane diff --git a/demonstrations_v2/tutorial_mitigation_advantage/demo.py b/demonstrations_v2/tutorial_mitigation_advantage/demo.py new file mode 100644 index 0000000000..6bd899605c --- /dev/null +++ b/demonstrations_v2/tutorial_mitigation_advantage/demo.py @@ -0,0 +1,275 @@ +r""" +Is quantum computing useful before fault tolerance? +=================================================== + +.. meta:: + :property="og:description": Evidence for the utility of quantum computing before fault tolerance + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_mitigation_advantage.png + +.. related:: + + tutorial_error_mitigation Error mitigation with Mitiq and PennyLane + tutorial_diffable-mitigation Differentiable quantum error mitigation + tutorial_noisy_circuits Noisy circuits + gbs Quantum advantage with Gaussian Boson Sampling + +*Author: Korbinian Kottmann — Posted: 16 June 2023.* + +Can we use contemporary quantum computers for tasks that are both useful *and* hard to classically simulate? +A recent `Nature paper `__ from the team at IBM claims that we can! See how they managed to faithfully estimate expectation +values of reasonably large and reasonably deep quantum circuits using an exciting new :doc:`zero noise extrapolation ` +technique for error mitigation in this demo. + +Introduction +------------ + +We know that quantum computers can do things that classical computers cannot. +But quantum algorithms like Shor's algorithm necessitate fault tolerance via +error correction, which is not yet feasible with currently available machines. One highly debated +question in the field is whether or not noisy devices we have access to `right now` are already +useful or can outperform a classical computer for certain tasks. For the latter point, +demonstrations of quantum computational advantage have been put forward with the +`2019 Sycamore `_ +(Google), the `2020 Jiuzhang `_ (Chinese academy of science) +and the `2022 Borealis `_ (Xanadu) experiments. +These demonstrations, however, are only of limited practical utility. + +A new quest has been set out +for 'practical' quantum computational advantage. That is, a 'useful' application for which a quantum computer +outperforms the best known classical methods. In the +new article [#ibm]_ by a team of scientists at IBM, a case is made that with their latest device +`ibm_kyiv `_ +comprising 127 qubits and record-breaking coherence times, they can faithfully simulate the time +dynamics of a complex quantum many-body system. One of the key achievements of the paper is the +successful application of error mitigation on a large system (that is making use of a learned noise model [#PEC]_), +and demonstrating that it can yield +faithful results even in very noisy scenarios with reasonably deep circuits. + +Problem setting +--------------- +Before we go into the details of the error mitigation methods, let us briefly summarize the problem +setting. The authors of [#ibm]_ are concerned with simulating the time dynamics of the 2D transverse field Ising model + +.. math:: H = -J \sum_{\langle qp \rangle}Z_q Z_p + h \sum_q X_q, + +with nearest neighbor interactions (indicated by :math:`\langle qp \rangle`) matching the topology of their 127-qubit +`ibm_kyiv `_ device. +The system is described by the positive coupling strength :math:`J` and transverse field `h.` +The time evolution is approximated by trotterization of the time evolution operator + +.. math:: U(T) \approx \left(\prod_{\langle qp \rangle} e^{i \delta t J Z_q Z_p} \prod_{q} e^{-i \delta t h X_q} \right)^{\frac{T}{\delta t}} + +for an evolution time :math:`T` and a Trotter step size :math:`\delta t.` That means the circuit of concern here is a +series of consecutive :math:`\text{RZZ}(\theta_J)` and :math:`\text{RX}(\theta_h)` rotations. The corresponding +angles are related to the physical parameters via :math:`\theta_J = -2J \delta t` and :math:`\theta_h = 2h \delta t```.` +From here on, we are going to focus just on the values of :math:`\theta_h` and keep :math:`\theta_J=-\pi/2` fixed +(in the paper, this is due to the simplification this introduces in the decomposition of the :math:`\text{RZZ}` gate in +terms of the required CNOT gates). + +Noisy simulation of the circuits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The complexity of the classical simulation varies with the parameter :math:`\theta_h.` For the extrema +:math:`\theta_h=0` and :math:`\theta_h=\pi/2,` the system becomes trivially solvable. We interpolate between +those extrema and show the final value of a single weight observable :math:`\langle Z_4\rangle` as is done in [#ibm]_. + +To reproduce the key ingredients of [#ibm]_, we are going to simulate a scaled down version of the real system using PennyLane. Instead of 127 qubits, we will use only 9, placed on a :math:`3 \times 3` grid with +nearest neighbor interactions. +We start by setting up the circuits for the time evolution and a noise model consisting of +:class:`~pennylane.DepolarizingChannel` applied to each gate the circuit executes. Physically, this corresponds to applying either of the +single qubit Pauli gates :math:`\{X, Y, Z\}` with probability :math:`p/3` after each gate in the circuit. In simulation, we can simply look +at the classical mixtures introduced by the Kraus operators of the noise channel. That is why we need to use the mixed state simulator. +For more information see e.g. our :doc:`demo on simulating noisy circuits `. +""" +import pennylane as qml +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +jax.config.update("jax_enable_x64", True) +jax.config.update('jax_platform_name', 'cpu') + +n_wires = 9 +# Describe noise +noise_gate = qml.DepolarizingChannel +p = 0.005 + +# Load devices +dev_ideal = qml.device("default.mixed", wires=n_wires) +dev_noisy = qml.transforms.insert(dev_ideal, noise_gate, p, position="all") + +# 3x3 grid with nearest neighbors +connections = [(0, 1), (1, 2), + (3, 4), (4, 5), + (6, 7), (7, 8), + (0, 3), (3, 6), + (1, 4), (4, 7), + (2, 5), (5, 8)] + +def time_evolution(theta_h, n_layers = 10, obs = qml.PauliZ(4)): + for _ in range(n_layers): + for i, j in connections: + qml.IsingZZ(-jnp.pi/2, wires=(i, j)) + for i in range(n_wires): + qml.RX(theta_h, wires=i) + return qml.expval(obs) + +qnode_ideal = qml.QNode(time_evolution, dev_ideal, interface="jax") +qnode_noisy = qml.QNode(time_evolution, dev_noisy, interface="jax") + +############################################################################## +# We can now simulate the final expectation value with and without noise. Note that the ``IsingZZ`` gate is not natively +# supported by the ``default.mixed`` device and will be decomposed into a supported gate set. The noise channels will be +# inserted after all gates in the final decomposed circuit. We use ``jax.vmap`` to vectorize and speed up the execution +# for different values of :math:`\theta_h.` + +thetas = jnp.linspace(0, jnp.pi/2, 50) + +res_ideal = jax.vmap(qnode_ideal)(thetas) +res_noisy = jax.vmap(qnode_noisy)(thetas) + +plt.plot(thetas, res_ideal, label="exact") +plt.plot(thetas, res_noisy, label="noisy") +plt.xticks([0, jnp.pi/8, jnp.pi/4, 3*jnp.pi/8, jnp.pi/2], ["0", "$\\pi$/8", "$\\pi/4$", "$3\\pi/4$", "$\\pi/2$"]) +plt.xlabel("$\\theta_h$") +plt.ylabel("$\\langle Z_4 \\rangle$") +plt.legend() +plt.show() + + + +############################################################################## +# We see that the fidelity of the result is decreased by the noise. Next, we show how this noise can be effectively mitigated. +# +# +# Error mitigation via zero noise extrapolation +# --------------------------------------------- +# +# :doc:`Error mitigation ` is the process of retrieving more accurate information via classical post-processing +# of noisy quantum executions. The authors in [#ibm]_ employ zero noise extrapolation (ZNE), which serves as +# a biased estimator of expectation values. The idea of ZNE is fairly straightforward: Imagine we want to +# obtain the exact quantum function :math:`f` that estimates an expectation value under noiseless evolution. +# However, we only have access to a noisy version :math:`f^{⚡}.` Now suppose we can controllably increase +# the noise present in terms of some noise gain parameter :math:`G.` Here, :math:`G=1` corresponds to +# the default noise present in the device. In ZNE, we evaluate :math:`f^{⚡}` at increasing values of :math:`G,` +# from which we can extrapolate back to zero noise :math:`G=0` via a suitable curve fit. +# +# In order to perform ZNE, we need a control knob that increases the noise of our circuit execution. +# One such method is described in our +# :doc:`demo on differentiable error mitigation ` using circuit folding. +# +# Noise-aware ZNE +# ~~~~~~~~~~~~~~~ +# +# In [#ibm]_, the authors use a more sophisticated control knob to artificially increase the noise. They first learn the parameters +# of an assumed noise model (in their case a Pauli Lindblad model) of their device [#PEC]_. Ideally, one would counteract those effects by +# probabilistically inverting the noise action (probabilistic error cancellation [#PEC]_). However, this comes with an increased sampling overhead, which is not feasible for the size +# of their problem. So instead, they use the knowledge of the learned noise model to artificially add extra noise and perform ZNE. +# +# The noise model of our simulation is relatively simple and we have full control over it. This means that we can simply attenuate the noise of +# our model by an appropriate gain factor. Here, :math:`G=(1, 1.2, 1.6)` in accordance with [#ibm]_. In order to do this in PennyLane, we simply +# set up two new noisy devices with the appropriately attenuated noise parameters. + +dev_noisy1 = qml.transforms.insert(dev_ideal, noise_gate, p*1.2, position="all") +dev_noisy2 = qml.transforms.insert(dev_ideal, noise_gate, p*1.6, position="all") + +qnode_noisy1 = qml.QNode(time_evolution, dev_noisy1, interface="jax") +qnode_noisy2 = qml.QNode(time_evolution, dev_noisy2, interface="jax") + +res_noisy1 = jax.vmap(qnode_noisy1)(thetas) +res_noisy2 = jax.vmap(qnode_noisy2)(thetas) + +############################################################################## +# We can take these results and simply extrapolate back to :math:`G=0` with a polynomial fit. +# We can visualize this by plotting the noisy, exact and extrapolated results. + +Gs = jnp.array([1., 1.2, 1.6]) +y = jnp.array([res_noisy[0], res_noisy1[0], res_noisy2[0]]) +coeff = jnp.polyfit(Gs, y, 2) +x = jnp.linspace(0, 1.6, 100) + +plt.plot(x, jnp.polyval(coeff, x), label="fit") +plt.plot(Gs, y, "x", label="noisy results") +plt.plot([0], res_ideal[0], "X", label="exact result") +plt.xlabel("noise gain G") +plt.ylabel("$\\langle Z_4 \\rangle$") +plt.legend() +plt.show() + +############################################################################## +# We now repeat this procedure for all values of :math:`\theta_h` and see how the results are much improved. +# We can use :func:`~pennylane.transforms.richardson_extrapolate` that performs a polynomial fit of a degree matching the input data size. + +res_mitigated = [qml.transforms.richardson_extrapolate(Gs, [res_noisy[i], res_noisy1[i], res_noisy2[i]]) for i in range(len(res_ideal))] + +plt.plot(thetas, res_ideal, label="exact") +plt.plot(thetas, res_mitigated, label="mitigated") +plt.plot(thetas, res_noisy, label="noisy") +plt.xticks([0, jnp.pi/8, jnp.pi/4, 3*jnp.pi/8, jnp.pi/2], ["0", "$\\pi$/8", "$\\pi/4$", "$3\\pi/4$", "$\\pi/2$"]) +plt.xlabel("$\\theta_h$") +plt.ylabel("$\\langle Z_4 \\rangle$") +plt.legend() +plt.show() + +############################################################################## +# The big achievement in [#ibm]_ is that they managed to showcase the feasibility of this approach on a large scale experimentally for their device. +# This is really good news, as it has not been clear whether or not noise mitigation can be successfully employed on larger scales. The key ingredient +# is the noise-aware attenuation, which allows for more realistic and finer extrapolation at low resource overhead. +# +# +# Comparison with classical methods +# --------------------------------- +# The authors of [#ibm]_ compare their experimental results with classical methods. For this, they consider three +# scenarios of different classical complexity. +# +# For :math:`\theta_h=-\pi/2` (case 1) the dynamics become trivial with just a global phase factor introduced, such that +# starting from the initial state :math:`|0\rangle^{\otimes 127},` the expectation values :math:`\langle Z_q \rangle` are trivially +# one at all times. This serves as an anchor point of orientation. Varying :math:`\theta_h` (case 2) then increases the +# classical simulation complexity. For the circuits chosen, it is still possible to simulate the dynamical +# expectation values of local observables by taking into account their light-cone in the evolution with reduced depth +# (note that these are not full state vector evolutions but rather just directly looking at the dynamical expectation +# values of interest). In the third and most complex case, the circuit is altered such that the light-cone trick from +# before does not work anymore. +# +# One of the points of the paper is to compare the experiments with sophisticated classical simulation methods. +# The authors chose tensor networks, in particular matrix product states (MPS) and isometric tensor network states +# (isoTNS) for simulation. MPS are native to one dimensional topologies, but are often still employed for two +# dimensional systems as is the case here. The justification for that is their lower computational and algorithmic +# complexity as well as the opportunity to draw from +# decades of algorithmic development and optimization. Better suited to the given problem are isoTNS, which are +# restrictions of projected entangled pair states (PEPS) with some simplifications reducing the high computational +# and algorithmic complexity, at the cost of more approximation errors. +# +# In both cases, the so-called bond-dimension :math:`\chi,` a hyperparameter chosen by the user, directly determines +# the bipartite entanglement entropy these states can capture. It is known that due to the area law of entanglement, +# many ground states of relevant physical system can be faithfully approximated with suitably chosen tensor network states +# with finite bond dimension. However, that is generally not the case for time dynamics as the entanglement entropy +# grows linearly and the area law no longer holds. Therefore, the employed tensor network methods are doomed for +# most dynamical simulations, as is showcased in the paper. +# +# `It can be argued `_ that there are better suited +# classical algorithms for these kind of dynamical simulations, +# `with neural quantum states being one of them `_. +# Further, tensor network methods in two and higher dimensions are extremely difficult to implement. The employed methods +# are not well suited for the problem and do not grasp the full breadth of possibilities of +# classical simulation methods. We are curious to see what experts in the field will come up +# with to showcase faithful classical simulations of these circuits. +# +# +# +# +# References +# ---------- +# +# .. [#ibm] +# +# Youngseok Kim, Andrew Eddins, Sajant Anand, Ken Xuan Wei, Ewout van den Berg, Sami Rosenblatt, Hasan Nayfeh, Yantao Wu, Michael Zaletel, Kristan Temme & Abhinav Kandala +# "Evidence for the utility of quantum computing before fault tolerance" +# `Nature 618, 500–505 `__, 2023. +# +# .. [#PEC] +# +# Ewout van den Berg, Zlatko K. Minev, Abhinav Kandala, Kristan Temme +# "Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors" +# `arXiv:2201.09866 `__, 2022. +# +# diff --git a/demonstrations_v2/tutorial_mitigation_advantage/metadata.json b/demonstrations_v2/tutorial_mitigation_advantage/metadata.json new file mode 100644 index 0000000000..08cb7d2a2d --- /dev/null +++ b/demonstrations_v2/tutorial_mitigation_advantage/metadata.json @@ -0,0 +1,74 @@ +{ + "title": "Is quantum computing useful before fault tolerance?", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-06-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/mitigation_advantage/thumbnail_tutorial_mitigation_advantage.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mitigation_advantage.png" + } + ], + "seoDescription": "Using IBM's 127-qubit quantum computer to simulate deep quantum circuits using Zero Noise Extrapolation", + "doi": "", + "references": [ + { + "id": "ibm", + "type": "article", + "title": "Evidence for the utility of quantum computing before fault tolerance", + "authors": "Youngseok Kim, Andrew Eddins, Sajant Anand, Ken Xuan Wei, Ewout van den Berg, Sami Rosenblatt, Hasan Nayfeh, Yantao Wu, Michael Zaletel, Kristan Temme & Abhinav Kandala", + "year": "2023", + "publisher": "Nature", + "doi": "10.1038/s41586-023-06096-3", + "url": "https://www.nature.com/articles/s41586-023-06096-3" + }, + { + "id": "PEC", + "type": "article", + "title": "Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors", + "authors": "Ewout van den Berg, Zlatko K. Minev, Abhinav Kandala, Kristan Temme", + "year": "2022", + "publisher": "", + "url": "https://arxiv.org/abs/2201.09866" + } + ], + "basedOnPapers": [ + "10.1038/s41586-023-06096-3" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "gbs", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_error_mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_mitigation_advantage/requirements.in b/demonstrations_v2/tutorial_mitigation_advantage/requirements.in new file mode 100644 index 0000000000..a7e20f9b86 --- /dev/null +++ b/demonstrations_v2/tutorial_mitigation_advantage/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_mol_geo_opt/demo.py b/demonstrations_v2/tutorial_mol_geo_opt/demo.py new file mode 100644 index 0000000000..681962e167 --- /dev/null +++ b/demonstrations_v2/tutorial_mol_geo_opt/demo.py @@ -0,0 +1,467 @@ +r""" +Optimization of molecular geometries +==================================== + +.. meta:: + :property="og:description": Find the equilibrium geometry of a molecule + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/fig_pes.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + +*Author: Alain Delgado — Posted: 30 June 2021. Last updated: 25 June 2022.* + +Predicting the most stable arrangement of atoms in a molecule is one of the most important tasks +in quantum chemistry. Essentially, this is an optimization problem where the total energy of the +molecule is minimized with respect to the positions of the atomic nuclei. The molecular +geometry obtained from this calculation is in fact the starting point for many simulations of +molecular properties. If the geometry is inaccurate, then any calculations that rely on it may +also be inaccurate. + +Since the nuclei are much heavier than the electrons, we can treat them as point particles +clamped to their positions. Under this assumption, the total energy of the molecule :math:`E(x)` +depends on the nuclear coordinates :math:`x,` which define the potential energy surface. +Solving the stationary problem :math:`\nabla_x E(x) = 0` corresponds to molecular geometry +optimization and the optimized nuclear coordinates determine the equilibrium geometry of the +molecule. The figure below illustrates these concepts for the +`trihydrogen cation `_. Its equilibrium +geometry in the electronic ground state corresponds to the minimum energy of the potential +energy surface. At this minimum, the three hydrogen atoms are located at the vertices of an +equilateral triangle whose side length is the optimized bond length :math:`d.` + +| + +.. figure:: /_static/demonstration_assets/mol_geo_opt/fig_pes.png + :width: 50% + :align: center + +| + +In this tutorial, you will learn how to recast the problem of finding the equilibrium +geometry of a molecule in terms of a general variational quantum algorithm. The +central idea is to consider explicitly that the target electronic Hamiltonian :math:`H(x)` +is a **parametrized** observable that depends on the nuclear coordinates :math:`x.` This +implies that the objective function, defined by the expectation value of the Hamiltonian +computed in the trial state prepared by a quantum computer, depends on both the quantum +circuit and the Hamiltonian parameters. + +The quantum algorithm in a nutshell +----------------------------------- + +The goal of the variational algorithm is to find the global minimum of +the cost function :math:`g(\theta, x) = \langle \Psi(\theta) \vert H(x) \vert \Psi(\theta) \rangle` +with respect to the circuit parameters :math:`\theta` and the +nuclear coordinates :math:`x` entering the electronic Hamiltonian of the molecule. To that end, +we use a gradient-descent method and follow a **joint** optimization scheme where the gradients of +the cost function with respect to circuit and Hamiltonian parameters are simultaneously computed +at each step. This approach does not require nested optimization of the state +parameters for each set of nuclear coordinates, as occurs in classical algorithms for +molecular geometry optimization, where the energy minimum is searched for along the potential energy +surface of the electronic state [#jensenbook]_. + +In this tutorial we demonstrate how to use PennyLane to implement +quantum optimization of molecular geometries. The algorithm consists of the following steps: + +#. Build the parametrized electronic Hamiltonian :math:`H(x)` of the molecule. + +#. Design the variational quantum circuit to prepare the electronic trial state of the + molecule, :math:`\vert \Psi(\theta) \rangle.` + +#. Define the cost function :math:`g(\theta, x) = \langle \Psi(\theta) \vert H(x) \vert + \Psi(\theta) \rangle`. + +#. Initialize the variational parameters :math:`\theta` and :math:`x.` Perform a joint + optimization of the circuit and Hamiltonian parameters to minimize the cost function + :math:`g(\theta, x).` The gradient with respect to the circuit parameters can be obtained + using a variety of established methods, which are natively supported in PennyLane. The + gradients with respect to the nuclear coordinates can be computed using the formula + + .. math:: + + \nabla_x g(\theta, x) = \langle \Psi(\theta) \vert \nabla_x H(x) \vert \Psi(\theta) \rangle. + +Once the optimization is finalized, the circuit parameters determine the energy of the +electronic state, and the nuclear coordinates determine the equilibrium geometry of the +molecule in this state. + +Let's get started! ⚛️ + +Building the parametrized electronic Hamiltonian +------------------------------------------------ + +In this example, we want to optimize the geometry of the trihydrogen cation +:math:`\mathrm{H}_3^+,` described in a minimal basis set, where two electrons are shared +between three hydrogen atoms (see figure above). The molecule is specified by providing a list +with the symbols of the atomic species and a one-dimensional array with the initial +set of nuclear coordinates in `atomic units +`_ . + +""" + +from pennylane import numpy as np + +symbols = ["H", "H", "H"] +x = np.array([0.028, 0.054, 0.0, 0.986, 1.610, 0.0, 1.855, 0.002, 0.0], requires_grad=True) + +############################################################################## +# Next, we need to build the parametrized electronic Hamiltonian :math:`H(x).` +# We use the Jordan-Wigner transformation [#seeley2012]_ to represent the fermionic +# Hamiltonian as a linear combination of Pauli operators, +# +# .. math:: +# +# H(x) = \sum_j h_j(x) \prod_i^{N} \sigma_i^{(j)}. +# +# The expansion coefficients :math:`h_j(x)` carry the dependence on the coordinates :math:`x,` +# the operators :math:`\sigma_i` represent the Pauli group :math:`\{I, X, Y, Z\},` and +# :math:`N` is the number of qubits required to represent the electronic wave function. +# +# We define the function ``H(x)`` to build the parametrized Hamiltonian +# of the trihydrogen cation using the +# :func:`~.pennylane.qchem.molecular_hamiltonian` function. + +import pennylane as qml + + +def H(x): + molecule = qml.qchem.Molecule(symbols, x, charge=1) + return qml.qchem.molecular_hamiltonian(molecule)[0] + + +############################################################################## +# The variational quantum circuit +# ------------------------------- +# +# Here, we describe the second step of the quantum algorithm: define the quantum circuit +# to prepare the electronic ground-state :math:`\vert \Psi(\theta)\rangle` of the +# :math:`\mathrm{H}_3^+` molecule. +# +# Six qubits are required to encode the occupation number of the molecular spin-orbitals. +# To capture the effects of electronic correlations [#kohanoff2006]_, we need to prepare +# the :math:`N`-qubit system in a superposition of the Hartree-Fock state +# :math:`\vert 110000 \rangle` with other states that differ by a double- or single-excitation. +# For example, the state :math:`\vert 000011 \rangle` is obtained by exciting two particles +# from qubits 0, 1 to 4, 5. Similarly, the state :math:`\vert 011000 \rangle` corresponds to a +# single excitation from qubit 0 to 2. This can be done using the single-excitation and +# double-excitation gates :math:`G` and :math:`G^{(2)}` [#qchemcircuits]_ implemented +# in the form of Givens rotations in PennyLane. For more details see the tutorial +# :doc:`tutorial_givens_rotations`. +# +# In addition, we use an adaptive algorithm [#geo_opt_paper]_ to select the excitation +# operations included in the variational quantum circuit. The algorithm proceeds as follows: +# +# #. Generate the indices of the qubits involved in all single- and +# double-excitations. +# For example, the indices of the singly-excited state :math:`\vert 011000 \rangle` +# are given by the list ``[0, 2]``. Similarly, the indices of the doubly-excited +# state :math:`\vert 000011 \rangle` are ``[0, 1, 4, 5]`.` +# +# #. Construct the circuit using all double-excitation gates. Compute the gradient +# of the cost function :math:`g(\theta, x)` with respect to each double-excitation +# gate and retain only those with non-zero gradient. +# +# #. Include the selected double-excitation gates and repeat the process for the +# single-excitation gates. +# +# #. Build the final variational quantum circuit by including the selected gates. +# +# For the :math:`\mathrm{H}_3^+` molecule in a minimal basis set we have a total of eight +# excitations of the reference state. After applying the adaptive algorithm the final +# quantum circuit contains only two double-excitation operations that act on the qubits +# ``[0, 1, 2, 3]`` and ``[0, 1, 4, 5]``. The circuit is illustrated in the figure below. +# +# | +# +# .. figure:: /_static/demonstration_assets/mol_geo_opt/fig_circuit.png +# :width: 60% +# :align: center +# +# | +# +# To implement this quantum circuit, we use the +# :func:`~.pennylane.qchem.hf_state` function to generate the +# occupation-number vector representing the Hartree-Fock state + +hf = qml.qchem.hf_state(electrons=2, orbitals=6) +print(hf) + +############################################################################## +# The ``hf`` array is used by the :class:`~.pennylane.BasisState` operation to initialize +# the qubit register. Then, the :class:`~.pennylane.DoubleExcitation` operations are applied +# First, we define the quantum device used to compute the expectation value. +# In this example, we use the ``lightning.qubit`` simulator: +num_wires = 6 +dev = qml.device("lightning.qubit", wires=num_wires) + + +@qml.qnode(dev, interface="autograd") +def circuit(params, obs, wires): + qml.BasisState(hf, wires=wires) + qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3]) + qml.DoubleExcitation(params[1], wires=[0, 1, 4, 5]) + + return qml.expval(obs) + + +############################################################################## +# This circuit prepares the trial state +# +# .. math:: +# +# \vert\Psi(\theta_1, \theta_2)\rangle = +# \mathrm{cos}(\theta_1)\mathrm{cos}(\theta_2)\vert110000\rangle - +# \mathrm{cos}(\theta_1)\mathrm{sin}(\theta_2)\vert000011\rangle - +# \mathrm{sin}(\theta_1)\vert001100\rangle, +# +# where :math:`\theta_1` and :math:`\theta_2` are the circuit parameters that need to be +# optimized to find the ground-state energy of the molecule. +# +# The cost function and the nuclear gradients +# ------------------------------------------- +# +# The third step of the algorithm is to define the cost function +# :math:`g(\theta, x) = \langle \Psi(\theta) \vert H(x) \vert\Psi(\theta) \rangle.` It +# evaluates the expectation value of the parametrized Hamiltonian :math:`H(x)` in the +# trial state :math:`\vert\Psi(\theta)\rangle.` + +############################################################################## +# Next, we define the ``cost`` function :math:`g(\theta, x)` which depends on +# both the circuit and the Hamiltonian parameters. Specifically we consider the +# expectation values of the Hamiltonian. + + +def cost(params, x): + hamiltonian = H(x) + return circuit(params, obs=hamiltonian, wires=range(num_wires)) + + +############################################################################## +# +# We minimize the cost function :math:`g(\theta, x)` using a gradient-based +# method, and compute the gradients with respect to both the +# circuit parameters :math:`\theta` and the nuclear coordinates :math:`x.` +# The circuit gradients are computed analytically using the automatic differentiation +# techniques available in PennyLane. The nuclear gradients are evaluated +# by taking the expectation value of the gradient of the electronic Hamiltonian, +# +# .. math:: +# +# \nabla_x g(\theta, x) = \langle \Psi(\theta) \vert \nabla_x H(x) \vert \Psi(\theta) \rangle. +# +# We use the :func:`finite_diff` function to compute the gradient of +# the Hamiltonian using a central-difference approximation. Then, we evaluate the expectation +# value of the gradient components :math:`\frac{\partial H(x)}{\partial x_i}.` This is implemented by +# the function ``grad_x``: + + +def finite_diff(f, x, delta=0.01): + """Compute the central-difference finite difference of a function""" + gradient = [] + + for i in range(len(x)): + shift = np.zeros_like(x) + shift[i] += 0.5 * delta + res = (f(x + shift) - f(x - shift)) * delta**-1 + gradient.append(res) + + return gradient + + +def grad_x(params, x): + grad_h = finite_diff(H, x) + grad = [circuit(params, obs=obs, wires=range(num_wires)) for obs in grad_h] + return np.array(grad) + + +############################################################################## +# Optimization of the molecular geometry +# -------------------------------------- +# +# Finally, we proceed to minimize our cost function to find the ground state equilibrium +# geometry of the :math:`\mathrm{H}_3^+` molecule. As a reminder, +# the circuit parameters and the nuclear coordinates will be jointly optimized at +# each optimization step. This approach does not require nested VQE +# optimization of the circuit parameters for each set of nuclear coordinates. +# +# We start by defining the classical optimizers: + +opt_theta = qml.GradientDescentOptimizer(stepsize=0.4) +opt_x = qml.GradientDescentOptimizer(stepsize=0.8) + +############################################################################## +# Next, we initialize the circuit parameters :math:`\theta.` The angles +# :math:`\theta_1` and :math:`\theta_2` are set to zero so that the +# initial state :math:`\vert\Psi(\theta_1, \theta_2)\rangle` +# is the Hartree-Fock state. + +theta = np.array([0.0, 0.0], requires_grad=True) + +############################################################################## +# The initial set of nuclear coordinates :math:`x,` defined at +# the beginning of the tutorial, was computed classically within the Hartree-Fock +# approximation using the GAMESS program [#ref_gamess]_. This is a natural choice +# for the starting geometry that we are aiming to improve due to the electronic +# correlation effects included in the trial state :math:`\vert\Psi(\theta)\rangle.` +# +# We carry out the optimization over a maximum of 100 steps. +# The circuit parameters and the nuclear coordinates are optimized until the +# maximum component of the nuclear gradient :math:`\nabla_x g(\theta,x)` is +# less than or equal to :math:`10^{-5}` Hartree/Bohr. Typically, this is the +# convergence criterion used for optimizing molecular geometries in +# quantum chemistry simulations. + +from functools import partial + +# store the values of the cost function +energy = [] + +# store the values of the bond length +bond_length = [] + +# Factor to convert from Bohrs to Angstroms +bohr_angs = 0.529177210903 + +for n in range(100): + + # Optimize the circuit parameters + theta.requires_grad = True + x.requires_grad = False + theta, _ = opt_theta.step(cost, theta, x) + + # Optimize the nuclear coordinates + x.requires_grad = True + theta.requires_grad = False + _, x = opt_x.step(cost, theta, x, grad_fn=grad_x) + + energy.append(cost(theta, x)) + bond_length.append(np.linalg.norm(x[0:3] - x[3:6]) * bohr_angs) + + if n % 4 == 0: + print(f"Step = {n}, E = {energy[-1]:.8f} Ha, bond length = {bond_length[-1]:.5f} A") + + # Check maximum component of the nuclear gradient + if np.max(grad_x(theta, x)) <= 1e-05: + break + +print("\n" f"Final value of the ground-state energy = {energy[-1]:.8f} Ha") +print("\n" "Ground-state equilibrium geometry") +print("%s %4s %8s %8s" % ("symbol", "x", "y", "z")) +for i, atom in enumerate(symbols): + print(f" {atom} {x[3 * i]:.4f} {x[3 * i + 1]:.4f} {x[3 * i + 2]:.4f}") + +############################################################################## +# Next, we plot the values of the ground state energy of the molecule +# and the bond length as a function of the optimization step. + +import matplotlib.pyplot as plt + +fig = plt.figure() +fig.set_figheight(5) +fig.set_figwidth(12) + +# Add energy plot on column 1 +E_fci = -1.27443765658 +E_vqe = np.array(energy) +ax1 = fig.add_subplot(121) +ax1.plot(range(n + 1), E_vqe - E_fci, "go", ls="dashed") +ax1.plot(range(n + 1), np.full(n + 1, 0.001), color="red") +ax1.set_xlabel("Optimization step", fontsize=13) +ax1.set_ylabel("$E_{VQE} - E_{FCI}$ (Hartree)", fontsize=13) +ax1.text(5, 0.0013, r"Chemical accuracy", fontsize=13) +plt.yscale("log") +plt.xticks(fontsize=12) +plt.yticks(fontsize=12) + +# Add bond length plot on column 2 +d_fci = 0.986 +ax2 = fig.add_subplot(122) +ax2.plot(range(n + 1), bond_length, "go", ls="dashed") +ax2.plot(range(n + 1), np.full(n + 1, d_fci), color="red") +ax2.set_ylim([0.965, 0.99]) +ax2.set_xlabel("Optimization step", fontsize=13) +ax2.set_ylabel("bond length ($\AA$)", fontsize=13) +ax2.text(5, 0.9865, r"Equilibrium bond length", fontsize=13) +plt.xticks(fontsize=12) +plt.yticks(fontsize=12) + +plt.subplots_adjust(wspace=0.3) +plt.show() + +############################################################################## +# | +# Notice that despite the fact that the ground-state energy is already converged +# within chemical accuracy (:math:`0.0016` Ha) after the fourth iteration, more +# optimization steps are required to find the equilibrium bond length of the +# molecule. +# +# The figure below animates snapshots of the atomic structure of the +# trihydrogen cation as the quantum algorithm was searching for the equilibrium +# geometry. For visualization purposes, the initial nuclear coordinates were +# generated by perturbing the HF geometry. The quantum algorithm +# is able to find the correct equilibrium geometry of the :math:`\mathrm{H}_3^+` +# molecule where the three H atoms are located at the vertices of an equilateral triangle. +# +# | +# +# .. figure:: /_static/demonstration_assets/mol_geo_opt/fig_movie.gif +# :width: 50% +# :align: center +# +# | +# +# To summarize, we have shown how the scope of variational quantum algorithms can be +# extended to perform quantum simulations of molecules involving both the electronic and +# the nuclear degrees of freedom. The joint optimization scheme described here +# is a generalization of the usual VQE algorithm where only the electronic +# state is parametrized. Extending the applicability of the variational quantum algorithms to +# target parametrized Hamiltonians could be also relevant to simulate the optical properties of +# molecules where the fermionic observables depend also on the electric field of the +# incoming radiation [#pulay]_. +# +# References +# ---------- +# +# .. [#jensenbook] +# +# F. Jensen. "Introduction to computational chemistry". +# (John Wiley & Sons, 2016). +# +# .. [#seeley2012] +# +# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for +# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 +# (2012). +# `__ +# +# .. [#kohanoff2006] +# +# Jorge Kohanoff. "Electronic structure calculations for solids and molecules: theory and +# computational methods". (Cambridge University Press, 2006). +# +# .. [#qchemcircuits] +# +# J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran. +# "Universal quantum circuits for quantum chemistry". arXiv:2106.13839, (2021) +# +# .. [#ref_gamess] +# +# M.W. Schmidt, K.K. Baldridge, J.A. Boatz, S.T. Elbert, M.S. Gordon, J.H. Jensen, +# S. Koseki, N. Matsunaga, K.A. Nguyen, S.Su, *et al.* "General atomic and molecular +# electronic structure system". `Journal of Computational Chemistry 14, 1347 (1993) +# `__ +# +# .. [#geo_opt_paper] +# +# A. Delgado, J.M. Arrazola, S. Jahangiri, Z. Niu, J. Izaac, C. Roberts, N. Killoran. +# "Variational quantum algorithm for molecular geometry optimization". +# arXiv:2106.13840, (2021) +# +# .. [#pulay] +# +# P. Pulay. +# "Analytical derivative methods in quantum chemistry". +# `Advances in Chemical Sciences (1987) +# `__ +# diff --git a/demonstrations_v2/tutorial_mol_geo_opt/metadata.json b/demonstrations_v2/tutorial_mol_geo_opt/metadata.json new file mode 100644 index 0000000000..084f7fd332 --- /dev/null +++ b/demonstrations_v2/tutorial_mol_geo_opt/metadata.json @@ -0,0 +1,106 @@ +{ + "title": "Optimization of molecular geometries", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2021-06-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimization_of_molecular_geometrics.png" + } + ], + "seoDescription": "Find the equilibrium geometry of a molecule", + "doi": "", + "references": [ + { + "id": "jensenbook", + "type": "book", + "title": "Introduction to computational chemistry", + "authors": "F. Jensen", + "year": "2016", + "publisher": "John Wiley & Sons", + "url": "" + }, + { + "id": "seeley2012", + "type": "article", + "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", + "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", + "year": "2012", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" + }, + { + "id": "kohanoff2006", + "type": "book", + "title": "Electronic structure calculations for solids and molecules: theory and computational methods", + "authors": "Jorge Kohanoff", + "year": "2006", + "publisher": "Cambridge University Press", + "url": "" + }, + { + "id": "qchemcircuits", + "type": "article", + "title": "Universal quantum circuits for quantum chemistry", + "authors": "J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran", + "year": "2021", + "journal": "", + "url": "" + }, + { + "id": "ref_gamess", + "type": "article", + "title": "General atomic and molecular electronic structure system", + "authors": "M.W. Schmidt, K.K. Baldridge, J.A. Boatz, S.T. Elbert, M.S. Gordon, J.H. Jensen, S. Koseki, N. Matsunaga, K.A. Nguyen, S.Su, et al.", + "year": "1993", + "journal": "Journal of Computational Chemistry", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/jcc.540141112" + }, + { + "id": "geo_opt_paper", + "type": "article", + "title": "Variational quantum algorithm for molecular geometry optimization", + "authors": "A. Delgado, J.M. Arrazola, S. Jahangiri, Z. Niu, J. Izaac, C. Roberts, N. Killoran", + "year": "2021", + "journal": "", + "url": "" + }, + { + "id": "pulay", + "type": "article", + "title": "Analytical derivative methods in quantum chemistry", + "authors": "P. Pulay", + "year": "1987", + "journal": "Advances in Chemical Sciences", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/9780470142943.ch4" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_mol_geo_opt/requirements.in b/demonstrations_v2/tutorial_mol_geo_opt/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_mol_geo_opt/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_mps/demo.py b/demonstrations_v2/tutorial_mps/demo.py new file mode 100644 index 0000000000..25d1ff46af --- /dev/null +++ b/demonstrations_v2/tutorial_mps/demo.py @@ -0,0 +1,695 @@ +r"""Introducing matrix product states for quantum practitioners +=============================================================== + +Matrix product states remain the workhorse for a broad range of modern classical quantum simulation techniques, +still to this day. Their unique features (like offering a canonical form) make them an incredibly neat tool +in terms of simplicity and algorithmic complexity. +In this demo, we are going to cover all the essentials you need to know in order to handle matrix product states, +and show how to use them to simulate quantum circuits. + +.. figure:: ../_static/demonstration_assets/how_to_simulate_quantum_circuits_with_tensor_networks/TN_MPS.gif + :align: center + :width: 90% + +Introduction +------------ + +Matrix product states (MPS) are an efficient representation of quantum states in one spatial dimension. +However, due to their unique features like offering a canonical form, they are employed in a variety of tasks beyond just 1D systems. + +The amount of entanglement the MPS can represent is user-controlled via a hyper-parameter, the so-called `bond dimension` :math:`\chi.` +If we allow :math:`\chi` to be of :math:`\mathcal{O}(2^{\frac{n}{2}})` for a system of :math:`n` qubits, we can write `any` state as an `exact` MPS. +To avoid exponentially large resources, however, one typically sets a finite bond dimension :math:`\chi` at the cost of introducing an approximation error. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_mps_simulation.png + :align: center + :width: 70% + +For some specific classes of states, this is provably sufficient to have faithful representations (see :ref:`Area Law`). +But because MPS come with a lot of powerful computational features that we are going to discuss later (in particular :ref:`canonical forms `), +they are still used in much more complex systems where these requirements do not hold anymore, and still yield good results. +For example, state-of-the-art `quantum chemistry `__ simulations were performed using MPS [#Baiardi]_ +and similar methods have been used to simulate experiments on the largest available quantum computers at the time [#Patra]_. + +It is known that there are more suitable tensor network states like projected entangled pair states (PEPS) for +more complex situations (see the :ref:`Area Law` section). +However, they all suffer from the need for significantly more complicated algorithms +and higher costs, as a lot of the things that make MPS so attractive, +like the availability of a canonical form, are not true anymore. +To put it plainly, it is often simply much easier to use readily available +MPS code and throw a large amount of resources into the bond dimension than to develop +more advanced tensor network methods. + +An exception to that are so-called `simple update` +methods [#Jiang]_, which use pseudo-canonical forms that allow a similarly simple +algorithmic complexity at the cost of not being optimal in its resources. +Reference [#Patra]_ is a good example of that. + +More advanced tensor network methods are continually being developed and optimized, though the biggest hindrance to the widespread use of already known advanced tensor network methods is the lack of reliable open-source implementations. +While the current state of affairs in academia is giving little incentive to change that, +MPS continue to be the workhorse for a wide variety of quantum simulation techniques. + +We are going to introduce the essentials of MPS in the first part of this demo. +Afterwards, in the second part, we will look at the specific application of simulating quantum circuits using MPS. + +Matrix product state essentials +------------------------------- + +Compression using singular value decomposition (SVD) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To understand MPS and the bond dimension, we first need to understand how one can generally use singular value decomposition (SVD) to do compression. +Any matrix :math:`M = \mathbb{C}^{M\times N}` can be singular-value-decomposed as + +.. math:: M = U \Lambda V^\dagger, + +where :math:`\Lambda` is the diagonal matrix of the :math:`r=\min(M, N)` real and non-negative singular values, +:math:`U \in \mathbb{C}^{M\times r}` is left-unitary, :math:`U^\dagger U = \mathbb{I}_r,` and +:math:`V^\dagger \in \mathbb{C}^{r\times N}` is right-unitary, :math:`V^\dagger (V^\dagger)^\dagger = \mathbb{I}_r.` +We say the columns and rows of :math:`U` and :math:`V^\dagger` are the left- and right-orthonormal +singular vectors, respectively. In the case of square and normal matrices, the singular values and singular vectors +are just the eigenvalues and eigenvectors. + +Small singular values and their corresponding singular vectors carry little information of the matrix. When the singular values have +a tail of small values, we can compress the matrix by throwing away both these numbers and the corresponding singular vectors. +The power of this approach is best seen by a small example. Let us load the image we have shown in the header of this demo and compress it. + +""" + +import numpy as np +import matplotlib.pyplot as plt + +# import image +img = plt.imread("../_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mps_simulation.png") +# alternative: import image directly from url +# import urllib2 +# img_url = urllib2.urlopen("https://pennylane.ai/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mps_simulation.png") +# img = plt.imread(img_url) + +# only look at one color channel for demonstration purposes +img = img[:, :, 0] + +# Perform SVD +U, Lambda, Vd = np.linalg.svd(img) + +# Keep only the 50 largest singular values and vectors +chi = 50 +U_compressed = U[:, :chi] +Lambda_compressed = Lambda[:chi] +Vd_compressed = Vd[:chi] + +# Reconstruct the compressed image +compressed_img = U_compressed @ np.diag(Lambda_compressed) @ Vd_compressed + +fig, axs = plt.subplots(ncols=2) +ax = axs[0] +ax.imshow(img, vmin=0, vmax=1) +ax.set_title("Uncompressed image") + +ax = axs[1] +ax.imshow(compressed_img, vmin=0, vmax=1) +ax.set_title("Compressed image") + +plt.show() + +size_original = np.prod(img.shape) +size_compressed = np.prod(U_compressed.shape) + np.prod(Lambda_compressed.shape) + np.prod(Vd_compressed.shape) + +print(f"original image size: {size_original}, compressed image size: {size_compressed}, factor {size_original/size_compressed:.3f} saving") +############################################################################## +# +# +# The original image is :math:`334 \times 542` pixels, that we compress as :math:`334 \times 50` pixels in +# :math:`U,` :math:`50` pixels in :math:`\Lambda` and :math:`50 \times 542` pixels in :math:`V^\dagger.` +# This is possible because the information density in the image is low, as seen by the distribution of singular values. +# Let us visualize this by re-computing the singular values and plotting their distribution (note that they are automatically ordered in descending order). + + +_, Lambda, _ = np.linalg.svd(img) # recompute the full spectrum +plt.plot(Lambda) +plt.xlabel("index $i$") +plt.ylabel("$\\Lambda_i$") +plt.show() + +############################################################################## +# +# We are later going to do the same trick with state vectors. +# Note that the compressed information is encoded in :math:`U,` :math:`\Lambda` and :math:`V^\dagger.` +# If we want to retrieve the actual image :math:`M` (or state vector), we still need to reconstruct the full :math:`334 \times 542` pixels. +# Luckily, as we will later see in the case of MPS, we can retrieve all relevant information efficiently from the compressed components without ever +# having to reconstruct the full state vector. +# +# Turn any state into an MPS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Formally, any quantum state :math:`|\psi\rangle \in \mathbb{C}^{2^n}` on :math:`n` qubits can be written as a matrix product state (MPS). +# The goal of this section will be to write an arbitrary state :math:`|\psi\rangle = \sum_{\sigma_1, .., \sigma_n} \psi_{\sigma_1, .., \sigma_n} |\sigma_1 .. \sigma_n\rangle` in the form +# +# .. math:: |\psi \rangle = \sum_{\sigma_1, .., \sigma_n} U^{\sigma_1} .. U^{\sigma_n} |\sigma_1 .. \sigma_n\rangle, +# +# where we decomposed the rank :math:`n` tensor :math:`\psi_{\sigma_1, .., \sigma_n}` into a product of matrices :math:`U^{\sigma_j}` +# for each value of :math:`\sigma_j` (equal to :math:`0` or :math:`1` for qubits). This is why it is called a **matrix product** state. +# While it historically makes sense to treat these indeed as matrices, given a concrete value of :math:`\sigma_j,` I find it more convenient +# to forget about the notion of matrices and just treat them as the collection of rank-3 tensors :math:`\{ U_{\mu_{i-1} \sigma_i \mu_i} \}` +# that have two so-called virtual indices, :math:`\mu_{i-1}, \mu_i,` and one so-called physical index, :math:`\sigma_j.` +# Whenever we write :math:`U^{\sigma_j}` with a superscript, we mean a matrix given a concrete value of :math:`\sigma_j.` +# In particular, we define :math:`\left(U^{\sigma_j}\right)_{\mu_{i-1} \mu_i} = U_{\mu_{i-1} \sigma_i \mu_i}` for notational convenience and to make it easier to follow along with the code. +# +# Graphically, this corresponds to splitting up the big rank-:math:`n` tensor into :math:`n` smaller tensors, +# similarly to our approach in the example of compressing an image using SVD. +# +# .. figure:: ../_static/demonstration_assets/mps/psi_to_mps_0.png +# :align: center +# :width: 70% +# +# .. note:: +# We are going to use combinations of tensor indices and treat them as one big index. +# In particular, the two indices :math:`\sigma_1 = \{0, 1\}` and :math:`\sigma_2 = \{0, 1\}` will have the combined index +# :math:`(\sigma_1 \sigma_2) = \{00, 01, 10, 11\}.` The actual order is a choice and does not matter for the analytic descriptions, +# but in practice, we just choose to do it in the same way as ``numpy`` arrays are reshaped for convenience. +# That way we don't have to worry about it and we also save on transpositions. +# +# The horizontal connections between the :math:`U` tensors are the matrix multiplications in the equation above. +# They are contractions over the virtual indices. The dangling vertical lines are the +# `physical` indices of the original state, :math:`\sigma_i.` +# +# Let us look at a concrete state vector with :math:`n=3` sites, so :math:`\psi_{\sigma_1 \sigma_2 \sigma_3},` and decompose it as an MPS. + +n = 3 # three sites = three legs +psi = np.random.rand(2**3) +psi = psi / np.linalg.norm(psi) # random, normalized state vector +psi = np.reshape(psi, (2, 2, 2)) # rewrite psi as rank-n tensor + +############################################################################## +# +# +# We rewrite the tensor as a matrix with indices of the first site :math:`\sigma_1` and the combined indices of all remaining sites, :math:`(\sigma_2 \sigma_3).` +# Now that we have a matrix, we can perform SVD to split off the first site. Mathematically, this is +# +# .. math:: \psi_{\sigma_1 \sigma_2 \sigma_3} \stackrel{\text{reshape}}{=} \psi_{\sigma_1, (\sigma_2 \sigma_3)} \stackrel{\text{SVD}}{=} \sum_{\mu_1} U_{\sigma_1 \mu_1} \Lambda_{\mu_1} V^\dagger_{\mu_1 (\sigma_2 \sigma_3)}. +# + +# reshape vector to matrix +psi = np.reshape(psi, (2, 2**(n-1))) +# SVD to split off first site +U, Lambda, Vd = np.linalg.svd(psi, full_matrices=False) + +############################################################################## +# +# We multiply the singular values onto :math:`V^\dagger` and call this the remainder state, :math:`\psi'_{\mu_1, (\sigma_2 \sigma_3)},` +# so overall we have +# +# .. math:: \psi_{\sigma_1 \sigma_2 \sigma_3} = \sum_{\mu_1} U_{\sigma_1 \mu_1} \psi'_{\mu_1, (\sigma_2 \sigma_3)}. +# +# Graphically, this corresponds to the following. +# +# .. figure:: ../_static/demonstration_assets/mps/psi_to_mps_1.png +# :align: center +# :width: 70% +# +# Note that because :math:`\Lambda` is diagonal, it has the same virtual index on either side. +# +# We keep the :math:`U` tensors. We want to maintain the convention that they are of the shape ``(virtual_left, physical, virtual_right).`` +# Because there is no virtual index on the left for the first site, we introduce a dummy index of size ``1``. +# This is just to make the bookkeeping of the final MPS a bit simpler, as all tensors have the same shape structure. + +Us = [] +U = np.reshape(U, (1, 2, 2)) # mu1, s2, mu2 +Us.append(U) + +############################################################################## +# +# This procedure is repeated through all sites. The first step was special in that :math:`U_{\sigma_1 \mu_1}` is a vector for each value of :math:`\sigma_1.` +# When splitting up :math:`\psi'_{\mu_1, (\sigma_2 \sigma_3)}` we combine the virtual bond with the current site, and have all remaining sites be the other leg of the matrix we create for SVD. +# In particular, we do the following. +# +# .. math:: \psi'_{\mu_1, (\sigma_2 \sigma_3)} \stackrel{\text{reshape}}{=} \psi'_{(\mu_1 \sigma_2), (\sigma_3)} \stackrel{\text{SVD}}{=} \sum_{\mu_2} U_{\mu_1 \sigma_2 \mu_2} \Lambda_{\mu_2} V_{\mu_2 \sigma_3} +# + +psi_remainder = np.diag(Lambda) @ Vd # mu1 (s2 s3) +psi_remainder = np.reshape(psi_remainder, (2*2, 2)) # (mu1 s2), s3 +U, Lambda, Vd = np.linalg.svd(psi_remainder, full_matrices=False) + +U = np.reshape(U, (2, 2, 2)) # mu1, s2, mu2 +Us.append(U) + +U.shape, Lambda.shape, Vd.shape + +############################################################################## +# We again multiply the singular values onto the new :math:`V^\dagger` and take that as the remainder state :math:`\psi''.` +# The state overall now reads +# +# .. math:: \psi_{\sigma_1 \sigma_2 \sigma_3} = \sum_{\mu_1 \mu_2} U_{\sigma_1\mu_1} U_{\mu_1 \sigma_2 \mu_2} \psi''_{\mu_2 \sigma_3}. +# +# Graphically, this corresponds to +# +# .. figure:: ../_static/demonstration_assets/mps/psi_to_mps_2.png +# :align: center +# :width: 70% +# +# When the state is normalized, we are done. Otherwise, we can do the procedure one more time, again with a virtual dummy dimension on the right-most site. + +psi_remainder = np.diag(Lambda) @ Vd # mu1 (s2 s3) +psi_remainder = np.reshape(psi_remainder, (2*2, 1)) # (mu1 s2), s3 +U, Lambda, Vd = np.linalg.svd(psi_remainder, full_matrices=False) + +U = np.reshape(U, (2, 2, 1)) # mu1, s2, mu2 +Us.append(U) + +U.shape, Lambda.shape, Vd.shape + +############################################################################## +# Because our state vector was already normalized, the singular value in this last SVD is just ``1``, else it would yield the norm of ``psi`` +# (a good exercise to confirm by skipping the normalization step in the definition of ``psi`` above). +# +# The collected tensors :math:`U_{\mu_{i-1} \sigma_i \mu_i}` now make up the Matrix Product State and describe the original state :math:`|\psi\rangle` +# by appropriately contracting the virtual indices :math:`\mu_i.` We can briefly confirm this by reverse engineering the original state. +# +# +# .. figure:: ../_static/demonstration_assets/mps/psi_to_mps_3.png +# :align: center +# :width: 70% +# +# +# Due to the convention of +# the indices as ``(virtual_left, physical, virtual_right)``, the contraction is simple and we can use +# `np.tensordot `_ with ``axes=1``, indicating matrix-product-like contraction of the left-most and right-most index. +# This is another way of thinking of the obtained state as a **matrix product** state. + +print(f"Shapes of Us: {[_.shape for _ in Us]}") + +psi_reconstruct = Us[0] + +for i in range(1, len(Us)): + # contract the rightmost with the left most index + psi_reconstruct = np.tensordot(psi_reconstruct, Us[i], axes=1) + +print(f"Shape of reconstructed psi: {psi_reconstruct.shape}") +# remove dummy dimensions +psi_reconstruct = np.reshape(psi_reconstruct, (2, 2, 2)) +# original shape of original psi +psi = np.reshape(psi, (2, 2, 2)) + +np.allclose(psi, psi_reconstruct) + + +############################################################################## +# +# Up to this point, the description of the original state in terms of the MPS, made up by the three matrices :math:`U_{\mu_{i-1} \sigma_i \mu_i},` is exact. +# With this construction, the sizes of the virtual bonds grow exponentially from :math:`2` to :math:`2^{n/2}` until the middle of the chain (to be confirmed here below). +# +# Just like in the example with images before, we can compress the state by only keeping +# the :math:`\chi` largest singular values with their respective singular vectors. +# This hyper-parameter :math:`\chi` is the bond dimension we mentioned earlier. It allows us +# to control the amount of entanglement the state can represent between everything that +# is left and right of the bond (more on that later). +# +# A full subroutine from :math:`|\psi\rangle` to its compressed MPS description is given by the following function ``dense_to_mps`.` +# It is convenient to also keep the singular values for each bond to easily change the orthonormality of the tensors, but more on that in the next section on canonical forms. + +def split(M, bond_dim): + """Split a matrix M via SVD and keep only the ``bond_dim`` largest entries.""" + U, S, Vd = np.linalg.svd(M, full_matrices=False) + bonds = len(S) + Vd = Vd.reshape(bonds, 2, -1) + U = U.reshape((-1, 2, bonds)) + + # keep only chi bonds + chi = np.min([bonds, bond_dim]) + U, S, Vd = U[:, :, :chi], S[:chi], Vd[:chi] + return U, S, Vd + +def dense_to_mps(psi, bond_dim): + """Turn a state vector ``psi`` into an MPS with bond dimension ``bond_dim``.""" + Ms = [] + Ss = [] + + psi = np.reshape(psi, (2, -1)) # split psi[2, 2, 2, 2..] = psi[2, (2x2x2...)] + U, S, Vd = split(psi, bond_dim) # psi[2, (2x2x..)] = U[2, mu] S[mu] Vd[mu, (2x2x2x..)] + + Ms.append(U) + Ss.append(S) + bondL = Vd.shape[0] + psi = np.tensordot(np.diag(S), Vd, 1) + + for _ in range(n-2): + psi = np.reshape(psi, (2*bondL, -1)) # reshape psi[2 * bondL, (2x2x2...)] + U, S, Vd = split(psi, bond_dim) # psi[2, (2x2x..)] = U[2, mu] S[mu] Vd[mu, (2x2x2x..)] + Ms.append(U) + Ss.append(S) + + psi = np.tensordot(np.diag(S), Vd, 1) + bondL = Vd.shape[0] + + # dummy step on last site + psi = np.reshape(psi, (-1, 1)) + U, _, _ = np.linalg.svd(psi, full_matrices=False) + + U = np.reshape(U, (-1, 2, 1)) + Ms.append(U) + + return Ms, Ss + +############################################################################## +# Let us look at a larger state. First, let us observe how the dimension scales exponentially when we don't truncate the bonds. + +n = 12 +bond_dim = 10000 + +psi = np.random.rand(*[2]*n) +psi = psi/np.linalg.norm(psi) +Ms, Ss = dense_to_mps(psi, bond_dim) + +[M.shape for M in Ms] + +############################################################################## +# When setting a finite bond dimension :math:`\chi \leq 2^{n/2},` +# we see the virtual bonds grow at the boundaries until reaching +# the maximal bond dimension and staying constant thereafter until +# reaching the other side. + +Ms, Ss = dense_to_mps(psi, 5) + +[M.shape for M in Ms] + +############################################################################## +# +# This was all to conceptually understand the relationship between dense vectors and a compressed matrix product state. +# We want to use MPS for many sites, where it is often not possible to write down the exponentially large state vector in the first place. +# In that case, we would simply start from an MPS description in terms of :math:`n` :math:`\chi \times 2 \times \chi` tensors. +# Luckily, we can obtain all relevant information without ever reconstructing the full state vector. +# +# .. _Canonical Forms: +# +# Canonical forms +# ~~~~~~~~~~~~~~~ +# +# In the above construction, we unknowingly already baked in a very useful feature of our MPS because all the :math:`U` matrices from the SVD +# are left-orthonormal (highlighted by the pink color in the illustrations of the left-orthonormal :math:`U` tensors). In particular, they satisfy +# +# .. math:: \sum_{\sigma_i} \left(U^{\sigma_i} \right)^\dagger U^{\sigma_i} = \mathbb{I}, +# +# which is a compact matrix notation treating :math:`U^{\sigma_i}` as a matrix for a concrete value of :math:`\sigma_i.` +# Making the coefficient in the matrix multiplication explicit we have +# +# .. math:: \sum_{\sigma_i \mu_{i-1}} U^{*}_{\mu_{i-1} \sigma_i \mu'_i} U_{\mu_{i-1} \sigma_i \mu_i} = \mathbb{I}_{\mu'_i \mu_i}. +# +# Note that we only use the complex conjugation :math:`U^{*}` instead of Hermitian conjugate :math:`U^\dagger` +# because we can just choose the indices to contract over accordingly. +# +# Let us briefly confirm that: + +for i in range(len(Ms)): + id_ = np.tensordot(Ms[i].conj(), Ms[i], axes=([0, 1], [0, 1])) + is_id = np.allclose(id_, np.eye(len(id_))) + print(f"U[{i}] is left-orthonormal: {is_id}") + +############################################################################## +# This is a very powerful identity as it tells us that contracting a site of the MPS from the left is just the identity. +# +# .. figure:: ../_static/demonstration_assets/mps/left_orthonormal.png +# :align: center +# :width: 30% +# +# This means that computing the norm, which is just contracting the MPS with itself, becomes trivial. +# +# .. figure:: ../_static/demonstration_assets/mps/norm_trivial.png +# :align: center +# :width: 70% +# +# The fact that we went through the MPS from left to right was a choice. +# We could have equivalently gone through the MPS from right to left and obtained a right-canonical state by keeping the right-orthonormal :math:`V^\dagger` of the decompositions. +# +# When computing expectation values, it is convenient to have the MPS in a mixed canonical form. +# Take some single-site observable :math:`O_i` for which we want to compute the expectation value. +# The best way to do this is to have the MPS such that all sites left of site :math:`i` are left-canonical and all sites right of it are right-canonical. +# That way, the contraction :math:`\langle \psi | O | \psi \rangle` for local expectation values reduces to contractions on just a single site, +# because all other contractions are just the identity. +# We call that single tensor :math:`\Theta,` it is all we need to compute the expectation value and we shall see how we can easily obtain it while not having to care about all other sites. +# +# .. figure:: ../_static/demonstration_assets/mps/mixed_canonical_observable.png +# :align: center +# :width: 70% +# +# We can obtain such a mixed canonical form by starting from our left-canonical MPS and going through the sites from right to left, thereby right-canonizing all sites until the observable. +# However, if we keep track of the singular values at all times, we can switch any site tensor from left- to right-orthonormal by just multiplying with the singular values. +# This is the so-called Vidal form introduced in [#Vidal]_ and it works like this: Even though we are not going to use them in our representation, it makes sense to introduce the "bare" local :math:`\Gamma`-tensors :math:`\{\Gamma^{\sigma_i}\}` in terms of +# +# .. math:: \Gamma^{\sigma_i} = \left(\Lambda^{[i-1]}\right)^{-1} U^{\sigma_i} = \left(V^\dagger\right)^{\sigma_i} \left(\Lambda^{[i]}\right)^{-1} +# +# and write the MPS in terms of those bare :math:`\Gamma`-tensors with the singular values connecting them. We can then just sub-select and recombine parts to get either right- or left-canonical tensors in the following manner. +# +# .. figure:: ../_static/demonstration_assets/mps/vidal.png +# :align: center +# :width: 70% +# +# In particular, to compute the expectation value described just above, we need the central tensor that we named :math:`\Theta.` +# We are not going to actually store the :math:`\Gamma`-tensors but continue to use the left-orthonormal :math:`U`-tensors. +# So all we need to do is construct :math:`\Theta_{\mu_{i-1} \sigma_i \mu_i} = \sum_{\tilde{\mu}_i} U_{\mu_{i-1} \sigma_i \tilde{\mu}_i} \Lambda^{[i]}_{\tilde{\mu}_i \mu_i}.` This has two advantages: 1) +# we only need to perform one contraction with the singular values from the right and 2) we avoid having to compute any +# inverses of the singular values, which numerically can become messy for very small singular values. +# +# Finally, the local observable expectation value simply becomes +# +# .. math:: \langle \psi | O | \psi \rangle = \text{tr}\left[ \sum_{\sigma_i \tilde{\sigma}_i} \Theta^{\sigma_i} O^{\sigma_i \tilde{\sigma}_i} \Theta^{*\tilde{\sigma}_i} \right], +# +# or, graphically, the following. +# +# .. figure:: ../_static/demonstration_assets/mps/final_expval.png +# :align: center +# :width: 50% +# +# The canonical form essentially allows us to treat local operations locally and remove all redundancy on other sites. This will come in handy later when we look at simulating quantum circuits with MPS. +# It also enables the very powerful density matrix renormalization group (DMRG) algorithm. Here, one constructs the ground state of a Hamiltonian by iteratively sweeping through the MPS back and forth, solving +# the eigenvalue problem locally at each site (with all other sites "frozen"). This works extremely well in practice and is hence still one of the workhorses of classical quantum simulation to this day. For a very good review +# on DMRG with MPS, see [#Schollwoeck]_. +# +# .. _Area Law: +# +# Entanglement and area laws +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Entanglement is best quantified via bipartitions of states. In particular, separating a full system :math:`|\psi\rangle` and a sub-system :math:`\rho_\text{sub},` the von Neumann entanglement entropy is given by +# +# .. math:: S(\rho_\text{sub}) = -\text{tr}\left[\rho_\text{sub} \log\left(\rho_\text{sub}\right) \right]. +# +# In an MPS, the singular values of the bonds naturally encode the entanglement of bipartitions between all sites left vs all sites right of the bond. +# In particular, the von Neumann entanglement entropy at bond :math:`i` is given by +# +# .. math:: S(\rho_{1:i}) = S(\rho_{i+1:n}) = - \sum_{\mu_i=1}^{\chi} \Lambda^2_{\mu_i} \log\left( \Lambda_{\mu_i}^2 \right). +# +# Given a bond dimension :math:`\chi,` the maximal entanglement entropy we can obtain is for the all-equal distribution of singular values, :math:`\Lambda_i^2 \equiv 1/\chi.` +# The entanglement entropy is thus bounded by +# +# .. math:: S(\rho_{1:i}) \leq \log(\chi) = \text{const}. +# +# This is the area law of entanglement for one-dimensional systems. A "volume" in one spatial dimension is a line, and its surface area, :math:`\partial V,` two points, so constant in the system size (see note below). +# +# .. note:: +# +# Ground states of local and gapped Hamiltonians are known to satisfy the area law of entanglement. +# This law states that the entanglement entropy of a sub-system grows with its surface area :math:`\partial V` instead of its volume :math:`V.` +# For one-dimensional systems, the volume is just a line and its surface area just a constant. The entanglement +# between any such sub-system in an MPS with a finite bond dimension :math:`\chi` is naturally bounded by :math:`\log(\chi)=\text{const.},` +# so MPS satisfy the area law of entanglement for one-dimensional systems. +# +# Projected entangled pair states (PEPS) are the natural generalization of MPS to regular 2D or 3D grids as well as more general graph connectivities and are known to fulfill the +# respective area laws of entanglement, making them the correct ansätze for local and gapped Hamiltonians in those cases. For example, for a 2D +# PEPS with a square subsystem of volume :math:`L \times L` and bond dimension :math:`\chi,` the entanglement entropy between the square and the rest of the system is bounded by +# :math:`\log(\chi^L) = L \log(\chi)`, which is proportional to the circumference :math:`\propto L,` and not its area. +# +# .. figure:: ../_static/demonstration_assets/mps/area_law.png +# :align: center +# :width: 50% + + + +############################################################################## +# +# +# Quantum simulation with MPS +# --------------------------- +# +# We can use MPS to classically simulate quantum algorithms. This is a very useful tool for as long as +# real quantum devices are noisy and costly to use. +# An MPS simulator works very similarly to a state vector simulator like :class:`~DefaultQubit`, the only difference is that the underlying state +# is encoded in an MPS. +# +# Applying local gates +# ~~~~~~~~~~~~~~~~~~~~ +# +# Due to its canonical form, applying local gates onto an MPS is very straightforward and little work. +# Let us walk through the example of applying a :math:`\text{CNOT}` gate on neighboring sites on the underlying MPS state of the simulator. +# +# Graphically, this is happening in three steps as illustrated here. +# +# .. figure:: ../_static/demonstration_assets/mps/apply_gate.png +# :align: center +# :width: 65% +# +# In the first step, we simply contract the appropriate physical indices of the MPS with those of the :math:`\text{CNOT}` matrix (which we reshape to :math:`2\times 2\times 2\times 2`). +# Note that we also take the bond singular values into account. The result is a big blob with two virtual indices and two physical indices. +# We then just split this blob in the same way we split up the dense state vector and keep track of its singular values to restore the canonical form. +# +# Applying non-local gates +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The situation gets a bit more complicated when we apply a :math:`\text{CNOT}` (or any multi-site) gate on non-neighboring sites. We have two possibilities to handle this. +# We can do what a quantum computer would do, which is swap out sites until the appropriate indices are neighboring, perform the operation, and then un-swap the sites. +# Alternatively, we can construct a so-called matrix product operator (MPO) that acts on all sites in between with an identity. This is done in the following way. +# +# First, we split the matrix of the gate into tensors that act locally. This is done again using SVD. In general, the MPO bond dimension for a two-qubit gate is maximally 4, +# but in some cases like :math:`\text{CNOT}` it is :math:`2,` so we can do a lossless compression by only keeping the two non-zero singular values and tossing the zeros. After we have done that, we can multiply +# the singular values onto either site as we do not have a use for them in the MPO other than for doing the compression. +# +# .. figure:: ../_static/demonstration_assets/mps/cnot_split.png +# :align: center +# :width: 50% +# +# Now if we want to apply the CNOT gate on non-neighboring sites, we fill the intermediate sites with identities :math:`\mathbb{I} = \delta_{\sigma_i \sigma'_i} \delta_{\mu_{i-1} \mu_i},` and contract that larger +# MPO with the MPS. +# +# .. figure:: ../_static/demonstration_assets/mps/non_local_cnot.png +# :align: center +# :width: 80% +# +# Here we just need to be careful to contract in the right order, otherwise we might end up with unnecessarily large tensors in intermediate steps. For example, if we first contract all physical indices we get a big blob +# that is exponentially large in the number of intermediate sites. While it is in general NP-hard to find the optimal contraction path, +# for MPS the optimal path is known. The way to do it is by alternating between the physical index and the corresponding two virtual indices, going either from left to right or, equivalently, from right to left (see Figure 21 in [#Schollwoeck]_). +# +# +# Running simulations and setting the bond dimension +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# While we focussed on the specific case of a :math:`\text{CNOT}` gate, this concept is readily generalized to arbitrary two- or multi-qubit gates. +# With that, we are now ready to run some quantum circuit simulations. We don't have to code up all contractions by hand. Instead, we can use +# PennyLane's :class:`~pennylane.devices.default_tensor.DefaultTensor` device that takes care of all of this under the hood. All we need to do is set the bond dimension and tell the device whether +# it should use the swap-unswap or MPO method for applying non-local gates. This is done via the keyword argument ``contract``, where we can choose between ``"swap+split"`` (what I called swap-unswap), +# ``"nonlocal"`` (what I called the MPO method), and ``"auto-mps"``, which uses swap-unswap for 2-qubit gates and the MPO method for 3 and more qubits. +# +# Aside from that, we can basically use the device like any other state vector simulator device. +# Let us run a VQE example from using the `PennyLane Datasets `__ data for the :math:`H_6` molecule. What you will see here is mostly boilerplate code in PennyLane for using ``default.tensor`,` and you can +# see our :doc:`demo on how to use default.tensor ` for more details. + +import pennylane as qml + +[dataset] = qml.data.load("qchem", molname="H6", bondlength=1.3, basis="STO-3G") + +H = dataset.hamiltonian # molecular Hamiltonian in qubit basis +n_wires = len(H.wires) # number of qubits + +def circuit(): + qml.BasisState(dataset.hf_state, wires=H.wires) # Hartree–Fock initial state + for op in dataset.vqe_gates: # Applying all pre-optimized VQE gates + qml.apply(op) + return qml.expval(H) # expectation value of molecular Hamiltonian + +# set up device with hyper-parameters and kwargs +mps = qml.device("default.tensor", wires=n_wires, method="mps", max_bond_dim=30, contract="auto-mps") + +# Create the QNode to execute the circuit on the device, and call it (w/o arguments) +res = qml.QNode(circuit, mps)() + +# Compare MPS simulation result with pre-optimized state-vector result +res, dataset.vqe_energy + +############################################################################## +# We've set the bond dimension to ``30`` kind of arbitrarily, and saw that we are pretty close to the exact result +# (note that exact here refers to the simulation method, not the ground state energy of the VQE result itself). +# But when dealing with systems of sizes where we don't have the means to compare to an exact result, how do know +# that our simulation results make sense, and are not scrambled by the errors introduced by a small bond dimension? +# +# The answer is **finite-size scaling** (sometimes also called bond dimension scaling or just extrapolation). This is a standard method +# in tensor network simulations and originates from condensed matter physics and quantum phase transitions. +# The idea is to run the same simulation with an increasing bond dimension and check that it saturates and converges to an extrapolated value. +# In spirit, this is similar to :doc:`zero noise extrapolation `. +# +# We choose a range of bond dimensions and plot the results for the simulation against them, keeping in mind that +# the maximum bond dimension of a system of :math:`n` qubits is :math:`2^{\frac{n}{2}}.` + +bond_dims = 2**np.arange(2, (n_wires//2)+1) # maximum required bond dimension is 2**(n_wires//2) = 64 +ress = [] + +for bond_dim in bond_dims: + mps = qml.device("default.tensor", wires=n_wires, method="mps", max_bond_dim=bond_dim, contract="auto-mps") + res = qml.QNode(circuit, mps)() + ress.append(res) + + +plt.plot(bond_dims, ress, "x:", label="mps sim") +plt.hlines(dataset.vqe_energy, bond_dims[0], bond_dims[-1], label="exact result") +plt.xscale("log") +plt.xlabel("bond dim $\\chi$") +plt.xticks(bond_dims, bond_dims) +plt.legend() +plt.show() + + +############################################################################## +# We see that already for :math:`\chi = 32` we have pretty accurate results. +# We might even get away with :math:`\chi = 16` for some qualitative simulations at the cost of some approximation error. +# +# Setting the bond dimension is an important part of performing MPS simulations. Finite-size scaling is a +# quantitative tool to orient ourselves and choose a suitable bond dimension for our simulations. +# We can extrapolate the exact result for every simulation run, +# but this is of course more expensive as it requires multiple executions at different bond dimensions, and is not guaranteed to converge. +# Sometimes we are also just happy to get a cheap qualitative result for large systems that may or may not be 100% accurate. +# +# .. note:: +# ``default.tensor`` is based on `quimb `_. Both +# ``quimb`` and ``default.tensor`` are under active development, so in case you encounter some rough edges, please +# submit a `GitHub issue `_ in the PennyLane repository. + + +############################################################################## +# +# Conclusion +# ---------- +# +# In this demo we introduced the basics of matrix product states (MPS) and saw how the existence of a canonical form simplifies a lot of the contractions. +# This fact can also be used for simulations of quantum circuits with local and non-local gates. +# We showed how to run quantum circuits using PennyLane's :class:`~pennylane.devices.default_tensor.DefaultTensor` device and how to systematically find an appropriate bond dimension. +# +# While MPS are mathematically known to be well-suited to describe a particular class of states (those that fulfill the area law of entanglement in 1D), we can +# also simulate more complex systems by throwing some extra resources into the bond dimension. + + + +############################################################################## +# +# References +# ---------- +# +# .. [#Baiardi] +# +# Alberto Baiardi, Markus Reiher +# "The Density Matrix Renormalization Group in Chemistry and Molecular Physics: Recent Developments and New Challenges" +# `arXiv:1910.00137 `__, 2019. +# +# .. [#Patra] +# +# Siddhartha Patra, Saeed S. Jahromi, Sukhbinder Singh, Roman Orus +# "Efficient tensor network simulation of IBM's largest quantum processors" +# `arXiv:2309.15642 `__, 2023. +# +# .. [#Jiang] +# +# H. C. Jiang, Z. Y. Weng, T. Xiang +# "Accurate determination of tensor network state of quantum lattice models in two dimensions" +# `arXiv:0806.3719 `__, 2008. +# +# .. [#Vidal] +# +# Guifre Vidal +# "Efficient classical simulation of slightly entangled quantum computations" +# `arXiv:quant-ph/0301063 `__, 2003. +# +# .. [#Schollwoeck] +# +# Ulrich Schollwoeck +# "The density-matrix renormalization group in the age of Matrix Product States" +# `arXiv:1008.3477 `__, 2010. +# + + +############################################################################## diff --git a/demonstrations_v2/tutorial_mps/metadata.json b/demonstrations_v2/tutorial_mps/metadata.json new file mode 100644 index 0000000000..0e251e4372 --- /dev/null +++ b/demonstrations_v2/tutorial_mps/metadata.json @@ -0,0 +1,101 @@ +{ + "title": "Introducing matrix product states for quantum practitioners", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-09-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-20T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mps_simulation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mps_simulation.png" + } + ], + "seoDescription": "What is a matrix product state (MPS)? Here is all you need to know about MPS from a quantum computation and quantum simulation perspective.", + "doi": "", + "references": [ + { + "id": "Baiardi", + "type": "preprint", + "title": "The Density Matrix Renormalization Group in Chemistry and Molecular Physics: Recent Developments and New Challenges", + "authors": "Alberto Baiardi, Markus Reiher", + "year": "2019", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1910.00137", + "url": "https://arxiv.org/abs/1910.00137" + }, + { + "id": "Patra", + "type": "preprint", + "title": "Efficient tensor network simulation of IBM's largest quantum processors", + "authors": "Siddhartha Patra, Saeed S. Jahromi, Sukhbinder Singh, Roman Orus", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.15642", + "url": "https://arxiv.org/abs/2309.15642" + }, + { + "id": "Jiang", + "type": "preprint", + "title": "Accurate determination of tensor network state of quantum lattice models in two dimensions", + "authors": "H. C. Jiang, Z. Y. Weng, T. Xiang", + "year": "2008", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.0806.3719", + "url": "https://arxiv.org/abs/0806.3719" + }, + { + "id": "Vidal", + "type": "preprint", + "title": "Efficient classical simulation of slightly entangled quantum computations", + "authors": "Guifre Vidal", + "year": "2003", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0301063", + "url": "https://arxiv.org/abs/quant-ph/0301063" + }, + { + "id": "Schollwoeck", + "type": "preprint", + "title": "The density-matrix renormalization group in the age of Matrix Product States", + "authors": "Ulrich Schollwoeck", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1008.3477", + "url": "https://arxiv.org/abs/1008.3477" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1008.3477" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_simulate_quantum_circuits_with_tensor_networks", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_mps/requirements.in b/demonstrations_v2/tutorial_mps/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_mps/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_multiclass_classification/demo.py b/demonstrations_v2/tutorial_multiclass_classification/demo.py new file mode 100644 index 0000000000..1acf359534 --- /dev/null +++ b/demonstrations_v2/tutorial_multiclass_classification/demo.py @@ -0,0 +1,335 @@ +r""" +.. _multiclass_margin_classifier: + +Multiclass margin classifier +============================ + +.. meta:: + :property="og:description": Using PyTorch to implement a multiclass + quantum variational classifier on MNIST data. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/margin_2.png + +.. related:: + + tutorial_variational_classifier Variational classifier + tutorial_data_reuploading_classifier Data-reuploading classifier + +*Author: Safwan Hossein — Posted: 09 April 2020. Last updated: 28 January 2021.* + +In this tutorial, we show how to use the PyTorch interface for PennyLane +to implement a multiclass variational classifier. We consider the iris database +from UCI, which has 4 features and 3 classes. We use multiple one-vs-all +classifiers with a margin loss (see `Multiclass Linear SVM +`__) to classify data. Each classifier is implemented +on an individual variational circuit, whose architecture is inspired by +`Farhi and Neven (2018) `__ as well as +`Schuld et al. (2018) `__. + +| + +.. figure:: ../_static/demonstration_assets/multiclass_classification/margin_2.png + :align: center + :width: 50% + :target: javascript:void(0) + +| + + +Initial Setup +~~~~~~~~~~~~~ + +We import PennyLane, the PennyLane-provided version of NumPy, +relevant torch modules, and define the constants that will +be used in this tutorial. + +Our feature size is 4, and we will use amplitude embedding. +This means that each possible amplitude (in the computational basis) will +correspond to a single feature. With 2 qubits (wires), there are +4 possible states, and as such, we can encode a feature vector +of size 4. +""" + +import pennylane as qml +import torch +import numpy as np +from torch.autograd import Variable +import torch.optim as optim + +np.random.seed(0) +torch.manual_seed(0) + +num_classes = 3 +margin = 0.15 +feature_size = 4 +batch_size = 10 +lr_adam = 0.01 +train_split = 0.75 +# the number of the required qubits is calculated from the number of features +num_qubits = int(np.ceil(np.log2(feature_size))) +num_layers = 6 +total_iterations = 100 + +dev = qml.device("default.qubit", wires=num_qubits) + + +################################################################################# +# Quantum Circuit +# ~~~~~~~~~~~~~~~ +# +# We first create the layer that will be repeated in our variational quantum +# circuits. It consists of rotation gates for each qubit, followed by +# entangling/CNOT gates + + +def layer(W): + for i in range(num_qubits): + qml.Rot(W[i, 0], W[i, 1], W[i, 2], wires=i) + for j in range(num_qubits - 1): + qml.CNOT(wires=[j, j + 1]) + if num_qubits >= 2: + # Apply additional CNOT to entangle the last with the first qubit + qml.CNOT(wires=[num_qubits - 1, 0]) + + +################################################################################# +# We now define the quantum nodes that will be used. As we are implementing our +# multiclass classifier as multiple one-vs-all classifiers, we will use 3 QNodes, +# each representing one such classifier. That is, ``circuit1`` classifies if a +# sample belongs to class 1 or not, and so on. The circuit architecture for all +# nodes are the same. We use the PyTorch interface for the QNodes. +# Data is embedded in each circuit using amplitude embedding. +# +# .. note:: +# For demonstration purposes we are using a very simple circuit here. +# You may find that other choices, for example more +# elaborate measurements, increase the power of the classifier. + + +def circuit(weights, feat=None): + qml.AmplitudeEmbedding(feat, range(num_qubits), pad_with=0.0, normalize=True) + + for W in weights: + layer(W) + + return qml.expval(qml.PauliZ(0)) + + +qnodes = [] +for iq in range(num_classes): + qnode = qml.QNode(circuit, dev, interface="torch") + qnodes.append(qnode) + + +################################################################################# +# The variational quantum circuit is parametrized by the weights. We use a +# classical bias term that is applied after processing the quantum circuit's +# output. Both variational circuit weights and classical bias term are optimized. + + +def variational_classifier(q_circuit, params, feat): + weights = params[0] + bias = params[1] + return q_circuit(weights, feat=feat) + bias + + +############################################################################## +# Loss Function +# ~~~~~~~~~~~~~ +# +# Implementing multiclass classifiers as a number of one-vs-all classifiers +# generally evokes using the margin loss. The output of the :math:`i` th classifier, :math:`c_i` +# on input :math:`x` is interpreted as a score, :math:`s_i` between [-1,1]. +# More concretely, we have: +# +# .. math:: s_i = c_i(x; \theta) +# +# The multiclass margin loss attempts to ensure that the score for the correct +# class is higher than that of incorrect classes by some margin. For a sample :math:`(x,y)` +# where :math:`y` denotes the class label, we can analytically express the mutliclass +# loss on this sample as: +# +# .. math:: L(x,y) = \sum_{j \ne y}{\max{\left(0, s_j - s_y + \Delta)\right)}} +# +# where :math:`\Delta` denotes the margin. The margin parameter is chosen as a hyperparameter. +# For more information, see `Multiclass Linear SVM `__. + + +def multiclass_svm_loss(q_circuits, all_params, feature_vecs, true_labels): + loss = 0 + num_samples = len(true_labels) + for i, feature_vec in enumerate(feature_vecs): + # Compute the score given to this sample by the classifier corresponding to the + # true label. So for a true label of 1, get the score computed by classifer 1, + # which distinguishes between "class 1" or "not class 1". + s_true = variational_classifier( + q_circuits[int(true_labels[i])], + (all_params[0][int(true_labels[i])], all_params[1][int(true_labels[i])]), + feature_vec, + ) + s_true = s_true.float() + li = 0 + + # Get the scores computed for this sample by the other classifiers + for j in range(num_classes): + if j != int(true_labels[i]): + s_j = variational_classifier( + q_circuits[j], (all_params[0][j], all_params[1][j]), feature_vec + ) + s_j = s_j.float() + li += torch.max(torch.zeros(1).float(), s_j - s_true + margin) + loss += li + + return loss / num_samples + + +########################################################################################## +# Classification Function +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +# Next, we use the learned models to classify our samples. For a given sample, +# compute the score given to it by classifier :math:`i,` which quantifies how likely it is that +# this sample belongs to class :math:`i.` For each sample, return the class with the highest score. + + +def classify(q_circuits, all_params, feature_vecs, labels): + predicted_labels = [] + for i, feature_vec in enumerate(feature_vecs): + scores = np.zeros(num_classes) + for c in range(num_classes): + score = variational_classifier( + q_circuits[c], (all_params[0][c], all_params[1][c]), feature_vec + ) + scores[c] = float(score) + pred_class = np.argmax(scores) + predicted_labels.append(pred_class) + return predicted_labels + + +def accuracy(labels, hard_predictions): + loss = 0 + for l, p in zip(labels, hard_predictions): + if torch.abs(l - p) < 1e-5: + loss = loss + 1 + loss = loss / labels.shape[0] + return loss + + +################################################################################# +# Data Loading and Processing +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now we load in the iris dataset and normalize the features so that the sum of the feature +# elements squared is 1 (:math:`\ell_2` norm is 1). + + +def load_and_process_data(): + data = np.loadtxt("../_static/demonstration_assets/multiclass_classification/iris.csv", delimiter=",") + X = torch.tensor(data[:, 0:feature_size]) + print("First X sample, original :", X[0]) + + # normalize each input + normalization = torch.sqrt(torch.sum(X ** 2, dim=1)) + X_norm = X / normalization.reshape(len(X), 1) + print("First X sample, normalized:", X_norm[0]) + + Y = torch.tensor(data[:, -1]) + return X, Y + + +# Create a train and test split. +def split_data(feature_vecs, Y): + num_data = len(Y) + num_train = int(train_split * num_data) + index = np.random.permutation(range(num_data)) + feat_vecs_train = feature_vecs[index[:num_train]] + Y_train = Y[index[:num_train]] + feat_vecs_test = feature_vecs[index[num_train:]] + Y_test = Y[index[num_train:]] + return feat_vecs_train, feat_vecs_test, Y_train, Y_test + + +################################################################################# +# Training Procedure +# ~~~~~~~~~~~~~~~~~~ +# +# In the training procedure, we begin by first initializing randomly the parameters +# we wish to learn (variational circuit weights and classical bias). As these are +# the variables we wish to optimize, we set the ``requires_grad`` flag to ``True``. We use +# minibatch training—the average loss for a batch of samples is computed, and the +# optimization step is based on this. Total training time with the default parameters +# is roughly 15 minutes. + + +def training(features, Y): + num_data = Y.shape[0] + feat_vecs_train, feat_vecs_test, Y_train, Y_test = split_data(features, Y) + num_train = Y_train.shape[0] + q_circuits = qnodes + + # Initialize the parameters + all_weights = [ + Variable(0.1 * torch.randn(num_layers, num_qubits, 3), requires_grad=True) + for i in range(num_classes) + ] + all_bias = [Variable(0.1 * torch.ones(1), requires_grad=True) for i in range(num_classes)] + optimizer = optim.Adam(all_weights + all_bias, lr=lr_adam) + params = (all_weights, all_bias) + print("Num params: ", 3 * num_layers * num_qubits * 3 + 3) + + costs, train_acc, test_acc = [], [], [] + + # train the variational classifier + for it in range(total_iterations): + batch_index = np.random.randint(0, num_train, (batch_size,)) + feat_vecs_train_batch = feat_vecs_train[batch_index] + Y_train_batch = Y_train[batch_index] + + optimizer.zero_grad() + curr_cost = multiclass_svm_loss(q_circuits, params, feat_vecs_train_batch, Y_train_batch) + curr_cost.backward() + optimizer.step() + + # Compute predictions on train and validation set + predictions_train = classify(q_circuits, params, feat_vecs_train, Y_train) + predictions_test = classify(q_circuits, params, feat_vecs_test, Y_test) + acc_train = accuracy(Y_train, predictions_train) + acc_test = accuracy(Y_test, predictions_test) + + print( + "Iter: {:5d} | Cost: {:0.7f} | Acc train: {:0.7f} | Acc test: {:0.7f} " + "".format(it + 1, curr_cost.item(), acc_train, acc_test) + ) + + costs.append(curr_cost.item()) + train_acc.append(acc_train) + test_acc.append(acc_test) + + return costs, train_acc, test_acc + + +# We now run our training algorithm and plot the results. Note that +# for plotting, the matplotlib library is required + +features, Y = load_and_process_data() +costs, train_acc, test_acc = training(features, Y) + +import matplotlib.pyplot as plt + +fig, ax1 = plt.subplots() +iters = np.arange(0, total_iterations, 1) +colors = ["tab:red", "tab:blue"] +ax1.set_xlabel("Iteration", fontsize=17) +ax1.set_ylabel("Cost", fontsize=17, color=colors[0]) +ax1.plot(iters, costs, color=colors[0], linewidth=4) +ax1.tick_params(axis="y", labelsize=14, labelcolor=colors[0]) + +ax2 = ax1.twinx() +ax2.set_ylabel("Test Acc.", fontsize=17, color=colors[1]) +ax2.plot(iters, test_acc, color=colors[1], linewidth=4) + +ax2.tick_params(axis="x", labelsize=14) +ax2.tick_params(axis="y", labelsize=14, labelcolor=colors[1]) + +plt.grid(False) +plt.tight_layout() +plt.show() diff --git a/demonstrations_v2/tutorial_multiclass_classification/metadata.json b/demonstrations_v2/tutorial_multiclass_classification/metadata.json new file mode 100644 index 0000000000..7a39583b3a --- /dev/null +++ b/demonstrations_v2/tutorial_multiclass_classification/metadata.json @@ -0,0 +1,37 @@ +{ + "title": "Multiclass margin classifier", + "authors": [ + { + "username": "shossein" + } + ], + "dateOfPublication": "2020-04-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_multiclass_margin_classifier.png" + } + ], + "seoDescription": "Using PyTorch to implement a multiclass quantum variational classifier on MNIST data.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_multiclass_classification/multiclass_classification/iris.csv b/demonstrations_v2/tutorial_multiclass_classification/multiclass_classification/iris.csv new file mode 100644 index 0000000000..8b4511f8be --- /dev/null +++ b/demonstrations_v2/tutorial_multiclass_classification/multiclass_classification/iris.csv @@ -0,0 +1,150 @@ +5.1,3.5,1.4,0.2,0 +4.9,3.0,1.4,0.2,0 +4.7,3.2,1.3,0.2,0 +4.6,3.1,1.5,0.2,0 +5.0,3.6,1.4,0.2,0 +5.4,3.9,1.7,0.4,0 +4.6,3.4,1.4,0.3,0 +5.0,3.4,1.5,0.2,0 +4.4,2.9,1.4,0.2,0 +4.9,3.1,1.5,0.1,0 +5.4,3.7,1.5,0.2,0 +4.8,3.4,1.6,0.2,0 +4.8,3.0,1.4,0.1,0 +4.3,3.0,1.1,0.1,0 +5.8,4.0,1.2,0.2,0 +5.7,4.4,1.5,0.4,0 +5.4,3.9,1.3,0.4,0 +5.1,3.5,1.4,0.3,0 +5.7,3.8,1.7,0.3,0 +5.1,3.8,1.5,0.3,0 +5.4,3.4,1.7,0.2,0 +5.1,3.7,1.5,0.4,0 +4.6,3.6,1.0,0.2,0 +5.1,3.3,1.7,0.5,0 +4.8,3.4,1.9,0.2,0 +5.0,3.0,1.6,0.2,0 +5.0,3.4,1.6,0.4,0 +5.2,3.5,1.5,0.2,0 +5.2,3.4,1.4,0.2,0 +4.7,3.2,1.6,0.2,0 +4.8,3.1,1.6,0.2,0 +5.4,3.4,1.5,0.4,0 +5.2,4.1,1.5,0.1,0 +5.5,4.2,1.4,0.2,0 +4.9,3.1,1.5,0.1,0 +5.0,3.2,1.2,0.2,0 +5.5,3.5,1.3,0.2,0 +4.9,3.1,1.5,0.1,0 +4.4,3.0,1.3,0.2,0 +5.1,3.4,1.5,0.2,0 +5.0,3.5,1.3,0.3,0 +4.5,2.3,1.3,0.3,0 +4.4,3.2,1.3,0.2,0 +5.0,3.5,1.6,0.6,0 +5.1,3.8,1.9,0.4,0 +4.8,3.0,1.4,0.3,0 +5.1,3.8,1.6,0.2,0 +4.6,3.2,1.4,0.2,0 +5.3,3.7,1.5,0.2,0 +5.0,3.3,1.4,0.2,0 +7.0,3.2,4.7,1.4,1 +6.4,3.2,4.5,1.5,1 +6.9,3.1,4.9,1.5,1 +5.5,2.3,4.0,1.3,1 +6.5,2.8,4.6,1.5,1 +5.7,2.8,4.5,1.3,1 +6.3,3.3,4.7,1.6,1 +4.9,2.4,3.3,1.0,1 +6.6,2.9,4.6,1.3,1 +5.2,2.7,3.9,1.4,1 +5.0,2.0,3.5,1.0,1 +5.9,3.0,4.2,1.5,1 +6.0,2.2,4.0,1.0,1 +6.1,2.9,4.7,1.4,1 +5.6,2.9,3.6,1.3,1 +6.7,3.1,4.4,1.4,1 +5.6,3.0,4.5,1.5,1 +5.8,2.7,4.1,1.0,1 +6.2,2.2,4.5,1.5,1 +5.6,2.5,3.9,1.1,1 +5.9,3.2,4.8,1.8,1 +6.1,2.8,4.0,1.3,1 +6.3,2.5,4.9,1.5,1 +6.1,2.8,4.7,1.2,1 +6.4,2.9,4.3,1.3,1 +6.6,3.0,4.4,1.4,1 +6.8,2.8,4.8,1.4,1 +6.7,3.0,5.0,1.7,1 +6.0,2.9,4.5,1.5,1 +5.7,2.6,3.5,1.0,1 +5.5,2.4,3.8,1.1,1 +5.5,2.4,3.7,1.0,1 +5.8,2.7,3.9,1.2,1 +6.0,2.7,5.1,1.6,1 +5.4,3.0,4.5,1.5,1 +6.0,3.4,4.5,1.6,1 +6.7,3.1,4.7,1.5,1 +6.3,2.3,4.4,1.3,1 +5.6,3.0,4.1,1.3,1 +5.5,2.5,4.0,1.3,1 +5.5,2.6,4.4,1.2,1 +6.1,3.0,4.6,1.4,1 +5.8,2.6,4.0,1.2,1 +5.0,2.3,3.3,1.0,1 +5.6,2.7,4.2,1.3,1 +5.7,3.0,4.2,1.2,1 +5.7,2.9,4.2,1.3,1 +6.2,2.9,4.3,1.3,1 +5.1,2.5,3.0,1.1,1 +5.7,2.8,4.1,1.3,1 +6.3,3.3,6.0,2.5,2 +5.8,2.7,5.1,1.9,2 +7.1,3.0,5.9,2.1,2 +6.3,2.9,5.6,1.8,2 +6.5,3.0,5.8,2.2,2 +7.6,3.0,6.6,2.1,2 +4.9,2.5,4.5,1.7,2 +7.3,2.9,6.3,1.8,2 +6.7,2.5,5.8,1.8,2 +7.2,3.6,6.1,2.5,2 +6.5,3.2,5.1,2.0,2 +6.4,2.7,5.3,1.9,2 +6.8,3.0,5.5,2.1,2 +5.7,2.5,5.0,2.0,2 +5.8,2.8,5.1,2.4,2 +6.4,3.2,5.3,2.3,2 +6.5,3.0,5.5,1.8,2 +7.7,3.8,6.7,2.2,2 +7.7,2.6,6.9,2.3,2 +6.0,2.2,5.0,1.5,2 +6.9,3.2,5.7,2.3,2 +5.6,2.8,4.9,2.0,2 +7.7,2.8,6.7,2.0,2 +6.3,2.7,4.9,1.8,2 +6.7,3.3,5.7,2.1,2 +7.2,3.2,6.0,1.8,2 +6.2,2.8,4.8,1.8,2 +6.1,3.0,4.9,1.8,2 +6.4,2.8,5.6,2.1,2 +7.2,3.0,5.8,1.6,2 +7.4,2.8,6.1,1.9,2 +7.9,3.8,6.4,2.0,2 +6.4,2.8,5.6,2.2,2 +6.3,2.8,5.1,1.5,2 +6.1,2.6,5.6,1.4,2 +7.7,3.0,6.1,2.3,2 +6.3,3.4,5.6,2.4,2 +6.4,3.1,5.5,1.8,2 +6.0,3.0,4.8,1.8,2 +6.9,3.1,5.4,2.1,2 +6.7,3.1,5.6,2.4,2 +6.9,3.1,5.1,2.3,2 +5.8,2.7,5.1,1.9,2 +6.8,3.2,5.9,2.3,2 +6.7,3.3,5.7,2.5,2 +6.7,3.0,5.2,2.3,2 +6.3,2.5,5.0,1.9,2 +6.5,3.0,5.2,2.0,2 +6.2,3.4,5.4,2.3,2 +5.9,3.0,5.1,1.8,2 diff --git a/demonstrations_v2/tutorial_multiclass_classification/requirements.in b/demonstrations_v2/tutorial_multiclass_classification/requirements.in new file mode 100644 index 0000000000..d8d1b51485 --- /dev/null +++ b/demonstrations_v2/tutorial_multiclass_classification/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +torch diff --git a/demonstrations_v2/tutorial_neutral_atoms/demo.py b/demonstrations_v2/tutorial_neutral_atoms/demo.py new file mode 100644 index 0000000000..99cd6e16f4 --- /dev/null +++ b/demonstrations_v2/tutorial_neutral_atoms/demo.py @@ -0,0 +1,820 @@ +r""".. _neutral: + +Neutral-atom quantum computers +============================= + +.. meta:: + :property="og:description": Learn how neutral atom quantum devices work using code + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_neutral_atoms.png + +.. related:: + tutorial_pasqal Quantum computation with neutral atoms + tutorial_ahs_aquila Pulse programming on Rydberg atom hardware + tutorial pulse_programming101 Differentiable pulse programming with qubit in PennyLane + tutorial_trapped_ions Trapped ion quantum computing + tutorial_sc_qubits Quantum computing with superconducting qubits + tutorial_photonics Photonic quantum computing + +*Author: Alvaro Ballon — Posted: 30 May 2023.* + +In the last few years, a new quantum technology has gained the attention of the quantum computing +community. Thanks to recent developments in optical-tweezer technology, +neutral atoms can be used as robust and versatile qubits. In 2022, a collaboration between QuEra and +various academic institutions produced a neutral-atom device with a whopping 256 qubits 😲 [#Aquila2022]_! It is +no surprise that this family of devices has gained traction in the private sector, with startups such +as Pasqal, QuEra, and Atom Computing suddenly finding themselves in the headlines. + +In this tutorial, we will explore the inner workings of neutral-atom quantum devices. We will also +discuss their strengths and weaknesses in terms of DiVincenzo's criteria, introduced in the blue box below. +By the end of this tutorial, you will have obtained a high-level understanding of neutral atom technologies +and be able to follow the new exciting developments that are bound to come. + +.. container:: alert alert-block alert-info + + **DiVincenzo's criteria**: In the year 2000, David DiVincenzo proposed a + wishlist for the experimental characteristics of a quantum computer [#DiVincenzo2000]_. + DiVincenzo's criteria have since become the main guideline for + physicists and engineers building quantum computers: + + 1. **Well-characterized and scalable qubits**. Many of the quantum systems that + we find in nature are not qubits, since we can't just isolate two specific + quantum states and tarder them. We must find a way to make them behave as such. + Moreover, we need to put many of these systems together. + + 2. **Qubit initialization**. We must be able to prepare the same state repeatedly within + an acceptable margin of error. + + 3. **Long coherence times**. Qubits will lose their quantum properties after + interacting with their environment for a while. We need them to last long + enough so that we can perform quantum operations. + + 4. **Universal set of gates**. We need to perform arbitrary operations on the + qubits. To do this, we require both single-qubit gates and two-qubit gates. + + 5. **Measurement of individual qubits**. To read the result of a quantum algorithm, + we must accurately measure the final state of a pre-chosen set of qubits. + +We will start by explaining how neutral atoms can be manipulated and isolated enough to be used as qubits. +Then, we will use PennyLane's pulse programming capabilities to understand how to apply single and multi-qubit gates. +Afterwards, we will learn how to perform measurements on the atom's states. Finally, we will explore +the work that still needs to be done to scale this technology even further. + +""" + +############################################################################## +# +# Trapping individual atoms +# ------------------------- +# +# In our :doc:`cousin demo ` about trapped-ion technologies, we learn that we can trap individual charged +# atoms by carefully controlled electric fields. But neutral atoms, by definition, have no charge, +# so they can't be affected by electric fields. How can we even hope to manipulate them individually? +# It turns out that the technology to do this has been around for decades [#Tweezers1985]_. +# **Optical tweezers**—highly focused laser beams—can grab small objects and hold them in place, no +# need to charge them! Let's see how they are able to do this. +# +# Laser beams are nothing but electromagnetic waves, that is, oscillating electric and magnetic +# fields. It would seem that a neutral atom could not be affected by them—but it can! To understand how, we need +# to keep in mind two facts. First, in a laser beam, light is more intense at the center of the beam +# and it dims progressively as we go toward the edges. This means that the average +# strength of the electric fields is higher closer to the center of the beam. +# Secondly, as small as neutral atoms are, they're not just +# points. They do carry charges that can move around relative to each other when we expose them to electric fields. +# +# The consequence of these two observations is that, if an atom inside a laser beam tries to escape toward the edge +# of the beam, the negative charges will be pulled toward the center of the beam, while the positive charges are pushed +# away. But, since the electric fields are stronger toward the center, the +# negative charges are pulled harder, so more will accumulate in the center. +# These negative charges will bring the positive charge that's trying to pull away back to the middle. You can +# look at the figure below to gain a bit more intuition. +# +# .. figure:: ../_static/demonstration_assets/neutral_atoms/force_gradient.png +# :align: center +# :width: 25% +# +# .. +# +# Electric and magnetic fields are more effective in atoms that are larger in size and whose electrons can reach high energy +# levels. Atoms with these features are known as **Rydberg atoms** and, as we will see later, their extra sensitivity to +# electric fields is also necessary to implemenent some quantum gates. +# +# In the last decade, optical tweezer technology has evolved to the point where we can move atoms around +# into customizable arrays (check out :doc:`this tutorial ` and have some fun doing this!). +# This means that we have a lot of freedom in how and when our atom-encoded qubits interact with each other. Sounds +# like a dream come true! However, there *are* some big challenges to address—we'll learn about these later. +# To get started, let's understand how neutral atoms can be used as qubits. +# +# Encoding a qubit in an atom +# --------------------------- +# +# To encode a qubit in a neutral atom, we need to have access to two distinct atomic quantum states. The most +# easily accessible quantum states in an atom are the electronic energy states. We would like to **switch +# one electron between two different energy states**, which means that we must make sure not to affect other +# electrons when we manipulate the atom. For this reason, the ideal atoms to work with are those with +# one valence electron, i.e. one "loose" electron that is not too tightly bound to the nucleus. +# +# .. note:: +# +# In some cases, such as the devices built by Atom Computing [#AtomComputing]_, +# qubits are not encoded in atomic energy levels, but in so-called nuclear-spin +# energy levels instead. Such qubits are known as **nuclear spin qubits**. In this demo, we will not focus +# on the physics of these qubits. However, similar principles to those we'll outline in this demo +# for qubit preparation, control, and measurement will apply to this type of qubit. +# +# +# A common choice is the Rubidium-85 +# atom, given that it's a Rydberg atom commonly used in atomic physics and we have the appropriate technology to change its +# energy state using lasers. If you need a refresher on how we change the electronic energy levels of atoms, do take +# a look at the blue box below! +# +# .. container:: alert alert-block alert-info +# +# **Atomic Physics Primer:** Atoms consist of a positively charged nucleus +# and negative electrons around it. The electrons inhabit energy +# levels, which have a population limit. As the levels fill up, the +# electrons occupy higher and higher electronic energy states, or +# energy levels. But as long as +# there is space, electrons can change energy levels, with a preference +# for the lower ones. This can happen spontaneously or due to external +# influences. +# +# When the lower energy levels are not occupied, the higher energy levels +# are unstable: electrons will prefer to minimize their energy and jump to +# a lower level on their own. What happens when an electron jumps from +# a high energy level to a lower one? Conservation of energy tells us +# that the energy must go somewhere. Indeed, a photon with an energy +# equal to the energy lost by the electron is emitted. This energy is +# proportional to the frequency (colour) of the photon. +# +# Conversely, we can use laser light to induce the opposite process. +# When an electron is in a stable or ground state, we can use lasers +# with their frequency set roughly to the difference +# in energy levels, or energy gap, between the ground state and an +# excited state. If a photon hits an electron, it will go to that +# higher energy state. When the light stimulus is removed, the excited +# electrons will return to stable states. The time it takes them to do +# so depends on the particular excited state they are in since, +# sometimes, the laws of physics will make it harder for electrons to +# jump back on their own. +# +# .. figure:: ../_static/demonstration_assets/neutral_atoms/atomic.png +# :align: center +# :width: 60% +# +# .. +# +# But even if we've chosen one electron in the atom, we need to make sure that we are effectively +# working with only two energy levels in that atom. This ensures that we have a qubit! +# One of the energy levels will be a ground state +# for the valence electron, which we call the *fiducial state* and denote by :math:`\lvert 0 \rangle.` +# The other energy level will be an long-lived excited state, known as a hyperfine state, denoted by :math:`\lvert 1 \rangle.` +# We'll induce transitions between these two states using light whose energy matches the energy difference between +# these atomic levels. +# +# +# Initializing the qubits +# ----------------------- +# +# We have chosen our atom and its energy levels, so the easy part is over! But there are still some difficult tasks +# ahead of us. In particular, we need to isolate individual atoms inside our optical +# tweezers *and* make sure that they are all in the **fiducial +# ground state,** as required by DiVincenzo's second criterion. This fiducial state +# is stable, since minimal-energy states will not spontaneously emit any energy. +# +# The first step to initialize the qubits is to cool down a cloud of atoms in a way that +# all of their electrons end up in the same state. There are many states of minimum energy, +# so we need to be careful that all electrons are in the same one! For Rubidium atoms, we +# use a technique known as **laser cooling**. It involves putting the atoms in a magnetic trap within a +# vacuum chamber and then +# using lasers both to freeze them in place and make sure all the electrons are in the same stable state. +# +# To understand how neutral atoms can be used to build a quantum device, +# let's figure out how all the electrons end up in the same energy state. It turns out that Rubidium-85 is +# the ideal atom not only because it has one valence electron, but also because it has a **closed optical loop.** +# +# Rubidium-85 has two ground states :math:`\vert 0\rangle` and :math:`\vert \bar{0}\rangle,` which are excited +# using the laser to two excited states :math:`\vert 1\rangle` and :math:`\vert \bar{1}\rangle` respectively. However, +# both of these excited states will decay to :math:`\vert 0\rangle` with high probability. This means that no +# matter what ground state the electrons occupied initially, they will most likely be driven to the same ground state +# through the laser cooling method. +# +# .. figure:: ../_static/demonstration_assets/neutral_atoms/closed_loop.png +# :align: center +# :width: 60% +# +# .. +# +# Great! We have our cloud of atoms all frozen and in the same ground state. But now we need to pick out +# single atoms and arrange them in nice ways. Here's where we use our optical tweezers. The width of the laser +# can be focused enough so that we are sure that at most one atom gets trapped. Moreover, one laser beam +# can be split into a variety of arrays of beams through a spatial light modulator, allowing us to rearrange +# the positions of the atoms in many ways. With our atoms in position and in the fiducial ground state, we're +# ready to do some quantum operations on them! +# +# Measuring an electronic state +# ------------------------------ +# +# Now that our fiducial state is prepared, let's focus on another essential part of a quantum computation: +# measuring the state of our system. But wait... isn't measurement the last step in a quantum circuit? +# Aren't we skipping ahead a little bit? Not really! Once we have our initial state, we should measure it to +# verify that we have indeed prepared the correct state. After all, some of the steps we carried out to +# prepare the atoms aren't really foolproof; there are two issues that we need to address. +# +# The first problem is that traps are designed to trap *at most* one atom. This means that some traps might contain +# **no** atoms! With cutting-edge initialization routines, about :math:`2\%` of the tweezers remain empty. The second issue is +# that laser cooling is not deterministic, which means that some atoms may not be in the ground state. We would like +# to exclude those from our initial state. Happily, there is a simple solution that addresses these two problems. +# +# To verify that a neutral atom is in the fiducial state :math:`\lvert 0 \rangle,` we shine a photon on it that stimulates +# the transition between this state to some short-lived excited state :math:`\lvert h \rangle.` Electrons excited in this way will +# promptly decay to the state :math:`\lvert 0 \rangle` again, emitting light. The electrons that are in some state different than +# :math:`\lvert 0 \rangle` never get excited, since the photon does not have the right energy. And, of course, nothing will happen +# in traps where there is no atom. The net result is that atoms in the ground state will shine, while others won't. This +# phenomenon, known as fluorescence, is also used in trapped ion technologies. The same method can be used at the end of +# a quantum computation to measure the final state of the atoms in the computational basis. +# +# .. figure:: ../_static/demonstration_assets/neutral_atoms/fluorescence.png +# :align: center +# :width: 60% +# +# .. +# +# .. note:: +# +# What about atoms in the state :math:`\vert \bar{0}\rangle?` Wouldn't they become excited as well? +# We can actually choose the energy level :math:`\vert h\rangle` such that the transition +# :math:`\vert \bar{0}\rangle \rightarrow \vert h\rangle` wouldn't conserve angular momentum, +# so it would be suppressed. +# +# Neutral atoms and light +# ----------------------- +# +# We want to carry out computations using the electronic energy levels of the neutral atoms, which means that we +# need to be able to control their quantum state. To do so, we need to act on it with a *light pulse*—a +# short burst of light whose amplitude and phase are carefully controlled over time. To predict exactly how +# pulses affect the quantum states, we need to write down the *Hamiltonian* of the system. +# +# .. note:: +# +# Recall that the Hamiltonian :math:`H` is the observable for the energy of the system, but it also describes +# how the system's quantum state evolves in time. If a system's initial state is :math:`\vert \psi(0)\rangle` +# then, after a time interval :math:`t,` the state is +# +# .. math:: +# +# \vert \psi(t)\rangle = \exp\left(-i\int_{0}^{t}H(\tau)d\tau\right)\vert \psi(0)\rangle. +# +# In general, this is not easy to calculate. But :func:`~pennylane.evolve` comes to our rescue, since it will calculate +# :math:`\vert \psi(t)\rangle` for us using some very clever approximations. +# +# When a pulse of light of frequency :math:`\nu(t),` amplitude :math:`\Omega(t)/2\pi` and phase :math:`\phi` is shone +# upon *all* the atoms in our array, the *Hamiltonian* describing this interaction turns out to be [#Neutral2020]_ +# +# .. math:: +# +# H_d = \Omega(t)\sum_{q\in\text{wires}}(\cos(\phi)\sigma_{q}^x-\sin(\phi)\sigma_{q}^y) - \frac{1}{2}\delta(t)\sum_{q\in\text{wires}}(\mathbb{I}_q -\sigma_{q}^z). +# +# Here, the **detuning** :math:`\delta(t)` is defined as the difference between the photon's energy and the energy :math:`E_{01}` +# needed to transition between the ground state :math:`\lvert 0 \rangle` and the excited state +# :math:`\lvert 1 \rangle:` +# +# .. math:: +# +# \delta(t) = \hbar\nu(t)-E_{01}. +# +# We will call :math:`H_d` the **drive Hamiltonian**, since the electronic states of the atoms are being +# "driven" by the light pulse. This Hamiltonian is time-dependent, and it may also depend +# on other parameters that describe the pulse. PennyLane's +# :class:`pennylane.pulse.ParametrizedHamiltonian` class will help us deal with such a mathematical object. +# You can learn more about Parametrized Hamiltonians in our `documentation `_ +# and in this :doc:`pulse Programming demo `. +# +# Driving excitations with pulses +# ------------------------------- +# +# The mathematical expression of the Hamiltonian tells us that the time evolution depends on +# the shape of the pulse, which we can control pretty much arbitrarily as long as it's finite in duration. +# We must choose a pulse shape that starts and dies off smoothly. It turns out that one of the best choices is +# the *Blackman window* pulse, which minimizes noise [#Pulser2022]_. +# The amplitude of a Blackman pulse of duration :math:`T` is given by +# +# .. math:: +# +# \frac{\Omega(t)}{2\pi} = \left\{\begin{array}{lr} \left(\frac{1-\alpha}{2}\right)A-\frac{A}{2}\cos\left(\frac{2\pi t}{T}\right)+\frac{\alpha A}{2}\cos\left(\frac{4\pi t}{T}\right), & \text{if } 0 \leq t \leq T \\ 0 & \text{otherwise.} \end{array}\right. +# +# Here, :math:`A` is the peak amplitude, which we will treat as an adjustable parameter. A +# standard choice is to fix :math:`\alpha = 0.16;` which we will use in this demo. We will also set :math:`T=0.2` ns, although +# this can be easily changed in programmable devices. +# Let's plot this function to get an idea of what the pulse looks like. First, let's import all the relevant libraries. + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt +import jax +from jax import numpy as jnp # Needed for pulse programming + +jax.config.update("jax_platform_name", "cpu") # Tell jax to use CPU by default +jax.config.update("jax_enable_x64", True) + +############################################################################## +# +# Now, let's define the ``blackman_window`` function and plot it. + +duration = 0.2 # We'll set all of our pulses' duration to 0.2 + + +def blackman_window(peak, time): + + blackman = ( + peak * 0.42 + - 1 / 2 * peak * jnp.cos(2 * jnp.pi * time / duration) + + peak * 0.08 * jnp.cos(4 * jnp.pi * time / duration) + ) + + return blackman + + +t_points = np.linspace(0, duration, 100) +y_points = [blackman_window(1, t) for t in t_points] + +plt.xlabel("Time", fontsize=10) +plt.ylabel("Amplitude", fontsize=10) + +plt.title(f"Blackman Window Pulse (duration = {duration})") +plt.plot(t_points, y_points, c="#66c4ed") +plt.show() +############################################################################## +# +# We will stick to using Blackman window pulses for the rest of this tutorial. +# +# Let us explore how an interaction with this pulse changes the quantum state. +# The drive Hamiltonian is already coded for us in PennyLane's :func:`~pennylane.pulse.rydberg_drive`. For conciseness, +# let's import it and call it ``H_d.`` Then, we can use :func:`~pennylane.evolve` +# to calculate how an initial state interacting with +# a pulse evolves in time. First, let's assume that the detuning :math:`\delta` is zero +# (indicating that the drive laser and Rydberg transition are in resonance). + +from pennylane.pulse import rydberg_drive as H_d + +# Choose some arbitrary parameters +peak = 2 +phase = np.pi / 2 +detuning = 0 + +# For now, let's act on only one neutral atom +single_qubit_dev = qml.device("default.qubit", wires=1) + + +@qml.qnode(single_qubit_dev) +def state_evolution(): + + # Use qml.evolve to find the final state of the atom after interacting with the pulse + qml.evolve(H_d(blackman_window, phase, detuning, wires=[0]))([peak], t=[0, duration]) + + return qml.state() + + +print("The final state is {}".format(state_evolution().round(2))) +############################################################################## +# +# We see that the electronic state changes indeed. As a sanity check, let's see what happens when the detuning is +# large, such that we expect not to drive the transition. + +# Choose some arbitrary parameters + +peak = 2 +phase = np.pi / 2 +detuning = 100 # Some large detuning to prove the point + + +@qml.qnode(single_qubit_dev) +def state_evolution_detuned(): + + # Use qml.evolve to find the final state of the atom after interacting with the pulse + qml.evolve(H_d(blackman_window, phase, detuning, wires=[0]))([peak], t=[0, duration]) + + return qml.state() + + +print( + "The final state is {}, which is the initial state!".format(state_evolution_detuned().round(2)) +) +############################################################################## +# +# All works as expected! +# +# Single-qubit gates +# ------------------ +# +# Note that, so far, we have paid no mind to the values for the peak amplitude +# nor the phase—we just chose some arbitrary values. But we can actually adjust these values +# to create some well-known quantum gates. That's the magic of pulse programming! +# Let's see how to properly choose these values. +# +# When the detuning is zero and the pulse acts only on one qubit, Schrodinger's equation tells us we can +# write the evolved state for one qubit as +# +# .. math:: +# +# \vert \psi(T)\rangle = \exp\left(-i\int_{0}^{T}\Omega(t)(\cos(\phi)\sigma^x-\sin(\phi)\sigma^y)dt\right)\vert \psi(0)\rangle. +# +# For a fixed value of the phase :math:`\phi,` the evolution depends only on the integral of :math:`\Omega(t)` over the +# duration of the pulse :math:`T.` The integral can be calculated exactly for our Blackman window, in terms of the peak amplitude: +# +# .. math:: +# +# \frac{1}{2\pi}\int_{0}^{T}\Omega(t)dt = \left(\frac{1-\alpha}{2}\right)A\times T = 0.42*0.2*A. +# +# For example, for :math:`\phi = 0,` the evolved state is of the form :math:`\vert\psi(t)\rangle = e^{-i\theta \sigma^x },` +# with :math:`\theta = \int_{0}^{T}\Omega(t).` This is none other than the rotation gate :math:`RX(\theta).` Therefore, if +# we want to implement a rotation by an angle :math:`\theta,` it suffices to use a Blackman pulse with peak amplitude +# +# .. math:: +# +# A = \frac{\theta}{2\pi\times 0.42 \times 0.2}. +# +# We can program the pulse easily using PennyLane, and verify that it gives us the correct result. + + +def neutral_atom_RX(theta): + + peak = theta / duration / 0.42 / (2 * jnp.pi) # Recall that duration is 0.2 + + # Set phase and detuning equal to zero for RX gate + qml.evolve(H_d(blackman_window, 0, 0, wires=[0]))([peak], t=[0, duration]) + + +print( + "For theta = pi/2, the matrix for the pulse-based RX gate is \n {} \n".format( + qml.matrix(neutral_atom_RX, wire_order=[0])(jnp.pi / 2).round(2) + ) +) +print( + "The matrix for the exact RX(pi/2) gate is \n {}".format( + qml.matrix(qml.RX(jnp.pi / 2, wires=0)).round(2) + ) +) +############################################################################## +# +# A similar argument can be made for :math:`RY` rotations, with the only difference being that :math:`\phi = -\pi/2.` + + +def neutral_atom_RY(theta): + + peak = theta / duration / 0.42 / (2 * jnp.pi) # Recall that duration is 0.2 + + # Set phase equal to pi/2 and detuning equal to zero for RY gate + qml.evolve(H_d(blackman_window, -jnp.pi / 2, 0, wires=[0]))([peak], t=[0, duration]) + + +print( + "For theta = pi/2, the matrix for the pulse-based RY gate is \n {} \n".format( + qml.matrix(neutral_atom_RY, wire_order=[0])(jnp.pi / 2).round(2) + ) +) +print( + "The matrix for the exact RY(pi/2) gate is \n {}".format( + qml.matrix(qml.RY(jnp.pi / 2, wires=0)).round(2) + ) +) +############################################################################## +# +# We have implemented two orthogonal rotations in our neutral-atom device. This means that we have a universal set of single-qubit gates: +# all one-qubit gates can be implemented using some combination of :math:`RX` and :math:`RY!` The easy part is over—now we need to +# figure out how to apply two-qubit gates. +# +# The Rydberg blockade +# -------------------- +# +# In the case of a neutral-atom device, implementing a two-qubit gate amounts to more than just applying pulses. We +# must make sure that the two atoms (i.e. our qubits) in question interact in a controlled way. The atoms are neutral, though, +# so do they even interact? They do, through various electromagnetic forces that arise due to the distributions of charges +# in the atoms, which are all accounted for in the so-called *Van der Waals* interaction. +# +# The Van der Waals interaction is usually pretty weak and short-ranged, but its effect will noticeably grow if we work +# with Rydberg atoms. The states of high energy that the electron in the atom can occupy sare +# known as **Rydberg states**. We will choose one such Rydberg state, which we denote by :math:`\vert r\rangle,` to serve as an auxiliary +# state in the implementation of two-qubit gates. Focusing only on the +# ground state :math:`\vert 0\rangle` and the Rydberg state :math:`\vert r\rangle` as accessible states, the Ryberg +# interaction is described by the *interaction Hamiltonian* [#Neutral2020]_. +# +# .. math:: +# +# H_i = \sum_{i", c="#9e9e9e") +plt.text(1.2, 50, "|0r>+|r0>", c="#ffd86d") +plt.text(1.2, 25, "|0r>-|r0>", c="#66c4ed") +plt.text(1.25, 6, "|00>", c="#e565e5") +plt.show() +############################################################################## +# +# Let's analyze what we see above. When the atoms are far away, the energy levels are evenly spaced. This means that if a pulse +# excites the system from :math:`\vert 00 \rangle` (both atoms in the ground state) to :math:`\vert 0r \rangle` (one atom in the ground state, +# one in the Rydberg state), then a similar second pulse could excite the system into :math:`\vert rr \rangle` (both atoms in the Ryberg state). +# However, as the atoms move close to each other, this is no longer true. When the distance becomes small, +# as soon as one of the atoms reaches the Rydberg state, the other one cannot reach that state with a similar pulse. +# +# .. note:: +# +# We are not using any realistic values for either the amplitude or the coupling strength. These have been chosen in arbitrary +# units for visualization purposes. If you would like to know more about the specifications for real quantum hardware, check out +# :doc:`this demo `. +# +# This phenomenon is called the **Rydberg blockade.** When the distance between two atoms is below a certain distance known as +# the **blockade radius,** one atom being in the Rydberg state "blocks" the other one from reaching its Rydberg state. Let's see +# how the Ryberg blockade helps us build two-qubit gates. +# +# The Ctrl-Z gate +# --------------- +# +# The native two-qubit gate for neutral atoms devices turns out to be the :math:`CZ` gate, which can be implemented with a sequence +# of :math:`RX` rotations (in the space spanned by :math:`\vert 0 \rangle` and :math:`\vert r \rangle`) on a set of two atoms: the **control atom** +# and the **target atom.** In particular, three pulses +# are needed: a :math:`\pi`-**pulse** (inducing a rotation by an angle :math:`\pi`) on the control atom a :math:`2\pi`-**pulse** +# (inducing a rotation by an angle :math:`2\pi`) on the target atom, and another :math:`\pi`-**pulse** on the control atom, in that +# order. Combined with the effects of the Rydberg blockade, this pulse combination will implement the desired gate. To see this, +# let's code the pulses needed first. + + +def two_pi_pulse(distance, coupling, wires=[0]): + + # Build full Hamiltonian + full_hamiltonian = H_d(blackman_window, 0, 0, wires) + H_i(distance, coupling) + + # Return the 2 pi pulse + qml.evolve(full_hamiltonian)([2 * jnp.pi / 0.42 / 0.2 / (2 * jnp.pi)], t=[0, 0.2]) + + +def pi_pulse(distance, coupling, wires=[0]): + + full_hamiltonian = H_d(blackman_window, 0, 0, wires) + H_i(distance, coupling) + + # Return the pi pulse + qml.evolve(full_hamiltonian)([jnp.pi / 0.42 / 0.2 / (2 * jnp.pi)], t=[0, 0.2]) + + +############################################################################## +# +# When acting on individual atoms, these pulses have the net effect of adding a phase of :math:`-1` to the state. But the presence +# of the Rydberg blocakde changes this outcome. +# Then, let's see the effect the sequence of pulses has on the :math:`\vert 00 \rangle` state when the atoms are close enough. +# + +dev_two_qubits = qml.device("default.qubit", wires=2) + + +@qml.qnode(dev_two_qubits) +def neutral_atom_CZ(distance, coupling): + + pi_pulse(distance, coupling, wires=[0]) + + two_pi_pulse(distance, coupling, wires=[1]) + + pi_pulse(distance, coupling, wires=[0]) + + return qml.state() + + +print( + "The final state after the set of pulses is {} when atoms are close.".format( + neutral_atom_CZ(0.2, 1).round(2) + ) +) +print( + "The final state after the set of pulses is {} when atoms are far.".format( + neutral_atom_CZ(2, 1).round(2) + ) +) +############################################################################## +# +# The effect is to multiply the two-qubit state by :math:`-1,` which doesn't happen without the Rydberg blockade! Indeed, when the atoms +# are far away from each other, each individual atomic state gets multiplied by :math:`-1.` Therefore, there would be +# no total phase change since the two-atom state gains a multiplier of :math:`(-1)\times(-1)=1.` It turns out that the Rydberg +# blockade is only important when the initial state is :math:`\vert 00 \rangle.` +# +# .. figure:: ../_static/demonstration_assets/neutral_atoms/control_z00.png +# :align: center +# :width: 60% +# +# .. +# +# If one of the atoms were to be in the state :math:`\vert 1 \rangle,` then the pulse wouldn't affect such an atom +# since it's not tuned to the :math:`\vert r \rangle \rightarrow \vert 1 \rangle` transition. +# +# .. figure:: ../_static/demonstration_assets/neutral_atoms/control_z01.png +# :align: center +# :width: 60% +# +# .. +# +# The net effect of the sequence of pulses is summarized in the following table. +# +# .. raw:: html +# +# +#
+# +# .. rst-class:: docstable +# +# +-------------------------+-------------------------------+ +# | Initial state | Final state | +# +=========================+===============================+ +# | :math:`\vert 00\rangle` | :math:`-\vert 00\rangle` | +# +-------------------------+-------------------------------+ +# | :math:`\vert 01\rangle` | :math:`-\vert 01\rangle` | +# +-------------------------+-------------------------------+ +# | :math:`\vert 10\rangle` | :math:`-\vert 10\rangle` | +# +-------------------------+-------------------------------+ +# | :math:`\vert 11\rangle` | :math:`\vert 11\rangle` | +# +-------------------------+-------------------------------+ +# +# .. raw:: html +# +#
+# +# Up to a global phase, this corresponds to the :math:`CZ` gate. Together with the :math:`RX` and :math:`RY` gates, we have a universal set of gates, +# since the `CNOT` gate can be expressed in terms of :math:`CZ` via the equation +# +# .. figure:: ../_static/demonstration_assets/neutral_atoms/cnot_and_cz.png +# :align: center +# :width: 60% +# +# .. +# +# .. note :: +# +# The method shown here is only one of the ways to implement the :math:`CZ` gate. Here, we have chosen to encode the qubits +# in a ground and a hyperfine state, which allows for simplicity. Depending on the hardware, one may also choose +# to encode the qubits in a ground state and a Rydberg state, or two Rydberg states. The Rydberg blockade is also +# the main phenomenon that allows for the implementation of two-qubit gates in these realizations, but the details +# are a bit different [#Morgado2011]_. +# +# Challenges and future improvements +# ---------------------------------- +# +# Great, this all seems to work like a charm... at least in theory. In practice, there are still challenges to overcome. +# We've managed to efficiently prepare qubits, apply gates, and measure, satisfying DiVincenzo's second, fourth, and fifth criteria. +# However, as with most quantum architectures, there are some challenges to overcome with regard to scalability and decoherence times. +# +# An important issue to deal with in quantum hardware in general. Quantum states are short-lived in the presence of external +# influences. We can never achieve a perfect vacuum in the chamber, and the particles and charges around the atoms will destroy +# our carefully crafted quantum states in a matter of microseconds. While our qubit states are long-lived, the auxiliary Rydberg +# state is more prone to decohere, which limits the amount of computations we can perform in a succession. Overall +# improving our register preparation, gates, and measurement protocols is of the essence to make more progress on +# neutral-atom technology. +# +# While we are able to trap many atoms with our current laser technology, scaling optical tweezer arrays to thousands of qubits +# poses an obstacle. We rely on spatial modulators to divide our laser beams, but this also reduces the strength of the tweezers. If +# we split a laser beam too much, the tweezers become too weak to contain an atom. Of course, we could simply use more laser sources, +# but the spatial requirements for the hardware would also grow. Alternatively, we can use laser sources with higher intensity, +# but such technology is still being developed. Another solution is to use photons through optical fibres to communicate between +# different processors, allowing for further connectivity and scalability. +# +# Another issue with scalability is the preparation times of the registers. While, with hundreds of qubits, we can still prepare +# and arrange the atoms in reasonable times, it becomes increasingly costly the more atoms we have. And we do need +# to reprepare the neutral atom array when we are done with a computation. It's not as easy as moving the atoms around +# faster—if we try to move the atoms around too fast, they will escape from the traps! Therefore, engineers are working +# on more efficient ways to move the tweezers around, minimizing the number of steps needed to prepare the initial +# state [#NeutralHardware2023]_. +# +# Finally, let us remark that there are some nuances with gate implementation—it's not nearly as simple in real-life as it is in theory. +# It is not easy to address individual atoms with the driving laser pulses. This is necessary for universal quantum computing, as we saw in +# the previous section. True local atom drives are still in the works, but even without them, we can still use these non-universal devices +# for applications in quantum simulation. +# +# Conclusion +# ---------- +# +# Neutral-atom quantum hardware is a promising and quickly developing technology which we should keep an eye on. The ability to +# easily create custom qubit topologies and the coherence time of the atoms are its main strong points, and its weaknesses are +# actually no too different from other qubit-based architectures. We can easily program neutral-atom devices using pulses, +# for which PennyLane is of great help. If you want to +# learn more, check out our tutorials on the :doc:`Aquila device, ` :doc:`neutral atom configurations, ` and +# :doc:`pulse programming `. And do take a look +# at the references below to dive into much more detail about the topics introduced here. +# +# References +# ---------- +# +# .. [#Aquila2022] +# +# QuEra (May 23, 2023). Aquila, our 256-qubit quantum processor, +# `https://www.quera.com/aquila `__. +# +# .. [#DiVincenzo2000] +# +# D. DiVincenzo. (2000) "The Physical Implementation of Quantum Computation", +# `Fortschritte der Physik 48 (9–11): 771–783 +# `__. +# (`arXiv `__) +# +# .. [#Tweezers1985] +# +# A. Ashkin, J. M. Dziedzic, J. E. Bjorkholm, and Steven Chu. (1986) +# "Observation of a single-beam gradient force optical trap for dielectric particles", +# Opt. Lett. 11, 288-290 +# +# .. [#AtomComputing] +# +# Atom Computing (May 23, 2023). Quantum Computing Technology, +# `https://atom-computing.com/quantum-computing-technology `__. +# +# .. [#Neutral2020] +# +# L. Henriet, et al. (2020) "Quantum computing with neutral atoms", +# Quantum volume 4, pg. 327 (`arXiv `__). +# +# .. [#Pulser2022] +# +# H. Silverio et al. (2022) "Pulser: An open-source package for the design of pulse sequences in programmable neutral-atom arrays", +# Quantum volume 6, pg. 629 (`arXiv `__). +# +# .. [#Morgado2011] +# +# M. Morgado and S. Whitlock.(2021) "Quantum simulation and computing with Rydberg-interacting qubits" +# AVS Quantum Sci. 3, 023501 (`arXiv `__). +# +# .. [#NeutralHardware2023] +# +# K. Wintersperger et al. (2023) "Neutral Atom Quantum Computing Hardware: Performance and End-User Perspective", +# (`arXiv `__) +# diff --git a/demonstrations_v2/tutorial_neutral_atoms/metadata.json b/demonstrations_v2/tutorial_neutral_atoms/metadata.json new file mode 100644 index 0000000000..f825dba837 --- /dev/null +++ b/demonstrations_v2/tutorial_neutral_atoms/metadata.json @@ -0,0 +1,135 @@ +{ + "title": "Neutral-atom quantum computers", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2023-05-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/neutral_atoms/thumbnail_tutorial_neutral_atoms.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_neutral_atoms.png" + } + ], + "seoDescription": "Learn how neutral atom quantum devices work using code.", + "doi": "", + "references": [ + { + "id": "Aquila2022", + "type": "webpage", + "title": "Aquila, our 256-qubit quantum processor.", + "authors": "Quera", + "year": "2023", + "journal": "", + "url": "https://www.quera.com/aquila" + }, + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation.", + "authors": "DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik 48", + "url": "https://arxiv.org/abs/quant-ph/0002077" + }, + { + "id": "Tweezers1985", + "type": "article", + "title": "Observation of a single-beam gradient force optical trap for dielectric particles.", + "authors": "A. Ashkin, J. M. Dziedzic, J. E. Bjorkholm, and Steven Chu.", + "year": "1986", + "journal": "", + "url": "" + }, + { + "id": "AtomComputing", + "type": "webpage", + "title": "Quantum Computing Technology.", + "authors": "Atom Computing", + "year": "2023", + "journal": "", + "url": "https://atom-computing.com/quantum-computing-technology" + }, + { + "id": "Neutral2020", + "type": "article", + "title": "Quantum computing with neutral atoms.", + "authors": "L. Henriet, et al.", + "year": "2020", + "journal": "Quantum volume", + "url": "https://arxiv.org/abs/2006.12326" + }, + { + "id": "Pulser2022", + "type": "article", + "title": "Pulser: An open-source package for the design of pulse sequences in programmable neutral-atom arrays.", + "authors": "H. Silverio et al.", + "year": "2022", + "journal": "Quantum volume", + "url": "https://arxiv.org/abs/2104.15044" + }, + { + "id": "Morgado2011", + "type": "article", + "title": "Quantum simulation and computing with Rydberg-interacting qubits", + "authors": "M. Morgado and S. Whitlock.", + "year": "2021", + "journal": "AVS Quantum Sci.", + "url": "https://arxiv.org/abs/2011.03031" + }, + { + "id": "NeutralHardware2023", + "type": "article", + "title": "Neutral Atom Quantum Computing Hardware: Performance and End-User Perspective.", + "authors": "K. Wintersperger et al.", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2304.14360" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_trapped_ions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_sc_qubits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_neutral_atoms/requirements.in b/demonstrations_v2/tutorial_neutral_atoms/requirements.in new file mode 100644 index 0000000000..a7e20f9b86 --- /dev/null +++ b/demonstrations_v2/tutorial_neutral_atoms/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py b/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py new file mode 100644 index 0000000000..112259d9cb --- /dev/null +++ b/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py @@ -0,0 +1,543 @@ +r""" +Optimizing noisy circuits with Cirq +=================================== + +.. meta:: + :property="og:description": Learn how noise can affect the optimization and training of quantum computations. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/noisy_circuit_optimization_thumbnail.png + +.. related:: + + pytorch_noise PyTorch and noisy devices + +*Author: Nathan Killoran — Posted: 01 June 2020. Last updated: 16 June 2021.* + +.. figure:: ../_static/demonstration_assets/noisy_circuit_optimization/noisy_qubit.png + :align: center + :width: 90% + +Until we have fault-tolerant quantum computers, +we will have to learn to live with noise. There are lots of exciting +ideas and algorithms in quantum computing and quantum machine learning, +but how well do they survive the reality of today's noisy devices? + +Background +---------- + +Quantum pure-state simulators are great and readily available in +a number of quantum software packages. +They allow us to experiment, prototype, test, and validate algorithms +and research ideas—up to a certain number of qubits, at least. + +But present-day hardware is not ideal. We're forced to confront +decoherence, bit flips, amplitude damping, and so on. Does the +presence of noise in near-term devices impact their use in, +for example, +:doc:`variational quantum algorithms `? +Won't our models, trained so carefully in simulators, fall apart +when we run on noisy devices? + +In fact, there is some optimism that variational algorithms may be +the best type of algorithms on near-term devices, and could +have an in-built adaptability to noise that more rigid +textbook algorithms do not possess. +Variational algorithms are somewhat robust against the fact +that the device they are run on may not be ideal. Being variational +in nature, they can be tuned to "work around" noise to some extent. + +Quantum machine learning leverages a lot of tools from its +classical counterpart. Fortunately, there is great evidence that +machine learning algorithms can not only be robust to noise, but +can even benefit from it! +Examples include the use of +`reduced-precision arithmetic in deep learning `_, +the strong performance of +`stochastic gradient descent `_, +and the use of "dropout" noise to +`prevent overfitting `_. + +With this evidence to motivate us, we can still hope to find, +extract, and work with the underlying quantum "signal" +that is influenced by a device's inherent noise. + +Noisy circuits: creating a Bell state +------------------------------------- + +Let's consider a simple quantum circuit which performs a standard +quantum information task: the creation of an entangled +state and the measurement of a +`Bell inequality `_ +(also known as the +`CHSH inequality `_). + +Since we'll be dealing with noise, we'll need to use a simulator +that supports noise and density-state representations of quantum +states (in contrast to many simulators, which use a pure-state +representation). + +Fortunately, `Cirq `_ provides mixed-state +simulators and noisy operations natively, so we can use the +`PennyLane-Cirq plugin `_ +to carry out our noisy simulations. +""" + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt + +dev = qml.device("cirq.mixedsimulator", wires=2, shots=1000) + +# CHSH observables +A1 = qml.PauliZ(0) +A2 = qml.PauliX(0) +B1 = qml.Hermitian(np.array([[1, 1], [1, -1]]) / np.sqrt(2), wires=1) +B2 = qml.Hermitian(np.array([[1, -1], [-1, -1]]) / np.sqrt(2), wires=1) +CHSH_observables = [A1 @ B1, A1 @ B2, A2 @ B1, A2 @ B2] + + +# subcircuit for creating an entangled pair of qubits +def bell_pair(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + + +# circuits for measuring each distinct observable +@qml.qnode(dev) +def measure_A1B1(): + bell_pair() + return qml.expval(A1 @ B1) + + +@qml.qnode(dev) +def measure_A1B2(): + bell_pair() + return qml.expval(A1 @ B2) + + +@qml.qnode(dev) +def measure_A2B1(): + bell_pair() + return qml.expval(A2 @ B1) + + +@qml.qnode(dev) +def measure_A2B2(): + bell_pair() + return qml.expval(A2 @ B2) + + +# now we measure each circuit and construct the CHSH inequality +expvals = [measure_A1B1(), measure_A1B2(), measure_A2B1(), measure_A2B2()] + +# The CHSH operator is A1 @ B1 + A1 @ B2 + A2 @ B1 - A2 @ B2 +CHSH_expval = np.sum(expvals[:3]) - expvals[3] +print(CHSH_expval) + +############################################################################## +# The output here is :math:`2\sqrt{2},` which is the maximal value of the +# CHSH inequality. States which have a value +# :math:`\langle CHSH \rangle \geq 2` can safely be considered +# "quantum". +# +# .. note:: In this situation "quantum" means that there is +# no `local hidden variable theory `_ +# which could produce these measurement outcomes. It does not strictly +# mean the presence of entanglement. +# +# Now let's turn up the noise! 📢 📢 📢 +# +# Cirq provides a number of noisy channels that are not part of +# PennyLane core. This is no issue, as the +# `PennyLane-Cirq `_ +# plugin provides these and allows them to be used directly in PennyLane +# circuit declarations. + +from pennylane_cirq import ops as cirq_ops + +# Note that the 'Operation' op is a generic base class +# from PennyLane core. +# All other ops are provided by Cirq. +available_ops = [op for op in dir(cirq_ops) if not op.startswith("_")] +print("\n".join(available_ops)) + +############################################################################## +# PennyLane operations and external framework-specific operations can be +# interwoven freely in circuits that use that plugin's device +# for execution. +# In this case, the Cirq-provided channels can be used with +# Cirq's mixed-state simulator. +# +# We'll use the ``BitFlip`` channel, which has the effect of +# randomly flipping the qubits in the computational basis. + +noise_vals = np.linspace(0, 1, 25) + +CHSH_vals = [] +noisy_expvals = [] + +for p in noise_vals: + # we overwrite the bell_pair() subcircuit to add + # extra noisy channels after the entangled state is created + def bell_pair(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + cirq_ops.BitFlip(p, wires=0) + cirq_ops.BitFlip(p, wires=1) + + # measuring the circuits will now use the new noisy bell_pair() function + expvals = [measure_A1B1(), measure_A1B2(), measure_A2B1(), measure_A2B2()] + noisy_expvals.append(expvals) +noisy_expvals = np.array(noisy_expvals) +CHSH_expvals = np.sum(noisy_expvals[:, :3], axis=1) - noisy_expvals[:, 3] + +# Plot the individual observables +plt.plot(noise_vals, noisy_expvals[:, 0], "D", label=r"$\hat{A1}\otimes \hat{B1}$", markersize=5) +plt.plot(noise_vals, noisy_expvals[:, 1], "x", label=r"$\hat{A1}\otimes \hat{B2}$", markersize=12) +plt.plot(noise_vals, noisy_expvals[:, 2], "+", label=r"$\hat{A2}\otimes \hat{B1}$", markersize=10) +plt.plot(noise_vals, noisy_expvals[:, 3], "v", label=r"$\hat{A2}\otimes \hat{B2}$", markersize=10) +plt.xlabel("Noise parameter") +plt.ylabel(r"Expectation value $\langle \hat{A}_i\otimes\hat{B}_j\rangle$") +plt.legend() +plt.show() + +############################################################################## +# By adding the bit-flip noise, we have degraded the value of the +# CHSH observable. The first two observables +# :math:`\hat{A}_1\otimes \hat{B}_1` +# and :math:`\hat{A}_1\otimes \hat{B}_2` are sensitive to this +# noise parameter. Their value is weakened when the noise parameter is not +# 0 or 1 (note that the the CHSH operator is symmetric with +# respect to bit flips). +# +# The latter two observables, on the other hand, are seemingly +# unaffected by the noise at all. +# +# We can see that even when noise is present, there may still be subspaces +# or observables which are minimally affected or unaffected. +# This gives us some hope that variational algorithms can learn +# to find and exploit such noise-free substructures on otherwise +# noisy devices. +# +# We can also plot the CHSH observable in the noisy case. Remember, +# values greater than 2 can safely be considered "quantum". + +plt.plot(noise_vals, CHSH_expvals, label="CHSH") +plt.plot(noise_vals, 2 * np.ones_like(noise_vals), label="Quantum-classical boundary") +plt.xlabel("Noise parameter") +plt.ylabel("CHSH Expectation value") +plt.legend() +plt.show() + +############################################################################## +# Too much noise (around 0.2 in this example), and we lose the +# quantumness we created in our circuit. But if we only have a little +# noise, the quantumness undeniably remains. So there is still hope +# that quantum algorithms can do something useful, even on noisy +# near-term devices, so long as the noise is not high. +# +# .. note:: +# +# In Google's quantum supremacy paper [#arute2019]_, +# they were able to show that some +# small signature of quantumness remained in their computations, +# even after a deep many-qubit noisy circuit was executed. + +############################################################################## +# Optimizing noisy circuits +# ------------------------- +# +# Now, how does noise affect the ability to optimize or train a +# variational circuit? +# +# Let's consider an analog of the basic +# :doc:`qubit rotation tutorial `, +# but where we add an extra noise channel after the gates. +# +# .. note:: We model the noise process as the application of ideal noise-free +# gates, followed by the action of a noisy channel. This is a common +# technique for modelling noise, but may not be appropriate for all +# situations. + + +@qml.qnode(dev) +def circuit(gate_params, noise_param=0.0): + qml.RX(gate_params[0], wires=0) + qml.RY(gate_params[1], wires=0) + cirq_ops.Depolarize(noise_param, wires=0) + return qml.expval(qml.PauliZ(0)) + + +gate_pars = [0.54, 0.12] +print("Expectation value:", circuit(gate_pars)) + +############################################################################## +# In this case, the depolarizing channel degrades +# the qubit's density matrix :math:`\rho` towards the state +# +# .. math:: +# +# \rho' = \tfrac{1}{3}\left[X\rho X + Y\rho Y + Z\rho Z\right] +# +# (at the value :math:`p=\frac{3}{4},` it passes through the +# maximally mixed state). +# We can see this in our circuit by looking at how the final +# :class:`~pennylane.ops.PauliZ` expectation value changes as +# a function of the noise strength. + +noise_vals = np.linspace(0.0, 1.0, 20) +expvals = [circuit(gate_pars, noise_param=p) for p in noise_vals] + +plt.plot(noise_vals, expvals, label="Expectation value") +plt.plot(noise_vals, np.ones_like(noise_vals), "--", label="Highest possible") +plt.plot(noise_vals, -np.ones_like(noise_vals), "--", label="Lowest possible") +plt.ylabel(r"Expectation value $\langle \hat{Z} \rangle$") +plt.xlabel(r"Noise strength $p$") +plt.legend() +plt.show() + +############################################################################## +# Let's fix the noise parameter and see how the noise affects the +# optimization of our circuit. The goal is the same as the +# :doc:`qubit rotation tutorial `, +# i.e., to tune the qubit state until it has a ``PauliZ`` expectation value +# of :math:`-1` (the lowest possible). + + +# declare the cost functions to be optimized +def cost(x): + return circuit(x, noise_param=0.0) + + +def noisy_cost(x): + return circuit(x, noise_param=0.3) + + +# initialize the optimizer +opt = qml.GradientDescentOptimizer(stepsize=0.4) + +# set the number of steps +steps = 100 +# set the initial parameter values +init_params = np.array([0.011, 0.055], requires_grad=True) +noisy_circuit_params = init_params +params = init_params + +for i in range(steps): + # update the circuit parameters + # we can optimize both in the same training loop + params = opt.step(cost, params) + noisy_circuit_params = opt.step(noisy_cost, noisy_circuit_params) + + if (i + 1) % 5 == 0: + print( + "Step {:5d}. Cost: {: .7f}; Noisy Cost: {: .7f}".format( + i + 1, cost(params), noisy_cost(noisy_circuit_params) + ) + ) + +print("\nOptimized rotation angles (noise-free case):") +print("({: .7f}, {: .7f})".format(*params)) +print("Optimized rotation angles (noisy case):") +print("({: .7f}, {: .7f})".format(*noisy_circuit_params)) + +############################################################################## +# There are a couple interesting observations here: +# +# i) The noisy circuit isn't able to achieve the same final +# cost function value as the ideal circuit. This is because +# the noise causes the state to become irreversibly mixed. +# Mixed states can't achieve the same extremal expectation values +# as pure states. +# +# ii) However, both circuits still converge to +# the *same parameter values* :math:`(0,\pi),` despite having +# different final states. +# +# It could have been the case that noisy devices irreparably +# damage the optimization of variational circuits, steering us +# towards parameter values which are not at all useful. +# Luckily, at least for the simple example above, this is not the case. +# *Optimizations on noisy devices can still lead to similar parameter +# values as when we run on ideal devices.* + +############################################################################## +# Understanding the effect of noisy channels +# ------------------------------------------ +# +# Let's dig a bit into the underlying quantum information theory +# to understand better what's happening [#meyer2020]_. +# Expectation values of :doc:`variational circuits `, +# like the one we are measuring, are composed of three pieces: +# +# i) an initial quantum state :math:`\rho` (usually the zero state); +# ii) a parameterized unitary transformation :math:`U(\theta)`); and +# iii) measurement of a final observable :math:`\hat{B}.` +# +# The equation for the expectation value is given by the +# `Born rule `_: +# +# .. math:: +# +# \langle \hat{B} \rangle (\theta) = +# \mathrm{Tr}(\hat{B}U(\theta)\rho U^\dagger(\theta)). +# +# When optimizing, we can compute gradients of many common gates +# using the :doc:`parameter-shift rule `: +# +# .. math:: +# +# \nabla_\theta\langle \hat{B} \rangle(\theta) +# = \frac{1}{2} +# \left[ +# \langle \hat{B} \rangle\left(\theta + \frac{\pi}{2}\right) +# - \langle \hat{B} \rangle\left(\theta - \frac{\pi}{2}\right) +# \right]. +# +# In our example, the parametrized unitary :math:`U(\theta)` is split into two gates, +# :math:`U = U_2 U_1,` +# where :math:`U_1=R_X` and :math:`U_1=R_Y,` and each takes an independent +# parameter :math:`\theta_i.` +# +# What happens when we apply a +# noisy channel :math:`\Lambda` after the gates? In this case, +# the expectation value is now taken with +# respect to the noisy circuit: +# +# .. math:: +# +# \langle \hat{B} \rangle (\theta) = +# \mathrm{Tr}\left(\hat{B}\Lambda\left[ +# U(\theta)\rho U^\dagger(\theta) +# \right]\right). +# +# Thus, we can treat it as the expectation value of the same +# observable, but with respect to a different state +# :math:`\rho' = \Lambda\left[U(\theta)\rho U^\dagger(\theta)\right].` +# +# Alternatively, using the Heisenberg picture, we can transfer the channel +# :math:`\Lambda` acting on the state :math:`U(\theta)\rho U^\dagger(\theta)` +# into the *adjoint channel* :math:`\Lambda^\dagger` acting on the +# observable :math:`\hat{B},` transforming it to a new observable +# :math:`\hat{B} = \Lambda^\dagger[\hat{B}]=\hat{B}'.` +# +# With the channel present, the expectation value can be interpreted +# as if we had the same variational state, but measured a different +# observable: +# +# .. math:: +# +# \langle \hat{B} \rangle (\theta) = +# \mathrm{Tr}(\hat{B}'U(\theta)\rho U^\dagger(\theta)) = +# \langle \hat{B}' \rangle (\theta). +# +# This has immediate consequences for the parameter-shift rule. With +# the channel present, we have simply +# +# .. math:: +# +# \nabla_\theta\langle \hat{B} \rangle(\theta) +# = \frac{1}{2} +# \left[ +# \langle \hat{B}' \rangle\left(\theta + \frac{\pi}{2}\right) +# - \langle \hat{B}' \rangle\left(\theta - \frac{\pi}{2}\right) +# \right]. +# +# In other words, the parameter-shift rule continues to hold for all +# gates, even when we have additional noise! +# +# .. note:: In the above derivation, we implicitly assumed that the channel +# does not depend on the variational circuit's parameters. If the +# channel depended on the particular state, or if it depended +# on the parameters :math:`\theta,` we would need to be more careful. +# +# +# Let's confirm the above derivation with an example. + +angles = np.linspace(0, 2 * np.pi, 50) +theta2 = np.pi / 4 + + +def param_shift(theta1): + return 0.5 * ( + noisy_cost([theta1 + np.pi / 2, theta2]) - noisy_cost([theta1 - np.pi / 2, theta2]) + ) + + +noisy_expvals = [noisy_cost([theta1, theta2]) for theta1 in angles] +noisy_param_shift = [param_shift(theta1) for theta1 in angles] + +plt.plot(angles, noisy_expvals, label="Expectation value") # looks like 0.4 * cos(phi) +plt.plot(angles, noisy_param_shift, label="Parameter-shift value") # looks like -0.4 * sin(phi) +plt.ylabel(r"Expectation value $\langle \hat{Z} \rangle$") +plt.xlabel(r"Angle $\theta_1$") +plt.legend() +plt.show() + + +############################################################################## +# By inspecting the two curves, we can see that the parameter-shift rule +# gives the correct gradient of the expectation value, even with the presence +# of the noisy channel! +# +# In this example, the influence of the channel is to +# attenuate the maximal amplitude that the qubit state can achieve +# (:math:`\approx 0.4`). But even though the qubit's amplitude is attenuated, +# the gradient computed by the parameter-shift rule still "points in the +# right direction". +# +# This backs up the observation we made earlier that +# the result of the optimization gave the correct values for the angle +# parameters, but the value of the final cost function was lower than +# the noise-free case. +# + +############################################################################## +# Interpreting noisy circuit optimizations +# ---------------------------------------- +# +# Despite the observations that we can compute gradients for +# noisy channels, and that optimization may lead to the same parameter +# values for both noise-free and noisy circuits, we must still remain +# cautious in how we interpret the results. +# +# We can evaluate the correct gradient for the expectation value +# +# .. math:: +# +# \langle \hat{B} \rangle = +# \mathrm{Tr}\left(\hat{B}\Lambda\left[ +# U(\theta)\rho U^\dagger(\theta) +# \right]\right), +# +# but, because the noisy channel is present, *this expectation value may not +# reflect the actual expectation value we wanted to compute*. This is +# important to keep in mind for certain algorithms that have a physical +# interpretation for the variational circuit. +# +# For example, in the +# :doc:`variational quantum eigensolver `, we +# want to find the ground-state energy of a physical system. +# If there is an appreciable amount of noise present, +# the state we are optimizing will necessarily become mixed, and +# we should be careful interpreting the optimum value as the exact +# ground-state energy. + + +############################################################################## +# References +# ---------- +# +# .. [#arute2019] +# +# Frank Arute et al. "Quantum supremacy using a programmable +# superconducting processor." +# Nature, 574(7779), 505-510. +# +# .. [#meyer2020] +# +# Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert. +# "A variational toolbox for quantum multi-parameter estimation." +# `arXiv:2006.06303 +# `__, 2020. +# +# diff --git a/demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json b/demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json new file mode 100644 index 0000000000..cfa07bf03f --- /dev/null +++ b/demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json @@ -0,0 +1,51 @@ +{ + "title": "Optimizing noisy circuits with Cirq", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-06-01T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimizing_noisy_circuits_Cirq.png" + } + ], + "seoDescription": "Learn how noise can affect the optimization and training of quantum computations.", + "doi": "", + "references": [ + { + "id": "arute2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor.", + "authors": "Frank Arute et al.", + "year": "2019", + "journal": "Nature", + "url": "" + }, + { + "id": "meyer2020", + "type": "article", + "title": "A variational toolbox for quantum multi-parameter estimation.", + "authors": "Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2006.06303" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in b/demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in new file mode 100644 index 0000000000..d190727410 --- /dev/null +++ b/demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +pennylane_cirq diff --git a/demonstrations_v2/tutorial_noisy_circuits/demo.py b/demonstrations_v2/tutorial_noisy_circuits/demo.py new file mode 100644 index 0000000000..71fb8ab91c --- /dev/null +++ b/demonstrations_v2/tutorial_noisy_circuits/demo.py @@ -0,0 +1,297 @@ +r""" + +Noisy circuits +============== + +.. meta:: + :property="og:description": Learn how to simulate noisy quantum circuits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/N-Nisq.png + +.. related:: + + tutorial_noisy_circuit_optimization Optimizing noisy circuits with Cirq + pytorch_noise PyTorch and noisy devices + +*Author: Juan Miguel Arrazola — Posted: 22 February 2021. Last updated: 08 April 2021.* + +In this demonstration, you'll learn how to simulate noisy circuits using built-in functionality in +PennyLane. We'll cover the basics of noisy channels and density matrices, then use example code to +simulate noisy circuits. PennyLane, the library for differentiable quantum computations, has +unique features that enable us to compute gradients of noisy channels. We'll also explore how +to employ channel gradients to optimize noise parameters in a circuit. + +We're putting the N in NISQ. + +.. figure:: ../_static/demonstration_assets/noisy_circuits/N-Nisq.png + :align: center + :width: 20% + + .. +""" + +############################################################################## +# +# Noisy operations +# ---------------- +# Noise is any unwanted transformation that corrupts the intended +# output of a quantum computation. It can be separated into two categories. +# +# * **Coherent noise** is described by unitary operations that maintain the purity of the +# output quantum state. A common source are systematic errors originating from +# imperfectly-calibrated devices that do not exactly apply the desired gates, e.g., applying +# a rotation by an angle :math:`\phi+\epsilon` instead of :math:`\phi.` +# +# * **Incoherent noise** is more problematic: it originates from a quantum computer +# becoming entangled with the environment, resulting in mixed states — probability +# distributions over different pure states. Incoherent noise thus leads to outputs that are +# always random, regardless of what basis we measure in. +# +# Mixed states are described by `density matrices +# `__. +# They provide a more general method of describing quantum states that elegantly +# encodes a distribution over pure states in a single mathematical object. +# Mixed states are the most general description of a quantum state, of which pure +# states are a special case. +# +# The purpose of PennyLane's ``default.mixed`` device is to provide native +# support for mixed states and for simulating noisy computations. Let's use ``default.mixed`` to +# simulate a simple circuit for preparing the +# Bell state :math:`|\psi\rangle=\frac{1}{\sqrt{2}}(|00\rangle+|11\rangle).` We ask the QNode to +# return the expectation value of :math:`Z_0\otimes Z_1:` +# +import pennylane as qml +from jax import numpy as np +import jax +import jaxopt + +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +dev = qml.device('default.mixed', wires=2) + +@qml.qnode(dev) +def circuit(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + +print(f"QNode output = {circuit():.4f}") + +###################################################################### +# With a small modification of the circuit we can also ask for the density matrix. In this case, the density matrix is +# equal to :math:`|\psi\rangle\langle\psi|,` +# where :math:`|\psi\rangle=\frac{1}{\sqrt{2}}(|00\rangle + |11\rangle).` + +@qml.qnode(dev) +def density_matrix_circuit(): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + return qml.state() + +matrix = density_matrix_circuit() +print(f"Output density matrix is = \n{np.real(matrix)}") + +###################################################################### +# Incoherent noise is modelled by +# quantum channels. Mathematically, a quantum channel is a linear, completely positive, +# and trace-preserving (`CPTP +# `__) map. A convenient strategy for representing +# quantum channels is to employ `Kraus operators +# `__ +# :math:`\{K_i\}` satisfying the condition +# :math:`\sum_i K_{i}^{\dagger} K_i = I.` For an initial state :math:`\rho,` the output +# state after the action of a channel :math:`\Phi` is: +# +# .. math:: +# +# \Phi(\rho) = \sum_i K_i \rho K_{i}^{\dagger}. +# +# Just like pure states are special cases of mixed states, unitary +# transformations are special cases of quantum channels. Unitary transformations are represented +# by a single Kraus operator, +# the unitary :math:`U,` and they transform a state as +# :math:`U\rho U^\dagger.` +# +# More generally, the action of a quantum channel can be interpreted as applying a +# transformation corresponding to the Kraus operator :math:`K_i` with some associated +# probability. More precisely, the channel applies the +# transformation +# :math:`\frac{1}{p_i}K_i\rho K_i^\dagger` with probability :math:`p_i = \text{Tr}[K_i \rho K_{i}^{ +# \dagger}]`. Quantum +# channels therefore represent a probability distribution over different possible +# transformations on a quantum state. For +# example, consider the bit flip channel. It describes a transformation that flips the state of +# a qubit (applies an X gate) with probability :math:`p` and leaves it unchanged with probability +# :math:`1-p.` Its Kraus operators are +# +# .. math:: +# +# K_0 &= \sqrt{1-p}\begin{pmatrix}1 & 0\\ 0 & 1\end{pmatrix}, \\ +# K_1 &= \sqrt{p}\begin{pmatrix}0 & 1\\ 1 & 0\end{pmatrix}. +# +# This channel can be implemented in PennyLane using the :class:`qml.BitFlip ` +# operation. +# +# Let's see what happens when we simulate this type of noise acting on +# both qubits in the circuit. We'll evaluate the QNode for different bit flip probabilities. +# + + +@qml.qnode(dev) +def bitflip_circuit(p): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.BitFlip(p, wires=0) + qml.BitFlip(p, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + +ps = [0.001, 0.01, 0.1, 0.2] +for p in ps: + print(f"QNode output for bit flip probability {p} is {bitflip_circuit(p):.4f}") + + +###################################################################### +# The circuit behaves quite differently in the presence of noise! This will be familiar to anyone +# that has run an algorithm on quantum hardware. It is also highlights why error +# mitigation and error correction are so important. We can use PennyLane to look under the hood and +# see the output state of the circuit for the largest noise parameter + +print(f"Output state for bit flip probability {p} is \n{np.real(dev.state)}") + +###################################################################### +# Besides the bit flip channel, PennyLane supports several other noisy channels that are commonly +# used to describe experimental imperfections: :class:`~.pennylane.PhaseFlip`, +# :class:`~.pennylane.AmplitudeDamping`, :class:`~.pennylane.GeneralizedAmplitudeDamping`, +# :class:`~.pennylane.PhaseDamping`, and the :class:`~.pennylane.DepolarizingChannel`. You can also +# build your own custom channel using the operation :class:`~.pennylane.QubitChannel` by +# specifying its Kraus operators, or even submit a `pull request +# `__ introducing a new channel. +# +# Let's take a look at another example. The depolarizing channel is a +# generalization of +# the bit flip and phase flip channels, where each of the three possible Pauli errors can be +# applied to a single qubit. Its Kraus operators are given by +# +# .. math:: +# +# K_0 &= \sqrt{1-p}\begin{pmatrix}1 & 0\\ 0 & 1\end{pmatrix}, \\ +# K_1 &= \sqrt{p/3}\begin{pmatrix}0 & 1\\ 1 & 0\end{pmatrix}, \\ +# K_2 &= \sqrt{p/3}\begin{pmatrix}0 & -i\\ i & 0\end{pmatrix}, \\ +# K_3 &= \sqrt{p/3}\begin{pmatrix}1 & 0\\ 0 & -1\end{pmatrix}. +# +# +# A circuit modelling the effect of depolarizing noise in preparing a Bell state is implemented +# below. + + +@qml.qnode(dev) +def depolarizing_circuit(p): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.DepolarizingChannel(p, wires=0) + qml.DepolarizingChannel(p, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + +ps = [0.001, 0.01, 0.1, 0.2] +for p in ps: + print(f"QNode output for depolarizing probability {p} is {depolarizing_circuit(p):.4f}") + +###################################################################### +# As before, the output deviates from the desired value as the amount of +# noise increases. +# Modelling the noise that occurs in real experiments requires careful consideration. +# PennyLane +# offers the flexibility to experiment with different combinations of noisy channels to either mimic +# the performance of quantum algorithms when deployed on real devices, or to explore the effect +# of more general quantum transformations. +# +# Channel gradients +# ----------------- +# +# The ability to compute gradients of any operation is an essential ingredient of +# :doc:`quantum differentiable programming `. +# In PennyLane, it is possible to +# compute gradients of noisy channels and optimize them inside variational circuits. +# PennyLane supports analytical +# gradients for channels whose Kraus operators are proportional to unitary +# matrices [#johannes]_. In other cases, gradients are evaluated using finite differences. +# +# To illustrate this property, we'll consider an elementary example. We aim to learn the noise +# parameters of a circuit in order to reproduce an observed expectation value. So suppose that we +# run the circuit to prepare a Bell state +# on a hardware device and observe that the expectation value of :math:`Z_0\otimes Z_1` is +# not equal to 1 (as would occur with an ideal device), but instead has the value 0.7781. In the +# experiment, it is known that the +# major source of noise is amplitude damping, for example as a result of photon loss. +# Amplitude damping projects a state to :math:`|0\rangle` with probability :math:`p` and +# otherwise leaves it unchanged. It is +# described by the Kraus operators +# +# .. math:: +# +# K_0 = \begin{pmatrix}1 & 0\\ 0 & \sqrt{1-p}\end{pmatrix}, \quad +# K_1 = \begin{pmatrix}0 & \sqrt{p}\\ 0 & 0\end{pmatrix}. +# +# What damping parameter (:math:`p`) explains the experimental outcome? We can answer this question +# by optimizing the channel parameters to reproduce the experimental +# observation! 💪 Since the parameter :math:`p` is a probability, we use a sigmoid function to +# ensure that the trainable parameters give rise to a valid channel parameter, i.e., a number +# between 0 and 1. +# +ev = 0.7781 # observed expectation value + +def sigmoid(x): + return 1/(1+np.exp(-x)) + +@qml.qnode(dev) +def damping_circuit(x): + qml.Hadamard(wires=0) + qml.CNOT(wires=[0, 1]) + qml.AmplitudeDamping(sigmoid(x), wires=0) # p = sigmoid(x) + qml.AmplitudeDamping(sigmoid(x), wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + +###################################################################### +# We optimize the circuit with respect to a simple cost function that attains its minimum when +# the output of the QNode is equal to the experimental value: + + +def cost(x, target): + return (damping_circuit(x) - target)**2 + +###################################################################### +# All that remains is to optimize the parameter. We use a straightforward gradient descent +# method. + +steps = 35 + +gd = jaxopt.GradientDescent(cost, maxiter=steps, tol=1e-5) + +x = np.array(0.01) +res = gd.run(x, ev) + +print(f"QNode output after optimization = {damping_circuit(res.params):.4f}") +print(f"Experimental expectation value = {ev}") +print(f"Optimized noise parameter p = {sigmoid(x.take(0)):.4f}") + +###################################################################### +# Voilà! We've trained the noisy channel to reproduce the experimental observation. 😎 +# +# Developing quantum algorithms that leverage the power of NISQ devices requires serious +# consideration of the effects of noise. With PennyLane, you have access to tools that can +# help you design, simulate, and optimize noisy quantum circuits. We look forward to seeing what +# the quantum community can achieve with them! 🚀 🎉 😸 +# +# +# References +# ---------- +# +# .. [#johannes] +# +# Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert, "A variational toolbox for quantum +# multi-parameter estimation." `arXiv:2006.06303 (2020) `__. +# +# diff --git a/demonstrations_v2/tutorial_noisy_circuits/metadata.json b/demonstrations_v2/tutorial_noisy_circuits/metadata.json new file mode 100644 index 0000000000..65f0ff746e --- /dev/null +++ b/demonstrations_v2/tutorial_noisy_circuits/metadata.json @@ -0,0 +1,47 @@ +{ + "title": "Noisy circuits", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2021-02-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_noisy_circuits.png" + } + ], + "seoDescription": "Learn how to simulate noisy quantum circuits", + "doi": "", + "references": [ + { + "id": "johannes", + "type": "article", + "title": "A variational toolbox for quantum multi-parameter estimation.", + "authors": "Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2006.06303" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_noisy_circuits/requirements.in b/demonstrations_v2/tutorial_noisy_circuits/requirements.in new file mode 100644 index 0000000000..375a0e28cc --- /dev/null +++ b/demonstrations_v2/tutorial_noisy_circuits/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +jaxopt +pennylane diff --git a/demonstrations_v2/tutorial_odegen/demo.py b/demonstrations_v2/tutorial_odegen/demo.py new file mode 100644 index 0000000000..13640a2d52 --- /dev/null +++ b/demonstrations_v2/tutorial_odegen/demo.py @@ -0,0 +1,369 @@ +r"""Evaluating analytic gradients of pulse programs on quantum computers +======================================================================== + + +Are you tired of spending precious quantum resources on computing stochastic gradients of quantum pulse programs? +In this demo we introduce ODEgen, a method to compute analytic gradients of pulse programs on quantum computers with high accuracy at lower cost! +Learn about how ODEgen achieves this and convince yourself of its strengths with a numerical experiment in this demo. + +| + +.. figure:: ../_static/demonstration_assets/odegen/odegen_fig1.png + :align: center + :width: 100% + :alt: Illustration of the ODEgen gradient method for pulse gates, compared to the stochastic parameter-shift rule + :target: javascript:void(0); + + Illustration of ODEgen and stochastic parameter-shift (SPS) for computing gradients of quantum pulse programs. + ODEgen offloads the complexity induced by the time-dynamics to a classical ODE solver, whereas SPS performs + Monte-Carlo sampling in time. + +| + +Introduction +------------ + +Many contemporary quantum computers are operated by steering the qubit state through an +electromagnetic pulse. This can be modeled by means of a time-dependent Hamiltonian + +.. math:: H(\theta, t) = H_\text{drift} + \sum_{j=1}^{N_g} f_j(\theta, t) H_j + +with time-dependent, parametrized pulse envelopes :math:`f_j(\theta, t)` for each of the :math:`N_g` pulse generators :math:`H_j,` and a constant drift term :math:`H_\text{drift}.` +Evolving a quantum state under :math:`H(\theta, t)` for some time T results in a unitary evolution :math:`U(\theta),` to which we refer to as a pulse gate. +A prominent example is superconducting qubit platforms as described in the :doc:`demo on differentiable pulse programming ` +or :doc:`the demo about OQC's Lucy `. + +The parameters :math:`\theta` of :math:`H(\theta, t)` determine the shape and strength of the pulse, +and can be subject to optimization in applications like the variational quantum eigensolver (VQE) [#Meitei]_. +Gradient based optimization on hardware is possible by utilizing the stochastic +parameter-shift (SPS) rule introduced in [#Banchi]_ and [#Leng]_. However, this method is intrinsically stochastic +and may require a large number of shots. + +In this demo, we are going to take a look at the recently introduced ODEgen method for computing analytic gradients +of pulse gates [#Kottmann]_. It utilizes classical +ordinary differential equation (ODE) solvers for computing gradient recipes of quantum pulse programs +that can be executed on hardware. + + +SPS & ODEgen +------------ + +Let us start by deriving both the SPS rule and ODEgen. + +We are interested in cost functions of the form + +.. math:: \mathcal{L}(\theta) = \langle 0 | U(\theta)^\dagger H_\text{obj} U(\theta) | 0 \rangle + +where we compute the expectation value of some objective Hamiltonian :math:`H_\text{obj}` (think e.g. quantum many-body Hamiltonian whose ground state energy we want to estimate). +For simplicity, we assume a sole pulse gate :math:`U(\theta)` generated by a pulse Hamiltonian :math:`H(\theta, t) = H_\text{drift} + \sum_{j=1}^{N_g} f_j(\theta, t) H_j.` +Further, let us assume the so-called pulse generators +:math:`H_j` in :math:`H(\theta, t)` to be Pauli words, which will make SPS rule below a bit more digestible. For more details on the general cases beyond Pauli word generators we refer to the original paper [#Kottmann]_. + +.. note:: + As a quick reminder, let us briefly review the parameter-shift rules for Pauli words. This will help to make the journey through the following a bit easier. + For a loss function :math:`L(\theta) = \langle 0 | U(\theta)^\dagger H_\text{obj} U(\theta)` with a gate :math:`U(\theta) = e^{-i \frac{\theta}{2} H}` generated by + a Pauli word :math:`H` (think e.g. :math:`H = X_0 Y_1`), one can compute the gradient in a hardware compatible manner via the parameter-shift rule + + .. math:: \frac{\partial}{\partial \theta} L(\theta) = \frac{1}{2} \left(L(\theta + \frac{\pi}{2}) - L(\theta - \frac{\pi}{2}) \right). + + For the case of multiple gates generated by different Paulis :math:`H_j` with different parameters :math:`\theta_j`, :math:`U(\theta) = \prod_j e^{-i \frac{\theta_j}{2} H_j},` + the rule still has the same form + + .. math:: \frac{\partial}{\partial \theta_j} L(\theta) = \frac{1}{2} \left(L(\theta + e_j\frac{\pi}{2}) - L(\theta - e_j \frac{\pi}{2}) \right). + + Here, the shifts of :math:`\pm \frac{\pi}{2}` are only applied to differentiated parameters via the basis vectors :math:`e_0 = (1,0,0,..),` :math:`e_1 = (0,1,0,..)` etc. + Pulse gates generated from :math:`H(\theta, t)` introduce two complications. First, the generators of the gates are now time-dependent. Second, the generator consists of a sum of non-commuting terms. These complications + are addressed in the SPS rule and ODEgen method, described below. + +SPS +~~~ + +We can compute the gradient of :math:`\mathcal{L}` by means of the stochastic parameter-shift rule via + +.. math:: \frac{\partial}{\partial \theta_j} \mathcal{L}(\theta) = \int_0^T d\tau \sum_{i=1}^{N_g} \frac{\partial f_i(\theta, \tau)}{\partial \theta_j} \left(\tilde{\mathcal{L}}^+_i(\tau) - \tilde{\mathcal{L}}^-_i(\tau) \right). + +The :math:`\tilde{\mathcal{L}}^\pm_i(\tau)` are the original expectation values but under +shifted evolutions :math:`U(T, \tau) e^{-i\left(\pm\frac{\pi}{4}\right)H_i} U(\tau, 0).` +As an exception, we explicitly state the evolution times and use the notation :math:`U(t_1, t_0)` +to indicate the evolution is going from time :math:`t_0` to :math:`t_1.` +One important point to stress is that in the case of the SPS rule, the circuit structure changes, whereas in the regular +parameter-shift rule the *same* circuits are executed with shifted parameters. + + +In practice, the integral is approximated via Monte Carlo integration + +.. math:: \frac{\partial}{\partial \theta_j} \mathcal{L}(\theta) \approx \frac{1}{N_s} \sum_{\tau \in \mathcal{U}([0, T])} \sum_{i=1}^{N_g} \frac{\partial f_i(\theta, \tau)}{\partial \theta_j} \left(\tilde{\mathcal{L}}^+_i(\tau) - \tilde{\mathcal{L}}^-_i(\tau) \right) + +where the :math:`\tau \in \mathcal{U}([0, T])` are sampled uniformly between :math:`0` and :math:`T,` and :math:`N_s` is the number +of Monte Carlo samples for the integration. The larger the number of samples, the better the approximation. +This comes at the cost of more quantum resources :math:`\mathcal{R},` +expressed as the number of distinct expectation values executed on the quantum device, + +.. math:: + + \mathcal{R}_\text{SPS} = 2 N_s N_g. + +ODEgen +~~~~~~ + +In contrast, the recently introduced ODEgen method [#Kottmann]_ has the advantage that it circumvents the need for Monte Carlo sampling by off-loading the complexity +introduced by the time-dynamics to a differentiable ODE solver. + +The first step of ODEgen is writing the derivative of a pulse unitary :math:`U(\theta)` as + +.. math:: \frac{\partial}{\partial \theta_j} U(\theta) = -i U(\theta) \mathcal{H}_j + +with a so-called effective generator :math:`\mathcal{H}_j` for each of the parameters :math:`\theta_j` (note that these are *not* the pulse generators :math:`H_j` in :math:`H(\theta, t)`). +We can obtain :math:`\mathcal{H}_j` classically by computing both :math:`\frac{\partial}{\partial \theta_j} U(\theta)` +and :math:`U(\theta)` in a forward and backward pass through the ODE solver. We already use such a solver in PennyLane for simulating pulses in :class:`~.pennylane.pulse.ParametrizedEvolution.` +Here, we use it to generate parameter-shift rules that can be executed on hardware. + +The next step is to decompose each effective generator into a basis the quantum computer can understand, and, in particular, can execute. +We choose the typical Pauli basis and write + +.. math:: \mathcal{H}_j = \sum_\ell \omega_\ell^{(j)} P_\ell + +for Pauli words :math:`P_\ell` (e.g. :math:`P_\ell = X_0 Y_1` for some :math:`\ell`) with coefficients :math:`\omega_\ell^{(j)}.` With this decomposition, we can write the gradient of the cost function in terms of parameter-shift rules +that can be executed on a quantum computer: + +.. math:: + \frac{\partial \mathcal{L}}{\partial \theta_j} = \langle 0 | \left[U(\theta)^\dagger H_\text{obj} U(\theta), -i\mathcal{H}_j \right] | 0 \rangle \\ + = \sum_\ell 2 \omega_\ell^{(j)} \langle 0 | \left[U(\theta)^\dagger H_\text{obj} U(\theta), -\frac{i}{2} P_\ell \right] |0\rangle \\ + = \sum_\ell 2 \omega_\ell^{(j)} \frac{d}{dx} \left[ \langle 0 | U(\theta)^\dagger e^{i\frac{x}{2} P_\ell} H_\text{obj} e^{-i\frac{x}{2} P_\ell} U(\theta)|0\rangle \right]_{x=0}. + +In particular, we can identify + +.. math:: L_\ell(x) = \langle 0 | U(\theta)^\dagger e^{i\frac{x}{2} P_\ell} H_\text{obj} e^{-i\frac{x}{2} P_\ell} U(\theta)|0\rangle + +as an expectation value shifted by the dummy variable :math:`x,` whose derivative is given by the standard two-term parameter-shift rule (see note above or `this derivation `_). +Overall, we have + +.. math:: \frac{\partial \mathcal{L}}{\partial \theta_j} = \sum_\ell \omega_\ell^{(j)} \left(L_\ell\left(\frac{\pi}{2}\right) - L_\ell\left(-\frac{\pi}{2}\right) \right). + +The quantum resources for ODEgen are :math:`2` executions for each non-zero Pauli term :math:`\omega_\ell^{(j)} P_\ell` (i.e. non-zero for any :math:`j` for a particular :math:`\ell`) of the decomposition. +This number is at most :math:`4^n-1` for :math:`n` qubits. A better upper bound is given by the dimension of the dynamical Lie algebra (DLA) of the pulse Hamiltonian. That is, the number of linearly independent operators +that can be generated from nested commutators of the pulse generators and the drift term. An example would be a pulse Hamiltonian composed of terms :math:`X_0,` :math:`X_1` and :math:`Z_0 Z_1.` A basis for the DLA of +this pulse Hamiltonian is given by :math:`\{X_0, X_1, Z_0Z_1, Y_0Y_1, Y_0Z_0, Z_0Y_0\}.` This tells us that only those terms can be non-zero in a decomposition of any effective generator from gates generated by such a pulse Hamiltonian. + +Overall, ODEgen is well-suited for pulse Hamiltonians that act effectively on few qubits - as is the case for superconducting qubit and ion trap qubit architectures - or yield a small DLA. + + + + +Numerical experiment +-------------------- + +We want to put ODEgen and SPS head to head in a variational quantum algorithm with the same available quantum resources. +For this, we are going to perform the variational quantum eigensolver (VQE) on the Heisenberg model Hamiltonian + +.. math:: H_\text{obj} = X_0 X_1 + Y_0 Y_1 + Z_0 Z_1 + +for two qubits. The ground state of this Hamiltonian is the maximally entangled singlet state +:math:`|\phi^- \rangle = (|01\rangle - |10\rangle)/\sqrt{2}` with ground state energy :math:`-3.` +Let us define it in PennyLane and also import some libraries that we are going to need for this demo. + +""" +import pennylane as qml +import numpy as np +import jax.numpy as jnp +import jax + +import optax + +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") + +import matplotlib.pyplot as plt + +H_obj = qml.sum(qml.PauliX(0) @ qml.PauliX(1), qml.PauliY(0) @ qml.PauliY(1), qml.PauliZ(0) @ qml.PauliZ(1)) +E_exact = -3. +wires = H_obj.wires + +############################################################################## +# We are going to consider a system of transmon qubits described by the Hamiltonian +# +# .. math:: H(\theta, t) = - \sum_i \frac{\omega_i}{2} Z_i + \sum_i \Omega_i(t) \sin(\nu_i t + \phi_i(t)) Y_i + \sum_{q, p \in \mathcal{C}} \frac{g_{qp}}{2} (X_i X_p + Y_i Y_p). +# +# The first term describes the single qubits with frequencies :math:`\omega_i.` +# The second term describes the driving with drive amplitudes :math:`\Omega_i,` drive frequencies :math:`\nu_i` and phases :math:`\phi_i.` +# You can check out our :doc:`recent demo on driving qubits on OQC's Lucy ` if +# you want to learn more about the details of controlling transmon qubits. +# The third term describes the coupling between neighboring qubits. We only have two qubits and a simple topology of +# :math:`\mathcal{C} = \{(0, 1)\},` hence only one coupling constant :math:`g_{01}.` +# The coupling is necessary to generate entanglement, which is achieved with cross-resonant driving in fixed-coupling +# transmon systems, as is the case here. +# +# We will use realistic parameters for the transmons, taken from the `coaxmon design paper `_ [#Patterson]_ +# (this is the blue-print for the transmon qubits in OQC's Lucy that you can :doc:`access on a pulse level in PennyLane `). +# In order to prepare the singlet ground state, we will perform a cross-resonance pulse, i.e. driving one qubit at its coupled neighbor's +# frequency for entanglement generation (see [#Patterson]_ or [#Krantz]_) while simultaneously driving the other qubit on resonance. +# We choose a gate time of :math:`100 \text{ ns}.` We will use a piecewise constant function :func:`~pennylane.pulse.pwc` to parametrize both +# the amplitude :math:`\Omega_i(t)` and the phase :math:`\phi_i(t)` in time, with ``t_bins = 10`` time bins to allow for enough flexibility in the evolution. + +T_CR = 100. # gate time for two qubit drive (cross resonance) +qubit_freq = 2*np.pi*np.array([6.509, 5.963]) +g = 2 * np.pi * 0.0123 + +def drive_field(T, wdrive): + """Set the evolution time ``T`` and drive frequency ``wdrive``""" + def wrapped(p, t): + # The first len(p) values of the trainable params p characterize the pwc function + amp = qml.pulse.pwc(T)(p[:len(p)//2], t) + phi = qml.pulse.pwc(T)(p[len(p)//2:], t) + return amp * jnp.sin(wdrive * t + phi) + + return wrapped + +H_pulse = qml.dot(-0.5*qubit_freq, [qml.PauliZ(i) for i in wires]) +H_pulse += g * (qml.PauliX(wires[0]) @ qml.PauliX(wires[1]) + qml.PauliY(wires[0]) @ qml.PauliY(wires[1])) + +H_pulse += drive_field(T_CR, qubit_freq[0]) * qml.PauliY(wires[0]) # on-resonance driving of qubit 0 +H_pulse += drive_field(T_CR, qubit_freq[0]) * qml.PauliY(wires[1]) # off-resonance driving of qubit 1 + +############################################################################## +# We can now define the cost function that computes the expectation value of +# the Heisenberg Hamiltonian after evolving the state with the parametrized pulse Hamiltonian. +# We then define the two separate qnodes with ODEgen and SPS as their differentiation methods, respectively. + +def qnode0(params): + qml.evolve(H_pulse)((params[0], params[1]), t=T_CR) + return qml.expval(H_obj) + +dev = qml.device("default.qubit", wires=range(2)) + +qnode_jax = qml.QNode(qnode0, dev, interface="jax") +value_and_grad_jax = jax.jit(jax.value_and_grad(qnode_jax)) + +num_split_times = 8 +qnode_sps = qml.QNode(qnode0, dev, interface="jax", diff_method=qml.gradients.stoch_pulse_grad, use_broadcasting=True, num_split_times=num_split_times) +value_and_grad_sps = jax.value_and_grad(qnode_sps) + +qnode_odegen = qml.QNode(qnode0, dev, interface="jax", diff_method=qml.gradients.pulse_odegen) +value_and_grad_odegen = jax.value_and_grad(qnode_odegen) + +############################################################################## +# We note that for as long as we use a simulator, there is naturally no difference between the gradients obtained +# from direct backpropagation and using ODEgen. + +tbins = 10 # number of time bins per pulse +n_param_batch = 2 # number of parameter batches + +x = jnp.ones((n_param_batch, tbins * 2)) + +res0, grad0 = value_and_grad_jax(x) +res1, grad1 = value_and_grad_odegen(x) +np.allclose(res0, res1, atol=1e-3), np.allclose(grad0, grad1, atol=1e-3) + +############################################################################## +# This allows us to use direct backpropagation in this demo, which is always faster in simulation. +# We now have all the ingredients to run VQE with ODEgen and SPS. We define the following standard +# optimization loop and run it from the same random initial values +# with ODEgen and SPS gradients. + +def run_opt(value_and_grad, theta, n_epochs=120, lr=0.1, b1=0.9, b2=0.999): + + optimizer = optax.adam(learning_rate=lr, b1=b1, b2=b2) + opt_state = optimizer.init(theta) + + energy = np.zeros(n_epochs) + gradients = [] + thetas = [] + + @jax.jit + def partial_step(grad_circuit, opt_state, theta): + # SPS gradients don't allow for full jitting of the update step + updates, opt_state = optimizer.update(grad_circuit, opt_state) + theta = optax.apply_updates(theta, updates) + + return opt_state, theta + + ## Optimization loop + for n in range(n_epochs): + val, grad_circuit = value_and_grad(theta) + opt_state, theta = partial_step(grad_circuit, opt_state, theta) + + energy[n] = val + gradients.append(grad_circuit) + thetas.append(theta) + + return thetas, energy + +key = jax.random.PRNGKey(0) +theta0 = jax.random.normal(key, shape=(n_param_batch, tbins * 2)) + +thetaf_odegen, energy_odegen = run_opt(value_and_grad_jax, theta0) +thetaf_sps, energy_sps = run_opt(value_and_grad_sps, theta0) + +plt.plot(np.array(energy_sps) - E_exact, label="SPS") +plt.plot(np.array(energy_odegen) - E_exact, label="ODEgen") +plt.legend() +plt.yscale("log") +plt.ylabel("$E(\\theta) - E_{{FCI}}$") +plt.xlabel("epochs") +plt.show() + + +############################################################################## +# We see that with analytic gradients (ODEgen), we can reach the ground state energy within 100 epochs, whereas with SPS gradients we cannot find the path +# towards the minimum due to the stochasticity of the gradient estimates. Note that both optimizations start from the same (random) initial point. +# This picture solidifies when repeating this procedure for multiple runs from different random initializations, as was demonstrated in [#Kottmann]_. +# +# We also want to make sure that this is a fair comparison in terms of quantum resources. In the case of ODEgen, we maximally have :math:`\mathcal{R}_\text{ODEgen} = 2 (4^n - 1) = 30` expectation values. +# For SPS we have :math:`2 N_g N_s = 32` (due to :math:`N_g = 2` and :math:`N_s=8` time samples per gradient that we chose in ``num_split_times`` above). Thus, overall, we require fewer +# quantum resources for ODEgen gradients while achieving better performance. +# +# Conclusion +# ---------- +# +# We introduced ODEgen for computing analytic gradients of pulse gates and showcased its advantages in simulation with an example using VQE. +# The method is particularly well-suited for quantum computing architectures that build complexity from few-qubit gates, as is the case +# for superconducting qubit architectures. +# We invite you to play with ODEgen yourself. Note that this feature is amenable to hardware and you can compute gradients on OQC's Lucy via PennyLane. +# We show you how to connect to Lucy and run pulse gates in our :doc:`recent demo `. +# Running VQE using ODEgen on hardware has recently been demonstrated in [#Kottmann]_ and you can directly find `the code here `_. + + + +############################################################################## +# +# References +# ---------- +# +# .. [#Kottmann] +# +# Korbinian Kottmann, Nathan Killoran +# "Evaluating analytic gradients of pulse programs on quantum computers" +# `arXiv:2309.16756 `__, 2023. +# +# .. [#Krantz] +# +# Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver +# "A Quantum Engineer's Guide to Superconducting Qubits" +# `arXiv:1904.06560 `__, 2019. +# +# .. [#Meitei] +# +# Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall +# "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE" +# `arXiv:2008.04302 `__, 2019. +# +# .. [#Banchi] +# +# Leonardo Banchi, Gavin E. Crooks +# "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule" +# `arXiv:2005.10299 `__, 2020 +# +# .. [#Leng] +# +# Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu +# "Differentiable Analog Quantum Computing for Optimization and Control" +# `arXiv:2210.15812 `__, 2022 +# +# .. [#Patterson] +# +# A. D. Patterson, J. Rahamim, T. Tsunoda, P. Spring, S. Jebari, K. Ratter, M. Mergenthaler, G. Tancredi, B. Vlastakis, M. Esposito, P. J. Leek +# "Calibration of the cross-resonance two-qubit gate between directly-coupled transmons" +# `arXiv:1905.05670 `__, 2019 +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_odegen/metadata.json b/demonstrations_v2/tutorial_odegen/metadata.json new file mode 100644 index 0000000000..50546e7299 --- /dev/null +++ b/demonstrations_v2/tutorial_odegen/metadata.json @@ -0,0 +1,118 @@ +{ + "title": "Evaluating analytic gradients of pulse programs on quantum computers", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-12-12T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Computing", + "Quantum Hardware" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/odegen/thumbnail_odegen.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_odegen.png" + } + ], + "seoDescription": "Learn how to compute analytic gradients of pulse programs on quantum hardware", + "doi": "", + "references": [ + { + "id": "Kottmann", + "type": "preprint", + "title": "Evaluating analytic gradients of pulse programs on quantum computers", + "authors": "Korbinian Kottmann, Nathan Killoran", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.16756", + "url": "https://arxiv.org/abs/2309.16756" + }, + { + "id": "Krantz", + "type": "article", + "title": "Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver", + "authors": "A Quantum Engineer's Guide to Superconducting Qubits", + "year": "2019", + "publisher": "", + "journal": "J. Magn. Reson. 172, 296-305", + "doi": "10.1063/1.5089550", + "url": "https://arxiv.org/abs/1904.06560" + }, + { + "id": "Mitei", + "type": "preprint", + "title": "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE", + "authors": "Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2020", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2008.04302", + "url": "https://arxiv.org/abs/2008.04302" + }, + { + "id": "Banchi", + "type": "article", + "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule", + "authors": "Leonardo Banchi, Gavin E. Crooks", + "year": "2020", + "publisher": "", + "journal": "Quantum", + "doi": "10.22331/q-2021-01-25-386", + "url": "https://arxiv.org/abs/2005.10299", + "preprint": "https://arxiv.org/abs/2005.10299" + }, + { + "id": "Leng", + "type": "preprint", + "title": "Differentiable Analog Quantum Computing for Optimization and Control", + "authors": "Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2210.15812", + "url": "https://arxiv.org/abs/2210.15812", + "preprint": "https://arxiv.org/abs/2210.15812" + }, + { + "id": "Patterson", + "type": "preprint", + "title": "Calibration of the cross-resonance two-qubit gate between directly-coupled transmons", + "authors": "A. D. Patterson, J. Rahamim, T. Tsunoda, P. Spring, S. Jebari, K. Ratter, M. Mergenthaler, G. Tancredi, B. Vlastakis, M. Esposito, P. J. Leek", + "year": "2019", + "publisher": "", + "journal": "Phys. Rev. Applied 12, 064013", + "doi": "10.1103/PhysRevApplied.12.064013", + "url": "https://doi.org/10.1103/PhysRevApplied.12.064013", + "preprint": "https://arxiv.org/abs/1905.05670" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "oqc_pulse", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 0.5 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_odegen/requirements.in b/demonstrations_v2/tutorial_odegen/requirements.in new file mode 100644 index 0000000000..543f5dbfb8 --- /dev/null +++ b/demonstrations_v2/tutorial_odegen/requirements.in @@ -0,0 +1,6 @@ +jax +jaxlib +matplotlib +numpy +optax +pennylane diff --git a/demonstrations_v2/tutorial_optimal_control/demo.py b/demonstrations_v2/tutorial_optimal_control/demo.py new file mode 100644 index 0000000000..1bcd01d8b8 --- /dev/null +++ b/demonstrations_v2/tutorial_optimal_control/demo.py @@ -0,0 +1,843 @@ +r""" +Optimal control for gate compilation +==================================== + +.. meta:: + :property="og:description": Optimize pulse programs to obtain digital gates. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_optimal_control.png + +.. related:: + + tutorial_pulse_programming101 Introduction to pulse programming in PennyLane + tutorial_neutral_atoms Introduction to neutral atom quantum computers + ahs_aquila Pulse programming on Rydberg atom hardware + + + +Today, quantum computations are largely phrased in the language of quantum circuits, +composed of digital quantum gates. +However, most quantum hardware does not come with such digital +gates as elementary native operations. +Instead, the hardware allows us to play sequences of analog electromagnetic pulses, +for example by shining laser pulses on trapped ions or Rydberg atoms, or by sending microwave +pulses onto superconducting qubit cavities. +These pulses need to be calibrated to produce the desired digital gates, and in +this tutorial we will be concerned with exactly this task. + +For this, we will parametrize a pulse sequence, which leads to a whole *space* +of possible sequences. Then we optimize the pulse parameters in order to +find a configuration in this space that behaves as closely to the target gate +as possible. +More concretely, we will optimize simple pulse programs on two and three qubits to +obtain a CNOT and a Toffoli gate. +This training of control parameters to achieve a specific time +evolution is a standard task in the field of *quantum optimal control*. + +| + +.. figure:: ../_static/demonstration_assets/optimal_control/OptimalControl_control_quantum_systems.png + :align: center + :width: 100% + :alt: Illustration of a metal hand crafting a CNOT gate, using qubit systems + :target: javascript:void(0); + +| + +For an introduction, see +:doc:`the demo on differentiable pulse programming ` +in PennyLane. +Instead of optimizing pulses to yield digital quantum gates, +we may use them directly to solve optimization problems, as is also showcased in this +introductory demo. If you are interested in specific hardware pulses, take a look at +:doc:`an introduction to neutral-atom quantum computing ` +or :doc:`the tutorial on the QuEra Aquila device `, which treat pulse +programming with Rydberg atoms. + +Quantum optimal control +----------------------- + +The overarching goal of quantum optimal control is to find the best way to steer +a microscopical physical system such that its dynamics matches a desired behaviour. +The meaning of "best" and "desired behaviour" will depend on the specific +task, and it is important to specify the underlying assumptions and constraints on +the system controls in order to make the problem statement well-defined. +Once we specified all these details, optimal control theory is concerned with +questions like +"How close can the system get to modelling the desired behaviour?", +"How can we find the best (sequence of) control parameters to obtain the desired behaviour?", +or +"What is the shortest time in which the system can reach a specific state, given some +initial state?" (controlling at the so-called quantum speed limit) [#CanevaMurphy09]_. + +In this tutorial, we consider the control of few-qubit systems through pulse sequences, +with the goal to produce a given target, namely a digital gate, to the highest possible +precision. +To do this, we will choose an ansatz for the pulse sequence that contains +free parameters and define a profit function that quantifies the similarity between +the qubit operation and the target gate. +Then, we maximize this function by optimizing the pulse parameters until we +find the desired gate to a sufficient precision--or can no longer improve on the +approximation we found. +For the training phase, we will make use of fully-differentiable classical simulations +of the qubit dynamics, allowing us to make use of backpropagation -- an efficient +differentiation technique widely used in machine learning -- and gradient-based +optimization. +At the same time we attempt to find pulse shapes and control parameters that are +(to some degree) realistically feasible, including bounded +pulse amplitudes and rates of change of the amplitudes. + +Tutorials that use other techniques are available, for example, for the +`open-source quantum toolbox QuTiP `__. + +Gate calibration via pulse programming +-------------------------------------- + +Here, we briefly discuss the general setup of pulse programs that we will use for our +optimal control application. For more details, you may peruse the related +tutorials focusing on pulse programming. + +Consider a quantum system comprised of :math:`n` two-level systems, or qubits, described +by a Hamiltonian + +.. math:: + + H(\boldsymbol{p}, t) = H_d + \sum_{i=1}^K f_i(\boldsymbol{p_i}, t) H_i. + +As we can see, :math:`H` depends on the time :math:`t` and on a set of control parameters +:math:`\boldsymbol{p},` which is composed of one parameter vector :math:`\boldsymbol{p_i}` +per term. Both :math:`t` and :math:`\boldsymbol{p}` +feed into functions :math:`f_i` that return scalar coefficients +for the (constant) Hamiltonian terms :math:`H_i.` In addition, there is a constant drift +Hamiltonian :math:`H_d.` +We will assume that the Hamiltonian :math:`H` fully describes the system of interest and, +in particular, we do not consider sources of noise in the system, such as leakage, dephasing, +or crosstalk, i.e. the accidental interaction with other parts of a larger, surrounding system. + +The time evolution of the state of our quantum system will be described +by the Schrödinger equation associated with :math:`H.` +However, for our purposes it will be more useful to consider the full unitary evolution that +the Hamiltonian causes, independently of the initial state. This way, we can compare it to +the digital target gate without iterating over different input and output states. +The Schrödinger equation dictates the behaviour of the evolution operator :math:`U` to be + +.. math:: + + \frac{d}{dt} U(\boldsymbol{p}, t) = -i H(\boldsymbol{p}, t) U(\boldsymbol{p}, t), + +where we implicitly fixed the initial time of the evolution to :math:`t_0=0.` +It is possible to simulate the dynamics of sufficiently small quantum systems on +a classical computer by solving the ordinary differential equation (ODE) above numerically. +For a fixed pulse duration :math:`T` and given control parameters :math:`\boldsymbol{p},` +a numerical ODE solver computes the matrix :math:`U(\boldsymbol{p}, T).` + +How can we tell whether the evolution of the qubit system is close to the digital gate +we aim to produce? We will need a measure of similarity, or fidelity. + +In this tutorial we will describe the similarity of two unitary matrices :math:`U` and +:math:`V` on :math:`n` qubits with a fidelity function: + +.. math:: + + f(U,V) = \frac{1}{2^n}\big|\operatorname{tr}(U^\dagger V)\big|. + +It is similar to an overlap measure obtained from the +`Frobenius norm `__ +but it allows us to ignore differences in the global phase. +Note that fidelity is often used to compare quantum states rather than gates, +and that noise often plays a role in this context. Here we only consider unitary +gates. + +We can maximize the fidelity function above to train the pulse parameters. For this +purpose we write + +.. math:: + + F(\boldsymbol{p}) \equiv f(U_\text{target}, U(\boldsymbol{p}, T)). + +Here :math:`U_\text{target}` is the unitary matrix of the gate that we want to compile. +We consider the total duration :math:`T` as a fixed constraint to the optimization +problem and therefore we do not denote it as a free parameter of :math:`F.` + +| + +.. figure:: ../_static/demonstration_assets/optimal_control/OptimalControl_distance.png + :align: center + :width: 100% + :alt: Illustration of a mountain with a path drawn from the ground to the peak, with markers for a pulse unitary and a CNOT gate + :target: javascript:void(0); + +| + +We can then maximize the fidelity :math:`F,` for example, using gradient-based +optimization algorithms like Adam [#KingmaBa14]_. +But how do we obtain the gradient of a function that requires us to run an ODE solver +to obtain its value? We are in luck! The implementation of pulse programming in PennyLane is +fully differentiable via backpropagation thanks to its backend based on the machine +learning library `JAX `__. +This enables us to optimize the gate sequences using efficiently computed gradients +(provided the target gate is not too large). + +Before we climb mount fidelity for particular example gates, let's briefly talk about +the pulse shape that we will use. + +Smooth rectangle pulses +----------------------- + +Let's look at a building block that we will use a lot: smoothened rectangular pulses. +We start with a simple rectangular pulse + +.. math:: + + R_\infty(t, (\Omega, t_0, t_1)) = \Omega \Theta(t-t_0) \Theta(t_1-t) + +where :math:`\Omega` is the amplitude, :math:`t_0` and :math:`t_1` are the start and end +times of the pulse, and :math:`\Theta(t)` is the +`Heaviside step function `__ +which is one for :math:`t\geq 0` and zero otherwise. +The trainable parameters of this pulse are the amplitude and the start/end times. + +There are two main issues with :math:`R_\infty` for our purposes: + +#. The Heaviside step function is not differentiable with respect + to the times :math:`t_0` and :math:`t_1` in the conventional sense (but + only if we were to consider distributions in addition to functions), and in + particular we cannot differentiate the resulting :math:`U(\boldsymbol{p},T)` + within the automatic differentiation framework provided by JAX. + +#. The instantaneous change in the amplitude will not be realizable in practice. + In reality, the pulses describe some electromagnetic control field that can only + be changed at a bounded rate and in a smooth manner. :math:`R_\infty` is not + only not smooth, it is not even continuous. So we should consider smooth + pulses with a bounded rate of change instead. + +We can solve both these issues by smoothening the rectangular pulse: +We simply replace the step functions above by a smooth variant, namely by sigmoid functions: + +.. math:: + + R_k(t, (\Omega, t_0, t_1)) &= \Omega S(t-t_0, k) S(t_1-t, k)\\ + S(t, k) &= (1+\exp(-k t))^{-1}. + +We introduced an additional parameter, :math:`k,` that controls the steepness of the sigmoid +functions and can be adapted to the constraints posed by hardware on the maximal rate of change. +In contrast to :math:`R_\infty,` its sister :math:`R_k` is smooth in all three arguments +:math:`\Omega`, :math:`t_0` and :math:`t_1,` and training these three parameters with +automatic differentiation will not be a problem. + +| + +.. figure:: ../_static/demonstration_assets/optimal_control/OptimalControl_Smoother_Rectangles.png + :align: center + :width: 100% + :alt: Sketch of converting a rectangular pulse shape into a smoothened rectangular pulse shape + :target: javascript:void(0); + +| + +Let's implement the smooth rectangle function using JAX's ``numpy``. We +directly implement the product of the two sigmoids in the function ``sigmoid_rectangle``: + +.. math:: + + R_k(t, (\Omega, t_0, t_1), k)= + \Omega [1+\exp(-k (t-t_0))+\exp(-k (t_1-t))+\exp(-k(t_1-t_0))]^{-1}. +""" + +import jax +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) # Use float64 precision +jax.config.update("jax_platform_name", "cpu") # Disables a warning regarding device choice + + +def sigmoid_rectangle(t, Omega, t_0, t_1, k=1.0): + """Smooth-rectangle pulse between t_0 and t_1, with amplitude Omega.""" + return Omega / ( + 1 + jnp.exp(-k * (t - t_0)) + jnp.exp(-k * (t_1 - t)) + jnp.exp(-k * (t_1 - t_0)) + ) + + +############################################################################# +# Let's look at a rectangular pulse and its smoothened sister, for a number of +# different smoothness parameters: + +import matplotlib.pyplot as plt + +t = jnp.linspace(0, 6, 1000) +t_0, t_1 = (1.3, 5.4) +amplitude = 2.3 +ks = [5, 10, 50] +rect = amplitude * jnp.heaviside(t - t_0, 1.0) * jnp.heaviside(t_1 - t, 1.0) + +for k in ks: + smooth = sigmoid_rectangle(t, amplitude, t_0, t_1, k) + plt.plot(t, smooth, label=f"Smooth rectangle $R_k$, $k={k}$") +plt.plot(t, rect, label="Rectangle $R_{\\infty}$, $k\\to\\infty$") +plt.legend(bbox_to_anchor=(0.6, 0.05), loc="lower center") +plt.xlabel("time $t$") +plt.ylabel("Pulse function") +plt.show() + +############################################################################# +# We see that for very large :math:`k,` the smooth rectangle becomes practically +# indistinguishable from the original rectangle function :math:`R_\infty.` This means +# that we can consider the smooth :math:`R_k` a *generalization* of the pulse shape, +# rather than a restriction. +# +# In the examples below, we will use a pulse ansatz :math:`S_k` that sums multiple smooth rectangles +# :math:`R_k` with the same value for :math:`k` but individual start/end times +# :math:`t_{0/1}` and amplitudes :math:`\Omega.` +# With this nicely trainable pulse shape in our hands, we now turn to the first gate +# calibration task. +# +# Pulse ansatz for CNOT calibration +# --------------------------------- +# +# In this first example we will tune a two-qubit pulse to produce a standard CNOT gate. +# +# We start by choosing a system Hamiltonian. +# It contains the drift term :math:`H_d = Z_0 + Z_1,` i.e. a Pauli :math:`Z` operator +# acting on each qubit, with a constant unit amplitude. +# The parametrized part uses five generating terms: Pauli :math:`Z` acting on the +# first qubit (:math:`Z_0`), all three Pauli operators acting on the second qubit +# (:math:`X_1, Y_1, Z_1`) and a single interaction term :math:`Z_0X_1,` resembling an +# abstract cross-resonance driving term. For all coefficient functions we choose +# the same function, :math:`f_i=S_k\ \forall i` (see the section above), but with distinct +# parameters. That is, our Hamiltonian is +# +# .. math:: +# +# H(\boldsymbol{p}, t) = \underset{H_d}{\underbrace{Z_0 + Z_1}} +# + S_k(\boldsymbol{p_1}, t) Z_0 +# + S_k(\boldsymbol{p_2}, t) X_1 +# + S_k(\boldsymbol{p_3}, t) Y_1 +# + S_k(\boldsymbol{p_4}, t) Z_1 +# + \underset{\text{interaction}}{\underbrace{S_k(\boldsymbol{p_5}, t) Z_0X_1}} +# +# Due to this choice, the :math:`Z_0` term +# commutes with all other terms, including the drift term, and can be considered a +# correction of the drive term to obtain the correct action on the first qubit. +# Although the interaction term was chosen to resemble a typical interaction in a +# superconducting cross resonance drive, this Hamiltonian remains a toy model. +# Realistic hardware Hamiltonians may impose additional constraints or provide +# fewer controls, and we do not consider the unit systems of such real-world systems +# here. +# +# The idea behind using the sum of smooth rectangles function for the parametrization +# is the following: +# Many methods in quantum optimal control work with discretized pulse shapes that keep +# the pulse envelope constant for short time bins. This approach leads to a large number +# of parameters that need to be trained, and it requires us to manually enforce that the +# values do not differ by too much between neighbouring time bins. +# The smooth rectangles introduced above have a limited rate of change by design, and +# the number of parameters is much smaller than in generic discretization approaches. +# Each coefficient function :math:`S_k` sums :math:`P` smooth rectangles +# :math:`R_k` with individual amplitudes and start and end times. Overall, this leads to +# :math:`n=5\cdot 3\cdot P=15P` parameters in :math:`H.` +# In this and the next example, we chose :math:`P` heuristically. +# +# Before we define this Hamiltonian, we implement the sum over multiple +# ``sigmoid_rectangle`` functions, including two normalization steps. +# First, we normalize the start and end times of the rectangles to the interval +# :math:`[\epsilon, T-\epsilon],` which makes sure that the pulse amplitudes are +# close to zero at :math:`t=0` and :math:`t=T.` Without this step, we might be +# tuning the pulses to be turned on (off) instantaneously at the beginning (end) of the +# sequence, negating our effort on the pulse shape itself not to vary too quickly. +# Second, we normalize the final output value to the interval +# :math:`(-\Omega_\text{max}, \Omega_\text{max}),` which +# allows us to bound the maximal amplitudes of the pulses to a realizable range while +# maintaining differentiability. +# +# For the normalization steps, we define a ``sigmoid`` and a ``normalize`` function. +# The first is a straightforward implementation of :math:`R_k` whereas the second +# uses the ``sigmoid`` function to normalize real numbers to the interval :math:`(-1, 1).` + + +def sigmoid(t, k=1.0): + """Sigmoid function with steepness parameter k.""" + return 1 / (1 + jnp.exp(-k * t)) + + +def normalize(t, k=1.0): + """Smoothly normalize a real input value to the interval (-1, 1) using 'sigmoid' + with steepness parameter k.""" + return 2 * sigmoid(t, k) - 1.0 + + +def smooth_rectangles(params, t, k=2.0, max_amp=1.0, eps=0.0, T=1.0): + """Compute the sum of P smooth-rectangle pulses and normalize their + starting and ending times, as well as the total output amplitude. + + Args: + params (tensor_like): Amplitudes and start and end times for the rectangles, + in the order '[amp_1, ... amp_P, t_{1, 0}, t_{1, 1}, ... t_{P, 0}, t_{P, 1}]'. + t (float): Time at which to evaluate the pulse function. + k (float): Steepness of the sigmoid functions that delimit the rectangles + max_amp (float): Maximal amplitude of the rectangles. The output will be normalized + to the interval '(-max_amp, max_amp)'. + eps (float): Margin to the beginning and end of the pulse sequence within which the + start and end times of the individual rectangles need to lie. + T (float): Total duration of the pulse. + + Returns: + float: Value of sum of smooth-rectangle pulses at 't' for the given parameters. + """ + P = len(params) // 3 + # Split amplitudes from times + amps, times = jnp.split(params, [P]) + # Normalize times to be sufficiently far away from 0 and T + times = sigmoid(times - T / 2, k=1.0) * (T - 2 * eps) + eps + # Extract the start and end times of single rectangles + times = jnp.reshape(times, (-1, 2)) + # Sum products of sigmoids (unit rectangles), rescaled with the amplitudes + rectangles = [sigmoid_rectangle(t, amp, *ts, k) for amp, ts in zip(amps, times)] + value = jnp.sum(jnp.array([rectangles])) + # Normalize the output value to be in [-max_amp, max_amp] with standard steepness + return max_amp * normalize(value, k=1.0) + + +############################################################################# +# Let's look at this function for some example parameters, with the same steepness +# parameter :math:`k=20` for all rectangles in the sum: + +from functools import partial + +T = 2 * jnp.pi # Total pulse sequence time +k = 20.0 # Steepness parameter +max_amp = 1.0 # Maximal amplitude \Omega_{max} +eps = 0.1 * T # Margin for the start/end times of the rectangles +# Bind hyperparameters to the smooth_rectangles function +S_k = partial(smooth_rectangles, k=k, max_amp=max_amp, eps=eps, T=T) + +# Set some arbitrary amplitudes and times +amps = jnp.array([0.4, -0.2, 1.9, -2.0]) # Four amplitudes +times = jnp.array([0.2, 0.6, 1.2, 1.8, 2.1, 3.7, 4.9, 5.9]) # Four pairs of start/end times +params = jnp.hstack([amps, times]) # Amplitudes and times form the trainable parameters + +plot_times = jnp.linspace(0, T, 300) +plot_S_k = [S_k(params, t) for t in plot_times] + +plt.plot(plot_times, plot_S_k) +ax = plt.gca() +ax.set(xlabel="Time t", ylabel=r"Pulse function $S_k(p, t)$") +plt.show() + +############################################################################# +# Note that the rectangles are rather round for these generic parameters. +# The optimized parameters in the training workflows below will lead to more +# sharply defined pulses that resemble rectangles more closely. The amplitude normalization +# step in ``smooth_rectangles`` enables us to produce them in a differentiable manner, +# as was our goal with introducing :math:`R_k.` +# Also note that the normalization of the final output value is not a simple clipping +# step, but again a smooth function. As a consequence, the amplitudes ``1.9`` and ``-2.`` +# in the example above, which are not in the interval ``[-1, 1]``, +# are not set to ``1`` and ``-1`` but take smaller absolute values. +# Finally, also note that the start and end times of the smooth rectangles are being +# normalized as well, in order to not end up too close to the boundaries of the +# total time interval. While this makes the pulse times differ from the input times, +# our pulse training will automatically consider this normalization step so that +# it has no major consequences for us. +# +# Using this function, we now may build the parametrized pulse Hamiltonian and the +# fidelity function discussed above. We make use of just-in-time (JIT) compilation, +# which will make the first execution of ``profit`` and ``grad`` slower, but speed +# up the subsequent executions a lot. For optimization workflows of small-scale +# functions, this almost always pays off. + +import pennylane as qml + +X, Y, Z = qml.PauliX, qml.PauliY, qml.PauliZ + +num_wires = 2 +# Hamiltonian terms of the drift and parametrized parts of H +ops_H_d = [Z(0), Z(1)] +ops_param = [Z(0), X(1), Y(1), Z(1), Z(0) @ X(1)] +# Coefficients: 1 for drift Hamiltonian and smooth rectangles for parametrized part +coeffs = [1.0, 1.0] + [S_k for op in ops_param] +# Build H +H = qml.dot(coeffs, ops_H_d + ops_param) +# Set tolerances for the ODE solver +atol = rtol = 1e-10 + +# Target unitary is CNOT. We get its matrix and note that we do not need the dagger +# because CNOT is Hermitian. +target = qml.CNOT([0, 1]).matrix() +target_name = "CNOT" +print(f"Our target unitary is a {target_name} gate, with matrix\n{target.astype('int')}") + + +def pulse_matrix(params): + """Compute the unitary time evolution matrix of the pulse for given parameters.""" + return qml.evolve(H, atol=atol, rtol=rtol)(params, T).matrix() + + +@jax.jit +def profit(params): + """Compute the fidelity function for given parameters.""" + # Compute the unitary time evolution of the pulse Hamiltonian + op_mat = pulse_matrix(params) + # Compute the fidelity between the target and the pulse evolution + return jnp.abs(jnp.trace(target.conj().T @ op_mat)) / 2**num_wires + + +grad = jax.jit(jax.grad(profit)) + +############################################################################# +# For the arbitrary parameters from above, of course we get a rather arbitrary unitary +# time evolution, which does not match the CNOT at all: + +params = [params] * len(ops_param) +arb_mat = jnp.round(pulse_matrix(params), 4) +arb_profit = profit(params) +print( + f"The arbitrarily chosen parameters yield the unitary\n{arb_mat}\n" + f"which has a fidelity of {arb_profit:.6f}." +) + +############################################################################# +# Before we can start the optimization, we require initial parameters. +# We set small alternating amplitudes and evenly distributed start and end times +# for :math:`P=3` smoothened rectangles. This choice leads to +# a total of :math:`15P=45` parameters in the pulse sequence. + +P = 3 # Number of rectangles P +# Initial parameters for the start and end times of the rectangles +times = [jnp.linspace(eps, T - eps, P * 2) for op in ops_param] +# All initial parameters: small alternating amplitudes and times +params = [jnp.hstack([jnp.array([0.1 * (-1) ** i for i in range(P)]), time]) for time in times] + +############################################################################# +# Now we are all set up to train the parameters of the pulse sequence to produce +# our target gate, the CNOT. We will use the Adam optimizer [#KingmaBa14]_, implemented in the +# `optax `__ +# library to our convenience. We keep track of the optimization via a list that contains +# the parameters and fidelity values. Then we can plot the fidelity across the optimization. +# As we will run a second optimization later on, we code up the optimizer run as a function. +# This function will report on the optimization progress and duration, and it will plot +# the trajectory of the profit function during the optimization. + +import time +import optax + + +def run_adam(profit_fn, grad_fn, params, learning_rate, num_steps): + start_time = time.process_time() + # Initialize the Adam optimizer + optimizer = optax.adam(learning_rate, b1=0.97) + opt_state = optimizer.init(params) + # Initialize a memory buffer for the optimization + hist = [(params.copy(), profit_fn(params))] + for step in range(num_steps): + g = grad_fn(params) + updates, opt_state = optimizer.update(g, opt_state, params) + + params = optax.apply_updates(params, updates) + hist.append([params, c := profit_fn(params)]) + if (step + 1) % (num_steps // 10) == 0: + print(f"Step {step+1:4d}: {c:.6f}") + _, profit_hist = list(zip(*hist)) + plt.plot(list(range(num_steps + 1)), profit_hist) + ax = plt.gca() + ax.set(xlabel="Iteration", ylabel=f"Fidelity $F(p)$") + plt.show() + end_time = time.process_time() + print(f"The optimization took {end_time-start_time:.1f} (CPU) seconds.") + return hist + + +learning_rate = -0.2 # negative learning rate leads to maximization +num_steps = 500 +hist = run_adam(profit, grad, params, learning_rate, num_steps) + +############################################################################# +# As we can see, Adam steadily increases the fidelity, bringing the pulse program +# closer and closer to the target unitary. On its way, the optimizer produces a mild +# oscillating behaviour. The precision to which the optimization can produce the +# target unitary depends on the expressivity of the pulses we use, +# but also on the precision with which we run the ODE solver and the hyperparameters +# of the optimizer. +# +# Let's pick those parameters with the largest fidelity we observed during +# the training and take a look at the pulses we found. We again prepare a function +# that plots the pulse sequence, which we can reuse later on. +# For the single-qubit terms, we encode their qubit in the color and the type of Pauli +# operator in the line style of the plotted line. + +colors = {0: "#70CEFF", 1: "#C756B2", 2: "#FDC357"} +dashes = {"X": [10, 0], "Y": [2, 2, 10, 2], "Z": [6, 2]} + +def plot_optimal_pulses(hist, pulse_fn, ops, T, target_name): + _, profit_hist = list(zip(*hist)) + fig, axs = plt.subplots(2, 1, figsize=(10, 9), gridspec_kw={"hspace": 0.0}, sharex=True) + + # Pick optimal parameters from the buffer of all observed profit values + max_params, max_profit = hist[jnp.argmax(jnp.array(profit_hist))] + plot_times = jnp.linspace(0, T, 300) + # Iterate over pulse parameters and parametrized operators + for i, (p, op) in enumerate(zip(max_params, ops)): + # Create label, and pick correct axis + label = str(op) + dash = dashes[label[0]] + ax = axs[0] if len(op.wires) == 1 else axs[1] + + # Set color according to qubit the term acts on + col = colors[op.wires[0]] + # Plot the pulse + values = [pulse_fn(p, t) for t in plot_times] + ax.plot(plot_times, values, label=label, dashes=dash, color=col) + ax.legend() + # Set legends and axis descriptions + axs[0].legend(title="Single-qubit terms", ncol=int(jnp.sqrt(len(ops)))) + axs[1].legend(title="Two-qubit terms") + title = f"{target_name}, Fidelity={max_profit:.6f}" + axs[0].set(ylabel=r"Pulse function $f(p, t)$", title=title) + axs[1].set(xlabel="Time $t$", ylabel=r"Pulse function $S_k(p, t)$") + plt.show() + + +plot_optimal_pulses(hist, S_k, ops_param, T, target_name) + +############################################################################# +# We observe that a single rectangular pulse is sufficient for some of the +# generating terms in the Hamiltonian whereas others end up at rather intricate +# pulse shapes. We see that their shape is closer to +# actual rectangles now, in particular for those with a saturated amplitude. +# +# The final fidelity tells us that we achieved our goal of finding a pulse +# sequence that implements a unitary close to a CNOT gate. +# It could be optimized further, for example by running the optimization for more +# training iterations, by tuning the optimizer further to avoid oscillations, +# or by increasing the precision with which we run the ODE solver. +# This likely would also allow to reduce the total duration of the pulse. +# +# Pulse sequence for Toffoli +# -------------------------- +# +# The second example we consider is the compilation of a Toffoli--or CCNOT--gate. +# We reuse most of the workflow from above and only change the pulse Hamiltonian as +# well as a few hyperparameters. +# +# In particular, the Hamiltonian uses the drift term :math:`H_d=Z_0+Z_1+Z_2` +# and the generators are all single-qubit Pauli operators on all three qubits, together +# with the interaction generators :math:`Z_0X_1, Z_1X_2, Z_2X_0.` Again, +# all parametrized terms use the coefficient function ``smooth_rectangles``. +# We allow for a longer pulse duration of :math:`3\pi` and five smooth rectangles in +# each pulse shape. +# +# In summary, we use nine single-qubit generators and three two-qubit generators, with +# five rectangles in each pulse shape and each rectangle being given by an amplitude and +# a start and end time. The pulse sequence thus has :math:`(9+3)\cdot 5\cdot 3=180` +# parameters. + +num_wires = 3 +# New pulse hyperparameters +T = 3 * jnp.pi # Longer total duration +eps = 0.1 * T +P = 5 # More rectangles in sum: P=5 +S_k = partial(smooth_rectangles, k=k, max_amp=max_amp, eps=eps, T=T) + +# Hamiltonian terms of the drift and parametrized parts of H +ops_H_d = [Z(0), Z(1), Z(2)] +ops_param = [pauli_op(w) for pauli_op in [X, Y, Z] for w in range(num_wires)] +ops_param += [Z(0) @ X(1), Z(1) @ X(2), Z(2) @ X(0)] + +# Coefficients: 1. for drift Hamiltonian and smooth rectangles for parametrized part +coeffs = [1.0, 1.0, 1.0] + [S_k for op in ops_param] +# Build H +H = qml.dot(coeffs, ops_H_d + ops_param) +# Set tolerances for the ODE solver +atol = rtol = 1e-10 + +# Target unitary is Toffoli. We get its matrix and note that we do not need the dagger +# because Toffoli is Hermitian and unitary. +target = qml.Toffoli([0, 1, 2]).matrix() +target_name = "Toffoli" +print(f"Our target unitary is a {target_name} gate, with matrix\n{target.astype('int')}") + + +def pulse_matrix(params): + """Compute the unitary time evolution matrix of the pulse for given parameters.""" + return qml.evolve(H, atol=atol, rtol=rtol)(params, T).matrix() + + +@jax.jit +def profit(params): + """Compute the fidelity function for given parameters.""" + # Compute the unitary time evolution of the pulse Hamiltonian + op_mat = pulse_matrix(params) + # Compute the fidelity between the target and the pulse evolution + return jnp.abs(jnp.trace(target.conj().T @ op_mat)) / 2**num_wires + + +grad = jax.jit(jax.grad(profit)) + +############################################################################# +# We create initial parameters similar to the above but allow for a larger number +# of :math:`1200` optimization steps and use a reduced learning rate (by absolute value) +# in the optimization with Adam. Our ``run_adam`` function from above comes +# in handy and also provides an overview of the optimization process in the +# produced plot. + +times = [jnp.linspace(eps, T - eps, P * 2) for op in ops_param] +params = [jnp.hstack([jnp.array([0.2 * (-1) ** i for i in range(P)]), time]) for time in times] + +num_steps = 1200 +learning_rate = -2e-3 +hist = run_adam(profit, grad, params, learning_rate, num_steps) + +params_hist, profit_hist = list(zip(*hist)) +max_params = params_hist[jnp.argmax(jnp.array(profit_hist))] + +############################################################################# +# This looks promising: Adam maximized the fidelity successfully and we thus compiled +# a pulse sequence that implements a Toffoli gate! +# To inspect how close the compiled pulse sequence is to the Toffoli gate, +# we can apply it to an exemplary quantum state, say :math:`|110\rangle,` +# and investigate the returned probabilities. A perfect Toffoli gate would +# flip the third qubit, returning a probability of one in the last entry +# and zeros elsewhere. + +dev = qml.device("default.qubit", wires=3) + + +@qml.qnode(dev, interface="jax") +def node(params): + # Prepare |110> + qml.PauliX(0) + qml.PauliX(1) + # Apply pulse sequence + qml.evolve(H, atol=atol, rtol=rtol)(params, T) + # Return quantum state + return qml.probs() + + +probs = node(max_params) +print(f"The state |110> is mapped to the probability vector\n{jnp.round(probs, 6)}.") + +############################################################################# +# We see that the returned probabilities are close to the expected vector. The +# last entry is close to one, the others are almost zero. +# However, there are more possible inputs to the gate, and we hardly want to +# stare at eight probability vectors to understand the quality of the compiled +# pulse sequence. Instead, let's plot the transition amplitudes with which our +# compiled pulse sequence maps computational basis vectors to each other. +# We include the complex phase of the amplitudes in the color of the bars. + +import matplotlib as mpl + +fig = plt.figure(figsize=(10, 8)) +ax = fig.add_subplot(111, projection="3d") + +dim = 8 +x = jnp.tile(jnp.arange(dim), dim) - 0.27 # Input state indices +y = jnp.repeat(jnp.arange(dim), dim) - 0.36 # Output state indices +mat = pulse_matrix(max_params).ravel() # Pulse matrix, reshaped to be a sequence of values +phases = jnp.angle(mat) # Complex phases +color_norm = mpl.colors.Normalize(-jnp.pi, jnp.pi) +bar_colors = mpl.cm.turbo(color_norm(phases)) +# Barplot with x, y positions, bottom, width, depth and height values for bars +ax.bar3d(x, y, 0.0, 0.6, 0.6, jnp.abs(mat).ravel(), shade=True, color=bar_colors) +# Specify a few visual attributes of the axes object +ax.set( + xticks=list(range(dim)), + yticks=list(range(dim)), + xticklabels=[f"|{bin(i)[2:].rjust(3, '0')}>" for i in range(dim)], + yticklabels=[f"|{bin(i)[2:].rjust(3, '0')}>" for i in range(dim)], + zticks=[0.2 * i for i in range(6)], + xlabel="Input state", + ylabel="Output state", +) +# Add axes for the colorbar +cax = plt.axes([0.85, 0.15, 0.02, 0.62]) +sc = mpl.cm.ScalarMappable(cmap=mpl.cm.turbo, norm=color_norm) +sc.set_array([]) +# Plot colorbar +plt.colorbar(sc, cax=cax, ax=ax) +plt.show() + +############################################################################# +# The transition amplitudes are as expected, except for very small deviations. +# All computational basis states are mapped to themselves, but the last two are +# swapped. The color of the entries close to one does not correspond to a phase +# of zero. However, the fact that they have the same color tells us that this +# deviation is a global phase, so that the pulse sequence is equivalent to +# the Toffoli gate. +# Let's also look at the pulse sequence itself: + +plot_optimal_pulses(hist, S_k, ops_param, T, target_name) + +############################################################################# +# As we can see, the optimized smooth rectangles do not fill out the time at maximal +# amplitudes. This means that we probably can find shorter pulse sequences with +# larger amplitudes that produce a Toffoli with the +# same fidelity. If you are interested, take a shot at it and try to +# optimize the sequence with respect to the number of generators and pulse duration! +# +# Conclusion +# ---------- +# +# In this tutorial we calibrated a two-qubit and a three-qubit pulse sequence +# to obtain a CNOT and a Toffoli gate, respectively. For this, we used smooth +# rectangular pulse shapes together with toy pulse Hamiltonians, and obtained +# very good approximations to the target gates. +# Thanks to JAX, just-in-time (JIT) compiling and the PennyLane ``pulse`` +# module, training the pulse sequences was simple to implement and fast to run. +# +# There are many different techniques in quantum optimal control that can be +# used to calibrate pulse sequences, some of which include gradient-based +# training. A widely-used technique called GRAPE [#KhanejaReiss05]_ +# makes use of discretized pulses, which leads to a large number of free parameters +# to be optimized with gradient ascent. +# The technique shown here reduces the parameter count significantly +# and provides smooth, bounded shapes by definition. +# +# Yet another method that does *not* use gradient-based optimization is +# the chopped random-basis quantum optimization (CRAB) algorithm [#DoriaCalarco11]_. +# It uses a different parametrization altogether, exploiting randomized basis functions +# for the pulse envelopes. +# +# While setting up the application examples, we accommodated for +# some requirements of realistic hardware, like smooth pulse shapes with bounded +# maximal amplitudes and bounded rates of change, and we tried to use only few +# interaction terms between qubits. However, it is important to note +# that the shown optimization remains a toy model for calibration of +# quantum hardware. We did not take into account the interaction terms +# or pulse shapes available on realistic devices and their control electronics. +# We also did not consider a unit system tied to real devices, and we +# ignored noise, which plays a very important role in today's quantum devices +# and in quantum optimal control. +# We leave the extension to real-world pulse Hamiltonians and noisy systems +# to a future tutorial--or maybe your work? +# +# References +# ------------- +# +# .. [#CanevaMurphy09] +# +# T. Caneva, M. Murphy, T. Calarco, R. Fazio, S. Montangero, V. Giovannetti and G. Santoro +# "Optimal Control at the Quantum Speed Limit" +# `Phys. Rev. Lett. 103, 240501 `__, +# `arxiv:0902.4193 `__, 2009 +# +# .. [#KingmaBa14] +# +# D. Kingma and J. Ba +# "Adam: A method for Stochastic Optimization" +# `arxiv:1412.6980 `__, 2014 +# +# .. [#KhanejaReiss05] +# +# N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbrüggen, S.J. Glaser +# "Optimal Control of Coupled Spin Dynamics: +# Design of NMR Pulse Sequences by Gradient Ascent Algorithms" +# `J. Magn. Reson. 172, 296-305 `__, +# 2005 +# +# .. [#DoriaCalarco11] +# +# P. Doria, T. Calarco and S. Montangero +# "Optimal Control Technique for Many-Body Quantum Dynamics" +# `Phys. Rev. Lett. 106, 190501 `__, +# `arxiv:1003.3750 `__, 2011 +# +# diff --git a/demonstrations_v2/tutorial_optimal_control/metadata.json b/demonstrations_v2/tutorial_optimal_control/metadata.json new file mode 100644 index 0000000000..463d39ff84 --- /dev/null +++ b/demonstrations_v2/tutorial_optimal_control/metadata.json @@ -0,0 +1,95 @@ +{ + "title": "Optimal control for gate compilation", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2023-08-08T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Computing", + "Quantum Hardware" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/optimal_control/thumbnail_tutorial_optimal_control.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_optimal_control.png" + } + ], + "seoDescription": "Learn how to optimize pulse programs to obtain digital gates", + "doi": "", + "references": [ + { + "id": "KingmaBa14", + "type": "preprint", + "title": "Adam: A method for Stochastic Optimization", + "authors": "D. Kingma and J. Ba", + "year": "2014", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2303.11355", + "url": "https://arxiv.org/abs/2303.11355" + }, + { + "id": "KhanejaReiss05", + "type": "article", + "title": "Optimal Control of Coupled Spin Dynamics: Design of NMR Pulse Sequences by Gradient Ascent Algorithms", + "authors": "N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbr\u00fcggen and S.J. Glaser", + "year": "2005", + "publisher": "", + "journal": "J. Magn. Reson. 172, 296-305", + "doi": "10.1016/j.jmr.2004.11.004", + "url": "https://www.ch.nat.tum.de/fileadmin/w00bzu/ocnmr/pdf/94_GRAPE_JMR_05_.pdf" + }, + { + "id": "CanevaMurphy09", + "type": "article", + "title": "Optimal Control at the Quantum Speed Limit", + "authors": "T. Caneva, M. Murphy, T. Calarco, R. Fazio, S. Montangero, V. Giovannetti and G. Santoro", + "year": "2009", + "publisher": "", + "journal": "Phys. Rev. Lett. 103, 240501", + "doi": "10.1103/PhysRevLett.103.240501", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.103.240501", + "preprint": "https://arxiv.org/abs/0902.4193" + }, + { + "id": "DoriaCalarco11", + "type": "article", + "title": "Optimal Control Technique for Many-Body Quantum Dynamics", + "authors": "P. Doria, T. Calarco and S. Montangero", + "year": "2011", + "publisher": "", + "journal": "Phys. Rev. Lett. 106, 190501", + "doi": "10.1103/PhysRevLett.106.190501", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.106.190501", + "preprint": "https://arxiv.org/abs/1003.3750" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_neutral_atoms", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_optimal_control/requirements.in b/demonstrations_v2/tutorial_optimal_control/requirements.in new file mode 100644 index 0000000000..0aae374bda --- /dev/null +++ b/demonstrations_v2/tutorial_optimal_control/requirements.in @@ -0,0 +1,5 @@ +jax +jaxlib +matplotlib +optax +pennylane diff --git a/demonstrations_v2/tutorial_pasqal/demo.py b/demonstrations_v2/tutorial_pasqal/demo.py new file mode 100644 index 0000000000..aa86406724 --- /dev/null +++ b/demonstrations_v2/tutorial_pasqal/demo.py @@ -0,0 +1,359 @@ +r""" +Quantum computation with neutral atoms +====================================== + +.. meta:: + :property="og:description": Neutral atom quantum devices allow you to place + qubits within interesting three-dimensional configurations. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pasqal_thumbnail.png + +.. related:: + ahs_aquila Pulse programming on neutral atom hardware + +*Author: Nathan Killoran — Posted: 13 October 2020. Last updated: 21 January 2021.* + +Quantum computing architectures come in many flavours: superconducting qubits, ion traps, +photonics, silicon, and more. One very interesting physical substrate is *neutral atoms*. These +quantum devices have some basic similarities to ion traps. Ion-trap devices make use of atoms +that have an imbalance between protons (positively charged) and electrons (negatively charged). +Neutral atoms, on the other hand, have an equal number of protons and electrons. + +In neutral-atom systems, the individual atoms can be easily programmed into various two- or +three-dimensional configurations. Like ion-trap systems, individual qubits can be encoded into +the energy levels of the atoms. The qubits thus retain the three-dimensional geometry of their +host atoms. Qubits that are nearby in space can be programmed to interact with one another +via two-qubit gates. This opens up some tantalizing possibilities for exotic quantum-computing +circuit topologies. + +.. figure:: https://raw.githubusercontent.com/lhenriet/cirq-pasqal/fc4f9c7792a8737fde76d4c05828aa538be8452e/pasqal-tutorials/files/eiffel_tower.png + :align: center + :width: 50% + + .. + + Neutral atoms (green dots) arranged in various configurations. These atoms can be + used to encode qubits and carry out quantum computations. Image originally from [#barredo2017]_. + +The startup `Pasqal `_ is one of the companies working to bring +neutral-atom quantum computing devices to the world. To support this new class of devices, +Pasqal has contributed some new features to the quantum software library `Cirq `_. + +In this demo, we will use PennyLane, Cirq, and TensorFlow to show off the unique abilities of +neutral atom devices, leveraging them to make a variational quantum circuit which has a +very unique topology: *the Eiffel tower*. Specifically, we will build a simple toy +circuit whose qubits are arranged like the Eiffel tower. The girders between +the points on the tower will represent two-qubit gates, with the final output of our +variational circuit coming at the very peak of the tower. + +Let's get to it! + +.. note:: + + To run this demo locally, you will need to install `Cirq + `_, (version >= 0.9.1), and the + `PennyLane-cirq plugin `_ + (version >= 0.13). You will also need to download a copy of the data, which + is available `here + `_. + +""" + +############################################################################## +# Building the Eiffel tower +# ------------------------- +# +# Our first step will be to load and visualize the data for the Eiffel tower +# configuration, which was generously provided by the team at Pasqal. +# (If running locally, the line below should be updated with the local +# path where you have saved the downloaded data). + +import numpy as np +coords = np.loadtxt("../_static/demonstration_assets/pasqal/Eiffel_tower_data.dat") +xs = coords[:,0] +ys = coords[:,1] +zs = coords[:,2] + +import matplotlib.pyplot as plt +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g',alpha=0.3) +plt.show() + +############################################################################## +# This dataset contains 126 points. Each point represents a distinct +# neutral-atom qubit. Simulating this many qubits would be outside the +# reach of Cirq's built-in simulators, so for this demo, +# we will pare down to just 9 points, evenly spaced around the tower. +# These are highlighted in red below. +# + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') +ax.scatter(xs, ys, zs, c='g', alpha=0.3) + +base_mask = [3, 7, 11, 15] +qubit_mask = [48, 51, 60, 63, 96, 97, 98, 99, 125] +input_coords = coords[base_mask] # we'll need this for a plot later +qubit_coords = coords[qubit_mask] + +subset_xs = qubit_coords[:, 0] +subset_ys = qubit_coords[:, 1] +subset_zs = qubit_coords[:, 2] +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0) +plt.show() + +############################################################################## +# Converting to Cirq qubits +# ------------------------- +# +# Our next step will be to convert these datapoints into objects that +# Cirq understands as qubits. For neutral-atom devices in Cirq, we can use the +# ``ThreeDQubit`` class, which carries information about the three-dimensional +# arrangement of the qubits. +# +# Now, neutral-atom devices come with some physical restrictions. +# Specifically, in a particular three-dimensional configuration, qubits that +# are too distant from one another can't easily interact. Instead, there is +# a notion of a *control radius;* any atoms which are within the system's +# control radius can interact with one another. Qubits separated by a +# distance larger than the control radius cannot interact. +# +# In order to allow our Eiffel tower qubits to interact with +# one another more easily, we will artificially scale some dimensions +# when placing the atoms. + +from cirq_pasqal import ThreeDQubit +xy_scale = 1.5 +z_scale = 0.75 +qubits = [ThreeDQubit(xy_scale * x, xy_scale * y, z_scale * z) + for x, y, z in qubit_coords] + +############################################################################## +# To simulate a neutral-atom quantum computation, we can use the +# ``"cirq.pasqal"`` device, available via the +# `PennyLane-Cirq plugin `_. +# We will need to provide this device with the ``ThreeDQubit`` object that we created +# above. We also need to instantiate the device with a fixed control radius. + +import pennylane as qml + +num_wires = len(qubits) +control_radius = 32.4 +dev = qml.device("cirq.pasqal", control_radius=control_radius, + qubits=qubits, wires=num_wires) + +############################################################################## +# Creating a quantum circuit +# -------------------------- +# +# We will now make a variational circuit out of the Eiffel tower configuration +# from above. Each of the 9 qubits we are using can be thought of +# as a single wire in a quantum circuit. We will cause these qubits to interact by applying +# a sequence of two-qubit gates. Specifically, the circuit consists of several +# stages: +# +# i. Input classical data is converted into quantum information at the first +# (lowest) vertical level of qubits. In this example, our classical data +# will be simple bit strings, which we can embed by using single-qubit +# bit flips (a simple +# `data-embedding `_ +# strategy). +# +# ii. For each corner of the tower, CNOTs are enacted between the first- +# and second-level qubits. +# +# iii. All qubits from the second level interact with a single "peak" qubit +# using a parametrized controlled-rotation operation. The free parameters +# of our variational circuit enter here. +# +# The output of our circuit is determined via a Pauli-Z measurement on +# the final "peak" qubit. +# +# That's a few things to keep track of, so let's show the circuit via a +# three-dimensional image: + +first_lvl_coords = qubit_coords[:4] +second_lvl_coords = qubit_coords[4:8] +peak_coords = qubit_coords[8] + +input_x, input_y, input_z = [input_coords[:, idx] + for idx in range(3)] +second_x, second_y, second_z = [first_lvl_coords[:, idx] + for idx in range(3)] +third_x, third_y, third_z = [second_lvl_coords[:, idx] + for idx in range(3)] +peak_x, peak_y, peak_z = peak_coords + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') +ax.view_init(40, 15) +fig.subplots_adjust(left=0, right=1, bottom=0, top=1) +ax.set_xlim(-20, 20) +ax.set_ylim(-20, 20) +ax.set_zlim(-40, 10) +plt.axis('off') + +ax.scatter(xs, ys, zs, c='g', alpha=0.3) +ax.scatter(subset_xs, subset_ys, subset_zs, c='r', alpha=1.0); + +# Two-qubit gates between second and third levels +for corner in range(4): + ax.plot(xs=[second_x[corner], third_x[corner]], + ys=[second_y[corner], third_y[corner]], + zs=[second_z[corner], third_z[corner]], + c='k'); + +# Two-qubit gates between third level and peak +for corner in range(4): + ax.plot(xs=[third_x[corner], peak_x], + ys=[third_y[corner], peak_y], + zs=[third_z[corner], peak_z], + c='k'); + +# Additional lines to guide the eye +for corner in range(4): + ax.plot(xs=[input_x[corner], second_x[corner]], + ys=[input_y[corner], second_y[corner]], + zs=[input_z[corner], second_z[corner]], + c='grey', linestyle='--'); + ax.plot(xs=[second_x[corner], second_x[(corner + 1) % 4]], + ys=[second_y[corner], second_y[(corner + 1) % 4]], + zs=[second_z[corner], second_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + ax.plot(xs=[third_x[corner], third_x[(corner + 1) % 4]], + ys=[third_y[corner], third_y[(corner + 1) % 4]], + zs=[third_z[corner], third_z[(corner + 1) % 4]], + c='grey', linestyle='--'); + +plt.show() + +############################################################################## +# In this figure, the red dots represent the specific qubits we will use in +# our circuit (the green dots are not used in this demo). +# +# The solid black lines indicate two-qubit gates between these qubits. +# The dashed grey lines are meant to guide the eye, but could also be +# used to make a more complex model by adding further two-qubit gates. +# +# Classical data is loaded in at the bottom qubits (the "tower legs") and +# the final measurement result is read out from the top "peak" qubit. +# The order of gate execution proceeds vertically from bottom to top, and +# clockwise at each level. +# +# The code below creates this particular quantum circuit configuration in +# PennyLane: + +peak_qubit = 8 + +def controlled_rotation(phi, wires): + qml.RY(phi, wires=wires[1]) + qml.CNOT(wires=wires) + qml.RY(-phi, wires=wires[1]) + qml.CNOT(wires=wires) + +@qml.qnode(dev, interface="tf") +def circuit(weights, data): + + # Input classical data loaded into qubits at second level + for idx in range(4): + if data[idx]: + qml.PauliX(wires=idx) + + # Interact qubits from second and third levels + for idx in range(4): + qml.CNOT(wires=[idx, idx + 4]) + + # Interact qubits from third level with peak using parameterized gates + for idx, wire in enumerate(range(4, 8)): + controlled_rotation(weights[idx], wires=[wire, peak_qubit]) + + return qml.expval(qml.PauliZ(wires=peak_qubit)) + + +############################################################################## +# Training the circuit +# -------------------- +# +# Let's now leverage this variational circuit to tackle a toy classification +# problem. +# For the purposes of this demo, we will consider a very simple classifier: +# +# * if the first input qubit is in the state :math:`\mid 0 \rangle,` the model +# should make the prediction "0", and +# +# * if the first input qubit is in the state :math:`\mid 1 \rangle,` the model +# should predict "1" (independent of the states of all other qubits). +# +# In other words, the idealized trained model should learn an +# identity transformation between the first qubit and the final one, while +# ignoring the states of all other qubits. +# +# With this goal in mind, we can create a basic cost function. This cost +# function randomly samples possible 4-bit input bitstrings, and compares +# the circuit's output with the value of the first bit. The other bits +# can be thought of as noise that we don't want our model to learn. + + +import tensorflow as tf +np.random.seed(143) +init_weights = np.pi * np.random.rand(4) + +weights = tf.Variable(init_weights, dtype=tf.float64) + +data = np.random.randint(0, 2, size=4) + +def cost(): + data = np.random.randint(0, 2, size=4) + label = data[0] + output = (-circuit(weights, data) + 1) / 2 + return tf.abs(output - label) ** 2 + +opt = tf.keras.optimizers.Adam(learning_rate=0.1) + +for step in range(100): + opt.minimize(cost, [weights]) + if step % 5 == 0: + print("Step {}: cost={}".format(step, cost())) + +print("Final cost value: {}".format(cost())) + +############################################################################## +# Success! The circuit has learned to transfer the state of the first qubit +# to the state of the last qubit, while ignoring the state of all other input +# qubits. +# +# The programmable three-dimensional configurations of neutral-atom quantum +# computers provide a special tool that is hard to replicate in other +# platforms. Could the physical +# arrangement of qubits, in particular the third dimension, be leveraged to +# make quantum algorithms more sparse or efficient? Could neutral-atom +# systems—with their unique programmability of the geometry—allow us to +# rapidly prototype and experiment with new circuit topologies? What +# possibilities could this open up for quantum computing, quantum chemistry, +# or quantum machine learning? +# + +############################################################################## +# References +# ---------- +# +# .. [#barredo2017] +# +# Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys. +# "Synthetic three-dimensional atomic structures assembled atom by atom." +# `arXiv:1712.02727 +# `__, 2017. +# +# diff --git a/demonstrations_v2/tutorial_pasqal/metadata.json b/demonstrations_v2/tutorial_pasqal/metadata.json new file mode 100644 index 0000000000..5f80463445 --- /dev/null +++ b/demonstrations_v2/tutorial_pasqal/metadata.json @@ -0,0 +1,37 @@ +{ + "title": "Quantum computation with neutral atoms", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-10-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QC_neutral_atoms.png" + } + ], + "seoDescription": "Neutral atom quantum devices allow you to place qubits within interesting three-dimensional configurations.", + "doi": "", + "references": [ + { + "id": "barredo2017", + "type": "article", + "title": "Synthetic three-dimensional atomic structures assembled atom by atom.", + "authors": "Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys", + "year": "2017", + "journal": "", + "url": "https://arxiv.org/abs/1712.02727" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_pasqal/pasqal/Eiffel_tower_data.dat b/demonstrations_v2/tutorial_pasqal/pasqal/Eiffel_tower_data.dat new file mode 100644 index 0000000000..b8e0242a57 --- /dev/null +++ b/demonstrations_v2/tutorial_pasqal/pasqal/Eiffel_tower_data.dat @@ -0,0 +1,126 @@ +-1.352365149746594142e+01 3.623651497465944615e+00 -3.825000000000000000e+01 +-1.975903440471390127e+01 2.365149746594530367e-02 -3.825000000000000000e+01 +-1.712365149746594284e+01 9.859034404713902688e+00 -3.825000000000000000e+01 +-6.259034404713903932e+00 -2.335903440471390269e+01 -3.825000000000000000e+01 +-3.623651497465944615e+00 -1.352365149746594142e+01 -3.825000000000000000e+01 +-9.859034404713902688e+00 -1.712365149746594284e+01 -3.825000000000000000e+01 +-2.365149746594530367e-02 -1.975903440471390127e+01 -3.825000000000000000e+01 +2.335903440471390269e+01 -6.259034404713903932e+00 -3.825000000000000000e+01 +3.623651497465944615e+00 1.352365149746594142e+01 -3.825000000000000000e+01 +9.859034404713902688e+00 1.712365149746594284e+01 -3.825000000000000000e+01 +2.365149746594530367e-02 1.975903440471390127e+01 -3.825000000000000000e+01 +6.259034404713903932e+00 2.335903440471390269e+01 -3.825000000000000000e+01 +1.352365149746594142e+01 -3.623651497465944615e+00 -3.825000000000000000e+01 +1.975903440471390127e+01 -2.365149746594530367e-02 -3.825000000000000000e+01 +1.712365149746594284e+01 -9.859034404713902688e+00 -3.825000000000000000e+01 +-2.335903440471390269e+01 6.259034404713903932e+00 -3.825000000000000000e+01 +-1.229422863405994804e+01 3.294228634059948924e+00 -3.375000000000000000e+01 +-1.619134295108992205e+01 1.044228634059949368e+00 -3.375000000000000000e+01 +-1.454422863405994804e+01 7.191342951089922053e+00 -3.375000000000000000e+01 +-1.844134295108992205e+01 4.941342951089922941e+00 -3.375000000000000000e+01 +-3.294228634059948924e+00 -1.229422863405994804e+01 -3.375000000000000000e+01 +-7.191342951089922053e+00 -1.454422863405994804e+01 -3.375000000000000000e+01 +-1.044228634059949368e+00 -1.619134295108992205e+01 -3.375000000000000000e+01 +-4.941342951089922941e+00 -1.844134295108992205e+01 -3.375000000000000000e+01 +3.294228634059948924e+00 1.229422863405994804e+01 -3.375000000000000000e+01 +7.191342951089922053e+00 1.454422863405994804e+01 -3.375000000000000000e+01 +1.044228634059949368e+00 1.619134295108992205e+01 -3.375000000000000000e+01 +4.941342951089922941e+00 1.844134295108992205e+01 -3.375000000000000000e+01 +1.229422863405994804e+01 -3.294228634059948924e+00 -3.375000000000000000e+01 +1.619134295108992205e+01 -1.044228634059949368e+00 -3.375000000000000000e+01 +1.454422863405994804e+01 -7.191342951089922053e+00 -3.375000000000000000e+01 +1.844134295108992205e+01 -4.941342951089922941e+00 -3.375000000000000000e+01 +-9.835382907247959494e+00 2.635382907247959761e+00 -2.925000000000000000e+01 +-1.163538290724795665e+01 5.753074360871939241e+00 -2.925000000000000000e+01 +-1.295307436087193764e+01 8.353829072479604934e-01 -2.925000000000000000e+01 +-1.475307436087193658e+01 3.953074360871939419e+00 -2.925000000000000000e+01 +-2.635382907247959761e+00 -9.835382907247959494e+00 -2.925000000000000000e+01 +-8.353829072479604934e-01 -1.295307436087193764e+01 -2.925000000000000000e+01 +-5.753074360871939241e+00 -1.163538290724795665e+01 -2.925000000000000000e+01 +-3.953074360871939419e+00 -1.475307436087193658e+01 -2.925000000000000000e+01 +2.635382907247959761e+00 9.835382907247959494e+00 -2.925000000000000000e+01 +8.353829072479604934e-01 1.295307436087193764e+01 -2.925000000000000000e+01 +5.753074360871939241e+00 1.163538290724795665e+01 -2.925000000000000000e+01 +3.953074360871939419e+00 1.475307436087193658e+01 -2.925000000000000000e+01 +9.835382907247959494e+00 -2.635382907247959761e+00 -2.925000000000000000e+01 +1.163538290724795665e+01 -5.753074360871939241e+00 -2.925000000000000000e+01 +1.295307436087193764e+01 -8.353829072479604934e-01 -2.925000000000000000e+01 +1.475307436087193658e+01 -3.953074360871939419e+00 -2.925000000000000000e+01 +-3.294228634059948924e+00 -1.229422863405994804e+01 -2.475000000000000000e+01 +1.927904550760216384e+00 -9.279228634059947467e+00 -2.475000000000000000e+01 +7.072095449239783171e+00 -6.309228634059948604e+00 -2.475000000000000000e+01 +1.229422863405994804e+01 -3.294228634059948924e+00 -2.475000000000000000e+01 +-6.309228634059948604e+00 -7.072095449239783171e+00 -2.475000000000000000e+01 +-1.087095449239783296e+00 -4.057095449239782603e+00 -2.475000000000000000e+01 +4.057095449239782603e+00 -1.087095449239783296e+00 -2.475000000000000000e+01 +9.279228634059947467e+00 1.927904550760216384e+00 -2.475000000000000000e+01 +-9.279228634059947467e+00 -1.927904550760216384e+00 -2.475000000000000000e+01 +-4.057095449239782603e+00 1.087095449239783296e+00 -2.475000000000000000e+01 +1.087095449239783296e+00 4.057095449239782603e+00 -2.475000000000000000e+01 +6.309228634059948604e+00 7.072095449239783171e+00 -2.475000000000000000e+01 +3.294228634059948924e+00 1.229422863405994804e+01 -2.475000000000000000e+01 +-7.072095449239783171e+00 6.309228634059948604e+00 -2.475000000000000000e+01 +-1.927904550760216384e+00 9.279228634059947467e+00 -2.475000000000000000e+01 +-1.229422863405994804e+01 3.294228634059948924e+00 -2.475000000000000000e+01 +-4.917691453623979747e+00 1.317691453623979880e+00 -2.025000000000000000e+01 +-7.167691453623979747e+00 5.214805770653954120e+00 -2.025000000000000000e+01 +-8.814805770653952877e+00 -9.323085463760198977e-01 -2.025000000000000000e+01 +-1.106480577065395288e+01 2.964805770653954120e+00 -2.025000000000000000e+01 +4.917691453623979747e+00 -1.317691453623979880e+00 -2.025000000000000000e+01 +7.167691453623979747e+00 -5.214805770653954120e+00 -2.025000000000000000e+01 +8.814805770653952877e+00 9.323085463760198977e-01 -2.025000000000000000e+01 +1.106480577065395288e+01 -2.964805770653954120e+00 -2.025000000000000000e+01 +-1.317691453623979880e+00 -4.917691453623979747e+00 -2.025000000000000000e+01 +9.323085463760198977e-01 -8.814805770653952877e+00 -2.025000000000000000e+01 +-5.214805770653954120e+00 -7.167691453623979747e+00 -2.025000000000000000e+01 +-2.964805770653954120e+00 -1.106480577065395288e+01 -2.025000000000000000e+01 +1.317691453623979880e+00 4.917691453623979747e+00 -2.025000000000000000e+01 +-9.323085463760198977e-01 8.814805770653952877e+00 -2.025000000000000000e+01 +5.214805770653954120e+00 7.167691453623979747e+00 -2.025000000000000000e+01 +2.964805770653954120e+00 1.106480577065395288e+01 -2.025000000000000000e+01 +-2.635382907247959761e+00 -9.835382907247959494e+00 -1.575000000000000000e+01 +9.835382907247959494e+00 -2.635382907247959761e+00 -1.575000000000000000e+01 +2.635382907247959761e+00 9.835382907247959494e+00 -1.575000000000000000e+01 +-9.835382907247959494e+00 2.635382907247959761e+00 -1.575000000000000000e+01 +-6.235382907247958961e+00 -3.599999999999999645e+00 -1.575000000000000000e+01 +6.235382907247958961e+00 3.599999999999999645e+00 -1.575000000000000000e+01 +-3.599999999999999645e+00 6.235382907247958961e+00 -1.575000000000000000e+01 +3.599999999999999645e+00 -6.235382907247958961e+00 -1.575000000000000000e+01 +1.976537180435969709e+00 7.376537180435968288e+00 -1.125000000000000000e+01 +7.376537180435968288e+00 -1.976537180435969709e+00 -1.125000000000000000e+01 +-7.376537180435968288e+00 1.976537180435969709e+00 -1.125000000000000000e+01 +-1.976537180435969709e+00 -7.376537180435968288e+00 -1.125000000000000000e+01 +-6.147114317029974018e+00 1.647114317029974462e+00 -6.750000000000000000e+00 +6.147114317029974018e+00 -1.647114317029974462e+00 -6.750000000000000000e+00 +1.647114317029974462e+00 6.147114317029974018e+00 -6.750000000000000000e+00 +-1.647114317029974462e+00 -6.147114317029974018e+00 -6.750000000000000000e+00 +-1.317691453623979880e+00 -4.917691453623979747e+00 -2.699999999999999734e+00 +4.917691453623979747e+00 -1.317691453623979880e+00 -2.699999999999999734e+00 +1.317691453623979880e+00 4.917691453623979747e+00 -2.699999999999999734e+00 +-4.917691453623979747e+00 1.317691453623979880e+00 -2.699999999999999734e+00 +-3.688268590217984144e+00 9.882685902179848547e-01 2.250000000000000000e+00 +3.688268590217984144e+00 -9.882685902179848547e-01 2.250000000000000000e+00 +9.882685902179848547e-01 3.688268590217984144e+00 2.250000000000000000e+00 +-9.882685902179848547e-01 -3.688268590217984144e+00 2.250000000000000000e+00 +8.235571585149872309e-01 3.073557158514987009e+00 9.000000000000000000e+00 +-3.073557158514987009e+00 8.235571585149872309e-01 9.000000000000000000e+00 +3.073557158514987009e+00 -8.235571585149872309e-01 9.000000000000000000e+00 +-8.235571585149872309e-01 -3.073557158514987009e+00 9.000000000000000000e+00 +-7.576725858337882702e-01 -2.827672585833787888e+00 1.575000000000000000e+01 +2.827672585833787888e+00 -7.576725858337882702e-01 1.575000000000000000e+01 +-2.827672585833787888e+00 7.576725858337882702e-01 1.575000000000000000e+01 +7.576725858337882702e-01 2.827672585833787888e+00 1.575000000000000000e+01 +7.247302994931886788e-01 2.704730299493188550e+00 2.250000000000000000e+01 +-2.704730299493188550e+00 7.247302994931886788e-01 2.250000000000000000e+01 +2.704730299493188550e+00 -7.247302994931886788e-01 2.250000000000000000e+01 +-7.247302994931886788e-01 -2.704730299493188550e+00 2.250000000000000000e+01 +-2.458845726811989874e+00 6.588457268119899402e-01 2.925000000000000000e+01 +2.458845726811989874e+00 -6.588457268119899402e-01 2.925000000000000000e+01 +6.588457268119899402e-01 2.458845726811989874e+00 2.925000000000000000e+01 +-6.588457268119899402e-01 -2.458845726811989874e+00 2.925000000000000000e+01 +8.235571585149872309e-01 3.073557158514987009e+00 3.285000000000000142e+01 +-3.073557158514987009e+00 8.235571585149872309e-01 3.285000000000000142e+01 +3.073557158514987009e+00 -8.235571585149872309e-01 3.285000000000000142e+01 +-8.235571585149872309e-01 -3.073557158514987009e+00 3.285000000000000142e+01 +-0.000000000000000000e+00 0.000000000000000000e+00 3.600000000000000000e+01 +4.199795599858229153e-17 -1.125331839357795486e-17 3.825000000000000000e+01 diff --git a/demonstrations_v2/tutorial_pasqal/requirements.in b/demonstrations_v2/tutorial_pasqal/requirements.in new file mode 100644 index 0000000000..a0bd965555 --- /dev/null +++ b/demonstrations_v2/tutorial_pasqal/requirements.in @@ -0,0 +1,5 @@ +cirq_pasqal +matplotlib +numpy +pennylane +tensorflow diff --git a/demonstrations_v2/tutorial_phase_kickback/demo.py b/demonstrations_v2/tutorial_phase_kickback/demo.py new file mode 100644 index 0000000000..ec0781ff61 --- /dev/null +++ b/demonstrations_v2/tutorial_phase_kickback/demo.py @@ -0,0 +1,183 @@ +r""" + +Building a quantum lock using phase kickback +============================================ + +.. meta:: + :property="og:description": Use phase kickback to create an unbreakable quantum lock + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_phase_kickback.png + +.. related:: + tutorial_qubit_rotation Basic tutorial: qubit rotation + +Greetings, quantum adventurers! In this exciting tutorial, we’ll be exploring the concept of quantum +phase kickback, used in many quantum algorithms such as the Deutsch–Jozsa algorithm, and quantum phase +estimation. Here, we'll be utilizing it to create a “quantum lock”. Are you ready to dive into the quantum +world and learn how to create an unbreakable lock? Let’s go! +""" + +###################################################################### +# Introduction to phase kickback +# ------------------------------ +# +# Phase kickback is a powerful quantum phenomenon that uses entanglement properties to allow for the transfer of phase information from a +# target register to a control qubit. It plays a vital role in the design of many quantum algorithms. +# +# In a phase kickback circuit, an ancilla qubit is prepared in a superposition state using a Hadamard +# gate and it acts as a control qubit for a controlled unitary gate applied to the target register. When +# the target register is in an eigenstate of the unitary gate, the corresponding eigenvalue’s phase is +# “kicked back” to the control qubit. A subsequent Hadamard gate on the ancilla qubit enables the +# extraction of the phase information through measurement. +# + +############################################################################## +# .. figure:: ../_static/demonstration_assets/phase_kickback/Phase_Kickback.png +# :align: center +# :width: 50% +# +# If you want to know more about the details, do not hesitate to consult the node `[P.1] `_ of the PennyLane Codebook. + +###################################################################### +# Setting up PennyLane +# -------------------- +# +# First, let’s import the necessary libraries and create a device to run our quantum +# circuits. Here we will work with 5 qubits, we will use qubit [0] as the control ancilla qubit, and qubits [1,2,3,4] will be our target qubits where we will encode :math:`|\psi\rangle.` +# + +import pennylane as qml +import numpy as np + +num_wires = 5 +dev = qml.device("default.qubit", wires=num_wires, shots=1) + +###################################################################### +# Building the quantum lock +# ------------------------- +# +# Now let’s create the most formidable lock in the universe: the “quantum lock”! Here our lock is +# represented by a unitary :math:`U,` which has all but one eigenvalue equal to 1. Our one “key” eigenstate +# has eigenvalue -1: +# +# .. math:: U|\text{key}\rangle = -|\text{key}\rangle +# +# But how can we differentiate the "key" eigenstate from the other eigenstate when the information is contained in the phase? +# That's where phase kickback comes in! When the correct eigenstate is input, the -1 phase imparted by :math:`U` is kicked back to +# the ancilla, effectively changing its state from :math:`|+\rangle` to :math:`|-\rangle.` +# Then the outcome of the measurement on the control qubit tells us whether the correct eigenstate was inputted or not. +# In this case, :math:`|1\rangle = H|-\rangle` represents unlocking the lock, and :math:`|0\rangle = H|+\rangle` represents failure. To make +# things simple, here we’ll work with a lock-in computational basis. In this setting, the key +# corresponds to a binary encoded integer :math:`m` , which will be our key eigenstate: +# +# .. math:: +# +# +# U|n\rangle = +# \begin{cases} +# -|n\rangle, & \text{if } n=m \\ +# |n\rangle, & \text{if } n\neq m +# \end{cases} +# +# We’ll make use of :class:`~.pennylane.FlipSign` to build our lock: +# + + +def quantum_lock(secret_key): + return qml.FlipSign(secret_key, wires=list(range(1, num_wires))) + + +###################################################################### +# Next, we need to prepare the corresponding eigenstate for a key we want to try out. Remember, the lock is only unlocked by the "key" eigenstate with eigenvalue -1. We’ll make use of +# :class:`~.pennylane.BasisState` to build the key: +# + + +def build_key(key): + return qml.BasisState(key, wires=list(range(1, num_wires))) + + +###################################################################### +# Now we’ll put it all together to build our quantum locking mechanism: +# + + +@qml.qnode(dev) +def quantum_locking_mechanism(lock, key): + build_key(key) + qml.Hadamard(wires=0) # Hadamard on ancilla qubit + qml.ctrl(lock, control=0) # Controlled unitary operation + qml.Hadamard(wires=0) # Hadamard again on ancilla qubit + return qml.sample(wires=0) + + +def check_key(lock, key): + if quantum_locking_mechanism(lock, key) == 1: + print("Great job, you have uncovered the mysteries of the quantum universe!") + else: + print("Nice try, but that's not the right key!") + + +###################################################################### +# Opening the Quantum Lock +# ------------------------ +# +# To open the quantum lock, we’ll need the correct input state or “quantum key”. Let’s see how the +# quantum system evolves when we input the right key. +# +# We first apply a Hadamard to our control qubit: +# +# .. math:: \frac{|0\rangle|\text{key}\rangle + |1\rangle|\text{key}\rangle}{\sqrt{2}} +# +# By applying the controlled unitary operation we get: +# +# .. math:: \frac{|0\rangle|\text{key}\rangle - |1\rangle|\text{key}\rangle}{\sqrt{2}} = |-\rangle|\text{key}\rangle +# +# Finally, we apply a Hadamard to our control qubit again to get: +# +# .. math:: |1\rangle|\text{key}\rangle +# +# And just like that, we’ve uncovered the quantum secrets hidden by the lock. Let’s now crack open our +# quantum lock in code! +# + +secret_key = np.array([0, 1, 1, 1]) +lock = quantum_lock(secret_key) + +check_key(lock, secret_key) + +###################################################################### +# What happens with an incorrect quantum key? +# ------------------------------------------- +# +# Now, we’ll try using the wrong key and see if we can still unlock the quantum lock. Will we be able +# to break through its quantum defenses? Let’s see how the quantum system evolves when we input the +# wrong key. +# +# We first apply a Hadamard to our control qubit: +# +# .. math:: \frac{|0\rangle|\text{incorrect key}\rangle + |1\rangle|\text{incorrect key}\rangle}{\sqrt{2}} +# +# Applying the controlled unitary operation, in this case, acts as the identity gate, hence we get: +# +# .. math:: \frac{|0\rangle|\text{incorrect key}\rangle + |1\rangle|\text{incorrect key}\rangle}{\sqrt{2}} = |+\rangle|\text{incorrect key}\rangle +# +# Finally, we apply a Hadamard to our control qubit again to get: +# +# .. math:: |0\rangle|\text{incorrect key}\rangle +# +# As you can see, we were unable to fool the almighty lock. Don’t believe me? See for yourself! +# + +incorrect_key = np.array([1, 1, 1, 1]) + +check_key(lock, incorrect_key) + +###################################################################### +# Conclusion +# ---------- +# +# Congratulations! 🎉 You’ve successfully explored the remarkable phenomenon of phase kickback and created an unbreakable +# “quantum lock”. Now you can impress your friends with your newfound quantum knowledge and your +# incredible quantum lock-picking skills! +# +# diff --git a/demonstrations_v2/tutorial_phase_kickback/metadata.json b/demonstrations_v2/tutorial_phase_kickback/metadata.json new file mode 100644 index 0000000000..b179cfb4ee --- /dev/null +++ b/demonstrations_v2/tutorial_phase_kickback/metadata.json @@ -0,0 +1,38 @@ +{ + "title": "Building a quantum lock using phase kickback", + "authors": [ + { + "username": "Dan" + } + ], + "dateOfPublication": "2023-08-01T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/phase_kickback/thumbnail_tutorial_phase_kickback.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_phase_kickback.png" + } + ], + "seoDescription": "Use phase kickback to create an unbreakable quantum lock", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_phase_kickback/requirements.in b/demonstrations_v2/tutorial_phase_kickback/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_phase_kickback/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_photonics/demo.py b/demonstrations_v2/tutorial_photonics/demo.py new file mode 100644 index 0000000000..1a0cd13aea --- /dev/null +++ b/demonstrations_v2/tutorial_photonics/demo.py @@ -0,0 +1,963 @@ +r""".. _photonics: + +Photonic quantum computers +============================= + +.. meta:: + :property="og:description": Learn how photonic quantum computers work through code + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/photonics_tn.png + +.. related:: + tutorial_pasqal Quantum computation with neutral atoms + tutorial_trapped_ions Trapped ion quantum computing + tutorial_sc_qubits Quantum computing with superconducting qubits + gbs Quantum advantage with Gaussian Boson Sampling + +*Author: Alvaro Ballon — Posted: 31 May 2022. Last updated: 16 June 2022.* + +To create a functional quantum computer, we need to produce and control a +large number of qubits. This feat has proven difficult, although significant +progress has been made using trapped ions, superconducting circuits, +and many other technologies. Scalability—the ability to put many +qubits together—is limited because individual qubits in a multi-qubit +system lose their quantum properties quickly. This phenomenon, +known as decoherence, happens due to the interactions of the qubits with +their surroundings. One way to get scalable structures is to +use photons (particles of light). The quantum states of photons +are more robust against decoherence, so we may be onto something! + +Indeed, many approaches to use photons for quantum +computing have been proposed. We will focus on *linear optical quantum computing*, +an approach that has already achieved quantum advantage. It +is being developed further by Xanadu, PsiQuantum, and other institutions +around the globe. Unlike other physical systems, photonics allows us access to +an infinite number of states. How can we leverage these extra states to make quantum computers? +By the end of this demo, you will be able to explain how photonic +devices can be used to build universal quantum computers. You will learn how to prepare, +measure, and manipulate the quantum states of light, and how we can encode +qubits in photons. Moreover, you will identify +the strengths and weaknesses of photonic devices in terms of +Di Vincenzo's criteria, introduced in the blue box below. + +.. container:: alert alert-block alert-info + + **Di Vincenzo's criteria**: In the year 2000, David DiVincenzo proposed a + wishlist for the experimental characteristics of a quantum computer [#DiVincenzo2000]_. + DiVincenzo's criteria have since become the main guideline for + physicists and engineers building quantum computers: + + 1. **Well-characterized and scalable qubits**. Many of the quantum systems that + we find in nature are not qubits, so we must find a way to make them behave as such. + Moreover, we need to put many of these systems together. + + 2. **Qubit initialization**. We must be able to prepare the same state repeatedly within + an acceptable margin of error. + + 3. **Long coherence times**. Qubits will lose their quantum properties after + interacting with their environment for a while. We would like them to last long + enough so that we can perform quantum operations. + + 4. **Universal set of gates**. We need to perform arbitrary operations on the + qubits. To do this, we require both single-qubit gates and two-qubit gates. + + 5. **Measurement of individual qubits**. To read the result of a quantum algorithm, + we must accurately measure the final state of a pre-chosen set of qubits. + +Our journey will start by defining the simplest states of light, known as *Gaussian states*. We will also +describe how we can perform simple gates and measurements on such states. The next step is to +figure out reliable methods to generate the more general *non-Gaussian* states that are required for universal +quantum computing. We'll see that we end up needing only +a special type of non-Gaussian states, known as GKP states. +Finally, we will bring all the concepts together to understand how quantum algorithms +can be performed using these tools. Let's get started! + +""" + +############################################################################## +# +# Gaussian states of light +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Why are the quantum states of light so durable? Photons seldom interact with each other, +# which means we can easily avoid uncontrolled interactions that destroy their quantum state. +# However, to build a universal quantum computer, we need multi-qubit gates, +# which means that photons must be made to communicate with each other somehow! We *can* make photons +# affect each other by using a material as a mediator. To start with, we will focus on manipulating +# photons using *linear materials*, whose properties are unchanged when they interact with light. With linear materials, +# we can produce a subset of the so-called **Gaussian states**. They can be fabricated with a 100% success rate +# using common optical devices, so they are our safest tool in photonics. +# +# To precisely define a Gaussian state, we need a mathematical representation for states +# of light. As is the case with qubits, states of light are represented by a linear +# combination of basis vectors. But unlike qubits, two basis vectors aren't enough. +# The reason is that light is characterized by its so-called *position and momentum quadratures* :math:`x` and :math:`p,` +# captured by the operators :math:`\hat{X}` and :math:`\hat{P}` respectively. Upon measurement, these quadratures +# can take any real value, which means that :math:`\hat{X}` and +# :math:`\hat{P}` have infinitely many eigenvectors. Therefore, to describe a quantum of light +# :math:`\left\lvert \psi \right\rangle,` we need infinitely many basis vectors! +# +# .. note:: +# +# The position and momentum quadratures :math:`x` and :math:`p` do not represent the position and momentum +# of one photon. They describe the state of possibly many photons, and they are related +# to the amplitude and phase of light. The names come from the fact that the quadrature observables :math:`\hat{X}` +# and :math:`\hat{P}` satisfy +# +# .. math:: \left[ \hat{X},\hat{P}\right]=i\hbar, +# +# which is the same relation satisfied by conventional position and momentum in quantum mechanics. This means that +# no simultaneous measurement of :math:`\hat{X}` and :math:`\hat{P}` can be performed. +# Moreover, the standard deviations of the measurements of :math:`x` and :math:`p` satisfy the +# uncertainty relation +# +# .. math:: \Delta x \Delta p \geq 1, +# +# where we work in units where :math:`\hbar = 2.` Sometimes the word "quadratures" is omitted for simplicity. +# +# For example, we write +# +# .. math:: \left\lvert \psi \right\rangle = \int_\mathbb{R}\psi(x)\vert x \rangle dx, +# +# where :math:`\vert x \rangle` is the eigenstate of :math:`\hat{X}` with eigenvalue +# :math:`x,` and :math:`\psi` is a complex-valued function known as the *wave function*. +# A similar expansion can be done in terms of the eigenstates :math:`\vert p \rangle` of :math:`\hat{P}.` +# Note that an integral is used here rather than an infinite sum because the eigenvalues of :math:`\hat{X}` and :math:`\hat{P}` are continuous. +# So how do we define a Gaussian state using this representation? It is a state that is completely +# determined by the average values :math:`\bar{x}` and :math:`\bar{p}` of the position and momentum quadratures, +# as well as their standard deviations :math:`\Delta x` and :math:`\Delta p.` The most trivial +# Gaussian state is the *vacuum*—the state with no photons. Let us see what happens if +# we sample measurements of the quadratures :math:`\hat{X}` and :math:`\hat{P}` when light is in the vacuum. +# We can use PennyLane's ``default.gaussian`` device, which lets us create, manipulate, and measure Gaussian +# states of light. Let's first call the usual imports, +# + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt + +############################################################################## +# +# and define the device. + +dev = qml.device("default.gaussian", wires=1, shots=1000) + +############################################################################## +# +# .. important:: +# +# What do the wires represent in a photonic device? They are independent +# information carriers known as **qumodes**. In photonic quantum computers, these are waves of light +# contained inside an optical cavity. Qumodes are not two-level systems in general, so we must use an +# infinite superposition of basis states to represent their quantum state. +# +# We would like to know how the measured values of position and momentum +# are distributed in the :math:`x`-:math:`p` space, usually called `phase space `__. +# The initial state in ``default.gaussian`` is the vacuum, so the circuits +# to measure the quadratures need not contain any operations, except for measurements! +# We plot 1000 measurement results for both :math:`x` and :math:`p.` + + +@qml.qnode(dev) +def vacuum_measure_x(): + return qml.sample(qml.QuadX(0)) # Samples X quadratures + + +@qml.qnode(dev) +def vacuum_measure_p(): + return qml.sample(qml.QuadP(0)) # Samples P quadrature + + +# Sample measurements in phase space +x_sample = vacuum_measure_x() +p_sample = vacuum_measure_p() + +# Import some libraries for a nicer plot +from scipy.stats import gaussian_kde +from numpy import vstack as vstack + +# Point density calculation +xp = vstack([x_sample, p_sample]) +z = gaussian_kde(xp)(xp) + +# Sort the points by density +sorted = z.argsort() +x, y, z = x_sample[sorted], p_sample[sorted], z[sorted] + +# Plot +fig, ax = plt.subplots() +ax.scatter(x, y, c = z, s = 50, cmap="RdYlGn") +plt.title("Vacuum", fontsize=12) +ax.set_ylabel("Momentum", fontsize = 11) +ax.set_xlabel("Position", fontsize = 11) +ax.set_aspect("equal", adjustable = "box") +plt.show() + +############################################################################## +# +# We observe that the values of the quadratures are distributed around the +# origin with a spread of approximately 1. We can check these eyeballed values explicitly, +# using a device without shots this time. + +dev_exact = qml.device("default.gaussian", wires=1) # No explicit shots gives analytic calculations + + +@qml.qnode(dev_exact) +def vacuum_mean_x(): + return qml.expval(qml.QuadX(0)) # Returns exact expecation value of x + + +@qml.qnode(dev_exact) +def vacuum_mean_p(): + return qml.expval(qml.QuadP(0)) # Returns exact expectation value of p + + +@qml.qnode(dev_exact) +def vacuum_var_x(): + return qml.var(qml.QuadX(0)) # Returns exact variance of x + + +@qml.qnode(dev_exact) +def vacuum_var_p(): + return qml.var(qml.QuadP(0)) # Returns exact variance of p + + +# Print calculated statistical quantities +print("Expectation value of x-quadrature: {}".format(vacuum_mean_x())) +print("Expectation value of p-quadrature: {}".format(vacuum_mean_p())) +print("Variance of x-quadrature: {}".format(vacuum_var_x())) +print("Variance of p-quadrature: {}".format(vacuum_var_p())) + +############################################################################## +# +# But where does the name Gaussian come from? If we plot the density +# of quadrature measurements for the vacuum state in three dimensions, we obtain the following plot. +# +# .. figure:: ../_static/demonstration_assets/photonics/vacuum_wigner.png +# :align: center +# :width: 70% +# +# .. +# +# Density of measurement results in phase space for the vacuum state +# +# The density has the shape of a 2-dimensional Gaussian surface, hence the name. *For Gaussian +# states only*, the density is exactly equal to the so-called `Wigner function `__ :math:`W(x,p),` +# defined using the wave function :math:`\psi(x):` +# +# .. math:: W(x,p) = \frac{1}{\pi\hbar}\int_{-\infty}^{\infty}\psi^{*}(x+y)\psi(x-y)e^{2ipy/\hbar}dy. +# +# Since the Wigner function satisfies +# +# .. math:: \int_{\mathbb{R}^2}W(x,p)dxdp = 1 +# +# and is positive for Gaussian states, +# it is *almost as if* the values of the momentum and position had an underlying classical +# probability distribution, save for the fact that the quadratures can't be measured simultaneously. +# For this reason, Gaussian states are considered to be "classical" states of light. Now we're ready +# for the technical definition of a Gaussian state. +# +# .. admonition:: Definition +# :class: defn +# +# A photonic system is said to be in a **Gaussian** state if its Wigner function is a two-dimensional +# Gaussian function [#Weedbrook2012]_. +# +# What other Gaussian states are there? The states produced by lasers are called *coherent states*, +# which are also Gaussian with :math:`\Delta x = \Delta p = 1.` Coherent states, in general, +# can have non-zero expectation values for the +# quadratures (i.e., they are not centered around the origin). +# +# The ``default.gaussian`` device allows for the easy preparation of coherent states +# through the function :class:`~pennylane.CoherentState`, which takes two parameters :math:`\alpha` and :math:`\phi.` +# Here, :math:`\alpha=\sqrt{\vert\bar{x}\vert^2+\vert\bar{p}\vert^2}` is the magnitude +# and :math:`\phi` is the polar angle of the point :math:`(\bar{x}, \bar{p}).` +# Let us plot sample quadrature measurements for a coherent state. + + +@qml.qnode(dev) +def measure_coherent_x(alpha, phi): + qml.CoherentState(alpha, phi, wires=0) # Prepares coherent state + return qml.sample(qml.QuadX(0)) # Measures X quadrature + + +@qml.qnode(dev) +def measure_coherent_p(alpha, phi): + qml.CoherentState(alpha, phi, wires=0) # Prepares coherent state + return qml.sample(qml.QuadP(0)) # Measures P quadrature + + +# Choose alpha and phi and sample 1000 measurements +x_sample_coherent = measure_coherent_x(3, np.pi / 3) +p_sample_coherent = measure_coherent_p(3, np.pi / 3) + +# Plot as before +xp = vstack([x_sample_coherent, p_sample_coherent]) +z1 = gaussian_kde(xp)(xp) + +sorted = z1.argsort() +x, y, z = x_sample_coherent[sorted], p_sample_coherent[sorted], z1[sorted] + +fig, ax1 = plt.subplots() +ax1.scatter(x, y, c = z, s = 50, cmap = "RdYlGn") +ax1.set_title("Coherent State", fontsize = 12) +ax1.set_ylabel("Momentum", fontsize = 11) +ax1.set_xlabel("Position", fontsize = 11) +ax1.set_aspect("equal", adjustable = "box") +plt.xlim([-0.5, 8]) +plt.ylim([0, 9]) +plt.show() +############################################################################## +# +# Indeed, we see that the distribution of quadrature measurements is similar to that of the vacuum, except that +# it is not centered at the origin. Instead it is centred at the alpha and phi coordinates we chose in the code above. + +############################################################################## +# +# Gaussian operations +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We have only learned about two types of Gaussian states so far. The vacuum can be obtained +# by carefully isolating a system from any environmental influences, and a coherent state can be produced +# by a laser, so we already have these at hand. But how can we obtain any Gaussian state of our liking? +# This is achieved through *Gaussian operations*, which transform a Gaussian state to +# another Gaussian state. These operations are relatively easy to implement in a lab +# using some of the optical elements introduced in the table below. +# +# .. rst-class:: docstable +# +# +---------------------+----------------------------------------------------------------------------+----------------------------------------------------------------------------+ +# | .. centered:: | .. centered:: | .. centered:: | +# | Element | Diagram | Description | +# +=====================+============================================================================+============================================================================+ +# | Waveguide | .. figure:: ../_static/demonstration_assets/photonics/Waveguide.png | A long strip of material that contains and guides | +# | | :align: center | electromagentic waves. For example, an optical fibre is a type | +# | | :width: 70% | of waveguide. | +# +---------------------+----------------------------------------------------------------------------+----------------------------------------------------------------------------+ +# | Phase-shifter | .. figure:: ../_static/demonstration_assets/photonics/Thermo-optic.png | A piece of material that changes the phase of light. The figure | +# | | :align: center | shows a particular implementation known as a thermo-optic phase | +# | | :width: 70% | shifter [#Sabouri2021]_, which is a (sometimes curved) waveguide | +# | | | that changes properties when heated up using a resistor. | +# | | | This allows us to control the applied phase difference. | +# +---------------------+----------------------------------------------------------------------------+----------------------------------------------------------------------------+ +# | Beamsplitter | .. figure:: ../_static/demonstration_assets/photonics/Beam_splitter.png | An element with two input and two output qumodes. It transmits a | +# | | :align: center | fraction :math:`T` of the photons coming in through either entry | +# | | :width: 100% | port, and reflects a fraction :math:`R=1-T.` The input qumodes can | +# | | | be combined to create entangled states across the output ports. | +# | | | In a photonic quantum computing chip, a `directional coupler | +# | | | `__ | +# | | | is used. | +# +---------------------+----------------------------------------------------------------------------+----------------------------------------------------------------------------+ +# +# The vacuum is centered at the origin in phase space. It is advantageous to generate states that +# are centered at any point in phase space. +# How would we, for example, change the mean :math:`\bar{x}` of the :math:`x`-quadrature +# without changing anything else about the state? This can be done +# via the *displacement operator*, implemented in PennyLane via :class:`~pennylane.Displacement`. +# Let's see the effect of this operation on an intial coherent state. + + +@qml.qnode(dev) +def displace_coherent_x(alpha, phi, x): + qml.CoherentState(alpha, phi, wires = 0) # Create coherent state + qml.Displacement(x, 0, wires = 0) # Second argument is the displacement direction in phase space + return qml.sample(qml.QuadX(0)) + + +@qml.qnode(dev) +def displace_coherent_p(alpha, phi, x): + qml.CoherentState(alpha, phi, wires = 0) + qml.Displacement(x, 0, wires = 0) + return qml.sample(qml.QuadP(0)) + + +# We plot both the initial and displaced state +initial_x = displace_coherent_x(3, np.pi / 3, 0) # initial state amounts to 0 displacement +initial_p = displace_coherent_p(3, np.pi / 3, 0) +displaced_x = displace_coherent_x(3, np.pi / 3, 3) # displace x=3 in x-direction +displaced_p = displace_coherent_p(3, np.pi / 3, 3) +# Plot as before +fig, ax1 = plt.subplots(figsize=(10, 5)) +xp1 = vstack([initial_x, initial_p]) +z1 = gaussian_kde(xp1)(xp1) +sorted1 = z1.argsort() +x1, y1, z1 = initial_x[sorted1], initial_p[sorted1], z1[sorted1] +xp2 = vstack([displaced_x, displaced_p]) +z2 = gaussian_kde(xp2)(xp2) +sorted2 = z2.argsort() +x2, y2, z2 = displaced_x[sorted2], displaced_p[sorted2], z2[sorted2] +ax1.scatter(x1, y1, c = z1, s = 50, cmap ="RdYlGn") +ax1.scatter(x2, y2, c = z2, s = 50, cmap = "RdYlGn") +plt.xlim([0, 12]) +plt.ylim([0, 9]) +ax1.set_aspect("equal", adjustable="box") +plt.text(1, 0.8, "Before displacement") +plt.text(7.5, 0.8, "After displacement") +ax1.set_ylabel("Momentum", fontsize=11) +ax1.set_xlabel("Position", fontsize=11) +ax1.set_title("Displacing coherent states", fontsize=12) +ax1.set_aspect("equal", adjustable = "box") +plt.show() + +############################################################################## +# +# Note that setting :math:`x=3` gave a displacement of 6 units in the horizontal direction in phase space. +# This is because the scale of phase space is set by our choice of units :math:`\hbar=2.` +# +# So how do we make a displacement operation in the lab? One method +# is shown below, which uses a beamsplitter and a source of **high-intensity coherent light** [#Paris1996]_. +# +# .. figure:: ../_static/demonstration_assets/photonics/Displacement.png +# :align: center +# :width: 70% +# +# .. +# +# This setup displaces the input state :math:`\lvert\psi\rangle` by a quantity proportional to :math:`z.` +# +# We can check that this setup implements a displacement operator using PennyLane. This time, +# we need two qumodes, since we rely on combining the input state that we want to displace with a +# coherent state in a beamsplitter. Let us code this circuit in the case that the input is a coherent state +# as a particular case (the operation will work for any state). +# Let us be mindful that this will only work when the amplitude of the input state is much smaller +# than that of the auxiliary coherent state. + +dev2 = qml.device("default.gaussian", wires=2, shots=1000) + + +@qml.qnode(dev2) +def disp_optics(z, x): + qml.CoherentState(z, 0, wires = 0) # High-amplitude auxiliary coherent state + qml.CoherentState(3, np.pi / 3, wires = 1) # Input state (e.g. low amplitude coherent state) + qml.Beamsplitter(np.arccos(1 - x ** 2 / z ** 2), 0, wires=[0, 1]) # Beamsplitter + return qml.sample(qml.QuadX(1)) # Measure x quadrature + + +@qml.qnode(dev2) +def mom_optics(z, x): + qml.CoherentState(z, 0, wires = 0) + qml.CoherentState(3, np.pi / 3, wires = 1) + qml.Beamsplitter(np.arccos(1 - x ** 2 / z ** 2), 0, wires = [0, 1]) + return qml.sample(qml.QuadP(1)) # Measure p quadrature + + +# Plot quadrature measurement before and after implementation of displacement +initial_x = disp_optics(100, 0) # Initial corresponds to beamsplitter with t=0 (x=0) +initial_p = mom_optics(100, 0) # Amplitude of coherent state must be large +displaced_x = disp_optics(100, 3) +displaced_p = mom_optics(100, 3) # Set some non-trivial t +# Plot as before +fig, ax1 = plt.subplots() +xp1 = vstack([initial_x, initial_p]) +z1 = gaussian_kde(xp1)(xp1) +sorted1 = z1.argsort() +x1, y1, z1 = initial_x[sorted1], initial_p[sorted1], z1[sorted1] +xp2 = vstack([displaced_x, displaced_p]) +z2 = gaussian_kde(xp2)(xp2) +sorted2 = z2.argsort() +x2, y2, z2 = displaced_x[sorted2], displaced_p[sorted2], z2[sorted2] +ax1.scatter(x1, y1, c = z1, s = 50, cmap = "RdYlGn") +ax1.scatter(x2, y2, c = z2, s = 50, cmap = "RdYlGn") +ax1.set_title("Initial", fontsize = 12) +plt.xlim([-0.5, 15]) +plt.ylim([0, 9]) +ax1.set_ylabel("Momentum", fontsize = 11) +ax1.set_xlabel("Position", fontsize = 11) +plt.text(1, 0.5, "Before displacement") +plt.text(9.5, 0.5, "After displacement") +ax1.set_aspect("equal", adjustable="box") +ax1.set_title("Implementation of displacement operator", fontsize = 12) +plt.show() + +############################################################################## +# +# We see that we get a displaced state. The amount of displacement can be adjusted by +# changing the parameters of the beamsplitter. +# Similarly, we can implement rotations in phase space using :class:`~pennylane.Rotation`, which +# simply amounts to changing the phase of light using a phase shifter. In phase space, +# this amounts to rotating the point :math:`(\bar{x},\bar{p})` around the origin. +# +# So far, we have focused on changing the mean values of :math:`x` and :math:`p.` +# But what if we also want to change the spread of the quadratures while keeping :math:`\Delta x\Delta p =1?`. +# This would "squeeze" the Wigner function in one direction. Aptly, the resulting state +# is known as a *squeezed state*, which is more difficult to obtain. +# It requires shining light through non-linear +# materials, where the state of light will undergo unitary evolution in a +# way that changes :math:`\Delta x` and :math:`\Delta p.` +# +# .. figure:: ../_static/demonstration_assets/photonics/Squeezer.png +# :align: center +# :width: 70% +# +# .. +# +# A non-linear material can work as a squeezer [#Braunstein2005]_. +# +# We won't go into detail here, +# but we note that the technology to produce these states is quite mature. +# In PennyLane, we can generate squeezed states through the squeezing +# operator :class:`~pennylane.Squeezing`. This function depends on the squeezing parameter +# :math:`r` which tells us how much the variance in :math:`x` and :math:`p` changes, and :math:`\phi,` +# which rotates the state in phase space. Let's take a look at how squeezing changes the distribution +# of quadrature measurements. + + +@qml.qnode(dev) +def measure_squeezed_x(r): + qml.Squeezing(r, 0, wires = 0) + return qml.sample(qml.QuadX(0)) + + +@qml.qnode(dev) +def measure_squeezed_p(r): + qml.Squeezing(r, 0, wires = 0) + return qml.sample(qml.QuadP(0)) + + +# Choose alpha and phi and sample 1000 measurements +x_sample_squeezed = measure_squeezed_x(0.4) +p_sample_squeezed = measure_squeezed_p(0.4) + +# Plot as before +xp = vstack([x_sample_squeezed, p_sample_squeezed]) +z = gaussian_kde(xp)(xp) + +sorted_meas = z.argsort() +x, y, z = x_sample_squeezed[sorted_meas], p_sample_squeezed[sorted_meas], z[sorted_meas] + +fig, ax1 = plt.subplots(figsize=(7, 7)) +ax1.scatter(x, y, c = z, s = 50, cmap = "RdYlGn") +ax1.set_title("Squeezed State", fontsize = 12) +ax1.set_ylabel("Momentum", fontsize = 11) +ax1.set_xlabel("Position", fontsize = 11) +ax1.set_xlim([-4, 4]) +ax1.set_aspect("equal", adjustable = "box") +plt.show() + +############################################################################## +# +# This confirms that squeezing changes the variances of the quadratures. +# +# .. note:: +# +# The squeezed states produced above satisfy :math:`\Delta x \Delta p = 1,` +# but more general Gaussian states need not satisfy these. For the purposes +# of photonic quantum computing, we won't need these generalized states. +# +# +# Measuring quadratures +# ~~~~~~~~~~~~~~~~~~~~~ +# +# Now that we know how to manipulate Gaussian states, we would like to perform measurements +# on them. So far, we have taken for granted that we can measure the quadratures +# :math:`\hat{X}` and :math:`\hat{P}.` But how do we actually measure them using optical elements? +# We will need a measuring device known as a photodetector. These contain a piece of a +# photoelectric material, where each outer electron can be stimulated by a photon. +# The more photons that are incident on the photodetector, the more electrons that are +# freed in the material, which in turn form an electric current. Mathematically, +# +# .. math:: I = qN, +# +# where :math:`I` is the electric current, :math:`N` is the number of photons, and :math:`q` is a detector-dependent +# proportionality constant [#Braunstein2005]_. Hence, measuring the current amounts to measuring the number of photons indirectly! +# +# The number of photons in a quantum of light is not fixed. It is measured by the quantum +# photon-number observable :math:`\hat{N},` which has eigenstates denoted :math:`\vert 0 \rangle, \vert 1\rangle, \vert 2 \rangle,\dots` +# These states, known as *Fock states*, do have a well-defined number of photons: +# repeated measurements of :math:`\hat{N}` on the same state will yield the same output. +# The natural number :math:`n` in the Fock state :math:`\vert n \rangle` denotes the only possible +# result we would get upon measuring the photon number. But nothing prevents light from being in a superposition of +# Fock states. For example, when we measure :math:`\hat{N}` for the state +# +# .. math:: \vert \psi \rangle = \frac{1}{\sqrt{3}}\left(\vert 0 \rangle + \vert 1 \rangle + \vert 2 \rangle\right), +# +# we get 0, 1, or 2 photons, each with probability :math:`\frac{1}{3}.` +# +# Except for the vacuum :math:`\vert 0 \rangle,` *Fock states are not Gaussian*. But all states of light +# are superpositions of Fock States, including Gaussian states! +# For example, let's measure the expected photon number for some squeezed state: + +dev3 = qml.device("default.gaussian", wires=1) + + +@qml.qnode(dev3) +def measure_n_coherent(alpha, phi): + qml.Squeezing(alpha, phi, wires = 0) + return qml.expval(qml.NumberOperator(0)) + + +coherent_expval = measure_n_coherent(1, np.pi / 3) +print("Expected number of photons: {}".format(coherent_expval)) + +############################################################################## +# +# Since the expectation value is not an integer number, the measurement results cannot have been all the same integer. +# This squeezed state cannot be a Fock state! +# +# But what about the promised quadrature measurements? We can perform them through a +# combination of photodetectors and a beamsplitter, +# as shown in the diagram below [#Braunstein2005]_. +# +# .. figure:: ../_static/demonstration_assets/photonics/Homodyne.png +# :align: center +# :width: 70% +# +# .. +# +# Measuring quadratures using photodetectors +# +# Let's code this setup using PennyLane and check that it amounts to the measurement of quadratures. + +dev_exact2 = qml.device("default.gaussian", wires = 2) + + +@qml.qnode(dev_exact2) +def measurement(a, phi): + qml.Displacement(a, phi, wires = 0) # Implement displacement using PennyLane + return qml.expval(qml.QuadX(0)) + + +@qml.qnode(dev_exact2) +def measurement2_0(a, theta, alpha, phi): + qml.Displacement(a, theta, wires = 0) # We choose the initial to be a displaced vacuum + qml.CoherentState(alpha, phi, wires = 1) # Prepare coherent as second qumode + qml.Beamsplitter(np.pi / 4, 0, wires=[0, 1]) # Interfere both states + return qml.expval(qml.NumberOperator(0)) # Read out N + +@qml.qnode(dev_exact2) +def measurement2_1(a, theta, alpha, phi): + qml.Displacement(a, theta, wires = 0) # We choose the initial to be a displaced vacuum + qml.CoherentState(alpha, phi, wires = 1) # Prepare coherent as second qumode + qml.Beamsplitter(np.pi / 4, 0, wires=[0, 1]) # Interfere both states + return qml.expval(qml.NumberOperator(1)) # Read out N + + +print( + "Expectation value of x-quadrature after displacement: {}\n".format(measurement(3, 0)) +) +print("Expected current in each detector:") +print("Detector 1: {}".format(measurement2_0(3, 0, 1, 0))) +print("Detector 2: {}".format(measurement2_1(3, 0, 1, 0))) +print( + "Difference between currents: {}".format( + measurement2_1(3, 0, 1, 0) - measurement2_0(3, 0, 1, 0) + ) +) + +############################################################################## +# +# Here we used :math:`q=1` as the detector constant, but we should note that this quantity can't really be measured +# precisely. However, we only care about distinguishing different states of light, so knowing the constant isn't really needed! +# +# Trying the above with many input states should convince you that this setup, known as *homodyne measurement*, +# allows us to measure the quadratures :math:`\hat{X}` and :math:`\hat{P}.` Feel free to play around +# changing the values of :math:`\phi` and :math:`a!` +# +# Beyond Gaussian states +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# We've learned a lot about Gaussian states now, but they don't seem to have many quantum properties. +# They are described by their positive Wigner +# function, which is to an extent analogous to a +# probability distribution. Are they really different from classical states? Not that much! To build a universal photonic +# quantum computer we need both Gaussian *and* non-Gaussian states. Moreover, +# we need to be able to entangle any two states. +# +# Entanglement is not a problem, since combinations of Gaussian operations involving +# squeezers and beamsplitters can easily create entangled states! +# Let us set on the more challenging mission to find a way to prepare non-Gaussian states. +# All of the operations that we have learned so far—displacements, rotations, squeezing—are Gaussian. +# Do we need some kind of strange material that will implement a non-Gaussian operation? That's certainly +# a possibility, and there are materials which can provide non-Gaussian interactions—like the `Kerr effect `__. But relying +# on these non-linear materials is far from optimal, since the Kerr effect is weak and we don't have much freedom to manipulate +# the setup into getting an arbitrary non-Gaussian state. +# +# But there's one non-Gausian operation that's been right in front of our eyes all this time. +# The measurement of the number of photons takes a Gaussian state and collapses it into a Fock state (although this destroys the photons); +# therefore, photon-number detection is not a Gaussian operation. Measuring the exact number of photons is not that easy. +# We need fancy devices known a photon-number resolving detectors (commonly abbreviated as PNRs), +# which are superconductor-based, so they work only at low temperatures. Combined with squeezed states and beamsplitters, +# we have all the ingredients to produce non-Gaussian states. +# +# Let's explore how this works. The main idea is to tweak a particular photonic circuit known +# as a *Gaussian Boson Sampler* [#Hamilton2017]_, which is shown below. +# +# .. figure:: ../_static/demonstration_assets/photonics/GBS.png +# :align: center +# :width: 70% +# +# .. +# +# A Gaussian Boson Sampling circuit. The beamsplitters here may include phase shifts. +# +# Gaussian boson sampling (GBS) is interesting on its own +# (see :doc:`this tutorial ` for an in-depth discussion). +# So far, two quantum devices have used large-scale versions of this circuit +# to achieve quantum advantage on a particular computation, which involves sampling from +# a probability distribution that classical computers take too long to simulate. In 2019, USTC's Jiuzhang device took 200 seconds +# to perform this sampling, which would take 2.5 billion years for some of our most powerful supercomputers [#Zhong2020]_. In 2022, +# Xanadu's Borealis performed the same calculation in 36 microseconds, with the added benefit of being programmable +# and available on the Cloud [#Madsen2020]_. +# +# But the most interesting application of GBS comes from removing the PNR in the last wire, as shown below. +# +# .. figure:: ../_static/demonstration_assets/photonics/GKP_Circuit.png +# :align: center +# :width: 70% +# +# .. +# +# Circuit to produce non-Gaussian states probabilistically +# +# Circuits like the above can, after photon detection of the other qumodes, +# produce non-Gaussian states. The reason is that the +# final state of the circuit is entangled, and we apply a non-Gaussian operation to some of the +# qumodes. This measurement affects the remaining qumode, whose state becomes non-Gaussian in general. This is +# the magic of quantum mechanics: due to entanglement, a measurement on a physical system can affect the state of another! Moreover, +# one can show that generalizations of the GBS circuit above can be built to produce any non-Gaussian state that we want [#Tzitrin2020]_. +# +# For example, the choice of parameters +# +# .. math:: t_1 = 0.8624, \quad t_2=0.7688, \quad t_3 = 0.7848, +# .. math:: S_1 = -1.38, \quad S_2 = -1.22, \quad S_3 = 0.780 \quad S_4 = 0.196, +# +# for this generalized GBS circuit produces, with some probability, the following state (expressed as a combination of Fock states) +# +# .. math:: \vert \psi \rangle = S(0.196)\left(0.661 \vert 0\rangle -0.343 \vert 2\rangle + 0.253\vert 4\rangle -0.368\vert 6\rangle +# +0.377 \vert 8\rangle + 0.323 \vert 10\rangle + 0.325\vert 12\rangle\right), +# +# where :math:`S` is the squeezing operator [#Tzitrin2020]_. This state's +# Wigner function is shown below. +# +# .. figure:: ../_static/demonstration_assets/photonics/gkp_wigner.png +# :align: center +# :width: 70% +# +# .. +# +# Wigner function of non-Gaussian state +# +# This Wigner function does not have the shape of a Gaussian and moreover, it can be negative—a tell-tale feature of +# non-Gaussian states (we can only interpret the Wigner function as some sort of probability distribution for the case of Gaussian states!). +# The only issue is that the non-Gaussian state is produced only with some probability, that is, +# *when the detectors measure some particular number of photons*. But, at the very least, we can +# be sure that we have obtained the non-Gaussian state we wanted, and otherwise we +# just discard the qumode. For more precise calculations, you can check out `this +# tutorial `__ from PennyLane's sister library The Walrus, +# which is optimized for simulating this type of circuit. +# +# Encoding qubits into qumodes +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# It's great that we can manipulate quantum states of light so freely, but we haven't discussed how +# to use them for quantum computing. We would like a way to encode qubits into qumodes, so +# that we can run any qubit-based quantum algorithm using qumodes. +# Surely there's more than one way to encode a two-dimensional subspace into an infinite-dimensional +# one. The only problem is that most of these encodings are extremely sensitive to the noise +# affecting the larger space. A way that has proven to be quite robust to errors is to +# encode qubits in states of light is using a special type of non-Gaussian states called *GKP states* [#Gottesman2001]_. +# +# GKP states are linear combinations of the following two basis states: +# +# .. math:: \vert 0 \rangle_{GKP} = \sum_{n} \vert 2n\pi\rangle_x, +# .. math:: \vert 1 \rangle_{GKP} = \sum_{n} \vert (2n+1)\pi\rangle_x, +# +# where the subscript :math:`x` means that the kets in the sum are eigenstates of the quadrature observable +# :math:`\hat{X}.` Therefore, an arbitrary qubit :math:`\vert \psi \rangle = \alpha\vert 0 \rangle + \beta\vert 1 \rangle` +# can be expressed through the qumode as +# +# .. math:: \vert \psi \rangle_{GKP} = \alpha\vert 0 \rangle_{GKP} + \beta\vert 1 \rangle_{GKP}. +# +# The only problem is that producing these GKP states is physically impossible, doing so would require infinite energy. +# Instead, we can produce approximate versions of them and still +# run a quantum computation with great precision. In fact, the GBS circuit we built to produce non-Gaussian states can also produce +# approximate GKP states. This will only happen when we measure 5 and 7 photons in each of the detectors [#Tzitrin2020]_. The probability of this happening +# is rather small but finite. +# +# We can remain within the subspace spanned by the GKP basis states by restricting the operations we apply +# on our qumodes. For example, we see that applying a displacement by :math:`\sqrt{\pi}` to :math:`\vert 0 \rangle_{GKP}` gives the +# :math:`\vert 1 \rangle_{GKP}` state, and vice versa. Therefore, the displacement operator corresponds to the qubit +# bit-flip gate :class:`~pennylane.PauliX`. Similarly, a rotation operator by :math:`\pi/2` implements the :class:`~pennylane.Hadamard` gate. +# The table below gives more detail on how to implement all the gates we need for +# universal quantum computation using optical gates on exact GKP states [#Bourassa2021]_ (on approximate GKP states, the effects of these +# gates will be approximate on the qubit level). +# +# +# .. rst-class:: docstable +# +# +---------------------+----------------------------------------------------------------------------+---------------------------------------------------------------------+ +# | .. centered:: | .. centered:: | .. centered:: | +# | Qumode Gate | Optical Diagram | Qubit gate on GKP states | +# +=====================+============================================================================+=====================================================================+ +# | Displacement | .. figure:: ../_static/demonstration_assets/photonics/Displacement.png | *Pauli X* gate if the displacement is by :math:`\sqrt{\pi}` in | +# | | :align: center | the :math:`x`-direction. *Pauli Z* if the same displacement is | +# | | :width: 70% | in the :math:`p`-direction | +# +---------------------+----------------------------------------------------------------------------+---------------------------------------------------------------------+ +# | Rotation | .. figure:: ../_static/demonstration_assets/photonics/Rotation.png | *Hadamard* gate for :math:`\phi=\frac{\pi}{2}.` | +# | | :align: center | | +# | | :width: 70% | | +# +---------------------+----------------------------------------------------------------------------+---------------------------------------------------------------------+ +# | Continuous variable | .. figure:: ../_static/demonstration_assets/photonics/CV_ctrlz.png | The squeezing parameter is given by :math:`r=\sinh^{-1}(1/2)` and | +# | CNOT | :align: center | the beamsplitters have :math:`T=\frac{1}{4}(1-\tanh(r)).` | +# | | :width: 100% | Applies a *Control-Z* operation on the GKP states when | +# | | | :math:`\phi = 0` and a *CNOT* operation when :math:`\phi=\pi/2.` | +# | | | | +# | | | | +# | | | | +# +---------------------+----------------------------------------------------------------------------+---------------------------------------------------------------------+ +# | Magic state | .. figure:: ../_static/demonstration_assets/photonics/Tgate.png | We use an auxiliary *magic state* :math:`\vert M\rangle,` | +# | teleportation | :align: center | which is the GKP state | +# | | :width: 100% | :math:`\vert M\rangle = \vert +\rangle +e^{i\pi/4} \vert -\rangle,` | +# | | | and a :math:`\hat{P}` homodyne measurement. If we measure | +# | | | :math:`\vert -\rangle,` we apply the shown rotations and squeezers | +# | | | with :math:`r=\cosh^{-1}(3/4),` :math:`\theta=\tan^{-1}(1/2),` | +# | | | and :math:`\phi=-\pi/2-\theta,` resulting in a GKP *T gate*. | +# +---------------------+----------------------------------------------------------------------------+---------------------------------------------------------------------+ +# +# Even if their effect is approximate, these gates are quick +# and quite straightforward to implement with our current technology. Therefore, we have all the ingredients to build a universal +# quantum computer using photons, summarized in the formula (see `this medium article `__): +# +# .. figure:: ../_static/demonstration_assets/photonics/formula_qc.png +# :align: center +# :width: 70% +# +# The state of the art +# ~~~~~~~~~~~~~~~~~~~~ +# +# We have now learned the basics of how to build a quantum computer using photonics. So what challenges +# are there to overcome for scaling further? +# Let us analyze what we have learned in terms of Di Vincenzo's criteria, so we can understand +# what Xanadu is doing to achieve the ambitious goal of building a million-qubit quantum computer. +# +# Looking at the first criterion, we already know that our qubits are far from perfect. +# Photonics rely on imperfect realizations of GKP states, which in turn makes quantum computations only approximate. +# Moreover, while we know a lot about GKP states, it is not easy to characterize them after taking into account the noise, +# so the qubits are not as well-defined as we would like. But our qubits are scalable: GBS circuits can be built on +# small chips, which we can stack and connect together using optical fibers. Moreover, compared to other implementations where +# low temperatures are needed everywhere, in photonic quantum computers we only need them for the PNRs to work. +# Since cryogenics are a bulky part of quantum computing architectures, photonic technology promises to be more scalable than, for example, +# :doc:`trapped ion ` or :doc:`superconducting ` devices. +# +# The second criterion, the ability to prepare a qubit, is clearly a challenge. We need GKP states, +# but these cannot be prepared deterministically; we need to get a bit lucky. +# We can bypass this by *multiplexing*, that is, using many +# Gaussian Boson Sampling circuits in parallel. Moreover, higher-quality GKP states need larger circuits, +# which in turn can decrease the probability of qubit production. How can we try to solve this? +# Xanadu is currently following a hybrid approach. +# When we fail to produce a GKP state, Xanadu's architecture produces squeezed states using a separate squeezer. +# Strongly-entangled squeezed states are a precious resource, since other encodings beyond GKP allow us +# to use these states as a resource for (non-universal) quantum computing [#Bourassa2021]_. +# +# The third criterion of long decoherence times seems innocuous at a first glance. However, although the quantum state of individual photons +# is robust, we do need to minimize the number of photons that escape to the environment. Recall that all quantum states +# of light are superpositions of Fock states. If a photon escapes, our state changes! The technology for the minimization of photon loss +# has come a long way, but it's not perfect yet. We can avoid losses by optimizing the depth of our circuits, so that photons +# have a smaller probability of escaping. The GKP encoding also works in our favour, since it is robust against noise and small +# displacements from GKP states can be easily steered back. +# +# The fourth criterion is pretty much satisfied by photonic quantum computers. We have seen that we can perform universal computations +# using Gaussian operations, provided that we have GKP states. Barring our imperfect qubits, quantum computing +# gates are straightforward to implement with good precision inside a chip. Moreover, entangling photons is relatively easy using +# common optical devices, as opposed to other technologies that rely on rather complicated and slow gates. +# +# Finally, we need to be able to measure qubits. Homodyne detection can be done easily and with great precision. In general, +# we do not need to measure the number of photons at the end of a quantum computation, quadrature measurement is enough to distinguish quantum +# states. The fancy PNRs are only required for qubit production! +# +# .. figure:: ../_static/demonstration_assets/photonics/chip.png +# :align: center +# :width: 40% +# +# .. +# +# Xanadu's X8 Gaussian Boson Sampling chip. Variants of this chip can be used to generate approximate GKP states. +# +# +# Conclusion +# ~~~~~~~~~~ +# +# The approach of photonic devices to quantum computing is quite different from other technologies. Recent theoretical +# and technological developments have given a boost to their status as a scalable approach, although the generation of qubits +# remains a challenge to overcome. The variety of ways that we can encode +# qubits into photonic states leave plenty of room for creativity, and opens the +# door for further research and engineering breakthroughs. If you would like to learn more about photonics, make sure to +# check out the `Strawberry Fields demos `__, +# as well as the references listed below. +# +# References +# ~~~~~~~~~~ +# +# .. [#DiVincenzo2000] +# +# D. DiVincenzo. (2000) "The Physical Implementation of Quantum Computation", +# `Fortschritte der Physik 48 (9–11): 771–783 +# `__. +# (`arXiv `__) +# +# .. [#Weedbrook2012] +# +# C. Weedbrook, et al. (2012) "Gaussian Quantum Information", +# `Rev. Mod. Phys. 84, 621 +# `__. +# (`arXiv `__) +# +# .. [#Sabouri2021] +# +# S. Sabouri, et al. (2021) "Thermo Optical Phase Shifter With Low Thermal Crosstalk for SOI Strip Waveguide" +# `IEEE Photonics Journal vol. 13, no. 2, 6600112 +# `__. +# +# .. [#Paris1996] +# +# M. Paris. (1996) "Displacement operator by beam splitter", +# `Physics Letters A, 217 (2-3): 78-80 +# `__. +# +# .. [#Braunstein2005] +# +# S. Braunstein, P. van Loock. (2005) "Quantum information with continuous variables", +# `Rev. Mod. Phys. 77, 513 +# `__. +# (`arXiv `__) +# +# .. [#Hamilton2017] +# +# C. Hamilton , et al. (2017) "Gaussian Boson Sampling", +# `Phys. Rev. Lett. 119, 170501 +# `__. +# (`arXiv `__) +# +# .. [#Zhong2020] +# +# H.S. Zhong, et al. (2020) "Quantum computational advantage using photons", +# `Science 370, 6523: 1460-1463 +# `__. +# (`arXiv `__) +# +# .. [#Madsen2020] +# +# L. Madsen, et al. (2022) "Quantum computational advantage with a programmable photonic processor" +# `Nature 606, 75-81 +# `__. +# +# .. [#Tzitrin2020] +# +# I. Tzitrin, et al. (2020) "Progress towards practical qubit computation using approximate Gottesman-Kitaev-Preskill codes" +# `Phys. Rev. A 101, 032315 +# `__. +# (`arXiv `__) +# +# .. [#Gottesman2001] +# +# D. Gotesman, A. Kitaev, J. Preskill. (2001) "Encoding a qubit in an oscillator", +# `Phys. Rev. A 64, 012310 +# `__. +# (`arXiv `__) +# +# .. [#Bourassa2021] +# +# E. Bourassa, et al. (2021) "Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer", +# `Quantum 5, 392 +# `__. +# (`arXiv `__) +# diff --git a/demonstrations_v2/tutorial_photonics/metadata.json b/demonstrations_v2/tutorial_photonics/metadata.json new file mode 100644 index 0000000000..97be9e976b --- /dev/null +++ b/demonstrations_v2/tutorial_photonics/metadata.json @@ -0,0 +1,142 @@ +{ + "title": "Photonic quantum computers", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2022-05-31T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_photonic_QC.png" + } + ], + "seoDescription": "Learn how photonic quantum computers work through code", + "doi": "", + "references": [ + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "D. DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" + }, + { + "id": "Weedbrook2012", + "type": "article", + "title": "Gaussian Quantum Information", + "authors": "C. Weedbrook, et al.", + "year": "2012", + "journal": "Rev. Mod. Phys.", + "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.84.621" + }, + { + "id": "Sabouri2021", + "type": "article", + "title": "Thermo Optical Phase Shifter With Low Thermal Crosstalk for SOI Strip Waveguide", + "authors": "S. Sabouri, et al.", + "year": "2021", + "journal": "IEEE Photonics Journal", + "volume": "13", + "number": "2", + "url": "https://ieeexplore.ieee.org/document/9345963" + }, + { + "id": "Paris1996", + "type": "article", + "title": "Displacement operator by beam splitter", + "authors": "M. Paris", + "year": "1996", + "journal": "Physics Letters A", + "url": "https://www.sciencedirect.com/science/article/abs/pii/0375960196003398?via%3Dihub" + }, + { + "id": "Braunstein2005", + "type": "article", + "title": "Quantum information with continuous variables", + "authors": "S. Braunstein, P. van Loock", + "year": "2005", + "journal": "Rev. Mod. Phys.", + "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.77.513" + }, + { + "id": "Hamilton2017", + "type": "article", + "title": "Gaussian Boson Sampling", + "authors": "C. Hamilton, et al.", + "year": "2017", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.170501" + }, + { + "id": "Zhong2020", + "type": "article", + "title": "Quantum computational advantage using photons", + "authors": "H.S. Zhong, et al.", + "year": "2020", + "journal": "Science", + "url": "https://www.science.org/doi/10.1126/science.abe8770" + }, + { + "id": "Madsen2020", + "type": "article", + "title": "Quantum computational advantage with a programmable photonic processor", + "authors": "L. Madsen, et al.", + "year": "2022", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-022-04725-x" + }, + { + "id": "Tzitrin2020", + "type": "article", + "title": "Progress towards practical qubit computation using approximate Gottesman-Kitaev-Preskill codes", + "authors": "I. Tzitrin, et al.", + "year": "2020", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.64.012310" + }, + { + "id": "Bourassa2021", + "type": "article", + "title": "Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer", + "authors": "E. Bourassa, et al.", + "year": "2021", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-02-04-392/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_trapped_ions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_sc_qubits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "gbs", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/photonic-quantum-computers-demo/7335" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_photonics/requirements.in b/demonstrations_v2/tutorial_photonics/requirements.in new file mode 100644 index 0000000000..8132c3977e --- /dev/null +++ b/demonstrations_v2/tutorial_photonics/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py new file mode 100644 index 0000000000..18e329177d --- /dev/null +++ b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py @@ -0,0 +1,815 @@ +r""" +Post-variational quantum neural networks +======================================== +""" + +###################################################################### +# You're sitting in front of your quantum computer, excitement buzzing through your veins as your +# carefully crafted Ansatz for a variational algorithm is finally ready. But oh buttersticks — +# after a few hundred iterations, your heart sinks as you realize you have encountered the dreaded barren plateau problem, where +# gradients vanish and optimisation grinds to a halt. What now? Panic sets in, but then you remember the new technique +# you read about. You reach into your toolbox and pull out the *post-variational strategy*. This approach shifts +# parameters from the quantum computer to classical computers, ensuring the convergence to a local minimum. By combining +# fixed quantum circuits with a classical neural network, you can enhance trainability and keep your +# research on track. +# +# This tutorial introduces post-variational quantum neural networks with example code from PennyLane and JAX. +# We build variational and post-variational networks through a step-by-step process, and compare their +# performance on the `digits dataset `__. +# +# + +###################################################################### +# Enter post-variational strategies +# --------------------- +# Variational algorithms are proposed to solve optimization problems in chemistry, combinatorial +# optimization and machine learning, with potential quantum advantage [#cerezo2021variational]_. Such algorithms often operate +# by first encoding data :math:`x` into an :math:`n`-qubit quantum state. The quantum state is then +# transformed by an ansatz :math:`U(\theta)`, and the parameters :math:`\theta` are optimized by +# evaluating gradients of the quantum circuit [#schuld2019evaluating]_ and calculating updates of the parameter on a classical +# computer. :doc:`Variational algorithms ` are a prerequisite to this article. +# +# However, many ansätze in the variational strategy face the barren plateau problem [#mcclean2018barren]_, which leads to difficulty in convergence +# using :doc:`gradient-based ` optimization techniques. Due to the general difficulty and lack of training gurantees +# of variational algorithms, here we will develop an alternative training strategy that does not involve tuning +# the quantum circuit parameters. However, we continue to use the variational method as the +# theoretical basis for optimisation. + +###################################################################### +# |image1| +# +# .. |image1| image:: ../_static/demonstration_assets/post-variational_quantum_neural_networks/PVdrawing.jpeg +# :width: 100.0% +# + +###################################################################### +# In this Demo we will also discuss “post-variational strategies” proposed in Ref. [#huang2024postvariational]_. We take the classical combination of +# multiple fixed quantum circuits and find the optimal combination by feeding them through a classical linear model or feed the outputs to a +# multilayer perceptron. We shift tunable parameters from the quantum computer to the classical +# computer, opting for ensemble strategies when optimizing quantum models. This sacrifices +# expressibility [#du2020expressive]_ of the circuit for better trainability of the entire model. Below, we discuss various +# strategies and design principles for constructing individual quantum circuits, where the resulting +# ensembles can be optimized with classical optimisation methods. +# + +###################################################################### +# We compare the post-variational strategies to the conventional variational :doc:`quantum neural network ` in the +# table below. +# + +###################################################################### +# |image2| +# +# .. |image2| image:: ../_static/demonstration_assets/post-variational_quantum_neural_networks/table.png +# :width: 100.0% +# + +###################################################################### +# This example demonstrates how to employ the post-variational quantum neural network on the classical +# machine learning task of image classification. In this demo we will solve the problem of identifying handwritten +# digits of twos and sixes and obtain training performance better than that of variational +# algorithms. This dataset is chosen such that the differences between the variational and post-variational approach +# are shown, but we note that the performances may vary for different datasets. +# + +###################################################################### +# The learning problem +# -------------------- +# + +###################################################################### +# We will begin by training our models on the digits dataset, which we import using `sklearn`. The dataset has greyscale +# images the size of :math:`8\times 8` pixels. We partition :math:`10\%` of the dataset for +# testing. +# + +import pennylane as qml +from pennylane import numpy as np +import jax +from jax import numpy as jnp +import optax +from itertools import combinations +from sklearn.datasets import load_digits +from sklearn.model_selection import train_test_split +from sklearn.neural_network import MLPClassifier +from sklearn.metrics import log_loss +import matplotlib.pyplot as plt +import matplotlib.colors +import warnings +warnings.filterwarnings("ignore") +np.random.seed(42) + +# Load the digits dataset with features (X_digits) and labels (y_digits) +X_digits, y_digits = load_digits(return_X_y=True) + +# Create a boolean mask to filter out only the samples where the label is 2 or 6 +filter_mask = np.isin(y_digits, [2, 6]) + +# Apply the filter mask to the features and labels to keep only the selected digits +X_digits = X_digits[filter_mask] +y_digits = y_digits[filter_mask] + +# Split the filtered dataset into training and testing sets with 10% of data reserved for testing +X_train, X_test, y_train, y_test = train_test_split( + X_digits, y_digits, test_size=0.1, random_state=42 +) + +# Normalize the pixel values in the training and testing data +# Convert each image from a 1D array to an 8x8 2D array, normalize pixel values, and scale them +X_train = np.array([thing.reshape([8, 8]) / 16 * 2 * np.pi for thing in X_train]) +X_test = np.array([thing.reshape([8, 8]) / 16 * 2 * np.pi for thing in X_test]) + +# Adjust the labels to be centered around 0 and scaled to be in the range -1 to 1 +# The original labels (2 and 6) are mapped to -1 and 1 respectively +y_train = (y_train - 4) / 2 +y_test = (y_test - 4) / 2 + + +###################################################################### +# A visualization of a few data points is shown below. +# + +fig, axes = plt.subplots(nrows=2, ncols=3, layout="constrained") +for i in range(2): + for j in range(3): + axes[i][j].matshow(X_train[2*(2*j+i)]) + axes[i][j].axis('off') +fig.subplots_adjust(hspace=0.0) +fig.tight_layout() +plt.show() + +###################################################################### +# Setting up the model +# -------------------- +# +# Here, we will create a simple quantum machine learning (QML) model for optimization. In particular: +# +# - We will embed our data through a series of rotation gates, this is called the feature map. +# - We will then have an ansatz of rotation gates with parameters' weights. +# + +###################################################################### +# For the feature map, each column of the image is encoded into a single qubit, and each row is +# encoded consecutively via alternating rotation-Z and rotation-X gates. The circuit for our feature +# map is shown below. +# + +###################################################################### +# |image3| +# +# .. |image3| image:: ../_static/demonstration_assets/post-variational_quantum_neural_networks/featuremap.png +# :width: 100.0% +# + +###################################################################### +# We use the following circuit as our ansatz. This ansatz is also used as backbone for all our +# post-variational strategies. Note that when we set all initial parameters to 0, the ansatz evaluates to +# identity. +# + +###################################################################### +# |image4| +# +# .. |image4| image:: ../_static/demonstration_assets/post-variational_quantum_neural_networks/ansatz.png +# :width: 100.0% +# + +###################################################################### +# We write code for the above ansatz and feature map as follows. +# + + +def feature_map(features): + # Apply Hadamard gates to all qubits to create an equal superposition state + for i in range(len(features[0])): + qml.Hadamard(i) + + # Apply angle embeddings based on the feature values + for i in range(len(features)): + # For odd-indexed features, use Z-rotation in the angle embedding + if i % 2: + qml.AngleEmbedding(features=features[i], wires=range(8), rotation="Z") + # For even-indexed features, use X-rotation in the angle embedding + else: + qml.AngleEmbedding(features=features[i], wires=range(8), rotation="X") + +# Define the ansatz (quantum circuit ansatz) for parameterized quantum operations +def ansatz(params): + # Apply RY rotations with the first set of parameters + for i in range(8): + qml.RY(params[i], wires=i) + + # Apply CNOT gates with adjacent qubits (cyclically connected) to create entanglement + for i in range(8): + qml.CNOT(wires=[(i - 1) % 8, (i) % 8]) + + # Apply RY rotations with the second set of parameters + for i in range(8): + qml.RY(params[i + 8], wires=i) + + # Apply CNOT gates with qubits in reverse order (cyclically connected) + # to create additional entanglement + for i in range(8): + qml.CNOT(wires=[(8 - 2 - i) % 8, (8 - i - 1) % 8]) +###################################################################### +# Variational approach +# --------------------- +# + +###################################################################### +# As a baseline comparison, we first test the performance of a shallow variational algorithm on the +# digits dataset shown above. We will build the quantum node by combining the above feature map and +# ansatz. +# + +dev = qml.device("default.qubit", wires=8) + + +@qml.qnode(dev) +def circuit(params, features): + feature_map(features) + ansatz(params) + return qml.expval(qml.PauliZ(0)) + + +def variational_classifier(weights, bias, x): + return circuit(weights, x) + bias + + +def square_loss(labels, predictions): + return np.mean((labels - qml.math.stack(predictions)) ** 2) + + +def accuracy(labels, predictions): + acc = sum([np.sign(l) == np.sign(p) for l, p in zip(labels, predictions)]) + acc = acc / len(labels) + return acc + + +def cost(params, X, Y): + predictions = [variational_classifier(params["weights"], params["bias"], x) for x in X] + return square_loss(Y, predictions) + + +def acc(params, X, Y): + predictions = [variational_classifier(params["weights"], params["bias"], x) for x in X] + return accuracy(Y, predictions) + + +np.random.seed(0) +weights = 0.01 * np.random.randn(16) +bias = jnp.array(0.0) +params = {"weights": weights, "bias": bias} +opt = optax.adam(0.05) +batch_size = 7 +num_batch = X_train.shape[0] // batch_size +opt_state = opt.init(params) +X_batched = X_train.reshape([-1, batch_size, 8, 8]) +y_batched = y_train.reshape([-1, batch_size]) + + +@jax.jit +def update_step_jit(i, args): + params, opt_state, data, targets, batch_no = args + _data = data[batch_no % num_batch] + _targets = targets[batch_no % num_batch] + _, grads = jax.value_and_grad(cost)(params, _data, _targets) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state, data, targets, batch_no + 1) + + +@jax.jit +def optimization_jit(params, data, targets): + opt_state = opt.init(params) + args = (params, opt_state, data, targets, 0) + (params, opt_state, _, _, _) = jax.lax.fori_loop(0, 200, update_step_jit, args) + return params + + +params = optimization_jit(params, X_batched, y_batched) +var_train_acc = acc(params, X_train, y_train) +var_test_acc = acc(params, X_test, y_test) + +print("Training accuracy: ", var_train_acc) +print("Testing accuracy: ", var_test_acc) + +###################################################################### +# In this example, the variational algorithm is having trouble finding a global minimum (and this +# problem persists even if we do hyperparameter tuning). On the other hand, given the general applicability +# and consequent hardness of finding suitable ansätze, we introduce three heursitical methods for building +# the set of quantum circuits that make up post-variational quantum neural networks, namely the observable +# construction heuristic, the ansatz expansion heuristic, and a hybrid of the two. +# + +###################################################################### +# Observable construction heuristic +# --------------------- + +###################################################################### +# The observable construction heuristic removes the use of ansätze in the quantum and constructs measurements +# directly on the quantum data embedded state. +# For simplicity, we measure the data embedded state on different combinations of Pauli observables in this +# Demo. We first define a series of :math:`k`-local trial observables +# :math:`O_1, O_2, \ldots , O_m`. After computing the quantum circuits, the measurement results are +# then combined classically, where the optimal weights of each measurement are computed via feeding our +# measurements through a classical multilayer perceptron. +# + +###################################################################### +# We generate the series of :math:`k`-local observables with the following code. +# + +def local_pauli_group(qubits: int, locality: int): + assert locality <= qubits, f"Locality must not exceed the number of qubits." + return list(generate_paulis(0, 0, "", qubits, locality)) + +# This is a recursive generator function that constructs Pauli strings. +def generate_paulis(identities: int, paulis: int, output: str, qubits: int, locality: int): + # Base case: if the output string's length matches the number of qubits, yield it. + if len(output) == qubits: + yield output + else: + # Recursive case: add an "I" (identity) to the output string. + yield from generate_paulis(identities + 1, paulis, output + "I", qubits, locality) + + # If the number of Pauli operators used is less than the locality, add "X", "Y", or "Z" + # systematically builds all possible Pauli strings that conform to the specified locality. + if paulis < locality: + yield from generate_paulis(identities, paulis + 1, output + "X", qubits, locality) + yield from generate_paulis(identities, paulis + 1, output + "Y", qubits, locality) + yield from generate_paulis(identities, paulis + 1, output + "Z", qubits, locality) + + +###################################################################### +# For each image sample, we measure the output of the quantum circuit using the :math:`k`-local observables +# sequence, and perform logistic regression on these outputs. We do this for 1-local, 2-local and +# 3-local observables in the `for`-loop below. +# + +# Initialize lists to store training and testing accuracies for different localities. +train_accuracies_O = [] +test_accuracies_O = [] + +for locality in range(1, 4): + print(str(locality) + "-local: ") + + # Define a quantum device with 8 qubits using the default simulator. + dev = qml.device("default.qubit", wires=8) + + # Define a quantum node (qnode) with the quantum circuit that will be executed on the device. + @qml.qnode(dev) + def circuit(features): + # Generate all possible Pauli strings for the given locality. + measurements = local_pauli_group(8, locality) + + # Apply the feature map to encode classical data into quantum states. + feature_map(features) + + # Measure the expectation values of the generated Pauli operators. + return [qml.expval(qml.pauli.string_to_pauli_word(measurement)) for measurement in measurements] + + # Vectorize the quantum circuit function to apply it to multiple data points in parallel. + vcircuit = jax.vmap(circuit) + + # Transform the training and testing datasets by applying the quantum circuit. + new_X_train = np.asarray(vcircuit(jnp.array(X_train))).T + new_X_test = np.asarray(vcircuit(jnp.array(X_test))).T + + # Train a Multilayer Perceptron (MLP) classifier on the transformed training data. + clf = MLPClassifier(early_stopping=True).fit(new_X_train, y_train) + + # Print the log loss for the training data. + print("Training loss: ", log_loss(y_train, clf.predict_proba(new_X_train))) + + # Print the log loss for the testing data. + print("Testing loss: ", log_loss(y_test, clf.predict_proba(new_X_test))) + + # Calculate and store the training accuracy. + acc = clf.score(new_X_train, y_train) + train_accuracies_O.append(acc) + print("Training accuracy: ", acc) + + # Calculate and store the testing accuracy. + acc = clf.score(new_X_test, y_test) + test_accuracies_O.append(acc) + print("Testing accuracy: ", acc) + print() + +locality = ("1-local", "2-local", "3-local") +train_accuracies_O = [round(value, 2) for value in train_accuracies_O] +test_accuracies_O = [round(value, 2) for value in test_accuracies_O] +x = np.arange(3) +width = 0.25 + +# Create a bar plot to visualize the training and testing accuracies. +fig, ax = plt.subplots(layout="constrained") +# Training accuracy bars: +rects = ax.bar(x, train_accuracies_O, width, label="Training", color="#FF87EB") +# Testing accuracy bars: +rects = ax.bar(x + width, test_accuracies_O, width, label="Testing", color="#70CEFF") +ax.bar_label(rects, padding=3) +ax.set_xlabel("Locality") +ax.set_ylabel("Accuracy") +ax.set_title("Accuracy of different localities") +ax.set_xticks(x + width / 2, locality) +ax.legend(loc="upper left") +plt.show() + + +###################################################################### +# We can see that the highest accuracy is achieved with the 3-local observables, which gives the +# classical model the most information about the outputs of the circuit. However, this is much +# more computationally resource heavy than its lower-locality counterparts. Note, however, that the +# complexity of the observable construction method for local observables can be vastly decreased by +# introducing the usage of classical shadows. [#huang2020predicting]_ +# + +###################################################################### +# Ansatz expansion heuristic +# --------------------- +# + +###################################################################### +# The ansatz expansion approach does model approximation by directly expanding the parameterised +# ansatz into an ensemble of fixed ansätze. Starting from a variational ansatz, multiple +# non-parameterized quantum circuits are constructed by Taylor expansion of the ansatz around a +# suitably chosen initial setting of the parameters :math:`\theta_0`, which we set here as 0. Gradients and higher-order +# derivatives of circuits then can be obtained by the :doc:`parameter-shift rule `. +# The output sof the different circuits are then fed +# into a classical neural network. +# + +###################################################################### +# The following code is used to generate a series of fixed parameters that can be encoded into the +# ansatz, using the above method. +# + +def deriv_params(thetas: int, order: int): + # This function generates parameter shift values for calculating derivatives + # of a quantum circuit. + # 'thetas' is the number of parameters in the circuit. + # 'order' determines the order of the derivative to calculate (1st order, 2nd order, etc.). + + def generate_shifts(thetas: int, order: int): + # Generate all possible combinations of parameters to shift for the given order. + shift_pos = list(combinations(np.arange(thetas), order)) + + # Initialize a 3D array to hold the shift values. + # Shape: (number of combinations, 2^order, thetas) + params = np.zeros((len(shift_pos), 2 ** order, thetas)) + + # Iterate over each combination of parameter shifts. + for i in range(len(shift_pos)): + # Iterate over each possible binary shift pattern for the given order. + for j in range(2 ** order): + # Convert the index j to a binary string of length 'order'. + for k, l in enumerate(f"{j:0{order}b}"): + # For each bit in the binary string: + if int(l) > 0: + # If the bit is 1, increment the corresponding parameter. + params[i][j][shift_pos[i][k]] += 1 + else: + # If the bit is 0, decrement the corresponding parameter. + params[i][j][shift_pos[i][k]] -= 1 + + # Reshape the parameters array to collapse the first two dimensions. + params = np.reshape(params, (-1, thetas)) + return params + + # Start with a list containing a zero-shift array for all parameters. + param_list = [np.zeros((1, thetas))] + + # Append the generated shift values for each order from 1 to the given order. + for i in range(1, order + 1): + param_list.append(generate_shifts(thetas, i)) + + # Concatenate all the shift arrays along the first axis to create the final parameter array. + params = np.concatenate(param_list, axis=0) + + # Scale the shift values by π/2. + params *= np.pi / 2 + + return params + + +###################################################################### +# We construct the circuit and measure the top qubit with Pauli-Z. +# + +n_wires = 8 +dev = qml.device("default.qubit", wires=n_wires) + +@jax.jit +@qml.qnode(dev, interface="jax") +def circuit(features, params, n_wires=8): + feature_map(features) + ansatz(params) + return qml.expval(qml.PauliZ(0)) + +###################################################################### +# For each image sample, we measure the outputs of each parameterised circuit for each feature, and +# feed the outputs into a multilayer perceptron. +# + +# Initialize lists to store training and testing accuracies for different derivative orders. +train_accuracies_AE = [] +test_accuracies_AE = [] + +# Loop through different derivative orders (1st order, 2nd order, 3rd order). +for order in range(1, 4): + print("Order number: " + str(order)) + + # Generate the parameter shifts required for the given derivative order. + to_measure = deriv_params(16, order) + + # Transform the training dataset by applying the quantum circuit with the + # generated parameter shifts. + new_X_train = [] + for thing in X_train: + result = circuit(thing, to_measure.T) + new_X_train.append(result) + + # Transform the testing dataset similarly. + new_X_test = [] + for thing in X_test: + result = circuit(thing, to_measure.T) + new_X_test.append(result) + + # Train a Multilayer Perceptron (MLP) classifier on the transformed training data. + clf = MLPClassifier(early_stopping=True).fit(new_X_train, y_train) + + # Print the log loss for the training data. + print("Training loss: ", log_loss(y_train, clf.predict_proba(new_X_train))) + + # Print the log loss for the testing data. + print("Testing loss: ", log_loss(y_test, clf.predict_proba(new_X_test))) + + # Calculate and store the training accuracy. + acc = clf.score(new_X_train, y_train) + train_accuracies_AE.append(acc) + print("Training accuracy: ", acc) + + # Calculate and store the testing accuracy. + acc = clf.score(new_X_test, y_test) + test_accuracies_AE.append(acc) + print("Testing accuracy: ", acc) + print() + +locality = ("1-order", "2-order", "3-order") +train_accuracies_AE = [round(value, 2) for value in train_accuracies_AE] +test_accuracies_AE = [round(value, 2) for value in test_accuracies_AE] +x = np.arange(3) +width = 0.25 +fig, ax = plt.subplots(layout="constrained") +rects = ax.bar(x, train_accuracies_AE, width, label="Training", color="#FF87EB") +ax.bar_label(rects, padding=3) +rects = ax.bar(x + width, test_accuracies_AE, width, label="Testing", color="#70CEFF") +ax.bar_label(rects, padding=3) +ax.set_xlabel("Order") +ax.set_ylabel("Accuracy") +ax.set_title("Accuracy of different derivative orders") +ax.set_xticks(x + width / 2, locality) +ax.legend(loc="upper left") +plt.show() + +###################################################################### +# Note that similar to the obsewrvable construction method, higher orders give higher testing accuracy. +# However, it is similarly more computationally expensive to execute. +# + +###################################################################### +# Hybrid strategy +# --------------------- +# + +###################################################################### +# When taking the strategy of observable construction, one additionally may want to use ansatz +# quantum circuits to increase the complexity of the model. Hence, we discuss a simple hybrid +# strategy that combines both the usage of ansatz expansion and observable construction. For each +# feature, we may first expand the ansatz with each of our parameters, then use each :math:`k`-local +# observable to conduct measurements. +# +# Due to the high number of circuits that need to be computed with this strategy, one may choose to +# further prune the circuits used in training, but this is not conducted in this demo. +# +# Note that in our example, we have only tested 3 hybrid samples to reduce the running time of this +# script, but one may choose to try other combinations of the 2 strategies to potentially obtain +# better results. +# + +# Initialize matrices to store training and testing accuracies for different +# combinations of locality and order. +train_accuracies = np.zeros([4, 4]) +test_accuracies = np.zeros([4, 4]) + +# Loop through different derivative orders (1st to 3rd) and localities (1-local to 3-local). +for order in range(1, 4): + for locality in range(1, 4): + # Skip invalid combinations where locality + order exceeds 3 or equals 0. + if locality + order > 3 or locality + order == 0: + continue + print("Locality: " + str(locality) + " Order: " + str(order)) + + # Define a quantum device with 8 qubits using the default simulator. + dev = qml.device("default.qubit", wires=8) + + # Generate the parameter shifts required for the given derivative order and transpose them. + params = deriv_params(16, order).T + + # Define a quantum node (qnode) with the quantum circuit that will be executed on the device. + @qml.qnode(dev) + def circuit(features, params): + # Generate the Pauli group for the given locality. + measurements = local_pauli_group(8, locality) + feature_map(features) + ansatz(params) + # Measure the expectation values of the generated Pauli operators. + return [qml.expval(qml.pauli.string_to_pauli_word(measurement)) for measurement in measurements] + + # Vectorize the quantum circuit function to apply it to multiple data points in parallel. + vcircuit = jax.vmap(circuit) + + # Transform the training dataset by applying the quantum circuit with the + # generated parameter shifts. + new_X_train = np.asarray( + vcircuit(jnp.array(X_train), jnp.array([params for i in range(len(X_train))])) + ) + # Reorder the axes and reshape the transformed data for input into the classifier. + new_X_train = np.moveaxis(new_X_train, 0, -1).reshape( + -1, len(local_pauli_group(8, locality)) * len(deriv_params(16, order)) + ) + + # Transform the testing dataset similarly. + new_X_test = np.asarray( + vcircuit(jnp.array(X_test), jnp.array([params for i in range(len(X_test))])) + ) + # Reorder the axes and reshape the transformed data for input into the classifier. + new_X_test = np.moveaxis(new_X_test, 0, -1).reshape( + -1, len(local_pauli_group(8, locality)) * len(deriv_params(16, order)) + ) + + # Train a Multilayer Perceptron (MLP) classifier on the transformed training data. + clf = MLPClassifier(early_stopping=True).fit(new_X_train, y_train) + + # Calculate and store the training and testing accuracies. + train_accuracies[order][locality] = clf.score(new_X_train, y_train) + test_accuracies[order][locality] = clf.score(new_X_test, y_test) + + print("Training loss: ", log_loss(y_train, clf.predict_proba(new_X_train))) + print("Testing loss: ", log_loss(y_test, clf.predict_proba(new_X_test))) + acc = clf.score(new_X_train, y_train) + train_accuracies[locality][order] = acc + print("Training accuracy: ", acc) + acc = clf.score(new_X_test, y_test) + test_accuracies[locality][order] = acc + print("Testing accuracy: ", acc) + print() + +###################################################################### +# Upon obtaining our hybrid results, we may now combine these results with that of the observable +# construction and ansatz expansion menthods, and plot all the post-variational strategies together on +# a heatmap. +# + +for locality in range(1, 4): + train_accuracies[locality][0] = train_accuracies_O[locality - 1] + test_accuracies[locality][0] = test_accuracies_O[locality - 1] +for order in range(1, 4): + train_accuracies[0][order] = train_accuracies_AE[order - 1] + test_accuracies[0][order] = test_accuracies_AE[order - 1] + +train_accuracies[3][3] = var_train_acc +test_accuracies[3][3] = var_test_acc + +cvals = [0, 0.5, 0.85, 0.95, 1] +colors = ["black", "#C756B2", "#FF87EB", "#ACE3FF", "#D5F0FD"] +norm = plt.Normalize(min(cvals), max(cvals)) +tuples = list(zip(map(norm, cvals), colors)) +cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", tuples) + + +locality = ["top qubit\n Pauli-Z", "1-local", "2-local", "3-local"] +order = ["0th Order", "1st Order", "2nd Order", "3rd Order"] + +fig, axes = plt.subplots(nrows=1, ncols=2, layout="constrained") +im = axes[0].imshow(train_accuracies, cmap=cmap, origin="lower") + +axes[0].set_yticks(np.arange(len(locality)), labels=locality) +axes[0].set_xticks(np.arange(len(order)), labels=order) +plt.setp(axes[0].get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") +for i in range(len(locality)): + for j in range(len(order)): + text = axes[0].text( + j, i, np.round(train_accuracies[i, j], 2), ha="center", va="center", color="black" + ) +axes[0].text(3, 3, '\n\n(VQA)', ha="center", va="center", color="black") + +axes[0].set_title("Training Accuracies") + +locality = ["top qubit\n Pauli-Z", "1-local", "2-local", "3-local"] +order = ["0th Order", "1st Order", "2nd Order", "3rd Order"] + +im = axes[1].imshow(test_accuracies, cmap=cmap, origin="lower") + +axes[1].set_yticks(np.arange(len(locality)), labels=locality) +axes[1].set_xticks(np.arange(len(order)), labels=order) +plt.setp(axes[1].get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") +for i in range(len(locality)): + for j in range(len(order)): + text = axes[1].text( + j, i, np.round(test_accuracies[i, j], 2), ha="center", va="center", color="black" + ) +axes[1].text(3, 3, '\n\n(VQA)', ha="center", va="center", color="black") + +axes[1].set_title("Test Accuracies") +fig.tight_layout() +plt.show() + +###################################################################### +# Experimental results +# -------------------- +# + +###################################################################### +# This demonstration shows that all used hybrid methods exceed the variational algorithm while using the same +# ansatz for the ansatz expansion and hybrid strategies. However, we do not expect all post-variational methods to outperform variational algorithm. +# For example, the ansatz expansion up to the first order is likely to be worse than the variational approach, as it is merely a one-step gradient update. +# +# From these performance results, we can obtain a glimpse of the effectiveness of each strategy. +# The inclusion of 1-local and 2-local observables provides a boost in accuracy when used +# in conjunction with first-order derivatives in the hybrid strategy. This implies that the addition +# of the observable expansion strategy can serve as a heuristic to expand the expressibility to +# ansatz expansion method, which in itself may not be sufficient as a good training strategy. +# + +###################################################################### +# Conclusion +# --------------------- +# + +###################################################################### +# This tutorial demonstrates post-variational quantum neural networks [#huang2024postvariational]_, +# an alternative implementation of quantum neural networks in the NISQ setting. +# In this tutorial, we have implemented the post variational strategies to classify handwritten digits +# of twos and sixes. +# +# Given a well-selected set of good fixed ansätze, the post-variational method involves training classical +# neural networks, to which we can employ techniques to ensure good trainability. While this property of +# post-variational methods provides well-optimised result based on the set of ansätze given, +# the barren plateau problems or the related exponential concentration are not directly resolved. The hardness of the problem is +# instead delegated to the selection of the set of fixed ansätze from an exponential amount of +# possible quantum circuits, which one can find using the three heuristical strategies introduced in this tutorial. +# +# + +###################################################################### +# +# References +# --------------------- +# +# .. [#cerezo2021variational] +# +# M. Cerezo, A. Arrasmith, R. Babbush, S. C. Benjamin, S. Endo, K. Fujii, +# J. R. McClean, K. Mitarai, X. Yuan, L. Cincio, and P. J. Coles, +# Variational quantum algorithms, +# `Nat. Rev. Phys. 3, 625, (2021) `__. +# +# +# .. [#schuld2019evaluating] +# +# M. Schuld, V. Bergholm, C. Gogolin, J. Izaac, and N. Killoran, +# Evaluating analytic gradients on quantum hardware, +# `Phys. Rev. A. 99, 032331, (2019) `__. +# +# +# .. [#mcclean2018barren] +# +# J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven, +# Barren plateaus in quantum neural network training landscapes, +# `Nat. Commun. 9, 4812, (2018) `__. +# +# +# .. [#huang2024postvariational] +# +# P.-W. Huang and P. Rebentrost, +# Post-variational quantum neural networks (2024), +# `arXiv:2307.10560 [quant-ph] `__. +# +# +# .. [#du2020expressive] +# +# Y. Du, M.-H. Hsieh, T. Liu, and D. Tao, +# Expressive power of parametrized quantum circuits, +# `Phys. Rev. Res. 2, 033125 (2020) `__. +# +# +# .. [#huang2020predicting] +# +# H.-Y. Huang, R. Kueng, and J. Preskill, +# Predicting many properties of a quantum system from very few measurements, +# `Nat. Phys. 16, 1050–1057 (2020) `__. +# +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json new file mode 100644 index 0000000000..99d9d748f8 --- /dev/null +++ b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json @@ -0,0 +1,136 @@ +{ + "title": "Post Variational Quantum Neural Networks", + "authors": [ + { + "username": "elainazhu" + }, + { + "username": "georgepwhuang" + } + ], + "dateOfPublication": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_post-variational_quantum_neural_networks.png" + }, + { + "type": "large_thumbnail", + "uri": "_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_post-variational_quantum_neural_networks.png" + } + ], + "seoDescription": "Learn about post-variational quantum neural networks", + "doi": "", + "references": [ + { + "id": "cerezo2021variational", + "type": "article", + "title": "Variational quantum algorithms", + "authors": "Marco Cerezo and Andrew Arrasmith and Ryan Babbush and Simon C. Benjamin and Suguru Endo and Keisuke Fujii and Jarrod R. McClean and Kosuke Mitarai and Xiao Yuan and Lukasz Cincio and Patrick J. Coles", + "year": "2021", + "publisher": "Nature Portfolio", + "journal": "Nat. Rev. Phys.", + "doi": "10.1038/s42254-021-00348-9", + "url": "https://doi.org/10.1038/s42254-021-00348-9" + }, + { + "id": "mcclean2018barren", + "type": "article", + "title": "Barren plateaus in quantum neural network training landscapes", + "authors": "Jarrod R. McClean and Sergio Boixo and Vadim N. Smelyanskiy and Ryan Babbush and Hartmut Neven", + "year": "2018", + "publisher": "Nature Portfolio", + "journal": "Nat. Commun.", + "doi": "10.1038/s41467-018-07090-4", + "url": "https://doi.org/10.1038/s41467-018-07090-4" + }, + { + "id": "du2020expressive", + "type": "article", + "title": "Expressive power of parametrized quantum circuits", + "authors": "Du, Yuxuan and Hsieh, Min-Hsiu and Liu, Tongliang and Tao, Dacheng", + "year": "2020", + "publisher": "American Physical Society", + "journal": "Phys. Rev. Res.", + "doi": "10.1103/PhysRevResearch.2.033125", + "url": "https://doi.org/10.1103/PhysRevResearch.2.033125" + }, + { + "id": "schuld2019evaluating", + "type": "article", + "title": "Evaluating analytic gradients on quantum hardware", + "authors": "Schuld, Maria and Bergholm, Ville and Gogolin, Christian and Izaac, Josh and Killoran, Nathan", + "year": "2019", + "publisher": "American Physical Society", + "journal": "Phys. Rev. A", + "doi": "10.1103/PhysRevA.99.032331", + "url": "https://doi.org/10.1103/PhysRevA.99.032331" + }, + { + "id": "huang2024postvariational", + "type": "article", + "title": "Post-variational quantum neural networks", + "authors": "Po-Wei Huang and Patrick Rebentrost", + "year": "2024", + "doi": "10.48550/arXiv.2307.10560", + "url": "https://arxiv.org/abs/2307.10560" + }, + { + "id": "huang2020predicting", + "type": "article", + "title": "Predicting many properties of a quantum system from very few measurements", + "authors": "Hsin-Yuan Huang and Richard Kueng and John Preskill", + "year": "2024", + "doi": "10.1038/s41567-020-0932-7", + "url": "https://doi.org/10.1038/s41567-020-0932-7" + } + ], + "basedOnPapers": [ + "https://arxiv.org/abs/2307.10560" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ensemble_multi_qpu", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in new file mode 100644 index 0000000000..2315fdb489 --- /dev/null +++ b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in @@ -0,0 +1,6 @@ +jax +jaxlib +matplotlib +optax +pennylane +scikit-learn diff --git a/demonstrations_v2/tutorial_pulse_programming101/demo.py b/demonstrations_v2/tutorial_pulse_programming101/demo.py new file mode 100644 index 0000000000..54bfe10d58 --- /dev/null +++ b/demonstrations_v2/tutorial_pulse_programming101/demo.py @@ -0,0 +1,484 @@ +r"""Differentiable pulse programming with qubits in PennyLane +============================================================= + +.. meta:: + :property="og:description": Simulating differentialble pulse programs in PennyLane with qubits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_pulse_programming.png + +.. related:: + ahs_aquila Pulse programming on neutral atom hardware + +Author: Korbinian Kottmann — Posted: 8 March 2023. + +Quantum computers perform gates via electromagnetic pulses on the hardware level. In differentiable pulse programming, we +can write quantum algorithms directly on the hardware level and variationally optimize the shape, phase and amplitude of the interactions +for our desired goals. +In this demo, we are going to introduce pulse programming with qubits in PennyLane and run the +ctrl-VQE algorithm [#Mitei]_ on a two-qubit Hamiltonian for the :math:`\text{HeH}^+` molecule. + +| + +.. figure:: ../_static/demonstration_assets/pulse_programming101/pulse_illustration.png + :align: center + :width: 50% + :target: javascript:void(0) + +| + +Pulses in quantum computers +--------------------------- + +In many quantum computing architectures such as `superconducting `_, `ion trap `_ +and `neutral atom Rydberg `_ systems, +qubits are realized through physical systems with a discrete set of energy levels. +For example, transmon qubits realize an anharmonic oscillator whose ground and first excited states can serve as the two energy +levels of a qubit. Such a qubit can be controlled via an electromagnetic field tuned to its energy gap. In general, this +electromagnetic field can be altered in time, leading to a time-dependent Hamiltonian :math:`H(t)` describing the effect of the field on the qubits. +We call driving the system with such an electromagnetic field for a fixed time window :math:`[t_0, t_1]` a *pulse sequence*. +During a pulse sequence, the state evolves according to the time-dependent Schrödinger equation + +.. math:: \frac{d}{dt}|\psi\rangle = -i H(t) |\psi\rangle + +from an initial state :math:`|\psi(t_0)\rangle` to a final state :math:`|\psi(t_1)\rangle.` This process corresponds to a unitary evolution :math:`U(t_0, t_1)` +of the input state from time :math:`t_0` to :math:`t_1,` i.e. :math:`|\psi(t_1)\rangle = U(t_0, t_1) |\psi(t_0)\rangle.` + +In most digital quantum computers (with the exception of `measurement-based `_ architectures), the amplitude and frequencies of predefined pulse sequences are +fine tuned to realize the native gates of the quantum computer. More specifically, the Hamiltonian interaction :math:`H(t)` +is tuned such that the respective evolution :math:`U(t_0, t_1)` realizes for example a Pauli or CNOT gate (see e.g. *cross-resonance* gates for superconducting qubits in [#Sheldon2016]_). + +Pulse programming in PennyLane +------------------------------ + +A user of a quantum computer typically operates on the higher and more abstract gate level. +Future fault-tolerant quantum computers require this abstraction to allow for error correction. +For noisy and intermediate-sized quantum computers, the abstraction of decomposing quantum algorithms +into a fixed native gate set can be a hindrance and unnecessarily increase execution time, therefore leading +to more noise in the computation. The idea of differentiable pulse programming is to optimize quantum circuits on the pulse +level instead, with the aim of achieving the shortest interaction sequence a hardware system allows. + +In PennyLane, we can simulate arbitrary qubit system interactions to explore the possibilities of such pulse programs. +First, we need to define the time-dependent Hamiltonian :math:`H(p, t)= \sum_i f_i(p_i, t) H_i` with constant operators :math:`H_i` and control fields :math:`f_i(p_i, t).` +The Hamiltonian depends on the set of parameters :math:`p = \{p_i\}.` One way to do this in PennyLane is in the following way: +""" + +import pennylane as qml +import numpy as np +import jax.numpy as jnp +import jax +import matplotlib.pyplot as plt + +# Set to float64 precision and remove jax CPU/GPU warning +jax.config.update("jax_enable_x64", True) +jax.config.update("jax_platform_name", "cpu") + + +def f1(p, t): + # polyval(p, t) evaluates a polynomial of degree N=len(p) + # i.e. p[0]*t**(N-1) + p[1]*t**(N-2) + ... + p[N-2]*t + p[N-1] + return jnp.polyval(p, t) + + +def f2(p, t): + return p[0] * jnp.sin(p[1] * t) + + +Ht = f1 * qml.PauliX(0) + f2 * qml.PauliY(1) + +############################################################################## +# This constructs a :class:`~pennylane.pulse.ParametrizedHamiltonian`. Note that the ``callable`` functions ``f1`` and ``f2`` +# are expected to have the fixed signature ``(p, t)``. When calling the :class:`~pennylane.pulse.ParametrizedHamiltonian`, +# a ``tuple`` or ``list`` of the parameters for each of the functions is passed in the same +# order the Hamiltonian was constructed. + +p1 = jnp.ones(5) # parameters for f1 +p2 = jnp.array([1.0, jnp.pi]) # parameters for f2 +t = 0.5 # some fixed point in time +print(Ht((p1, p2), t)) # order of parameters p1, p2 matters + +############################################################################## +# We can construct general Hamiltonians of the form :math:`\sum_i H_i^d + \sum_i f_i(p_i, t) H_i` +# using :func:`qml.dot `. Such a time-dependent Hamiltonian consists of time-independent drift terms :math:`H_i^d` +# and time-dependent control terms :math:`f_i(p_i, t) H_i` with scalar complex-valued functions :math:`f_i(p, t).` +# In the following we are going to construct :math:`\sum_i X_i X_{i+1} + \sum_i f_i(p_i, t) Z_i` with :math:`f_i(p_i, t) = \sin(p_i^0 t) + \sin(p_i^1 t) \forall i` as an example: + +coeffs = [1.0] * 2 +coeffs += [lambda p, t: jnp.sin(p[0] * t) + jnp.sin(p[1] * t) for _ in range(3)] +ops = [qml.PauliX(i) @ qml.PauliX(i + 1) for i in range(2)] +ops += [qml.PauliZ(i) for i in range(3)] + +Ht = qml.dot(coeffs, ops) + +# random coefficients +key = jax.random.PRNGKey(777) +subkeys = jax.random.split(key, 3) # create list of 3 subkeys +params = [jax.random.uniform(subkeys[i], shape=[2], maxval=5) for i in range(3)] +print(Ht(params, 0.5)) + +############################################################################## +# We can visualize the Hamiltonian interaction by plotting the time-dependent envelopes. We refer to the drift term as all constant terms in time, i.e. :math:`\sum_i X_i X_{i+1},` +# and plot the envelopes :math:`f_i(p_i, t)` of the time-dependent terms :math:`f_i(p_i, t) Z_i.` + +ts = jnp.linspace(0.0, 5.0, 100) +fs = Ht.coeffs_parametrized +ops = Ht.ops_parametrized +n_channels = len(fs) +fig, axs = plt.subplots(nrows=n_channels, figsize=(5, 2 * n_channels), gridspec_kw={"hspace": 0}) +for n in range(n_channels): + ax = axs[n] + ax.plot(ts, fs[n](params[n], ts)) + ax.set_ylabel(f"$f_{n}$") +axs[0].set_title(f"Envelopes $f_i(p_i, t)$ of $\sum_i X_i X_{{i+1}} + \sum_i f_i(p_i, t) Z_i$") +axs[-1].set_xlabel("time t") +plt.tight_layout() +plt.show() + +############################################################################## +# +# A pulse program is then executed by using the :func:`~.pennylane.evolve` transform to create the evolution +# gate :math:`U(t_0, t_1),` which implicitly depends on the parameters ``p`.` The objective of the program +# is then to compute the expectation value of some objective Hamiltonian ``H_obj`` (here :math:`\sum_i Z_i` as a simple example). + +dev = qml.device("default.qubit", range(4)) + +ts = jnp.array([0.0, 3.0]) +H_obj = sum([qml.PauliZ(i) for i in range(4)]) + + +@jax.jit +@qml.qnode(dev, interface="jax") +def qnode(params): + qml.evolve(Ht)(params, ts) + return qml.expval(H_obj) + + +print(qnode(params)) + +############################################################################## +# We used the decorator ``jax.jit`` to compile this execution just-in-time. This means the first execution will typically take a little longer with the +# benefit that all following executions will be significantly faster, see the `jax docs on jitting `_. +# Note that when removing the ``jax.jit`` decorator, the numerical solver `odeint `_ for the time evolution +# inside :func:`~.pennylane.evolve` is still jit-compiled by default. +# +# Researchers interested in more specific hardware systems can simulate them using the specific Hamiltonian interactions. +# For example, we will simulate a transmon qubit system in the ctrl-VQE example in the last section of this demo. +# +# Gradients of pulse programs +# --------------------------- +# Internally, pulse programs in PennyLane solve the time-dependent Schrödinger equation using the `Dopri5 `_ solver for +# ordinary differential equations (ODEs). In particular, the step sizes between :math:`t_0` and :math:`t_1` are chosen adaptively to stay within a given error tolerance. +# We can backpropagate through this ODE solver and obtain the gradient via ``jax.grad``. + +print(jax.grad(qnode)(params)) + +############################################################################## +# Alternatively, one could consider computing the gradient with the parameter shift rule [#Leng2022]_, which is particularly interesting for +# real hardware execution. In classical simulations, however, backpropagation is recommended. + + +############################################################################## +# Piecewise-constant parametrizations +# ------------------------------------ +# PennyLane also provides a variety of convenience functions to create, for example, piece-wise-constant parametrizations +# defining the function values at fixed time bins as parameters. We can construct such a callable with :func:`~pennylane.pulse.pwc` +# by providing a ``timespan`` argument which is expected to be either a total time (``float``) or a start and end time (``tuple``). + +timespan = 10.0 +coeffs = [qml.pulse.pwc(timespan) for _ in range(2)] + +############################################################################## +# This creates a callable with signature ``(p, t)`` that returns ``p[int(len(p)*t/duration)]``, such that the passed parameters are the function values +# for different time bins. +# Note how the number of time bins is implicitly defined through the length of the parameters. In the following example, we are going to use +# ``4`` and ``10`` time bins defined through the length of parameters, respectively. Let us create uniformly random parameters between 0 and 5 and plot +# the corresponding piece-wise-constant function sampled at ``100`` different points in time. + +key = jax.random.PRNGKey(777) +subkeys = jax.random.split(key, 2) # creates a list of two sub-keys +theta0 = jax.random.uniform(subkeys[0], shape=[4], maxval=5) +theta1 = jax.random.uniform(subkeys[1], shape=[10], maxval=5) +theta = [theta0, theta1] + +ts = jnp.linspace(0.0, timespan, 100)[:-1] +fig, axs = plt.subplots(nrows=2, sharex=True) +for i in range(2): + ax = axs[i] + ax.plot(ts, coeffs[i](theta[i], ts), ".-") + ax.set_ylabel(f"coeffs[{i}]") +ax.set_xlabel("time t") +plt.show() + +############################################################################## +# We can use these callables as before to construct a :func:`~.pennylane.pulse.ParametrizedHamiltonian`. + +ops = [qml.PauliX(i) for i in range(2)] +H = qml.pulse.ParametrizedHamiltonian(coeffs, ops) +print(H(theta, 0.5)) + +############################################################################## +# Note that this construction is equivalent to using :func:`qml.dot `. +# +# Variational quantum eigensolver with pulse programming +# ------------------------------------------------------ +# We can now use the ability to access gradients to perform the variational quantum eigensolver on the pulse level (ctrl-VQE) as is done in [#Mitei]_. +# For a more general introduction to VQE, see :doc:`tutorial_vqe`. +# First, we define the molecular Hamiltonian whose energy expectation value we want to minimize. This serves as our objective Hamiltonian. +# We are using :math:`\text{HeH}^+` as a simple example and load it from the `PennyLane quantum datasets `_ website. +# We are going to use the tapered Hamiltonian, which makes use of symmetries to reduce the number of qubits, see :doc:`tutorial_qubit_tapering` for details. + +data = qml.data.load("qchem", molname="HeH+", basis="STO-3G", bondlength=1.5)[0] +H_obj = data.tapered_hamiltonian +H_obj_coeffs, H_obj_ops = H_obj.terms() + +# casting the Hamiltonian coefficients to a jax Array +H_obj = qml.Hamiltonian(jnp.array(H_obj_coeffs), H_obj_ops) +E_exact = data.fci_energy +n_wires = len(H_obj.wires) + +############################################################################## +# As a realistic physical system with pulse level control, we are considering a coupled transmon qubit system with the constant drift term Hamiltonian +# +# .. math:: H_D = \sum_q \omega_q a_q^\dagger a_q - \sum_q \frac{\delta_q}{2} a^\dagger_q a^\dagger_q a_q a_q + \sum_{\langle pq \rangle} g_{pq} a^\dagger_p a_q +# +# with bosonic creation and annihilation operators. The anharmonicity :math:`\delta_q` is describing the contribution to higher energy levels. +# We are only going to consider the qubit subspace and hence set this term to zero. +# The order of magnitude of the resonance frequencies :math:`\omega_q` and coupling strength :math:`g_{pq}` are taken from [#Mitei]_ (in GHz). +# Let us construct the Hamiltonian in PennyLane: + + +def a(wires): + return 0.5 * qml.PauliX(wires) + 0.5j * qml.PauliY(wires) + + +def ad(wires): + return 0.5 * qml.PauliX(wires) - 0.5j * qml.PauliY(wires) + + +omega = 2 * jnp.pi * jnp.array([4.8080, 4.8333]) +g = 2 * jnp.pi * jnp.array([0.01831, 0.02131]) + +H_D = qml.dot(omega, [ad(i) @ a(i) for i in range(n_wires)]) +H_D += qml.dot( + g, + [ad(i) @ a((i + 1) % n_wires) + ad((i + 1) % n_wires) @ a(i) for i in range(n_wires)], +) + +############################################################################## +# The system is driven under the control term +# +# .. math:: H_C(t) = \sum_q \Omega_q(t) \left(e^{i\nu_q t} a_q + e^{-i\nu_q t} a^\dagger_q \right) +# +# with the (real) time-dependent amplitudes :math:`\Omega_q(t)` and frequencies :math:`\nu_q` of the drive. +# We let :math:`\Omega(t)` be a real piecewise-constant function whose values are optimized. +# In a transmon qubit systems, entangling gates such as ``CNOT`` are realized by driving a target qubit with the resonance frequency of the control qubit. +# This is referred to as cross resonance and is described in [#Sheldon2016]_. +# Here, we allow for more general two-qubit interactions by training the drive frequency :math:`\nu_q` on each qubit. +# +# For this drive, there are certain restrictions by the hardware that we want to already account for to make our simulation as realistic as possible. +# We therefore restrict the amplitude to :math:`\pm 20 \text{MHz}` and the frequency deviation :math:`\Delta \nu_q = \omega_q - \nu_q` to :math:`\pm 1 \text{GHz}` +# (as is done in [#Mitei]_). We achieve this by normalizing the respective quantities with a shifted sigmoid :math:`\mathcal{N}(x) = \frac{1 - e^{-x}}{1 + e^{-x}},` +# which ensures differentiability. + + +def normalize(x): + """Differentiable normalization to +/- 1 outputs (shifted sigmoid)""" + return (1 - jnp.exp(-x)) / (1 + jnp.exp(-x)) + + +# Because ParametrizedHamiltonian expects each callable function to have the signature +# f(p, t) but we have additional parameters it depends on, we create a wrapper function +# that constructs the callables with the appropriate parameters imprinted on them +def drive_field(T, omega, sign=1.0): + def wrapped(p, t): + # The first len(p)-1 values of the trainable params p characterize the pwc function + amp = qml.pulse.pwc(T)(p[:-1], t) + # The amplitude is normalized to maximally reach +/-20MHz (0.02GHz) + amp = 0.02 * normalize(amp) + + # The last value of the trainable params p provides the drive frequency deviation + # We normalize as the difference to drive can maximally be +/-1 GHz + d_angle = normalize(p[-1]) + phase = jnp.exp(sign * 1j * (omega + d_angle) * t) + return amp * phase + + return wrapped + + +duration = 15.0 + +fs = [drive_field(duration, omega[i], 1.0) for i in range(n_wires)] +fs += [drive_field(duration, omega[i], -1.0) for i in range(n_wires)] +ops = [a(i) for i in range(n_wires)] +ops += [ad(i) for i in range(n_wires)] + +H_C = qml.dot(fs, ops) + +############################################################################## +# Overall, we end up with the time-dependent parametrized Hamiltonian :math:`H(p, t) = H_D + H_C(p, t)` +# under which the system is evolved for the given time window of ``15ns``. Note that we are expressing time +# in nanoseconds (:math:`10^{-9}` s) and frequencies (and energies) in gigahertz (:math:`10^{9}` Hz), such that both +# exponents cancel. + +H_pulse = H_D + H_C + +############################################################################## +# Now we define the ``qnode`` that computes the expectation value of the molecular Hamiltonian. + +dev = qml.device("default.qubit", wires=range(n_wires)) + +@qml.qnode(dev, interface="jax") +def qnode(theta, t=duration): + qml.BasisState(jnp.array(data.tapered_hf_state), wires=H_obj.wires) + qml.evolve(H_pulse)(params=(*theta, *theta), t=t) + return qml.expval(H_obj) + + +value_and_grad = jax.jit(jax.value_and_grad(qnode)) + +############################################################################## +# We now have all the ingredients to run our ctrl-VQE program. We use the ``adam`` implementation in `optax `_, +# a package for optimizations in ``jax``. +# +# It has been shown that the loss landscapes of pulse programs are trap-free for a variety of conditions and loss functions, including ours [#Russell2016]_. +# In practice however, we see that the optimization is senstive to the initial values of the parameters and the optimization strategy. +# In particular, we often find ourselves with very slow progress during optimization, indicating wide flat regions in the loss landscape. +# This can be salvaged by increasing the learning rate. Sometimes, it proved advantageous to increase the learning rate after an +# initial finer search for a better starting point. Further, we note that with the increase in the number of parameters due to the continuous evolution, +# the optimization becomes harder. +# +# Whether or not that is due to the increased parameter search space or an inherent effect of pulse programs like barren plateaus in variational quantum circuits +# is to be determined in future work. +# +# We systematically tried a variety of combinations of learning rate schedule, optimizer, and initial values. Here, we provide one possible choice leading to good results. +# +# We choose ``t_bins = 100`` segments for the piece-wise-constant parametrization of the pulses. + +t_bins = 100 # number of time bins + +key = jax.random.PRNGKey(999) +theta = 0.9 * jax.random.uniform(key, shape=jnp.array([n_wires, t_bins + 1])) + +import optax +from datetime import datetime + +n_epochs = 60 + +# The following block creates a constant schedule of the learning rate +# that increases from 0.1 to 0.5 after 10 epochs +schedule0 = optax.constant_schedule(1e-1) +schedule1 = optax.constant_schedule(5e-1) +schedule = optax.join_schedules([schedule0, schedule1], [10]) +optimizer = optax.adam(learning_rate=schedule) +opt_state = optimizer.init(theta) + +energy = np.zeros(n_epochs + 1) +energy[0] = qnode(theta) +gradients = np.zeros(n_epochs) + +## Compile the evaluation and gradient function and report compilation time +time0 = datetime.now() +_ = value_and_grad(theta) +time1 = datetime.now() +print(f"grad and val compilation time: {time1 - time0}") + +## Optimization loop +for n in range(n_epochs): + val, grad_circuit = value_and_grad(theta) + updates, opt_state = optimizer.update(grad_circuit, opt_state) + theta = optax.apply_updates(theta, updates) + + energy[n + 1] = val + gradients[n] = np.mean(np.abs(grad_circuit)) + + if not n % 10: + print(f"{n+1} / {n_epochs}; energy discrepancy: {val-E_exact}") + print(f"mean grad: {gradients[n]}") + +############################################################################## +# We see that we have converged to chemical accuracy after half the number of epochs. + +fig, ax = plt.subplots(nrows=1, figsize=(5, 3), sharex=True) + +y = np.array(energy) - E_exact +ax.plot(y, ".:", label="$\\langle H_{{obj}}\\rangle - E_{{FCI}}$") +ax.fill_between([0, len(y)], [1e-3] * 2, 3e-4, alpha=0.2, label="chem acc.") +ax.set_yscale("log") +ax.set_ylabel("Energy ($E_H$)") +ax.set_xlabel("epoch") +ax.legend() + +plt.tight_layout() +plt.show() + +############################################################################## +# We can also visualize the envelopes for each qubit in time. +# We only plot the real amplitude :math:`\Omega(t)` and indicate the deviation +# :math:`\Delta \nu_q = \omega_q - \nu_q` of the drive frequency :math:`\nu_q` from the qubit frequency :math:`\omega_q` +# in the labels. + + +n_channels = n_wires +ts = jnp.linspace(0, duration, t_bins) +fig, axs = plt.subplots(nrows=n_channels, figsize=(5, 2 * n_channels), sharex=True) +for n in range(n_channels): + ax = axs[n] + label = f"$\\Delta \\nu_{n}$: {normalize(theta[n][-1]):.3}" + ax.plot(ts, 0.02 * normalize(theta[n][:-1]), ".:", label=label) + ax.set_ylabel(f"$amp_{n}$ (GHz)") + ax.legend() +ax.set_xlabel("t (ns)") + +plt.tight_layout() +plt.show() + +############################################################################## +# Note that we obtain bang-bang like solutions as indicated in [#Asthana2022]_, making it +# likely we are close to the minimal evolution time with ``15ns``. +# +# Conclusion +# ---------- +# Pulse programming is an exciting new field within noisy quantum computing. By skipping the digital abstraction, one can +# write variational programs on the hardware level, potentially minimizing the computation time. Ideally, this allows for effectively deeper +# circuits on noisy hardware. +# On the other hand, the possibility to continuously vary the Hamiltonian interaction in time significantly increases +# the parameter space. A good parametrization trading off flexibility and the number of parameters is therefore necessary as systems scale up. +# Further, the increased flexibility also affects the search space in Hilbert space that pulse gates can reach. +# Barren plateaus in variational quantum algorithms are typically due to a lack of a good inductive bias in the ansatz, i.e. having a search space that is too large. +# It is therefore crucial to find physically motivated ansätze for pulse programs. +# +# References +# ---------- +# +# .. [#Mitei] +# +# Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall +# "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE" +# `arXiv:2008.04302 `__, 2020 +# +# .. [#Sheldon2016] +# +# Sarah Sheldon, Easwar Magesan, Jerry M. Chow, Jay M. Gambetta +# "Procedure for systematically tuning up crosstalk in the cross resonance gate" +# `arXiv:1603.04821 `__, 2016. +# +# .. [#Leng2022] +# +# Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu +# "Differentiable Analog Quantum Computing for Optimization and Control" +# `arXiv:2210.15812 `__, 2022 +# +# .. [#Asthana2022] +# +# Ayush Asthana, Chenxu Liu, Oinam Romesh Meitei, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall +# "Minimizing state preparation times in pulse-level variational molecular simulations" +# `arXiv:2203.06818 `__, 2022. +# +# .. [#Russell2016] +# +# Benjamin Russell, Herschel Rabitz, Rebing Wu +# "Quantum Control Landscapes Are Almost Always Trap Free" +# `arXiv:1608.06198 `__, 2016. +# +# +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_pulse_programming101/metadata.json b/demonstrations_v2/tutorial_pulse_programming101/metadata.json new file mode 100644 index 0000000000..3be2908e82 --- /dev/null +++ b/demonstrations_v2/tutorial_pulse_programming101/metadata.json @@ -0,0 +1,77 @@ +{ + "title": "Differentiable pulse programming with qubits in PennyLane", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-03-08T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/pulse_programming101/thumbnail_tutorial_pulse_programming.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_pulse_programming.png" + } + ], + "seoDescription": "Simulating differentialble pulse programs in PennyLane with qubits", + "doi": "", + "references": [ + { + "id": "Mitei", + "type": "article", + "title": "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE", + "authors": "Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2020", + "publisher": "", + "url": "https://arxiv.org/abs/2008.04302" + }, + { + "id": "Sheldon2016", + "type": "article", + "title": "Procedure for systematically tuning up crosstalk in the cross resonance gate", + "authors": "Sarah Sheldon, Easwar Magesan, Jerry M. Chow, Jay M. Gambetta", + "year": "2016", + "publisher": "", + "url": "https://arxiv.org/abs/1603.04821" + }, + { + "id": "Leng2022", + "type": "article", + "title": "Differentiable Analog Quantum Computing for Optimization and Control", + "authors": "Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu", + "year": "2022", + "publisher": "", + "url": "https://arxiv.org/abs/2210.15812" + }, + { + "id": "Asthana2022", + "type": "article", + "title": "Minimizing state preparation times in pulse-level variational molecular simulations", + "authors": "Ayush Asthana, Chenxu Liu, Oinam Romesh Meitei, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2022", + "publisher": "", + "url": "https://arxiv.org/abs/2203.06818" + }, + { + "id": "Russell2016", + "type": "article", + "title": "Quantum Control Landscapes Are Almost Always Trap Free", + "authors": "Benjamin Russell, Herschel Rabitz, Rebing Wu", + "year": "2016", + "publisher": "", + "url": "https://arxiv.org/abs/1608.06198" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_pulse_programming101/requirements.in b/demonstrations_v2/tutorial_pulse_programming101/requirements.in new file mode 100644 index 0000000000..ed2cc33de0 --- /dev/null +++ b/demonstrations_v2/tutorial_pulse_programming101/requirements.in @@ -0,0 +1,7 @@ +datetime +jax +jaxlib +matplotlib +numpy +optax +pennylane diff --git a/demonstrations_v2/tutorial_qaoa_intro/demo.py b/demonstrations_v2/tutorial_qaoa_intro/demo.py new file mode 100644 index 0000000000..1224ea5f14 --- /dev/null +++ b/demonstrations_v2/tutorial_qaoa_intro/demo.py @@ -0,0 +1,513 @@ +r""" +Intro to QAOA +============= + +.. meta:: + :property="og:description": Learn how to implement QAOA with PennyLane + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qaoa_layer.png + +.. related:: + + tutorial_qaoa_maxcut QAOA for MaxCut + +*Author: Jack Ceroni — Posted: 18 November 2020. Last updated: 11 January 2021.* + +The Quantum Approximate Optimization Algorithm (QAOA) is a widely-studied +method for solving combinatorial optimization problems on NISQ devices. +The applications of QAOA are broad and far-reaching, and the performance +of the algorithm is of great interest to the quantum computing research community. + +.. figure:: ../_static/demonstration_assets/qaoa_module/qaoa_circuit.png + :align: center + :width: 90% + +The goal of this tutorial is to introduce the basic concepts of QAOA and +to guide you through PennyLane's built-in QAOA +functionality. You will learn how to use time evolution to establish a +connection between Hamiltonians and quantum circuits, and how to layer +these circuits to create more powerful algorithms. These simple ingredients, +together with the ability to optimize quantum circuits, are the building blocks of QAOA. By focusing +on the fundamentals, PennyLane provides general and flexible capabilities that can be tailored and +refined to implement QAOA for a wide variety of problems. In the last part of the tutorial, you will +learn how to bring these pieces together and deploy a complete QAOA workflow to solve the +minimum vertex cover problem. Let's get started! 🎉 + +Circuits and Hamiltonians +------------------------- + +When considering quantum circuits, it is often convenient to define them by a +series of quantum gates. But there are many instances where +it is useful to think of a quantum circuit in terms of a +`Hamiltonian `__. +Indeed, gates are physically implemented by performing time evolution under a carefully engineered +Hamiltonian. These transformations are described by the time evolution operator, +which is a unitary defined as:""" + +###################################################################### +# .. math:: U(H, \ t) \ = \ e^{-i H t / \hbar}. +# +# The time evolution operator is determined completely in terms of a Hamiltonian +# :math:`H` and a scalar :math:`t` representing time. In fact, any unitary +# :math:`U` can be written in the form :math:`e^{i \gamma H},` where :math:`\gamma` is a scalar +# and :math:`H` is a Hermitian operator, +# interpreted as a Hamiltonian. Thus, time evolution establishes a connection that allows us to +# describe quantum circuits in terms of Hamiltonians. 🤯 +# +# In general, implementing a quantum circuit that exactly exponentiates a Hamiltonian +# with many non-commuting terms, i.e., a Hamiltonian of the form: +# +# .. math:: H \ = \ H_1 \ + \ H_2 \ + \ H_3 \ + \ \cdots \ + \ H_N, +# +# is very challenging. Instead, we can use the +# `Trotter-Suzuki `__ decomposition formula +# +# .. math:: e^{A \ + \ B} \ \approx \ \Big(e^{A/n} e^{B/n}\Big)^{n}, +# +# to implement an *approximate* time-evolution unitary: +# +# .. math:: U(H, t, n) \ = \ \displaystyle\prod_{j \ = \ 1}^{n} +# \displaystyle\prod_{k} e^{-i H_k t / n} \ \ \ \ \ \ \ \ \ \ H \ +# = \ \displaystyle\sum_{k} H_k, +# +# where :math:`U` approaches :math:`e^{-i H t}` as :math:`n` +# becomes larger. +# +# .. figure:: ../_static/demonstration_assets/qaoa_module/ham_circuit.png +# :align: center +# :width: 70% +# +# In PennyLane, this is implemented using the :func:`~.pennylane.templates.ApproxTimeEvolution` +# template. For example, let's say we have the following Hamiltonian: + +import pennylane as qml + +H = qml.Hamiltonian([1, 1, 0.5], [qml.PauliX(0), qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]) +print(H) + + +###################################################################### +# +# We can implement the approximate time-evolution operator corresponding to this +# Hamiltonian: + +dev = qml.device("default.qubit", wires=2) + +t = 1 +n = 2 + + +@qml.qnode(dev) +def circuit(): + qml.ApproxTimeEvolution(H, t, n) + return [qml.expval(qml.PauliZ(i)) for i in range(2)] + + +print(qml.draw(circuit, level="device")()) + +###################################################################### +# Layering circuits +# ----------------- +# +# Think of all the times you have copied a text or image, then pasted it repeatedly to create +# many duplicates. This is also a useful feature when designing quantum algorithms! +# The idea of repetition is ubiquitous in quantum computing: +# from amplitude amplification in `Grover’s algorithm +# `__ +# to layers in `quantum neural networks +# `__ +# and `Hamiltonian simulation +# `__, repeated application +# of a circuit is a central tool in quantum algorithms. +# +# +# .. figure:: ../_static/demonstration_assets/qaoa_module/repeat.png +# :align: center +# :width: 100% +# +# +# Circuit repetition is implemented in PennyLane using the :func:`~.pennylane.layer` function. This +# method allows us to take a function containing either quantum operations, a template, or even a +# single quantum gate, and repeatedly apply it to a set of wires. +# +# .. figure:: ../_static/demonstration_assets/qaoa_module/qml_layer.png +# :align: center +# :width: 90% +# +# To create a larger circuit consisting of many repetitions, we pass the circuit to be +# repeated as an argument and specify the number of repetitions. For example, let's +# say that we want to layer the following circuit three times: + + +def circ(theta): + qml.RX(theta, wires=0) + qml.Hadamard(wires=1) + qml.CNOT(wires=[0, 1]) + + +@qml.qnode(dev) +def circuit(param): + circ(param) + return [qml.expval(qml.PauliZ(i)) for i in range(2)] + + +print(qml.draw(circuit)(0.5)) + +###################################################################### +# +# We simply pass this function into the :func:`~.pennylane.layer` function: +# + + +@qml.qnode(dev) +def circuit(params, **kwargs): + qml.layer(circ, 3, params) + return [qml.expval(qml.PauliZ(i)) for i in range(2)] + + +print(qml.draw(circuit)([0.3, 0.4, 0.5])) + +###################################################################### +# +# We have learned how time evolution can be used to create circuits from Hamiltonians, +# and how these can be layered to create longer circuits. We are now ready to +# explore QAOA. +# + + +###################################################################### +# QAOA +# ---- +# +# The quantum approximate optimization algorithm (QAOA) is a general technique that can be used +# to find approximate solutions to combinatorial optimization problems, in particular problems +# that can be cast as searching for an optimal bitstring. QAOA consists of the following +# steps: +# +# 1. Define a *cost Hamiltonian* :math:`H_C` such that its ground state +# encodes the solution to the optimization problem. +# +# 2. Define a *mixer Hamiltonian* :math:`H_M.` +# +# 3. Construct the circuits :math:`e^{-i \gamma H_C}` and :math:`e^{-i\alpha H_M}.` We call +# these the *cost* and *mixer layers*, respectively. +# +# 4. Choose a parameter :math:`n\geq 1` and build the circuit +# +# .. math:: U(\boldsymbol\gamma, \ \boldsymbol\alpha) \ = \ e^{-i \alpha_n H_M} +# e^{-i \gamma_n H_C} \ ... \ e^{-i \alpha_1 H_M} e^{-i \gamma_1 H_C}, +# +# consisting of repeated application of the cost and mixer layers. +# +# 5. Prepare an initial state, apply :math:`U(\boldsymbol\gamma,\boldsymbol\alpha),` +# and use classical techniques to optimize the parameters. +# +# 6. After the circuit has been optimized, measurements of the output state reveal +# approximate solutions to the optimization problem. +# +# In summary, the starting point of QAOA is the specification of cost and mixer Hamiltonians. +# We then use time evolution and layering to create a variational circuit and optimize its +# parameters. The algorithm concludes by sampling from the circuit to get an approximate solution to +# the optimization problem. Let's see it in action! 🚀 +# + +###################################################################### +# Minimum Vertex Cover with QAOA +# ------------------------------ +# +# Our goal is to find the `minimum vertex +# cover `__ of a graph: +# a collection of vertices such that +# each edge in the graph contains at least one of the vertices in the cover. Hence, +# these vertices "cover" all the edges 👍. +# We wish to find the vertex cover that has the +# smallest possible number of vertices. +# +# Vertex covers can be represented by a bit string +# where each bit denotes whether the corresponding vertex is present in the cover. For example, +# the bit string 01010 represents a cover consisting of the second and fourth vertex in a graph with five vertices. +# +# .. figure:: ../_static/demonstration_assets/qaoa_module/minvc.png +# :align: center +# :width: 90% +# +# To implement QAOA with PennyLane, we first import the necessary dependencies: +# + +from pennylane import qaoa +from pennylane import numpy as np +from matplotlib import pyplot as plt +import networkx as nx + + +###################################################################### +# +# We also define the four-vertex graph for which we +# want to find the minimum vertex cover: + +edges = [(0, 1), (1, 2), (2, 0), (2, 3)] +graph = nx.Graph(edges) + +nx.draw(graph, with_labels=True) +plt.show() + + +###################################################################### +# +# There are two minimum vertex covers of this graph: the vertices 0 and 2, +# and the vertices 1 and 2. These can be respectively represented by the bit strings 1010 and +# 0110. The goal of the algorithm is to sample these bit strings with high probability. +# +# The PennyLane QAOA module has a collection of built-in optimization +# problems, including minimum vertex cover. For each problem, you can retrieve the cost Hamiltonian +# as well as a recommended mixer Hamiltonian. This +# makes it straightforward to obtain the Hamiltonians for specific problems while still +# permitting the flexibility to make other choices, for example by adding constraints or +# experimenting with different mixers. +# +# In our case, the cost +# Hamiltonian has two ground states, :math:`|1010\rangle` and :math:`|0110\rangle,` coinciding +# with the solutions of the problem. The mixer Hamiltonian is the simple, non-commuting sum of Pauli-X +# operations on each node of the graph: + +cost_h, mixer_h = qaoa.min_vertex_cover(graph, constrained=False) + +print("Cost Hamiltonian", cost_h) +print("Mixer Hamiltonian", mixer_h) + +###################################################################### +# +# A single layer of QAOA consists of time evolution under these +# Hamiltonians: +# +# .. figure:: ../_static/demonstration_assets/qaoa_module/layer.png +# :align: center +# :width: 90% +# +# While it is possible to use :func:`~.pennylane.templates.ApproxTimeEvolution`, the QAOA module allows you to +# build the cost and mixer layers directly using the functions :func:`~.pennylane.qaoa.cost_layer` and +# :func:`~.pennylane.qaoa.mixer_layer`, which take as input the respective Hamiltonian and variational parameters: + + +def qaoa_layer(gamma, alpha): + qaoa.cost_layer(gamma, cost_h) + qaoa.mixer_layer(alpha, mixer_h) + + +###################################################################### +# +# We are now ready to build the full variational circuit. The number of wires is equal to +# the number of vertices of the graph. We initialize the state to an even superposition over +# all basis states. +# For this example, we employ a circuit consisting of two QAOA layers: + + +wires = range(4) +depth = 2 + + +def circuit(params, **kwargs): + for w in wires: + qml.Hadamard(wires=w) + qml.layer(qaoa_layer, depth, params[0], params[1]) + + +###################################################################### +# +# Note that :func:`~.pennylane.layer` allows us to pass variational parameters +# ``params[0]`` and ``params[1]`` into each layer of the circuit. That's it! The last +# step is PennyLane's specialty: optimizing the circuit parameters. +# +# The cost function is the expectation value of :math:`H_C,` which we want to minimize. We +# use the function :func:`~.pennylane.expval` which returns the +# expectation value of the Hamiltonian with respect to the circuit's output state. +# We also define the device on which the simulation is performed. We use the +# PennyLane-Qulacs plugin to run the circuit on the Qulacs simulator: +# + +dev = qml.device("qulacs.simulator", wires=wires) + + +@qml.qnode(dev) +def cost_function(params): + circuit(params) + return qml.expval(cost_h) + + +###################################################################### +# +# Finally, we optimize the cost function using the built-in +# :func:`~.pennylane.GradientDescentOptimizer`. We perform optimization for seventy steps and initialize the +# parameters: + + +optimizer = qml.GradientDescentOptimizer() +steps = 70 +params = np.array([[0.5, 0.5], [0.5, 0.5]], requires_grad=True) + + +###################################################################### +# +# Notice that we set each of the initial parameters to :math:`0.5.` For demonstration purposes, +# we chose initial parameters that we know work fairly well, and don't get stuck in any local minima. +# +# The choice of initial parameters for a variational circuit is usually a difficult problem, +# so we won't linger on it too much in this tutorial, but it is important to note that +# finding an initial set of parameters that work well for a few toy problems often yields good results +# for more complex instances of the algorithm as well. +# +# Now, we can optimize the circuit: +# + +for i in range(steps): + params = optimizer.step(cost_function, params) + +print("Optimal Parameters") +print(params) + + +###################################################################### +# +# With the optimal parameters, we can now reconstruct the probability +# landscape. We redefine the +# full QAOA circuit with the optimal parameters, but this time we +# return the probabilities of measuring each bitstring: +# + + +@qml.qnode(dev) +def probability_circuit(gamma, alpha): + circuit([gamma, alpha]) + return qml.probs(wires=wires) + + +probs = probability_circuit(params[0], params[1]) + + +###################################################################### +# +# Finally, we can display a bar graph showing the probability of +# measuring each bitstring: + +plt.style.use("seaborn") +plt.bar(range(2 ** len(wires)), probs) +plt.show() + + +###################################################################### +# +# The states +# :math:`|6\rangle \ = \ |0110\rangle` and +# :math:`|10\rangle \ = \ |1010\rangle` have the highest probabilities of +# being measured, just as expected! +# +# .. figure:: ../_static/demonstration_assets/qaoa_module/graph.png +# :align: center +# :width: 90% +# + +###################################################################### +# Customizing QAOA +# ---------------- +# +# QAOA is not one-size-fits-all when it comes to solving optimization problems. In many cases, +# cost and mixer Hamiltonians will be very specific to one scenario, and not necessarily +# fit within the structure of the pre-defined problems in the :func:`~.pennylane.qaoa` submodule. Luckily, +# one of the core principles behind the entire PennyLane library is customizability, and this principle hold true for +# QAOA submodule as well! +# +# The QAOA workflow above gave us two optimal solutions: :math:`|6\rangle = |0110\rangle` +# and :math:`|10\rangle = |1010\rangle.` What if we add a constraint +# that made one of these solutions "better" than the other? Let's imagine that we are interested in +# solutions that minimize the original cost function, +# *but also colour the first and third vertices* :math:`1.` A constraint of this form will +# favour :math:`|10\rangle,` making it the only true ground state. +# +# It is easy to introduce constraints of this form in PennyLane. +# We can use the :func:`~.pennylane.qaoa.edge_driver` cost +# Hamiltonian to "reward" cases in which the first and last vertices of the graph +# are :math:`0:` + +reward_h = qaoa.edge_driver(nx.Graph([(0, 2)]), ["11"]) + +###################################################################### +# +# We then weigh and add the constraining term +# to the original minimum vertex cover Hamiltonian: + +new_cost_h = cost_h + 2 * reward_h + +###################################################################### +# +# Notice that PennyLane allows for simple addition and multiplication of +# Hamiltonian objects using inline arithmetic operations ➕ ➖ ✖️➗! Finally, we can +# use this new cost Hamiltonian to define a new QAOA workflow: + + +def qaoa_layer(gamma, alpha): + qaoa.cost_layer(gamma, new_cost_h) + qaoa.mixer_layer(alpha, mixer_h) + + +def circuit(params, **kwargs): + for w in wires: + qml.Hadamard(wires=w) + qml.layer(qaoa_layer, depth, params[0], params[1]) + + +@qml.qnode(dev) +def cost_function(params): + circuit(params) + return qml.expval(new_cost_h) + + +params = np.array([[0.5, 0.5], [0.5, 0.5]], requires_grad=True) + +for i in range(steps): + params = optimizer.step(cost_function, params) + +print("Optimal Parameters") +print(params) + + +###################################################################### +# +# We then reconstruct the probability landscape with the optimal parameters: +# + + +@qml.qnode(dev) +def probability_circuit(gamma, alpha): + circuit([gamma, alpha]) + return qml.probs(wires=wires) + + +probs = probability_circuit(params[0], params[1]) + +plt.style.use("seaborn") +plt.bar(range(2 ** len(wires)), probs) +plt.show() + +###################################################################### +# +# Just as we expected, the :math:`|10\rangle` state is now favoured +# over :math:`|6\rangle!` +# + +###################################################################### +# Conclusion +# ---------- +# +# You have learned how to use the PennyLane QAOA functionality, while +# also surveying some of the fundamental features that make the QAOA module simple and +# flexible. Now, it's your turn to experiment with QAOA! If you need some inspiration for how to get +# started: +# +# - Experiment with different optimizers and different devices. Which ones work the best? +# - Play around with some of the other built-in cost and mixer Hamiltonians. +# - Try making your own custom constraining terms. Is QAOA properly amplifying some bitstrings over others? +# +# .. figure:: ../_static/demonstration_assets/qaoa_module/qaoa_circuit.png +# :align: center +# :width: 90% +# +# diff --git a/demonstrations_v2/tutorial_qaoa_intro/metadata.json b/demonstrations_v2/tutorial_qaoa_intro/metadata.json new file mode 100644 index 0000000000..fa8e69f0cc --- /dev/null +++ b/demonstrations_v2/tutorial_qaoa_intro/metadata.json @@ -0,0 +1,33 @@ +{ + "title": "Intro to QAOA", + "authors": [ + { + "username": "jceroni" + } + ], + "dateOfPublication": "2020-11-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_intro_to_QAOA.png" + } + ], + "seoDescription": "Learn how to implement QAOA with PennyLane", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/qaoa-and-optimization/5188" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qaoa_intro/requirements.in b/demonstrations_v2/tutorial_qaoa_intro/requirements.in new file mode 100644 index 0000000000..c38ea5aa94 --- /dev/null +++ b/demonstrations_v2/tutorial_qaoa_intro/requirements.in @@ -0,0 +1,5 @@ +matplotlib +networkx +pennylane +pennylane-qulacs +qulacs diff --git a/demonstrations_v2/tutorial_qaoa_maxcut/demo.py b/demonstrations_v2/tutorial_qaoa_maxcut/demo.py new file mode 100644 index 0000000000..ab2d32087b --- /dev/null +++ b/demonstrations_v2/tutorial_qaoa_maxcut/demo.py @@ -0,0 +1,296 @@ +r"""QAOA for MaxCut +=================== + +In this tutorial we implement the quantum approximate optimization algorithm (QAOA) for the MaxCut +problem as proposed by `Farhi, Goldstone, and Gutmann (2014) `__. First, we +give an overview of the MaxCut problem using a simple example, a graph with 4 vertices and 4 edges. We then +show how to find the maximum cut by running the QAOA algorithm using PennyLane. + +Background +---------- + +The MaxCut problem +~~~~~~~~~~~~~~~~~~ +The aim of MaxCut is to maximize the number of edges (yellow lines) in a graph that are "cut" by +a given partition of the vertices (blue circles) into two sets (see figure below). + +.. figure:: ../_static/demonstration_assets/qaoa_maxcut/qaoa_maxcut_partition.png + :align: center + :scale: 65% + :alt: qaoa_operators + +| + +Consider a graph with :math:`m` edges and :math:`n` vertices. We seek the partition +:math:`z` of the vertices into two sets +:math:`A` and :math:`B` which maximizes + +.. math:: + C(z) = \sum_{\alpha=1}^{m}C_\alpha(z), + +where :math:`C` counts the number of edges cut. :math:`C_\alpha(z)=1` if :math:`z` places one vertex from the +:math:`\alpha^\text{th}` edge in set :math:`A` and the other in set :math:`B,` and :math:`C_\alpha(z)=0` otherwise. +Finding a cut which yields the maximum possible value of :math:`C` is an NP-complete problem, so our best hope for a +polynomial-time algorithm lies in an approximate optimization. +In the case of MaxCut, this means finding a partition :math:`z` which +yields a value for :math:`C(z)` that is close to the maximum possible value. + +We can represent the assignment of vertices to set :math:`A` or :math:`B` using a bitstring, +:math:`z=z_1...z_n` where :math:`z_i=0` if the :math:`i^\text{th}` vertex is in :math:`A` and +:math:`z_i = 1` if it is in :math:`B.` For instance, +in the situation depicted in the figure above the bitstring representation is :math:`z=0101\text{,}` +indicating that the :math:`0^{\text{th}}` and :math:`2^{\text{nd}}` vertices are in :math:`A` +while the :math:`1^{\text{st}}` and :math:`3^{\text{rd}}` are in +:math:`B.` This assignment yields a value for the objective function (the number of yellow lines cut) +:math:`C=4,` which turns out to be the maximum cut. In the following sections, +we will represent partitions using computational basis states and use PennyLane to +rediscover this maximum cut. + +.. note:: In the graph above, :math:`z=1010` could equally well serve as the maximum cut. + +A circuit for QAOA +~~~~~~~~~~~~~~~~~~~~ +This section describes implementing a circuit for QAOA using basic unitary gates to find approximate +solutions to the MaxCut problem. +Firstly, denoting the partitions using computational basis states :math:`|z\rangle,` we can represent the terms in the +objective function as operators acting on these states + +.. math:: + C_\alpha = \frac{1}{2}\left(1-\sigma_{z}^j\sigma_{z}^k\right), + +where the :math:`\alpha\text{th}` edge is between vertices :math:`(j,k).` +:math:`C_\alpha` has eigenvalue 1 if and only if the :math:`j\text{th}` and :math:`k\text{th}` +qubits have different z-axis measurement values, representing separate partitions. +The objective function :math:`C` can be considered a diagonal operator with integer eigenvalues. + +QAOA starts with a uniform superposition over the :math:`n` bitstring basis states, + +.. math:: + |+_{n}\rangle = \frac{1}{\sqrt{2^n}}\sum_{z\in \{0,1\}^n} |z\rangle. + + +We aim to explore the space of bitstring states for a superposition which is likely to yield a +large value for the :math:`C` operator upon performing a measurement in the computational basis. +Using the :math:`2p` angle parameters +:math:`\boldsymbol{\gamma} = \gamma_1\gamma_2...\gamma_p,` :math:`\boldsymbol{\beta} = \beta_1\beta_2...\beta_p` +we perform a sequence of operations on our initial state: + +.. math:: + |\boldsymbol{\gamma},\boldsymbol{\beta}\rangle = U_{B_p}U_{C_p}U_{B_{p-1}}U_{C_{p-1}}...U_{B_1}U_{C_1}|+_n\rangle + +where the operators have the explicit forms + +.. math:: + U_{B_l} &= e^{-i\beta_lB} = \prod_{j=1}^n e^{-i\beta_l\sigma_x^j}, \\ + U_{C_l} &= e^{-i\gamma_lC} = \prod_{\text{edge (j,k)}} e^{-i\gamma_l(1-\sigma_z^j\sigma_z^k)/2}. + +In other words, we make :math:`p` layers of parametrized :math:`U_bU_C` gates. +These can be implemented on a quantum circuit using the gates depicted below, up to an irrelevant constant +that gets absorbed into the parameters. + +.. figure:: ../_static/demonstration_assets/qaoa_maxcut/qaoa_operators.png + :align: center + :scale: 100% + :alt: qaoa_operators + +| + +.. note:: + An alternative implementation of :math:`U_{C_l}` would be :math:`ZZ(\gamma_l)`, available + via :class:`~.pennylane.IsingZZ` in PennyLane. + +Let :math:`\langle \boldsymbol{\gamma}, +\boldsymbol{\beta} | C | \boldsymbol{\gamma},\boldsymbol{\beta} \rangle` be the expectation of the objective operator. +In the next section, we will use PennyLane to perform classical optimization +over the circuit parameters :math:`(\boldsymbol{\gamma}, \boldsymbol{\beta}).` +This will specify a state :math:`|\boldsymbol{\gamma},\boldsymbol{\beta}\rangle` which is +likely to yield an approximately optimal partition :math:`|z\rangle` upon performing a measurement in the +computational basis. +In the case of the graph shown above, we want to measure either 0101 or 1010 from our state since these correspond to +the optimal partitions. + +.. figure:: ../_static/demonstration_assets/qaoa_maxcut/qaoa_optimal_state.png + :align: center + :scale: 60% + :alt: optimal_state + +| + +Qualitatively, QAOA tries to evolve the initial state into the plane of the +:math:`|0101\rangle,` :math:`|1010\rangle` basis states (see figure above). + + +Implementing QAOA in PennyLane +------------------------------ + +Imports and setup +~~~~~~~~~~~~~~~~~ + +To get started, we import PennyLane along with the PennyLane-provided +version of NumPy. +""" + +import pennylane as qml +from pennylane import numpy as np + +np.random.seed(42) + +############################################################################## +# Operators +# ~~~~~~~~~ +# We specify the number of qubits (vertices) with ``n_wires`` and +# compose the unitary operators using the definitions +# above. :math:`U_B` operators act on individual wires, while :math:`U_C` +# operators act on wires whose corresponding vertices are joined by an edge in +# the graph. We also define the graph using +# the list ``graph``, which contains the tuples of vertices defining +# each edge in the graph. + +n_wires = 4 +graph = [(0, 1), (0, 3), (1, 2), (2, 3)] + + +# unitary operator U_B with parameter beta +def U_B(beta): + for wire in range(n_wires): + qml.RX(2 * beta, wires=wire) + + +# unitary operator U_C with parameter gamma +def U_C(gamma): + for edge in graph: + qml.CNOT(wires=edge) + qml.RZ(gamma, wires=edge[1]) + qml.CNOT(wires=edge) + # Could also do + # IsingZZ(gamma, wires=edge) + + +############################################################################## +# We will need a way to convert a bitstring, representing a sample of multiple qubits +# in the computational basis, to integer or base-10 form. + + +def bitstring_to_int(bit_string_sample): + return int(2 ** np.arange(len(bit_string_sample)) @ bit_string_sample[::-1]) + + +############################################################################## +# Circuit +# ~~~~~~~ +# Next, we create a quantum device with 4 qubits. + +dev = qml.device("lightning.qubit", wires=n_wires, shots=20) + +############################################################################## +# We also require a quantum node which will apply the operators according to the angle parameters, +# and return the expectation value of :math:`\sum_{\text{edge} (j,k)}\sigma_z^{j}\sigma_z^k` +# for the cost Hamiltonian :math:`C.` +# We set up this node to take the parameters ``gammas`` and ``betas`` as inputs, which determine +# the number of layers (repeated applications of :math:`U_BU_C`) of the circuit via their length. +# We also give the node a keyword argument ``return_samples``. If set to ``False`` (default), the +# expectation value of the cost Hamiltonian is returned. +# Once optimized, the same quantum node can be used for sampling an approximately optimal bitstring +# by setting ``return_samples=True``. + + +@qml.qnode(dev) +def circuit(gammas, betas, return_samples=False): + # apply Hadamards to get the n qubit |+> state + for wire in range(n_wires): + qml.Hadamard(wires=wire) + # p instances of unitary operators + for gamma, beta in zip(gammas, betas): + U_C(gamma) + U_B(beta) + + if return_samples: + # sample bitstrings to obtain cuts + return qml.sample() + # during the optimization phase we are evaluating the objective using expval + C = qml.sum(*(qml.Z(w1) @ qml.Z(w2) for w1, w2 in graph)) + return qml.expval(C) + + +def objective(params): + """Minimize the negative of the objective function C by postprocessing the QNnode output.""" + return -0.5 * (len(graph) - circuit(*params)) + + +############################################################################## +# Optimization +# ~~~~~~~~~~~~ +# Finally, we optimize the objective over the +# angle parameters :math:`\boldsymbol{\gamma}` (``params[0]``) and :math:`\boldsymbol{\beta}` +# (``params[1]``) and then sample the optimized +# circuit multiple times to yield a distribution of bitstrings. The optimal partitions +# (:math:`z=0101` or :math:`z=1010`) should be the most frequently sampled bitstrings. +# We perform a maximization of :math:`C` by minimizing :math:`-C,` following the convention +# that optimizations are cast as minimizations in PennyLane. + + +def qaoa_maxcut(n_layers=1): + print(f"\np={n_layers:d}") + + # initialize the parameters near zero + init_params = 0.01 * np.random.rand(2, n_layers, requires_grad=True) + + # initialize optimizer: Adagrad works well empirically + opt = qml.AdagradOptimizer(stepsize=0.5) + + # optimize parameters in objective + params = init_params.copy() + steps = 30 + for i in range(steps): + params = opt.step(objective, params) + if (i + 1) % 5 == 0: + print(f"Objective after step {i+1:3d}: {-objective(params): .7f}") + + # sample 100 bitstrings by setting return_samples=True and the QNode shot count to 100 + bitstrings = circuit(*params, return_samples=True, shots=100) + # convert the samples bitstrings to integers + sampled_ints = [bitstring_to_int(string) for string in bitstrings] + + # print optimal parameters and most frequently sampled bitstring + counts = np.bincount(np.array(sampled_ints)) + most_freq_bit_string = np.argmax(counts) + print(f"Optimized parameter vectors:\ngamma: {params[0]}\nbeta: {params[1]}") + print(f"Most frequently sampled bit string is: {most_freq_bit_string:04b}") + + return -objective(params), sampled_ints + + +# perform QAOA on our graph with p=1,2 and keep the lists of sampled integers +int_samples1 = qaoa_maxcut(n_layers=1)[1] +int_samples2 = qaoa_maxcut(n_layers=2)[1] + +############################################################################## +# For ``n_layers=1``, we find an objective function value of around :math:`C=3.` +# In the case where we set ``n_layers=2``, we recover the optimal +# objective function :math:`C=4.` +# +# Plotting the results +# -------------------- +# We can plot the distribution of measurements obtained from the optimized circuits. As +# expected for this graph, the partitions 0101 and 1010 are measured with the highest frequencies, +# and in the case where we set ``n_layers=2`` we obtain one of the optimal partitions with +# 100% certainty. + +import matplotlib.pyplot as plt + +xticks = range(0, 16) +xtick_labels = list(map(lambda x: format(x, "04b"), xticks)) +bins = np.arange(0, 17) - 0.5 + +fig, _ = plt.subplots(1, 2, figsize=(8, 4)) +for i, samples in enumerate([int_samples1, int_samples2], start=1): + plt.subplot(1, 2, i) + plt.title(f"n_layers={i}") + plt.xlabel("bitstrings") + plt.ylabel("freq.") + plt.xticks(xticks, xtick_labels, rotation="vertical") + plt.hist(samples, bins=bins) +plt.tight_layout() +plt.show() + +############################################################################## diff --git a/demonstrations_v2/tutorial_qaoa_maxcut/metadata.json b/demonstrations_v2/tutorial_qaoa_maxcut/metadata.json new file mode 100644 index 0000000000..1758c95f27 --- /dev/null +++ b/demonstrations_v2/tutorial_qaoa_maxcut/metadata.json @@ -0,0 +1,35 @@ +{ + "title": "QAOA for MaxCut", + "authors": [ + { + "username": "alowe" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-11-20T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QAOA_for_MaxCut.png" + } + ], + "seoDescription": "Implementing the quantum approximate optimization algorithm using PennyLane to solve the MaxCut problem.", + "doi": "", + "references": [], + "basedOnPapers": [ + "10.48550/arXiv.1411.4028" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/qaoa-and-optimization/5188" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qaoa_maxcut/requirements.in b/demonstrations_v2/tutorial_qaoa_maxcut/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_qaoa_maxcut/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_qcbm/demo.py b/demonstrations_v2/tutorial_qcbm/demo.py new file mode 100644 index 0000000000..c164010005 --- /dev/null +++ b/demonstrations_v2/tutorial_qcbm/demo.py @@ -0,0 +1,576 @@ +r"""Quantum Circuit Born Machines +============================= + +Unsupervised generative modelling emerges as a promising application for achieving practical quantum advantage +on classical data due to its high complexity relative to supervised machine learning tasks. This makes them +a suitable candidate for leveraging the potential of near-term quantum computers. A popular quantum generative +model known as Quantum Circuit Born Machines (QCBMs) has shown impressive results in modelling distributions +across various datasets, including both toy and real-world datasets, and you will learn about them in this demo. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_QuantumCircuitBornMachines_2024-05-13.png + :align: center + :width: 50% + :target: javascript:void(0) + + +Generative modelling with QCBMs +------------- + +Quantum Circuit Born machines (QCBMs) show promise in unsupervised generative modelling, aiming to learn and +represent classical dataset probability distributions through pure quantum states [#Liu]_ [#Ben]_. They are popular +due to their high expressive power [#Du]_. Born machines leverage the probabilistic interpretation of +quantum wavefunctions, representing probability distributions with pure quantum states instead of +thermal distributions like Boltzmann machines. This allows Born machines to directly generate +samples through projective measurements on qubits, offering a faster alternative to the Gibbs +sampling approach [#Ack]_. + +For a dataset :math:`\mathcal{D} = \{x\}` with independent and identically distributed samples from +an unknown target distribution :math:`\pi(x),` QCBM is used to generate samples closely resembling +the target distribution. QCBM transforms the input product state :math:`|\textbf{0} \rangle` to a +parameterized quantum state :math:`|\psi_\boldsymbol{\theta}\rangle.` Measuring this output state in the +computational basis yields a sample of bits :math:`x \sim p_\theta(x).` + +.. math:: + + p_\boldsymbol{\theta}(x) = |\langle x | \psi_\boldsymbol{\theta} \rangle|^2. + +The objective is to align the model probability distribution :math:`p_\boldsymbol{\theta}` with the target +distribution :math:`\pi.` + +In this tutorial, following [#Liu]_, we will implement a gradient-based algorithm for QCBM using +PennyLane. We describe the model and learning algorithm followed by its application to the +:math:`3 \times 3` Bars and Stripes dataset and double Gaussian peaks. + +To train the QCBM, we use the squared maximum mean discrepancy (MMD) as the loss function + +.. math:: + + \mathcal{L}(\boldsymbol{\theta}) = \left\|\sum_{x} p_\boldsymbol{\theta}(x) \phi(x)- \sum_{x} \pi(x) \phi(x) \right\|^2, + + +where :math:`\phi(x)` maps :math:`x` to a larger feature space. Using a kernel +:math:`K(x,y) = \phi(x)^T\phi(y)` allows us to work in a lower-dimensional space. We use the Radial +basis function (RBF) kernel for this purpose, which is defined as: + +.. math:: + + K(x,y) = \frac{1}{c}\sum_{i=1}^c \exp \left( \frac{|x-y|^2}{2\sigma_i^2} \right). + +Here, :math:`\sigma_i` is the bandwidth parameter controlling the Gaussian kernel's width. +:math:`\mathcal{L}` approaches to zero if and only if :math:`p_\boldsymbol{\theta}` approaches :math:`\pi` [#Gret]_. +To learn more about kernel methods, check out this :doc:`demo `. + +We can now write the loss function in terms of :math:`K(x,y)` as + +.. math:: + + \mathcal{L} = \underset{x, y \sim p_\boldsymbol{\theta}}{\mathbb{E}}[{K(x,y)}]-2\underset{x\sim p_\boldsymbol{\theta},y\sim \pi}{\mathbb{E}}[K(x,y)]+\underset{x, y \sim \pi}{\mathbb{E}}[K(x, y)] +""" + +###################################################################### +# Armed with these ingredients, we can write code for the QCBM and the loss function. +# We first define the ``MMD`` class for computing the squared MMD loss with radial basis +# function kernel. Upon initialization, it calculates the kernel function. +# + +import jax +import jax.numpy as jnp + +jax.config.update("jax_enable_x64", True) + + +class MMD: + + def __init__(self, scales, space): + gammas = 1 / (2 * (scales**2)) + sq_dists = jnp.abs(space[:, None] - space[None, :]) ** 2 + self.K = sum(jnp.exp(-gamma * sq_dists) for gamma in gammas) / len(scales) + self.scales = scales + + def k_expval(self, px, py): + # Kernel expectation value + return px @ self.K @ py + + def __call__(self, px, py): + pxy = px - py + return self.k_expval(pxy, pxy) + + +###################################################################### +# Defining classes helps in caching the kernel instead of calculating it everytime to find the +# expectation value. Next up, the ``QCBM`` holds the definition for the quantum circuit born machine +# and the objective function to minimize. +# + +from functools import partial + + +class QCBM: + + def __init__(self, circ, mmd, py): + self.circ = circ + self.mmd = mmd + self.py = py # target distribution π(x) + + @partial(jax.jit, static_argnums=0) + def mmd_loss(self, params): + px = self.circ(params) + return self.mmd(px, self.py), px + + +###################################################################### +# Learning the Bars and Stripes data distribution +# ----------------------------------------------- +# +# We train the QCBM on the Bars and stripes dataset. The dataset has binary black and white images of +# size :math:`n \times n` pixels. We consider :math:`n=3` for this tutorial, which will give 14 valid +# configurations. The dataset is represented by flattened bitstrings. The quantum circuit will use 9 +# qubits in total. +# + +###################################################################### +# Data generation +# ~~~~~~~~~~~~~~~ +# + +import numpy as np + + +def get_bars_and_stripes(n): + bitstrings = [list(np.binary_repr(i, n))[::-1] for i in range(2**n)] + bitstrings = np.array(bitstrings, dtype=int) + + stripes = bitstrings.copy() + stripes = np.repeat(stripes, n, 0) + stripes = stripes.reshape(2**n, n * n) + + bars = bitstrings.copy() + bars = bars.reshape(2**n * n, 1) + bars = np.repeat(bars, n, 1) + bars = bars.reshape(2**n, n * n) + return np.vstack((stripes[0 : stripes.shape[0] - 1], bars[1 : bars.shape[0]])) + + +n = 3 +size = n**2 +data = get_bars_and_stripes(n) +print(data.shape) + +###################################################################### +# The dataset has 9 features per data point. A visualization of one data point is shown below. Each +# data point represents a flattened bitstring. +# + +import matplotlib.pyplot as plt + +sample = data[1].reshape(n, n) + +plt.figure(figsize=(2, 2)) +plt.imshow(sample, cmap="gray", vmin=0, vmax=1) +plt.grid(color="gray", linewidth=2) +plt.xticks([]) +plt.yticks([]) + +for i in range(n): + for j in range(n): + text = plt.text( + i, + j, + sample[j][i], + ha="center", + va="center", + color="gray", + fontsize=12, + ) + +print(f"\nSample bitstring: {''.join(np.array(sample.flatten(), dtype='str'))}") + +###################################################################### +# We can plot the full dataset of :math:`3 \times 3` images. +# + +plt.figure(figsize=(4, 4)) +j = 1 +for i in data: + plt.subplot(4, 4, j) + j += 1 + plt.imshow(np.reshape(i, (n, n)), cmap="gray", vmin=0, vmax=1) + plt.xticks([]) + plt.yticks([]) + +###################################################################### +# Next we compute the integers represented by the valid configurations. We will use them later +# to evaluate the performance of the QCBM. +# + +bitstrings = [] +nums = [] +for d in data: + bitstrings += ["".join(str(int(i)) for i in d)] + nums += [int(bitstrings[-1], 2)] +print(nums) + +###################################################################### +# Using the dataset, we can compute the target probability distribution :math:`\pi(x)` which is visualized below. +# + +probs = np.zeros(2**size) +probs[nums] = 1 / len(data) + +plt.figure(figsize=(12, 5)) +plt.bar(np.arange(2**size), probs, width=2.0, label=r"$\pi(x)$") +plt.xticks(nums, bitstrings, rotation=80) + +plt.xlabel("Samples") +plt.ylabel("Prob. Distribution") +plt.legend(loc="upper right") +plt.subplots_adjust(bottom=0.3) +plt.show() + +###################################################################### +# One can observe that it is a uniform distribution and only the probabilities of the valid configurations are +# non-zero while rest are zero. Next we define a parameterized quantum circuit to train. This quantum circuit +# will act as a generative model, thus, realize a Born machine. +# + +import pennylane as qml + +np.random.seed(42) + + +n_qubits = size +dev = qml.device("default.qubit", wires=n_qubits) + +n_layers = 6 +wshape = qml.StronglyEntanglingLayers.shape(n_layers=n_layers, n_wires=n_qubits) +weights = np.random.random(size=wshape) + + +@qml.qnode(dev) +def circuit(weights): + qml.StronglyEntanglingLayers(weights=weights, ranges=[1] * n_layers, wires=range(n_qubits)) + return qml.probs() + + +jit_circuit = jax.jit(circuit) + +###################################################################### +# Using the ``MMD`` and ``QCBM`` classes defined earlier, we create their respective objects. Using Optax, we +# define the Adam optimizer. +# + +import optax + +bandwidth = jnp.array([0.25, 0.5, 1]) +space = jnp.arange(2**n_qubits) + +mmd = MMD(bandwidth, space) +qcbm = QCBM(jit_circuit, mmd, probs) + +opt = optax.adam(learning_rate=0.1) +opt_state = opt.init(weights) + +###################################################################### +# We can also verify that the summation in the first line of :math:`\mathcal{L}` is equal to the +# expectation values in the second line. +# + +loss_1, px = qcbm.mmd_loss(weights) # squared MMD +loss_2 = mmd.k_expval(px, px) - 2 * mmd.k_expval(px, probs) + mmd.k_expval(probs, probs) +print(loss_1) +print(loss_2) + +###################################################################### +# Training +# ~~~~~~~~ +# +# We define the ``update_step`` method which +# +# - computes the squared MMD loss and gradients. +# +# - apply the update step of our optimizer. +# +# - updates the parameter values. +# +# - calculates the KL divergence. +# +# The KL divergence [#Kull]_ is a measure of how far the predicted distribution :math:`p_\boldsymbol{\theta}(x)` +# is from the target distribution :math:`\pi(x).` +# + + +@jax.jit +def update_step(params, opt_state): + (loss_val, qcbm_probs), grads = jax.value_and_grad(qcbm.mmd_loss, has_aux=True)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + kl_div = -jnp.sum(qcbm.py * jnp.nan_to_num(jnp.log(qcbm_probs / qcbm.py))) + return params, opt_state, loss_val, kl_div + + +history = [] +divs = [] +n_iterations = 100 + +for i in range(n_iterations): + weights, opt_state, loss_val, kl_div = update_step(weights, opt_state) + + if i % 10 == 0: + print(f"Step: {i} Loss: {loss_val:.4f} KL-div: {kl_div:.4f}") + + history.append(loss_val) + divs.append(kl_div) + +###################################################################### +# Visualizing the training results, we get the following plot. +# + +fig, ax = plt.subplots(1, 2, figsize=(12, 5)) + +ax[0].plot(history) +ax[0].set_xlabel("Iteration") +ax[0].set_ylabel("MMD Loss") + +ax[1].plot(divs, color="green") +ax[1].set_xlabel("Iteration") +ax[1].set_ylabel("KL Divergence") +plt.show() + +###################################################################### +# Comparing the target probability distribution with the QCBM predictions, we can see that the +# predictions results in a good approximation. +# + +qcbm_probs = np.array(qcbm.circ(weights)) + +plt.figure(figsize=(12, 5)) + +plt.bar( + np.arange(2**size), + probs, + width=2.0, + label=r"$\pi(x)$", + alpha=0.4, + color="tab:blue", +) +plt.bar( + np.arange(2**size), + qcbm_probs, + width=2.0, + label=r"$p_\theta(x)$", + alpha=0.9, + color="tab:green", +) + +plt.xlabel("Samples") +plt.ylabel("Prob. Distribution") + +plt.xticks(nums, bitstrings, rotation=80) +plt.legend(loc="upper right") +plt.subplots_adjust(bottom=0.3) +plt.show() + +###################################################################### +# Testing +# ~~~~~~~ +# +# To visualize the performance of the model, we generate samples and compute +# :math:`\chi \equiv` P(:math:`x` is a bar or stripe) which is a measure of +# generation quality. +# + + +def circuit(weights): + qml.StronglyEntanglingLayers(weights=weights, ranges=[1] * n_layers, wires=range(n_qubits)) + return qml.sample() + + +for N in [2000, 20000]: + dev = qml.device("default.qubit", wires=n_qubits, shots=N) + circ = qml.QNode(circuit, device=dev) + preds = circ(weights) + mask = np.any(np.all(preds[:, None] == data, axis=2), axis=1) # Check for row-wise equality + chi = np.sum(mask) / N + print(f"χ for N = {N}: {chi:.4f}") + +print(f"χ for N = ∞: {np.sum(qcbm_probs[nums]):.4f}") + +###################################################################### +# Few of the samples are plotted below. The ones with a red border represents +# invalid images. +# + +plt.figure(figsize=(8, 8)) +j = 1 +for i, m in zip(preds[:64], mask[:64]): + ax = plt.subplot(8, 8, j) + j += 1 + plt.imshow(np.reshape(i, (n, n)), cmap="gray", vmin=0, vmax=1) + if ~m: + plt.setp(ax.spines.values(), color="red", linewidth=1.5) + plt.xticks([]) + plt.yticks([]) + +###################################################################### +# The model is able to learn the target distribution due to a circuit with larger layers. Also, +# [#Liu]_ has argued that training a QCBM with deeper circuits does not suffer from the +# vanishing gradients problem. +# + +###################################################################### +# Learning a mixture of Gaussians +# ------------------------------- +# +# Now we use a QCBM to model a mixture of Gaussians +# +# .. math:: +# +# \pi(x)\propto e^{-\frac{1}{2}\left(\frac{x-\mu_1}{\sigma}\right)^2}+e^{-\frac{1}{2}\left(\frac{x-\mu_2}{\sigma}\right)^2}, +# +# with :math:`x` ranging from :math:`0 \dots 2^{n}-1,` where :math:`n` is the +# number of qubits. +# + + +def mixture_gaussian_pdf(x, mus, sigmas): + mus, sigmas = np.array(mus), np.array(sigmas) + vars = sigmas**2 + values = [ + (1 / np.sqrt(2 * np.pi * v)) * np.exp(-((x - m) ** 2) / (2 * v)) for m, v in zip(mus, vars) + ] + values = np.sum([val / sum(val) for val in values], axis=0) + return values / np.sum(values) + + +n_qubits = 6 +x_max = 2**n_qubits +x_input = np.arange(x_max) +mus = [(2 / 7) * x_max, (5 / 7) * x_max] +sigmas = [x_max / 8] * 2 +data = mixture_gaussian_pdf(x_input, mus, sigmas) + +plt.plot(data, label=r"$\pi(x)$") +plt.legend() +plt.show() + +###################################################################### +# In contrast to the Bars and Stripes dataset, the Gaussian mixture distribution exhibits +# a smooth and non-zero probability for every basis state. Similar to the previous experiment, +# we will create an ansatz and measure probabilities. +# + +dev = qml.device("default.qubit", wires=n_qubits) + +n_layers = 4 +wshape = qml.StronglyEntanglingLayers.shape(n_layers=n_layers, n_wires=n_qubits) +weights = np.random.random(size=wshape) + + +@qml.qnode(dev) +def circuit(weights): + qml.StronglyEntanglingLayers( + weights=weights, ranges=[1] * n_layers, wires=range(n_qubits) + ) + return qml.probs() + + +jit_circuit = jax.jit(circuit) + +qml.draw_mpl(circuit, level="device")(weights) +plt.show() + +###################################################################### +# With the quantum circuit defined, we are ready to optimize the squared MMD loss function +# which follows a code similar to the Bars and Stripes case. +# + +bandwidth = jnp.array([0.25, 60]) +space = jnp.arange(2**n_qubits) + +mmd = MMD(bandwidth, space) +qcbm = QCBM(jit_circuit, mmd, data) + +opt = optax.adam(learning_rate=0.1) +opt_state = opt.init(weights) + +history = [] +divs = [] +n_iterations = 100 + +for i in range(n_iterations): + weights, opt_state, loss_val, kl_div = update_step(weights, opt_state) + + if i % 10 == 0: + print(f"Step: {i} Loss: {loss_val:.4f} KL-div: {kl_div:.4f}") + + history.append(loss_val) + divs.append(kl_div) + +###################################################################### +# Finally, we plot the histogram with the probabilities obtained from QCBM and compare +# it with the actual probability distribution. +# + +qcbm_probs = qcbm.circ(weights) + +plt.plot(range(x_max), data, linestyle="-.", label=r"$\pi(x)$") +plt.bar(range(x_max), qcbm_probs, color="green", alpha=0.5, label="samples") + +plt.xlabel("Samples") +plt.ylabel("Prob. Distribution") + +plt.legend() +plt.show() + +###################################################################### +# The histogram (green bars) aligns remarkably well with the exact probability distribution (blue +# dashed curve). +# + +###################################################################### +# Conclusion +# ---------- +# +# In this tutorial, we introduced and implemented Quantum Circuit Born Machine (QCBM) using PennyLane. +# The algorithm is a gradient-based learning involving optimizing the squared MMD loss. We also +# evaluated QCBMs on the Bars and Stripes and two peaks datasets. One can also leverage the differentiable +# learning of the QCBM to solve combinatorial problems where the output is binary strings. +# + +###################################################################### +# References +# ---------- +# +# .. [#Liu] +# +# Liu, Jin-Guo, and Lei Wang. “Differentiable learning of quantum circuit born machines.” Physical +# Review A 98.6 (2018): 062324. +# +# .. [#Ben] +# +# Benedetti, Marcello, et al. “A generative modeling approach for benchmarking and training shallow +# quantum circuits.” npj Quantum Information 5.1 (2019): 45. +# +# .. [#Du] +# +# Du, Yuxuan, et al. "Expressive power of parametrized quantum circuits." Physical Review Research +# 2.3 (2020): 033125. +# +# .. [#Ack] +# +# Ackley, David H., Geoffrey E. Hinton, and Terrence J. Sejnowski. "A learning algorithm for +# Boltzmann machines." Cognitive science 9.1 (1985): 147-169. +# +# .. [#Gret] +# +# Gretton, Arthur, et al. "A kernel method for the two-sample-problem." Advances in neural +# information processing systems 19 (2006). +# +# .. [#Kull] +# +# Kullback, Solomon, and Richard A. Leibler. "On information and sufficiency." The annals +# of mathematical statistics 22.1 (1951): 79-86. +# +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_qcbm/metadata.json b/demonstrations_v2/tutorial_qcbm/metadata.json new file mode 100644 index 0000000000..32d8c24d84 --- /dev/null +++ b/demonstrations_v2/tutorial_qcbm/metadata.json @@ -0,0 +1,98 @@ +{ + "title": "Quantum Circuit Born Machines", + "authors": [ + { + "username": "grdahale" + } + ], + "dateOfPublication": "2024-05-22T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qcbm/thumbnail_QuantumCircuitBornMachines_2024-05-13.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuantumCircuitBornMachines_2024-05-13.png" + } + ], + "seoDescription": "Learn how to use the Quantum Circuit Born Machines (QCBMs).", + "doi": "", + "references": [ + { + "id": "Liu", + "type": "article", + "title": "Differentiable learning of quantum circuit Born machines", + "authors": "Liu, Jin-Guo and Wang, Lei", + "year": "2018", + "publisher": "American Physical Society", + "journal": "Phys. Rev. A", + "doi": "10.1103/PhysRevA.98.062324", + "url": "https://link.aps.org/doi/10.1103/PhysRevA.98.062324" + }, + { + "id": "Ben", + "type": "article", + "title": "A generative modeling approach for benchmarking and training shallow quantum circuits", + "authors": "Benedetti, Marcello and Garcia-Pintos, Delfina and Nam, Yunseong and Perdomo-Ortiz, Alejandro", + "year": "2018", + "journal": "npj Quantum Information", + "doi": "10.1038/s41534-019-0157-8" + }, + { + "id": "Du", + "type": "article", + "title": "Expressive power of parametrized quantum circuits", + "authors": "Du, Yuxuan and Hsieh, Min-Hsiu and Liu, Tongliang and Tao, Dacheng", + "year": "2020", + "publisher": "American Physical Society", + "journal": "Phys. Rev. Res.", + "doi": "10.1103/PhysRevResearch.2.033125", + "url": "https://link.aps.org/doi/10.1103/PhysRevResearch.2.033125" + }, + { + "id": "Ack", + "type": "article", + "title": "A learning algorithm for boltzmann machines", + "authors": "David H. Ackley and Geoffrey E. Hinton and Terrence J. Sejnowski", + "year": "1985", + "journal": "Cognitive Science", + "doi": "10.1016/S0364-0213(85)80012-4", + "url": "https://www.sciencedirect.com/science/article/pii/S0364021385800124" + }, + { + "id": "Gret", + "type": "article", + "title": "A Kernel Method for the Two-Sample-Problem", + "authors": "Gretton, Arthur and Borgwardt, Karsten and Rasch, Malte and Sch\u00f6lkopf, Bernhard and Smola, Alexander", + "year": "2006", + "journal": "Advances in neural information processing systems" + }, + { + "id": "Kull", + "type": "article", + "title": "On Information and Sufficiency", + "authors": "Solomon Kullback and R. A. Leibler", + "year": "1951", + "journal": "Annals of Mathematical Statistics", + "url": "https://api.semanticscholar.org/CorpusID:120349231" + } + ], + "basedOnPapers": [ + "10.1103/PhysRevA.98.062324" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + } + ], + "hardware": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qcbm/requirements.in b/demonstrations_v2/tutorial_qcbm/requirements.in new file mode 100644 index 0000000000..543f5dbfb8 --- /dev/null +++ b/demonstrations_v2/tutorial_qcbm/requirements.in @@ -0,0 +1,6 @@ +jax +jaxlib +matplotlib +numpy +optax +pennylane diff --git a/demonstrations_v2/tutorial_qchem_external/demo.py b/demonstrations_v2/tutorial_qchem_external/demo.py new file mode 100644 index 0000000000..598a4b9ba0 --- /dev/null +++ b/demonstrations_v2/tutorial_qchem_external/demo.py @@ -0,0 +1,227 @@ +r""" + +Using PennyLane with PySCF and OpenFermion +========================================== + +.. meta:: + :property="og:description": Learn how to integrate external quantum chemistry libraries with PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_external_libs.png + + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + +*Author: Soran Jahangiri — Posted: 3 January 2023.* + +The quantum chemistry module in PennyLane, :mod:`qchem `, provides built-in +methods to compute molecular integrals, solve Hartree-Fock equations, and construct +`fully-differentiable `_ molecular +Hamiltonians. PennyLane also lets you take advantage of various +external resources and libraries to build upon existing tools. In this demo we will show you how +to integrate PennyLane with `PySCF `_ and +`OpenFermion `_ to compute molecular integrals, +construct molecular Hamiltonians, and import initial states. + +Building molecular Hamiltonians +------------------------------- +In PennyLane, Hamiltonians for quantum chemistry are built with the +:func:`~.pennylane.qchem.molecular_hamiltonian` function by specifying a backend for solving the +Hartree–Fock equations. The default backend is the differentiable Hartree–Fock solver of the +:mod:`qchem ` module. A molecular Hamiltonian can also be constructed with +non-differentiable backends that use the electronic structure package +`PySCF `_ or the +`OpenFermion-PySCF `_ plugin. These +backends can be selected by setting the keyword argument ``method='pyscf'`` or +``method='openfermion'`` in :func:`~.pennylane.qchem.molecular_hamiltonian`. This requires +``PySCF`` or ``OpenFermion-PySCF`` to be installed by the user depending on the desired backend: + +.. code-block:: bash + + pip install pyscf # for method='pyscf` + pip install openfermionpyscf # for method='openfermion` + +For example, the molecular Hamiltonian for a water molecule can be constructed with the ``pyscf`` +backend as: +""" + +import pennylane as qml +from pennylane import numpy as np + +symbols = ["H", "O", "H"] +geometry = np.array([[-0.0399, -0.0038, 0.0000], + [ 1.5780, 0.8540, 0.0000], + [ 2.7909, -0.5159, 0.0000]], requires_grad = False) +molecule = qml.qchem.Molecule(symbols, geometry) + +H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This generates a PennyLane :class:`~.pennylane.Hamiltonian` that can be used in a VQE workflow or +# converted to a +# `sparse matrix `_ +# in the computational basis. +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.import_operator` function. Here is an example: + +from openfermion.ops import QubitOperator + +H = 0.1 * QubitOperator('X0 X1') + 0.2 * QubitOperator('Z0') +H = qml.qchem.import_operator(H) + +print(f'Type: \n {type(H)} \n') +print(f'Hamiltonian: \n {H}') + +############################################################################## +# Computing molecular integrals +# ----------------------------- +# In order to build a +# `molecular Hamiltonian `_, we need +# one- and two-electron integrals in the molecular orbital basis. These integrals are used to +# construct a fermionic Hamiltonian which is then mapped onto the qubit basis. These molecular +# integrals can be computed with the +# :func:`~.pennylane.qchem.electron_integrals` function of PennyLane. Alternatively, the integrals +# can be computed with the `PySCF `_ package and used in PennyLane +# workflows such as building a +# `fermionic Hamiltonian `_ or +# quantum `resource estimation `_. +# Let's use water as an example. +# +# First, we define the PySCF molecule object and run a restricted Hartree-Fock +# calculation: + +from pyscf import gto, ao2mo, scf + +mol_pyscf = gto.M(atom = '''H -0.02111417 -0.00201087 0.; + O 0.83504162 0.45191733 0.; + H 1.47688065 -0.27300252 0.''') +rhf = scf.RHF(mol_pyscf) +energy = rhf.kernel() + +############################################################################## +# We obtain the molecular integrals ``one_ao`` and ``two_ao`` in the basis of atomic orbitals +# by following the example `here `_: + +one_ao = mol_pyscf.intor_symmetric('int1e_kin') + mol_pyscf.intor_symmetric('int1e_nuc') +two_ao = mol_pyscf.intor('int2e_sph') + +############################################################################## +# These integrals are then mapped to the basis of molecular orbitals: + +one_mo = np.einsum('pi,pq,qj->ij', rhf.mo_coeff, one_ao, rhf.mo_coeff) +two_mo = ao2mo.incore.full(two_ao, rhf.mo_coeff) + +############################################################################## +# Note that the two-electron integral tensor is represented in +# `chemists' notation `_. To use it +# in PennyLane, we need to convert it into the so-called +# *physicists' notation*: + +two_mo = np.swapaxes(two_mo, 1, 3) + +############################################################################## +# Let's now look at an example where these molecular integrals are used to build the fermionic +# Hamiltonian of water. To do that we also need to compute the nuclear energy contribution: + +core_constant = np.array([rhf.energy_nuc()]) + +############################################################################## +# We now use the integrals to construct a fermionic Hamiltonian with PennyLane's powerful tools +# for creating and manipulating +# `fermionic operators `_: + +H_fermionic = qml.qchem.fermionic_observable(core_constant, one_mo, two_mo) + +############################################################################## +# The Hamiltonian can be mapped to the qubit basis with the :func:`~.pennylane.jordan_wigner` +# function: + +H = qml.jordan_wigner(H_fermionic) + +############################################################################## +# Importing initial states +# ------------------------ +# Simulating molecules with quantum algorithms requires defining an initial state that should have +# non-zero overlap with the molecular ground state. A trivial choice for the initial state is the +# Hartree-Fock state which is obtained by putting the electrons in the lowest-energy molecular +# orbitals. For molecules with a complicated electronic structure, the Hartree-Fock state has +# only a small overlap with the ground state, which makes executing quantum algorithms +# inefficient. +# +# Initial states obtained from affordable post-Hartree-Fock calculations can be used to make the +# quantum workflow more performant. For instance, configuration interaction (CI) and coupled cluster +# (CC) calculations with single and double (SD) excitations can be performed using PySCF and the +# resulting wave function can be used as the initial state in the quantum algorithm. PennyLane +# provides the :func:`~.pennylane.qchem.import_state` function that takes a PySCF solver object, +# extracts the wave function and returns a state vector in the computational basis that can be used +# in a quantum circuit. Let’s look at an example. +# +# First, we run CCSD calculations for the hydrogen molecule to obtain the solver object. + +from pyscf import gto, scf, cc + +mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0, 0, 0.7)]]) +myhf = scf.RHF(mol).run() +mycc = cc.CCSD(myhf).run() + +############################################################################## +# Then, we use the :func:`~.pennylane.qchem.import_state` function to obtain the +# state vector. + +state = qml.qchem.import_state(mycc) +print(state) + +############################################################################## +# You can verify that this state is a superposition of the Hartree-Fock state and a doubly-excited +# state. +# +# Converting fermionic operators +# ------------------------------ +# Fermionic operators are commonly used to construct observables for molecules and spin systems. +# You can easily convert between fermionic operators created with PennyLane and OpenFermion by using +# the :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. Let's +# look at some examples. First, we create a fermionic operator with OpenFermion and convert it to a +# PennyLane fermionic operator. + +from openfermion import FermionOperator +openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^') +pennylane_op = qml.from_openfermion(openfermion_op) +print(pennylane_op) + +############################################################################## +# The resulting operator can be used in PennyLane like any other fermionic object. We now take this +# PennyLane fermionic operator and convert it back to an OpenFermion operator. + +openfermion_op = qml.to_openfermion(pennylane_op) +print(openfermion_op) + +############################################################################## +# The :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions support +# converting several operator types. You can look at the function documentations for more details +# and examples. + +############################################################################## +# Conclusions +# ----------- +# This tutorial demonstrates how to use PennyLane with external quantum chemistry libraries such as +# `PySCF `_ and +# `OpenFermion `_. +# +# To summarize: +# +# 1. We can construct molecular Hamiltonians in PennyLane by using a user-installed version of PySCF +# by passing the argument ``method=pyscf`` to the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function. +# 2. We can directly use one- and two-electron integrals from PySCF, but we need to convert the +# tensor containing the two-electron integrals from chemists' notation to physicists' notation. +# 3. We can easily convert between OpenFermion operators and PennyLane operators using the +# :func:`~.pennylane.from_openfermion` and :func:`~.pennylane.to_openfermion` functions. +# 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the +# :func:`~.pennylane.qchem.import_state` function. +# diff --git a/demonstrations_v2/tutorial_qchem_external/metadata.json b/demonstrations_v2/tutorial_qchem_external/metadata.json new file mode 100644 index 0000000000..e6aae2983b --- /dev/null +++ b/demonstrations_v2/tutorial_qchem_external/metadata.json @@ -0,0 +1,52 @@ +{ + "title": "Using PennyLane with PySCF and OpenFermion", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2023-01-03T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry", + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/external_libs/thumbnail_tutorial_external_libs.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_external_libs.png" + } + ], + "seoDescription": "Learn how to integrate external quantum chemistry libraries with PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_adaptive_circuits", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qchem_external/requirements.in b/demonstrations_v2/tutorial_qchem_external/requirements.in new file mode 100644 index 0000000000..55116fe210 --- /dev/null +++ b/demonstrations_v2/tutorial_qchem_external/requirements.in @@ -0,0 +1,3 @@ +openfermion +pennylane +pyscf diff --git a/demonstrations_v2/tutorial_qft/demo.py b/demonstrations_v2/tutorial_qft/demo.py new file mode 100644 index 0000000000..5b779ab2a7 --- /dev/null +++ b/demonstrations_v2/tutorial_qft/demo.py @@ -0,0 +1,179 @@ +r"""Intro to the Quantum Fourier Transform +============================================================= + +The quantum Fourier transform (QFT) is one of the most important building blocks in quantum algorithms, famously used in `quantum phase estimation `__ and `Shor's factoring algorithm `__. + +The QFT is a quantum analog of the discrete Fourier transform --- the main tool of digital signal processing --- which is used to analyze periodic functions by mapping between time and frequency representations. + +In this tutorial you will learn how to define this operation and how to build it with basic gates. + +.. figure:: ../_static/demonstration_assets/qft/socialthumbnail_large_QFT_2024-04-04.png + :align: center + :width: 60% + :target: javascript:void(0) + +Defining the Quantum Fourier Transform +--------------------------------------- + +To appreciate the QFT, it will help to start with its classical counterpart. +The discrete Fourier transform (DFT) takes as input a vector :math:`(x_0, \dots, x_{N-1}) \in \mathbb{C}^N` and returns another vector :math:`(y_0, \dots, y_{N-1}) \in \mathbb{C}^N` where: + +.. math:: + y_k = \sum_{j = 0}^{N-1} x_j \exp \left(-\frac{2\pi i kj}{N}\right). + +For ease of comparison we assume :math:`N = 2^n`. The idea of the QFT is to perform the same operation but in a quantum state :math:`|x\rangle = \sum_{i = 0}^{N-1} x_i |i\rangle.` +In this case, the output is another quantum state :math:`|y\rangle = \sum_{i = 0}^{N-1} y_i |i\rangle` where: + +.. math:: + y_k = \frac{1}{\sqrt{N}} \sum_{j = 0}^{N-1} x_j \exp \left(\frac{2\pi i kj}{N} \right). + +For historical reasons, the sign of the exponent is positive in the defintion of the QFT, as opposed to a negative exponent in the DFT. Therefore the DFT technically coincides with the inverse operation :math:`\text{QFT}^{\dagger}` . Also, in the QFT we include normalization factor :math:`\frac{1}{\sqrt{N}}.` + +These transformations are linear and can be represented by a matrix. Let's verify that they actually match using scipy's implementation of the DFT and PennyLane's implementation of the QFT: +""" + +from scipy.linalg import dft +import pennylane as qml +import numpy as np + +n = 2 + +print("DFT matrix for n = 2:\n") +print(np.round(1 / np.sqrt(2 ** n) * dft(2 ** n), 2)) + +qft_inverse = qml.adjoint(qml.QFT([0,1])) + +print("\n inverse QFT matrix for n = 2:\n") +print(np.round(qft_inverse.matrix(), 2)) + +############################################# +# The QFT achieves something remarkable: it is able to transform an :math:`N`-dimensional vector encoded in a system of only :math:`n=\log_2 N` qubits. As we now explain, this is possible using only :math:`\mathcal{O}(n^2)` operations, as opposed to :math:`\mathcal{O}(n2^n)` steps required for the DFT. +# +# Building the Quantum Fourier Transform +# -------------------------------------- +# +# To implement the QFT on a quantum computer, it is useful to express the transformation using the equivalent representation: +# +# .. math:: +# +# \text{QFT}|x\rangle = \bigotimes_{k = n-1}^{0} \left (|0\rangle + \exp \left (\frac{2\pi i 2^k}{2^n} x \right) |1\rangle \right ), +# +# for :math:`x \in [0, \dots, N-1].` The nice thing about this formula is that it expresses the output state as a tensor product of single-qubit states. We call :math:`U_k` the unitary operator that is able to prepare the state of the k-th qubit. This operator is defined as: +# +# .. math:: +# +# U_k |x\rangle = |x_0 \dots x_{k-1}\rangle \otimes \left (|0\rangle + \exp \left (\frac{2\pi i 2^k}{2^n} x \right) |1\rangle \right ) \otimes |x_{k+1} \dots x_{n-1}\rangle, +# +# where :math:`|x\rangle = |x_0 \dots x_{n-1}\rangle.` +# We can build :math:`U_k` with one Hadamard gate and controlled :class:`~.pennylane.PhaseShift` gates. Below we show an animation of the operator for the particular case of :math:`n = 4` and :math:`k = 1.` +# We represent the phase-shift gates with a box indicating the phase that they apply. +# +# .. figure:: ../_static/demonstration_assets/qft/qft_gif.gif +# :align: center +# :width: 80% +# +# +# Each gate applies a phase proportional to the position of its control qubit, and we only need to control on qubits that are "below" the target one. +# With these operators we can now prepare the QFT by applying them sequentially: +# +# .. math:: +# +# QFT = U_{n-1} \dots U_1 U_0. +# +# +# Overall, each :math:`U_k` uses :math:`n-k-1` controlled phase-shift operations and one Hadamard, which needs to be repeated for all :math:`n` qubits. This leads to a total of :math:`\mathcal{O}(n^2)` gates to implement the QFT. For example, the circuit implementation for the case of 3 qubits would be: +# +# .. figure:: +# ../_static/demonstration_assets/qft/qft3.jpeg +# +# Although this representation already defines the QFT, there are different conventions when writing the final result. +# In PennyLane, we rearrange the qubits in the opposite ordering; that is why we +# apply SWAP gates at the end. Let's see how the decomposition looks like using the drawer: + +import pennylane as qml +from functools import partial +import matplotlib.pyplot as plt + +plt.style.use('pennylane.drawer.plot') + +# This line is to expand the circuit to see the operators +@partial(qml.devices.preprocess.decompose, stopping_condition = lambda obj: False, max_expansion=1) + +def circuit(): + qml.QFT(wires=range(4)) + +qml.draw_mpl(circuit, decimals = 2, style = "pennylane")() +plt.show() + +############################################# +# Note that the numerical arguments are :math:`\frac{\pi}{2} \approx 1.57,` :math:`\frac{\pi}{4} \approx 0.79` and :math:`\frac{\pi}{8} \approx 0.39` (rounded to the first two decimal places). +# +# Quantum Fourier transform in practice +# -------------------------------------- +# +# We have seen how to define the QFT and how to build it with basic gates; +# now it's time to put it into practice. Let's imagine that we have an operator that prepares the state: +# +# .. math:: +# +# |\psi\rangle = \frac{1}{\sqrt{2^5}} \sum_{x=0}^{31} \exp \left (\frac{-2 \pi i x}{10} \right)|x\rangle, +# +# whose associated period is :math:`10.` We will use the QFT in PennyLane to find that period, +# but first let's visualize the state by drawing the amplitudes: + + +def prep(): + """quntum function that prepares the state.""" + qml.PauliX(wires=0) + for wire in range(1, 6): + qml.Hadamard(wires=wire) + qml.ControlledSequence(qml.PhaseShift(-2 * np.pi / 10, wires=0), control=range(1, 6)) + qml.PauliX(wires=0) + +dev = qml.device("default.qubit") +@qml.qnode(dev) +def circuit(): + prep() + return qml.state() + +state = circuit().real[:32] + +plt.bar(range(len(state)), state) +plt.xlabel("|x⟩") +plt.ylabel("Amplitude (real part)") +plt.show() + +############################################# +# In this image we have represented only the real part so we can visualize it easily. +# The goal now is to compute the period of the function encoded in this five-qubit state. We will use the QFT, +# which is able to transform the state into the frequency domain. This is shown in the code below: +# + +@qml.qnode(dev) +def circuit(): + prep() + qml.QFT(wires=range(1, 6)) + + return qml.probs(wires=range(1, 6)) + +state = circuit()[:32] + +plt.bar(range(len(state)), state) +plt.xlabel("|x⟩") +plt.ylabel("probs") +plt.show() + +############################################# +# The output has a clear peak at :math:`|x\rangle = 3.` +# This value corresponds to an approximation of :math:`2^nf` where :math:`f` is the frequency and :math:`n` is the +# number of qubits. +# Once we know the frequency, we invert it to obtain the period :math:`T` of our state. +# In this case, the period is :math:`T = 2^5 / 3 \sim 10.33,` close to the real value of :math:`10.` +# +# Conclusion +# ---------- +# In this tutorial, we've journeyed through the fundamentals and construction of the quantum Fourier transform, a +# cornerstone in quantum computing. We explored its mathematical +# formulation, its implementation with basic quantum gates, and its application demonstrating its usefulness in algorithms like quantum phase estimation. +# It is a technique that deserves to be mastered! +# diff --git a/demonstrations_v2/tutorial_qft/metadata.json b/demonstrations_v2/tutorial_qft/metadata.json new file mode 100644 index 0000000000..82ef40f977 --- /dev/null +++ b/demonstrations_v2/tutorial_qft/metadata.json @@ -0,0 +1,31 @@ +{ + "title": "Intro to Quantum Fourier Transform", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-04-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qft/thumbnail_QFT.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QFT_2024-04-04.png" + } + ], + "seoDescription": "Master the basics of the quantum fourier transform", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qft/requirements.in b/demonstrations_v2/tutorial_qft/requirements.in new file mode 100644 index 0000000000..8132c3977e --- /dev/null +++ b/demonstrations_v2/tutorial_qft/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_qft_arithmetics/demo.py b/demonstrations_v2/tutorial_qft_arithmetics/demo.py new file mode 100644 index 0000000000..74ba2dd3a5 --- /dev/null +++ b/demonstrations_v2/tutorial_qft_arithmetics/demo.py @@ -0,0 +1,430 @@ +r""".. _qft_arithmetics: + +Basic arithmetic with the quantum Fourier transform (QFT) +======================================= + +.. meta:: + :property="og:description": Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qft_arithmetics_thumbnail.png + +.. related:: + tutorial_qubit_rotation Basis tutorial: qubit rotation + + + +*Author: Guillermo Alonso-Linaje — Posted: 07 November 2022.* + +Arithmetic is a fundamental branch of mathematics that consists of the study of the main operations with numbers such as +addition, multiplication, subtraction and division. Using arithmetic operations we can understand the world around us +and solve many of our daily tasks. + +Arithmetic is crucial for the implementation of any kind of algorithm in classical computer science, but also in quantum computing. For this reason, in this demo we are going to show +an approach to defining arithmetic operations on quantum computers. The simplest and most direct way to achieve this goal is to use the +quantum Fourier transform (QFT), which we will demonstrate on a basic level. + +In this demo we will not focus on understanding how the QFT is built, +as we can find a great explanation in the +`PennyLane Codebook `__. Instead, we will develop the +intuition for how it works and how we can best take advantage of it. + +Motivation +---------- + +The first question we have to ask ourselves is whether it makes sense to perform these basic operations on a quantum computer. Is the +goal purely academic or is it really something that is needed in certain algorithms? Why implement in a quantum computer +something that we can do with a calculator? + +When it comes to basic quantum computing algorithms like the Deustch–Jozsa or +Grover's algorithm, we might think that we have never needed to apply any arithmetic operations such as addition or multiplication. +However, the reality is different. When we learn about these algorithms from an academic point of view, +we work with a ready-made operator that we never have to worry about, the *oracle*. +Unfortunately, in real-world situations, we will have to build this seemingly magical operator by hand. +As an example, let's imagine that we want to use Grover's algorithm to search for `magic squares `__. +To define the oracle, which determines whether a solution is valid or not, we must perform sums over the rows and +columns to check that they all have the same value. Therefore, to create this oracle, +we will need to define a sum operator within the quantum computer. + +The second question we face is why we should work with the QFT at all. There are other procedures that could be used to perform these basic operations; for example, by +imitating the classical algorithm. But, as we can see in [#Draper2000]_, it has already been proven that the QFT needs fewer qubits to perform these operations, which is +nowadays of vital importance. + +We will organize the demo as follows. Initially, we will talk about the Fourier basis to give an intuitive idea of how +it works, after which we will address different basic arithmetic operations. Finally, we will move on to a practical example +in which we will factor numbers using Grover's algorithm. + + +QFT representation +----------------- + +To apply the QFT to basic arithmetic operations, our objective now is to learn how to add, +subtract and multiply numbers using quantum devices. As we are working with qubits, +—which, like bits, can take the +values :math:`0` or :math:`1`—we will represent the numbers in binary. For the +purposes of this tutorial, we will assume that we are working only with +integers. Therefore, if we have :math:`n` qubits, we will be able to +represent the numbers from :math:`0` to :math:`2^n-1.` + +The first thing we need to know is PennyLane's +standard for encoding numbers in a binary format. A binary number can be +represented as a string of 1s and 0s, which we can represent as the multi-qubit state + +.. math:: \vert m \rangle = \vert \overline{q_0q_1...q_{n-1}}\rangle, + +where the formula to obtain the equivalent decimal number :math:`m` will be: + +.. math:: m= \sum_{i = 0}^{n-1}2^{n-1-i}q_i. + +Note that :math:`\vert m \rangle` refers to the basic state +generated by the binary encoding of the number :math:`m.` +For instance, the natural number :math:`6` +is represented by the quantum state :math:`\vert 110\rangle,` since :math:`\vert 110 \rangle = 1 \cdot 2^2 + 1\cdot 2^1+0\cdot 2^0 = 6.` + +Let’s see how we would represent all the integers from :math:`0` to :math:`7` using the product state of three qubits, visualized by separate Bloch spheres for each qubit. + +.. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_computational_basis.gif + :width: 90% + :align: center + + Representation of integers using a computational basis of three qubits. + +.. note:: + + The `Bloch sphere `_ + is a way of graphically representing the state of a qubit. + At the top of the sphere we place the state :math:`\vert 0 \rangle,` at the bottom + :math:`\vert 1 \rangle,` and in the rest of the + sphere we will place the possible states in superposition. It is a very useful + representation that helps better visualize and interpret quantum gates such as rotations. + +We can use +the :class:`qml.BasisEmbedding ` +template to obtain the binary representation in a simple way. +Let's see how we would code the number :math:`6.` +""" + +import pennylane as qml +import matplotlib.pyplot as plt + +dev = qml.device("default.qubit", wires=3) + +@qml.compile +@qml.qnode(dev) +def basis_embedding_circuit(m): + qml.BasisEmbedding(m, wires=range(3)) + return qml.state() + +m = 6 # number to be encoded + +qml.draw_mpl(basis_embedding_circuit, show_all_wires=True)(m) +plt.show() + +###################################################################### +# +# As we can see, the first qubit—the :math:`0`-th wire—is placed on top and the rest of the qubits are +# below it. However, this is not the only way we could represent numbers. +# We can also represent them in different bases, such as the so-called *Fourier base*. +# +# In this case, all the states of the basis will be represented via qubits in +# the XY-plane of the Bloch sphere, each rotated by a certain +# amount. +# +# +# How do we know how much we must rotate each qubit to represent a certain number? +# It is actually very easy! Suppose we are working with +# :math:`n` qubits and we want to represent the number :math:`m` in the +# Fourier basis. Then the :math:`j`-th qubit will have the phase: +# +# .. math:: \alpha_j = \frac{2m\pi}{2^{j}}. +# +# Now we can represent numbers in the Fourier basis using three qubits: +# +# .. figure:: /_static/demonstration_assets/qft_arithmetics/3_qubits_fourier_basis.gif +# :width: 90% +# :align: center +# +# Representation of integers using the Fourier basis with three qubits +# +# As we can see, the third qubit will rotate +# :math:`\frac{1}{8}` of a turn counterclockwise with each number. The next qubit +# rotates :math:`\frac{1}{4}` of a full turn and, finally, the first qubit rotates +# half a turn for each increase in number. +# +# Adding a number to a register +# ------------------------------ +# +# The fact that the states encoding the numbers are now in phase gives us great +# flexibility in carrying out our arithmetic operations. To see this in practice, +# let’s look at the situation in which want to create an operator Sum +# such that: +# +# .. math:: \text{Sum(k)}\vert m \rangle = \vert m + k \rangle. +# +# The procedure to implement this unitary operation is the following: +# +# #. We convert the state from the computational basis into the Fourier basis by applying the QFT to the :math:`\vert m \rangle` state via the :class:`~pennylane.QFT` operator. +# +# #. We rotate the :math:`j`-th qubit by the angle :math:`\frac{2k\pi}{2^{j}}` using the :math:`R_Z` gate, which leads to the new phases, :math:`\frac{2(m + k)\pi}{2^{j}}.` +# +# #. We apply the QFT inverse to return to the computational basis and obtain :math:`m+k.` +# +# +# Let's see how this process would look in PennyLane. +# + +import pennylane as qml +import numpy as np + +n_wires = 4 +dev = qml.device("default.qubit", wires=n_wires, shots=1) + +def add_k_fourier(k, wires): + for j in range(len(wires)): + qml.RZ(k * np.pi / (2**j), wires=wires[j]) + +@qml.qnode(dev) +def sum(m, k): + qml.BasisEmbedding(m, wires=range(n_wires)) # m encoding + + qml.QFT(wires=range(n_wires)) # step 1 + + add_k_fourier(k, range(n_wires)) # step 2 + + qml.adjoint(qml.QFT)(wires=range(n_wires)) # step 3 + + return qml.sample() + + +print(f"The ket representation of the sum of 3 and 4 is {sum(3,4)}") + +###################################################################### +# Perfect, we have obtained :math:`\vert 0111 \rangle,` which is equivalent to the number :math:`7` in binary! +# +# Note that this is a deterministic algorithm, which means that we have obtained the desired solution by executing a single shot. +# On the other hand, if the result of an operation is greater than the maximum +# value :math:`2^n-1,` we will start again from zero, that is to say, we +# will calculate the sum modulo :math:`2^n-1.` For instance, in our three-qubit example, suppose that +# we want to calculate :math:`6+3.` We see that we do not have +# enough memory space, as :math:`6+3 = 9 > 2^3-1.` The result we will get will +# be :math:`9 \pmod 8 = 1,` or :math:`\vert 001 \rangle` in binary. Make sure to use +# enough qubits to represent your solutions! +# Finally, it is important to point out that it is not necessary to know how the +# QFT is constructed in order to use it. By knowing the properties of the +# new basis, we can use it in a simple way. +# +# Adding two different registers +# ------------------------------ +# +# In this previous algorithm, we had to pass to the operator the value of :math:`k` that we wanted to add to the starting state. +# But at other times, instead of passing that value to the operator, we would like it to pull the information from another register. +# That is, we are looking for a new operator :math:`\text{Sum}_2` such that +# +# .. math:: \text{Sum}_2\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m+k \rangle. +# +# In this case, we can understand the third register (which is initially +# at :math:`0`) as a counter that will tally as many units as :math:`m` and +# :math:`k` combined. The binary decomposition will +# make this simple. If we have :math:`\vert m \rangle = \vert \overline{q_0q_1q_2} \rangle,` we will +# have to add :math:`1` to the counter if :math:`q_2 = 1` and nothing +# otherwise. In general, we should add :math:`2^{n-i-1}` units if the :math:`i`-th +# qubit is in state :math:`\vert 1 \rangle` and 0 otherwise. As we can see, this is the same idea that is also +# behind the concept of a controlled gate. Indeed, observe that we will, indeed, apply a corresponding +# phase if indeed the control qubit is in the state :math:`\vert 1\rangle.` +# Let us now code the :math:`\text{Sum}_2` operator. + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) # total number of qubits used + +def addition(wires_m, wires_k, wires_solution): + # prepare solution qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_m)): + qml.ctrl(add_k_fourier, control=wires_m[i])(2 **(len(wires_m) - i - 1), wires_solution) + + # add k to the counter + for i in range(len(wires_k)): + qml.ctrl(add_k_fourier, control=wires_k[i])(2 **(len(wires_k) - i - 1), wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def sum2(m, k, wires_m, wires_k, wires_solution): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # apply the addition circuit + addition(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + +print(f"The ket representation of the sum of 7 and 3 is " + f"{sum2(7, 3, wires_m, wires_k, wires_solution)}") + +qml.draw_mpl(sum2, show_all_wires=True)(7, 3, wires_m, wires_k, wires_solution) +plt.show() + +###################################################################### +# Great! We have just seen how to add a number to a counter. In the example above, +# we added :math:`3 + 7` to get :math:`10,` which in binary +# is :math:`\vert 1010 \rangle.` +# +# Multiplying qubits +# ------------------- +# +# Following the same idea, we will see how easily we can +# implement multiplication. For this purpose we'll take two arbitrary numbers :math:`m` and :math:`k` +# to carry out the operation. This time, we look for an operator Mul such that +# +# .. math:: \text{Mul}\vert m \rangle \vert k \rangle \vert 0 \rangle = \vert m \rangle \vert k \rangle \vert m\cdot k \rangle. +# +# To understand the multiplication process, let's work with the binary decomposition of +# :math:`k:=\sum_{i=0}^{n-1}2^{n-i-1}k_i` and +# :math:`m:=\sum_{j=0}^{l-1}2^{l-j-1}m_i.` In this case, the product would +# be: +# +# .. math:: k \cdot m = \sum_{i=0}^{n-1}\sum_{j = 0}^{l-1}m_ik_i (2^{n-i-1} \cdot 2^{l-j-1}). +# +# In other words, if :math:`k_i = 1` and :math:`m_i = 1,` we would add +# :math:`2^{n-i-1} \cdot 2^{l-j-1}` units to the counter, where :math:`n` and :math:`l` +# are the number of qubits with which we encode :math:`m` and :math:`k` respectively. +# Let's code to see how it works! + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution, shots=1) + +n_wires = len(dev.wires) + +def multiplication(wires_m, wires_k, wires_solution): + # prepare sol-qubits to counting + qml.QFT(wires=wires_solution) + + # add m to the counter + for i in range(len(wires_k)): + for j in range(len(wires_m)): + coeff = 2 ** (len(wires_m) + len(wires_k) - i - j - 2) + qml.ctrl(add_k_fourier, control=[wires_k[i], wires_m[j]])(coeff, wires_solution) + + # return to computational basis + qml.adjoint(qml.QFT)(wires=wires_solution) + +@qml.qnode(dev) +def mul(m, k): + # m and k codification + qml.BasisEmbedding(m, wires=wires_m) + qml.BasisEmbedding(k, wires=wires_k) + + # Apply multiplication + multiplication(wires_m, wires_k, wires_solution) + + return qml.sample(wires=wires_solution) + + +print(f"The ket representation of the multiplication of 3 and 7 is {mul(3,7)}") + +qml.draw_mpl(mul, show_all_wires=True)(3, 7) +plt.show() + + +###################################################################### +# Awesome! We have multiplied :math:`7 \cdot 3` and, as a result, we have +# :math:`\vert 10101 \rangle,` which is :math:`21` in binary. +# +# +# Factorization with Grover +# ------------------------- +# +# With this, we have already gained a large repertoire of interesting +# operations that we can do, but we can give the idea one more twist and +# apply what we have learned in an example. +# +# Let’s imagine now that we want just the opposite: to factor the +# number :math:`21` as a product of two terms. Is this something we could do +# using our previous reasoning? The answer is yes! We can make use of +# `Grover's algorithm `_ to +# amplify the states whose product is the number we +# are looking for. All we would need is to construct the oracle :math:`U,` i.e., an +# operator such that +# +# .. math:: U\vert m \rangle \vert k \rangle = \vert m \rangle \vert k \rangle \text{ if }m\cdot k \not = 21, +# +# .. math:: U\vert m \rangle \vert k \rangle = -\vert m \rangle \vert k \rangle \text{ if }m\cdot k = 21 +# +# The idea of the oracle is as simple as this: +# +# #. use auxiliary registers to store the product, +# #. check if the product state is :math:`\vert 10101 \rangle` and, in that case, change the sign, +# #. execute the inverse of the circuit to clear the auxiliary qubits. +# #. calculate the probabilities and see which states have been amplified. +# +# Let's go back to PennyLane to implement this idea. + +n = 21 # number we want to factor + +wires_m = [0, 1, 2] # qubits needed to encode m +wires_k = [3, 4, 5] # qubits needed to encode k +wires_solution = [6, 7, 8, 9, 10] # qubits needed to encode the solution + +dev = qml.device("default.qubit", wires=wires_m + wires_k + wires_solution) + +n_wires = len(dev.wires) + +@qml.qnode(dev) +def factorization(n, wires_m, wires_k, wires_solution): + # Superposition of the input + for wire in wires_m: + qml.Hadamard(wires=wire) + + for wire in wires_k: + qml.Hadamard(wires=wire) + + # Apply the multiplication + multiplication(wires_m, wires_k, wires_solution) + + # Change sign of n + qml.FlipSign(n, wires=wires_solution) + + # Uncompute multiplication + qml.adjoint(multiplication)(wires_m, wires_k, wires_solution) + + # Apply Grover operator + qml.GroverOperator(wires=wires_m + wires_k) + + return qml.probs(wires=wires_m) + + +plt.bar(range(2 ** len(wires_m)), factorization(n, wires_m, wires_k, wires_solution)) +plt.xlabel("Basic states") +plt.ylabel("Probability") +plt.show() + +###################################################################### +# By plotting the probabilities of obtaining each basic state we see that +# prime factors have been amplified! Factorization via Grover’s algorithm +# does not achieve exponential improvement that +# `Shor's algorithm `_ does, but we +# can see that this construction is simple and a great example to +# illustrate basic arithmetic! +# +# I hope we can now all see that oracles are not something magical and that there +# is a lot of work behind their construction! This will help us in the future to build +# more complicated operators, but until then, let’s keep on learning. 🚀 +# +# References +# ---------- +# +# .. [#Draper2000] +# +# Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. +# +# diff --git a/demonstrations_v2/tutorial_qft_arithmetics/metadata.json b/demonstrations_v2/tutorial_qft_arithmetics/metadata.json new file mode 100644 index 0000000000..fc8450ff02 --- /dev/null +++ b/demonstrations_v2/tutorial_qft_arithmetics/metadata.json @@ -0,0 +1,44 @@ +{ + "title": "Basic arithmetic with the quantum Fourier transform (QFT)", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2022-11-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_basic_arithmetic_QFT.png" + } + ], + "seoDescription": "Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic", + "doi": "", + "references": [ + { + "id": "Draper2000", + "type": "article", + "title": "Addition on a Quantum Computer", + "authors": "Thomas G. Draper", + "year": "2000", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0008033" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qft_arithmetics/requirements.in b/demonstrations_v2/tutorial_qft_arithmetics/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_qft_arithmetics/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_qgrnn/demo.py b/demonstrations_v2/tutorial_qgrnn/demo.py new file mode 100644 index 0000000000..6005ee5054 --- /dev/null +++ b/demonstrations_v2/tutorial_qgrnn/demo.py @@ -0,0 +1,675 @@ +""" +The Quantum Graph Recurrent Neural Network +=========================================== + +.. meta:: + :property="og:description": Using a quantum graph recurrent neural network to learn quantum dynamics. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qgrnn_thumbnail.png + +*Author: Jack Ceroni — Posted: 27 July 2020. Last updated: 25 March 2021.* + +""" + +###################################################################### +# This demonstration investigates quantum graph +# recurrent neural networks (QGRNN), which are the quantum analogue of a +# classical graph recurrent neural network, and a subclass of the more +# general quantum graph +# neural network ansatz. Both the QGNN and QGRNN were introduced in +# `this paper (2019) `__. + +###################################################################### +# The Idea +# -------- +# + + +###################################################################### +# A graph is defined as a set of *nodes* along with a set of +# **edges**, which represent connections between nodes. +# Information can be encoded into graphs by assigning numbers +# to nodes and edges, which we call **weights**. +# It is usually convenient to think of a graph visually: +# +# .. image:: ../_static/demonstration_assets/qgrnn/graph.png +# :width: 70% +# :align: center +# +# In recent years, the concept of a +# `graph neural network `__ (GNN) has been +# receiving a lot of attention from the machine learning community. +# A GNN seeks +# to learn a representation (a mapping of data into a +# low-dimensional vector space) of a given graph with feature vectors assigned +# to nodes and edges. Each of the vectors in the learned +# representation preserves not only the features, but also the overall +# topology of the graph, i.e., which nodes are connected by edges. The +# quantum graph neural network attempts to do something similar, but for +# features that are quantum-mechanical; for instance, a +# collection of quantum states. +# + + +###################################################################### +# Consider the class of qubit Hamiltonians that are *quadratic*, meaning that +# the terms of the Hamiltonian represent either interactions between two +# qubits, or the energy of individual qubits. +# This class of Hamiltonians is naturally described by graphs, with +# second-order terms between qubits corresponding to weighted edges between +# nodes, and first-order terms corresponding to node weights. +# +# A well known example of a quadratic Hamiltonian is the transverse-field +# Ising model, which is defined as +# +# .. math:: +# +# \hat{H}_{\text{Ising}}(\boldsymbol\theta) \ = \ \displaystyle\sum_{(i, j) \in E} +# \theta_{ij}^{(1)} Z_{i} Z_{j} \ + \ \displaystyle\sum_{i} \theta_{i}^{(2)} Z_{i} \ + \ +# \displaystyle\sum_{i} X_{i}, +# +# where :math:`\boldsymbol\theta \ = \ \{\theta^{(1)}, \ \theta^{(2)}\}.` +# In this Hamiltonian, the set :math:`E` that determines which pairs of qubits +# have :math:`ZZ` interactions can be represented by the set of edges for some graph. With +# the qubits as nodes, this graph is called the *interaction graph*. +# The :math:`\theta^{(1)}` parameters correspond to the edge weights and +# the :math:`\theta^{(2)}` +# parameters correspond to weights on the nodes. +# + + +###################################################################### +# This result implies that we can think about *quantum circuits* with +# graph-theoretic properties. Recall that the time-evolution operator +# with respect to some Hamiltonian :math:`H` is defined as: +# +# .. math:: U \ = \ e^{-it H}. +# +# Thus, we have a clean way of taking quadratic Hamiltonians and turning +# them into unitaries (quantum circuits) that preserve the same correspondance to a graph. +# In the case of the Ising Hamiltonian, we have: +# +# .. math:: +# +# U_{\text{Ising}} \ = \ e^{-it \hat{H}_{\text{Ising}} (\boldsymbol\theta)} \ = \ \exp \Big[ -it +# \Big( \displaystyle\sum_{(i, j) \in E} \theta_{ij}^{(1)} Z_{i} Z_{j} \ + \ +# \displaystyle\sum_{i} \theta_{i}^{(2)} Z_{i} \ + \ \displaystyle\sum_{i} X_{i} \Big) \Big] +# +# In general, this kind of unitary is very difficult to implement on a quantum computer. +# However, we can approximate it using the `Trotter-Suzuki decomposition +# `__: +# +# .. math:: +# +# \exp \Big[ -it \Big( \displaystyle\sum_{(i, j) \in E} \theta_{ij}^{(1)} Z_{i} Z_{j} \ + \ +# \displaystyle\sum_{i} \theta_{i}^{(2)} Z_{i} \ + \ \displaystyle\sum_{i} X_{i} \Big) \Big] +# \ \approx \ \displaystyle\prod_{k \ = \ 1}^{t / \Delta} \Bigg[ \displaystyle\prod_{j \ = \ +# 1}^{Q} e^{-i \Delta \hat{H}_{\text{Ising}}^{j}(\boldsymbol\theta)} \Bigg] +# +# where :math:`\hat{H}_{\text{Ising}}^{j}(\boldsymbol\theta)` is the :math:`j`-th term of the +# Ising Hamiltonian and :math:`\Delta` is some small number. +# +# This circuit is a specific instance of the **Quantum Graph +# Recurrent Neural Network**, which in general is defined as a variational ansatz of +# the form +# +# .. math:: +# +# U_{H}(\boldsymbol\mu, \ \boldsymbol\gamma) \ = \ \displaystyle\prod_{i \ = \ 1}^{P} \Bigg[ +# \displaystyle\prod_{j \ = \ 1}^{Q} e^{-i \gamma_j H^{j}(\boldsymbol\mu)} \Bigg], +# +# for some parametrized quadratic Hamiltonian, :math:`H(\boldsymbol\mu).` + +###################################################################### +# Using the QGRNN +# ^^^^^^^^^^^^^^^^ +# + +###################################################################### +# Since the QGRNN ansatz is equivalent to the +# approximate time evolution of some quadratic Hamiltonian, we can use it +# to learn the dynamics of a quantum system. +# +# Continuing with the Ising model example, let's imagine we have some system +# governed by :math:`\hat{H}_{\text{Ising}}(\boldsymbol\alpha)` for an unknown set of +# target parameters, +# :math:`\boldsymbol\alpha` and an unknown interaction graph :math:`G.` Let's also +# suppose we have access to copies of some +# low-energy, non-ground state of the target Hamiltonian, :math:`|\psi_0\rangle.` In addition, +# we have access to a collection of time-evolved states, +# :math:`\{ |\psi(t_1)\rangle, \ |\psi(t_2)\rangle, \ ..., \ |\psi(t_N)\rangle \},` defined by: +# +# .. math:: |\psi(t_k)\rangle \ = \ e^{-i t_k \hat{H}_{\text{Ising}}(\boldsymbol\alpha)} |\psi_0\rangle. +# +# We call the low-energy states and the collection of time-evolved states *quantum data*. +# From here, we randomly pick a number of time-evolved states +# from our collection. For any state that we choose, which is +# evolved to some time :math:`t_k,` we compare it +# to +# +# .. math:: +# +# U_{\hat{H}_{\text{Ising}}}(\boldsymbol\mu, \ \Delta) |\psi_0\rangle \ \approx \ e^{-i t_k +# \hat{H}_{\text{Ising}}(\boldsymbol\mu)} |\psi_0\rangle. +# +# This is done by feeding one of the copies of :math:`|\psi_0\rangle` into a quantum circuit +# with the QGRNN ansatz, with some guessed set of parameters :math:`\boldsymbol\mu` +# and a guessed interaction graph, :math:`G'.` +# We then use a classical optimizer to maximize the average +# "similarity" between the time-evolved states and the states prepared +# with the QGRNN. +# +# As the QGRNN states becomes more similar to +# each time-evolved state for each sampled time, it follows that +# :math:`\boldsymbol\mu \ \rightarrow \ \boldsymbol\alpha` +# and we are able to learn the unknown parameters of the Hamiltonian. +# +# .. figure:: ../_static/demonstration_assets/qgrnn/qgrnn3.png +# :width: 90% +# :align: center +# +# A visual representation of one execution of the QGRNN for one piece of quantum data. +# + + +###################################################################### +# Learning an Ising Model with the QGRNN +# --------------------------------------- +# + + +###################################################################### +# We now attempt to use the QGRNN to learn the parameters corresponding +# to an arbitrary transverse-field Ising model Hamiltonian. +# + + +###################################################################### +# Getting Started +# ^^^^^^^^^^^^^^^^ +# + + +###################################################################### +# We begin by importing the necessary dependencies: +# + + +import pennylane as qml +from matplotlib import pyplot as plt +from pennylane import numpy as np +import scipy +import networkx as nx +import copy + + +###################################################################### +# We also define some fixed values that are used throughout +# the simulation. +# + + +qubit_number = 4 +qubits = range(qubit_number) + + +###################################################################### +# In this +# simulation, we don't have quantum data readily available to pass into +# the QGRNN, so we have to generate it ourselves. To do this, we must +# have knowledge of the target interaction graph and the target Hamiltonian. +# +# Let us use the following cyclic graph as the target interaction graph +# of the Ising Hamiltonian: +# + + +ising_graph = nx.cycle_graph(qubit_number) + +print(f"Edges: {ising_graph.edges}") +nx.draw(ising_graph) + + + +###################################################################### +# We can then initialize the “unknown” target parameters that describe the +# target Hamiltonian, :math:`\boldsymbol\alpha \ = \ \{\alpha^{(1)}, \ \alpha^{(2)}\}.` +# Recall from the introduction that we have defined our parametrized +# Ising Hamiltonian to be of the form: +# +# .. math:: +# +# \hat{H}_{\text{Ising}}(\boldsymbol\theta) \ = \ \displaystyle\sum_{(i, j) \in E} +# \theta_{ij}^{(1)} Z_{i} Z_{j} \ + \ \displaystyle\sum_{i} \theta_{i}^{(2)} Z_{i} \ + \ +# \displaystyle\sum_{i} X_{i}, +# +# where :math:`E` is the set of edges in the interaction graph, and +# :math:`X_i` and :math:`Z_i` are the Pauli-X and Pauli-Z on the +# :math:`i`-th qubit. +# +# For this tutorial, we choose the target parameters by sampling from +# a uniform probability distribution ranging from :math:`-2` to :math:`2,` with +# two-decimal precision. +# + +target_weights = [0.56, 1.24, 1.67, -0.79] +target_bias = [-1.44, -1.43, 1.18, -0.93] + + +###################################################################### +# In theory, these parameters can +# be any value we want, provided they are reasonably small enough that the QGRNN can reach them +# in a tractable number of optimization steps. +# In ``matrix_params``, the first list represents the :math:`ZZ` interaction parameters and +# the second list represents the single-qubit :math:`Z` parameters. +# +# Finally, +# we use this information to generate the matrix form of the +# Ising model Hamiltonian in the computational basis: +# + + +def create_hamiltonian_matrix(n_qubits, graph, weights, bias): + + full_matrix = np.zeros((2 ** n_qubits, 2 ** n_qubits)) + + # Creates the interaction component of the Hamiltonian + for i, edge in enumerate(graph.edges): + interaction_term = 1 + for qubit in range(0, n_qubits): + if qubit in edge: + interaction_term = np.kron(interaction_term, qml.matrix(qml.PauliZ(0))) + else: + interaction_term = np.kron(interaction_term, np.identity(2)) + full_matrix += weights[i] * interaction_term + + # Creates the bias components of the matrix + for i in range(0, n_qubits): + z_term = x_term = 1 + for j in range(0, n_qubits): + if j == i: + z_term = np.kron(z_term, qml.matrix(qml.PauliZ(0))) + x_term = np.kron(x_term, qml.matrix(qml.PauliX(0))) + else: + z_term = np.kron(z_term, np.identity(2)) + x_term = np.kron(x_term, np.identity(2)) + full_matrix += bias[i] * z_term + x_term + + return full_matrix + + +# Prints a visual representation of the Hamiltonian matrix +ham_matrix = create_hamiltonian_matrix(qubit_number, ising_graph, target_weights, target_bias) +plt.matshow(ham_matrix, cmap="hot") +plt.show() + + + + +###################################################################### +# Preparing Quantum Data +# ^^^^^^^^^^^^^^^^^^^^^^ +# + + +###################################################################### +# The collection of quantum data needed to run the QGRNN has two components: +# (i) copies of a low-energy state, and (ii) a collection of time-evolved states, each of which are +# simply the low-energy state evolved to different times. +# The following is a low-energy state of the target Hamiltonian: +# + +low_energy_state = [ + (-0.054661080280306085 + 0.016713907320174026j), + (0.12290003656489545 - 0.03758500591109822j), + (0.3649337966440005 - 0.11158863596657455j), + (-0.8205175732627094 + 0.25093231967092877j), + (0.010369790825776609 - 0.0031706387262686003j), + (-0.02331544978544721 + 0.007129899300113728j), + (-0.06923183949694546 + 0.0211684344103713j), + (0.15566094863283836 - 0.04760201916285508j), + (0.014520590919500158 - 0.004441887836078486j), + (-0.032648113364535575 + 0.009988590222879195j), + (-0.09694382811137187 + 0.02965579457620536j), + (0.21796861485652747 - 0.06668776658411019j), + (-0.0027547112135013247 + 0.0008426289322652901j), + (0.006193695872468649 - 0.0018948418969390599j), + (0.018391279795405405 - 0.005625722994009138j), + (-0.041350974715649635 + 0.012650711602265649j), +] + + +###################################################################### +# This state can be obtained by using a decoupled version of the +# :doc:`Variational Quantum Eigensolver ` algorithm (VQE). +# Essentially, we choose a +# VQE ansatz such that the circuit cannot learn the exact ground state, +# but it can get fairly close. Another way to arrive at the same result is +# to perform VQE with a reasonable ansatz, but to terminate the algorithm +# before it converges to the ground state. If we used the exact ground state +# :math:`|\psi_0\rangle,` the time-dependence would be trivial and the +# data would not provide enough information about the Hamiltonian parameters. +# +# We can verify that this is a low-energy +# state by numerically finding the lowest eigenvalue of the Hamiltonian +# and comparing it to the energy expectation of this low-energy state: +# + + +res = np.vdot(low_energy_state, (ham_matrix @ low_energy_state)) +energy_exp = np.real_if_close(res) +print(f"Energy Expectation: {energy_exp}") + + +ground_state_energy = np.real_if_close(min(np.linalg.eig(ham_matrix)[0])) +print(f"Ground State Energy: {ground_state_energy}") + + +###################################################################### +# We have in fact found a low-energy, non-ground state, +# as the energy expectation is slightly greater than the energy of the true ground +# state. This, however, is only half of the information we need. We also require +# a collection of time-evolved, low-energy states. +# Evolving the low-energy state forward in time is fairly straightforward: all we +# have to do is multiply the initial state by a time-evolution unitary. This operation +# can be defined as a custom gate in PennyLane: +# + + +def state_evolve(hamiltonian, qubits, time): + + U = scipy.linalg.expm(-1j * hamiltonian * time) + qml.QubitUnitary(U, wires=qubits) + + +###################################################################### +# We don't actually generate time-evolved quantum data quite yet, +# but we now have all the pieces required for its preparation. +# + + +###################################################################### +# Learning the Hamiltonian +# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# + + +###################################################################### +# With the quantum data defined, we are able to construct the QGRNN and +# learn the target Hamiltonian. +# Each of the exponentiated +# Hamiltonians in the QGRNN ansatz, +# :math:`\hat{H}^{j}_{\text{Ising}}(\boldsymbol\mu),` are the +# :math:`ZZ`, :math:`Z,` and :math:`X` terms from the Ising +# Hamiltonian. This gives: +# + + +def qgrnn_layer(weights, bias, qubits, graph, trotter_step): + + # Applies a layer of RZZ gates (based on a graph) + for i, edge in enumerate(graph.edges): + qml.MultiRZ(2 * weights[i] * trotter_step, wires=(edge[0], edge[1])) + + # Applies a layer of RZ gates + for i, qubit in enumerate(qubits): + qml.RZ(2 * bias[i] * trotter_step, wires=qubit) + + # Applies a layer of RX gates + for qubit in qubits: + qml.RX(2 * trotter_step, wires=qubit) + + +###################################################################### +# As was mentioned in the first section, the QGRNN has two +# registers. In one register, some piece of quantum data +# :math:`|\psi(t)\rangle` is prepared and in the other we have +# :math:`U_{H}(\boldsymbol\mu, \ \Delta) |\psi_0\rangle.` We need a +# way to measure the similarity between these states. +# This can be done by using the fidelity, which is +# simply the modulus squared of the inner product between the states, +# :math:`| \langle \psi(t) | U_{H}(\Delta, \ \boldsymbol\mu) |\psi_0\rangle |^2.` +# To calculate this value, we use a `SWAP +# test `__ between the registers: +# + + +def swap_test(control, register1, register2): + + qml.Hadamard(wires=control) + for reg1_qubit, reg2_qubit in zip(register1, register2): + qml.CSWAP(wires=(control, reg1_qubit, reg2_qubit)) + qml.Hadamard(wires=control) + + +###################################################################### +# After performing this procedure, the value returned from a measurement of the circuit is +# :math:`\langle Z \rangle,` with respect to the ``control`` qubit. +# The probability of measuring the :math:`|0\rangle` state +# in this control qubit is related to both the fidelity +# between registers and :math:`\langle Z \rangle.` Thus, with a bit of algebra, +# we find that :math:`\langle Z \rangle` is equal to the fidelity. +# +# Before creating the full QGRNN and the cost function, we +# define a few more fixed values. Among these is a "guessed" +# interaction graph, which we set to be a +# `complete graph `__. This choice is +# motivated by the fact that any target interaction graph will be a subgraph +# of this initial guess. Part of the idea behind the QGRNN is that +# we don’t know the interaction graph, and it has to be learned. In this case, the graph +# is learned *automatically* as the target parameters are optimized. The +# :math:`\boldsymbol\mu` parameters that correspond to edges that don't exist in +# the target graph will simply approach :math:`0.` +# + +# Defines some fixed values + +reg1 = tuple(range(qubit_number)) # First qubit register +reg2 = tuple(range(qubit_number, 2 * qubit_number)) # Second qubit register + +control = 2 * qubit_number # Index of control qubit +trotter_step = 0.01 # Trotter step size + +# Defines the interaction graph for the new qubit system + +new_ising_graph = nx.complete_graph(reg2) + +print(f"Edges: {new_ising_graph.edges}") +nx.draw(new_ising_graph) + + +###################################################################### +# With this done, we implement the QGRNN circuit for some given time value: +# + + +def qgrnn(weights, bias, time=None): + + # Prepares the low energy state in the two registers + qml.StatePrep(np.kron(low_energy_state, low_energy_state), wires=reg1 + reg2) + + # Evolves the first qubit register with the time-evolution circuit to + # prepare a piece of quantum data + state_evolve(ham_matrix, reg1, time) + + # Applies the QGRNN layers to the second qubit register + depth = time / trotter_step # P = t/Delta + for _ in range(0, int(depth)): + qgrnn_layer(weights, bias, reg2, new_ising_graph, trotter_step) + + # Applies the SWAP test between the registers + swap_test(control, reg1, reg2) + + # Returns the results of the SWAP test + return qml.expval(qml.PauliZ(control)) + + +###################################################################### +# We have the full QGRNN circuit, but we still need to define a cost function. +# We know that +# :math:`| \langle \psi(t) | U_{H}(\boldsymbol\mu, \ \Delta) |\psi_0\rangle |^2` +# approaches :math:`1` as the states become more similar and approaches +# :math:`0` as the states become orthogonal. Thus, we choose +# to minimize the quantity +# :math:`-| \langle \psi(t) | U_{H}(\boldsymbol\mu, \ \Delta) |\psi_0\rangle |^2.` +# Since we are interested in calculating this value for many different +# pieces of quantum data, the final cost function is the average +# negative fidelity* between registers: +# +# .. math:: +# +# \mathcal{L}(\boldsymbol\mu, \ \Delta) \ = \ - \frac{1}{N} \displaystyle\sum_{i \ = \ 1}^{N} | +# \langle \psi(t_i) | \ U_{H}(\boldsymbol\mu, \ \Delta) \ |\psi_0\rangle |^2, +# +# where we use :math:`N` pieces of quantum data. +# +# Before creating the cost function, we must define a few more fixed +# variables: +# + +N = 15 # The number of pieces of quantum data that are used for each step +max_time = 0.1 # The maximum value of time that can be used for quantum data + + +###################################################################### +# We then define the negative fidelity cost function: +# +rng = np.random.default_rng(seed=42) + +def cost_function(weight_params, bias_params): + + # Randomly samples times at which the QGRNN runs + times_sampled = rng.random(size=N) * max_time + + # Cycles through each of the sampled times and calculates the cost + total_cost = 0 + for dt in times_sampled: + result = qgrnn_qnode(weight_params, bias_params, time=dt) + total_cost += -1 * result + + return total_cost / N + + +###################################################################### +# Next we set up for optimization. +# + +# Defines the new device +qgrnn_dev = qml.device("default.qubit", wires=2 * qubit_number + 1) + +# Defines the new QNode +qgrnn_qnode = qml.QNode(qgrnn, qgrnn_dev) + +steps = 300 + +optimizer = qml.AdamOptimizer(stepsize=0.5) + +weights = rng.random(size=len(new_ising_graph.edges), requires_grad=True) - 0.5 +bias = rng.random(size=qubit_number, requires_grad=True) - 0.5 + +initial_weights = copy.copy(weights) +initial_bias = copy.copy(bias) + +###################################################################### +# All that remains is executing the optimization loop. + +for i in range(0, steps): + (weights, bias), cost = optimizer.step_and_cost(cost_function, weights, bias) + + # Prints the value of the cost function + if i % 5 == 0: + print(f"Cost at Step {i}: {cost}") + print(f"Weights at Step {i}: {weights}") + print(f"Bias at Step {i}: {bias}") + print("---------------------------------------------") + +###################################################################### +# With the learned parameters, we construct a visual representation +# of the Hamiltonian to which they correspond and compare it to the +# target Hamiltonian, and the initial guessed Hamiltonian: +# + +new_ham_matrix = create_hamiltonian_matrix( + qubit_number, nx.complete_graph(qubit_number), weights, bias +) + +init_ham = create_hamiltonian_matrix( + qubit_number, nx.complete_graph(qubit_number), initial_weights, initial_bias +) + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(6, 6)) + +axes[0].matshow(ham_matrix, vmin=-7, vmax=7, cmap="hot") +axes[0].set_title("Target", y=1.13) + +axes[1].matshow(init_ham, vmin=-7, vmax=7, cmap="hot") +axes[1].set_title("Initial", y=1.13) + +axes[2].matshow(new_ham_matrix, vmin=-7, vmax=7, cmap="hot") +axes[2].set_title("Learned", y=1.13) + +plt.subplots_adjust(wspace=0.3, hspace=0.3) +plt.show() + + + + +###################################################################### +# These images look very similar, indicating that the QGRNN has done a good job +# learning the target Hamiltonian. +# +# We can also look +# at the exact values of the target and learned parameters. +# Recall how the target +# interaction graph has :math:`4` edges while the complete graph has :math:`6.` +# Thus, as the QGRNN converges to the optimal solution, the weights corresponding to +# edges :math:`(1, 3)` and :math:`(2, 0)` in the complete graph should go to :math:`0,` as +# this indicates that they have no effect, and effectively do not exist in the learned +# Hamiltonian. + +# We first pick out the weights of edges (1, 3) and (2, 0) +# and then remove them from the list of target parameters + +weights_noedge = [] +weights_edge = [] +for ii, edge in enumerate(new_ising_graph.edges): + if (edge[0] - qubit_number, edge[1] - qubit_number) in ising_graph.edges: + weights_edge.append(weights[ii]) + else: + weights_noedge.append(weights[ii]) + +###################################################################### +# Then, we print all of the weights: +# + +print("Target parameters Learned parameters") +print("Weights") +print("-" * 41) +for ii_target, ii_learned in zip(target_weights, weights_edge): + print(f"{ii_target : <20}|{ii_learned : >20}") + +print("\nBias") +print("-"*41) +for ii_target, ii_learned in zip(target_bias, bias): + print(f"{ii_target : <20}|{ii_learned : >20}") + +print(f"\nNon-Existing Edge Parameters: {[val.unwrap() for val in weights_noedge]}") + +###################################################################### +# The weights of edges :math:`(1, 3)` and :math:`(2, 0)` +# are very close to :math:`0,` indicating we have learned the cycle graph +# from the complete graph. In addition, the remaining learned weights +# are fairly close to those of the target Hamiltonian. +# Thus, the QGRNN is functioning properly, and has learned the target +# Ising Hamiltonian to a high +# degree of accuracy! +# + +###################################################################### +# References +# ---------- +# +# 1. Verdon, G., McCourt, T., Luzhnica, E., Singh, V., Leichenauer, S., & +# Hidary, J. (2019). Quantum Graph Neural Networks. arXiv preprint +# `arXiv:1909.12264 `__. +# +# diff --git a/demonstrations_v2/tutorial_qgrnn/metadata.json b/demonstrations_v2/tutorial_qgrnn/metadata.json new file mode 100644 index 0000000000..7fa78032c8 --- /dev/null +++ b/demonstrations_v2/tutorial_qgrnn/metadata.json @@ -0,0 +1,38 @@ +{ + "title": "The Quantum Graph Recurrent Neural Network", + "authors": [ + { + "username": "jceroni" + } + ], + "dateOfPublication": "2020-07-27T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_graph_recurrent_neural.png" + } + ], + "seoDescription": "Using a quantum graph recurrent neural network to learn quantum dynamics.", + "doi": "", + "references": [ + { + "id": "Verdon2019", + "type": "article", + "title": "Quantum Graph Neural Networks", + "authors": "Verdon, G., McCourt, T., Luzhnica, E., Singh, V., Leichenauer, S., & Hidary, J.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.12264" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1909.12264" + ], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qgrnn/requirements.in b/demonstrations_v2/tutorial_qgrnn/requirements.in new file mode 100644 index 0000000000..a56451577c --- /dev/null +++ b/demonstrations_v2/tutorial_qgrnn/requirements.in @@ -0,0 +1,4 @@ +matplotlib +networkx +pennylane +scipy diff --git a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py new file mode 100644 index 0000000000..1b9090a5de --- /dev/null +++ b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py @@ -0,0 +1,401 @@ +""" +How to quantum just-in-time (QJIT) compile Grover's algorithm with Catalyst +==================================================================== + +""" + +###################################################################### +# `Grover's algorithm `__ is an `oracle +# `__-based quantum algorithm, first +# proposed by Lov Grover in 1996 [#Grover1996]_, to solve unstructured search problems using a +# `quantum computer `__. For example, we could use +# Grover's algorithm to search for a phone number in a randomly ordered database containing +# :math:`N` entries and say (with high probability) that the database contains that number by +# performing :math:`O(\sqrt{N})` queries on the database, whereas a classical search algorithm would +# require :math:`O(N)` queries to perform the same task. +# +# More formally, the *unstructured search problem* is defined as a search for a string of bits in a +# list containing :math:`N` items given an *oracle access function* :math:`f(x).` This function is +# defined such that :math:`f(x) = 1` if :math:`x` is the bitstring we are looking for (the +# *solution*), and :math:`f(x) = 0` otherwise. The generalized form of Grover's algorithm accepts +# :math:`M` solutions, with :math:`1 \leq M \leq N.` +# +# In this tutorial, we will implement the generalized Grover's algorithm using `Catalyst +# `__, a quantum just-in-time (QJIT) compiler framework +# for PennyLane, which makes it possible to compile, optimize, and execute hybrid quantum–classical +# workflows. We will also measure the performance improvement we get from using Catalyst with +# respect to the native Python implementation and show that the runtime performance of circuits +# compiled with Catalyst can be approximately an order of magnitude faster. +# +# .. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_qjit_compile_grovers_algorithm_with_catalyst.png +# :align: center +# :width: 70% +# :target: javascript:void(0) + + +###################################################################### +# Generalized Grover's algorithm with PennyLane +# --------------------------------------------- +# +# In the :doc:`Grover's Algorithm ` tutorial, we saw how to implement +# the generalized Grover's algorithm in PennyLane. The procedure is as follows: +# +# #. Initialize the system to an equal superposition over all states. +# #. Perform :math:`r(N, M)` *Grover iterations*: +# +# #. Apply the unitary *oracle operator*, :math:`U_\omega,` implemented using +# :class:`~.pennylane.FlipSign`, for each solution index :math:`\omega.` +# #. Apply the *Grover diffusion operator*, :math:`U_D,` implemented using +# :class:`~.pennylane.GroverOperator`. +# +# #. Measure the resulting quantum state in the computational basis. +# +# We also saw (in Ref. [#NandC2000]_) that the optimal number of Grover iterations to find the +# solution is given by +# +# .. math:: r(N, M) \approx \left \lceil \frac{\pi}{4} \sqrt{\frac{N}{M}} \right \rceil . +# +# For simplicity, throughout the rest of this tutorial we will consider the search for the :math:`M +# = 2` solution states :math:`\vert 0\rangle ^{\otimes n}` and :math:`\vert 1\rangle ^{\otimes n},` +# where :math:`n = \log_2 N` is the number of qubits, in a "database" of size :math:`N = 2^n` +# containing all possible :math:`n`-qubit states. +# +# First, we'll import the required packages and define the Grover's algorithm circuit, as we did in +# the :doc:`previous tutorial `. + +import matplotlib.pyplot as plt +import numpy as np +import pennylane as qml + + +def equal_superposition(wires): + for wire in wires: + qml.Hadamard(wires=wire) + + +def oracle(wires, omega): + qml.FlipSign(omega, wires=wires) + + +def num_grover_iterations(N, M): + return np.ceil(np.sqrt(N / M) * np.pi / 4).astype(int) + + +def grover_circuit(num_qubits): + wires = list(range(num_qubits)) + omega = np.array([np.zeros(num_qubits), np.ones(num_qubits)]) + + M = len(omega) + N = 2**num_qubits + + # Initial state preparation + equal_superposition(wires) + + # Grover iterations + for _ in range(num_grover_iterations(N, M)): + for omg in omega: + oracle(wires, omg) + qml.templates.GroverOperator(wires) + + return qml.probs(wires=wires) + + +###################################################################### +# We'll begin with a circuit defined using the default state-simulator device, ``"default.qubit"``, +# as our baseline. See the documentation in :func:`~.pennylane.device` for a list of other supported +# devices. To run our performance benchmarks, we'll increase the number of qubits in our circuit to +# :math:`n = 12.` + +NUM_QUBITS = 12 + +dev = qml.device("default.qubit", wires=NUM_QUBITS) + + +@qml.qnode(dev) +def circuit_default_qubit(): + return grover_circuit(NUM_QUBITS) + + +results = circuit_default_qubit() + + +###################################################################### +# Let's quickly confirm that Grover's algorithm correctly identified the solution states +# :math:`\vert 0\rangle ^{\otimes n}` and :math:`\vert 1\rangle ^{\otimes n}` as the most likely +# states to be measured. + + +def most_probable_states_descending(probs, N): + """Returns the indices of the N most probable states in descending order.""" + if N > len(probs): + raise ValueError("N cannot be greater than the length of the probs array.") + + return np.argsort(probs)[-N:][::-1] + + +def print_most_probable_states_descending(probs, N): + """Prints the most probable states and their probabilities in descending order.""" + for i in most_probable_states_descending(probs, N): + print(f"Prob of state '{i:0{NUM_QUBITS}b}': {probs[i]:.4g}") + + +print_most_probable_states_descending(results, N=2) + + +###################################################################### +# It worked! We are now ready to QJIT compile our Grover's algorithm circuit. + + +###################################################################### +# Quantum just-in-time compiling the circuit +# ------------------------------------------ +# +# Catalyst is developed natively for `PennyLane's high-performance simulators +# `__ and, at the time of writing, does not support the +# ``"default.qubit"`` state-simulator device. Let's first define a new circuit using `Lightning +# `__, which is a PennyLane plugin that provides more +# performant state simulators written in C++. See the :doc:`Catalyst documentation +# ` for the full list of devices supported by Catalyst. + +dev = qml.device("lightning.qubit", wires=NUM_QUBITS) + + +@qml.qnode(dev) +def circuit_lightning(): + return grover_circuit(NUM_QUBITS) + + +###################################################################### +# Then, to QJIT compile our circuit with Catalyst, we simply wrap it with :func:`~pennylane.qjit`. + +circuit_qjit = qml.qjit(circuit_lightning) + + +###################################################################### +# .. note:: +# +# The Catalyst :class:`~.qjit` decorator supports capturing control flow when specified using +# the :func:`~pennylane.for_loop`, :func:`~pennylane.while_loop`, and :func:`~pennylane.cond` +# functions, or additionally, can automatically capture native Python control flow via +# experimental :doc:`AutoGraph ` support. +# +# In this tutorial, however, you'll notice that our ``grover_circuit`` function is able to use +# native Python control flow without the need to convert the Python ``for`` loops to the +# QJIT-compatible :func:`~.for_loop`, for instance, and without using AutoGraph. The reason we +# are able to do so here is twofold: +# +# * The circuit we have compiled, ``circuit_lightning``, is *unparameterized*, meaning it takes +# in no input arguments. Thus, the control flow of the circuit does not depend on any dynamic +# variables (whose values are known only at run time). +# +# * The ranges of the ``for`` loops depend only on static variables (i.e., constants known at +# compile time), in this case Python-native numerics and lists, and NumPy arrays. +# +# Hence, the complete control flow of the circuit is known at compile time, which allows us to +# use native Python control-flow statements. +# +# See the :doc:`Sharp bits and debugging tips ` section of the Catalyst +# documentation for more details on this subject. + + +###################################################################### +# We now have our QJIT object, ``circuit_qjit``. A small detail to note in this case is that because +# the function ``circuit_lightning`` takes no input arguments, Catalyst will, in fact, +# *ahead-of-time* (AOT) compile the circuit at instantiation, meaning that when we call this QJIT +# object for the first time, the compilation will have already taken place, and Catalyst will +# execute the compiled code. With JIT compilation, by contrast, the compilation is triggered at the +# first call site rather than at instantiation. See the `Compilation Modes +# `__ +# documentation in the :doc:`Catalyst Quick Start ` guide for more +# information on the difference between JIT and AOT compilation. +# +# The compilation step will incur some runtime overhead, which we will measure below. Let's first +# call the compiled circuit and confirm that we get the same results. + +results_qjit = circuit_qjit() +print_most_probable_states_descending(results_qjit, N=2) + + +###################################################################### +# Indeed, we get the same results as before: the compiled circuit has correctly identified the +# solution states :math:`\vert 0\rangle ^{\otimes n}` and :math:`\vert 1\rangle ^{\otimes n}` as the +# most likely states to be measured. We can also compare the results more rigorously by comparing +# element-wise the computed probability of every state (within the given floating-point tolerance): + +results_are_equal = np.allclose(results, results_qjit, atol=1e-12) +print(f"Native-Python and compiled circuits yield same results? {results_are_equal}") + + +###################################################################### +# Success! + + +###################################################################### +# Benchmarking +# ------------ +# +# Let's start profiling the circuits we have defined. We have four function executions in total to +# profile: +# +# #. Executing the circuit using ``"default.qubit"``. +# #. Executing the circuit using ``"lightning.qubit"``. +# #. Compiling the circuit with Catalyst, to measure the AOT compilation overhead. +# #. Calls to the QJIT-compiled circuit, to measure the circuit execution time. +# +# We'll use the `timeit `__ module part of the Python +# Standard Library to measure the runtimes. To improve the statistical precision of these +# measurements, we'll repeat the operations for items (2) and (4) five times; item (1) is slow, and +# item (3) is only run once by construction, so we will not repeat these operations. + +import timeit + +NUM_REPS = 5 + +runtimes_native_default = timeit.repeat( + "circuit_default_qubit()", + globals={"circuit_default_qubit": circuit_default_qubit}, + number=1, + repeat=1, +) +runtimes_native_lightning = timeit.repeat( + "circuit_lightning()", + globals={"circuit_lightning": circuit_lightning}, + number=1, + repeat=NUM_REPS, +) +runtimes_compilation = timeit.repeat( + "qml.qjit(circuit_lightning)", + setup="import pennylane as qml", + globals={"circuit_lightning": circuit_lightning}, + number=1, + repeat=1, +) +runtimes_qjit_call = timeit.repeat( + "_circuit_qjit()", + setup="import pennylane as qml; _circuit_qjit = qml.qjit(circuit_lightning);", + globals={"circuit_lightning": circuit_lightning}, + number=1, + repeat=NUM_REPS, +) + +run_names = [ + "Native (default.qubit)", + "Native (lightning.qubit)", + "QJIT compilation", + "QJIT call", +] +run_names_display = [name.replace(" ", "\n", 1) for name in run_names] +runtimes = [ + np.mean(runtimes_native_default), + np.mean(runtimes_native_lightning), + np.mean(runtimes_compilation), + np.mean(runtimes_qjit_call), +] + + +def std_err(x): + """Standard error = sample standard deviation / sqrt(sample size)""" + if len(x) == 1: + return np.nan + return np.std(x, ddof=1) / np.sqrt(len(x)) + + +runtimes_err = [ + std_err(runtimes_native_default), + std_err(runtimes_native_lightning), + std_err(runtimes_compilation), + std_err(runtimes_qjit_call), +] + +for i in range(len(run_names)): + print(f"{run_names[i]} runtime: ({runtimes[i]:.4g} +/- {runtimes_err[i]:.2g}) s") + + +###################################################################### +# Let's plot these runtimes as a bar chart to compare them visually. + +fig = plt.figure(figsize=[8.0, 4.8]) +plt.title("Grover's Algorithm Runtime Benchmarks") +bars = plt.bar(run_names_display, runtimes, color="#70CEFF") +plt.errorbar( + run_names_display, runtimes, yerr=runtimes_err, fmt="None", capsize=2.0, c="k" +) +plt.bar_label(bars, fmt="{:#.2g} s", padding=5) +plt.xlabel("Function Executed") +plt.ylabel("Runtime [s]") +plt.margins(y=0.15) +plt.text( + 0.98, + 0.98, + f"Number of qubits, $n = {NUM_QUBITS}$", + ha="right", + va="top", + transform=plt.gca().transAxes, +) +plt.tight_layout() +plt.show() + + +###################################################################### +# This plot illustrates the power of Catalyst: by simply wrapping our Grover's algorithm circuit as +# a QJIT-compiled object (or AOT-compiled in this case) with :func:`~pennylane.qjit`, we have +# achieved execution runtimes approximately an order of magnitude shorter than the PennyLane circuit +# implemented using the ``"lightning.qubit"`` device. +# +# There is one important caveat in this example, however, which is that the compilation step itself +# takes several times longer than the time it takes to run the circuit using the Lightning state +# simulator directly. This is an important factor to consider when deciding whether to QJIT compile +# your own circuits in performance-critical applications. In the case of Grover's algorithm, we only +# needed to execute the circuit once to obtain the solution, so the runtime incurred in the +# compilation step offsets the gain in the compiled circuit's execution time. However, should it be +# necessary to execute your own quantum circuit many times, the runtime savings are compounded with +# every subsequent call to the compiled circuit. Since the compilation step only needs to be +# performed once, the *total* runtime of the QJIT workflow may begin to outperform the baseline +# Lightning workflow as the number of circuit executions increases. [*]_ + + +###################################################################### +# Conclusion +# ----------- +# +# This tutorial has demonstrated how to just-in-time compile a quantum circuit implementing the +# generalized Grover's algorithm using `Catalyst `__. +# +# For a circuit with :math:`n = 12` qubits, analogous to a search in a randomly ordered "database" +# containing :math:`N = 2^{12} = 4096` entries, Catalyst offers a circuit-execution runtime +# performance approximately an order of magnitude better than the same circuit implemented using the +# Lightning state-simulator device, with the caveat that the compilation step itself incurs some +# runtime over the workflow with a direct call to the Lightning-implemented circuit. +# +# To learn more about Catalyst and how to use it to compile and optimize your quantum programs and +# workflows, check out the :doc:`Catalyst Quick Start ` guide. + + +###################################################################### +# References +# ---------- +# +# .. [#Grover1996] +# +# L. K. Grover (1996) "A fast quantum mechanical algorithm for database search". `Proceedings of +# the Twenty-Eighth Annual ACM Symposium on Theory of Computing. STOC '96. Philadelphia, +# Pennsylvania, USA: Association for Computing Machinery: 212–219 +# `__. +# (arXiv: `9605043 [quant-ph] `__) +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. + + +###################################################################### +# Footnotes +# --------- +# +# .. [*] +# +# The performance improvements that can be achieved with QJIT compilation will depend on the +# specific size and topology of your PennyLane circuit. + +###################################################################### diff --git a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json new file mode 100644 index 0000000000..8aef341082 --- /dev/null +++ b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json @@ -0,0 +1,64 @@ +{ + "title": "How to quantum just-in-time (QJIT) compile Grover's algorithm with Catalyst", + "authors": [ + { + "username": "joeycarter" + } + ], + "dateOfPublication": "2024-11-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": [ + "Algorithms", + "Devices and Performance", + "How-to", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qjit_compile_grovers_algorithm_with_catalyst.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qjit_compile_grovers_algorithm_with_catalyst.png" + } + ], + "seoDescription": "See how to use Catalyst to just-in-time (QJIT) compile a PennyLane circuit implementing Grover's algorithm.", + "doi": "", + "references": [ + { + "id": "Grover1996", + "type": "article", + "title": "A fast quantum mechanical algorithm for database search", + "authors": "L. K. Grover", + "year": "1996", + "journal": "Proceedings of the twenty-eighth annual ACM symposium on Theory of Computing", + "doi": "10.1145/237814.237866" + }, + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_grovers_algorithm", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_qnn_module_tf/demo.py b/demonstrations_v2/tutorial_qnn_module_tf/demo.py new file mode 100644 index 0000000000..dc4cad811a --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_module_tf/demo.py @@ -0,0 +1,232 @@ +""" +Turning quantum nodes into Keras Layers +======================================= + +.. meta:: + :property="og:description": Learn how to create hybrid ML models in PennyLane using Keras + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/Keras_logo.png + +.. related:: + + tutorial_qnn_module_torch Turning quantum nodes into Torch Layers + +*Author: Tom Bromley — Posted: 02 November 2020. Last updated: 28 January 2021.* + +Creating neural networks in `Keras `__ is easy. Models are constructed from +elementary *layers* and can be trained using a high-level API. For example, the following code +defines a two-layer network that could be used for binary classification: +""" + +import tensorflow as tf + +tf.keras.backend.set_floatx('float64') + +layer_1 = tf.keras.layers.Dense(2) +layer_2 = tf.keras.layers.Dense(2, activation="softmax") + +model = tf.keras.Sequential([layer_1, layer_2]) +model.compile(loss="mae") + +############################################################################### +# The model can then be trained using `model.fit() +# `__. +# +# **What if we want to add a quantum layer to our model?** This is possible in PennyLane: +# :doc:`QNodes <../glossary/hybrid_computation>` can be converted into Keras layers and combined +# with the wide range of built-in classical +# `layers `__ to create truly hybrid +# models. This tutorial will guide you through a simple example to show you how it's done! +# +# .. note:: +# +# A similar demo explaining how to +# :doc:`turn quantum nodes into Torch layers ` +# is also available. +# +# Fixing the dataset and problem +# ------------------------------ +# +# Let us begin by choosing a simple dataset and problem to allow us to focus on how the hybrid +# model is constructed. Our objective is to classify points generated from scikit-learn's +# binary-class +# `make_moons() `__ dataset: + +import matplotlib.pyplot as plt +import numpy as np +from sklearn.datasets import make_moons + +# Set random seeds +np.random.seed(42) +tf.random.set_seed(42) + +X, y = make_moons(n_samples=200, noise=0.1) +y_hot = tf.keras.utils.to_categorical(y, num_classes=2) # one-hot encoded labels + +c = ["#1f77b4" if y_ == 0 else "#ff7f0e" for y_ in y] # colours for each class +plt.axis("off") +plt.scatter(X[:, 0], X[:, 1], c=c) +plt.show() + +############################################################################### +# Defining a QNode +# ---------------- +# +# Our next step is to define the QNode that we want to interface with Keras. Any combination of +# device, operations and measurements that is valid in PennyLane can be used to compose the +# QNode. However, the QNode arguments must satisfy additional :doc:`conditions +# ` including having an argument called ``inputs``. All other +# arguments must be arrays or tensors and are treated as trainable weights in the model. We fix a +# two-qubit QNode using the +# :doc:`default.qubit ` simulator and +# operations from the :doc:`templates ` module. + +import pennylane as qml + +n_qubits = 2 +dev = qml.device("default.qubit", wires=n_qubits) + +@qml.qnode(dev) +def qnode(inputs, weights): + qml.AngleEmbedding(inputs, wires=range(n_qubits)) + qml.BasicEntanglerLayers(weights, wires=range(n_qubits)) + return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)] + +############################################################################### +# Interfacing with Keras +# ---------------------- +# +# With the QNode defined, we are ready to interface with Keras. This is achieved using the +# :class:`~pennylane.qnn.KerasLayer` class of the :mod:`~pennylane.qnn` module, which converts the +# QNode to the elementary building block of Keras: a *layer*. We shall see in the following how the +# resultant layer can be combined with other well-known neural network layers to form a hybrid +# model. +# +# We must first define the ``weight_shapes`` dictionary. Recall that all of +# the arguments of the QNode (except the one named ``inputs``) are treated as trainable +# weights. For the QNode to be successfully converted to a layer in Keras, we need to provide the +# details of the shape of each trainable weight for them to be initialized. The ``weight_shapes`` +# dictionary maps from the argument names of the QNode to corresponding shapes: + +n_layers = 6 +weight_shapes = {"weights": (n_layers, n_qubits)} + +############################################################################### +# In our example, the ``weights`` argument of the QNode is trainable and has shape given by +# ``(n_layers, n_qubits)``, which is passed to +# :func:`~pennylane.templates.layers.BasicEntanglerLayers`. +# +# Now that ``weight_shapes`` is defined, it is easy to then convert the QNode: + +qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits) + +############################################################################### +# With this done, the QNode can now be treated just like any other Keras layer and we can proceed +# using the familiar Keras workflow. +# +# Creating a hybrid model +# ----------------------- +# +# Let's create a basic three-layered hybrid model consisting of: +# +# 1. a 2-neuron fully connected classical layer +# 2. our 2-qubit QNode converted into a layer +# 3. another 2-neuron fully connected classical layer +# 4. a softmax activation to convert to a probability vector +# +# A diagram of the model can be seen in the figure below. +# +# .. figure:: /_static/demonstration_assets/qnn_module/qnn_keras.png +# :width: 100% +# :align: center +# +# We can construct the model using the +# `Sequential `__ API: + +clayer_1 = tf.keras.layers.Dense(2) +clayer_2 = tf.keras.layers.Dense(2, activation="softmax") +model = tf.keras.models.Sequential([clayer_1, qlayer, clayer_2]) + +############################################################################### +# Training the model +# ------------------ +# +# We can now train our hybrid model on the classification dataset using the usual Keras +# approach. We'll use the +# standard `SGD `__ optimizer +# and the mean absolute error loss function: + +opt = tf.keras.optimizers.SGD(learning_rate=0.2) +model.compile(opt, loss="mae", metrics=["accuracy"]) + +############################################################################### +# Note that there are more advanced combinations of optimizer and loss function, but here we are +# focusing on the basics. +# +# The model is now ready to be trained! + +fitting = model.fit(X, y_hot, epochs=6, batch_size=5, validation_split=0.25, verbose=2) + +############################################################################### +# How did we do? The model looks to have successfully trained and the accuracy on both the +# training and validation datasets is reasonably high. In practice, we would aim to push the +# accuracy higher by thinking carefully about the model design and the choice of hyperparameters +# such as the learning rate. +# +# Creating non-sequential models +# ------------------------------ +# +# The model we created above was composed of a sequence of classical and quantum layers. This +# type of model is very common and is suitable in a lot of situations. However, in some cases we +# may want a greater degree of control over how the model is constructed, for example when we +# have multiple inputs and outputs or when we want to distribute the output of one layer into +# multiple subsequent layers. +# +# Suppose we want to make a hybrid model consisting of: +# +# 1. a 4-neuron fully connected classical layer +# 2. a 2-qubit quantum layer connected to the first two neurons of the previous classical layer +# 3. a 2-qubit quantum layer connected to the second two neurons of the previous classical layer +# 4. a 2-neuron fully connected classical layer which takes a 4-dimensional input from the +# combination of the previous quantum layers +# 5. a softmax activation to convert to a probability vector +# +# A diagram of the model can be seen in the figure below. +# +# .. figure:: /_static/demonstration_assets/qnn_module/qnn2_keras.png +# :width: 100% +# :align: center +# +# This model can also be constructed using the `Functional API +# `__: + +# re-define the layers +clayer_1 = tf.keras.layers.Dense(4) +qlayer_1 = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits) +qlayer_2 = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits) +clayer_2 = tf.keras.layers.Dense(2, activation="softmax") + +# construct the model +inputs = tf.keras.Input(shape=(2,)) +x = clayer_1(inputs) +x_1, x_2 = tf.split(x, 2, axis=1) +x_1 = qlayer_1(x_1) +x_2 = qlayer_2(x_2) +x = tf.concat([x_1, x_2], axis=1) +outputs = clayer_2(x) + +model = tf.keras.Model(inputs=inputs, outputs=outputs) + +############################################################################### +# As a final step, let's train the model to check if it's working: + +opt = tf.keras.optimizers.SGD(learning_rate=0.2) +model.compile(opt, loss="mae", metrics=["accuracy"]) + +fitting = model.fit(X, y_hot, epochs=6, batch_size=5, validation_split=0.25, verbose=2) + +############################################################################### +# Great! We've mastered the basics of constructing hybrid classical-quantum models using +# PennyLane and Keras. Can you think of any interesting hybrid models to construct? How do they +# perform on realistic datasets? + +############################################################################## diff --git a/demonstrations_v2/tutorial_qnn_module_tf/metadata.json b/demonstrations_v2/tutorial_qnn_module_tf/metadata.json new file mode 100644 index 0000000000..1ea07b10f3 --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_module_tf/metadata.json @@ -0,0 +1,33 @@ +{ + "title": "Turning quantum nodes into Keras Layers", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-11-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_nodes_into_keras.png" + } + ], + "seoDescription": "Learn how to create hybrid ML models in PennyLane using Keras", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qnn_module_torch", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qnn_module_tf/requirements.in b/demonstrations_v2/tutorial_qnn_module_tf/requirements.in new file mode 100644 index 0000000000..9f9b8dab3a --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_module_tf/requirements.in @@ -0,0 +1,5 @@ +matplotlib +numpy +pennylane +scikit-learn +tensorflow diff --git a/demonstrations_v2/tutorial_qnn_module_torch/demo.py b/demonstrations_v2/tutorial_qnn_module_torch/demo.py new file mode 100644 index 0000000000..4c897559fd --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_module_torch/demo.py @@ -0,0 +1,291 @@ +""" +Turning quantum nodes into Torch Layers +======================================= + +.. meta:: + :property="og:description": Learn how to create hybrid ML models in PennyLane using Torch + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/PyTorch_icon.png + +.. related:: + + tutorial_qnn_module_tf Turning quantum nodes into Keras Layers + +*Author: Tom Bromley — Posted: 02 November 2020. Last updated: 28 January 2021.* + +Creating neural networks in `PyTorch `__ is easy using the +`nn module `__. Models are constructed from elementary +*layers* and can be trained using the PyTorch API. For example, the following code defines a +two-layer network that could be used for binary classification: +""" + +import torch + +layer_1 = torch.nn.Linear(2, 2) +layer_2 = torch.nn.Linear(2, 2) +softmax = torch.nn.Softmax(dim=1) + +layers = [layer_1, layer_2, softmax] +model = torch.nn.Sequential(*layers) + +############################################################################### +# **What if we want to add a quantum layer to our model?** This is possible in PennyLane: +# :doc:`QNodes <../glossary/hybrid_computation>` can be converted into ``torch.nn`` layers and +# combined with the wide range of built-in classical +# `layers `__ to create truly hybrid +# models. This tutorial will guide you through a simple example to show you how it's done! +# +# .. note:: +# +# A similar demo explaining how to +# :doc:`turn quantum nodes into Keras layers ` +# is also available. +# +# Fixing the dataset and problem +# ------------------------------ +# +# Let us begin by choosing a simple dataset and problem to allow us to focus on how the hybrid +# model is constructed. Our objective is to classify points generated from scikit-learn's +# binary-class +# `make_moons() `__ dataset: + +import matplotlib.pyplot as plt +import numpy as np +from sklearn.datasets import make_moons + +# Set random seeds +torch.manual_seed(42) +np.random.seed(42) + +X, y = make_moons(n_samples=200, noise=0.1) +y_ = torch.unsqueeze(torch.tensor(y), 1) # used for one-hot encoded labels +y_hot = torch.scatter(torch.zeros((200, 2)), 1, y_, 1) + +c = ["#1f77b4" if y_ == 0 else "#ff7f0e" for y_ in y] # colours for each class +plt.axis("off") +plt.scatter(X[:, 0], X[:, 1], c=c) +plt.show() + +############################################################################### +# Defining a QNode +# ---------------- +# +# Our next step is to define the QNode that we want to interface with ``torch.nn``. Any +# combination of device, operations and measurements that is valid in PennyLane can be used to +# compose the QNode. However, the QNode arguments must satisfy additional :doc:`conditions +# ` including having an argument called ``inputs``. All other +# arguments must be arrays or tensors and are treated as trainable weights in the model. We fix a +# two-qubit QNode using the +# :doc:`default.qubit ` simulator and +# operations from the :doc:`templates ` module. + +import pennylane as qml + +n_qubits = 2 +dev = qml.device("default.qubit", wires=n_qubits) + +@qml.qnode(dev) +def qnode(inputs, weights): + qml.AngleEmbedding(inputs, wires=range(n_qubits)) + qml.BasicEntanglerLayers(weights, wires=range(n_qubits)) + return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)] + +############################################################################### +# Interfacing with Torch +# ---------------------- +# +# With the QNode defined, we are ready to interface with ``torch.nn``. This is achieved using the +# :class:`~pennylane.qnn.TorchLayer` class of the :mod:`~pennylane.qnn` module, which converts the +# QNode to the elementary building block of ``torch.nn``: a *layer*. We shall see in the +# following how the resultant layer can be combined with other well-known neural network layers +# to form a hybrid model. +# +# We must first define the ``weight_shapes`` dictionary. Recall that all of +# the arguments of the QNode (except the one named ``inputs``) are treated as trainable +# weights. For the QNode to be successfully converted to a layer in ``torch.nn``, we need to provide +# the details of the shape of each trainable weight for them to be initialized. The +# ``weight_shapes`` dictionary maps from the argument names of the QNode to corresponding shapes: + +n_layers = 6 +weight_shapes = {"weights": (n_layers, n_qubits)} + +############################################################################### +# In our example, the ``weights`` argument of the QNode is trainable and has shape given by +# ``(n_layers, n_qubits)``, which is passed to +# :func:`~pennylane.templates.layers.BasicEntanglerLayers`. +# +# Now that ``weight_shapes`` is defined, it is easy to then convert the QNode: + +qlayer = qml.qnn.TorchLayer(qnode, weight_shapes) + +############################################################################### +# With this done, the QNode can now be treated just like any other ``torch.nn`` layer and we can +# proceed using the familiar Torch workflow. +# +# Creating a hybrid model +# ----------------------- +# +# Let's create a basic three-layered hybrid model consisting of: +# +# 1. a 2-neuron fully connected classical layer +# 2. our 2-qubit QNode converted into a layer +# 3. another 2-neuron fully connected classical layer +# 4. a softmax activation to convert to a probability vector +# +# A diagram of the model can be seen in the figure below. +# +# .. figure:: /_static/demonstration_assets/qnn_module/qnn_torch.png +# :width: 100% +# :align: center +# +# We can construct the model using the +# `Sequential `__ API: + +clayer_1 = torch.nn.Linear(2, 2) +clayer_2 = torch.nn.Linear(2, 2) +softmax = torch.nn.Softmax(dim=1) +layers = [clayer_1, qlayer, clayer_2, softmax] +model = torch.nn.Sequential(*layers) + +############################################################################### +# Training the model +# ------------------ +# +# We can now train our hybrid model on the classification dataset using the usual Torch +# approach. We'll use the +# standard `SGD `__ optimizer +# and the mean absolute error loss function: + +opt = torch.optim.SGD(model.parameters(), lr=0.2) +loss = torch.nn.L1Loss() + +############################################################################### +# Note that there are more advanced combinations of optimizer and loss function, but here we are +# focusing on the basics. +# +# The model is now ready to be trained! + +X = torch.tensor(X, requires_grad=True).float() +y_hot = y_hot.float() + +batch_size = 5 +batches = 200 // batch_size + +data_loader = torch.utils.data.DataLoader( + list(zip(X, y_hot)), batch_size=5, shuffle=True, drop_last=True +) + +epochs = 6 + +for epoch in range(epochs): + + running_loss = 0 + + for xs, ys in data_loader: + opt.zero_grad() + + loss_evaluated = loss(model(xs), ys) + loss_evaluated.backward() + + opt.step() + + running_loss += loss_evaluated + + avg_loss = running_loss / batches + print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss)) + +y_pred = model(X) +predictions = torch.argmax(y_pred, axis=1).detach().numpy() + +correct = [1 if p == p_true else 0 for p, p_true in zip(predictions, y)] +accuracy = sum(correct) / len(correct) +print(f"Accuracy: {accuracy * 100}%") + +############################################################################### +# How did we do? The model looks to have successfully trained and the accuracy is reasonably +# high. In practice, we would aim to push the accuracy higher by thinking carefully about the +# model design and the choice of hyperparameters such as the learning rate. +# +# Creating non-sequential models +# ------------------------------ +# +# The model we created above was composed of a sequence of classical and quantum layers. This +# type of model is very common and is suitable in a lot of situations. However, in some cases we +# may want a greater degree of control over how the model is constructed, for example when we +# have multiple inputs and outputs or when we want to distribute the output of one layer into +# multiple subsequent layers. +# +# Suppose we want to make a hybrid model consisting of: +# +# 1. a 4-neuron fully connected classical layer +# 2. a 2-qubit quantum layer connected to the first two neurons of the previous classical layer +# 3. a 2-qubit quantum layer connected to the second two neurons of the previous classical layer +# 4. a 2-neuron fully connected classical layer which takes a 4-dimensional input from the +# combination of the previous quantum layers +# 5. a softmax activation to convert to a probability vector +# +# A diagram of the model can be seen in the figure below. +# +# .. figure:: /_static/demonstration_assets/qnn_module/qnn2_torch.png +# :width: 100% +# :align: center +# +# This model can also be constructed by creating a new class that inherits from the +# ``torch.nn`` `Module `__ and +# overriding the ``forward()`` method: + +class HybridModel(torch.nn.Module): + def __init__(self): + super().__init__() + self.clayer_1 = torch.nn.Linear(2, 4) + self.qlayer_1 = qml.qnn.TorchLayer(qnode, weight_shapes) + self.qlayer_2 = qml.qnn.TorchLayer(qnode, weight_shapes) + self.clayer_2 = torch.nn.Linear(4, 2) + self.softmax = torch.nn.Softmax(dim=1) + + def forward(self, x): + x = self.clayer_1(x) + x_1, x_2 = torch.split(x, 2, dim=1) + x_1 = self.qlayer_1(x_1) + x_2 = self.qlayer_2(x_2) + x = torch.cat([x_1, x_2], axis=1) + x = self.clayer_2(x) + return self.softmax(x) + +model = HybridModel() + +############################################################################### +# As a final step, let's train the model to check if it's working: + +opt = torch.optim.SGD(model.parameters(), lr=0.2) +epochs = 6 + +for epoch in range(epochs): + + running_loss = 0 + + for xs, ys in data_loader: + opt.zero_grad() + + loss_evaluated = loss(model(xs), ys) + loss_evaluated.backward() + + opt.step() + + running_loss += loss_evaluated + + avg_loss = running_loss / batches + print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss)) + +y_pred = model(X) +predictions = torch.argmax(y_pred, axis=1).detach().numpy() + +correct = [1 if p == p_true else 0 for p, p_true in zip(predictions, y)] +accuracy = sum(correct) / len(correct) +print(f"Accuracy: {accuracy * 100}%") + +############################################################################### +# Great! We've mastered the basics of constructing hybrid classical-quantum models using +# PennyLane and Torch. Can you think of any interesting hybrid models to construct? How do they +# perform on realistic datasets? + +############################################################################## diff --git a/demonstrations_v2/tutorial_qnn_module_torch/metadata.json b/demonstrations_v2/tutorial_qnn_module_torch/metadata.json new file mode 100644 index 0000000000..daca515919 --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_module_torch/metadata.json @@ -0,0 +1,33 @@ +{ + "title": "Turning quantum nodes into Torch Layers", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-11-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_nodes_into_touch.png" + } + ], + "seoDescription": "Learn how to create hybrid ML models in PennyLane using Torch", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qnn_module_tf", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qnn_module_torch/requirements.in b/demonstrations_v2/tutorial_qnn_module_torch/requirements.in new file mode 100644 index 0000000000..375ecbc231 --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_module_torch/requirements.in @@ -0,0 +1,5 @@ +matplotlib +numpy +pennylane +scikit-learn +torch diff --git a/demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py b/demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py new file mode 100644 index 0000000000..61b0ea9006 --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py @@ -0,0 +1,289 @@ +r""" +Multidimensional regression with a variational quantum circuit +=========================================================== + +In this tutorial, we show how to use a variational quantum circuit to fit the simple multivariate function + +.. math:: f(x_1, x_2) = \frac{1}{2} \left( x_1^2 + x_2^2 \right). + +In [#schuld]_ it has been shown that, under some conditions, there exist variational quantum circuits that are expressive enough to realize any possible set +of Fourier coefficients. We will use a simple two-qubit parameterized quantum circuit to construct a partial Fourier series for fitting +the target function. + +The main outline of the process is as follows: + +1. Build a circuit consisting of layers of alternating data-encoding and parameterized training blocks. + + +2. Optimize the expectation value of the circuit output against a target function to be fitted. + + +3. Obtain a partial Fourier series for the target function. Since the function is not periodic, this partial Fourier series will only approximate the function in the region we will use for training. + + +4. Plot the optimized circuit expectation value against the exact function to compare the two. + +What is a quantum model? +------------------------ + +A quantum model :math:`g_{\vec{\theta}}(\vec{x})` is the expectation value of some observable :math:`M` estimated +on the state prepared by a parameterized circuit :math:`U(\vec{x}, \vec{\theta})`: + +.. math:: g_{\vec{\theta}}(\vec{x}) = \langle 0 | U^\dagger (\vec{x}, \vec{\theta}) M U(\vec{x}, \vec{\theta}) | 0 \rangle. + +By repeatedly running the circuit with a set of parameters :math:`\vec{\theta}` and set of data points :math:`\vec{x}`, we can +approximate the expectation value of the observable :math:`M` in the state :math:`U(\vec{x}, \vec{\theta}) | 0 \rangle.` Then, the parameters can be +optimized to minimize some loss function. + +Building the variational circuit +-------------------------------- + +In this example, we will use a variational quantum circuit to find the Fourier series that +approximates the function :math:`f(x_1, x_2) = \frac{1}{2} \left( x_1^2 + x_2^2 \right)`. The variational circuit that we are using is made up of :math:`L` layers. Each layer consists of a *data-encoding block* +:math:`S(\vec{x})` and a *training block* :math:`W(\vec{\theta})`. The overall circuit is: + +.. math:: U(\vec{x}, \vec{\theta}) = W^{(L+1)}(\vec{\theta}) S(\vec{x}) W^{(L)} (\vec{\theta}) \ldots W^{(2)}(\vec{\theta}) S(\vec{x}) W^{(1)}(\vec{\theta}). + +The training blocks :math:`W(\vec{\theta})` depend on the parameters :math:`\vec{\theta}` that can be optimized classically. + +.. figure:: ../_static/demonstration_assets/qnn_multivariate_regression/qnn_circuit.png + :align: center + :width: 90% + +We will build a circuit such that the expectation value of the :math:`Z\otimes Z` observable is a partial Fourier series +that approximates :math:`f(\vec{x})`, i.e., + +.. math:: g_{\vec{\theta}}(\vec{x})= \sum_{\vec{\omega} \in \Omega} c_\vec{\omega} e^{i \vec{\omega} \vec{x}} \approx f(\vec{x}). + +Then, we can directly plot the partial Fourier series. We can also apply a Fourier transform to +:math:`g_{\vec{\theta}}`, so we can obtain the Fourier coefficients, :math:`c_\vec{\omega}`. To know more about how to obtain the +Fourier series, check out these two related tutorials [#demoschuld]_, [#demoqibo]_. + +Constructing the quantum circuit +-------------------------------- + +First, let's import the necessary libraries and seed the random number generator. We will use Matplotlib for plotting and JAX [#demojax]_ for optimization. +We will also define the device, which has two qubits, using :func:`~.pennylane.device`. +""" + +import matplotlib.pyplot as plt +import pennylane as qml +from pennylane import numpy as pnp +import jax +from jax import numpy as jnp +import optax + +pnp.random.seed(42) + +dev = qml.device('default.qubit', wires=2) + +###################################################################### +# Now we will construct the data-encoding circuit block, :math:`S(\vec{x})`, as a product of :math:`R_z` rotations: +# +# .. math:: +# S(\vec{x}) = R_z(x_1) \otimes R_z(x_2). +# +# Specifically, we define the :math:`S(\vec{x})` operator using the :class:`~.pennylane.AngleEmbedding` function. + +def S(x): + qml.AngleEmbedding( x, wires=[0,1],rotation='Z') + +###################################################################### +# For the :math:`W(\vec{\theta})` operator, we will use an ansatz that is available in PennyLane, called :class:`~.pennylane.StronglyEntanglingLayers`. + +def W(params): + qml.StronglyEntanglingLayers(params, wires=[0,1]) + +###################################################################### +# Now we will build the circuit in PennyLane by alternating layers of :math:`W(\vec{\theta})` and :math:`S(\vec{x})` layers. On this prepared state, we estimate the expectation value of the :math:`Z\otimes Z` operator, using PennyLane's :func:`~.pennylane.expval` function. + +@qml.qnode(dev,interface="jax") +def quantum_neural_network(params, x): + layers=len(params[:,0,0])-1 + n_wires=len(params[0,:,0]) + n_params_rot=len(params[0,0,:]) + for i in range(layers): + W(params[i,:,:].reshape(1,n_wires,n_params_rot)) + S(x) + W(params[-1,:,:].reshape(1,n_wires,n_params_rot)) + + return qml.expval(qml.PauliZ(wires=0)@qml.PauliZ(wires=1)) + +###################################################################### +# The function we will be fitting is :math:`f(x_1, x_2) = \frac{1}{2} \left( x_1^2 + x_2^2 \right)`, which we will define as ``target_function``: + +def target_function(x): + f=1/2*(x[0]**2+x[1]**2) + return f + +###################################################################### +# Now we will specify the range of :math:`x_1` and :math:`x_2` values and store those values in an input data vector. We are fitting the function for :math:`x_1, x_2 \in [-1, 1]` using 30 evenly spaced samples for each variable. + +x1_min=-1 +x1_max=1 +x2_min=-1 +x2_max=1 +num_samples=30 + +###################################################################### +# Now we build the training data with the exact target function :math:`f(x_1, x_2)`. To do so, it is convenient to +# create a two-dimensional grid to make sure that, for each value of +# :math:`x_1,` we perform a sweep over all the values of :math:`x_2` and viceversa. + +x1_train=pnp.linspace(x1_min,x1_max, num_samples) +x2_train=pnp.linspace(x2_min,x2_max, num_samples) +x1_mesh,x2_mesh=pnp.meshgrid(x1_train, x2_train) + +###################################################################### +# We define ``x_train`` and ``y_train`` using the above vectors, reshaping them for our convenience +x_train=pnp.stack((x1_mesh.flatten(), x2_mesh.flatten()), axis=1) +y_train = target_function([x1_mesh,x2_mesh]).reshape(-1,1) +# Let's take a look at how they look like +print("x_train:\n", x_train[:5]) +print("y_train:\n", y_train[:5]) + +###################################################################### +# Optimizing the circuit +# ---------------------- +# +# We want to optimize the circuit above so that the expectation value of :math:`Z \otimes Z` +# approximates the exact target function. This is done by minimizing the mean squared error between +# the circuit output and the exact target function. In particular, the optimization +# process to train the variational circuit will be performed using JAX, an auto differentiable machine learning framework +# to accelerate the classical optimization of the parameters. Check out [#demojax]_ +# to learn more about +# how to use JAX to optimize your QML models. +# +# .. figure:: ../_static/demonstration_assets/qnn_multivariate_regression/qnn_diagram.jpg +# :align: center +# :width: 90% +# + +@jax.jit +def mse(params,x,targets): + # We compute the mean square error between the target function and the quantum circuit to quantify the quality of our estimator + return (quantum_neural_network(params,x)-jnp.array(targets))**2 +@jax.jit +def loss_fn(params, x,targets): + # We define the loss function to feed our optimizer + mse_pred = jax.vmap(mse,in_axes=(None, 0,0))(params,x,targets) + loss = jnp.mean(mse_pred) + return loss + +####################################################################### +#Here, we are choosing an Adam optimizer with a learning rate of 0.05 and 300 steps. + +opt = optax.adam(learning_rate=0.05) +max_steps=300 + +@jax.jit +def update_step_jit(i, args): + # We loop over this function to optimize the trainable parameters + params, opt_state, data, targets, print_training = args + loss_val, grads = jax.value_and_grad(loss_fn)(params, data, targets) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + + def print_fn(): + jax.debug.print("Step: {i} Loss: {loss_val}", i=i, loss_val=loss_val) + # if print_training=True, print the loss every 50 steps + jax.lax.cond((jnp.mod(i, 50) == 0 ) & print_training, print_fn, lambda: None) + return (params, opt_state, data, targets, print_training) + +@jax.jit +def optimization_jit(params, data, targets, print_training=False): + opt_state = opt.init(params) + args = (params, opt_state, jnp.asarray(data), targets, print_training) + # We loop over update_step_jit max_steps iterations to optimize the parameters + (params, opt_state, _, _, _) = jax.lax.fori_loop(0, max_steps+1, update_step_jit, args) + return params + +###################################################################### +# Now we will train the variational circuit with 4 layers and obtain a vector :math:`\vec{\theta}` with the optimized parameters. + +wires=2 +layers=4 +params_shape = qml.StronglyEntanglingLayers.shape(n_layers=layers+1,n_wires=wires) +params=pnp.random.default_rng().random(size=params_shape) +best_params=optimization_jit(params, x_train, jnp.array(y_train), print_training=True) + +###################################################################### +# If you run this yourself, you'll see that the training step with JAX is extremely fast! +# Once the optimized :math:`\vec{\theta}` has been obtained, we can use those parameters to build our fitted version of the function. + +def evaluate(params, data): + y_pred = jax.vmap(quantum_neural_network, in_axes=(None, 0))(params, data) + return y_pred +y_predictions=evaluate(best_params,x_train) + +###################################################################### +# To compare the fitted function to the exact target function, let's take a look at the :math:`R^2` score: + +from sklearn.metrics import r2_score +r2 = round(float(r2_score(y_train, y_predictions)),3) +print("R^2 Score:", r2) + +###################################################################### +# Let's now plot the results to visually check how good our fit is! + +fig = plt.figure() +# Target function +ax1 = fig.add_subplot(1, 2, 1, projection='3d') +ax1.plot_surface(x1_mesh,x2_mesh, y_train.reshape(x1_mesh.shape), cmap='viridis') +ax1.set_zlim(0,1) +ax1.set_xlabel('$x$',fontsize=10) +ax1.set_ylabel('$y$',fontsize=10) +ax1.set_zlabel('$f(x,y)$',fontsize=10) +ax1.set_title('Target ') + +# Predictions +ax2 = fig.add_subplot(1, 2, 2, projection='3d') +ax2.plot_surface(x1_mesh,x2_mesh, y_predictions.reshape(x1_mesh.shape), cmap='viridis') +ax2.set_zlim(0,1) +ax2.set_xlabel('$x$',fontsize=10) +ax2.set_ylabel('$y$',fontsize=10) +ax2.set_zlabel('$f(x,y)$',fontsize=10) +ax2.set_title(f' Predicted \nAccuracy: {round(r2*100,3)}%') + +# Show the plot +plt.tight_layout(pad=3.7) + +###################################################################### +# Cool! We have managed to successfully fit a multidimensional function using a parametrized quantum circuit! + +###################################################################### +# Conclusions +# ------------------------------------------ +# In this demo, we've shown how to utilize a variational quantum circuit to solve a regression problem for a two-dimensional function. +# The results show a good agreement with the target function and the model +# can be trained further, increasing the number of iterations in the training to maximize the accuracy. It also +# paves the way for addressing a regression problem for an :math:`N`-dimensional function, as everything presented +# here can be easily generalized. A final check that could be done is to obtain the Fourier coefficients of the +# trained circuit and compare it with the Fourier series we obtained directly from the target function. +# +# References +# ---------- +# +# .. [#schuld] +# +# Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer +# "The effect of data encoding on the expressive power of variational quantum machine learning models.", +# `arXiv:2008.0865 `__, 2021. +# +# .. [#demoschuld] +# +# Johannes Jakob Meyer, Maria Schuld +# “Tutorial: Quantum models as Fourier series”, +# `Pennylane: Quantum models as Fourier series `__, 2021. +# +# .. [#demoqibo] +# +# Jorge J. Martinez de Lejarza +# "Tutorial: Quantum Fourier Iterative Amplitude Estimation", +# `Qibo: Quantum Fourier Iterative Amplitude Estimation `__, 2023. +# .. [#demojax] +# +# Josh Izaac, Maria Schuld +# "How to optimize a QML model using JAX and Optax", +# `Pennylane: How to optimize a QML model using JAX and Optax `__, 2024 +# diff --git a/demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json b/demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json new file mode 100644 index 0000000000..cb296d316d --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json @@ -0,0 +1,83 @@ +{ + "title": "Multidimensional regression with a variational quantum circuit", + "authors": [ + { + "username": "gmlejarza" + }, + { + "username": "quantumpenguin" + } + ], + "dateOfPublication": "2024-10-01T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Quantum Machine Learning", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qnn_multivariate_regression.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qnn_multivariate_regression.png" + } + ], + "seoDescription": "Learn to use a quantum neural network to fit a multivariate function.", + "doi": "", + "references": [ + { + "id": "schuld", + "type": "article", + "title": "The effect of data encoding on the expressive power of variational quantum machine learning models", + "authors": "Maria Schuld, Ryan Sweke, Johannes Jakob Meyer", + "year": "2021", + "publisher": "", + "url": "https://arxiv.org/pdf/2008.08605" + }, + { + "id": "qibo", + "type": "article", + "title": "Tutorial: Quantum Fourier Iterative Amplitude Estimation", + "authors": "Jorge J. Martinez de Lejarza", + "year": "2023", + "publisher": "", + "url": "hhttps://qibo.science/qibo/stable/code-examples/tutorials/qfiae/qfiae_demo.html" + }, + { + "id": "demoschuld", + "type": "other", + "title": "Tutorial: Quantum models as Fourier series", + "authors": "Johannes Jakob Meyer, Maria Schuld", + "year": "2021", + "publisher": "", + "url": "https://pennylane.ai/qml/demos/tutorial_expressivity_fourier_series/" + }, + { + "id": "demojax", + "type": "other", + "title": "How to optimize a QML model using JAX and Optax", + "authors": "Josh Izaac, Maria Schuld", + "year": "2024", + "publisher": "", + "url": "ttps://pennylane.ai/qml/demos/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in b/demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in new file mode 100644 index 0000000000..2315fdb489 --- /dev/null +++ b/demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in @@ -0,0 +1,6 @@ +jax +jaxlib +matplotlib +optax +pennylane +scikit-learn diff --git a/demonstrations_v2/tutorial_qpe/demo.py b/demonstrations_v2/tutorial_qpe/demo.py new file mode 100644 index 0000000000..3f917b4883 --- /dev/null +++ b/demonstrations_v2/tutorial_qpe/demo.py @@ -0,0 +1,292 @@ +r"""Intro to Quantum Phase Estimation +============================================================= + +The Quantum Phase Estimation (QPE) algorithm [#qpe]_ is one of the most important tools in quantum +computing. Maybe **the** most important. It solves a deceptively simple task: given an eigenstate of a +unitary operator, find its eigenvalue. This demo explains the basics of the QPE algorithm. +After reading it, you will be able to understand +the algorithm and how to implement it in PennyLane. + +.. figure:: ../_static/demonstration_assets/qpe/socialthumbnail_large_Quantum_Phase_Estimation_2023-11-27.png + :align: center + :width: 50% + + + +Quantum phase estimation +------------------------ + +Let's define the problem more carefully. We are given a unitary +operator :math:`U` and one of its eigenstates :math:`|\psi \rangle.` The operator is unitary, +so we can write: + +.. math:: + U |\psi \rangle = e^{i \phi} |\psi \rangle, + +where :math:`\phi` is the *phase* of the eigenvalue (remember, unitaries have eigenvalues with an +absolute value of 1). The goal is to estimate :math:`\phi,` +hence the name *phase estimation*. Our challenge is to design a quantum algorithm to solve this problem. +How would that work? + + + +Part 1: Representing the phase +------------------------------ +A first step is to find a quantum circuit that performs the transformation + +.. math:: + |\psi \rangle |0\rangle \rightarrow |\psi \rangle |\phi\rangle. + +We could then obtain :math:`\phi` directly by measuring the second register. We call this the **estimation register**. + +But let's be more careful. Because the +complex exponential has period :math:`2\pi,` technically the phase is not unique. Instead, we +define :math:`\phi = 2\pi \theta` so that :math:`\theta` is a number between 0 and 1; this forces :math:`\phi` +to be between 0 and :math:`2\pi.` We'll refer to :math:`\theta` as the phase from now on. + +How can we represent :math:`\theta` on a quantum computer? The answer is the first clever part of the algorithm: we represent +:math:`\theta` in binary. 🧠 + +Since you probably don't use binary fractions on a daily basis (or do you?), it's worth stopping for a moment +to make sure we're on the same page. + +.. tip:: + **Binary fractions** + + When we write the number 0.15625, it is being expressed as a sum of multiples of powers of + 10: + + .. math:: + 0.15625 = 1 \times 10^{-1} + 5 \times 10^{-2} + 6 \times 10^{-3} + 2 \times 10^{-4} + 5 \times 10^{-5}. + + But nothing is stopping us from using 2 instead of 10. In binary, the same number is + + .. math:: + 0.00101 = 0 \times 2^{-1} + 0 \times 2^{-2} + 1 \times 2^{-3} + 0 \times 2^{-4} + 1 \times 2^{-5}. + + (You can confirm this by computing 1/8 + 1/32 on a calculator). Similarly, 0.5 is 0.1 in binary, + and 0.3125 is 0.0101. + +Ok, now back to quantum. A binary representation is useful because we can encode it using +qubits, e.g., :math:`|110010\rangle` for :math:`\theta=0.110010.` The phase is retrieved by measuring the qubits. +The **precision** of the estimate is determined by the number of qubits. We've used examples of fractions that can be +conveniently expressed exactly with just a few binary points, but this won't +always be possible. For example, the binary expansion of :math:`0.8` is :math:`0.11001100...` which does not terminate. +From now on, we'll use :math:`n` for the number of estimation qubits. + +Part 2: Quantum Fourier Transform +--------------------------------- + +The second clever part of the algorithm is to follow advice given to many physicists: +"When in doubt, take the Fourier transform"; or in our case, "When in doubt, take the quantum Fourier transform (QFT)". + +.. math:: + \text{QFT}|\theta\rangle = \frac{1}{\sqrt{2^n}}\sum_{k=0} e^{2 \pi i\theta k} |k\rangle. + +Note that this results in a uniform superposition, where each basis state has an additional phase. +If we can prepare that state, then applying the *inverse* QFT would give +:math:`|\theta\rangle` in the estimation register. +This looks more promising, especially if we notice the appearance of the eigenvalues :math:`e^{2 \pi i\theta},` +although with an extra factor of :math:`k.` We can obtain this factor by applying the unitary :math:`k` times to the state :math:`|\psi\rangle:` + +.. math:: + U^k|\psi\rangle = e^{2\pi i \theta k} |\psi\rangle. + +Therefore, we will use :math:`|\psi\rangle` and :math:`U` to generate the factors that are of interest to us in each of the basic states. +It would then be enough to create an operator such that: + +.. math:: + |\psi\rangle |k\rangle \rightarrow U^k |\psi\rangle |k\rangle. + + +In this way, if we apply this operator to the uniform superposition we obtain: + +.. math:: + \frac{1}{\sqrt{2^n}}\sum_{k=0}|\psi\rangle |k\rangle \rightarrow \frac{1}{\sqrt{2^n}}\sum_{k=0}U^k|\psi\rangle|k\rangle = |\psi\rangle \frac{1}{\sqrt{2^n}}\sum_{k=0} e^{2 \pi i\theta k} |k\rangle + +This is exactly what we want! +In PennyLane we refer to this as a :class:`~.ControlledSequence` operation. Let's see how to build it. + +Part 3: Controlled sequence +--------------------------- + +We follow another timeless piece of physics advice: "If stuck, start with the simplest case". +Let's see what happens with two qubits. After applying the Hadamards (and omitting normalization factors), +the operator we need is + +.. math:: + |\psi\rangle |00\rangle + |\psi\rangle |01\rangle + |\psi\rangle |10\rangle+ |\psi\rangle |11\rangle\rightarrow + |\psi\rangle |00\rangle + U |\psi\rangle |01\rangle + U^2 |\psi\rangle |10\rangle+ U^3 |\psi\rangle |11\rangle. + +Notice something? The power of :math:`U` is the same as the binary representation of the corresponding basis state. For example, :math:`U^3` is applied when the estimation register is in state :math:`|11\rangle,` and 11 is just the number 3 in binary. +Therefore, the desired operation can be implemented by applying :math:`U` controlled on the first qubit, and +:math:`U^2` controlled on the second qubit. + +We can extend this idea to any number of qubits. +The following animation illustrates this effect. + +.. figure:: ../_static/demonstration_assets/qpe/controlledSequence.gif + :align: center + :width: 80% + + Example of the controlled sequence operator applied to different 3-qubit basic states. The gates that are being actually applied are + shown in black since the controlling qubit takes the value 1. It can be verified that the power of the final operator matches the binary input. + +With six qubits, an example would be + +.. math:: + |\psi\rangle |010111\rangle \rightarrow U^{16}U^4U^2U^{1}|\psi\rangle |010111\rangle = U^{23}|\psi\rangle |010111\rangle. + +Note that 010111 is 23 in binary. + +So we have the answer: apply :math:`U^{2^m}` controlled on the `m`-th estimation qubit. +This operator facilitates, among other things, performing :doc:`arithmetics in quantum computers `. + +Bringing it all together, here is the quantum phase estimation algorithm in all its glory: + +The QPE algorithm +----------------- + +1. Start with the state :math:`|\psi \rangle |0\rangle.` Apply a Hadamard gate to all estimation qubits to implement the + transformation + + .. math:: + + |\psi \rangle |0\rangle \rightarrow |\psi\rangle \frac{1}{\sqrt{2^n}}\sum_{k=0} |k\rangle. + +2. Apply a :class:`~.ControlledSequence` operation, i.e., :math:`U^{2^m}` controlled on the `m`-th estimation qubit. + This gives + + .. math:: + + |\psi\rangle \frac{1}{\sqrt{2^n}}\sum_{k=0} |k\rangle \rightarrow |\psi\rangle \frac{1}{\sqrt{2^n}}\sum_{k=0} e^{2\pi i \theta k}|k\rangle. + +3. Apply the inverse quantum Fourier transform to the estimation qubits + + .. math:: + + |\psi\rangle \frac{1}{\sqrt{2^n}}\sum_{k=0} e^{2 \pi i \theta k}|k\rangle \rightarrow |\psi\rangle|\theta\rangle. + +4. Measure the estimation qubits to recover :math:`\theta.` + +.. figure:: ../_static/demonstration_assets/qpe/qpe.png + :align: center + :width: 80% + + The quantum phase estimation circuit. + +QPE is doing something incredible: it can calculate eigenvalues **without ever diagonalizing +a matrix**. Wow! This is true even if we relax the assumption that the input is an eigenstate. By linearity, for an arbitrary +state expanded in the eigenbasis of :math:`U` as + +.. math:: + |\Psi\rangle = \sum_i c_i |\psi_i\rangle, + +QPE outputs the eigenphase :math:`\theta_i` with probability :math:`|c_i|^2.` + +Most of the heavy lifting is done by the controlled sequence step. Control-U operations are the heart of the algorithm, +coupled with a clever use of quantum Fourier transforms. This feature is crucial for quantum chemistry applications, +where preparing good initial states is essential [#initial_state]_. +If you want to learn more about this check out our :doc:`demo `. + +One more point of importance. Generally it is not possible to represent a given phase exactly using a limited number of +estimation qubits. Thus, there is typically a distribution of possible +outcomes, which induce an error in the estimation. We'll see an example in the code below. +The error *decreases* exponentially with the number of estimation qubits, but the number of controlled-U operations +*increases* exponentially. The math is such that these effects basically cancel out and the cost of estimating a phase +with error :math:`\varepsilon` is proportional to :math:`1/\varepsilon.` + +All the previous ideas help to also understand the Phase KickBack algorithm in the case of one qubit. +If you want to learn more about this subroutine, take a look at this :doc:`demo `. + +Time to code! +------------- +We already know the three building blocks of QPE; it is time to put them to practice. +We use a single-qubit :class:`~pennylane.PhaseShift` operator :math:`U = R_{\phi}(2 \pi / 5)` +and its eigenstate :math:`|1\rangle`` with corresponding phase :math:`\theta=0.2.` + +""" + +import pennylane as qml +import numpy as np + +def U(wires): + return qml.PhaseShift(2 * np.pi / 5, wires=wires) + +############################################################################## +# We construct a uniform superposition by applying Hadamard gates followed by a :class:`~.ControlledSequence` +# operation. +# Finally, we perform the adjoint of :class:`~.QFT` and +# return the probability of each computational basis state. + + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def circuit_qpe(estimation_wires): + # initialize to state |1> + qml.PauliX(wires=0) + + for wire in estimation_wires: + qml.Hadamard(wires=wire) + + qml.ControlledSequence(U(wires=0), control=estimation_wires) + + qml.adjoint(qml.QFT)(wires=estimation_wires) + + return qml.probs(wires=estimation_wires) + +############################################################################## +# Let's run the circuit and plot the results. We use 4 estimation qubits. + + +import matplotlib.pyplot as plt + +estimation_wires = range(1, 5) + +results = circuit_qpe(estimation_wires) + +bit_strings = [f"0.{x:0{len(estimation_wires)}b}" for x in range(len(results))] + +plt.bar(bit_strings, results) +plt.xlabel("phase") +plt.ylabel("probability") +plt.xticks(rotation="vertical") +plt.subplots_adjust(bottom=0.3) + +plt.show() + +############################################################################## +# Since the eigenphase cannot be represented exactly using 4 bits, there is a +# distribution of possible outcomes. The peak occurs +# at :math:`\phi = 0.0011,` which is :math:`0.1875` in decimal. This is the closest value we can get with +# a 4-bit representation to the exact value :math:`0.2.` 🎊 +# +# Conclusion +# ---------- +# This demo presented the "textbook" version of QPE. There are multiple variations, notably iterative QPE that +# uses a single estimation qubit, as well as Bayesian versions that saturate optimal prefactors appearing in the +# total cost. There are also mathematical subtleties about cost and errors that are important but out of +# scope for this demo. +# +# Finally, there is extensive work on how to implement the unitaries themselves. In quantum chemistry, +# the main strategy is to encode a molecular Hamiltonian +# into a unitary such that the phases are invertible functions of the Hamiltonian eigenvalues. This can be done for instance +# through the mapping :math:`U=e^{-iHt},` which can be implemented using Hamiltonian simulation techniques. More advanced techniques employ a qubitization-based encoding. QPE can then +# be used to estimate eigenvalues like ground-state energies by sampling them with respect to a distribution +# induced by the input state. +# +# References +# --------------- +# +# .. [#qpe] +# +# A.Yu.Kitaev. "Quantum measurements and the Abelian Stabilizer Problem", +# `Arxiv `__, 1995 +# +# .. [#initial_state] +# +# Stepan Fomichev et al. "Initial state preparation for quantum chemistry on quantum computers", +# `Arxiv `__, 2023 +# +# diff --git a/demonstrations_v2/tutorial_qpe/metadata.json b/demonstrations_v2/tutorial_qpe/metadata.json new file mode 100644 index 0000000000..85d506579f --- /dev/null +++ b/demonstrations_v2/tutorial_qpe/metadata.json @@ -0,0 +1,60 @@ +{ + "title": "Intro to Quantum Phase Estimation", + "authors": [ + { + "username": "ixfoduap" + }, + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-01-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qpe/thumbnail_Quantum_Phase_Estimation_2023-11-27.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Quantum_Phase_Estimation_2023-11-27.png" + } + ], + "seoDescription": "Master the basics of the quantum phase estimation", + "doi": "", + "references": [ + { + "id": "qpe", + "type": "article", + "title": "Quantum measurements and the Abelian Stabilizer Problem", + "authors": "A.Yu.Kitaev.", + "year": "1995", + "publisher": "", + "url": "https://arxiv.org/abs/quant-ph/9511026" + }, + { + "id": "initial_state", + "type": "article", + "title": "Initial state preparation for quantum chemistry on quantum computers", + "authors": "Stepan Fomichev et al.", + "year": "2023", + "publisher": "", + "url": "https://arxiv.org/pdf/2310.18410.pdf/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_phase_kickback", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/introduction-to-quantum-phase-estimation-demo/7337" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qpe/requirements.in b/demonstrations_v2/tutorial_qpe/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_qpe/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_qsvt_hardware/demo.py b/demonstrations_v2/tutorial_qsvt_hardware/demo.py new file mode 100644 index 0000000000..824fa6f776 --- /dev/null +++ b/demonstrations_v2/tutorial_qsvt_hardware/demo.py @@ -0,0 +1,164 @@ +r"""How to implement QSVT on hardware +======================================= + +The :doc:`quantum singular value transform (QSVT) ` +is a quantum algorithm that allows us to perform polynomial +transformations on matrices or operators, and it is rapidly becoming +a go-to algorithm for :doc:`quantum application research ` +in the `ISQ era `__. + +In this how-to guide, we will show how we can implement the QSVT +subroutine in a hardware-compatible way, taking your application research +to the next level. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_qsvt_hardware.png + :align: center + :width: 50% + :target: javascript:void(0) + + +Calculating angles +------------------ + +Our goal is to apply a polynomial transformation to a given Hamiltonian, i.e., :math:`p(\mathcal{H}).` To achieve this, we must consider the two +fundamental components of the QSVT algorithm: + +- **Projection angles**: A list of angles that will determine the coefficients of the polynomial to be applied. +- **Block encoding**: The strategy used to encode the Hamiltonian. We will use the :doc:`linear combinations of unitaries ` approach via the PennyLane :class:`~.qml.PrepSelPrep` operation. + +Calculating angles is not a trivial task, but there are tools such as `pyqsp `_ that do the job for us. +For instance, to find the angles to apply the polynomial :math:`p(x) = -x + \frac{x^3}{2}+ \frac{x^5}{2},` we can run this code: + +.. code-block:: python + + from pyqsp.angle_sequence import QuantumSignalProcessingPhases + import numpy as np + + # Define the polynomial, the coefficients are in the order of the polynomial degree. + poly = np.array([0,-1, 0, 0.5, 0 , 0.5]) + + ang_seq = QuantumSignalProcessingPhases(poly, signal_operator="Wx") + +The angles obtained after execution are as follows: + +""" + +ang_seq = [ + -1.5115007723754004, + 0.6300762184670975, + 0.8813995564082947, + -2.2601930971815003, + 3.7716688720568885, + 0.059295554419495855, +] + +###################################################################### +# We use these angles to apply the polynomial transformation. +# However, we are not finished yet: these angles have been calculated following the "Wx" +# convention [#unification]_, while :class:`~.qml.PrepSelPrep` follows a different one. Moreover, the angles obtained in the +# context of QSP (the ones given by ``pyqsp``) are not the same as the ones we have to use in QSVT. That is why +# we must transform the angles: + +import numpy as np + + +def convert_angles(angles): + num_angles = len(angles) + update_vals = np.zeros(num_angles) + + update_vals[0] = 3 * np.pi / 4 - (3 + len(angles) % 4) * np.pi / 2 + update_vals[1:-1] = np.pi / 2 + update_vals[-1] = -np.pi / 4 + + return angles + update_vals + + +angles = convert_angles(ang_seq) +print(angles) + +###################################################################### +# Using these angles, we can now start working with the template. +# +# QSVT on hardware +# ----------------- +# +# The :class:`~.qml.QSVT` template expects two inputs. The first one is the block encoding operator, :class:`~.qml.PrepSelPrep`, +# and the second one is a set of projection operators, :class:`~.qml.PCPhase`, that encode the angles properly. +# We will see how to apply them later, but first let's define +# a Hamiltonian and manually apply the polynomial of interest: + +import pennylane as qml +from numpy.linalg import matrix_power as mpow + +coeffs = np.array([0.2, -0.7, -0.6]) +coeffs /= np.linalg.norm(coeffs, ord=1) # Normalize the coefficients + +obs = [qml.X(3), qml.X(3) @ qml.Z(4), qml.Z(3) @ qml.Y(4)] + +H = qml.dot(coeffs, obs) + +H_mat = qml.matrix(H, wire_order=[3, 4]) + +# We calculate p(H) = -H + 0.5 * H^3 + 0.5 * H^5 +H_poly = -H_mat + 0.5 * mpow(H_mat, 3) + 0.5 * mpow(H_mat, 5) + +print(np.round(H_poly, 4)) + +###################################################################### +# Now that we know what the target result is, let's see how to apply the polynomial with a quantum circuit instead. +# We start by defining the proper input operators for the :class:`~.qml.QSVT` template. + +# We need |log2(len(coeffs))| = 2 control wires to encode the Hamiltonian +control_wires = [1, 2] +block_encode = qml.PrepSelPrep(H, control=control_wires) + +projectors = [ + qml.PCPhase(angles[i], dim=2 ** len(H.wires), wires=control_wires + H.wires) + for i in range(len(angles)) +] + + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def circuit(): + + qml.Hadamard(0) + qml.ctrl(qml.QSVT, control=0, control_values=[1])(block_encode, projectors) + qml.ctrl(qml.adjoint(qml.QSVT), control=0, control_values=[0])(block_encode, projectors) + qml.Hadamard(0) + + return qml.state() + + +matrix = qml.matrix(circuit, wire_order=[0] + control_wires + H.wires)() +print(np.round(matrix[: 2 ** len(H.wires), : 2 ** len(H.wires)], 4)) + +###################################################################### +# The matrix obtained using QSVT is the same as the one obtained by applying the polynomial +# directly to the Hamiltonian! That means the circuit is encoding :math:`p(\mathcal{H})` correctly. +# The great advantage of this approach is that all the building blocks used in the circuit can be +# decomposed into basic gates easily, allowing this circuit +# to be easily executed on hardware devices with PennyLane. +# +# Please also note that QSVT encodes the desired polynomial :math:`p(\mathcal{H})` as well as +# a polynomial :math:`i q(\mathcal{H}).` To isolate :math:`p(\mathcal{H}),` we have used an auxiliary qubit and considered that +# the sum of a complex number and its conjugate gives us twice its real part. We +# recommend :doc:`this demo ` to learn more about the structure +# of the circuit. +# +# Conclusion +# ---------- +# In this brief how-to we demonstrated applying QSVT on a sample Hamiltonian. Note that the algorithm is sensitive to +# the block-encoding method, so please make sure that the projection angles are converted to the proper format. +# This how-to serves as a guide for running your own workflows and experimenting with more advanced Hamiltonians and polynomial functions. +# +# References +# ---------- +# +# .. [#unification] +# +# John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang. +# "A Grand Unification of Quantum Algorithms". +# `arXiv preprint arXiv:2105.02859 `__. +# diff --git a/demonstrations_v2/tutorial_qsvt_hardware/metadata.json b/demonstrations_v2/tutorial_qsvt_hardware/metadata.json new file mode 100644 index 0000000000..b51d363302 --- /dev/null +++ b/demonstrations_v2/tutorial_qsvt_hardware/metadata.json @@ -0,0 +1,43 @@ +{ + "title": "How to implement QSVT on hardware", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-09-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qsvt_hardware.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qsvt_hardware.png" + } + ], + "seoDescription": "Learn how to implement QSVT on hardware.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_apply_qsvt", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qsvt_hardware/requirements.in b/demonstrations_v2/tutorial_qsvt_hardware/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_qsvt_hardware/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py b/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py new file mode 100644 index 0000000000..9db0a91576 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py @@ -0,0 +1,709 @@ +r""" + +.. _quantum_analytic_descent: + +Quantum analytic descent +======================== + +.. meta:: + :property="og:description": Implement the Quantum analytic descent algorithm for VQE. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/flowchart.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_natural_gradient Quantum natural gradient + tutorial_rotoselect Quantum circuit structure learning + tutorial_stochastic_parameter_shift The stochastic parameter-shift rule + + +*Authors: Elies Gil-Fuster, David Wierichs (Xanadu Residents) — Posted: 30 June 2021. Last updated: 18 November 2021* + +One of the main problems of many-body physics is that of finding the ground +state and ground state energy of a given Hamiltonian. +`The Variational Quantum Eigensolver (VQE) `_ combines smart circuit +design with gradient-based optimization to solve this task. +Several practical demonstrations have shown how near-term quantum +devices may be suitable for VQE and other variational quantum algorithms. +One issue for such an approach, though, is that the optimization landscape is +non-convex, so reaching a good enough local minimum quickly requires hundreds or +thousands of update steps. This is problematic because computing gradients of the +cost function on a quantum computer is inefficient when it comes to circuits +with many parameters. + +At the same time, we have a good understanding of the *local* shape +of the cost landscape around any reference point. +Cashing in on this, the authors of the +Quantum Analytic Descent paper [#QAD]_ +propose an algorithm that constructs a classical model which approximates the +landscape, so that the gradients can be calculated on a classical computer, which is much cheaper. +In order to build the classical model, we need to use the quantum device to +evaluate the cost function on (a) a reference point :math:`\boldsymbol{\theta}_0,` +and (b) a number of points shifted away from :math:`\boldsymbol{\theta}_0.` +With the cost values at these points, we can build the classical model that +approximates the landscape. + +In this demo, you will learn how to implement Quantum Analytic Descent using PennyLane. +In addition, you will look under the hood of the constructed models and the optimization steps +carried out by the algorithm. +So: sit down, relax, and enjoy your optimization! + +| + +.. figure:: ../_static/demonstration_assets/quantum_analytic_descent/xkcd.png + :align: center + :width: 50% + :target: javascript:void(0) + + Optimization progress with Quantum Analytic Descent. + + + +VQEs give rise to trigonometric cost functions +---------------------------------------------- + +When we talk about VQEs we have a quantum circuit with :math:`n` qubits in mind, which are typically initialized in the base state :math:`|0\rangle.` +The body of the circuit is a *variational form* :math:`V(\boldsymbol{\theta})` – a fixed architecture of quantum gates parametrized by an array of real-valued parameters :math:`\boldsymbol{\theta}\in\mathbb{R}^m.` +After the variational form, the circuit ends with the measurement of a chosen observable +:math:`\mathcal{M},` based on the problem +we are trying to solve. + +The idea in VQE is to fix a variational form such that the expected value of the measurement relates to the energy of an interesting Hamiltonian: + +.. math:: E(\boldsymbol{\theta}) = \langle 0|V^\dagger(\boldsymbol{\theta})\mathcal{M}V(\boldsymbol{\theta})|0\rangle. + +We want to find the lowest possible energy the system can attain; +this corresponds to running an optimization program to find the :math:`\boldsymbol{\theta}` that minimizes the function above. + + +If the gates in the variational form are restricted to be Pauli rotations, then the cost function is a sum of *multilinear trigonometric terms* in each of the parameters. +That's a scary sequence of words! +What it means is that if we look at :math:`E(\boldsymbol{\theta})` but we focus only on one of the parameters, say :math:`\theta_i`, then we can write the functional dependence as a linear combination of three functions: :math:`1`, :math:`\sin(\theta_i),` and :math:`\cos(\theta_i).` +That is, for each parameter :math:`\theta_i` there exist :math:`a_i`, :math:`b_i,` and :math:`c_i` such that the cost can be written as + +.. math:: E(\boldsymbol{\theta}) = a_i + b_i\sin(\theta_i) + c_i\cos(\theta_i). + +All parameters but :math:`\theta_i` are absorbed in the coefficients :math:`a_i,` :math:`b_i` and :math:`c_i.` +Another technique using this structure of :math:`E(\boldsymbol{\theta})` are the +Rotosolve/Rotoselect algorithms [#Rotosolve]_ for which there also is `a PennyLane demo `__. + +Let's look at a toy example to illustrate this structure of the cost function. +""" + +import pennylane as qml +from pennylane import numpy as np +import matplotlib.pyplot as plt +import warnings + +warnings.filterwarnings("ignore") + +np.random.seed(0) + +# Create a device with 2 qubits. +dev = qml.device("lightning.qubit", wires=2) + +# Define the variational form V and observable M and combine them into a QNode. +@qml.qnode(dev, diff_method="parameter-shift", max_diff=2) +def circuit(parameters): + qml.RX(parameters[0], wires=0) + qml.RX(parameters[1], wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + +############################################################################### +# Let us now look at how the energy value depends on each of the two parameters alone. +# For that, we just fix one parameter and show the cost when varying the other one: + +# Create 1D sweeps through parameter space with the other parameter fixed. +num_samples = 50 + +# Fix a parameter position. +parameters = np.array([3.3, 0.5], requires_grad=True) + +theta_func = np.linspace(0, 2 * np.pi, num_samples) +C1 = [circuit(np.array([theta, parameters[1]])) for theta in theta_func] +C2 = [circuit(np.array([parameters[0], theta])) for theta in theta_func] + +# Show the sweeps. +fig, ax = plt.subplots(1, 1, figsize=(4, 3)) +ax.plot(theta_func, C1, label="$E(\\theta, 0.5)$", color="r") +ax.plot(theta_func, C2, label="$E(3.3, \\theta)$", color="orange") +ax.set_xlabel("$\\theta$") +ax.set_ylabel("$E$") +ax.legend() +plt.tight_layout() + +# Create a 2D grid and evaluate the energy on the grid points. +# We cut out a part of the landscape to increase clarity. +X, Y = np.meshgrid(theta_func, theta_func) +Z = np.zeros_like(X) +for i, t1 in enumerate(theta_func): + for j, t2 in enumerate(theta_func): + # Cut out the viewer-facing corner + if (2 * np.pi - t2) ** 2 + t1 ** 2 > 4: + Z[i, j] = circuit([t1, t2]) + else: + X[i, j] = Y[i, j] = Z[i, j] = np.nan + +# Show the energy landscape on the grid. +fig, ax = plt.subplots(1, 1, subplot_kw={"projection": "3d"}, figsize=(4, 4)) +surf = ax.plot_surface(X, Y, Z, label="$E(\\theta_1, \\theta_2)$", alpha=0.7, color="#209494") +line1 = ax.plot( + [parameters[1]] * num_samples, + theta_func, + C1, + label="$E(\\theta_1, \\theta_2^{(0)})$", + color="r", + zorder=100, +) +line2 = ax.plot( + theta_func, + [parameters[0]] * num_samples, + C2, + label="$E(\\theta_1^{(0)}, \\theta_2)$", + color="orange", + zorder=100, +) + +############################################################################### +# Of course this is an overly simplified example, but the key take-home message so far is: +# *if the variational parameters feed into Pauli rotations, the energy landscape is a multilinear combination of trigonometric functions*. +# What is a good thing about trigonometric functions? +# That's right! +# We have studied them since high school and know how their graphs look. +# +# The QAD strategy +# ---------------- +# +# By now we know how the energy landscape looks for a small example. +# Scaling this up to more parameters would quickly become unfeasible because we need to query a quantum computer for every combination of parameter values. +# The secret ingredient of this sauce is that we only need to build an approximate classical model. +# Using an approximate classical model has one feature and one bug. +# The feature: it is cheap to construct. +# The bug: well, it's only approximate, so we cannot rely on it fully. +# And one extra feature (you didn't see that coming, did you?): if the reference point about which we build the classical model is a true local minimum, then it will be a local minimum of the classical model too. +# And that is the key! +# Given a reference point, we use the classical model to find a point that's closer to the true minimum, and then use that point as reference for a new model! +# This is what is called Quantum Analytic Descent (QAD), and if you are fine not knowing yet what all the symbols mean, here's its pseudo-algorithm: +# +# #. Set an initial reference point :math:`\boldsymbol{\theta}_0.` +# #. Build the model :math:`\hat{E}(\boldsymbol{\theta})\approx E(\boldsymbol{\theta}_0+\boldsymbol{\theta})` at this point. +# #. Find the minimum :math:`\boldsymbol{\theta}_\text{min}` of the model. +# #. Set :math:`\boldsymbol{\theta}_0+\boldsymbol{\theta}_\text{min}` as the new reference point :math:`\boldsymbol{\theta}_0,` go back to Step 2. +# #. After convergence or a fixed number of models built, output the last minimum :math:`\boldsymbol{\theta}_\text{opt}=\boldsymbol{\theta}_0+\boldsymbol{\theta}_\text{min}.` +# +# Computing a classical model +# --------------------------- +# +# Knowing how the cost looks when restricted to only one parameter (see the plot above), nothing keeps us in theory from constructing a perfect classical model. +# The only thing we need to do is write down a general multilinear trigonometric polynomial and determine its coefficients. +# Simple, right? +# Well, for :math:`m` parameters, there would be :math:`3^m` coefficients to estimate, which gives us the ever-dreaded exponential scaling. +# Although conceptually simple, building an exact model would require exponentially many resources, and that's a no-go. +# What can we do, then? +# The authors of QAD propose building an imperfect model. +# This makes *all* the difference—they use a classical model that is accurate only in +# a region close to a given reference point, and that delivers good results for the optimization! +# +# Function expansions +# ^^^^^^^^^^^^^^^^^^^ +# +# What do we usually do when we want to approximate something in a region near to a reference point? +# Correct! +# We use a Taylor expansion! +# But what if we told you there is a better option for the case at hand? +# In the QAD paper, the authors state that a *trigonometric expansion* up to second order is already a sound model candidate. Let's recap such expansions. +# +# .. admonition:: Taylor expansion vs. Trigonometric expansion +# +# In spirit, a trigonometric expansion and a Taylor expansion are not that different: both are linear combinations of some basis functions, where the coefficients of the sum take very specific values usually related to the derivatives of the function we want to approximate. +# The difference between Taylor's and a trigonometric expansion is mainly what basis of functions we take. +# In Calculus I we learned that a Taylor series in one variable :math:`x` uses the integer powers of the variable namely :math:`\{1, x, x^2, x^3, \ldots\},` in short :math:`\{x^n\}_{n\in\mathbb{N}}:` +# +# .. math:: f_\text{Taylor}(x) = \sum c_n(x-x_0)^n. +# +# A trigonometric expansion instead uses a different basis, also for one variable: :math:`\{1, \sin(x), \cos(x), \sin(2x), \cos(2x), \ldots\},` which we could call the set of trigonometric monomials with integer frequency, or in short :math:`\{\sin(nx),\cos(nx)\}_{n\in\mathbb{N}}:` +# +# .. math:: f_\text{Trig}(x) = \sum a_n \cos(n(x-x_0))+ b_n \sin(n(x-x_0)). +# +# For higher-dimensional variables we have to take products of the basis functions of each coordinate, i.e., of monomials or trigonometric monomials respectively. +# This does lead to an exponentially increasing number of terms, but if we chop the series soon enough it will not get too much out of hand. +# The proposal here is to only go up to second order terms, so we are safe. +# +# Expanding in adapted trigonometric polynomials +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# One important aspect in which trigonometric series differ from regular +# expansions is that there is not a clear separation between what terms +# contribute to each order of the expansion (due to the fact that all +# derivatives of sine and cosine are non-zero in general). +# Because of this, we group the terms by their leading order contribution, and +# in the following table write them next to their non-trigonometric analogues. +# All chosen trigonometric monomials have leading order coefficient :math:`1` +# and they all differ in their leading order contribution. +# +# .. list-table:: +# :widths: 10 70 20 +# :header-rows: 1 +# +# * - Order +# - Trigonometric monomial +# - Taylor monomial +# * - 0 +# - :math:`A(\boldsymbol{\theta})= \prod_{i=1}^m \cos\left(\frac{\theta_i}{2}\right)^2` +# - :math:`1` +# * - 1 +# - :math:`B_k(\boldsymbol{\theta}) = 2\cos\left(\frac{\theta_k}{2}\right)\sin\left(\frac{\theta_k}{2}\right)\prod_{i\neq k} \cos\left(\frac{\theta_i}{2}\right)^2` +# - :math:`x_k` +# * - 2 +# - :math:`C_k(\boldsymbol{\theta}) = 2\sin\left(\frac{\theta_k}{2}\right)^2\prod_{i\neq k} \cos\left(\frac{\theta_i}{2}\right)^2` +# - :math:`x_k^2` +# * - 2 +# - :math:`D_{kl}(\boldsymbol{\theta}) = 4\sin\left(\frac{\theta_k}{2}\right)\cos\left(\frac{\theta_k}{2}\right)\sin\left(\frac{\theta_l}{2}\right)\cos\left(\frac{\theta_l}{2}\right)\prod_{i\neq k,l} \cos\left(\frac{\theta_i}{2}\right)^2` +# - :math:`x_kx_l` +# +# Those are really large terms compared to a Taylor series! +# However, you may have noticed all of those terms have large parts in common. +# Indeed, we can rewrite the longer ones in a shorter way which is more decent to look at: +# +# .. math:: +# +# B_k(\boldsymbol{\theta}) &= 2\tan\left(\frac{\theta_k}{2}\right)A(\boldsymbol{\theta})\\ +# C_k(\boldsymbol{\theta}) &= 2\tan\left(\frac{\theta_k}{2}\right)^2 A(\boldsymbol{\theta})\\ +# D_{kl}(\boldsymbol{\theta}) &= 4\tan\left(\frac{\theta_k}{2}\right)\tan\left(\frac{\theta_l}{2}\right)A(\boldsymbol{\theta}) +# +# With that, we know what type of terms we should expect to encounter in our local classical model: +# the model we want to construct is a linear combination of the functions +# :math:`A(\boldsymbol{\theta}),` :math:`B_k(\boldsymbol{\theta})` and :math:`C_k(\boldsymbol{\theta})` +# for each parameter, and :math:`D_{kl}(\boldsymbol{\theta})` for every pair of different parameters :math:`(\theta_k,\theta_l).` +# +# Computing the expansion coefficients +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# We can now use the derivatives of the function we are approximating to obtain the coefficients of the linear combination. +# As the terms we include in the expansion have leading orders :math:`0`, :math:`1` and :math:`2,` these derivatives are +# :math:`E(\boldsymbol{\theta})`, :math:`\partial E(\boldsymbol{\theta})/\partial \theta_k,` +# :math:`\partial^2 E(\boldsymbol{\theta})/\partial\theta_k^2,` and :math:`\partial^2 E(\boldsymbol{\theta})/\partial \theta_k\partial\theta_l.` +# However, the trigonometric polynomials may contain multiple orders in :math:`\boldsymbol{\theta}.` For example, both +# :math:`A(\boldsymbol{\theta})` and :math:`C_k(\boldsymbol{\theta})` contribute to the second order, so we have to account +# for this in the coefficient of :math:`\partial^2 E(\boldsymbol{\theta})/\partial \theta_k^2.` +# We can name the different coefficients (including the function value itself) accordingly to how we named the terms in the series: +# +# .. math:: +# +# E^{(A)} &= E(\boldsymbol{\theta})\Bigg|_{\boldsymbol{\theta}=0} \\ +# E^{(B)}_k &= \frac{\partial E(\boldsymbol{\theta})}{\partial\theta_k}\Bigg|_{\boldsymbol{\theta}=0} \\ +# E^{(C)}_k &= \frac{\partial^2 E(\boldsymbol{\theta})}{\partial\theta_k^2}\Bigg|_{\boldsymbol{\theta}=0} + \frac{1}{2}E(\boldsymbol{\theta})\Bigg|_{\boldsymbol{\theta}=0}\\ +# E^{(D)}_{kl} &= \frac{\partial^2 E(\boldsymbol{\theta})}{\partial\theta_k\partial\theta_l}\Bigg|_{\boldsymbol{\theta}=0} +# +# In PennyLane, computing the gradient of a cost function with respect to an array of parameters can be easily done +# with the `parameter-shift rule `_. +# By iterating the rule, we can obtain the second derivatives – the Hessian (see for example [#higher_order_diff]_). +# Let us implement a function that does just that and prepares the coefficients :math:`E^{(A/B/C/D)}:` + + +def get_model_data(fun, params): + """Computes the coefficients for the classical model, E^(A), E^(B), E^(C), and E^(D).""" + num_params = len(params) + + # E_A contains the energy at the reference point + E_A = fun(params) + + # E_B contains the gradient. + E_B = qml.grad(fun)(params) + + hessian = qml.jacobian(qml.grad(fun))(params) + + # E_C contains the slightly adapted diagonal of the Hessian. + E_C = np.diag(hessian) + E_A / 2 + + # E_D contains the off-diagonal parts of the Hessian. + # We store each pair (k, l) only once, namely the upper triangle. + E_D = np.triu(hessian, 1) + + return E_A, E_B, E_C, E_D + + +############################################################################### +# Let's test our brand-new function for the circuit from above, at a random parameter position: + +parameters = np.random.random(2, requires_grad=True) * 2 * np.pi +print(f"Random parameters (params): {parameters}") +coeffs = get_model_data(circuit, parameters) +print( + f"Coefficients at params:", + f" E_A = {coeffs[0]}", + f" E_B = {coeffs[1]}", + f" E_C = {coeffs[2]}", + f" E_D = {coeffs[3]}", + sep="\n", +) + +############################################################################### +# The classical model (finally!) +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# Bringing all of the above ingredients together, we have the following gorgeous trigonometric expansion up to second order: +# +# .. math:: \hat{E}(\boldsymbol{\theta}) := A(\boldsymbol{\theta}) E^{(A)} + \sum_{k=1}^m\left[B_k(\boldsymbol{\theta})E_k^{(B)} + C_k(\boldsymbol{\theta}) E_k^{(C)}\right] + \sum_{k:@ +# Yes, but careful! +# While the *true cost* values are bounded, that does not mean the ones of the *model* are! +# Notice also how this only happens at the first stages of analytic descent. +# +# Bringing together a few chords we have touched so far: once we fix a reference value, the further we go from it, the less accurate our model becomes. +# Thus, if we start far off from the true minimum, it can happen that our model exaggerates how steep the landscape is and then the model minimum lies lower than that of the true cost. +# We see how values exiting the allowed range of the true cost function does not have an +# impact on the overall optimization. +# +# In this demo we've seen how to implement the Quantum Analytic Descent algorithm +# using PennyLane for a toy example of a Variational Quantum Eigensolver. +# By making extensive use of 3D plots we have also tried to illustrate exactly +# what is going on in both the true cost landscape and the trigonometric expansion +# approximation. +# Recall we wanted to avoid working on the true landscape itself because we can +# only access it via very costly quantum computations. +# Instead, a fixed number of runs on the quantum device for a few iterations +# allowed us to construct a classical model on which we performed (cheap) +# classical optimization. +# +# And that was it! Thanks for coming to our show. +# Don't forget to fasten your seat belts on your way home! It was a pleasure +# having you here today. +# +# References +# ---------- +# +# .. [#QAD] +# +# Balint Koczor, Simon Benjamin. "Quantum Analytic Descent". +# `arXiv preprint arXiv:2008.13774 `__. +# +# .. [#Rotosolve] +# +# Mateusz Ostaszewski, Edward Grant, Marcello Benedetti. +# "Structure optimization for parameterized quantum circuits". +# `arXiv preprint arXiv:1905.09692 `__. +# +# .. [#higher_order_diff] +# +# Andrea Mari, Thomas R. Bromley, Nathan Killoran. +# "Estimating the gradient and higher-order derivatives on quantum hardware". +# `Phys. Rev. A 103, 012405 `__, 2021. +# `arXiv preprint arXiv:2008.06517 `__. +# +# diff --git a/demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json b/demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json new file mode 100644 index 0000000000..7c72957f8b --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json @@ -0,0 +1,80 @@ +{ + "title": "Quantum analytic descent", + "authors": [ + { + "username": "egfuster" + }, + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2021-06-30T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_analytic_descent.png" + } + ], + "seoDescription": "Implement the Quantum analytic descent algorithm for VQE.", + "doi": "", + "references": [ + { + "id": "QAD", + "type": "article", + "title": "Quantum Analytic Descent", + "authors": "Balint Koczor, Simon Benjamin", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2008.13774" + }, + { + "id": "Rotosolve", + "type": "article", + "title": "Structure optimization for parameterized quantum circuits", + "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Benedetti", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1905.09692" + }, + { + "id": "higher_order_diff", + "type": "article", + "title": "Estimating the gradient and higher-order derivatives on quantum hardware", + "authors": "Andrea Mari, Thomas R. Bromley, Nathan Killoran", + "year": "2021", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.012405" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2008.13774" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rotoselect", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_stochastic_parameter_shift", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in b/demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_quantum_chemistry/demo.py b/demonstrations_v2/tutorial_quantum_chemistry/demo.py new file mode 100644 index 0000000000..9fad88c657 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_chemistry/demo.py @@ -0,0 +1,328 @@ +r""" +Building molecular Hamiltonians +=============================== + + +.. meta:: + :property="og:description": Learn how to build electronic Hamiltonians of molecules. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/water_structure.png + +.. related:: + tutorial_vqe A brief overview of VQE + +*Author: Alain Delgado — Posted: 02 August 2020. Last updated: 29 August 2023.* + +.. note:: + + A wide variety of molecular data, including Hamiltonians, is + available on the `PennyLane Datasets service `__. + +The ultimate goal of computational quantum chemistry is to unravel the +quantum effects that determine the structure and properties of molecules. Reaching +this goal is challenging since the characteristic energies associated with +these effects, e.g., the electronic correlation energy, are typically a tiny fraction +of the total energy of the molecule. + +Accurate molecular properties can be computed from the wave function describing the +interacting electrons in a molecule. The **electronic** wave function +:math:`\Psi(r)` satisfies the `Schrödinger equation +`_ + +.. math:: + H_e \Psi(r) = E \Psi(r), + +where :math:`H_e` and :math:`E` denote the electronic Hamiltonian and the +total energy of the molecule, respectively. When solving the latter equation, +the nuclei of the molecule can be treated as point particles whose coordinates +are fixed [#BornOpp1927]_. In this approximation, both the total energy and +the electronic Hamiltonian depend parametrically on the nuclear coordinates. + + +In this tutorial, you will learn how to use PennyLane to build a +representation of the electronic Hamiltonian :math:`H_e` that can be used to perform +**quantum** simulations of molecules [#yudong2019]_. First, we show how to define +the structure of the molecule in terms of the symbols and the coordinates of +the atoms. Next, we describe how to solve the `Hartree-Fock +equations `_ for the target +molecule. Finally, we discuss some advanced features that can be used to simulate +more complicated systems. + +Let's get started! + +Defining the molecular structure +-------------------------------- +In this example we construct the electronic Hamiltonian of the water molecule. + + +.. figure:: ../_static/demonstration_assets/quantum_chemistry/water_structure.png + :width: 30% + :align: center + +The structure of a molecule is defined by the symbols and the nuclear coordinates of +its constituent atoms. It can be specified using different `chemical file formats +`_. Within PennyLane, the molecular +structure is defined by providing a list with the atomic symbols and a one-dimensional +array with the nuclear coordinates in +`atomic units `_. +""" +import numpy as np + +symbols = ["H", "O", "H"] +coordinates = np.array([[-0.0399, -0.0038, 0.0], [1.5780, 0.8540, 0.0], [2.7909, -0.5159, 0.0]]) + +############################################################################## +# The :func:`~.pennylane.qchem.read_structure` function can also be used to read the +# molecular geometry from an external file. + + +from pennylane import qchem + +symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") + +############################################################################## +# The xyz format is supported. +# +# Solving the Hartree-Fock equations +# ---------------------------------- +# The molecule's electronic Hamiltonian is commonly represented using the +# `second-quantization `_ formalism, +# which we will explore in more detail in the +# next section. To that aim, a basis of **single-particle** states needs to be chosen. +# In quantum chemistry these states are the +# `molecular orbitals `_ +# which describe the wave function of a single electron in the molecule. +# +# Molecular orbitals are typically represented as a linear combination of **atomic orbitals**. +# The expansion coefficients in the atomic basis are calculated using the +# `Hartree-Fock (HF) method `_. +# In the HF approximation, each electron in the molecule is treated as an **independent** +# particle that moves under the influence of the Coulomb potential due to the nuclei, and a mean +# field generated by all other electrons [#pople1977]_. The optimized coefficients are precisely +# what we need to build the second-quantized Hamiltonian. +# +# PennyLane provides a differentiable Hartree-Fock solver and the functionality to construct a +# fully-differentiable molecular Hamiltonian. +# +# Building the Hamiltonian +# ------------------------ +# In the second quantization formalism, the electronic wave function of the molecule +# is represented in the occupation number basis. For :math:`M` *spin* molecular +# orbitals, the elements of this basis are labelled as +# :math:`\vert n_0, n_1, \dots, n_{M-1} \rangle,` where :math:`n_i = 0,1` +# indicates the occupation of each orbital. In this representation, the electronic +# Hamiltonian is given by +# +# .. math:: +# H = \sum_{p,q} h_{pq} c_p^\dagger c_q + +# \frac{1}{2} \sum_{p,q,r,s} h_{pqrs} c_p^\dagger c_q^\dagger c_r c_s, +# +# where :math:`c^\dagger` and :math:`c` are the electron creation +# and annihilation operators, respectively, and the coefficients +# :math:`h_{pq}` and :math:`h_{pqrs}` denote the one- and two-electron +# Coulomb integrals [#ref_integrals]_ evaluated using the Hartree-Fock +# orbitals. +# +# We can use the states of :math:`M` qubits to encode any element +# of the occupation number basis +# +# .. math:: +# \vert n_0, n_1, \dots, n_{M-1} \rangle \rightarrow \vert q_0 q_1 \cdots q_{M-1} \rangle. +# +# This implies that we need to map the fermionic operators onto operators +# that act on qubits. This can be done by using the +# `Jordan-Wigner `_ +# transformation [#seeley2012]_ which allows us to decompose the fermionic Hamiltonian +# into a linear combination of the tensor product of Pauli operators +# +# .. math:: +# H = \sum_j C_j \otimes_i \sigma_i^{(j)}, +# +# where :math:`C_j` is a scalar coefficient and :math:`\sigma_i` represents an +# element of the Pauli group :math:`\{ I, X, Y, Z \}.` +# +# In PennyLane we have the :func:`~.pennylane.qchem.molecular_hamiltonian` +# function which encapsulates all the steps explained above. It simplifies the process of building +# the electronic Hamiltonian to a single line of code. We just need to input +# the molecule, as shown below: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule) +print("Number of qubits: {:}".format(qubits)) +print("Qubit Hamiltonian") +print(H) + +############################################################################## +# Advanced features +# ----------------- +# The :class:`~.pennylane.qchem.Molecule` object allows us to define additional +# keyword arguments to solve the Hartree-Fock equations of more complicated systems. +# The net charge of the molecule may be specified to simulate positively or negatively +# charged molecules. For a neutral system we choose + +charge = 0 + +############################################################################## +# We can also specify the +# `spin multiplicity `_. For the +# water molecule, which contains ten electrons, the `Slater determinant +# `_ resulting from occupying the five +# lowest-energy orbitals with two *paired* electrons per orbital has spin multiplicity one. +# Alternatively, if we define an occupation where the first four orbitals are doubly occupied +# and the next two are singly occupied by *unpaired* electrons, the HF state will have +# multiplicity three. +# +# | +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/hf_references.png +# :width: 50% +# :align: center +# +# | +# +# For the neutral water molecule we have, + +multiplicity = 1 + +############################################################################## +# As mentioned above, molecular orbitals are represented as a linear combination +# of atomic orbitals which are typically modeled as `Gaussian-type orbitals +# `_. We can specify different types +# of `Gaussian atomic bases `_. In this example we +# choose a `minimal basis set +# `_. + +basis_set = "sto-3g" + +############################################################################## +# PennyLane also allows us to define an active space [#truhlar2018]_ to perform quantum +# simulations with a reduced number of qubits. This is done by classifying the molecular +# orbitals as core, active, and external orbitals: +# +# * Core orbitals are always occupied by two electrons. +# * Active orbitals can be occupied by zero, one, or two electrons. +# * The external orbitals are never occupied. +# +# Within this approximation, a certain number of **active electrons** are allowed to +# populate a finite set of **active orbitals**. +# +# .. figure:: ../_static/demonstration_assets/quantum_chemistry/sketch_active_space.png +# :width: 40% +# :align: center +# +# .. note:: +# The number of active **spin-orbitals** determines the **number of qubits** required +# to perform the quantum simulations. +# +# For the water molecule in a minimal basis set we have a total of ten electrons +# and seven molecular orbitals. In this example we define a symmetric active space with +# four electrons and four active orbitals using +# the :func:`~.pennylane.qchem.active_space` function: + +electrons = 10 +orbitals = 7 +core, active = qchem.active_space(electrons, orbitals, active_electrons=4, active_orbitals=4) + +############################################################################## +# Viewing the results: + +print("List of core orbitals: {:}".format(core)) +print("List of active orbitals: {:}".format(active)) +print("Number of qubits: {:}".format(2 * len(active))) + +############################################################################## +# Finally, we use the :func:`~.pennylane.qchem.molecular_hamiltonian` function to +# build the resulting Hamiltonian of the water molecule: + +molecule = qchem.Molecule( + symbols, + coordinates, + charge=charge, + mult=multiplicity, + basis_name=basis_set +) + +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=4, + active_orbitals=4, +) + +print("Number of qubits required to perform quantum simulations: {:}".format(qubits)) +print("Hamiltonian of the water molecule") +print(H) + +############################################################################## +# In this case, since we have truncated the basis of molecular orbitals, the resulting +# observable is an approximation of the Hamiltonian generated in the +# section `Building the Hamiltonian `__. +# +# OpenFermion-PySCF backend +# ------------------------- +# The :func:`~.pennylane.qchem.molecular_hamiltonian` function can also be used to construct the +# molecular Hamiltonian with a non-differentiable backend that uses the +# `OpenFermion-PySCF `_ plugin interfaced with the +# electronic structure package `PySCF `_. This +# backend can be selected by setting ``method='pyscf'`` in +# :func:`~.pennylane.qchem.molecular_hamiltonian`: + +molecule = qchem.Molecule(symbols, coordinates) +H, qubits = qchem.molecular_hamiltonian(molecule, method="pyscf") +print(H) + +############################################################################## +# This backend requires the ``OpenFermion-PySCF`` plugin to be installed by the user with +# +# .. code-block:: bash +# +# pip install openfermionpyscf +# +# Additionally, if you have built your electronic Hamiltonian independently using +# `OpenFermion `_ tools, it can +# be readily converted to a PennyLane observable using the +# :func:`~.pennylane.qchem.import_operator` function. +# +# You have completed the tutorial! Now, select your favorite molecule and build its electronic +# Hamiltonian. +# To see how simple it is to implement the VQE algorithm to compute the ground-state energy of +# your molecule using PennyLane, take a look at the tutorial :doc:`tutorial_vqe`. +# +# References +# ---------- +# +# .. [#yudong2019] +# +# Yudong Cao, Jonathan Romero, *et al.*, "Quantum Chemistry in the Age of Quantum Computing". +# `Chem. Rev. 2019, 119, 19, 10856-10915. +# `_ +# +# .. [#BornOpp1927] +# +# M. Born, J.R. Oppenheimer, "Quantum Theory of the Molecules". +# `Annalen der Physik 84, 457-484 (1927) +# `_ +# +# .. [#pople1977] +# +# Rolf Seeger, John Pople. "Self‐consistent molecular orbital methods. XVIII. Constraints and +# stability in Hartree–Fock theory". `Journal of Chemical Physics 66, +# 3045 (1977). `_ +# +# .. [#ref_integrals] +# +# J.T. Fermann, E.F. Valeev, "Fundamentals of Molecular Integrals Evaluation". +# `arXiv:2007.12057 `_ +# +# .. [#seeley2012] +# +# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for +# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). +# `_ +# +# .. [#truhlar2018] +# +# J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar. "Automatic Selection of an +# Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT". +# `Journal of Chemical Theory and Computation 14, 2017 (2018). +# `_ +# diff --git a/demonstrations_v2/tutorial_quantum_chemistry/metadata.json b/demonstrations_v2/tutorial_quantum_chemistry/metadata.json new file mode 100644 index 0000000000..7ba317278e --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_chemistry/metadata.json @@ -0,0 +1,87 @@ +{ + "title": "Building molecular Hamiltonians", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2020-08-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_building_molecular_Hamiltonians.png" + } + ], + "seoDescription": "Learn how to build electronic Hamiltonians of molecules.", + "doi": "", + "references": [ + { + "id": "yudong2019", + "type": "article", + "title": "Quantum Chemistry in the Age of Quantum Computing", + "authors": "Yudong Cao, Jonathan Romero, et al.", + "year": "2019", + "journal": "Chem. Rev.", + "url": "https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803" + }, + { + "id": "BornOpp1927", + "type": "article", + "title": "Quantum Theory of the Molecules", + "authors": "M. Born, J.R. Oppenheimer", + "year": "1927", + "journal": "Annalen der Physik", + "url": "https://onlinelibrary.wiley.com/doi/abs/10.1002/andp.19273892002" + }, + { + "id": "pople1977", + "type": "article", + "title": "Self\u2010consistent molecular orbital methods. XVIII. Constraints and stability in Hartree\u2013Fock theory", + "authors": "Rolf Seeger, John Pople", + "year": "1977", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.434318" + }, + { + "id": "ref_integrals", + "type": "article", + "title": "Fundamentals of Molecular Integrals Evaluation", + "authors": "J.T. Fermann, E.F. Valeev", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2007.12057" + }, + { + "id": "seeley2012", + "type": "article", + "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", + "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", + "year": "2012", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" + }, + { + "id": "truhlar2018", + "type": "article", + "title": "Automatic Selection of an Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT", + "authors": "J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar", + "year": "2018", + "journal": "Journal of Chemical Theory and Computation", + "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.8b00032" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_chemistry/quantum_chemistry/h2o.xyz b/demonstrations_v2/tutorial_quantum_chemistry/quantum_chemistry/h2o.xyz new file mode 100644 index 0000000000..afa795830d --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_chemistry/quantum_chemistry/h2o.xyz @@ -0,0 +1,5 @@ +3 +Water +H -0.02110 -0.00200 0.00000 +O 0.83450 0.45190 0.00000 +H 1.47690 -0.27300 0.00000 diff --git a/demonstrations_v2/tutorial_quantum_chemistry/requirements.in b/demonstrations_v2/tutorial_quantum_chemistry/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_chemistry/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py b/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py new file mode 100644 index 0000000000..8f7546b166 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py @@ -0,0 +1,889 @@ +r""".. _quantum_circuit_cutting: +Quantum Circuit Cutting +======================= + +.. meta:: + :property="og:description": We dive into two algorithms for splitting a large quantum circuit into smaller ones. + +.. related:: + tutorial_qaoa_intro Intro to QAOA + tutorial_qaoa_maxcut QAOA for MaxCut + tutorial_haar_measure Understanding the Haar measure + tutorial_unitary_designs Unitary designs + + +*Authors: Gideon Uchehara, Matija Medvidović, Anuj Apte — Posted: 02 September 2022. Last updated: 02 September 2022.* + +Introduction +------------------------------------- + +Quantum circuits with a large number of qubits are difficult to simulate. +They cannot be programmed on actual hardware due to size constraints +(insufficient qubits), and they are also error-prone. What if we "cut" +a large circuit into smaller, more manageable pieces? This is the main +idea behind the algorithm that allows you to simulate large quantum +circuits on a small quantum computer called *quantum circuit cutting*. + +In this demo, we will first introduce the theory behind quantum circuit +cutting based on Pauli measurements and see how it is implemented in +PennyLane. This method was first introduced in [#Peng2019]_. +Thereafter, we discuss the theoretical basis on randomized circuit +cutting with two-designs and demonstrate the resulting improvement in +performance compared to Pauli measurement-based circuit cutting for an +instance of Quantum Approximate Optimization Algorithm (QAOA). + + + +Background: Understanding the Pauli cutting method +-------------------------------------------------- + +Consider a two-level quantum system in an arbitrary state, described by +density matrix :math:`\rho.` The quantum state :math:`\rho` can be expressed +as a linear combination of the Pauli matrices: + +.. math:: + \rho = \frac{1}{2}\sum_{i=1}^{8} c_i Tr(\rho O_i)\rho_i. + +Here, we have denoted Pauli matrices by :math:`O_i,` their +eigenprojectors by :math:`\rho_i` and their corresponding eigenvalues by +:math:`c_i.` In the above equation, + +.. math:: + O_1 = O_2 = I, +.. math:: + O_3 = O_4 = X, +.. math:: + O_5 = O_6 = Y + +and + +.. math:: + O_7 = O_8 = Z. + +Also, + +.. math:: + \rho_1 = \rho_7=\left | {0} \right\rangle \left\langle {0} \right |, +.. math:: + \rho_2 = \rho_8 = \left | {1} \right\rangle \left\langle {1} \right |, +.. math:: + \rho_3 = \left | {+} \right\rangle \left\langle {+} \right |, +.. math:: + \rho_4 = \left | {-} \right\rangle \left\langle {-} \right |, +.. math:: + \rho_5 = \left | {+i} \right\rangle \left\langle {+i} \right |, +.. math:: + \rho_6 = \left | {-i} \right\rangle \left\langle {-i} \right | + +and + +.. math:: + c_i = \pm 1. + +The above equation can be implemented as a quantum circuit on a quantum +computer. To do this, each term :math:`Tr(\rho O_i)\rho_i` in the equation +is broken into two parts. The first part, :math:`Tr(\rho O_i)` is the +expectation of the observable :math:`O_i` when the system is in the state +:math:`\rho`. Let's call this first circuit subcircuit-:math:`u.` +The second part, :math:`\rho_i` is initialization or preparation of the +eigenstate, :math:`\rho_i`. Let's call this Second circuit subcircuit-:math:`v.` +The above equation shows how we can recover a quantum state after a cut is made +on one of its qubits as shown in figure 1. This forms the core of quantum +circuit cutting. + +It turns out that we only have to do three measurements +:math:`\left (Tr(\rho X), Tr(\rho Y), Tr(\rho Z) \right)` for +subcircuit-:math:`u` and initialize subcircuit-:math:`v` with only four +states: :math:`\left | {0} \right\rangle,` +:math:`\left | {1} \right\rangle,` :math:`\left | {+} \right\rangle` and +:math:`\left | {+i} \right\rangle.` The other two nontrivial expectation +values for states :math:`\left | {-} \right\rangle` and +:math:`\left | {- i} \right\rangle` can be derived with classical +post-processing. + +In general, there is a resolution of the identity along a wire (qubit) that +we can interpret as circuit cutting. In the following section, we will +provide a more clever way of resolving the same identity that leads +to fewer shots needed to estimate observables. + +.. figure:: ../_static/demonstration_assets/quantum_circuit_cutting/1Qubit-Circuit-Cutting.png + :align: center + :width: 80% + + Figure 1. The Pauli circuit cutting method for 1-qubit circuit. The + first half of the cut circuit on the left (subcircuit-u) is the part + with ``MeasureNode``. The second half of the cut circuit on the right + (subcircuit-v) is the part with ``PrepareNode`` + +PennyLane implementation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PennyLane's built-in circuit cutting algorithm, ``qml.cut_circuit``, +takes a large quantum circuit and decomposes it into smaller subcircuits that +are executed on a small quantum device. The results from executing the +smaller subcircuits are then recombined through some classical post-processing +to obtain the original result of the large quantum circuit. + +Let’s simulate a "real-world" scenario with ``qml.cut_circuit`` using the +circuit below. +""" + +# Import the relevant libraries +from functools import partial + +import pennylane as qml +from pennylane import numpy as np + +dev = qml.device("default.qubit", wires=3) + + +@qml.qnode(dev) +def circuit(x): + qml.RX(x, wires=0) + qml.RY(0.9, wires=1) + qml.RX(0.3, wires=2) + + qml.CZ(wires=[0, 1]) + qml.RY(-0.4, wires=0) + + qml.CZ(wires=[1, 2]) + + return qml.expval(qml.pauli.string_to_pauli_word("ZZZ")) + + +x = np.array(0.531, requires_grad=True) +fig, ax = qml.draw_mpl(circuit)(x) + + +###################################################################### +# Given the above quantum circuit, our goal is to simulate a 3-qubit quantum +# circuit on a 2-qubit quantum computer. This means that we have to cut +# the circuit such that the resulting subcircuits have at most 2 qubits. +# +# Apart from ensuring that the number of qubits for each subcircuit does not +# exceed the number of qubits on our quantum device, we also have to ensure +# that the resulting subcircuits have the most efficient classical +# post-processing. This is not quite trivial to determine in most cases, but +# for the above circuit, the best cut location turns out to be between +# the two ``CZ`` gates on qubit 1 (more on this later). Hence, we place a +# ``WireCut`` operation at that location as shown below: +# + +dev = qml.device("default.qubit", wires=3) + +# Quantum Circuit with QNode + + +@qml.qnode(dev) +def circuit(x): + qml.RX(x, wires=0) + qml.RY(0.9, wires=1) + qml.RX(0.3, wires=2) + + qml.CZ(wires=[0, 1]) + qml.RY(-0.4, wires=0) + + qml.WireCut(wires=1) # Cut location + + qml.CZ(wires=[1, 2]) + + return qml.expval(qml.pauli.string_to_pauli_word("ZZZ")) + + +x = np.array(0.531, requires_grad=True) # Defining the parameter x +fig, ax = qml.draw_mpl(circuit)(x) # Drawing circuit + + +###################################################################### +# The dashed line and the scissors between the two ``CZ`` gates on qubit 1 in the +# above figure show where we have chosen to cut. This is where the ``WireCut`` +# operation is inserted. ``WireCut`` is used to manually mark locations for +# wire cuts. +# +# Next, we apply ``qml.cut_circuit`` operation as a decorator to the +# ``circuit`` function to perform circuit cutting on the quantum circuit. + +dev = qml.device("default.qubit", wires=3) + +# Quantum Circuit with QNode + + +@qml.cut_circuit # Applying qml.cut_circuit for circuit cut operation +@qml.qnode(dev) +def circuit(x): + qml.RX(x, wires=0) + qml.RY(0.9, wires=1) + qml.RX(0.3, wires=2) + + qml.CZ(wires=[0, 1]) + qml.RY(-0.4, wires=0) + + qml.WireCut(wires=1) # Cut location + + qml.CZ(wires=[1, 2]) + + return qml.expval(qml.pauli.string_to_pauli_word("ZZZ")) + + +x = np.array(0.531, requires_grad=True) +circuit(x) # Executing the quantum circuit + + +###################################################################### +# Let's explore what happens behind the scenes in ``qml.cut_circuit``. When the +# ``circuit`` qnode function is executed, the quantum circuit is converted to +# a `quantum tape `__ +# and then to a graph. Any ``WireCut`` in the quantum +# circuit graph is replaced with ``MeasureNode`` and ``PrepareNode`` pairs as +# shown in figure 2. The ``MeasureNode`` is the point on the cut qubit that +# indicates where to measure the observable :math:`O_i` after cut. On the other +# hand, the ``PrepareNode`` is the point on the cut qubit that indicates where +# to initialize the state :math:`\rho` after cut. +# Both ``MeasureNode`` and ``PrepareNode`` are placeholder +# operations that allow us to cut the quantum circuit graph and then iterate +# over measurements of Pauli observables and preparations of their corresponding +# eigenstate configurations at cut locations. + +################################################################### +# .. figure:: ../_static/demonstration_assets/quantum_circuit_cutting/MeasurePrepareNodes.png +# :align: center +# :width: 90% +# +# Figure 2. Replace WireCut with MeasureNode and PrepareNode +# +# Cutting at the said location gives two graph fragments with 2 qubits each. To +# separate these fragments into different subcircuit graphs, the +# ``fragment_graph()`` function is called to pull apart the quantum circuit +# graph as shown in figure 3. The subcircuit graphs are reconverted back to +# quantum tapes and ``qml.cut_circuit`` runs multiple configurations of the +# 2-qubit subcircuit tapes which are then post-processed to replicate the result +# of the uncut circuit. +# + + +###################################################################### +# .. figure:: ../_static/demonstration_assets/quantum_circuit_cutting/separateMeasurePrepareNodes.png +# :align: center +# :width: 90% +# +# Figure 3. Separate fragments into different subcircuits +# +# + + +###################################################################### +# **Automatic cut placement** +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# We manually found a good cut position, but what if we didn't know where it +# was in general? Changing cut positions results in different outcomes in terms +# of simulation efficiency, so choosing the optimal cut reduces post-processing +# overhead and improves simulation efficiency. +# +# Automatic cut placment is a PennyLane functionality that aids us in +# finding the optimal cut that fragments a circuit such that +# the classical post-processing overhead is minimized. The main algorithm +# behind automatic cut placement is `graph partitioning `__ +# +# If ``auto_cutter`` is enabled in ``qml.cut_circuit``, PennyLane makes attempts +# to find an optimal cut using graph partitioning. Whenever it is difficult to +# manually determine the optimal cut location, this is the recommended +# approach to circuit cutting. The following example shows this capability +# on the same circuit as above but with the ``WireCut`` removed. +# + +dev = qml.device("default.qubit", wires=3) + + +@partial(qml.cut_circuit, auto_cutter=True) # auto_cutter enabled +@qml.qnode(dev) +def circuit(x): + qml.RX(x, wires=0) + qml.RY(0.9, wires=1) + qml.RX(0.3, wires=2) + + qml.CZ(wires=[0, 1]) + qml.RY(-0.4, wires=0) + + qml.CZ(wires=[1, 2]) + + return qml.expval(qml.pauli.string_to_pauli_word("ZZZ")) + + +x = np.array(0.531, requires_grad=True) +circuit(x) + + +###################################################################### +# Randomized Circuit Cutting +# ------------------------------------ +# +# After reviewing the standard circuit cutting based on Pauli measurements +# on single qubits, we are now ready to discuss an improved circuit +# cutting protocol that uses randomized measurements to speed up circuit +# cutting. Our description of this method will be based on the recently +# published work [#Lowe2022]_. +# +# The key idea behind this approach is to use measurements in an entangled +# basis that is based on a unitary 2-design to get more information about +# the state with fewer measurements compared to single-qubit Pauli +# measurements. +# +# The concept of 2-designs is simple — a unitary 2-design is a finite +# collection of unitaries such that the average of any degree 2 polynomial +# function of a linear operator over the design is exactly the same as the +# average over Haar random measure. For further explanation of this measure read +# the `Haar Measure demo `__. +# +# More precisely, let :math:`P(U)` be a polynomial with homogeneous degree at most two in +# the entries of a unitary matrix :math:`U,` and degree two in the complex +# conjugates of those entries. A unitary 2-design is a set of :math:`L` +# unitaries :math:`\{U_{L}\}` such that +# +# .. math:: \frac{1}{L} \sum_{l=1}^{L} P(U_l) = \int_{\mathcal{U}(d)} P (U) d\mu(U)~. +# +# The elements of the Clifford group over the qubits being cut are an +# example of a 2-design. We don’t have a lot of space here to go into too +# many details. But fear not - there is an `entire +# demo `__ +# dedicated to this wonderful topic! +# +# .. figure:: ../_static/demonstration_assets/quantum_circuit_cutting/flowchart.svg +# :align: center +# :width: 90% +# +# Figure 4. Illustration of Randomized Circuit Cutting based on Two-Designs. Taken from [#Lowe2022]_. +# +# If :math:`k` qubits are being cut, then the dimensionality of the +# Hilbert space is :math:`d=2^{k}.` The key idea of Randomized Circuit Cutting +# is to employ two different quantum channels with probabilities such that together +# they comprise a resolution of Identity. In the randomized measurement circuit +# cutting procedure, we trace out the :math:`k` qubits and prepare a random basis +# state with probability :math:`d/(2d+1).` For a linear operator +# :math:`X \in \mathbf{L}(\mathbb{C}^{d})` acting on the :math:`k` qubits, +# this operation corresponds to the completely depolarizing channel +# +# .. math:: \Psi_{1}(X) = \textrm{Tr}(X)\frac{\mathbf{1}}{d}~. +# +# Otherwise, we perform a measure-and-prepare protocol based on +# a unitary 2-design (e.g. a random Clifford) with probability +# :math:`(d+1)/(2d+1),` corresponding to the channel +# +# .. math:: \Psi_{0}(X) = \frac{1}{d+1}\left(\textrm{Tr}(X)\mathbf{1} + X\right)~. +# +# The sets of Kraus operators for the channels :math:`\Psi_{1} and \Psi_{0}` are +# +# .. math:: +# +# \Psi_{1}(X) \xrightarrow{} \left\{ \frac{|i\rangle \langle j|}{\sqrt{d}} \right\} \quad +# \Psi_{0}(X) \xrightarrow{} \left\{ \frac{\mathbf{1}}{\sqrt{d+1}} ~,~ \frac{|i\rangle \langle j|}{\sqrt{d+1}} \right\}~, +# +# where indices :math:`i,j` run over the :math:`d` basis elements. +# +# Together, these two channels can be used to obtain a resolution of the +# Identity channel on the :math:`k`-qubits as follows +# +# .. math:: X = (d+1)\Psi_0(X)-d\Psi_1(X)~. +# +# By employing this procedure, we can estimate the outcome of the original +# circuit by using the cut circuits. For an error threshold of +# :math:`\varepsilon,` the associated overhead is +# :math:`O(4^{k}(n+k^{2})/\varepsilon^{2}).` When :math:`k` is a small +# constant and the circuit is cut into roughly two equal halves, this +# procedure effectively doubles the number of qubits that can be +# simulated given a quantum device, since the overhead is :math:`O(4^k)` +# which is much lower than the :math:`O(16^k)` overhead of cutting with single-qubit +# measurements. Note that, although the overhead incurred is smaller, the +# average depth of the circuit is greater since a random Clifford unitary +# over the :math:`k` qubits has to be implemented when randomized measurement +# is performed. +# +# Comparison +# --------------------- +# +# We have seen that looking at circuit cutting through the lens of +# 2-designs can be a source of considerable speedups. A good test case +# where one may care about accurately estimating an observable is the +# `Quantum Approximate Optimization +# Algorithm `__ +# (QAOA). In its simplest form, QAOA concerns itself with finding a +# lowest energy state of a *cost Hamiltonian* :math:`H_{\mathcal{C}}:` +# +# .. math:: H_\mathcal{C} = \frac{1}{|E|} \sum _{(i, j) \in E} Z_i Z_j +# +# on a graph :math:`G=(V,E),` where :math:`Z_i` is a Pauli-:math:`Z` +# operator. The normalization factor is just here so that expectation +# values do not lie outside the :math:`[-1,1]` interval. +# +# Setup +# ~~~~~ +# +# Suppose that we have a specific class of graphs we care about and +# someone already provided us with optimal angles :math:`\gamma` and +# :math:`\beta` for QAOA of depth :math:`p=1.` Here’s how to map the input +# graph :math:`G` to the QAOA circuit that solves our problem: +# +# .. figure:: ../_static/demonstration_assets/quantum_circuit_cutting/graph_to_circuit.svg +# :align: center +# :width: 90% +# +# Figure 5. An example of mapping an input interaction graph to a QAOA +# circuit. (Note: the “stick” gates represent the ZZ rotation gates, to avoid +# overcrowding the diagram.) +# +# Let’s generate a similar QAOA graph to the one in the figure this using +# `NetworkX `__! +# + +import networkx as nx +from itertools import product, combinations + +np.random.seed(1337) + +n_side_nodes = 2 +n_middle_nodes = 3 + +top_nodes = range(0, n_side_nodes) +middle_nodes = range(n_side_nodes, n_side_nodes + n_middle_nodes) +bottom_nodes = range(n_side_nodes + n_middle_nodes, n_middle_nodes + 2 * n_side_nodes) + +top_edges = list(product(top_nodes, middle_nodes)) +bottom_edges = list(product(middle_nodes, bottom_nodes)) + +graph = nx.Graph() +graph.add_edges_from(combinations(top_nodes, 2), color=0) +graph.add_edges_from(top_edges, color=0) +graph.add_edges_from(bottom_edges, color=1) +graph.add_edges_from(combinations(bottom_nodes, 2), color=1) + +nx.draw_spring(graph, with_labels=True) + +###################################################################### +# For this graph, optimal QAOA parameters read: +# +# .. math:: +# +# \gamma ^* \approx -0.240 \; ; \qquad \beta ^* \approx 0.327 \quad \Rightarrow \quad \left\langle H_\mathcal{C} \right\rangle ^* \approx -0.248 +# + +optimal_params = np.array([-0.240, 0.327]) +optimal_cost = -0.248 + +###################################################################### +# We also define our cost operator :math:`H_{\mathcal{C}}` as a function. +# Because it is diagonal in the computational basis, we only need to +# define its action on computational basis bitstrings. +# + + +def qaoa_cost(bitstring): + + bitstring = np.atleast_2d(bitstring) + # Make sure that we operate correctly on a batch of bitstrings + + z = (-1) ** bitstring[:, graph.edges()] # Filter out pairs of bits correspondimg to graph edges + costs = z.prod(axis=-1).sum(axis=-1) # Do products and sums + return np.squeeze(costs) / len(graph.edges) # Normalize + + +###################################################################### +# Let’s make a quick and simple QAOA circuit in PennyLane. Before we actually +# cut the circuit, we have to briefly think about the cut placement. First, we +# want to apply all ZZ rotation gates corresponding to the ``top_edges``, place the wire +# cut, and then the ``bottom_edges``, to ensure that the circuit actually splits +# in two after cutting. +# + + +def qaoa_template(params): + + gamma, beta = params + + for i in range(len(graph)): # Apply the Hadamard gates + qml.Hadamard(wires=i) + + for i, j in top_edges: + + # Apply the ZZ rotation gates + # corresponding to the + # green edges in the figure + + qml.MultiRZ(2 * gamma, wires=[i, j]) + + qml.WireCut(wires=middle_nodes) # Place the wire cut + + for i, j in bottom_edges: + + # Apply the ZZ rotation gates + # corresponding to the + # purple edges in the figure + + qml.MultiRZ(2 * gamma, wires=[i, j]) + + for i in graph.nodes(): # Finally, apply the RX gates + qml.RX(2 * beta, wires=i) + + +###################################################################### +# Let’s construct the ``QuantumTape`` corresponding to this template and +# draw the circuit: +# + +from pennylane.tape import QuantumTape + +all_wires = list(range(len(graph))) + +with qml.queuing.AnnotatedQueue() as q: + qaoa_template(optimal_params) + qml.sample(wires=all_wires) + +tape = QuantumTape.from_queue(q) + +fig, _ = qml.drawer.tape_mpl(tape) +fig.set_size_inches(12, 6) + + +###################################################################### +# The Pauli cutting method +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To run fragment subcircuits and combine them into a finite-shot estimate +# of the optimal cost function using the Pauli cut method, we can use +# built-in PennyLane functions. We simply use the ``qml.cut_circuit_mc`` +# transform and everything is taken care of for us. +# +# Note that we have already introduced the ``qml.cut_circuit`` transform +# in the previous section. The ``_mc`` appendix stands for Monte Carlo and +# is used to calculate finite-shot estimates of observables. The +# observable itself is passed to the ``qml.cut_circuit_mc`` transform as a +# function mapping a bitstring (circuit sample) to a single number. +# + +dev = qml.device("default.qubit", wires=all_wires) + + +@partial(qml.cut_circuit_mc, classical_processing_fn=qaoa_cost) +@qml.qnode(dev) +def qaoa(params): + qaoa_template(params) + return qml.sample(wires=all_wires) + + +###################################################################### +# We can obtain the cost estimate by simply running ``qaoa`` like a +# “normal” ``QNode``. Let’s do just that for a grid of values so we can +# study convergence. +# + +n_shots = 10000 + +shot_counts = np.logspace(1, 4, num=20, dtype=int, requires_grad=False) +pauli_cost_values = np.zeros_like(shot_counts, dtype=float) + +for i, shots in enumerate(shot_counts): + pauli_cost_values[i] = qaoa(optimal_params, shots=shots) + + +###################################################################### +# We will save these results for later and plot them together with results +# of the randomized measurement method. +# +# The randomized channel-based cutting method +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# As noted earlier, the easiest way to mathematically represent the +# randomized channel-based method is to write down Kraus operators for the +# relevant channels, :math:`\Psi _0` and :math:`\Psi _1.` Once we have +# represented them in explicit matrix form, we can simply use ``qml.QubitChannel``. +# +# +# To get our matrices, we represent the computational basis set along the +# :math:`k` cut wires as a unit vector +# +# .. math:: +# +# \left\vert i \right\rangle \mapsto (0, \ldots, 1,\ldots,0) +# +# with the 1 positioned at index :math:`i.` Therefore: +# +# .. math:: +# +# \left\vert i \right\rangle \left\langle j \right\vert \mapsto \begin{pmatrix} +# 0 & 0 & \cdots & 0 & 0 \\ +# 0 & \ddots & \cdots & 0 & 0 \\ +# \vdots & 0 & 1 & 0 & \vdots \\ +# 0 & 0 & \cdots & \ddots & 0 \\ +# 0 & 0 & \cdots & 0 & 0 \\ +# \end{pmatrix} +# +# where the 1 sits at column :math:`i` and row :math:`j.` +# +# Given this representation, a neat way to get all Kraus operators’ matrix +# representations is the following: +# + + +def make_kraus_ops(num_wires: int): + + d = 2**num_wires + + # High level idea: Take the identity operator on d^2 x d^2 and look at each row independently. + # When reshaped into a matrix, it gives exactly the matrix representation of |i>`__ +# (MPS) circuit. However, in order to cut a :math:`p=2` QAOA circuit, we +# would need 2 cuts. This introduces some subtleties within the context of +# classical simulation that we point out here. +# +# The measurement performed as a part of the first cut always induces a +# reduced state on the remaining wires. If the circuit has an MPS +# structure, we can just measure all qubits at once —a part of the +# measured bitstring gets passed into the second fragment and the +# remaining bits go directly into the output bitstring. However, when we +# try the same thing on a non-MPS circuit, additional gates need to be +# applied on the wires that now hold a reduced state. This is the other +# reason why it is easier to simulate circuit cutting of a non-MPS circuit +# with a mixed-state simulator. +# +# .. figure:: ../_static/demonstration_assets/quantum_circuit_cutting/mid_circuit_measure.svg +# :align: center +# :width: 90% +# +# Figure 7. A schematic representation of the mid-circuit measurement +# “problem”. +# +# Note that, in these cases, memory requirements of classical simulation +# are increased from :math:`O(2^n)` to :math:`O(4^n).` However, this is +# only a constraint for classical simulation where we have to choose +# between state-vector and density-matrix approaches. Real quantum +# devices don’t have such limitations, of course. +# +# +# References +# ---------- +# +# .. [#Peng2019] +# +# T. Peng, A. Harrow, M. Ozols, and X. Wu (2019) "Simulating Large Quantum Circuits on a Small Quantum Computer". +# (`arXiv `__) +# +# .. [#Lowe2022] +# +# A. Lowe et. al. (2022) "Fast quantum circuit cutting with randomized measurements". +# (`arXiv `__) +# +# diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json b/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json new file mode 100644 index 0000000000..9b7b66a7d1 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json @@ -0,0 +1,73 @@ +{ + "title": "Quantum Circuit Cutting", + "authors": [ + { + "username": "gideonuchehara" + }, + { + "username": "Matematija" + }, + { + "username": "aapte" + } + ], + "dateOfPublication": "2022-09-02T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_circuit_cutting.png" + } + ], + "seoDescription": "We dive into two algorithms for splitting a large quantum circuit into smaller ones.", + "doi": "", + "references": [ + { + "id": "Peng2019", + "type": "article", + "title": "Simulating Large Quantum Circuits on a Small Quantum Computer", + "authors": "T. Peng, A. Harrow, M. Ozols, and X. Wu", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1904.00102" + }, + { + "id": "Lowe2022", + "type": "article", + "title": "Fast quantum circuit cutting with randomized measurements", + "authors": "A. Lowe et al.", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2207.14734" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_haar_measure", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in b/demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in new file mode 100644 index 0000000000..f84259cda4 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in @@ -0,0 +1,3 @@ +matplotlib +networkx +pennylane diff --git a/demonstrations_v2/tutorial_quantum_dropout/demo.py b/demonstrations_v2/tutorial_quantum_dropout/demo.py new file mode 100644 index 0000000000..4469c56943 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_dropout/demo.py @@ -0,0 +1,700 @@ +r"""Dropout for Quantum Neural Networks +=================================== +""" + +###################################################################### +# Are you struggling with overfitting while training Quantum Neural Networks (QNNs)? +# +# In this demo, we show how to exploit the quantum version of the dropout technique to avoid the problem of +# overfitting in overparametrized QNNs. What follows is based on the paper “A General +# Approach to Dropout in Quantum Neural Networks” by F. Scala, et al. [#dropout]_. +# +# .. figure:: ../_static/demonstration_assets/quantum_dropout/socialthumbnail_large_QuantumDropout_2024-03-07.png +# :align: center +# :width: 60% +# :target: javascript:void(0) +# +# +# What is overfitting and dropout? +# --------------------------------- +# +# Neural Networks (NNs) usually require highly flexible models with lots of trainable parameters in +# order to *learn* a certain underlying function (or data distribution). +# However, being able to learn with low in-sample error is not enough; *generalization* — the ability to provide +# good predictions on previously unseen data — is also desirable. +# +# Highly expressive models may suffer from **overfitting**, which means that +# they are trained too well on the training data, and as a result perform poorly on new, unseen +# data. This happens because the model has learned the noise in the training data, rather than the +# underlying pattern that is generalizable to new data. +# +# **Dropout** is a common technique for classical Deep Neural Networks (DNNs) preventing computational units +# from becoming too specialized and reducing the risk of overfitting [#Hinton2012]_, [#Srivastava2014]_. It consists of randomly removing +# neurons or connections *only during training* to block the flow of information. Once the +# model is trained, the DNN is employed in its original form. +# +# Why dropout for Quantum Neural Networks? +# ---------------------------------------- +# +# Recently, it has been shown that the use of overparametrized QNN models +# changes the optimization landscape by removing lots of local minima [#Kiani2020]_, [#Larocca2023]_. On the one hand, this increased number of +# parameters leads to faster and easier training, but on the other hand, it may drive +# the model to overfit the data. This is also strictly related to the `repeated encoding `__ of classical +# data to achieve nonlinearity in the computation. This is why, inspired from classical DNNs, one +# can think of applying some sort of dropout to QNNs. This would correspond to randomly dropping some +# (groups of) parameterized gates during training to achieve better generalization. +# +# Quantum dropout of rotations in a sine regression +# -------------------------------------------------- +# +# In this demo we will exploit quantum dropout to avoid overfitting during the regression of noisy +# data originally coming from the *sinusoidal* function. In particular, we will randomly “drop” +# rotations during the training phase. In practice, this will correspond to temporarily setting parameters to a value of 0. +# +# Let’s start by importing Pennylane and ``numpy`` and fixing the random seed for reproducibility: +# + +import numpy as np +import pennylane as qml + +seed = 12345 +np.random.seed(seed=seed) + +###################################################################### +# The circuit +# ~~~~~~~~~~~ +# +# Now we define the embedding of classical data and the variational ansatz that will then be combined +# to construct our QNN. Dropout will happen inside the variational ansatz. Obtaining dropout with standard +# Pennylane would be quite straightforward by means of some "if statements", but the training procedure +# will take ages. Here we will leverage JAX in order to speed up the training process with +# Just In Time (JIT) compilation. The drawback is that the definition of the variational ansatz becomes a +# little elaborated, since JAX has its own language for conditional statements. For this purpose we +# define two functions ``true_cond`` and ``false_cond`` to work with ``jax.lax.cond```, which is the JAX +# conditional statement. See this `demo `__ +# for additional insights on how to optimize QNNs with JAX. +# +# Practically speaking, rotation dropout will be performed by passing a list to the ansatz. +# The single qubit rotations are applied depending on the values stored in this list: +# if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. +# How to produce this list will be explained later in this demo (see the ``make_dropout`` function). + +import jax # require for Just In Time (JIT) compilation +import jax.numpy as jnp + +jax.config.update("jax_platform_name", "cpu") +jax.config.update("jax_enable_x64", True) + + +def embedding(x, wires): + # Encodes the datum multiple times in the register, + # employing also nonlinear functions + assert len(x) == 1 # check feature is 1-D + for i in wires: + qml.RY(jnp.arcsin(x), wires=i) + for i in wires: + qml.RZ(jnp.arccos(x ** 2), wires=i) + + +def true_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is dropped + return 0.0 + + +def false_cond(angle): + # necessary for using an if statement within jitted function + # exploiting jax.lax.cond + # if this function is assessed the rotation is kept + return angle + + +def var_ansatz( + theta, wires, rotations=[qml.RX, qml.RZ, qml.RX], entangler=qml.CNOT, keep_rotation=None +): + + """Single layer of the variational ansatz for our QNN. + We have a single qubit rotation per each qubit (wire) followed by + a linear chain of entangling gates (entangler). This structure is repeated per each rotation in `rotations` + (defining `inner_layers`). + The single qubit rotations are applied depending on the values stored in `keep_rotation`: + if the value is negative the rotation is dropped (rotation dropout), otherwise it is applied. + + Params: + - theta: variational angles that will undergo optimization + - wires: list of qubits (wires) + - rotations: list of rotation kind per each `inner_layer` + - entangler: entangling gate + - keep_rotation: list of lists. There is one list per each `inner_layer`. + In each list there are indexes of the rotations that we want to apply. + Some of these values may be substituted by -1 value + which means that the rotation gate wont be applied (dropout). + """ + + # the length of `rotations` defines the number of inner layers + N = len(wires) + assert len(theta) == 3 * N + wires = list(wires) + + counter = 0 + # keep_rotations contains a list per each inner_layer + for rots in keep_rotation: + # we cicle over the elements of the lists inside keep_rotation + for qb, keep_or_drop in enumerate(rots): + rot = rotations[counter] # each inner layer can have a different rotation + + angle = theta[counter * N + qb] + # conditional statement implementing dropout + # if `keep_or_drop` is negative the rotation is dropped + angle_drop = jax.lax.cond(keep_or_drop < 0, true_cond, false_cond, angle) + rot(angle_drop, wires=wires[qb]) + for qb in wires[:-1]: + entangler(wires=[wires[qb], wires[qb + 1]]) + counter += 1 + + +###################################################################### +# And then we define the hyperparameters of our QNN, namely the number of qubits, +# the number of sublayers in the variational ansatz (``inner_layers``) and the resulting +# number of parameters per layer: +# + +n_qubits = 5 +inner_layers = 3 +params_per_layer = n_qubits * inner_layers + +###################################################################### +# Now we actually build the QNN: +# + + +def create_circuit(n_qubits, layers): + device = qml.device("default.qubit", wires=n_qubits) + + @qml.qnode(device) + def circuit(x, theta, keep_rot): + # print(x) + # print(theta) + + for i in range(layers): + embedding(x, wires=range(n_qubits)) + + keep_rotation = keep_rot[i] + + var_ansatz( + theta[i * params_per_layer : (i + 1) * params_per_layer], + wires=range(n_qubits), + entangler=qml.CNOT, + keep_rotation=keep_rotation, + ) + + return qml.expval(qml.PauliZ(wires=0)) # we measure only the first qubit + + return circuit + + +###################################################################### +# Let’s have a look at a single layer of our QNN: +# +import matplotlib.pyplot as plt + + +plt.style.use("pennylane.drawer.plot") # set pennylane theme, which is nice to see + +# create the circuit with given number of qubits and layers +layers = 1 +circ = create_circuit(n_qubits, layers=layers) + +# for the moment let's keep all the rotations in all sublayers +keep_all_rot = [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)], +] +# we count the parameters +numbered_params = np.array(range(params_per_layer * layers), dtype=float) +# we encode a single coordinate +single_sample = np.array([0]) + +qml.draw_mpl(circ, decimals=2,)(single_sample, numbered_params, keep_all_rot) + +plt.show() + +###################################################################### +# We now build the model that we will employ for the regression task. +# Since we want to have an overparametrized QNN, we will add 10 layers and we exploit +# ``JAX`` to speed the training up: +# + +layers = 10 +qnn_tmp = create_circuit(n_qubits, layers) +qnn_tmp = jax.jit(qnn_tmp) +qnn_batched = jax.vmap( + qnn_tmp, (0, None, None) +) # we want to vmap on 0-axis of the first circuit param +# in this way we process in parallel all the inputs +# We jit for faster execution +qnn = jax.jit(qnn_batched) + + +###################################################################### +# Dropping rotations +# ~~~~~~~~~~~~~~~~~~ +# +# As anticipated, we need to set some random parameters to 0 at each optimization step. Given a layer +# dropout rate :math:`p_L` (this will be called ``layer_drop_rate``) and the gate dropout rate :math:`p_G` +# (this will be called ``rot_drop_rate``), the probability :math:`p` that a +# gate is dropped in a layer can be calculated with the conditioned probability law: +# +# .. math:: +# +# +# p=p(A\cap B)=p(A|B)p(B)=p_Gp_L +# +# where :math:`B` represents the selection of a specific layer and +# :math:`A` the selection of a specific gate within the chosen layer. +# +# In the following cell we define a function that produces the list of the indices of rotation gates that +# are kept. For gates which are dropped, the value ``-1`` is assigned to the corresponding index. The structure of the list +# is nested; we have one list per ``inner_layer`` inside one list per each layer, all contained in another list. +# This function will be called at each iteration. +# + + +def make_dropout(key): + drop_layers = [] + + for lay in range(layers): + # each layer has prob p_L=layer_drop_rate of being dropped + # according to that for every layer we sample + # if we have to appy dropout in it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - layer_drop_rate, layer_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 1: # if it has to be dropped + drop_layers.append(lay) + + keep_rot = [] + # we make list of indexes corresponding to the rotations gates + # that are kept in the computation during a single train step + for i in range(layers): + # each list is divded in layers and then in "inner layers" + # this is strictly related to the QNN architecture that we use + keep_rot_layer = [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + + if i in drop_layers: # if dropout has to be applied in this layer + keep_rot_layer = [] # list of indexes for a single layer + inner_keep_r = [] # list of indexes for a single inner layer + for param in range(params_per_layer): + # each rotation within the layer has prob p=rot_drop_rate of being dropped + # according to that for every parameter (rotation) we sample + # if we have to drop it or not + out = jax.random.choice( + key, jnp.array(range(2)), p=jnp.array([1 - rot_drop_rate, rot_drop_rate]) + ) + key = jax.random.split(key)[0] # update the random key + + if out == 0: # if we have to keep it + inner_keep_r.append(param % n_qubits) # % is required because we work + # inner layer by inner layer + else: # if the rotation has to be dropped + inner_keep_r.append(-1) # we assign the value -1 + + if param % n_qubits == n_qubits - 1: # if it's the last qubit of the register + # append the inner layer list + keep_rot_layer.append(inner_keep_r) + # and reset it + inner_keep_r = [] + + keep_rot.append(keep_rot_layer) + + return jnp.array(keep_rot) + + +###################################################################### +# We can check the output of the ``make_dropout`` function: +# + +# setting the drop probability +layer_drop_rate, rot_drop_rate = 0.5, 0.3 # 15% probability of dropping a gate + +# JAX random key +key = jax.random.PRNGKey(12345) +# create the list of indexes, +# -1 implies we are dropping a gate +keep_rot = make_dropout(key) + +# let's just print the list for first layer +print(keep_rot[0]) + +###################################################################### +# Noisy sinusoidal function +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To test the effectiveness of the dropout technique, we will use a prototypical dataset +# with which it is very easy to overfit: the sinusoidal function. We produce some +# points according to the :math:`\sin` function and then we add some white Gaussian noise +# (noise that follows a normal distribution) :math:`\epsilon.` The noise is essential to obtain overfitting; +# when our model is extremely expressive, it is capable of exactly fit each point and some parameters +# become hyper-specialized in recognizing the noisy features. This makes predictions on new unseen +# data difficult, since the overfitting model did not learn the true underlying data distribution. +# The dropout technique will help in avoiding co-adaptation and hyper-specialization, +# effectively reducing overfitting. +# + +from sklearn.model_selection import train_test_split + + +def make_sin_dataset(dataset_size=100, test_size=0.4, noise_value=0.4, plot=False): + """1D regression problem y=sin(x*\pi)""" + # x-axis + x_ax = np.linspace(-1, 1, dataset_size) + y = [[np.sin(x * np.pi)] for x in x_ax] + np.random.seed(123) + # noise vector + noise = np.array([np.random.normal(0, 0.5, 1) for i in y]) * noise_value + X = np.array(x_ax) + y = np.array(y + noise) # apply noise + + # split the dataset + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=test_size, random_state=40, shuffle=True + ) + + X_train = X_train.reshape(-1, 1) + X_test = X_test.reshape(-1, 1) + + y_train = y_train.reshape(-1, 1) + y_test = y_test.reshape(-1, 1) + + return X_train, X_test, y_train, y_test + + +from matplotlib import ticker + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + + +fig, ax = plt.subplots() +plt.plot(X, y, "o", label="Training") +plt.plot(X_test, y_test, "o", label="Test") + +plt.plot( + np.linspace(-1, 1, 100), + [np.sin(x * np.pi) for x in np.linspace(-1, 1, 100)], + linestyle="dotted", + label=r"$\sin(x)$", +) +plt.ylabel(r"$y = \sin(\pi\cdot x) + \epsilon$") +plt.xlabel(r"$x$") +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) +plt.legend() + +plt.show() + +###################################################################### +# Since our circuit is only able to provide outputs in the range :math:`[-1,1],` we rescale all the +# noisy data within this range. To do this we leverage the `MinMaxScaler` from `sklearn`. +# It is common practice to fit the scaler only from training data and then apply it also to the +# test. The reason behind this is that in general one only has knowledge about the training dataset. +# (If the training dataset is not exhaustively representative of the underlying distribution, +# this preprocessing may lead to some outliers in the test set to be scaled out of the desired range.) +# + +from sklearn.preprocessing import MinMaxScaler + +scaler = MinMaxScaler(feature_range=(-1, 1)) +y = scaler.fit_transform(y) +y_test = scaler.transform(y_test) + +# reshaping for computation +y = y.reshape(-1,) +y_test = y_test.reshape(-1,) + +###################################################################### +# Optimization +# ~~~~~~~~~~~~ +# +# At this point we have to set the hyperparameters of the optimization, namely the number of epochs, the +# learning rate, and the optimizer: +# + +import optax # optimization using jax + +epochs = 700 +optimizer = optax.adam(learning_rate=0.01) + +###################################################################### +# We define the cost function as the Mean Square Error: +# + + +@jax.jit +def calculate_mse_cost(X, y, theta, keep_rot): + yp = qnn(X, theta, keep_rot) + # depending on your version of Pennylane you may require the following line + ##### + yp = jnp.array(yp).T + ##### + cost = jnp.mean((yp - y) ** 2) + + return cost + + +# Optimization update step +@jax.jit +def optimizer_update(opt_state, params, x, y, keep_rot): + loss, grads = jax.value_and_grad(lambda theta: calculate_mse_cost(x, y, theta, keep_rot))( + params + ) + updates, opt_state = optimizer.update(grads, opt_state) + + params = optax.apply_updates(params, updates) + return params, opt_state, loss + + +###################################################################### +# Training the model +# ------------------ +# +# And now we can try to train the model. We execute different runs of the training to understand the +# average behaviour of quantum dropout. To see the effect of dropout we can set different values of +# ``layer_drop_rate`` and ``rot_drop_rate``: +# + +n_run = 3 +drop_rates = [(0.0, 0.0), (0.3, 0.2), (0.7, 0.7)] + +train_history = {} +test_history = {} +opt_params = {} + + +for layer_drop_rate, rot_drop_rate in drop_rates: + # initialization of some lists to store data + costs_per_comb = [] + test_costs_per_comb = [] + opt_params_per_comb = [] + # we execute multiple runs in order to see the average behaviour + for tmp_seed in range(seed, seed + n_run): + key = jax.random.PRNGKey(tmp_seed) + assert len(X.shape) == 2 # X must be a matrix + assert len(y.shape) == 1 # y must be an array + assert X.shape[0] == y.shape[0] # compatibility check + + # parameters initialization with gaussian ditribution + initial_params = jax.random.normal(key, shape=(layers * params_per_layer,)) + # update the random key + key = jax.random.split(key)[0] + + params = jnp.copy(initial_params) + + # optimizer initialization + opt_state = optimizer.init(initial_params) + + # lists for saving single run training and test cost trend + costs = [] + test_costs = [] + + for epoch in range(epochs): + # generate the list for dropout + keep_rot = make_dropout(key) + # update the random key + key = jax.random.split(key)[0] + + # optimization step + params, opt_state, cost = optimizer_update(opt_state, params, X, y, keep_rot) + + ############## performance evaluation ############# + # inference is done with the original model + # with all the gates + keep_rot = jnp.array( + [ + [list(range((n_qubits))) for j in range(1, inner_layers + 1)] + for i in range(layers) + ] + ) + # inference on train set + cost = calculate_mse_cost(X, y, params, keep_rot) + + costs.append(cost) + + # inference on test set + test_cost = calculate_mse_cost(X_test, y_test, params, keep_rot) + test_costs.append(test_cost) + + # we print updates every 5 iterations + if epoch % 5 == 0: + print( + f"{layer_drop_rate:.1f}-{rot_drop_rate:.1f}", + f"run {tmp_seed-seed} - epoch {epoch}/{epochs}", + f"--- Train cost:{cost:.5f}", + f"--- Test cost:{test_cost:.5f}", + end="\r", + ) + + costs_per_comb.append(costs) + test_costs_per_comb.append(test_costs) + opt_params_per_comb.append(params) + print() + costs_per_comb = np.array(costs_per_comb) + test_costs_per_comb = np.array(test_costs_per_comb) + opt_params_per_comb = np.array(opt_params_per_comb) + + train_history[(layer_drop_rate, rot_drop_rate)] = costs_per_comb + test_history[(layer_drop_rate, rot_drop_rate)] = test_costs_per_comb + opt_params[(layer_drop_rate, rot_drop_rate)] = opt_params_per_comb + +###################################################################### +# Performance evaluation +# ---------------------- +# +# Let’s compare the difference in performance with a plot: +# + +fig, axs = plt.subplots(1, 2, figsize=(12, 4)) +plt.subplots_adjust(wspace=0.05) +axs[0].set_title("MSE train") +for k, v in train_history.items(): + train_losses = np.array(v) + mean_train_history = np.mean(train_losses, axis=0) + std_train_history = np.std(train_losses, axis=0,) + + mean_train_history = mean_train_history.reshape((epochs,)) + std_train_history = std_train_history.reshape((epochs,)) + + # shadow standard deviation + axs[0].fill_between( + range(epochs), + mean_train_history - std_train_history, + mean_train_history + std_train_history, + alpha=0.2, + ) + # average trend + axs[0].plot(range(epochs), mean_train_history, label=f"{k}") # Avg Loss + +axs[1].set_title("MSE test") +for k, v in test_history.items(): + test_losses = np.array(v) + mean_test_history = np.mean(test_losses, axis=0) + std_test_history = np.std(test_losses, axis=0,) + + mean_test_history = mean_test_history.reshape((epochs,)) + std_test_history = std_test_history.reshape((epochs,)) + + # shadow standard deviation + axs[1].fill_between( + range(epochs), + mean_test_history - std_test_history, + mean_test_history + std_test_history, + alpha=0.2, + ) + # averange trend + axs[1].plot(range(epochs), mean_test_history, label=f"{k}") # Avg Loss + +axs[0].legend(loc="upper center", bbox_to_anchor=(1.01, 1.25), ncol=4, fancybox=True, shadow=True) + +for ax in axs.flat: + ax.set_xlabel("Epochs") + ax.set_ylabel("MSE") + ax.set_yscale("log") + ax.set_ylim([1e-3, 0.6]) + ax.label_outer() + +plt.subplots_adjust(bottom=0.3) + +plt.show() + +###################################################################### +# On the left you can see that without dropout there is a deep minimization of the training loss, +# moderate values of dropout converge, whereas high drop probabilities impede any learning. On +# the right, we can see the difference in generalization during the optimization process. Standard +# training without dropout initially reaches a low value of generalization error, but as the +# model starts to learn the noise in the training data (overfitting), the generalization error grows +# back. Oppositely, moderate values of dropout enable generalization errors comparable to the respective +# training ones. As the learning is not successful for elevated drop probabilities, the generalization +# error is huge. It is interesting to notice that the “not-learning” error is very close to the final +# error of the QNN trained without dropout. +# +# Hence, one can conclude that low values of dropout greatly improve the generalization performance of +# the model and remove overfitting, even if the randomness of the technique inevitably makes the +# training a little noisy. On the other hand, high drop probabilities only hinder the training +# process. +# +# Validation +# ~~~~~~~~~~ +# +# To validate the technique we can also check how the model predicts in the whole :math:`[-1,1]` range +# with and without quantum dropout. +# + +X, X_test, y, y_test = make_sin_dataset(dataset_size=20, test_size=0.25) + +# spanning the whole range +x_ax = jnp.linspace(-1, 1, 100).reshape(100, 1) + +# selecting which run we want to plot +run = 1 + +fig, ax = plt.subplots() +styles = ["dashed", "-.", "solid", "-."] +for i, k in enumerate(train_history.keys()): + if k[0] == 0.3: + alpha = 1 + else: + alpha = 0.5 + # predicting and rescaling + yp = scaler.inverse_transform(qnn(x_ax, opt_params[k][run], keep_rot).reshape(-1, 1)) + plt.plot([[i] for i in np.linspace(-1, 1, 100)], yp, label=k, alpha=alpha, linestyle=styles[i]) + +plt.scatter(X, y, label="Training", zorder=10) +plt.scatter(X_test, y_test, label="Test", zorder=10) + +ylabel = r"$y = \sin(\pi\cdot x) + \epsilon$" +plt.xlabel("x", fontsize="medium") +plt.ylabel(ylabel, fontsize="medium") +plt.legend() +ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5)) + +plt.show() + +###################################################################### +# The model without dropout overfits the noisy data by trying to exactly predict each of them, +# whereas dropout actually mitigates overfitting and makes the approximation of the underlying sinusoidal +# function way smoother. +# +# Conclusion +# ---------------------- +# In this demo, we explained the basic idea behind quantum dropout and +# how to avoid overfitting by randomly "dropping" some rotation gates +# of a QNN during the training phase. We invite you to check out the paper [#dropout]_ +# for more dropout techniques and additional analysis. Try it yourself and develop new +# dropout strategies. +# +# +# References +# ---------- +# +# .. [#dropout] Scala, F., Ceschini, A., Panella, M., & Gerace, D. (2023). +# *A General Approach to Dropout in Quantum Neural Networks*. +# `Adv. Quantum Technol., 2300220 `__. +# +# .. [#Hinton2012] Hinton, G., Srivastava, N., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2012). +# *Improving neural networks by preventing co-adaptation of feature detectors*. +# `arXiv:1207.0580. `__. +# +# .. [#Srivastava2014] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). +# *Dropout: A Simple Way to Prevent Neural Networks from Overfitting*. +# `Journal of Machine Learning Research, 15(56):1929−1958. `__. +# +# .. [#Kiani2020] Kiani,B. T., Lloyd, S., & Maity, R. (2020). +# *Learning Unitaries by Gradient Descent*. +# `arXiv: 2001.11897. `__. +# +# .. [#Larocca2023] Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M. (2023). +# *Theory of overparametrization in quantum neural networks*. +# `Nat. Comp. Science, 3, 542–551. `__. +# diff --git a/demonstrations_v2/tutorial_quantum_dropout/metadata.json b/demonstrations_v2/tutorial_quantum_dropout/metadata.json new file mode 100644 index 0000000000..d74f9d9c8a --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_dropout/metadata.json @@ -0,0 +1,68 @@ +{ + "title": "Dropout in Quantum Neural Networks", + "authors": [ + { + "username": "fscala" + } + ], + "dateOfPublication": "2024-03-12T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [ + "QNN", + "overfitting", + "dropout", + "regularization" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/quantum_dropout/thumbnail_QuantumDropout_2024-03-07.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuantumDropout_2024-03-07.png" + } + ], + "seoDescription": "Learn to avoid overfitting employing quantum dropout", + "doi": "", + "references": [ + { + "id": "dropout", + "type": "article", + "title": "A General Approach to Dropout in Quantum Neural Networks", + "authors": "Scala, F., Ceschini, A., Panella, M., & Gerace, D.", + "year": "2023", + "journal": "Advanced Quantum Technologies", + "url": "https://onlinelibrary.wiley.com/doi/full/10.1002/qute.202300220", + "doi": "10.1002/qute.202300220" + }, + { + "id": "Kiani2020", + "type": "article", + "title": "Learning Unitaries by Gradient Descent", + "authors": "Kiani,B. T., Lloyd, S., & Maity, R.", + "year": "2020", + "journal": "arXiv", + "url": "https://arxiv.org/abs/2001.11897" + }, + { + "id": "Larocca2023", + "type": "article", + "title": "Theory of overparametrization in quantum neural networks", + "authors": "Larocca, M., Ju, N., Garc\u00eda-Mart\u00edn, D., Coles, P. J., & Cerezo, M.", + "year": "2023", + "journal": "Nature Computational Science", + "url": "http://dx.doi.org/10.1038/s43588-023-00467-6", + "doi": "10.1038/s43588-023-00467-6" + } + ], + "basedOnPapers": [ + "10.1002/qute.202300220" + ], + "referencedByPapers": [], + "relatedContent": [], + "hardware": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_dropout/requirements.in b/demonstrations_v2/tutorial_quantum_dropout/requirements.in new file mode 100644 index 0000000000..4dffc525cf --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_dropout/requirements.in @@ -0,0 +1,7 @@ +jax +jaxlib +matplotlib +numpy +optax +pennylane +scikit-learn diff --git a/demonstrations_v2/tutorial_quantum_gans/demo.py b/demonstrations_v2/tutorial_quantum_gans/demo.py new file mode 100644 index 0000000000..f24a785ecf --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_gans/demo.py @@ -0,0 +1,599 @@ +r""" +.. _quantum_gans: + +Quantum GANs +============ + +.. meta:: + :property="og:description": Explore quantum GANs to generate hand-written digits of zero + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/patch.jpeg + +.. related:: + tutorial_QGAN Quantum generative adversarial networks with Cirq + TensorFlow + +""" + + +###################################################################### +# *Author: James Ellis — Posted: 01 February 2022. Last updated: 27 January 2022.* +# +# In this tutorial, we will explore quantum GANs to generate hand-written +# digits of zero. We will first cover the theory of the classical case, +# then extend to a quantum method recently proposed in the literature. If +# you have no experience with GANs, particularly in PyTorch, you might +# find `PyTorch's +# tutorial `__ +# useful since it serves as the foundation for what is to follow. +# + + +###################################################################### +# Generative Adversarial Networks (GANs) +# -------------------------------------- +# + + +###################################################################### +# The goal of generative adversarial networks (GANs) [#goodfellow2014]_ is to generate +# data that resembles the original data used in training. To achieve this, +# we train two neural networks simulatenously: a generator and a +# discriminator. The job of the generator is to create fake data which +# imitates the real training dataset. On the otherhand, the discriminator +# acts like a detective trying to discern real from fake data. During the +# training process, both players iteratively improve with one another. By +# the end, the generator should hopefully generate new data very similar +# to the training dataset. +# +# Specifically, the training dataset represents samples drawn from some +# unknown data distribution :math:`P_{data},` and the generator has the +# job of trying to capture this distribution. The generator, :math:`G,` +# starts from some initial latent distribution, :math:`P_z,` and maps it +# to :math:`P_g = G(P_z).` The best solution would be for +# :math:`P_g = P_{data}.` However, this point is rarely achieved in +# practice apart from in the most simple tasks. +# +# Both the discriminator, :math:`D`, and generator, :math:`G,` play in a +# 2-player minimax game. The discriminator tries to maximise the +# probability of discerning real from fake data, while the generator tries +# to minimise the same probability. The value function for the game is +# summarised by, +# +# .. math:: +# +# \begin{align} +# \min_G \max_D V(D,G) &= \mathbb{E}_{\boldsymbol{x}\sim p_{data}}[\log D(\boldsymbol{x})] \\ +# & ~~ + \mathbb{E}_{\boldsymbol{z}\sim p_{\boldsymbol{z}}}[\log(1 - D(G(\boldsymbol{z}))] +# \end{align} +# +# - :math:`\boldsymbol{x}:` real data sample +# - :math:`\boldsymbol{z}:` latent vector +# - :math:`D(\boldsymbol{x}):` probability of the discriminator +# classifying real data as real +# - :math:`G(\boldsymbol{z}):` fake data +# - :math:`D(G(\boldsymbol{z})):` probability of discriminator +# classifying fake data as real +# +# In practice, the two networks are trained iteratively, each with +# a separate loss function to be minimised, +# +# .. math:: L_D = -[y \cdot \log(D(x)) + (1-y)\cdot \log(1-D(G(z)))] +# +# .. math:: L_G = [(1-y) \cdot \log(1-D(G(z)))] +# +# where :math:`y` is a binary label for real (:math:`y=1`) or fake +# (:math:`y=0`) data. In practice, generator training is shown to be more +# stable [#goodfellow2014]_ when made to maximise :math:`\log(D(G(z)))` instead of +# minimising :math:`\log(1-D(G(z))).` Hence, the generator loss function to +# be minimised becomes, +# +# .. math:: L_G = -[(1-y) \cdot \log(D(G(z)))] +# + + +###################################################################### +# Quantum GANs: The Patch Method +# ------------------------------ +# + + +###################################################################### +# In this tutorial, we re-create one of the quantum GAN methods presented +# by Huang et al. [#huang2020]_: the patch method. This method uses several quantum +# generators, with each sub-generator, :math:`G^{(i)},` responsible for +# constructing a small patch of the final image. The final image is +# contructed by concatenting all of the patches together as shown below. +# +# .. figure:: ../_static/demonstration_assets/quantum_gans/patch.jpeg +# :width: 90% +# :alt: quantum_patch_method +# :align: center +# +# The main advantage of this method is that it is particulary suited to +# situations where the number of available qubits are limited. The same +# quantum device can be used for each sub-generator in an iterative +# fashion, or execution of the generators can be parallelised across +# multiple devices. +# +# .. note:: +# +# In this tutorial, parenthesised superscripts are used to denote +# individual objects as part of a collection. +# + + +###################################################################### +# Module Imports +# -------------- +# + +# Library imports +import math +import random +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +import pennylane as qml + +# Pytorch imports +import torch +import torch.nn as nn +import torch.optim as optim +import torchvision +import torchvision.transforms as transforms +from torch.utils.data import Dataset, DataLoader + +# Set the random seed for reproducibility +seed = 42 +torch.manual_seed(seed) +np.random.seed(seed) +random.seed(seed) + + +###################################################################### +# Data +# ---- +# + + +###################################################################### +# As mentioned in the introduction, we will use a `small +# dataset `__ +# of handwritten zeros. First, we need to create a custom dataloader for +# this dataset. +# + + +class DigitsDataset(Dataset): + """Pytorch dataloader for the Optical Recognition of Handwritten Digits Data Set""" + + def __init__(self, csv_file, label=0, transform=None): + """ + Args: + csv_file (string): Path to the csv file with annotations. + root_dir (string): Directory with all the images. + transform (callable, optional): Optional transform to be applied + on a sample. + """ + self.csv_file = csv_file + self.transform = transform + self.df = self.filter_by_label(label) + + def filter_by_label(self, label): + # Use pandas to return a dataframe of only zeros + df = pd.read_csv(self.csv_file) + df = df.loc[df.iloc[:, -1] == label] + return df + + def __len__(self): + return len(self.df) + + def __getitem__(self, idx): + if torch.is_tensor(idx): + idx = idx.tolist() + + image = self.df.iloc[idx, :-1] / 16 + image = np.array(image) + image = image.astype(np.float32).reshape(8, 8) + + if self.transform: + image = self.transform(image) + + # Return image and label + return image, 0 + + +###################################################################### +# Next we define some variables and create the dataloader instance. +# + +image_size = 8 # Height / width of the square images +batch_size = 1 + +transform = transforms.Compose([transforms.ToTensor()]) +dataset = DigitsDataset(csv_file="../_static/demonstration_assets/quantum_gans/optdigits.tra", transform=transform) +dataloader = torch.utils.data.DataLoader( + dataset, batch_size=batch_size, shuffle=True, drop_last=True +) + + +###################################################################### +# Let's visualize some of the data. + +plt.figure(figsize=(8,2)) + +for i in range(8): + image = dataset[i][0].reshape(image_size,image_size) + plt.subplot(1,8,i+1) + plt.axis('off') + plt.imshow(image.numpy(), cmap='gray') + +plt.show() + + +###################################################################### +# Implementing the Discriminator +# ------------------------------ +# + + +###################################################################### +# For the discriminator, we use a fully connected neural network with two +# hidden layers. A single output is sufficient to represent the +# probability of an input being classified as real. +# + + +class Discriminator(nn.Module): + """Fully connected classical discriminator""" + + def __init__(self): + super().__init__() + + self.model = nn.Sequential( + # Inputs to first hidden layer (num_input_features -> 64) + nn.Linear(image_size * image_size, 64), + nn.ReLU(), + # First hidden layer (64 -> 16) + nn.Linear(64, 16), + nn.ReLU(), + # Second hidden layer (16 -> output) + nn.Linear(16, 1), + nn.Sigmoid(), + ) + + def forward(self, x): + return self.model(x) + + +###################################################################### +# Implementing the Generator +# -------------------------- +# + + +###################################################################### +# Each sub-generator, :math:`G^{(i)},` shares the same circuit +# architecture as shown below. The overall quantum generator consists of +# :math:`N_G` sub-generators, each consisting of :math:`N` qubits. The +# process from latent vector input to image output can be split into four +# distinct sections: state embedding, parameterisation, non-linear +# transformation, and post-processing. Each of the following sections +# below refer to a single iteration of the training process to simplify +# the discussion. +# +# .. figure:: ../_static/demonstration_assets/quantum_gans/qcircuit.jpeg +# :width: 90% +# :alt: quantum_circuit +# :align: center +# +# **1) State Embedding** +# +# +# A latent vector, :math:`\boldsymbol{z}\in\mathbb{R}^N,` is sampled from +# a uniform distribution in the interval :math:`[0,\pi/2).` All +# sub-generators receive the same latent vector which is then embedded +# using RY gates. +# +# **2) Parameterised Layers** +# +# +# The parameterised layer consists of parameterised RY gates followed by +# control Z gates. This layer is repeated :math:`D` times in total. +# +# **3) Non-Linear Transform** +# +# +# Quantum gates in the circuit model are unitary which, by definition, +# linearly transform the quantum state. A linear mapping between the +# latent and generator distribution would suffice for only the most +# simple generative tasks, hence we need non-linear transformations. We +# will use ancillary qubits to help. +# +# For a given sub-generator, the pre-measurement quantum state is given +# by, +# +# .. math:: |\Psi(z)\rangle = U_{G}(\theta)|\boldsymbol{z}\rangle +# +# where :math:`U_{G}(\theta)` represents the overall unitary of the +# parameterised layers. Let us inspect the state when we take a partial +# measurment, :math:`\Pi,` and trace out the ancillary subsystem, +# :math:`\mathcal{A},` +# +# .. math:: \rho(\boldsymbol{z}) = \frac{\text{Tr}_{\mathcal{A}}(\Pi \otimes \mathbb{I} |\Psi(z)\rangle \langle \Psi(\boldsymbol{z})|) }{\text{Tr}(\Pi \otimes \mathbb{I} |\Psi(\boldsymbol{z})\rangle \langle \Psi(\boldsymbol{z})|))} = \frac{\text{Tr}_{\mathcal{A}}(\Pi \otimes \mathbb{I} |\Psi(\boldsymbol{z})\rangle \langle \Psi(\boldsymbol{z})|) }{\langle \Psi(\boldsymbol{z})| \Pi \otimes \mathbb{I} |\Psi(\boldsymbol{z})\rangle} +# +# The post-measurement state, :math:`\rho(\boldsymbol{z}),` is dependent +# on :math:`\boldsymbol{z}` in both the numerator and denominator. This +# means the state has been non-linearly transformed! For this tutorial, +# :math:`\Pi = (|0\rangle \langle0|)^{\otimes N_A},` where :math:`N_A` +# is the number of ancillary qubits in the system. +# +# With the remaining data qubits, we measure the probability of +# :math:`\rho(\boldsymbol{z})` in each computational basis state, +# :math:`P(j),` to obtain the sub-generator output, +# :math:`\boldsymbol{g}^{(i)},` +# +# .. math:: \boldsymbol{g}^{(i)} = [P(0), P(1), ... ,P(2^{N-N_A} - 1)] +# +# **4) Post Processing** +# +# +# Due to the normalisation constraint of the measurment, all elements in +# :math:`\boldsymbol{g}^{(i)}` must sum to one. This is +# a problem if we are to use :math:`\boldsymbol{g}^{(i)}` as the pixel +# intensity values for our patch. For example, imagine a hypothetical +# situation where a patch of full intensity pixels was the target. The +# best patch a sub-generator could produce would be a patch of pixels all +# at a magnitude of :math:`\frac{1}{2^{N-N_A}}.` To alleviate this +# constraint, we apply a post-processing technique to each patch, +# +# .. math:: \boldsymbol{\tilde{x}^{(i)}} = \frac{\boldsymbol{g}^{(i)}}{\max_{k}\boldsymbol{g}_k^{(i)}} +# +# Therefore, the final image, :math:`\boldsymbol{\tilde{x}},` is given by +# +# .. math:: \boldsymbol{\tilde{x}} = [\boldsymbol{\tilde{x}^{(1)}}, ... ,\boldsymbol{\tilde{x}^{(N_G)}}] +# + +# Quantum variables +n_qubits = 5 # Total number of qubits / N +n_a_qubits = 1 # Number of ancillary qubits / N_A +q_depth = 6 # Depth of the parameterised quantum circuit / D +n_generators = 4 # Number of subgenerators for the patch method / N_G + + +###################################################################### +# Now we define the quantum device we want to use, along with any +# available CUDA GPUs (if available). +# + +# Quantum simulator +dev = qml.device("lightning.qubit", wires=n_qubits) +# Enable CUDA device if available +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + +###################################################################### +# Next, we define the quantum circuit and measurement process described above. +@qml.qnode(dev, diff_method="parameter-shift") +def quantum_circuit(noise, weights): + + weights = weights.reshape(q_depth, n_qubits) + + # Initialise latent vectors + for i in range(n_qubits): + qml.RY(noise[i], wires=i) + + # Repeated layer + for i in range(q_depth): + # Parameterised layer + for y in range(n_qubits): + qml.RY(weights[i][y], wires=y) + + # Control Z gates + for y in range(n_qubits - 1): + qml.CZ(wires=[y, y + 1]) + + return qml.probs(wires=list(range(n_qubits))) + + +# For further info on how the non-linear transform is implemented in Pennylane +# https://discuss.pennylane.ai/t/ancillary-subsystem-measurement-then-trace-out/1532 +def partial_measure(noise, weights): + # Non-linear Transform + probs = quantum_circuit(noise, weights) + probsgiven0 = probs[: (2 ** (n_qubits - n_a_qubits))] + probsgiven0 /= torch.sum(probs) + + # Post-Processing + probsgiven = probsgiven0 / torch.max(probsgiven0) + return probsgiven + +###################################################################### +# Now we create a quantum generator class to use during training. + +class PatchQuantumGenerator(nn.Module): + """Quantum generator class for the patch method""" + + def __init__(self, n_generators, q_delta=1): + """ + Args: + n_generators (int): Number of sub-generators to be used in the patch method. + q_delta (float, optional): Spread of the random distribution for parameter initialisation. + """ + + super().__init__() + + self.q_params = nn.ParameterList( + [ + nn.Parameter(q_delta * torch.rand(q_depth * n_qubits), requires_grad=True) + for _ in range(n_generators) + ] + ) + self.n_generators = n_generators + + def forward(self, x): + # Size of each sub-generator output + patch_size = 2 ** (n_qubits - n_a_qubits) + + # Create a Tensor to 'catch' a batch of images from the for loop. x.size(0) is the batch size. + images = torch.Tensor(x.size(0), 0).to(device) + + # Iterate over all sub-generators + for params in self.q_params: + + # Create a Tensor to 'catch' a batch of the patches from a single sub-generator + patches = torch.Tensor(0, patch_size).to(device) + for elem in x: + q_out = partial_measure(elem, params).float().unsqueeze(0) + patches = torch.cat((patches, q_out)) + + # Each batch of patches is concatenated with each other to create a batch of images + images = torch.cat((images, patches), 1) + + return images + + +###################################################################### +# Training +# -------- +# + +###################################################################### +# Let's define learning rates and number of iterations for the training process. + +lrG = 0.3 # Learning rate for the generator +lrD = 0.01 # Learning rate for the discriminator +num_iter = 500 # Number of training iterations + +###################################################################### +# Now putting everything together and executing the training process. + +discriminator = Discriminator().to(device) +generator = PatchQuantumGenerator(n_generators).to(device) + +# Binary cross entropy +criterion = nn.BCELoss() + +# Optimisers +optD = optim.SGD(discriminator.parameters(), lr=lrD) +optG = optim.SGD(generator.parameters(), lr=lrG) + +real_labels = torch.full((batch_size,), 1.0, dtype=torch.float, device=device) +fake_labels = torch.full((batch_size,), 0.0, dtype=torch.float, device=device) + +# Fixed noise allows us to visually track the generated images throughout training +fixed_noise = torch.rand(8, n_qubits, device=device) * math.pi / 2 + +# Iteration counter +counter = 0 + +# Collect images for plotting later +results = [] + +while True: + for i, (data, _) in enumerate(dataloader): + + # Data for training the discriminator + data = data.reshape(-1, image_size * image_size) + real_data = data.to(device) + + # Noise follwing a uniform distribution in range [0,pi/2) + noise = torch.rand(batch_size, n_qubits, device=device) * math.pi / 2 + fake_data = generator(noise) + + # Training the discriminator + discriminator.zero_grad() + outD_real = discriminator(real_data).view(-1) + outD_fake = discriminator(fake_data.detach()).view(-1) + + errD_real = criterion(outD_real, real_labels) + errD_fake = criterion(outD_fake, fake_labels) + # Propagate gradients + errD_real.backward() + errD_fake.backward() + + errD = errD_real + errD_fake + optD.step() + + # Training the generator + generator.zero_grad() + outD_fake = discriminator(fake_data).view(-1) + errG = criterion(outD_fake, real_labels) + errG.backward() + optG.step() + + counter += 1 + + # Show loss values + if counter % 10 == 0: + print(f'Iteration: {counter}, Discriminator Loss: {errD:0.3f}, Generator Loss: {errG:0.3f}') + test_images = generator(fixed_noise).view(8,1,image_size,image_size).cpu().detach() + + # Save images every 50 iterations + if counter % 50 == 0: + results.append(test_images) + + if counter == num_iter: + break + if counter == num_iter: + break + + +###################################################################### +#.. note:: +# +# You may have noticed ``errG = criterion(outD_fake, real_labels)`` and +# wondered why we don’t use ``fake_labels`` instead of ``real_labels``. +# However, this is simply a trick to be able to use the same +# ``criterion`` function for both the generator and discriminator. +# Using ``real_labels`` forces the generator loss function to use the +# :math:`\log(D(G(z))` term instead of the :math:`\log(1 - D(G(z))` term +# of the binary cross entropy loss function. +# + +###################################################################### +# Finally, we plot how the generated images evolved throughout training. + +fig = plt.figure(figsize=(10, 5)) +outer = gridspec.GridSpec(5, 2, wspace=0.1) + +for i, images in enumerate(results): + inner = gridspec.GridSpecFromSubplotSpec(1, images.size(0), + subplot_spec=outer[i]) + + images = torch.squeeze(images, dim=1) + for j, im in enumerate(images): + + ax = plt.Subplot(fig, inner[j]) + ax.imshow(im.numpy(), cmap="gray") + ax.set_xticks([]) + ax.set_yticks([]) + if j==0: + ax.set_title(f'Iteration {50+i*50}', loc='left') + fig.add_subplot(ax) + +plt.show() + + +###################################################################### +# Acknowledgements +# ---------------- +# Many thanks to Karolis Špukas who I co-developed much of the code with. +# I also extend my thanks to Dr. Yuxuan Du for answering my questions +# regarding his paper. I am also indebited to the Pennylane community for +# their help over the past few years. +# +# +# References +# ---------- +# +# .. [#goodfellow2014] +# +# Ian J. Goodfellow et al. *Generative Adversarial Networks*. +# `arXiv:1406.2661 `__ (2014). +# +# .. [#huang2020] +# +# He-Liang Huang et al. *Experimental Quantum Generative Adversarial Networks for Image Generation*. +# `arXiv:2010.06201 `__ (2020). +# +# diff --git a/demonstrations_v2/tutorial_quantum_gans/metadata.json b/demonstrations_v2/tutorial_quantum_gans/metadata.json new file mode 100644 index 0000000000..88b84b3c82 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_gans/metadata.json @@ -0,0 +1,52 @@ +{ + "title": "Quantum GANs", + "authors": [ + { + "username": "jellis" + } + ], + "dateOfPublication": "2022-02-01T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_GANs.png" + } + ], + "seoDescription": "Explore quantum GANs to generate hand-written digits of zero", + "doi": "", + "references": [ + { + "id": "goodfellow2014", + "type": "article", + "title": "Generative Adversarial Networks", + "authors": "Ian J. Goodfellow et al.", + "year": "2014", + "journal": "", + "url": "https://arxiv.org/abs/1406.2661" + }, + { + "id": "huang2020", + "type": "article", + "title": "Experimental Quantum Generative Adversarial Networks for Image Generation", + "authors": "He-Liang Huang et al.", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2010.06201" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_QGAN", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/questions-on-qgan-tutorial/2607" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_gans/quantum_gans/optdigits.tra b/demonstrations_v2/tutorial_quantum_gans/quantum_gans/optdigits.tra new file mode 100644 index 0000000000..188f5d2148 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_gans/quantum_gans/optdigits.tra @@ -0,0 +1,3823 @@ +0,1,6,15,12,1,0,0,0,7,16,6,6,10,0,0,0,8,16,2,0,11,2,0,0,5,16,3,0,5,7,0,0,7,13,3,0,8,7,0,0,4,12,0,1,13,5,0,0,0,14,9,15,9,0,0,0,0,6,14,7,1,0,0,0 +0,0,10,16,6,0,0,0,0,7,16,8,16,5,0,0,0,11,16,0,6,14,3,0,0,12,12,0,0,11,11,0,0,12,12,0,0,8,12,0,0,7,15,1,0,13,11,0,0,0,16,8,10,15,3,0,0,0,10,16,15,3,0,0,0 +0,0,8,15,16,13,0,0,0,1,11,9,11,16,1,0,0,0,0,0,7,14,0,0,0,0,3,4,14,12,2,0,0,1,16,16,16,16,10,0,0,2,12,16,10,0,0,0,0,0,2,16,4,0,0,0,0,0,9,14,0,0,0,0,7 +0,0,0,3,11,16,0,0,0,0,5,16,11,13,7,0,0,3,15,8,1,15,6,0,0,11,16,16,16,16,10,0,0,1,4,4,13,10,2,0,0,0,0,0,15,4,0,0,0,0,0,3,16,0,0,0,0,0,0,1,15,2,0,0,4 +0,0,5,14,4,0,0,0,0,0,13,8,0,0,0,0,0,3,14,4,0,0,0,0,0,6,16,14,9,2,0,0,0,4,16,3,4,11,2,0,0,0,14,3,0,4,11,0,0,0,10,8,4,11,12,0,0,0,4,12,14,7,0,0,6 +0,0,11,16,10,1,0,0,0,4,16,10,15,8,0,0,0,4,16,3,11,13,0,0,0,1,14,6,9,14,0,0,0,0,0,0,12,10,0,0,0,0,0,6,16,6,0,0,0,0,5,15,15,8,8,3,0,0,10,16,16,16,16,6,2 +0,0,1,11,13,11,7,0,0,0,9,14,6,4,3,0,0,0,16,12,16,15,2,0,0,5,16,10,4,12,6,0,0,1,1,0,0,10,4,0,0,0,0,0,5,10,0,0,0,0,0,8,15,3,0,0,0,0,1,13,5,0,0,0,5 +0,0,8,10,8,7,2,0,0,1,15,14,12,12,4,0,0,7,15,12,5,0,0,0,0,5,14,12,15,7,0,0,0,0,0,0,2,13,0,0,0,0,0,0,4,12,0,0,0,0,6,7,14,5,0,0,0,0,4,13,8,0,0,0,5 +0,0,15,2,14,13,2,0,0,0,16,15,12,13,8,0,0,2,16,12,1,6,10,0,0,7,15,3,0,5,8,0,0,5,12,0,0,8,8,0,0,5,12,0,7,15,5,0,0,5,16,13,16,6,0,0,0,0,10,12,5,0,0,0,0 +0,0,3,13,13,2,0,0,0,6,16,12,10,8,0,0,0,9,15,12,16,6,0,0,0,10,16,16,13,0,0,0,0,1,12,16,12,14,4,0,0,0,11,8,0,3,12,0,0,0,13,11,8,13,12,0,0,0,3,15,11,6,0,0,8 +0,0,6,14,14,16,16,8,0,0,7,11,8,10,15,3,0,0,0,0,4,15,10,0,0,1,15,16,16,16,14,0,0,3,11,13,13,0,0,0,0,0,0,15,5,0,0,0,0,0,7,13,0,0,0,0,0,0,10,12,0,0,0,0,7 +0,0,0,3,16,11,1,0,0,0,0,8,16,16,1,0,0,0,0,9,16,14,0,0,0,1,7,16,16,11,0,0,0,9,16,16,16,8,0,0,0,1,8,6,16,7,0,0,0,0,0,5,16,9,0,0,0,0,0,2,14,14,1,0,1 +0,0,0,4,13,16,16,3,0,0,8,16,9,12,16,4,0,7,16,3,3,15,13,0,0,9,15,14,16,16,6,0,0,1,8,7,12,15,0,0,0,0,0,0,13,10,0,0,0,0,0,3,15,6,0,0,0,0,0,5,15,4,0,0,9 +0,0,7,12,6,2,0,0,0,0,16,16,13,14,1,0,0,9,16,11,3,0,0,0,0,8,16,16,16,4,0,0,0,1,2,0,6,12,0,0,0,0,0,0,7,12,0,0,0,0,6,9,16,6,0,0,0,0,5,16,9,0,0,0,5 +0,0,7,11,11,6,0,0,0,9,16,12,10,14,0,0,0,5,2,0,4,14,0,0,0,0,1,5,14,6,0,0,0,1,15,16,16,10,0,0,0,0,7,4,4,15,6,0,0,0,5,4,8,13,12,0,0,0,14,16,12,10,1,0,3 +0,1,10,15,8,0,0,0,0,6,16,7,11,8,0,0,0,7,16,3,1,13,1,0,0,7,13,0,0,10,6,0,0,8,12,0,0,14,4,0,0,3,16,0,6,15,2,0,0,0,15,9,16,4,0,0,0,0,9,15,8,0,0,0,0 +0,0,0,1,11,7,0,0,0,0,2,13,10,16,4,0,0,0,13,4,1,16,0,0,0,6,14,8,12,16,7,0,0,0,8,8,15,10,2,0,0,0,0,1,12,1,0,0,0,0,0,4,16,0,0,0,0,0,0,3,15,0,0,0,4 +0,0,5,12,16,16,3,0,0,0,11,11,4,16,9,0,0,0,0,4,8,16,5,0,0,0,4,16,16,16,14,0,0,0,0,11,14,1,0,0,0,0,0,13,10,0,0,0,0,0,3,16,1,0,0,0,0,0,8,12,0,0,0,0,7 +0,0,1,8,13,13,2,0,0,4,16,8,1,12,4,0,0,7,13,3,10,13,0,0,0,3,15,15,14,15,0,0,0,0,13,10,0,10,6,0,0,0,11,5,0,9,8,0,0,0,7,11,4,14,2,0,0,0,1,13,12,4,0,0,8 +0,0,0,2,13,12,4,0,0,0,3,15,15,13,12,0,0,2,15,14,1,12,8,0,0,8,16,14,16,16,11,0,0,3,16,16,16,16,10,0,0,0,0,0,8,13,0,0,0,0,0,0,13,7,0,0,0,0,0,0,15,3,0,0,4 +0,0,4,11,15,16,15,0,0,0,13,13,8,13,14,0,0,0,0,0,0,15,5,0,0,0,2,4,10,15,1,0,0,0,10,16,16,16,8,0,0,0,1,13,13,1,0,0,0,0,1,16,6,0,0,0,0,0,6,14,2,0,0,0,7 +0,0,4,10,13,11,1,0,0,2,13,10,4,8,8,0,0,6,13,4,9,15,4,0,0,4,16,16,16,13,0,0,0,0,12,11,1,8,8,0,0,0,12,4,0,7,8,0,0,0,12,8,8,14,0,0,0,0,6,13,11,1,0,0,8 +0,0,3,11,13,14,6,0,0,0,12,9,3,0,0,0,0,2,14,0,0,0,0,0,0,8,10,6,12,12,2,0,0,7,16,13,6,14,8,0,0,0,0,0,1,14,1,0,0,0,1,4,11,6,0,0,0,0,3,13,10,0,0,0,5 +0,0,1,4,11,13,7,0,0,2,14,12,10,16,5,0,0,7,14,6,14,12,0,0,0,2,12,11,10,15,1,0,0,0,0,0,0,16,4,0,0,0,0,0,3,16,3,0,0,0,0,0,10,11,0,0,0,0,0,1,14,3,0,0,9 +0,0,9,13,1,0,0,0,0,0,8,16,6,0,0,0,0,0,7,16,10,0,0,0,0,0,13,16,10,0,0,0,0,0,9,16,14,0,0,0,0,0,0,7,16,5,0,0,0,0,3,9,16,13,8,5,0,0,4,15,16,16,16,16,1 +0,0,9,16,11,0,0,0,0,4,16,13,16,4,0,0,0,0,9,3,13,9,0,0,0,0,0,0,16,8,0,0,0,0,0,3,16,4,0,0,0,0,0,12,15,1,0,0,0,0,6,16,16,15,11,1,0,0,10,16,9,9,13,6,2 +0,0,2,13,9,0,0,0,0,0,14,11,12,7,0,0,0,6,16,1,0,16,0,0,0,5,12,0,0,11,5,0,0,8,13,0,0,8,7,0,0,1,16,0,0,9,8,0,0,0,13,3,6,16,1,0,0,0,3,16,14,4,0,0,0 +0,0,0,10,12,0,0,0,0,0,9,14,4,0,0,0,0,0,15,3,0,0,0,0,0,2,15,2,6,1,0,0,0,2,16,15,12,15,4,0,0,0,16,5,0,3,14,0,0,0,12,10,4,11,14,0,0,0,1,11,14,12,1,0,6 +0,0,0,0,10,13,0,0,0,0,0,0,15,16,0,0,0,0,0,7,16,14,0,0,0,3,12,16,16,13,0,0,0,3,11,9,16,9,0,0,0,0,0,0,16,9,0,0,0,0,0,0,15,12,0,0,0,0,0,0,8,15,2,0,1 +0,0,7,9,13,11,2,0,0,6,16,9,1,13,8,0,0,8,14,5,11,14,2,0,0,3,16,16,16,6,0,0,0,2,16,5,1,12,5,0,0,4,15,0,0,8,8,0,0,3,16,4,7,13,2,0,0,0,13,12,8,1,0,0,8 +0,0,9,14,16,7,0,0,0,1,14,6,14,13,0,0,0,0,0,0,14,8,0,0,0,0,10,13,16,13,2,0,0,0,16,16,16,13,8,0,0,0,2,15,4,0,0,0,0,0,8,13,0,0,0,0,0,0,12,10,0,0,0,0,7 +0,0,8,16,8,0,0,0,0,1,16,9,10,9,0,0,0,4,15,0,0,10,1,0,0,6,12,0,0,6,6,0,0,5,12,0,0,4,8,0,0,4,14,0,0,7,8,0,0,1,16,9,8,14,4,0,0,0,6,15,14,5,0,0,0 +0,0,9,16,16,15,2,0,0,0,9,6,8,16,8,0,0,0,0,2,1,16,7,0,0,0,12,16,16,16,13,0,0,0,3,4,15,10,1,0,0,0,0,7,15,2,0,0,0,0,3,15,7,0,0,0,0,0,8,13,1,0,0,0,7 +0,0,2,14,10,0,0,0,0,1,14,12,0,0,0,0,0,5,14,1,0,0,0,0,0,6,11,0,0,0,0,0,0,7,15,13,15,7,1,0,0,3,15,8,0,11,10,0,0,0,11,9,4,8,15,0,0,0,1,12,14,12,4,0,6 +0,0,0,3,11,16,11,0,0,0,5,16,14,13,16,3,0,3,16,8,0,13,14,0,0,10,15,6,11,16,6,0,0,8,16,15,14,15,1,0,0,0,3,0,10,12,0,0,0,0,0,0,15,9,0,0,0,0,0,1,16,5,0,0,9 +0,0,0,0,13,16,3,0,0,0,0,1,15,16,0,0,0,2,5,13,16,14,0,0,0,10,16,15,16,12,0,0,0,1,4,5,16,12,0,0,0,0,0,1,16,14,0,0,0,0,0,0,16,15,0,0,0,0,0,0,11,16,8,0,1 +0,0,4,14,16,16,12,0,0,0,12,9,0,5,16,3,0,1,15,14,10,13,12,0,0,8,16,16,16,9,3,0,0,1,4,8,15,0,0,0,0,0,0,8,13,0,0,0,0,0,1,14,9,0,0,0,0,0,6,15,4,0,0,0,9 +0,0,6,12,11,3,0,0,0,0,16,9,16,7,0,0,0,0,0,10,13,0,0,0,0,0,14,16,16,10,1,0,0,0,8,2,3,15,4,0,0,0,1,0,0,12,6,0,0,8,15,6,9,16,3,0,0,0,7,12,13,6,0,0,3 +0,0,0,3,10,15,9,4,0,0,2,15,7,9,16,6,0,0,11,6,0,10,16,1,0,1,15,14,16,16,7,0,0,0,5,7,2,14,2,0,0,0,0,0,10,6,0,0,0,0,0,1,15,0,0,0,0,0,0,7,9,0,0,0,9 +0,0,0,2,10,16,12,2,0,0,2,14,12,7,16,3,0,3,15,15,4,10,15,0,0,6,16,16,16,16,14,0,0,0,3,3,4,16,6,0,0,0,0,0,6,16,2,0,0,0,0,0,10,14,0,0,0,0,0,0,14,9,0,0,4 +0,0,8,14,9,2,0,0,0,2,14,5,11,13,0,0,0,0,15,0,6,16,4,0,0,0,13,9,15,15,6,0,0,0,0,5,1,7,9,0,0,0,0,0,0,5,12,0,0,0,12,3,0,9,8,0,0,0,7,14,15,13,0,0,9 +0,0,6,16,12,2,0,0,0,6,16,9,11,11,0,0,0,7,14,0,5,14,0,0,0,3,6,0,7,11,0,0,0,0,0,0,14,7,0,0,0,0,0,8,15,0,0,0,0,0,4,16,10,4,3,0,0,0,7,16,13,14,16,3,2 +0,0,8,14,2,0,0,0,0,0,5,16,6,0,0,0,0,0,0,16,11,0,0,0,0,0,2,16,15,0,0,0,0,0,0,14,16,2,0,0,0,0,0,1,16,9,0,0,0,0,5,15,16,15,12,8,0,0,4,15,16,13,12,12,1 +0,0,0,4,11,16,11,0,0,0,6,13,3,3,15,0,0,0,14,2,4,14,11,0,0,0,12,16,15,15,8,0,0,0,0,2,0,14,2,0,0,0,0,0,4,11,0,0,0,0,0,0,14,4,0,0,0,0,0,3,10,0,0,0,9 +0,0,0,0,9,15,12,0,0,0,2,14,9,6,16,1,0,0,12,4,1,12,11,0,0,4,15,7,13,16,3,0,0,2,12,11,1,15,0,0,0,0,0,0,5,11,0,0,0,0,0,0,8,8,0,0,0,0,0,0,9,7,0,0,9 +0,0,2,13,5,0,0,0,0,0,11,15,0,0,0,0,0,1,16,2,0,0,0,0,0,2,16,8,6,2,0,0,0,5,16,9,10,15,2,0,0,0,16,4,0,9,8,0,0,0,11,10,3,16,6,0,0,0,4,15,12,9,1,0,6 +0,0,0,1,10,16,4,0,0,0,1,13,16,16,3,0,0,0,11,15,9,16,3,0,0,11,16,9,14,13,0,0,0,10,16,16,16,16,9,0,0,1,4,4,16,9,2,0,0,0,0,0,14,5,0,0,0,0,0,0,13,9,0,0,4 +0,0,4,13,16,8,0,0,0,6,16,9,11,12,0,0,0,9,11,4,16,6,0,0,0,0,8,15,16,12,1,0,0,0,2,6,1,13,8,0,0,0,9,1,0,9,8,0,0,0,13,8,9,15,4,0,0,0,6,14,12,5,0,0,3 +0,1,14,14,4,0,0,0,0,10,15,15,15,0,0,0,0,14,10,4,16,3,0,0,0,2,1,9,16,0,0,0,0,0,0,13,13,0,0,0,0,0,3,16,5,0,0,0,0,0,13,16,13,5,4,0,0,2,15,14,16,16,16,5,2 +0,2,7,14,11,1,0,0,0,8,15,3,7,8,0,0,0,10,10,1,12,8,0,0,0,4,16,16,15,6,0,0,0,2,16,6,0,11,7,0,0,1,16,0,0,4,13,0,0,0,16,2,5,14,4,0,0,0,7,16,14,6,0,0,8 +0,1,14,15,5,0,0,0,0,3,16,15,15,3,0,0,0,3,16,9,16,5,0,0,0,0,5,6,16,4,0,0,0,0,0,8,15,1,0,0,0,0,0,14,11,0,0,0,0,0,9,16,14,8,7,0,0,0,15,16,16,16,16,6,2 +0,0,1,8,14,14,6,0,0,0,3,11,7,12,14,0,0,0,0,0,0,9,11,0,0,1,12,12,12,15,8,0,0,0,7,8,14,13,1,0,0,0,0,4,15,2,0,0,0,0,0,13,6,0,0,0,0,0,1,14,2,0,0,0,7 +0,0,0,1,15,9,1,0,0,0,0,5,16,16,0,0,0,0,0,13,16,11,0,0,0,2,8,16,16,11,0,0,0,5,12,13,16,8,0,0,0,0,0,5,16,7,0,0,0,0,0,7,16,4,0,0,0,0,0,3,16,15,1,0,1 +0,0,0,4,15,6,0,0,0,0,3,15,9,4,14,0,0,2,15,9,0,11,10,0,0,7,16,10,12,16,14,0,0,3,14,13,15,14,2,0,0,0,0,0,12,10,0,0,0,0,0,2,16,6,0,0,0,0,0,6,13,1,0,0,4 +0,0,2,13,6,0,0,0,0,0,10,14,6,0,0,0,0,0,15,6,0,0,0,0,0,0,16,3,0,0,0,0,0,0,14,16,16,12,1,0,0,2,16,13,3,9,10,0,0,0,8,14,4,13,13,0,0,0,2,12,14,12,4,0,6 +0,0,11,16,6,0,0,0,0,2,16,14,14,1,0,0,0,6,16,2,15,6,0,0,0,2,11,0,15,5,0,0,0,0,0,0,16,5,0,0,0,0,0,4,16,2,0,0,0,0,6,15,15,8,8,3,0,0,12,16,15,16,16,6,2 +0,0,7,14,6,0,0,0,0,5,16,9,13,3,0,0,0,8,12,0,5,12,0,0,0,4,12,0,0,11,6,0,0,6,12,0,0,9,7,0,0,1,15,0,1,15,5,0,0,0,13,7,13,11,0,0,0,0,4,15,11,0,0,0,0 +0,0,0,0,8,12,5,0,0,0,0,10,11,11,9,0,0,0,11,10,0,16,1,0,0,6,16,13,12,15,4,0,0,2,8,8,12,14,4,0,0,0,0,0,12,4,0,0,0,0,0,0,13,1,0,0,0,0,0,0,11,0,0,0,4 +0,0,1,12,11,0,0,0,0,0,8,14,3,0,0,0,0,1,14,5,0,0,0,0,0,1,16,9,4,0,0,0,0,3,16,13,12,12,3,0,0,1,16,3,0,1,15,0,0,0,9,9,2,9,15,2,0,0,2,14,15,12,3,0,6 +0,2,11,15,16,8,0,0,0,4,9,1,5,16,4,0,0,0,0,5,13,13,0,0,0,0,4,16,16,5,0,0,0,0,0,2,7,14,2,0,0,0,2,0,0,11,7,0,0,6,10,1,8,14,2,0,0,3,14,15,11,3,0,0,3 +0,0,5,12,14,16,5,0,0,6,16,15,10,16,6,0,0,0,3,0,3,16,2,0,0,0,1,4,14,13,2,0,0,7,16,16,16,16,11,0,0,4,9,14,12,1,1,0,0,0,0,16,8,0,0,0,0,0,4,16,5,0,0,0,7 +0,0,0,0,13,5,0,0,0,0,0,3,16,13,0,0,0,0,0,10,16,12,0,0,0,6,16,16,16,8,0,0,0,4,8,6,16,5,0,0,0,0,0,4,16,4,0,0,0,0,0,4,16,8,0,0,0,0,0,0,12,13,5,0,1 +0,0,10,8,11,2,0,0,0,0,16,15,8,13,2,0,0,4,16,4,0,9,7,0,0,7,14,0,0,3,8,0,0,8,12,0,0,7,8,0,0,6,6,0,3,15,4,0,0,4,14,10,16,7,0,0,0,0,10,14,5,0,0,0,0 +0,1,9,13,9,0,0,0,0,3,16,10,11,11,0,0,0,1,9,0,2,15,0,0,0,0,0,0,4,15,0,0,0,0,0,1,11,8,0,0,0,0,0,10,15,1,0,0,0,0,6,16,16,13,9,0,0,0,11,12,8,8,8,1,2 +0,3,12,16,10,0,0,0,0,8,16,10,16,6,0,0,0,2,9,0,12,8,0,0,0,0,0,0,15,9,0,0,0,0,0,9,14,1,0,0,0,0,4,16,9,0,0,0,0,1,14,16,13,12,9,1,0,4,16,16,13,15,16,3,2 +0,0,10,16,16,16,4,0,0,2,14,8,1,1,0,0,0,7,15,12,12,7,0,0,0,3,9,7,5,16,6,0,0,0,0,0,0,5,8,0,0,0,0,0,0,13,7,0,0,0,11,4,13,12,0,0,0,0,9,13,9,0,0,0,5 +0,0,8,15,5,0,0,0,0,4,15,12,16,0,0,0,0,10,9,0,12,4,0,0,0,3,1,0,13,3,0,0,0,0,0,1,16,0,0,0,0,0,0,6,11,0,0,0,0,0,5,16,16,16,15,0,0,0,9,10,4,6,9,0,2 +0,0,0,0,11,2,0,0,0,0,0,8,12,0,0,0,0,0,2,15,4,4,8,0,0,1,13,8,0,10,10,0,0,9,16,8,9,16,7,0,0,6,12,12,13,14,1,0,0,0,0,0,14,8,0,0,0,0,0,3,16,4,0,0,4 +0,0,5,12,16,12,0,0,0,2,16,10,8,16,4,0,0,7,16,8,2,16,4,0,0,0,5,15,16,10,0,0,0,0,0,12,16,8,0,0,0,0,6,12,5,16,4,0,0,0,8,12,6,16,6,0,0,0,5,15,15,9,0,0,8 +0,0,0,6,14,11,1,0,0,0,2,14,16,16,2,0,0,0,11,16,16,16,0,0,0,5,15,16,16,16,4,0,0,1,5,7,16,16,2,0,0,0,0,3,16,16,3,0,0,0,0,4,16,16,4,0,0,0,0,4,15,15,4,0,1 +0,0,5,10,16,15,6,0,0,0,12,9,8,15,12,0,0,0,0,0,1,16,5,0,0,2,15,12,13,15,2,0,0,2,11,11,15,10,5,0,0,0,0,11,7,0,0,0,0,0,4,16,1,0,0,0,0,0,9,12,0,0,0,0,7 +0,0,10,13,15,12,5,0,0,0,9,5,5,13,7,0,0,0,0,1,11,13,0,0,0,0,5,16,16,3,0,0,0,0,2,10,12,15,7,0,0,0,0,0,1,16,4,0,0,0,2,8,13,12,1,0,0,0,10,15,7,0,0,0,3 +0,0,2,12,11,2,0,0,0,1,15,12,9,13,0,0,0,6,13,1,0,14,2,0,0,6,12,0,0,8,8,0,0,8,12,0,0,7,8,0,0,5,15,0,0,7,9,0,0,0,13,9,8,15,4,0,0,0,3,13,16,8,0,0,0 +0,0,5,13,14,5,0,0,0,5,16,12,12,16,0,0,0,1,3,0,11,14,0,0,0,0,0,9,16,4,0,0,0,0,2,16,16,9,1,0,0,0,0,3,5,16,7,0,0,0,2,4,9,16,5,0,0,0,5,13,14,6,0,0,3 +0,0,6,9,13,11,0,0,0,10,14,8,9,16,4,0,0,0,0,0,1,15,6,0,0,0,0,3,14,14,1,0,0,0,7,16,16,16,4,0,0,0,1,1,0,12,9,0,0,0,1,2,5,14,10,0,0,0,7,16,13,10,1,0,3 +0,0,7,11,16,7,0,0,0,1,12,12,13,16,0,0,0,0,0,0,6,16,0,0,0,4,13,10,15,13,2,0,0,1,8,14,16,12,4,0,0,0,2,15,7,0,0,0,0,0,9,15,0,0,0,0,0,0,12,7,0,0,0,0,7 +0,1,9,15,16,8,0,0,0,7,16,12,16,8,0,0,0,0,2,0,16,8,0,0,0,1,8,12,16,10,1,0,0,4,16,16,16,16,11,0,0,0,12,16,3,4,2,0,0,0,9,16,0,0,0,0,0,0,14,11,0,0,0,0,7 +0,0,1,9,13,11,3,0,0,0,13,12,8,10,12,0,0,5,13,1,8,15,10,0,0,6,16,16,15,16,6,0,0,0,5,5,5,14,0,0,0,0,0,1,12,7,0,0,0,0,0,8,15,0,0,0,0,0,0,15,7,0,0,0,9 +0,0,0,4,13,12,2,0,0,0,1,15,16,16,3,0,0,0,7,16,16,16,2,0,0,1,14,16,16,16,3,0,0,7,16,16,16,16,0,0,0,0,0,11,16,13,0,0,0,0,0,8,16,15,3,0,0,0,0,6,16,12,2,0,1 +0,0,6,13,15,4,0,0,0,5,16,9,8,12,0,0,0,4,4,0,10,10,0,0,0,0,1,10,16,10,1,0,0,0,3,11,8,15,8,0,0,0,0,0,0,11,7,0,0,0,4,4,8,15,1,0,0,0,4,15,15,1,0,0,3 +0,0,7,13,16,9,0,0,0,0,11,8,9,16,5,0,0,0,0,0,7,15,0,0,0,0,0,13,16,5,0,0,0,0,0,10,12,16,3,0,0,0,0,0,3,16,2,0,0,0,2,7,14,7,0,0,0,0,10,10,2,0,0,0,3 +0,0,0,2,14,2,0,0,0,0,0,11,13,0,0,0,0,0,8,15,3,2,10,0,0,2,16,9,0,12,15,0,0,13,16,9,12,16,9,0,0,12,16,16,16,16,2,0,0,0,4,3,14,14,0,0,0,0,0,0,15,13,0,0,4 +0,3,9,14,16,13,1,0,0,7,13,9,10,16,4,0,0,0,1,1,12,12,1,0,0,0,0,14,16,4,0,0,0,0,0,8,14,16,2,0,0,0,0,0,3,16,8,0,0,0,3,9,15,12,1,0,0,2,16,13,7,0,0,0,3 +0,0,0,4,16,4,0,0,0,0,0,14,14,2,0,0,0,0,7,16,5,1,3,0,0,4,16,11,1,13,11,0,0,13,16,13,13,16,8,0,0,9,16,16,16,16,4,0,0,0,0,4,16,7,0,0,0,0,0,5,16,9,0,0,4 +0,0,12,16,7,0,0,0,0,1,16,9,15,2,0,0,0,0,3,0,12,4,0,0,0,0,0,0,13,4,0,0,0,0,0,7,15,1,0,0,0,1,11,16,7,0,0,0,0,7,16,16,11,6,2,0,0,1,8,11,12,13,7,0,2 +0,0,6,13,16,10,1,0,0,0,15,7,5,13,7,0,0,7,15,1,1,14,6,0,0,1,12,13,14,9,0,0,0,0,0,15,16,3,0,0,0,0,7,13,8,15,0,0,0,0,12,8,7,16,1,0,0,0,5,16,16,5,0,0,8 +0,0,4,12,16,15,5,0,0,0,13,16,16,16,7,0,0,4,16,13,14,11,0,0,0,6,16,16,14,1,0,0,0,0,9,16,12,1,0,0,0,0,12,12,15,7,0,0,0,0,13,10,13,12,0,0,0,0,3,15,13,4,0,0,8 +0,0,3,12,13,10,1,0,0,2,13,9,7,14,8,0,0,8,12,0,0,13,8,0,0,4,15,13,14,16,1,0,0,0,1,4,14,9,0,0,0,0,0,6,14,1,0,0,0,0,2,16,5,0,0,0,0,0,2,16,1,0,0,0,9 +0,0,1,11,12,4,0,0,0,1,11,12,9,15,5,0,0,7,16,4,3,15,7,0,0,3,15,13,15,11,0,0,0,0,3,16,13,14,0,0,0,0,8,9,0,14,7,0,0,0,11,9,9,15,6,0,0,0,2,12,14,8,0,0,8 +0,0,0,4,13,0,0,0,0,0,0,15,8,2,5,0,0,0,11,9,0,12,8,0,0,4,14,1,0,14,5,0,0,8,14,6,7,16,6,0,0,2,11,13,15,13,2,0,0,0,0,1,15,3,0,0,0,0,0,4,14,0,0,0,4 +0,0,12,15,15,12,1,0,0,0,3,4,5,15,7,0,0,0,0,0,5,14,0,0,0,1,11,12,14,11,0,0,0,0,4,11,16,16,7,0,0,0,0,13,7,0,0,0,0,0,4,14,1,0,0,0,0,0,12,5,0,0,0,0,7 +0,0,2,10,12,4,0,0,0,0,10,16,16,16,0,0,0,0,13,16,16,12,0,0,0,0,12,16,16,12,0,0,0,0,12,16,16,12,0,0,0,0,12,16,16,13,0,0,0,0,8,16,16,15,0,0,0,0,4,10,8,3,0,0,1 +0,0,0,10,8,0,0,0,0,0,3,16,7,0,0,0,0,0,10,9,0,0,0,0,0,0,14,9,7,3,0,0,0,1,16,16,14,16,5,0,0,1,15,10,0,3,14,1,0,0,8,12,5,5,15,4,0,0,1,9,15,16,11,0,6 +0,0,1,10,16,7,0,0,0,1,14,14,12,16,4,0,0,6,16,2,1,16,4,0,0,6,16,11,13,16,2,0,0,0,11,12,16,11,0,0,0,0,0,3,16,5,0,0,0,0,0,11,14,0,0,0,0,0,0,13,11,0,0,0,9 +0,0,0,0,10,0,0,0,0,0,0,6,13,0,0,0,0,0,1,13,5,2,2,0,0,0,11,9,0,14,7,0,0,5,16,1,8,16,2,0,0,3,16,14,16,15,5,0,0,0,0,0,16,8,0,0,0,0,0,0,15,8,0,0,4 +0,0,0,8,12,2,0,0,0,0,9,15,8,12,0,0,0,5,16,3,0,12,2,0,0,5,13,0,0,5,7,0,0,8,10,0,0,4,8,0,0,0,16,0,0,3,12,0,0,0,11,9,4,14,6,0,0,0,0,10,13,9,1,0,0 +0,0,0,3,15,11,4,0,0,0,0,13,16,15,0,0,0,0,7,16,16,12,0,0,0,6,16,16,16,12,0,0,0,5,10,5,16,13,0,0,0,0,0,4,16,16,2,0,0,0,0,6,16,16,2,0,0,0,0,3,13,12,1,0,1 +0,1,8,13,16,13,0,0,0,5,14,7,8,15,0,0,0,0,0,2,13,8,0,0,0,0,0,13,16,13,1,0,0,0,0,2,5,14,7,0,0,0,0,0,0,13,7,0,0,0,2,6,12,13,1,0,0,0,7,13,7,1,0,0,3 +0,0,0,7,13,4,0,0,0,0,8,15,7,2,0,0,0,2,15,5,0,0,0,0,0,5,15,0,0,0,0,0,0,5,16,16,15,12,4,0,0,2,15,11,4,10,12,0,0,0,7,13,5,12,13,0,0,0,1,8,13,12,3,0,6 +0,0,2,14,3,0,0,0,0,0,13,16,12,0,0,0,0,8,12,2,12,0,0,0,0,6,8,0,12,0,0,0,0,0,0,0,15,0,0,0,0,0,1,5,15,0,0,0,0,0,7,16,16,14,9,0,0,0,2,12,12,12,11,0,2 +0,0,0,2,13,1,0,0,0,0,1,15,11,0,0,0,0,0,8,15,2,2,0,0,0,1,16,7,3,16,3,0,0,7,16,10,10,16,4,0,0,5,12,13,16,15,2,0,0,0,0,1,14,10,0,0,0,0,0,0,15,11,0,0,4 +0,0,11,16,16,16,3,0,0,1,12,12,13,16,5,0,0,0,0,0,7,16,2,0,0,0,5,13,16,15,1,0,0,0,12,16,16,16,10,0,0,0,1,13,10,4,1,0,0,0,8,16,2,0,0,0,0,0,15,11,0,0,0,0,7 +0,0,0,7,15,1,0,0,0,0,2,14,11,0,0,0,0,0,8,16,4,1,7,0,0,5,16,9,0,12,15,0,0,11,16,14,12,16,9,0,0,6,12,12,16,16,1,0,0,0,0,5,16,8,0,0,0,0,0,10,12,0,0,0,4 +0,0,2,13,9,0,0,0,0,2,16,15,14,6,0,0,0,8,16,5,3,15,0,0,0,7,16,1,0,11,7,0,0,6,12,0,0,8,8,0,0,0,16,2,0,7,10,0,0,0,12,12,7,15,5,0,0,0,0,13,16,9,0,0,0 +0,2,14,16,14,2,0,0,0,9,15,9,16,8,0,0,0,1,3,1,16,6,0,0,0,0,0,5,16,3,0,0,0,0,0,13,13,0,0,0,0,0,7,16,3,0,0,0,0,2,15,16,10,12,6,0,0,4,16,16,16,16,11,0,2 +0,0,8,15,16,14,5,0,0,5,16,4,6,16,8,0,0,7,16,4,9,15,3,0,0,0,12,16,16,3,0,0,0,0,3,15,16,9,0,0,0,0,13,6,6,16,4,0,0,0,16,8,8,16,6,0,0,0,11,16,13,8,0,0,8 +0,4,14,14,16,16,4,0,0,5,16,11,5,4,0,0,0,4,16,4,0,0,0,0,0,6,16,16,16,11,0,0,0,4,11,6,7,16,4,0,0,0,0,0,4,16,4,0,0,0,1,7,15,12,0,0,0,4,16,12,5,0,0,0,5 +0,0,0,9,15,10,1,0,0,0,4,15,16,16,4,0,0,4,15,16,16,16,4,0,0,8,15,10,16,16,8,0,0,0,0,0,16,16,7,0,0,0,0,1,16,16,6,0,0,0,0,7,16,16,4,0,0,0,0,6,12,12,1,0,1 +0,0,5,16,10,1,0,0,0,11,14,13,14,12,0,0,0,12,16,4,3,15,5,0,0,11,12,1,0,7,9,0,0,9,10,0,0,3,14,0,0,6,14,0,0,9,16,0,0,0,14,9,9,16,11,0,0,0,5,15,16,15,1,0,0 +0,0,0,0,11,15,2,0,0,0,0,7,16,16,7,0,0,0,3,15,16,16,4,0,0,6,16,16,16,16,3,0,0,3,8,6,16,16,0,0,0,0,0,4,16,16,0,0,0,0,0,4,16,16,4,0,0,0,0,1,13,16,3,0,1 +0,0,3,12,15,4,0,0,0,0,14,12,13,14,2,0,0,1,16,0,8,16,10,0,0,5,15,13,16,16,8,0,0,1,8,9,11,16,5,0,0,0,0,2,15,10,0,0,0,0,0,10,14,2,0,0,0,0,3,14,5,0,0,0,9 +0,0,0,10,13,1,0,0,0,0,8,16,12,2,0,0,0,1,15,10,0,0,0,0,0,6,16,7,5,0,0,0,0,2,16,16,16,15,3,0,0,2,16,12,0,7,13,0,0,0,11,10,4,10,16,2,0,0,2,11,15,14,9,0,6 +0,0,1,8,16,14,1,0,0,0,11,13,7,15,7,0,0,2,15,2,0,9,12,0,0,6,15,9,13,16,8,0,0,0,6,12,13,16,2,0,0,0,0,0,12,10,0,0,0,0,0,8,15,1,0,0,0,0,0,13,7,0,0,0,9 +0,0,2,12,15,2,0,0,0,0,8,14,4,1,0,0,0,0,15,7,0,0,0,0,0,2,16,6,4,2,0,0,0,3,16,16,15,15,4,0,0,2,15,11,0,4,14,0,0,0,11,9,0,3,16,0,0,0,2,13,16,16,12,0,6 +0,0,4,12,14,9,1,0,0,1,16,13,9,16,7,0,0,7,16,1,4,16,4,0,0,8,13,4,12,16,4,0,0,2,14,14,16,9,0,0,0,0,0,7,16,2,0,0,0,0,1,14,11,0,0,0,0,0,4,16,5,0,0,0,9 +0,1,11,13,13,12,7,0,0,2,16,6,4,10,9,0,0,2,16,2,0,0,0,0,0,4,16,16,16,4,0,0,0,1,10,5,7,12,0,0,0,0,0,0,5,11,0,0,0,0,1,5,14,3,0,0,0,0,15,14,7,0,0,0,5 +0,0,0,11,11,0,0,0,0,0,3,16,7,8,11,0,0,0,14,14,0,16,12,0,0,7,16,7,9,16,9,0,0,8,16,16,16,16,11,0,0,1,4,10,16,8,0,0,0,0,0,9,15,0,0,0,0,0,0,9,11,0,0,0,4 +0,0,11,10,8,14,10,0,0,1,16,14,12,12,7,0,0,0,16,8,0,0,0,0,0,5,16,16,10,2,0,0,0,2,8,8,12,15,1,0,0,0,0,0,5,16,3,0,0,0,5,9,16,10,0,0,0,2,12,12,4,0,0,0,5 +0,0,0,7,14,15,3,0,0,0,11,16,9,15,10,0,0,5,14,3,4,14,10,0,0,7,15,10,16,16,4,0,0,2,11,11,10,16,1,0,0,0,0,0,11,11,0,0,0,0,0,5,16,7,0,0,0,0,0,10,15,0,0,0,9 +0,1,13,11,11,13,5,0,0,8,16,9,8,8,1,0,0,4,16,0,0,0,0,0,0,4,16,16,14,6,0,0,0,2,11,8,9,16,4,0,0,0,0,0,0,12,8,0,0,0,1,5,8,15,6,0,0,2,14,12,12,6,0,0,5 +0,1,13,16,16,16,4,0,0,0,11,12,13,16,4,0,0,0,0,0,13,11,0,0,0,0,11,16,16,16,7,0,0,0,11,15,14,13,8,0,0,0,0,16,7,0,0,0,0,0,9,16,1,0,0,0,0,0,14,10,0,0,0,0,7 +0,0,0,0,13,12,1,0,0,0,0,5,16,16,1,0,0,0,0,14,16,14,0,0,0,0,7,16,16,10,0,0,0,5,16,16,16,14,0,0,0,2,6,9,16,16,1,0,0,0,0,2,16,16,7,0,0,0,0,0,8,12,6,0,1 +0,0,4,10,16,9,0,0,0,4,15,9,8,12,0,0,0,4,4,0,8,10,0,0,0,0,0,6,16,13,1,0,0,0,0,9,10,12,7,0,0,0,0,0,0,10,7,0,0,0,1,6,10,16,3,0,0,0,4,14,10,2,0,0,3 +0,0,5,9,12,12,2,0,0,0,7,10,10,16,5,0,0,0,0,0,6,13,0,0,0,0,7,9,14,14,6,0,0,0,8,14,15,8,2,0,0,0,0,11,7,0,0,0,0,0,6,15,0,0,0,0,0,0,11,7,0,0,0,0,7 +0,0,4,11,15,4,0,0,0,3,15,15,10,16,0,0,0,8,15,2,0,14,4,0,0,7,13,0,0,10,8,0,0,7,12,0,0,13,4,0,0,1,15,4,1,15,2,0,0,0,10,12,11,12,0,0,0,0,2,11,13,2,0,0,0 +0,0,8,13,7,0,0,0,0,5,16,9,15,6,0,0,0,5,8,0,7,13,0,0,0,2,4,0,1,15,0,0,0,0,0,0,4,13,0,0,0,0,0,0,10,10,0,0,0,0,5,12,16,9,8,1,0,0,8,13,14,15,16,4,2 +0,0,0,11,13,1,0,0,0,2,7,16,11,11,0,0,0,8,16,8,0,9,1,0,0,7,12,7,0,4,5,0,0,3,9,0,0,4,9,0,0,1,12,1,0,10,7,0,0,0,7,11,10,15,0,0,0,0,1,11,13,5,0,0,0 +0,0,0,2,14,8,0,0,0,0,0,8,14,2,6,0,0,0,3,15,7,6,16,2,0,1,13,12,1,12,11,0,0,11,16,16,16,16,10,0,0,5,10,8,13,15,3,0,0,0,0,0,14,12,0,0,0,0,0,4,16,7,0,0,4 +0,0,1,12,16,12,2,0,0,0,8,12,11,16,4,0,0,5,16,3,12,13,1,0,0,2,15,15,15,3,0,0,0,0,5,16,15,6,0,0,0,0,6,13,8,16,6,0,0,0,6,13,4,13,12,0,0,0,1,14,16,11,2,0,8 +0,0,9,13,6,0,0,0,0,5,16,9,14,1,0,0,0,9,9,0,11,5,0,0,0,1,1,0,10,6,0,0,0,0,0,0,15,4,0,0,0,0,0,5,15,0,0,0,0,0,5,15,11,8,8,0,0,0,8,16,12,12,15,4,2 +0,0,6,15,16,11,3,0,0,0,12,8,8,14,9,0,0,0,0,0,3,16,4,0,0,0,0,5,16,13,1,0,0,0,0,8,12,14,7,0,0,0,0,0,3,15,2,0,0,0,2,9,15,8,0,0,0,0,10,9,2,0,0,0,3 +0,0,6,7,8,9,9,0,0,5,16,16,14,13,3,0,0,5,10,7,3,0,0,0,0,5,16,16,13,0,0,0,0,0,0,0,12,1,0,0,0,0,0,0,13,1,0,0,0,0,8,11,12,0,0,0,0,0,10,11,2,0,0,0,5 +0,1,8,14,16,6,0,0,0,9,15,10,13,10,0,0,0,1,2,7,16,5,0,0,0,0,4,16,9,0,0,0,0,0,4,16,11,2,0,0,0,0,0,4,13,16,9,0,0,1,8,4,4,13,13,0,0,0,9,14,16,13,7,0,3 +0,2,12,13,12,2,0,0,0,2,7,5,15,7,0,0,0,0,0,9,16,2,0,0,0,4,16,14,3,0,0,0,0,2,12,16,14,5,0,0,0,0,0,1,6,15,4,0,0,0,1,0,2,13,6,0,0,4,13,15,16,12,1,0,3 +0,0,0,6,14,0,0,0,0,0,3,15,9,0,0,0,0,0,14,14,0,7,10,0,0,8,16,8,0,14,15,1,0,12,16,12,10,16,10,0,0,5,14,16,16,15,2,0,0,0,0,4,16,6,0,0,0,0,0,8,14,0,0,0,4 +0,0,7,15,11,0,0,0,0,9,10,14,15,12,0,0,0,9,12,0,0,4,1,0,0,7,16,14,9,0,0,0,0,0,7,13,16,6,0,0,0,0,0,0,12,9,0,0,0,0,11,10,16,8,0,0,0,0,5,16,14,3,0,0,5 +0,1,12,14,4,0,0,0,0,7,15,7,9,0,0,0,0,6,15,1,3,0,0,0,0,1,14,9,5,9,6,0,0,0,3,16,15,5,2,0,0,2,15,13,15,0,0,0,0,7,14,4,14,3,0,0,0,0,11,14,11,0,0,0,8 +0,0,1,7,16,2,0,0,0,0,14,13,12,10,1,0,0,3,14,0,3,16,4,0,0,1,15,16,15,16,0,0,0,0,2,7,2,10,3,0,0,0,0,0,0,11,2,0,0,0,0,9,2,10,4,0,0,0,0,9,16,12,1,0,9 +0,0,0,11,6,0,0,0,0,0,0,15,10,0,0,0,0,0,5,16,2,0,0,0,0,0,8,13,0,0,0,0,0,0,10,12,5,7,1,0,0,0,9,16,12,12,15,2,0,0,7,15,6,9,15,8,0,0,1,10,15,15,10,0,6 +0,0,5,11,8,4,0,0,0,0,15,16,16,15,1,0,0,0,12,16,16,15,2,0,0,3,16,16,16,11,0,0,0,4,16,16,16,4,0,0,0,3,16,16,16,8,0,0,0,0,12,16,16,9,0,0,0,0,3,8,12,8,0,0,1 +0,2,15,15,5,0,0,0,0,4,13,10,16,0,0,0,0,0,4,4,16,0,0,0,0,0,0,2,15,0,0,0,0,0,0,9,10,0,0,0,0,0,3,15,3,0,0,0,0,1,13,14,8,8,6,0,0,2,13,12,10,9,6,0,2 +0,0,9,15,3,0,0,0,0,1,15,16,15,5,0,0,0,3,16,8,9,14,0,0,0,4,13,0,0,10,4,0,0,3,13,0,0,7,6,0,0,2,12,0,0,10,10,0,0,1,12,9,13,16,6,0,0,0,7,16,16,8,0,0,0 +0,0,7,11,11,2,0,0,0,0,16,16,15,13,0,0,0,3,15,7,0,10,3,0,0,3,12,0,0,5,7,0,0,7,9,0,0,10,6,0,0,4,13,0,3,15,6,0,0,2,15,12,15,14,0,0,0,0,7,16,12,2,0,0,0 +0,0,1,14,4,0,0,0,0,0,6,15,2,0,0,0,0,0,12,8,0,0,0,0,0,0,12,4,0,0,0,0,0,2,16,13,12,9,0,0,0,5,16,11,8,15,10,0,0,0,13,9,4,9,11,0,0,0,3,11,14,13,4,0,6 +0,0,9,16,15,2,0,0,0,4,16,13,15,11,0,0,0,6,10,0,14,10,0,0,0,0,0,4,16,4,0,0,0,0,0,10,14,0,0,0,0,0,5,15,4,0,0,0,0,0,14,14,11,12,5,0,0,0,12,16,16,11,3,0,2 +0,0,11,16,16,16,7,0,0,0,9,8,8,16,13,0,0,0,0,0,2,16,10,0,0,0,2,4,12,16,4,0,0,0,15,16,16,16,10,0,0,0,5,14,10,4,0,0,0,0,6,16,0,0,0,0,0,0,12,11,0,0,0,0,7 +0,0,0,12,6,0,0,0,0,0,3,16,1,0,0,0,0,0,6,13,0,0,0,0,0,0,10,9,0,0,0,0,0,0,13,8,8,3,0,0,0,1,16,12,8,12,8,0,0,0,8,12,1,0,14,3,0,0,0,8,14,16,12,3,6 +0,0,0,5,15,1,0,0,0,0,1,14,12,0,0,0,0,1,12,15,0,3,13,1,0,7,16,4,0,11,15,0,0,14,13,0,4,16,5,0,0,14,16,16,16,11,0,0,0,2,8,13,16,3,0,0,0,0,0,7,16,2,0,0,4 +0,0,0,9,7,0,0,0,0,0,4,16,5,0,0,0,0,0,8,13,0,0,0,0,0,0,13,8,0,0,0,0,0,2,15,7,8,7,1,0,0,1,15,11,8,10,13,0,0,0,11,10,4,5,15,2,0,0,0,9,12,13,9,0,6 +0,0,3,16,11,1,0,0,0,0,12,16,16,2,0,0,0,3,16,16,11,0,0,0,0,5,16,16,7,0,0,0,0,6,16,16,5,0,0,0,0,1,16,16,3,0,0,0,0,0,11,16,9,0,0,0,0,0,4,12,14,12,5,0,1 +0,0,5,11,12,5,0,0,0,0,9,16,16,11,0,0,0,0,11,16,16,4,0,0,0,1,16,16,15,2,0,0,0,0,14,16,11,0,0,0,0,2,16,16,8,0,0,0,0,0,14,16,8,0,0,0,0,0,8,16,13,1,0,0,1 +0,0,0,1,14,2,0,0,0,0,0,10,13,0,1,0,0,0,6,13,2,6,9,0,0,1,15,4,0,13,6,0,0,6,14,6,7,16,2,0,0,1,9,13,16,14,5,0,0,0,0,0,16,2,0,0,0,0,0,0,14,0,0,0,4 +0,2,12,16,15,8,0,0,0,13,16,13,8,12,2,0,0,16,13,1,0,0,0,0,0,9,16,13,5,0,0,0,0,1,11,13,14,2,0,0,0,0,0,0,16,8,0,0,0,0,3,11,16,5,0,0,0,0,14,16,10,1,0,0,5 +0,0,6,14,14,4,0,0,0,0,13,15,11,13,0,0,0,0,2,2,2,16,1,0,0,0,0,0,3,14,1,0,0,0,0,0,9,11,0,0,0,0,0,4,16,3,0,0,0,0,4,15,16,16,14,2,0,0,7,13,12,9,9,3,2 +0,0,7,14,1,0,0,0,0,0,14,13,6,0,0,0,0,0,12,8,3,0,0,0,0,0,3,14,12,16,6,0,0,0,2,14,16,4,0,0,0,0,11,14,8,14,2,0,0,3,16,3,1,16,4,0,0,0,8,14,16,11,1,0,8 +0,0,0,10,7,0,0,0,0,0,1,16,5,0,0,0,0,0,0,15,2,7,4,0,0,0,0,11,16,14,4,0,0,1,12,16,15,1,0,0,0,7,16,4,16,1,0,0,0,1,12,9,16,4,0,0,0,0,1,11,15,2,0,0,8 +0,0,1,8,16,4,0,0,0,0,13,10,5,8,0,0,0,1,16,5,10,15,2,0,0,1,11,13,11,14,4,0,0,0,0,0,0,8,8,0,0,0,0,0,0,8,5,0,0,0,0,5,0,13,4,0,0,0,0,12,16,10,0,0,9 +0,2,10,15,14,9,2,0,0,4,11,4,5,14,8,0,0,0,0,0,8,16,3,0,0,0,0,6,14,3,0,0,0,0,0,8,16,7,0,0,0,0,0,0,7,16,1,0,0,2,4,4,7,15,1,0,0,3,12,14,12,6,0,0,3 +0,0,5,13,1,0,0,0,0,0,13,16,7,1,0,0,0,1,16,16,16,13,1,0,0,2,16,10,0,12,5,0,0,2,16,0,0,3,10,0,0,2,15,0,0,6,14,0,0,0,14,5,8,16,9,0,0,0,4,13,15,7,0,0,0 +0,1,10,16,15,0,0,0,0,7,15,5,8,0,0,0,0,10,12,0,0,0,0,0,0,4,16,4,2,9,5,0,0,0,10,15,15,15,8,0,0,0,12,16,13,1,0,0,0,3,16,13,16,1,0,0,0,0,12,16,14,1,0,0,8 +0,0,0,5,16,1,0,0,0,0,1,16,10,0,0,0,0,0,13,13,1,3,8,0,0,6,16,3,0,13,14,0,0,13,16,3,5,16,5,0,0,15,16,16,16,15,0,0,0,1,7,11,16,6,0,0,0,0,0,7,15,3,0,0,4 +0,1,11,16,16,7,0,0,0,0,8,7,12,12,0,0,0,0,0,0,12,8,0,0,0,0,0,2,14,4,0,0,0,1,13,16,16,11,4,0,0,1,12,16,10,8,3,0,0,0,11,11,0,0,0,0,0,3,16,5,0,0,0,0,7 +0,0,0,11,11,0,0,0,0,0,3,16,7,0,0,0,0,0,6,15,1,0,0,0,0,0,7,12,0,0,0,0,0,0,12,13,8,7,0,0,0,1,16,12,8,11,12,0,0,0,8,13,4,7,16,1,0,0,2,11,13,12,7,0,6 +0,0,7,15,14,4,0,0,0,0,13,13,10,11,0,0,0,0,0,1,0,15,1,0,0,0,0,0,1,16,0,0,0,0,0,0,6,14,0,0,0,0,0,4,15,8,0,0,0,0,5,14,16,16,9,0,0,0,5,11,7,4,9,2,2 +0,0,9,16,12,1,0,0,0,10,16,10,15,4,0,0,0,8,6,0,8,7,0,0,0,0,0,0,8,10,0,0,0,0,0,1,14,5,0,0,0,0,0,10,15,1,1,0,0,0,10,16,14,14,11,0,0,0,8,15,14,10,3,0,2 +0,0,0,9,14,0,0,0,0,0,6,16,8,0,0,0,0,0,14,14,0,6,15,0,0,9,16,3,3,16,10,0,0,13,16,4,11,15,1,0,0,13,16,16,16,12,2,0,0,2,4,10,16,2,0,0,0,0,0,9,15,2,0,0,4 +0,0,0,11,3,0,0,0,0,0,5,16,2,0,0,0,0,0,12,11,0,0,0,0,0,0,14,5,0,0,0,0,0,0,16,3,1,1,0,0,0,0,16,16,16,16,11,0,0,0,12,8,4,6,16,2,0,0,2,11,15,16,11,1,6 +0,0,0,7,15,1,0,0,0,0,2,14,10,0,2,0,0,0,10,12,1,2,15,3,0,5,16,3,0,9,14,0,0,14,12,2,2,16,5,0,0,14,16,16,16,16,1,0,0,5,8,12,16,4,0,0,0,0,0,9,16,6,0,0,4 +0,0,0,3,16,3,0,0,0,0,0,9,16,2,0,0,0,0,2,14,9,5,6,0,0,0,8,15,2,15,12,0,0,4,16,6,3,16,5,0,0,11,16,13,16,16,8,0,0,5,11,13,16,10,1,0,0,0,0,4,16,7,0,0,4 +0,0,4,14,14,4,0,0,0,5,14,3,1,4,0,0,0,8,12,0,2,2,0,0,0,4,15,12,16,16,8,0,0,0,14,16,9,0,0,0,0,4,16,11,15,2,0,0,0,2,15,8,12,12,0,0,0,0,4,12,16,10,0,0,8 +0,1,10,16,15,1,0,0,0,5,16,13,16,4,0,0,0,5,9,3,16,2,0,0,0,0,0,10,10,0,0,0,0,0,2,15,6,0,0,0,0,0,9,12,1,0,0,0,0,0,12,14,10,4,0,0,0,0,11,16,16,16,2,0,2 +0,0,2,13,10,4,0,0,0,0,0,16,16,11,0,0,0,0,0,13,16,15,0,0,0,0,1,14,16,11,0,0,0,0,4,16,16,6,0,0,0,0,3,16,14,2,0,0,0,0,7,16,16,3,0,0,0,0,4,13,16,4,0,0,1 +0,0,11,16,16,9,0,0,0,2,16,12,16,11,0,0,0,1,3,1,16,8,0,0,0,0,0,12,14,2,0,0,0,0,3,16,8,0,0,0,0,0,11,15,0,0,0,0,0,0,13,15,12,12,4,0,0,0,13,16,16,15,2,0,2 +0,1,10,16,16,14,1,0,0,1,11,5,4,15,0,0,0,0,0,0,6,10,0,0,0,0,1,13,12,1,0,0,0,0,2,15,16,12,1,0,0,0,0,0,3,14,4,0,0,1,6,0,2,14,3,0,0,2,12,16,15,8,0,0,3 +0,0,9,11,1,0,0,0,0,4,16,14,9,0,0,0,0,1,6,3,13,0,0,0,0,0,0,3,14,0,0,0,0,0,0,5,13,0,0,0,0,0,0,14,10,3,0,0,0,0,11,16,16,16,14,2,0,0,6,9,7,4,8,6,2 +0,0,13,13,12,12,2,0,0,0,14,16,16,14,3,0,0,0,10,16,5,0,0,0,0,0,1,10,14,1,0,0,0,0,0,2,16,4,0,0,0,0,0,0,12,8,0,0,0,0,4,6,15,5,0,0,0,1,15,16,15,1,0,0,5 +0,0,5,11,13,10,0,0,0,0,8,9,9,16,5,0,0,0,0,0,0,14,7,0,0,0,0,0,1,16,1,0,0,0,3,4,12,9,1,0,0,10,16,16,14,12,2,0,0,3,2,15,2,0,0,0,0,0,8,6,0,0,0,0,7 +0,0,7,16,11,0,0,0,0,5,15,12,16,3,0,0,0,6,16,14,16,7,0,0,0,1,8,12,16,8,0,0,0,0,0,0,6,15,2,0,0,0,0,0,0,16,7,0,0,0,0,0,2,13,8,0,0,0,8,16,16,14,4,0,9 +0,0,4,15,14,1,0,0,0,2,16,14,16,9,0,0,0,5,16,1,10,16,0,0,0,4,16,16,16,16,1,0,0,0,9,12,10,16,6,0,0,0,0,0,0,13,7,0,0,0,3,6,5,14,8,0,0,0,6,16,16,15,2,0,9 +0,0,9,15,13,5,1,0,0,2,16,13,9,12,3,0,0,8,12,0,0,0,0,0,0,2,16,15,6,0,0,0,0,0,1,8,15,3,0,0,0,0,0,0,6,11,0,0,0,0,5,6,12,10,0,0,0,1,10,13,10,1,0,0,5 +0,0,7,16,12,5,0,0,0,0,7,16,16,7,0,0,0,0,3,16,16,8,0,0,0,0,5,16,16,7,0,0,0,0,11,16,15,2,0,0,0,0,14,16,14,0,0,0,0,0,14,16,16,3,0,0,0,0,6,13,14,5,0,0,1 +0,0,6,12,14,11,0,0,0,1,11,8,10,14,0,0,0,0,0,0,12,9,0,0,0,0,0,11,10,0,0,0,0,0,0,10,16,13,1,0,0,0,0,0,7,15,5,0,0,0,4,4,5,14,3,0,0,0,10,15,12,8,0,0,3 +0,0,4,15,11,0,0,0,0,1,15,6,14,3,0,0,0,3,14,8,14,10,0,0,0,1,9,12,7,13,0,0,0,0,0,0,0,14,2,0,0,0,0,0,0,13,4,0,0,0,7,4,3,13,4,0,0,0,7,13,15,9,0,0,9 +0,1,11,16,9,0,0,0,0,10,14,9,13,2,0,0,0,4,3,0,12,4,0,0,0,0,0,0,12,3,0,0,0,0,0,5,15,0,0,0,0,0,2,13,10,0,0,0,0,1,16,16,9,10,7,0,0,0,10,13,12,10,9,0,2 +0,0,13,16,14,6,0,0,0,3,16,14,12,15,2,0,0,7,16,2,0,0,0,0,0,12,16,10,6,0,0,0,0,6,16,16,16,6,0,0,0,0,0,1,10,13,0,0,0,0,1,4,13,14,0,0,0,0,12,16,16,10,0,0,5 +0,0,3,15,7,0,0,0,0,0,13,16,10,0,0,0,0,5,16,7,5,3,0,0,0,5,16,8,16,15,2,0,0,2,12,1,6,8,10,0,0,1,14,2,0,0,13,0,0,0,9,15,12,15,16,0,0,0,2,10,15,16,7,0,0 +0,0,0,4,16,4,0,0,0,0,0,3,16,6,0,0,0,0,0,10,14,1,9,2,0,0,3,15,4,9,15,0,0,3,15,9,3,14,10,0,2,15,16,15,16,16,3,0,1,10,9,9,16,10,0,0,0,0,0,5,16,2,0,0,4 +0,0,6,16,16,12,0,0,0,0,8,9,13,16,2,0,0,0,0,0,4,16,1,0,0,0,1,5,12,12,0,0,0,0,13,16,16,16,7,0,0,0,8,14,12,10,3,0,0,0,3,16,3,0,0,0,0,0,9,11,0,0,0,0,7 +0,1,14,13,16,12,1,0,0,9,16,13,7,8,1,0,0,10,16,11,7,0,0,0,0,1,13,16,16,6,0,0,0,0,0,0,5,12,0,0,0,0,0,0,5,11,0,0,0,0,1,6,15,9,0,0,0,0,13,15,8,1,0,0,5 +0,0,3,15,13,1,0,0,0,0,13,14,11,4,0,0,0,0,14,11,3,1,0,0,0,0,4,16,12,16,6,0,0,0,5,16,16,6,0,0,0,3,16,9,11,11,0,0,0,3,16,6,2,16,4,0,0,0,6,13,14,9,0,0,8 +0,0,4,10,16,14,4,0,0,0,10,16,16,16,4,0,0,0,16,16,16,8,0,0,0,0,16,16,16,0,0,0,0,0,16,16,16,0,0,0,0,4,16,16,16,0,0,0,0,0,16,16,16,0,0,0,0,0,6,14,14,0,0,0,1 +0,0,5,8,0,0,0,0,0,0,12,16,13,7,0,0,0,0,15,15,13,16,3,0,0,3,15,0,0,10,6,0,0,3,13,0,0,7,9,0,0,4,16,0,1,12,12,0,0,1,15,12,14,16,9,0,0,0,6,14,14,9,1,0,0 +0,2,14,16,9,0,0,0,0,8,16,13,16,3,0,0,0,10,9,5,16,2,0,0,0,1,0,8,14,0,0,0,0,0,0,14,10,0,0,0,0,0,5,16,5,0,0,0,0,3,16,16,11,6,1,0,0,2,13,16,16,16,12,0,2 +0,0,0,7,16,2,0,0,0,0,3,15,10,0,0,0,0,1,13,14,2,3,9,0,0,9,16,7,0,14,14,0,0,13,16,0,5,16,8,0,0,14,16,16,16,15,2,0,0,0,4,7,16,6,0,0,0,0,0,8,15,0,0,0,4 +0,1,9,15,13,0,0,0,0,6,16,16,16,2,0,0,0,3,8,8,16,4,0,0,0,0,0,12,13,0,0,0,0,0,1,16,9,0,0,0,0,0,9,16,2,0,0,0,0,0,12,14,10,8,1,0,0,0,13,16,16,16,5,0,2 +0,0,6,11,0,0,0,0,0,1,15,12,2,4,0,0,0,5,12,3,16,16,4,0,0,5,9,0,8,7,8,0,0,8,8,0,0,4,8,0,0,7,9,0,0,4,9,0,0,0,15,8,9,15,7,0,0,0,6,16,16,9,0,0,0 +0,0,6,13,16,12,1,0,0,3,16,11,8,9,1,0,0,3,16,5,2,8,5,0,0,1,13,14,16,16,6,0,0,0,5,16,16,3,0,0,0,0,14,12,13,10,0,0,0,0,15,11,10,14,0,0,0,0,4,15,16,7,0,0,8 +0,0,0,5,15,0,0,0,0,0,0,11,11,0,0,0,0,0,1,16,4,4,6,0,0,0,13,12,1,15,7,0,0,2,16,2,8,14,2,0,0,12,16,15,16,15,0,0,0,5,8,10,16,2,0,0,0,0,0,3,11,2,0,0,4 +0,0,2,14,16,11,0,0,0,0,11,15,12,10,2,0,0,1,15,3,0,0,0,0,0,1,16,5,0,0,0,0,0,0,12,16,7,0,0,0,0,0,0,9,16,7,0,0,0,0,3,10,15,12,0,0,0,0,3,16,15,7,0,0,5 +0,0,0,12,15,9,11,6,0,0,0,5,10,13,16,2,0,0,0,0,0,9,9,0,0,0,0,4,10,16,2,0,0,0,8,16,16,16,5,0,0,0,3,8,14,2,0,0,0,0,0,11,9,0,0,0,0,0,0,12,1,0,0,0,7 +0,0,6,9,0,0,0,0,0,0,7,16,10,13,7,0,0,0,1,8,16,14,1,0,0,0,0,1,13,7,0,0,0,0,12,16,16,10,1,0,0,0,3,15,10,8,2,0,0,0,2,16,2,0,0,0,0,0,4,13,0,0,0,0,7 +0,0,0,0,6,15,12,0,0,0,1,11,14,13,15,0,0,1,12,10,0,11,7,0,0,5,15,1,3,16,3,0,0,5,16,13,15,14,0,0,0,0,4,4,11,13,0,0,0,0,0,0,8,13,0,0,0,0,0,0,8,8,0,0,9 +0,0,0,13,9,10,14,2,0,0,0,11,12,15,12,0,0,0,0,0,0,14,3,0,0,1,4,4,7,15,1,0,0,7,16,16,16,12,6,0,0,2,4,8,11,0,0,0,0,0,0,11,6,0,0,0,0,0,0,13,7,0,0,0,7 +0,1,13,13,1,0,0,0,0,9,16,16,7,0,0,0,0,5,5,13,8,0,0,0,0,0,2,16,6,0,0,0,0,0,11,15,1,0,0,0,0,4,16,8,1,0,0,0,0,4,16,16,16,16,6,0,0,1,11,14,16,16,11,0,2 +0,0,0,8,14,10,0,0,0,0,2,14,16,14,0,0,0,0,12,16,16,14,2,0,0,5,16,16,16,12,1,0,0,1,4,10,16,14,0,0,0,0,0,9,16,12,0,0,0,0,0,9,16,12,0,0,0,0,0,8,15,12,0,0,1 +0,0,0,8,16,10,8,12,0,0,0,12,16,16,16,11,0,0,0,0,0,14,14,0,0,0,0,1,8,16,11,0,0,0,1,15,16,16,7,0,0,0,2,9,16,8,0,0,0,0,0,7,16,3,0,0,0,0,0,10,12,0,0,0,7 +0,0,3,11,2,7,13,0,0,0,3,13,16,16,7,0,0,0,0,0,5,13,0,0,0,0,0,0,9,7,0,0,0,8,16,16,16,16,2,0,0,2,4,12,11,1,0,0,0,0,0,15,5,0,0,0,0,0,3,16,3,0,0,0,7 +0,0,0,0,15,7,0,0,0,0,0,7,16,4,0,0,0,0,1,15,11,1,5,0,0,2,14,12,1,12,13,0,0,7,16,11,8,16,11,0,0,12,16,16,16,16,3,0,0,1,8,5,14,15,0,0,0,0,0,0,13,12,0,0,4 +0,0,0,1,13,8,0,0,0,0,0,9,16,15,0,0,0,0,5,16,16,12,0,0,0,2,15,16,16,10,0,0,0,8,14,3,16,10,0,0,0,0,0,4,16,8,0,0,0,0,0,4,16,11,0,0,0,0,0,0,15,12,0,0,1 +0,0,0,3,11,16,11,0,0,0,6,16,11,16,7,0,0,0,14,8,0,15,1,0,0,8,16,4,12,11,0,0,0,1,11,15,16,11,0,0,0,0,0,0,15,5,0,0,0,0,0,0,16,6,0,0,0,0,0,2,16,5,0,0,9 +0,1,11,16,7,0,0,0,0,8,16,15,10,0,0,0,0,12,8,12,11,0,0,0,0,2,0,15,7,0,0,0,0,0,8,14,0,0,0,0,0,0,13,12,0,3,3,0,0,5,16,13,14,16,16,1,0,0,16,16,15,10,5,0,2 +0,0,2,9,0,0,0,0,0,0,11,8,0,0,0,0,0,1,16,0,0,0,0,0,0,4,11,0,0,0,0,0,0,7,8,3,8,3,0,0,0,6,11,13,16,16,4,0,0,0,14,16,7,12,12,0,0,0,3,14,16,14,6,0,6 +0,0,0,2,9,16,3,0,0,0,2,14,15,16,2,0,0,0,11,10,7,9,0,0,0,1,16,4,11,5,0,0,0,0,14,16,16,10,0,0,0,0,0,1,14,11,0,0,0,0,0,0,14,7,0,0,0,0,0,0,14,3,0,0,9 +0,0,0,9,13,1,0,0,0,0,3,15,7,1,0,0,0,0,8,10,0,0,0,0,0,0,13,5,0,0,0,0,0,0,14,16,12,3,0,0,0,0,13,10,7,15,1,0,0,0,5,12,2,11,7,0,0,0,0,8,16,16,4,0,6 +0,0,0,1,8,15,12,0,0,0,2,14,13,16,5,0,0,0,9,14,9,9,0,0,0,0,8,16,16,15,2,0,0,0,0,2,10,14,1,0,0,0,0,0,10,12,0,0,0,0,0,0,12,7,0,0,0,0,0,0,11,1,0,0,9 +0,0,2,15,7,0,0,0,0,0,7,16,6,0,0,0,0,0,14,15,1,3,1,0,0,5,16,7,3,16,8,0,0,13,15,0,10,16,4,0,0,13,16,16,16,15,0,0,0,2,6,15,15,5,0,0,0,0,4,15,4,0,0,0,4 +0,0,0,0,5,14,8,0,0,0,1,10,13,13,11,0,0,0,11,9,2,14,6,0,0,4,16,9,12,16,3,0,0,0,6,4,4,15,4,0,0,0,0,0,0,16,4,0,0,0,0,0,0,15,3,0,0,0,0,0,2,15,0,0,9 +0,0,6,16,12,1,0,0,0,0,15,14,14,13,0,0,0,2,16,6,5,16,2,0,0,0,15,4,2,16,6,0,0,1,16,5,1,16,6,0,0,2,16,2,11,14,1,0,0,0,16,5,15,5,0,0,0,0,7,16,7,0,0,0,0 +0,1,6,8,11,16,6,0,0,5,16,16,12,9,4,0,0,8,13,1,0,0,0,0,0,6,16,11,4,0,0,0,0,0,7,12,16,10,0,0,0,0,0,0,7,12,0,0,0,0,1,5,14,11,0,0,0,0,9,15,9,1,0,0,5 +0,0,8,12,8,8,10,0,0,0,5,10,13,16,8,0,0,0,0,0,5,11,0,0,0,2,8,12,15,12,2,0,0,8,12,15,12,8,4,0,0,0,2,13,2,0,0,0,0,0,8,11,0,0,0,0,0,0,10,5,0,0,0,0,7 +0,0,6,16,13,4,0,0,0,0,10,8,8,13,0,0,0,0,0,0,6,11,0,0,0,0,3,7,14,2,0,0,0,2,16,13,15,6,0,0,0,0,3,0,6,14,0,0,0,0,3,4,5,14,0,0,0,0,9,16,12,4,0,0,3 +0,0,4,12,15,8,0,0,0,4,15,10,10,12,0,0,0,13,10,2,16,4,0,0,0,6,12,15,9,0,0,0,0,0,2,16,13,3,0,0,0,0,10,9,6,13,1,0,0,0,10,9,0,10,8,0,0,0,2,13,16,14,5,0,8 +0,0,0,0,9,15,0,0,0,0,0,4,16,6,0,0,0,0,0,13,12,0,0,0,0,0,10,14,1,12,6,0,0,4,16,6,0,16,12,0,0,13,15,12,16,16,9,0,0,13,16,15,13,16,5,0,0,1,4,0,9,12,0,0,4 +0,0,1,13,15,7,0,0,0,0,9,14,9,15,1,0,0,2,16,5,0,10,4,0,0,5,13,5,0,4,8,0,0,8,8,0,0,7,8,0,0,3,14,1,0,9,7,0,0,0,13,13,9,16,4,0,0,0,3,13,16,10,0,0,0 +0,2,12,14,12,1,0,0,0,7,13,6,14,5,0,0,0,0,0,3,15,3,0,0,0,2,15,16,14,1,0,0,0,1,6,5,12,13,0,0,0,0,0,0,1,14,6,0,0,4,8,4,5,14,8,0,0,2,13,16,15,9,1,0,3 +0,0,0,0,5,15,1,0,0,0,0,0,14,16,2,0,0,0,1,12,16,16,2,0,0,1,13,16,12,16,0,0,0,4,13,2,8,13,0,0,0,0,0,0,9,13,0,0,0,0,0,0,11,16,0,0,0,0,0,0,4,16,4,0,1 +0,0,7,12,12,10,2,0,0,1,16,15,12,15,9,0,0,0,16,7,0,1,2,0,0,0,11,16,10,1,0,0,0,0,0,3,14,13,1,0,0,0,0,0,3,16,1,0,0,0,8,7,15,12,0,0,0,0,11,12,6,0,0,0,5 +0,0,5,15,15,5,0,0,0,2,15,5,3,13,0,0,0,3,9,0,6,10,0,0,0,4,8,3,13,1,0,0,0,3,14,14,6,0,0,0,0,0,7,12,14,11,1,0,0,0,12,5,1,13,9,0,0,0,5,12,14,10,1,0,8 +0,0,6,12,14,10,0,0,0,4,16,13,10,13,0,0,0,8,9,0,0,0,0,0,0,6,14,10,7,0,0,0,0,0,8,9,15,11,0,0,0,0,0,0,1,14,7,0,0,0,4,8,8,15,5,0,0,0,5,16,12,6,0,0,5 +0,0,0,1,11,14,10,0,0,0,6,14,7,10,13,0,0,0,15,3,1,14,5,0,0,0,15,11,9,16,5,0,0,0,3,8,7,16,4,0,0,0,0,0,2,11,0,0,0,0,0,0,10,10,0,0,0,0,0,0,14,7,0,0,9 +0,4,15,14,11,4,0,0,0,2,11,8,14,16,1,0,0,0,0,5,15,9,0,0,0,0,9,16,12,0,0,0,0,0,2,8,15,12,1,0,0,0,0,0,2,16,8,0,0,1,4,4,10,16,5,0,0,4,16,15,11,3,0,0,3 +0,0,0,5,0,4,13,5,0,0,6,16,16,16,14,1,0,0,2,6,8,16,2,0,0,0,2,4,12,14,0,0,0,5,15,16,16,16,4,0,0,6,9,11,15,4,0,0,0,0,0,15,9,0,0,0,0,0,0,13,6,0,0,0,7 +0,0,7,16,13,6,0,0,0,2,14,5,7,15,2,0,0,3,7,1,10,14,0,0,0,5,14,14,13,1,0,0,0,0,6,16,9,0,0,0,0,0,10,7,13,2,0,0,0,0,14,4,13,4,0,0,0,0,9,14,11,1,0,0,8 +0,0,5,10,14,5,0,0,0,4,16,13,13,12,0,0,0,7,10,0,10,7,0,0,0,0,0,6,16,6,0,0,0,0,0,6,14,15,1,0,0,0,0,0,0,11,7,0,0,0,4,8,8,15,7,0,0,0,5,14,12,8,0,0,3 +0,0,2,15,13,5,0,0,0,0,5,13,12,15,0,0,0,4,14,13,15,14,0,0,0,8,16,16,15,3,0,0,0,0,13,11,9,12,0,0,0,0,15,2,0,15,1,0,0,0,13,8,4,14,3,0,0,0,2,12,13,12,2,0,8 +0,0,4,15,11,2,0,0,0,0,15,15,14,11,0,0,0,2,16,6,0,15,1,0,0,2,16,5,0,11,5,0,0,2,16,4,0,7,9,0,0,0,15,2,0,10,9,0,0,0,11,13,9,16,4,0,0,0,2,10,16,10,0,0,0 +0,0,1,11,15,8,0,0,0,0,11,11,4,16,0,0,0,6,11,1,3,13,0,0,0,3,14,6,14,2,0,0,0,0,2,16,10,0,0,0,0,0,4,11,10,7,0,0,0,0,5,8,5,13,0,0,0,0,1,12,16,5,0,0,8 +0,0,1,8,13,10,2,0,0,0,11,16,13,13,3,0,0,4,15,0,0,0,0,0,0,3,15,7,1,0,0,0,0,0,8,15,15,7,0,0,0,0,0,2,10,16,2,0,0,0,2,7,8,16,4,0,0,0,2,15,12,7,0,0,5 +0,0,7,14,16,8,0,0,0,0,5,10,13,7,0,0,0,0,0,0,13,3,0,0,0,0,4,8,16,8,4,0,0,2,16,16,13,12,4,0,0,1,3,14,2,0,0,0,0,0,2,14,0,0,0,0,0,0,8,13,0,0,0,0,7 +0,0,0,6,13,3,0,0,0,0,4,16,13,1,0,0,0,0,13,11,1,0,0,0,0,5,15,1,0,0,0,0,0,5,13,0,0,0,0,0,0,2,16,10,16,13,3,0,0,0,11,16,14,16,15,0,0,0,1,8,13,13,9,0,6 +0,0,0,2,9,15,15,0,0,0,4,14,12,9,12,0,0,2,14,6,0,10,10,0,0,6,16,6,7,16,8,0,0,1,13,16,11,15,7,0,0,0,0,0,1,16,3,0,0,0,0,0,7,15,0,0,0,0,0,0,11,9,0,0,9 +0,0,0,0,10,12,0,0,0,0,0,0,15,12,0,0,0,0,0,7,16,12,0,0,0,0,9,16,16,11,0,0,0,8,16,11,16,12,0,0,0,3,4,3,16,9,0,0,0,0,0,0,15,10,0,0,0,0,0,0,9,13,0,0,1 +0,0,0,0,9,13,0,0,0,0,0,3,15,12,0,0,0,0,2,13,16,14,0,0,0,4,14,14,14,14,0,0,0,5,11,1,9,13,0,0,0,0,0,0,11,11,0,0,0,0,0,0,10,10,0,0,0,0,0,0,9,9,0,0,1 +0,0,0,14,8,8,14,0,0,0,1,11,15,16,13,0,0,0,0,0,6,16,3,0,0,3,13,16,16,16,3,0,0,2,8,11,15,9,4,0,0,0,0,10,7,0,0,0,0,0,0,16,5,0,0,0,0,0,0,14,1,0,0,0,7 +0,0,6,16,14,3,0,0,0,0,9,5,5,11,0,0,0,0,3,2,3,12,0,0,0,2,15,14,12,3,0,0,0,0,3,15,12,8,0,0,0,0,10,6,0,11,2,0,0,0,13,3,0,9,7,0,0,0,5,14,14,12,4,0,8 +0,0,0,0,10,5,0,0,0,0,0,0,16,4,0,0,0,0,0,9,12,4,3,0,0,0,2,15,1,11,8,0,0,0,14,7,0,14,4,0,0,6,16,9,15,15,2,0,0,9,13,8,13,4,0,0,0,0,0,0,9,4,0,0,4 +0,2,13,9,1,0,0,0,0,8,15,13,10,0,0,0,0,2,6,1,12,0,0,0,0,0,0,4,12,0,0,0,0,0,0,12,6,0,0,0,0,0,8,12,0,0,0,0,0,2,16,11,14,16,9,0,0,2,15,12,11,5,1,0,2 +0,0,6,14,16,5,0,0,0,3,16,10,14,14,0,0,0,3,6,1,13,9,0,0,0,0,2,15,16,11,0,0,0,0,2,12,14,16,2,0,0,0,0,0,1,16,7,0,0,0,6,13,10,15,3,0,0,0,10,16,13,5,0,0,3 +0,0,5,13,10,0,0,0,0,1,15,16,16,4,0,0,0,3,11,0,10,7,0,0,0,0,1,0,13,0,0,0,0,0,0,5,11,0,0,0,0,0,3,15,3,0,0,0,0,0,8,16,16,15,2,0,0,0,6,13,12,10,2,0,2 +0,0,1,13,16,16,9,0,0,0,13,14,12,12,11,0,0,9,16,5,0,0,0,0,0,9,16,6,2,0,0,0,0,3,15,16,15,3,0,0,0,0,3,11,16,10,0,0,0,0,4,16,16,7,0,0,0,0,0,15,15,2,0,0,5 +0,0,0,4,13,6,0,0,0,0,0,11,16,12,0,0,0,0,8,16,16,10,0,0,0,6,16,16,16,6,0,0,0,5,12,12,16,3,0,0,0,0,0,10,15,0,0,0,0,0,0,10,14,0,0,0,0,0,0,5,16,2,0,0,1 +0,0,3,0,6,15,0,0,0,9,16,14,16,15,0,0,0,5,7,8,14,11,0,0,0,0,1,4,13,11,2,0,0,0,8,16,16,16,12,0,0,0,1,8,15,11,2,0,0,0,0,0,12,12,0,0,0,0,0,0,8,13,0,0,7 +0,0,0,11,4,0,0,0,0,0,6,15,2,0,0,0,0,0,14,7,0,0,0,0,0,3,16,9,12,6,0,0,0,4,16,15,10,12,9,0,0,2,16,9,0,0,12,0,0,0,8,13,2,2,14,2,0,0,0,8,15,15,11,0,6 +0,0,3,12,12,1,0,0,0,0,15,15,13,11,0,0,0,3,16,4,0,14,2,0,0,5,15,5,0,5,7,0,0,8,8,0,0,4,8,0,0,4,14,0,0,7,11,0,0,0,14,11,12,16,3,0,0,0,3,14,12,4,0,0,0 +0,0,2,10,12,11,1,0,0,0,11,14,9,16,3,0,0,2,16,1,11,11,0,0,0,6,16,15,14,3,0,0,0,0,7,16,12,0,0,0,0,0,5,14,11,13,0,0,0,0,7,14,9,14,0,0,0,0,2,13,16,7,0,0,8 +0,0,0,0,3,13,4,0,0,0,0,0,11,16,8,0,0,0,0,8,16,16,6,0,0,1,9,16,9,16,2,0,0,7,16,5,5,16,0,0,0,0,2,0,8,13,0,0,0,0,0,0,9,12,0,0,0,0,0,0,5,15,3,0,1 +0,0,0,0,9,8,0,0,0,0,0,4,16,2,0,0,0,0,0,15,10,0,0,0,0,0,6,11,1,6,6,0,0,1,14,2,0,9,7,0,0,9,7,0,3,16,2,0,0,7,16,16,16,15,3,0,0,1,4,2,11,3,0,0,4 +0,0,0,0,12,14,0,0,0,0,0,1,16,16,1,0,0,0,5,14,16,12,0,0,0,5,16,16,16,8,0,0,0,9,15,9,16,8,0,0,0,0,1,2,16,7,0,0,0,0,0,4,16,10,0,0,0,0,0,0,10,16,6,0,1 +0,0,0,2,11,16,12,0,0,0,4,15,14,16,8,0,0,1,16,13,14,16,6,0,0,2,16,16,11,15,5,0,0,0,6,12,16,10,0,0,0,0,0,0,15,6,0,0,0,0,0,2,16,7,0,0,0,0,0,1,16,5,0,0,9 +0,0,7,14,12,1,0,0,0,2,16,12,16,8,0,0,0,2,10,0,12,8,0,0,0,0,5,12,16,3,0,0,0,0,5,12,15,13,1,0,0,0,0,0,5,16,3,0,0,0,12,8,4,13,3,0,0,0,9,16,14,9,0,0,3 +0,0,3,14,16,16,12,0,0,2,15,16,11,8,6,0,0,9,16,4,0,0,0,0,0,9,16,16,14,3,0,0,0,0,9,9,14,14,0,0,0,0,0,0,5,16,0,0,0,0,0,4,14,12,0,0,0,0,2,16,13,3,0,0,5 +0,0,0,1,15,4,0,0,0,0,0,6,16,1,0,0,0,0,0,14,12,0,0,0,0,0,10,16,2,3,0,0,0,4,16,8,3,16,4,0,0,12,16,13,15,16,4,0,0,8,14,12,16,14,5,0,0,0,0,1,16,6,0,0,4 +0,0,8,16,11,0,0,0,0,1,14,10,16,5,0,0,0,1,4,7,15,1,0,0,0,0,6,16,15,4,0,0,0,0,0,5,10,16,5,0,0,0,4,0,1,16,4,0,0,0,13,8,15,10,0,0,0,0,11,14,5,0,0,0,3 +0,0,10,16,16,16,2,0,0,0,7,8,10,16,0,0,0,0,0,0,10,14,5,0,0,0,8,15,16,16,8,0,0,0,14,16,11,0,0,0,0,0,3,16,2,0,0,0,0,0,10,10,0,0,0,0,0,0,12,3,0,0,0,0,7 +0,0,2,11,0,0,0,0,0,0,8,10,0,0,0,0,0,0,14,6,0,0,0,0,0,0,15,3,3,0,0,0,0,3,16,16,16,16,5,0,0,2,15,5,0,5,13,0,0,0,12,13,8,10,15,0,0,0,1,10,16,13,6,0,6 +0,0,1,11,14,12,1,0,0,0,11,12,9,16,3,0,0,7,6,6,12,6,0,0,0,6,13,10,14,1,0,0,0,0,12,16,4,0,0,0,0,0,14,13,14,4,0,0,0,0,11,12,11,15,0,0,0,0,1,9,14,14,1,0,8 +0,0,11,15,9,0,0,0,0,0,13,9,13,10,0,0,0,0,13,6,1,11,3,0,0,4,12,0,0,4,8,0,0,8,7,0,0,5,8,0,0,7,8,0,5,15,3,0,0,2,14,10,16,10,0,0,0,0,12,12,3,0,0,0,0 +0,0,9,16,10,0,0,0,0,0,4,10,15,2,0,0,0,0,13,0,4,13,0,0,0,4,16,0,0,13,2,0,0,7,12,0,0,10,6,0,0,8,13,1,2,13,8,0,0,5,16,16,16,16,2,0,0,0,9,16,15,8,0,0,0 +0,3,14,9,5,0,0,0,0,0,7,10,16,0,0,0,0,0,0,2,14,0,0,0,0,0,0,10,6,0,0,0,0,0,3,15,1,0,0,0,0,1,13,8,0,0,0,0,0,8,16,10,12,15,7,0,0,3,12,16,13,8,3,0,2 +0,0,7,15,6,0,0,0,0,0,5,8,14,7,0,0,0,0,7,2,4,13,2,0,0,0,14,3,0,12,4,0,0,2,16,0,1,14,5,0,0,5,13,0,7,16,3,0,0,1,16,9,15,13,0,0,0,0,9,15,10,1,0,0,0 +0,0,0,5,16,2,0,0,0,0,1,15,8,0,0,0,0,0,10,13,0,2,2,0,0,6,15,6,10,16,12,0,0,13,16,16,14,16,10,0,0,8,12,5,7,16,4,0,0,0,0,2,14,8,0,0,0,0,0,9,13,1,0,0,4 +0,0,2,14,11,1,0,0,0,0,8,13,2,0,0,0,0,0,12,8,0,0,0,0,0,0,16,4,0,0,0,0,0,0,16,12,15,11,4,0,0,2,16,12,4,5,14,0,0,0,13,11,4,12,15,1,0,0,3,11,16,12,3,0,6 +0,0,1,11,2,0,0,11,0,0,5,16,3,0,7,15,0,0,8,16,0,1,15,7,0,0,8,16,8,13,12,0,0,0,7,16,16,16,6,0,0,0,0,4,14,12,0,0,0,0,0,6,14,0,0,0,0,0,2,15,6,0,0,0,4 +0,0,1,13,1,0,0,0,0,0,7,14,0,0,0,0,0,0,12,7,0,0,0,0,0,0,14,5,0,0,0,0,0,1,16,4,4,4,1,0,0,0,14,15,11,10,12,0,0,0,8,11,0,4,15,4,0,0,1,10,16,14,9,0,6 +0,0,0,4,12,15,5,0,0,1,10,15,14,16,7,0,0,3,16,16,16,16,7,0,0,0,3,4,3,13,8,0,0,0,0,0,2,16,4,0,0,0,0,0,11,11,0,0,0,0,0,1,14,5,0,0,0,0,0,3,16,2,0,0,9 +0,0,15,16,16,16,3,0,0,0,7,8,10,16,4,0,0,0,0,0,8,15,0,0,0,0,4,7,16,15,6,0,0,9,16,16,14,13,3,0,0,2,11,15,2,0,0,0,0,0,13,9,0,0,0,0,0,2,16,4,0,0,0,0,7 +0,0,6,13,16,11,0,0,0,11,16,11,4,16,4,0,0,3,13,13,9,16,1,0,0,0,0,10,16,8,0,0,0,0,0,13,16,9,0,0,0,0,7,14,6,13,0,0,0,0,8,13,10,15,0,0,0,0,5,14,14,6,0,0,8 +0,0,0,2,8,13,14,2,0,0,5,14,9,4,16,5,0,1,14,0,5,14,13,0,0,0,16,16,13,16,5,0,0,0,1,0,7,9,0,0,0,0,0,0,13,2,0,0,0,0,0,2,13,0,0,0,0,0,0,3,10,0,0,0,9 +0,0,0,2,13,14,1,0,0,2,9,16,16,16,2,0,0,12,16,16,16,16,2,0,0,10,12,3,16,12,0,0,0,0,0,5,16,8,0,0,0,0,0,5,16,5,0,0,0,0,0,5,16,7,0,0,0,0,0,2,15,9,0,0,1 +0,0,3,10,16,11,0,0,0,3,12,10,6,16,2,0,0,9,16,6,12,11,1,0,0,0,5,16,15,0,0,0,0,0,7,12,15,2,0,0,0,0,10,5,6,11,0,0,0,0,11,4,10,13,0,0,0,0,3,16,14,5,0,0,8 +0,0,0,12,14,6,0,0,0,1,5,1,8,16,3,0,0,4,15,12,16,16,3,0,0,0,12,16,12,2,0,0,0,4,16,14,11,0,0,0,0,4,15,5,15,2,0,0,0,0,9,16,15,8,0,0,0,0,0,10,16,9,0,0,8 +0,0,3,14,14,3,0,0,0,0,7,6,5,14,0,0,0,0,7,7,3,13,3,0,0,0,2,15,15,5,0,0,0,0,8,16,11,0,0,0,0,1,14,3,10,4,0,0,0,1,13,3,3,11,0,0,0,0,4,10,16,11,0,0,8 +0,0,4,12,16,4,0,0,0,0,15,15,6,15,0,0,0,3,16,11,0,8,4,0,0,4,11,3,0,4,8,0,0,5,8,0,0,9,7,0,0,1,12,0,0,13,5,0,0,0,13,10,14,11,0,0,0,0,4,12,13,1,0,0,0 +0,0,0,6,12,0,0,0,0,0,5,14,4,0,0,0,0,1,15,3,0,1,12,0,0,2,16,2,0,8,11,0,0,3,16,15,10,15,6,0,0,0,2,12,14,13,1,0,0,0,0,0,12,5,0,0,0,0,0,7,13,0,0,0,4 +0,0,0,0,6,15,1,0,0,0,0,3,14,16,4,0,0,0,4,15,16,16,2,0,0,4,16,13,6,16,0,0,0,5,7,0,7,16,0,0,0,0,0,0,8,16,0,0,0,0,0,0,10,16,0,0,0,0,0,0,4,12,0,0,1 +0,1,9,15,16,13,4,0,0,6,9,4,4,13,8,0,0,0,0,0,7,15,4,0,0,0,0,10,16,8,0,0,0,0,0,4,12,13,1,0,0,0,0,0,0,14,8,0,0,0,6,4,7,15,6,0,0,0,8,16,12,5,0,0,3 +0,0,0,12,3,0,0,0,0,0,6,13,0,0,0,0,0,0,11,7,0,0,0,0,0,0,13,2,2,2,0,0,0,1,16,11,16,16,6,0,0,0,15,12,4,14,13,0,0,0,10,11,7,16,12,0,0,0,0,11,15,11,2,0,6 +0,0,4,11,7,1,0,0,0,0,9,16,16,12,0,0,0,0,10,16,16,8,0,0,0,0,10,16,16,10,0,0,0,2,15,16,16,8,0,0,0,0,16,16,16,4,0,0,0,0,5,16,16,13,2,0,0,0,5,10,11,8,3,0,1 +0,0,1,8,14,8,0,0,0,7,16,15,10,5,0,0,0,14,10,2,0,0,0,0,0,4,14,8,0,0,0,0,0,0,2,13,11,0,0,0,0,0,0,1,12,7,0,0,0,0,2,13,10,16,0,0,0,0,0,13,12,11,1,0,5 +0,2,16,9,0,0,0,0,0,2,16,16,10,1,0,0,0,0,1,7,16,13,0,0,0,1,5,0,8,16,3,0,0,4,8,0,0,12,7,0,0,5,10,0,5,15,4,0,0,8,15,14,16,10,0,0,0,3,15,15,8,1,0,0,0 +0,2,8,10,13,15,8,0,0,1,16,15,10,7,2,0,0,0,10,14,2,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,11,10,0,0,0,0,0,0,1,14,3,0,0,0,0,2,7,15,3,0,0,0,12,16,10,4,0,0,5 +0,1,7,16,16,16,8,0,0,10,16,14,12,10,3,0,0,12,14,1,0,0,0,0,0,2,15,13,1,0,0,0,0,0,2,16,11,0,0,0,0,0,1,5,15,7,0,0,0,0,11,10,13,16,1,0,0,0,8,16,16,10,0,0,5 +0,1,13,16,14,6,1,0,0,0,13,7,8,16,4,0,0,0,2,1,12,15,2,0,0,0,2,15,13,3,0,0,0,0,3,14,15,6,0,0,0,0,0,1,8,16,3,0,0,4,15,10,5,16,5,0,0,3,12,15,16,13,1,0,3 +0,0,0,12,12,0,0,0,0,0,9,16,9,0,0,0,0,3,16,14,5,5,2,0,0,6,16,16,16,16,9,0,0,0,5,8,13,16,6,0,0,0,0,1,15,16,2,0,0,0,0,5,16,11,0,0,0,0,0,11,16,9,0,0,4 +0,0,11,14,16,8,0,0,0,1,12,12,14,16,3,0,0,0,0,0,4,16,4,0,0,0,0,0,7,16,3,0,0,0,7,8,15,16,11,0,0,5,16,16,16,8,0,0,0,0,7,13,14,0,0,0,0,0,15,15,3,0,0,0,7 +0,0,6,14,15,4,0,0,0,3,16,10,11,14,0,0,0,2,8,0,6,16,2,0,0,0,0,0,8,10,0,0,0,0,0,0,14,7,0,0,0,0,0,7,13,0,0,0,0,0,4,16,11,14,4,0,0,0,5,15,13,8,1,0,2 +0,0,0,5,12,0,0,0,0,0,0,13,13,0,0,0,0,0,5,16,3,0,0,0,0,0,8,15,0,0,0,0,0,0,10,12,8,6,0,0,0,2,15,15,12,14,9,0,0,0,8,15,6,12,12,0,0,0,0,8,13,15,5,0,6 +0,0,7,10,12,16,14,1,0,0,10,15,12,15,15,1,0,0,0,0,1,13,11,0,0,0,0,0,5,16,10,0,0,0,7,12,15,16,9,0,0,0,9,15,16,3,0,0,0,0,3,15,8,0,0,0,0,0,9,15,2,0,0,0,7 +0,0,3,8,12,14,15,3,0,0,4,8,4,8,16,3,0,0,0,0,0,6,14,0,0,0,2,10,12,15,8,0,0,0,9,8,15,12,4,0,0,0,0,5,15,3,0,0,0,0,0,10,10,0,0,0,0,0,2,16,4,0,0,0,7 +0,0,1,15,0,0,0,0,0,0,4,15,1,0,0,0,0,0,12,9,0,0,0,0,0,1,16,9,2,0,0,0,0,5,16,13,14,10,2,0,0,2,16,5,0,10,9,0,0,1,13,12,8,12,11,0,0,0,1,12,13,10,3,0,6 +0,0,5,12,13,9,5,0,0,1,14,8,6,14,14,0,0,0,6,0,3,15,9,0,0,0,3,12,13,4,0,0,0,0,12,16,14,6,0,0,0,0,0,1,10,16,3,0,0,0,3,8,11,15,3,0,0,0,6,14,10,2,0,0,3 +0,0,5,10,15,9,0,0,0,6,14,8,9,16,4,0,0,4,2,2,13,13,0,0,0,0,0,8,16,1,0,0,0,0,0,0,9,13,1,0,0,0,1,2,0,12,7,0,0,0,9,9,4,11,10,0,0,0,4,13,13,10,1,0,3 +0,2,16,16,11,2,0,0,0,0,8,11,16,8,0,0,0,0,4,14,15,1,0,0,0,0,13,16,12,1,0,0,0,0,2,8,15,14,1,0,0,0,4,0,3,16,6,0,0,5,15,8,14,15,2,0,0,2,12,12,9,5,0,0,3 +0,1,9,12,15,16,7,0,0,10,16,15,12,11,3,0,0,13,16,2,0,0,0,0,0,5,16,13,2,0,0,0,0,0,4,15,15,1,0,0,0,0,2,4,15,9,0,0,0,0,14,14,16,11,0,0,0,0,11,16,12,1,0,0,5 +0,0,4,15,13,0,0,0,0,1,15,9,9,9,1,0,0,4,16,6,13,16,4,0,0,0,8,9,6,16,4,0,0,0,0,0,0,16,4,0,0,0,0,0,0,16,5,0,0,0,2,8,5,16,4,0,0,0,3,15,14,7,1,0,9 +0,0,0,6,10,14,6,0,0,0,14,16,12,16,8,0,0,0,8,16,16,16,8,0,0,5,12,8,1,13,6,0,0,0,0,0,4,16,3,0,0,0,0,0,12,11,0,0,0,0,0,1,16,2,0,0,0,0,0,9,11,0,0,0,9 +0,0,0,13,3,0,0,0,0,0,10,13,1,0,0,0,0,3,16,7,0,1,3,0,0,8,16,8,5,13,15,0,0,4,16,16,16,15,4,0,0,0,3,11,16,5,0,0,0,0,0,13,13,0,0,0,0,0,1,15,3,0,0,0,4 +0,0,0,0,13,13,0,0,0,0,2,12,16,16,0,0,0,3,15,16,16,13,0,0,0,11,15,4,14,12,0,0,0,2,2,0,16,11,0,0,0,0,0,0,15,11,0,0,0,0,0,3,16,10,0,0,0,0,0,0,15,15,0,0,1 +0,0,3,15,3,0,0,0,0,0,12,14,0,0,5,3,0,2,16,10,0,5,16,5,0,3,16,14,12,15,14,0,0,0,13,16,16,14,3,0,0,0,0,8,16,5,0,0,0,0,0,13,13,0,0,0,0,0,5,16,4,0,0,0,4 +0,0,0,4,10,16,14,0,0,0,6,16,12,11,16,2,0,3,15,15,10,15,16,2,0,1,15,16,10,15,14,0,0,0,0,0,1,16,8,0,0,0,0,0,7,15,3,0,0,0,0,0,12,12,0,0,0,0,0,1,16,8,0,0,9 +0,7,16,16,16,5,0,0,0,1,8,8,15,7,0,0,0,0,0,3,15,2,0,0,0,0,0,11,13,4,1,0,0,0,8,16,16,16,11,0,0,0,13,14,8,4,2,0,0,5,16,4,0,0,0,0,0,7,12,0,0,0,0,0,7 +0,0,2,8,15,14,0,0,0,1,14,12,8,16,0,0,0,6,12,1,5,13,0,0,0,1,2,1,14,5,0,0,0,0,0,9,13,0,0,0,0,0,0,15,4,0,0,0,0,0,0,16,8,11,7,0,0,0,0,9,13,7,0,0,2 +0,0,1,14,8,0,0,0,0,0,9,14,2,0,3,3,0,4,16,3,0,2,16,3,0,5,16,9,8,13,12,0,0,2,16,16,16,14,2,0,0,0,0,0,13,9,0,0,0,0,0,8,14,1,0,0,0,0,0,15,7,0,0,0,4 +0,0,5,10,14,6,0,0,0,13,16,15,10,3,0,0,0,15,8,0,0,0,0,0,0,8,11,1,0,0,0,0,0,0,7,13,3,0,0,0,0,0,0,5,13,5,0,0,0,0,6,8,13,14,1,0,0,0,4,12,13,11,0,0,5 +0,0,0,1,10,15,2,0,0,0,6,14,12,16,6,0,0,4,16,15,13,16,7,0,0,6,16,14,9,16,2,0,0,0,1,0,2,14,0,0,0,0,0,0,8,10,0,0,0,0,0,0,14,3,0,0,0,0,0,0,14,0,0,0,9 +0,1,6,13,13,4,0,0,0,9,16,14,15,15,0,0,0,5,5,0,6,13,0,0,0,0,0,1,12,11,0,0,0,0,0,7,14,1,0,0,0,0,1,16,8,0,0,0,0,0,8,16,9,13,10,0,0,0,4,14,16,9,2,0,2 +0,0,11,16,7,0,0,0,0,4,16,16,16,0,0,0,0,12,9,4,16,0,0,0,0,8,5,1,16,2,0,0,0,0,0,11,13,0,0,0,0,0,3,16,14,7,9,0,0,0,13,16,16,16,13,0,0,0,13,13,10,2,0,0,2 +0,0,1,13,7,0,0,0,0,0,12,15,1,0,0,0,0,6,16,5,0,3,5,0,0,10,16,9,11,16,14,0,0,6,14,16,16,16,5,0,0,0,0,1,16,11,0,0,0,0,0,9,16,2,0,0,0,0,0,14,13,0,0,0,4 +0,0,8,13,16,12,1,0,0,6,16,13,14,16,5,0,0,5,14,14,16,14,0,0,0,0,0,4,16,6,0,0,0,0,0,11,12,0,0,0,0,0,3,16,11,0,0,0,0,0,7,16,4,0,0,0,0,0,10,15,1,0,0,0,9 +0,0,0,9,14,1,0,0,0,0,5,16,4,0,1,1,0,0,14,13,0,3,13,6,0,1,16,16,16,16,14,1,0,0,7,12,14,16,2,0,0,0,0,0,14,10,0,0,0,0,0,6,16,2,0,0,0,0,0,13,5,0,0,0,4 +0,0,5,12,14,4,0,0,0,10,16,15,14,15,0,0,0,8,5,1,10,13,0,0,0,0,0,0,14,10,0,0,0,0,0,11,16,3,0,0,0,0,4,15,13,0,0,0,0,0,12,16,12,9,11,0,0,0,2,14,16,13,7,0,2 +0,0,0,12,16,5,0,0,0,0,2,13,16,8,0,0,0,6,15,16,16,10,0,0,0,4,8,9,16,14,0,0,0,0,0,0,14,16,4,0,0,0,0,0,7,16,12,0,0,0,0,7,11,16,12,0,0,0,0,13,16,13,3,0,1 +0,0,5,10,13,9,0,0,0,7,16,16,16,14,0,0,0,4,6,5,16,7,0,0,0,0,0,1,16,8,0,0,0,0,0,0,16,16,0,0,0,0,0,0,12,16,3,0,0,0,1,11,16,8,0,0,0,0,6,13,7,0,0,0,3 +0,0,0,1,16,1,0,0,0,0,0,7,12,0,0,0,0,0,3,15,4,0,0,0,0,1,14,8,0,10,9,0,0,8,13,0,3,16,2,0,0,8,16,13,16,13,0,0,0,0,8,5,16,6,0,0,0,0,0,1,14,1,0,0,4 +0,0,13,10,5,10,0,0,0,4,16,10,8,14,0,0,0,0,14,6,15,10,0,0,0,0,4,16,14,0,0,0,0,0,2,16,8,0,0,0,0,0,8,16,12,0,0,0,0,0,12,16,10,0,0,0,0,0,10,13,2,0,0,0,8 +0,0,5,16,16,8,0,0,0,0,10,16,16,11,0,0,0,0,12,13,1,0,0,0,0,0,5,16,1,0,0,0,0,0,0,14,6,0,0,0,0,0,6,10,11,0,0,0,0,0,15,16,16,0,0,0,0,0,5,15,14,0,0,0,5 +0,0,0,10,12,0,0,0,0,0,8,16,9,4,0,0,0,0,15,10,0,0,0,0,0,2,16,6,0,0,0,0,0,3,16,10,10,0,0,0,0,1,15,16,16,10,0,0,0,0,9,13,11,16,3,0,0,0,1,9,15,13,0,0,6 +0,3,10,12,15,10,1,0,0,6,11,8,14,16,4,0,0,0,0,4,16,12,0,0,0,0,1,15,12,1,0,0,0,0,0,14,12,0,0,0,0,0,0,8,16,1,0,0,0,1,7,14,13,0,0,0,0,3,15,9,1,0,0,0,3 +0,1,9,14,16,16,3,0,0,2,16,16,15,12,3,0,0,0,10,16,1,0,0,0,0,0,1,15,7,0,0,0,0,0,0,12,12,0,0,0,0,0,0,9,16,0,0,0,0,2,14,15,14,0,0,0,0,1,16,16,4,0,0,0,5 +0,0,0,8,15,5,0,0,0,0,0,15,16,11,0,0,0,0,8,16,16,13,0,0,0,7,16,16,16,16,4,0,0,2,4,0,10,16,10,0,0,0,0,0,8,16,11,0,0,0,0,7,14,16,10,0,0,0,0,11,16,9,1,0,1 +0,0,3,10,8,0,0,0,0,4,16,13,12,11,0,0,0,6,16,11,15,16,1,0,0,0,7,13,16,12,0,0,0,0,0,0,13,10,0,0,0,0,0,0,15,9,0,0,0,0,5,12,16,5,0,0,0,0,7,13,8,0,0,0,9 +0,0,4,10,14,12,10,0,0,0,7,8,8,12,13,0,0,0,0,0,0,10,12,0,0,0,1,5,8,16,6,0,0,0,5,16,16,13,1,0,0,0,0,7,15,4,0,0,0,0,0,14,12,0,0,0,0,0,2,16,2,0,0,0,7 +0,0,6,14,15,6,0,0,0,14,16,16,16,16,0,0,0,8,8,10,16,13,0,0,0,0,0,14,16,7,0,0,0,0,0,6,16,12,0,0,0,0,0,0,12,16,6,0,0,0,0,7,16,16,5,0,0,0,6,16,12,1,0,0,3 +0,0,0,9,11,0,0,0,0,0,0,13,16,6,0,0,0,0,7,15,16,5,0,0,0,7,16,16,16,5,0,0,0,1,7,4,15,10,0,0,0,0,0,0,12,14,0,0,0,0,0,6,15,16,0,0,0,0,0,10,16,8,0,0,1 +0,0,0,8,14,4,0,0,0,0,8,16,13,1,0,0,0,1,15,13,0,0,0,0,0,2,16,4,0,0,0,0,0,7,16,3,8,7,1,0,0,0,16,16,16,16,11,0,0,0,9,16,16,16,12,0,0,0,1,10,16,12,1,0,6 +0,0,14,16,9,12,1,0,0,0,16,10,10,16,6,0,0,0,12,12,13,13,1,0,0,0,4,16,14,1,0,0,0,0,6,16,7,0,0,0,0,1,15,15,12,0,0,0,0,5,16,12,12,0,0,0,0,1,10,14,3,0,0,0,8 +0,0,9,14,7,0,0,0,0,2,16,16,16,15,0,0,0,3,16,16,16,10,0,0,0,0,9,16,16,10,0,0,0,0,0,3,16,10,0,0,0,0,0,11,14,9,0,0,0,0,5,16,15,3,0,0,0,0,10,12,4,0,0,0,9 +0,0,0,7,14,6,0,0,0,0,11,16,9,2,0,0,0,4,15,10,0,0,0,0,0,9,12,0,3,1,0,0,0,7,13,2,16,12,2,0,0,3,16,2,2,13,9,0,0,0,11,13,6,15,10,0,0,0,0,8,16,13,1,0,6 +0,2,9,14,8,7,0,0,0,11,16,16,16,16,0,0,0,6,16,16,16,16,2,0,0,0,0,0,8,16,7,0,0,0,0,0,8,16,4,0,0,0,0,1,15,16,0,0,0,0,4,14,16,7,0,0,0,1,13,14,2,0,0,0,9 +0,0,0,8,11,0,0,0,0,0,3,16,5,0,0,0,0,0,11,13,0,0,0,0,0,1,16,5,0,1,0,0,0,7,15,0,3,16,3,0,0,8,13,1,10,16,4,0,0,7,16,16,16,16,2,0,0,0,4,6,5,15,3,0,4 +0,1,12,16,8,2,0,0,0,2,16,8,16,15,0,0,0,0,16,9,16,6,0,0,0,0,8,16,8,0,0,0,0,0,9,16,1,0,0,0,0,0,15,16,8,0,0,0,0,3,16,16,9,0,0,0,0,0,7,14,2,0,0,0,8 +0,0,0,2,14,1,0,0,0,0,0,10,12,0,0,0,0,0,8,15,1,2,1,0,0,3,15,5,0,12,7,0,0,10,14,0,6,16,2,0,0,8,16,16,16,12,0,0,0,0,2,4,16,5,0,0,0,0,0,2,13,0,0,0,4 +0,0,5,11,14,9,1,0,0,6,15,12,13,16,2,0,0,8,5,4,16,9,0,0,0,0,0,4,16,8,0,0,0,0,0,2,15,11,0,0,0,0,0,0,10,16,3,0,0,0,1,11,16,10,1,0,0,0,5,13,6,0,0,0,3 +0,0,0,0,10,10,2,0,0,0,0,6,16,7,0,0,0,0,3,16,7,0,0,0,0,0,14,13,0,3,0,0,0,4,16,6,4,16,4,0,0,5,16,9,13,16,6,0,0,0,7,12,16,14,1,0,0,0,0,0,12,14,0,0,4 +0,0,9,12,16,16,3,0,0,0,14,16,16,11,0,0,0,0,7,16,2,0,0,0,0,0,1,16,7,0,0,0,0,0,0,12,10,0,0,0,0,0,0,8,15,0,0,0,0,3,14,16,12,0,0,0,0,1,15,16,6,0,0,0,5 +0,0,5,11,12,2,0,0,0,5,16,14,14,12,0,0,0,5,7,0,10,10,0,0,0,0,0,1,14,6,0,0,0,0,0,14,12,1,0,0,0,0,11,16,1,0,0,0,0,0,15,13,8,5,2,0,0,0,3,12,16,15,9,0,2 +0,0,2,10,15,16,16,5,0,0,10,13,12,14,16,6,0,0,0,0,0,8,16,1,0,0,0,4,4,14,12,0,0,0,6,16,16,16,8,0,0,0,2,11,16,11,1,0,0,0,0,12,15,0,0,0,0,0,1,16,8,0,0,0,7 +0,0,4,14,15,3,0,0,0,0,14,16,16,9,0,0,0,0,11,16,16,15,0,0,0,0,3,13,16,16,1,0,0,0,0,0,3,16,6,0,0,0,0,0,13,16,1,0,0,0,2,11,16,12,0,0,0,0,6,15,10,1,0,0,9 +0,0,0,7,16,6,0,0,0,2,9,16,16,11,0,0,0,10,16,12,13,16,1,0,0,2,4,1,7,16,5,0,0,0,0,0,2,16,10,0,0,0,0,0,2,16,13,0,0,0,0,4,14,16,12,0,0,0,0,4,14,9,2,0,1 +0,0,6,16,16,11,0,0,0,0,15,16,14,8,0,0,0,0,9,13,0,0,0,0,0,0,3,15,1,0,0,0,0,0,0,11,8,0,0,0,0,0,2,13,14,0,0,0,0,0,16,16,13,0,0,0,0,0,7,16,6,0,0,0,5 +0,0,0,13,14,2,0,0,0,0,7,16,12,2,0,0,0,0,14,14,1,0,0,0,0,0,15,11,0,0,0,0,0,4,16,16,14,14,4,0,0,2,16,16,13,10,14,0,0,0,9,13,8,12,16,1,0,0,1,11,16,16,10,0,6 +0,0,0,8,14,16,6,0,0,3,13,16,13,15,14,0,0,3,12,6,0,11,16,0,0,0,0,0,4,15,13,0,0,0,0,12,16,16,11,0,0,0,1,11,16,11,0,0,0,0,0,7,16,5,0,0,0,0,0,11,12,0,0,0,7 +0,0,2,7,13,3,0,0,0,0,10,16,12,13,0,0,0,0,15,9,1,12,4,0,0,1,15,0,0,8,7,0,0,5,8,0,0,12,5,0,0,1,11,0,1,15,4,0,0,0,14,13,15,10,0,0,0,0,3,13,14,3,0,0,0 +0,0,0,14,8,1,0,0,0,0,9,16,16,4,0,0,0,11,16,16,14,0,0,0,0,5,8,14,16,2,0,0,0,0,0,7,16,6,0,0,0,0,0,4,16,12,0,0,0,0,1,6,16,14,0,0,0,0,2,14,16,9,0,0,1 +0,0,3,12,13,1,0,0,0,0,14,16,15,11,0,0,0,0,15,15,15,14,0,0,0,0,11,16,15,16,2,0,0,0,1,5,3,16,6,0,0,0,0,0,1,16,6,0,0,0,0,5,15,16,4,0,0,0,6,16,16,6,0,0,9 +0,0,9,16,7,5,0,0,0,0,9,16,16,16,0,0,0,0,7,16,15,6,0,0,0,0,7,16,7,0,0,0,0,0,14,16,5,0,0,0,0,3,16,16,4,0,0,0,0,1,16,16,5,0,0,0,0,0,7,12,1,0,0,0,8 +0,0,0,1,11,5,0,0,0,0,0,13,12,1,0,0,0,0,8,14,3,0,0,0,0,2,16,6,2,12,4,0,0,7,16,7,8,15,5,0,0,2,16,16,16,11,0,0,0,0,4,10,16,7,0,0,0,0,0,3,14,4,0,0,4 +0,0,1,14,15,3,0,0,0,0,9,16,16,8,0,0,0,9,16,16,14,10,0,0,0,0,3,8,16,14,0,0,0,0,0,2,16,16,2,0,0,0,0,0,13,16,9,0,0,0,1,10,16,16,9,0,0,0,2,10,15,10,0,0,1 +0,0,4,12,16,9,0,0,0,0,13,10,4,14,3,0,0,5,13,1,0,12,4,0,0,4,12,0,0,8,8,0,0,6,12,0,0,10,7,0,0,3,15,0,2,16,3,0,0,0,16,13,15,11,0,0,0,0,6,15,10,0,0,0,0 +0,0,1,10,13,0,0,0,0,0,11,16,9,0,0,0,0,0,15,12,0,0,0,0,0,3,16,5,0,0,0,0,0,5,16,8,12,10,1,0,0,2,16,8,10,15,9,0,0,0,9,14,8,12,15,0,0,0,0,8,14,15,8,0,6 +0,0,1,9,13,16,8,0,0,0,12,13,9,11,14,0,0,0,0,0,0,9,12,0,0,0,0,5,9,15,10,0,0,0,5,16,16,16,5,0,0,0,0,0,13,12,0,0,0,0,0,2,16,7,0,0,0,0,0,9,16,0,0,0,7 +0,0,6,16,16,10,0,0,0,5,16,16,13,6,0,0,0,1,15,15,1,0,0,0,0,0,4,16,5,0,0,0,0,0,0,14,11,0,0,0,0,0,0,13,14,0,0,0,0,0,9,16,11,0,0,0,0,0,8,15,5,0,0,0,5 +0,0,0,12,9,0,0,0,0,0,4,16,16,2,0,0,0,5,15,16,16,3,0,0,0,6,14,13,15,12,0,0,0,0,0,0,8,16,2,0,0,0,0,0,0,16,10,0,0,0,0,7,9,15,15,0,0,0,0,10,16,14,5,0,1 +0,2,15,12,3,6,0,0,0,5,16,13,6,16,6,0,0,6,16,1,3,16,2,0,0,0,15,11,15,14,0,0,0,0,9,16,16,3,0,0,0,0,13,16,6,0,0,0,0,8,16,16,2,0,0,0,0,3,15,13,0,0,0,0,8 +0,0,5,13,14,1,0,0,0,0,14,16,16,9,0,0,0,0,10,16,16,14,0,0,0,0,2,12,14,16,2,0,0,0,0,0,8,16,2,0,0,0,0,4,15,16,2,0,0,0,5,16,16,14,0,0,0,0,4,14,15,1,0,0,9 +0,0,1,13,10,0,0,0,0,0,10,16,7,0,0,0,0,3,16,7,0,0,0,0,0,3,16,3,0,0,0,0,0,8,16,6,8,7,0,0,0,3,15,16,16,16,8,0,0,0,9,16,16,16,5,0,0,0,0,8,15,9,0,0,6 +0,1,7,14,16,11,0,0,0,11,16,12,15,16,1,0,0,8,4,3,16,10,0,0,0,0,0,1,16,7,0,0,0,0,0,0,16,11,0,0,0,0,0,0,12,16,0,0,0,0,2,7,15,13,0,0,0,0,10,15,9,1,0,0,3 +0,1,9,14,7,2,0,0,0,8,16,11,16,14,2,0,0,5,16,14,16,16,4,0,0,0,4,7,10,16,7,0,0,0,0,0,4,16,8,0,0,0,0,0,6,16,9,0,0,0,4,11,16,11,0,0,0,0,12,14,8,0,0,0,9 +0,0,0,0,13,6,0,0,0,0,1,10,13,3,0,0,0,0,5,16,5,0,0,0,0,2,15,9,0,1,0,0,0,7,16,1,5,16,6,0,0,8,16,12,16,14,0,0,0,2,11,13,16,12,0,0,0,0,0,1,14,5,0,0,4 +0,0,5,12,13,1,0,0,0,3,15,14,7,10,0,0,0,0,15,7,14,16,2,0,0,0,8,16,16,9,0,0,0,0,3,16,16,1,0,0,0,0,12,16,16,6,0,0,0,1,16,16,16,7,0,0,0,0,6,14,12,1,0,0,8 +0,0,9,16,16,8,0,0,0,2,16,16,13,4,0,0,0,3,16,6,1,0,0,0,0,0,11,11,0,0,0,0,0,0,2,14,5,0,0,0,0,0,0,12,10,0,0,0,0,0,10,16,15,0,0,0,0,0,10,15,6,0,0,0,5 +0,0,13,10,2,8,0,0,0,2,16,13,13,14,0,0,0,0,14,4,12,11,0,0,0,0,12,13,16,5,0,0,0,0,3,16,13,0,0,0,0,0,9,16,9,0,0,0,0,0,16,16,10,0,0,0,0,0,11,13,2,0,0,0,8 +0,1,12,16,10,1,0,0,0,11,15,15,16,8,0,0,0,9,16,16,16,14,0,0,0,0,11,7,6,16,8,0,0,0,0,0,8,13,2,0,0,0,0,8,15,12,0,0,0,0,7,16,15,3,0,0,0,0,9,8,1,0,0,0,9 +0,0,0,0,11,9,0,0,0,0,0,8,15,3,0,0,0,0,4,15,5,0,0,0,0,1,14,9,0,5,3,0,0,8,15,0,1,16,7,0,0,12,15,12,15,15,3,0,0,6,15,12,15,12,0,0,0,0,0,0,10,4,0,0,4 +0,0,4,15,16,6,0,0,0,0,14,15,8,14,2,0,0,7,12,2,0,8,4,0,0,7,8,0,0,5,8,0,0,8,8,0,0,9,8,0,0,3,11,0,0,10,7,0,0,0,15,7,8,14,2,0,0,0,5,12,14,6,0,0,0 +0,0,4,13,12,1,0,0,0,2,15,12,11,7,0,0,0,1,12,13,15,14,0,0,0,0,3,16,16,5,0,0,0,0,4,16,11,0,0,0,0,0,10,16,16,0,0,0,0,0,14,16,13,0,0,0,0,0,8,13,3,0,0,0,8 +0,0,5,15,15,2,0,0,0,4,15,11,16,4,0,0,0,2,4,6,16,2,0,0,0,0,0,14,10,0,0,0,0,0,6,16,2,0,0,0,0,0,9,13,0,0,0,0,0,0,10,15,8,4,3,0,0,0,3,14,16,14,4,0,2 +0,0,4,9,7,13,1,0,0,1,16,6,6,14,4,0,0,7,13,0,0,10,8,0,0,8,6,0,0,12,7,0,0,8,5,0,0,13,4,0,0,8,10,0,5,16,4,0,0,1,15,12,15,10,0,0,0,0,5,14,9,1,0,0,0 +0,1,6,14,10,1,0,0,0,9,16,15,16,13,0,0,0,6,6,9,16,7,0,0,0,0,0,14,10,0,0,0,0,0,0,8,15,5,0,0,0,0,0,0,13,13,0,0,0,0,2,9,15,8,0,0,0,0,10,16,7,0,0,0,3 +0,0,8,16,6,0,0,0,0,0,13,10,8,8,0,0,0,0,8,12,13,15,3,0,0,0,6,16,16,6,0,0,0,0,11,16,6,0,0,0,0,2,14,14,11,0,0,0,0,1,16,11,15,1,0,0,0,0,9,16,10,0,0,0,8 +0,0,6,13,11,4,0,0,0,5,16,10,14,12,0,0,0,7,8,1,14,9,0,0,0,0,0,5,15,3,0,0,0,0,2,15,10,0,0,0,0,0,12,14,0,0,0,0,0,4,16,12,4,6,5,0,0,1,9,14,13,12,5,0,2 +0,0,2,13,8,0,0,0,0,0,12,15,16,11,0,0,0,2,16,3,3,13,4,0,0,5,13,0,0,9,7,0,0,7,8,0,0,13,3,0,0,3,14,0,1,15,2,0,0,0,14,10,12,12,0,0,0,0,2,13,12,3,0,0,0 +0,0,6,16,16,7,0,0,0,1,15,16,14,10,0,0,0,0,15,11,0,0,0,0,0,0,6,16,2,0,0,0,0,0,0,10,10,0,0,0,0,0,0,7,14,0,0,0,0,0,5,12,16,2,0,0,0,0,9,16,10,0,0,0,5 +0,0,3,9,13,14,1,0,0,4,16,15,11,14,8,0,0,0,2,0,2,14,6,0,0,0,0,0,10,16,5,0,0,0,5,16,16,16,7,0,0,0,1,10,16,5,0,0,0,0,0,8,16,0,0,0,0,0,0,10,10,0,0,0,7 +0,0,0,11,12,0,0,0,0,0,8,16,12,0,0,0,0,0,15,14,1,0,0,0,0,1,15,8,0,0,0,0,0,3,16,6,1,0,0,0,0,1,15,16,16,16,10,0,0,0,8,16,16,16,16,3,0,0,0,7,15,16,9,0,6 +0,0,1,15,9,0,0,0,0,0,4,16,16,2,0,0,0,5,15,16,16,5,0,0,0,2,8,11,16,12,0,0,0,0,0,2,16,16,2,0,0,0,0,0,13,16,8,0,0,0,2,8,13,16,8,0,0,0,1,11,14,12,2,0,1 +0,0,0,10,11,0,0,0,0,0,3,16,5,8,5,0,0,0,10,14,2,16,2,0,0,4,15,5,8,12,0,0,0,12,16,12,15,16,6,0,0,14,16,16,16,14,2,0,0,0,0,11,13,0,0,0,0,0,0,14,5,0,0,0,4 +0,0,6,16,12,1,0,0,0,0,16,10,13,7,0,0,0,0,14,6,10,12,0,0,0,0,5,14,16,16,6,0,0,0,0,0,4,11,9,0,0,0,0,0,0,7,13,0,0,0,10,10,4,11,12,0,0,0,6,14,12,12,5,0,9 +0,0,6,14,10,0,0,0,0,0,8,16,16,0,0,0,0,0,8,16,16,1,0,0,0,1,13,16,14,0,0,0,0,3,13,16,13,0,0,0,0,0,7,16,16,0,0,0,0,0,6,16,16,9,0,0,0,0,5,14,16,13,4,0,1 +0,0,2,15,5,0,0,0,0,0,5,15,1,7,0,0,0,0,10,10,6,16,0,0,0,5,15,2,13,11,0,0,0,14,15,12,16,16,6,0,0,14,16,16,16,14,3,0,0,1,4,15,11,1,0,0,0,0,2,15,7,0,0,0,4 +0,0,13,16,6,0,0,0,0,6,16,13,15,0,0,0,0,4,15,7,16,0,0,0,0,0,3,10,13,0,0,0,0,0,0,14,10,0,0,0,0,1,13,16,1,2,0,0,0,8,16,16,15,16,2,0,0,1,11,15,16,16,3,0,2 +0,0,9,16,9,1,0,0,0,5,16,9,16,11,0,0,0,8,12,0,15,12,0,0,0,1,15,16,16,16,2,0,0,0,0,5,4,16,4,0,0,0,0,0,0,13,7,0,0,1,12,12,12,15,9,0,0,1,10,14,8,8,1,0,9 +0,1,13,16,9,0,0,0,0,6,16,14,13,0,0,0,0,5,11,8,15,0,0,0,0,0,0,12,9,0,0,0,0,0,3,16,6,0,0,0,0,1,13,13,1,0,0,0,0,11,16,16,16,16,5,0,0,2,12,14,15,16,5,0,2 +0,0,10,12,2,0,0,0,0,0,16,14,8,0,0,0,0,0,14,7,12,0,0,0,0,0,1,5,12,0,0,0,0,0,0,9,9,0,0,0,0,0,3,16,2,0,0,0,0,4,16,16,10,6,2,0,0,1,8,8,11,13,10,0,2 +0,3,15,11,1,0,0,0,0,8,13,12,7,0,0,0,0,5,5,9,9,0,0,0,0,0,2,14,10,0,0,0,0,0,3,12,15,13,1,0,0,0,0,0,1,15,7,0,0,4,11,5,10,16,4,0,0,4,12,13,12,3,0,0,3 +0,0,4,15,14,2,0,0,0,0,14,8,16,2,0,0,0,0,6,3,16,0,0,0,0,0,0,12,16,5,0,0,0,0,0,7,15,16,4,0,0,0,0,0,0,16,4,0,0,0,8,12,10,15,3,0,0,0,6,13,13,4,0,0,3 +0,0,3,15,16,16,3,0,0,0,3,9,13,16,2,0,0,0,0,0,10,16,0,0,0,0,6,12,16,16,9,0,0,0,15,16,16,14,7,0,0,0,0,10,15,1,0,0,0,0,2,16,10,0,0,0,0,0,7,15,4,0,0,0,7 +0,0,8,16,16,16,7,0,0,0,16,12,8,8,4,0,0,2,16,6,0,0,0,0,0,7,16,16,16,8,0,0,0,2,12,9,9,16,3,0,0,0,0,0,1,16,3,0,0,0,8,11,12,16,2,0,0,0,10,16,14,6,0,0,5 +0,2,14,10,1,0,0,0,0,6,13,13,6,0,0,0,0,8,5,6,8,0,0,0,0,3,2,8,6,0,0,0,0,0,0,11,4,0,0,0,0,0,6,14,0,1,0,0,0,3,16,15,12,15,7,0,0,2,13,9,8,9,7,0,2 +0,0,6,12,12,12,2,0,0,1,16,12,12,12,5,0,0,5,12,5,1,0,0,0,0,8,16,16,14,0,0,0,0,2,6,0,16,1,0,0,0,0,0,0,15,1,0,0,0,0,8,11,13,0,0,0,0,0,9,9,3,0,0,0,5 +0,0,9,6,0,0,0,0,0,0,16,4,0,0,0,0,0,2,15,0,0,0,0,0,0,6,12,1,2,0,0,0,0,7,15,14,16,11,1,0,0,4,16,10,4,16,5,0,0,0,16,11,8,16,6,0,0,0,6,14,14,9,0,0,6 +0,1,9,12,12,15,6,0,0,1,16,11,8,8,4,0,0,6,16,5,4,2,0,0,0,7,16,16,16,15,2,0,0,0,2,2,3,14,6,0,0,0,0,0,2,14,6,0,0,2,11,8,12,13,2,0,0,1,11,16,10,2,0,0,5 +0,0,0,11,16,10,0,0,0,0,0,15,16,8,0,0,0,0,7,16,16,6,0,0,0,4,16,16,16,4,0,0,0,1,4,12,16,4,0,0,0,0,0,9,16,7,0,0,0,0,0,11,16,15,0,0,0,0,0,8,14,11,2,0,1 +0,0,2,15,16,10,0,0,0,0,1,12,14,16,0,0,0,0,0,0,5,16,2,0,0,0,0,0,8,15,1,0,0,0,7,8,14,15,4,0,0,5,16,16,16,15,2,0,0,1,4,14,12,0,0,0,0,0,3,15,5,0,0,0,7 +0,0,7,15,12,5,0,0,0,0,15,7,6,16,2,0,0,3,16,3,10,16,6,0,0,0,14,16,16,11,2,0,0,0,1,14,16,3,0,0,0,0,2,14,15,12,0,0,0,0,12,8,8,16,0,0,0,0,9,16,14,10,0,0,8 +0,0,10,11,10,2,0,0,0,0,16,14,14,10,0,0,0,3,16,13,12,9,0,0,0,4,16,16,14,15,2,0,0,0,0,0,0,12,4,0,0,0,0,0,0,13,7,0,0,0,7,8,14,14,1,0,0,0,10,13,9,0,0,0,5 +0,0,1,12,16,3,0,0,0,0,7,11,8,12,0,0,0,2,14,1,0,14,1,0,0,4,14,0,0,11,7,0,0,3,13,0,0,5,8,0,0,0,13,4,0,9,7,0,0,0,10,13,8,14,1,0,0,0,1,9,14,6,0,0,0 +0,0,4,15,16,15,2,0,0,0,16,12,10,15,9,0,0,2,16,5,0,9,11,0,0,0,15,6,0,8,10,0,0,1,16,7,0,13,6,0,0,2,16,10,4,16,0,0,0,0,10,16,16,8,0,0,0,0,2,14,12,2,0,0,0 +0,0,14,10,0,0,0,0,0,2,16,14,4,0,0,0,0,0,9,8,8,0,0,0,0,0,0,10,4,0,0,0,0,0,2,15,2,0,0,0,0,2,13,9,0,2,1,0,0,8,16,13,13,16,5,0,0,1,9,15,16,16,7,0,2 +0,0,0,10,16,13,7,0,0,0,2,9,10,15,11,0,0,0,0,0,0,13,8,0,0,0,1,7,9,16,5,0,0,0,7,16,16,15,5,0,0,0,0,0,14,3,0,0,0,0,0,6,16,1,0,0,0,0,0,16,8,0,0,0,7 +0,0,4,14,13,3,0,0,0,1,15,11,16,9,0,0,0,2,15,0,10,14,0,0,0,0,13,11,13,16,4,0,0,0,1,11,12,14,7,0,0,0,0,0,0,10,9,0,0,3,16,10,6,9,12,0,0,0,6,9,12,13,6,0,9 +0,2,11,16,5,0,0,0,0,10,14,12,12,0,0,0,0,3,2,8,11,0,0,0,0,0,0,11,12,0,0,0,0,0,1,13,16,10,0,0,0,0,0,0,4,16,5,0,0,0,8,6,6,16,10,0,0,2,12,13,12,10,1,0,3 +0,0,0,10,13,3,0,0,0,0,0,13,16,7,0,0,0,0,4,16,16,5,0,0,0,2,16,16,16,6,0,0,0,7,16,16,16,11,0,0,0,0,0,8,16,12,0,0,0,0,0,15,16,14,0,0,0,0,0,8,13,9,0,0,1 +0,0,5,14,16,15,2,0,0,0,12,14,11,16,5,0,0,0,3,0,8,15,0,0,0,0,0,3,15,11,0,0,0,1,11,16,16,15,6,0,0,1,13,16,16,13,3,0,0,0,3,16,9,0,0,0,0,0,6,16,2,0,0,0,7 +0,0,6,13,2,0,0,0,0,3,15,14,8,0,0,0,0,8,6,5,12,0,0,0,0,3,2,4,10,0,0,0,0,0,0,8,8,0,0,0,0,0,0,13,4,0,0,0,0,0,10,16,16,16,7,0,0,0,7,12,10,13,7,0,2 +0,0,11,7,0,0,0,0,0,3,16,4,0,0,0,0,0,2,15,2,0,0,0,0,0,3,16,4,0,0,0,0,0,8,16,16,16,10,1,0,0,5,16,7,3,16,8,0,0,0,13,14,11,16,6,0,0,0,9,16,11,2,0,0,6 +0,0,3,14,16,16,4,0,0,0,4,9,10,16,5,0,0,0,0,0,7,16,2,0,0,0,3,8,13,16,7,0,0,1,16,16,16,16,9,0,0,0,7,11,15,3,0,0,0,0,0,15,12,0,0,0,0,0,5,16,5,0,0,0,7 +0,0,0,6,15,10,1,0,0,0,1,11,16,16,5,0,0,0,8,16,16,16,6,0,0,8,16,16,16,16,6,0,0,0,3,7,16,16,4,0,0,0,0,4,16,16,4,0,0,0,0,6,16,16,7,0,0,0,0,4,14,14,6,0,1 +0,0,4,14,11,2,0,0,0,0,8,11,5,16,0,0,0,0,8,13,9,15,6,0,0,0,3,16,16,12,1,0,0,0,11,14,12,0,0,0,0,4,11,1,15,3,0,0,0,6,11,4,7,12,0,0,0,1,8,14,16,9,0,0,8 +0,0,7,15,13,2,0,0,0,4,16,6,13,8,0,0,0,0,7,1,13,7,0,0,0,0,0,2,16,11,0,0,0,0,0,1,12,15,6,0,0,0,0,0,0,5,11,0,0,3,13,9,7,13,7,0,0,0,6,13,15,9,1,0,3 +0,0,5,12,12,5,0,0,0,0,13,4,5,16,0,0,0,0,15,1,12,14,0,0,0,0,9,16,16,6,0,0,0,0,7,16,16,1,0,0,0,1,13,4,7,11,0,0,0,2,13,1,5,16,0,0,0,0,6,9,9,6,0,0,8 +0,1,11,14,0,0,0,0,0,4,15,14,6,0,0,0,0,6,9,7,8,0,0,0,0,3,5,6,11,0,0,0,0,0,0,9,8,0,0,0,0,0,1,14,7,0,0,0,0,1,15,16,16,16,6,0,0,0,8,8,12,13,4,0,2 +0,0,5,16,16,16,6,0,0,0,5,10,11,16,3,0,0,0,0,0,10,14,0,0,0,0,1,4,16,10,0,0,0,0,10,16,16,16,6,0,0,0,6,14,13,12,3,0,0,0,2,16,6,0,0,0,0,0,7,14,1,0,0,0,7 +0,0,1,9,13,1,0,0,0,0,11,11,13,9,0,0,0,2,15,0,4,16,4,0,0,8,9,0,0,13,6,0,0,5,12,0,0,9,8,0,0,0,15,3,0,8,8,0,0,0,6,14,4,11,7,0,0,0,0,11,16,13,2,0,0 +0,0,5,13,15,12,1,0,0,0,16,12,4,4,1,0,0,6,16,5,4,3,0,0,0,6,16,16,16,15,2,0,0,0,3,0,0,12,6,0,0,0,0,0,0,14,5,0,0,0,0,0,8,16,1,0,0,0,4,14,16,7,0,0,5 +0,0,10,12,12,5,0,0,0,0,8,16,16,14,0,0,0,0,8,16,16,11,0,0,0,0,13,16,16,7,0,0,0,0,9,16,16,1,0,0,0,0,9,16,16,7,0,0,0,1,15,16,16,7,0,0,0,0,7,12,12,9,0,0,1 +0,0,6,13,12,2,0,0,0,6,16,12,16,13,3,0,0,7,13,5,16,16,8,0,0,1,14,16,15,7,0,0,0,0,3,16,14,1,0,0,0,0,4,16,16,11,0,0,0,0,6,16,13,16,4,0,0,0,7,15,12,10,0,0,8 +0,0,4,11,10,2,0,0,0,2,16,5,14,9,0,0,0,1,15,4,13,16,3,0,0,0,4,16,15,4,0,0,0,0,2,16,11,0,0,0,0,0,9,11,14,7,0,0,0,0,14,3,5,15,0,0,0,0,5,8,12,10,0,0,8 +0,0,8,15,12,3,0,0,0,5,16,6,10,14,0,0,0,7,14,7,14,16,2,0,0,0,11,16,13,6,1,0,0,0,11,16,12,0,0,0,0,0,16,7,10,12,0,0,0,4,16,0,2,16,6,0,0,1,10,16,16,15,2,0,8 +0,0,2,9,16,5,0,0,0,0,14,9,10,15,4,0,0,4,16,2,10,16,11,0,0,2,15,16,16,9,0,0,0,0,2,16,16,11,0,0,0,0,10,9,7,16,0,0,0,0,10,11,8,16,0,0,0,0,2,15,14,8,0,0,8 +0,0,0,5,14,0,0,0,0,0,0,14,11,0,0,0,0,0,1,16,5,0,0,0,0,0,5,16,1,0,0,0,0,0,12,16,12,4,0,0,0,0,16,16,12,15,7,0,0,0,7,16,11,12,14,0,0,0,0,5,15,16,11,0,6 +0,0,0,2,12,3,0,0,0,0,0,9,11,0,0,0,0,0,2,15,6,6,7,0,0,0,13,7,0,16,3,0,0,7,15,3,2,16,2,0,0,13,16,16,16,13,1,0,0,1,4,5,16,6,0,0,0,0,0,4,16,3,0,0,4 +0,1,10,16,15,3,0,0,0,8,14,4,16,12,0,0,0,8,13,1,16,12,0,0,0,2,13,16,16,13,0,0,0,0,0,3,9,16,0,0,0,0,6,4,4,16,4,0,0,0,16,13,10,16,5,0,0,0,7,12,13,10,1,0,9 +0,0,0,9,16,9,0,0,0,0,0,14,16,11,0,0,0,0,5,16,16,8,0,0,0,3,15,16,16,6,0,0,0,5,15,16,16,7,0,0,0,0,4,16,16,5,0,0,0,0,1,16,16,12,0,0,0,0,1,12,13,12,0,0,1 +0,0,4,14,15,9,1,0,0,0,0,14,16,16,4,0,0,0,0,15,16,16,0,0,0,0,7,16,16,13,0,0,0,2,16,16,16,12,0,0,0,0,4,15,16,8,0,0,0,0,5,16,16,12,0,0,0,0,4,14,15,5,0,0,1 +0,0,2,15,11,1,0,0,0,0,9,12,14,13,2,0,0,2,15,2,1,13,6,0,0,7,16,0,0,9,8,0,0,4,16,0,0,10,7,0,0,3,16,3,2,14,2,0,0,0,14,14,14,9,0,0,0,0,3,11,13,1,0,0,0 +0,0,11,15,8,0,0,0,0,6,16,10,16,0,0,0,0,2,7,8,16,0,0,0,0,0,0,13,16,2,0,0,0,0,0,8,14,13,1,0,0,0,3,0,3,16,5,0,0,0,16,10,12,15,2,0,0,0,12,15,9,2,0,0,3 +0,0,0,14,8,0,0,0,0,0,3,16,5,0,0,0,0,0,12,12,10,14,0,0,0,4,16,6,13,11,0,0,0,12,16,7,16,14,3,0,0,15,16,16,16,16,6,0,0,2,5,13,16,4,0,0,0,0,0,15,11,0,0,0,4 +0,0,0,5,15,0,0,0,0,0,1,14,12,7,3,0,0,0,10,15,5,16,6,0,0,5,16,7,5,16,3,0,0,12,16,13,15,16,9,0,0,4,12,13,16,13,3,0,0,0,0,7,16,5,0,0,0,0,0,7,15,0,0,0,4 +0,1,11,16,8,0,0,0,0,8,16,5,16,3,0,0,0,8,11,0,13,10,0,0,0,3,15,9,11,15,2,0,0,0,1,7,7,15,7,0,0,0,0,0,0,8,12,0,0,1,10,5,4,11,12,0,0,0,8,15,16,15,6,0,9 +0,0,5,13,1,0,0,0,0,0,13,11,0,0,0,0,0,0,16,2,0,0,0,0,0,5,16,0,0,0,0,0,0,8,15,9,14,6,0,0,0,7,16,16,10,16,2,0,0,1,16,16,4,16,5,0,0,0,4,14,16,13,0,0,6 +0,0,1,11,16,16,13,0,0,0,3,7,4,9,13,0,0,0,0,0,0,10,6,0,0,0,2,4,8,15,6,0,0,0,9,13,15,14,5,0,0,0,1,2,15,0,0,0,0,0,0,12,9,0,0,0,0,0,2,15,3,0,0,0,7 +0,0,5,12,14,16,8,0,0,0,16,16,13,12,4,0,0,0,16,14,8,2,0,0,0,1,16,16,16,15,2,0,0,5,16,9,6,16,4,0,0,0,0,0,4,16,4,0,0,0,3,12,15,15,2,0,0,0,5,13,13,2,0,0,5 +0,0,2,12,15,5,0,0,0,0,11,9,9,15,3,0,0,0,15,1,0,15,4,0,0,4,10,0,0,13,3,0,0,3,14,0,0,12,8,0,0,0,15,3,0,13,5,0,0,0,10,13,5,16,2,0,0,0,1,13,16,8,0,0,0 +0,1,13,9,0,0,0,0,0,10,14,15,2,0,0,0,0,8,6,10,6,0,0,0,0,2,3,10,5,0,0,0,0,0,3,16,2,0,0,0,0,0,6,11,0,0,0,0,0,4,16,15,12,12,6,0,0,0,10,12,12,13,11,0,2 +0,2,12,8,0,0,0,0,0,11,16,16,0,0,0,0,0,7,8,14,6,0,0,0,0,1,2,12,8,0,0,0,0,0,0,14,4,0,0,0,0,0,2,16,1,0,0,0,0,2,13,16,9,15,11,0,0,2,14,16,13,15,9,0,2 +0,0,13,10,8,8,3,0,0,3,16,16,13,11,3,0,0,5,16,8,0,0,0,0,0,6,16,16,15,6,0,0,0,0,3,2,10,15,3,0,0,0,0,0,5,16,2,0,0,0,4,8,16,8,0,0,0,0,14,14,4,0,0,0,5 +0,0,2,11,15,16,15,0,0,0,8,13,12,16,13,0,0,0,0,0,6,16,5,0,0,0,0,6,13,16,7,0,0,0,9,16,16,16,9,0,0,0,5,9,16,5,0,0,0,0,0,11,16,1,0,0,0,0,1,16,9,0,0,0,7 +0,0,10,16,15,12,0,0,0,0,14,9,4,4,0,0,0,0,16,0,0,0,0,0,0,5,16,12,12,8,0,0,0,7,15,10,8,15,6,0,0,2,1,0,2,16,4,0,0,0,3,10,14,12,1,0,0,0,10,14,6,0,0,0,5 +0,0,6,13,15,8,0,0,0,4,16,12,12,16,5,0,0,6,16,2,5,16,5,0,0,1,15,13,13,16,1,0,0,0,0,7,13,16,1,0,0,0,0,0,4,16,0,0,0,4,16,11,7,16,0,0,0,1,10,13,16,13,0,0,9 +0,0,0,9,16,11,0,0,0,0,6,15,12,16,6,0,0,0,16,11,0,9,12,0,0,5,16,3,0,8,8,0,0,4,16,0,0,8,8,0,0,2,16,7,0,12,5,0,0,0,11,16,14,16,1,0,0,0,1,9,13,6,0,0,0 +0,0,5,15,2,0,0,0,0,1,15,10,0,0,0,0,0,8,16,2,0,0,0,0,0,10,13,1,4,6,0,0,0,8,16,13,16,15,9,0,0,6,16,13,1,4,12,0,0,1,16,15,3,8,11,0,0,0,5,12,16,16,4,0,6 +0,0,1,12,16,2,0,0,0,0,5,16,15,0,0,0,0,0,13,16,11,0,0,0,0,8,16,16,12,0,0,0,0,7,10,16,15,0,0,0,0,0,0,14,16,2,0,0,0,0,0,13,16,7,0,0,0,0,0,12,16,9,0,0,1 +0,0,5,8,11,13,10,0,0,0,12,12,7,4,4,0,0,3,12,0,4,0,0,0,0,5,16,16,16,14,0,0,0,1,7,1,0,14,6,0,0,0,0,0,0,12,4,0,0,7,11,1,6,12,1,0,0,0,8,13,10,2,0,0,5 +0,0,2,12,13,7,0,0,0,2,14,5,0,13,2,0,0,10,6,0,2,16,4,0,0,6,13,7,9,16,4,0,0,0,8,12,8,13,4,0,0,0,0,0,0,7,7,0,0,0,0,0,0,4,11,0,0,0,1,14,11,13,12,0,9 +0,0,8,16,15,9,0,0,0,2,15,5,3,16,6,0,0,8,12,0,3,16,8,0,0,8,15,10,14,16,5,0,0,1,9,10,3,15,7,0,0,0,0,0,0,15,5,0,0,0,1,4,0,13,7,0,0,0,5,16,16,16,2,0,9 +0,0,3,15,15,3,0,0,0,2,14,10,13,9,0,0,0,6,16,1,6,12,0,0,0,9,14,7,14,13,0,0,0,2,12,12,14,14,0,0,0,0,0,0,6,16,0,0,0,0,0,4,5,16,5,0,0,0,2,12,16,16,4,0,9 +0,0,0,7,16,2,0,1,0,0,0,11,13,1,13,8,0,0,6,16,4,8,15,1,0,0,15,16,16,16,12,0,0,0,8,8,13,16,3,0,0,0,0,0,13,11,0,0,0,0,0,4,16,4,0,0,0,0,0,9,12,0,0,0,4 +0,0,0,8,16,16,16,9,0,0,0,8,8,9,16,8,0,0,0,0,0,7,16,1,0,0,0,0,1,12,14,0,0,0,3,14,16,16,6,0,0,0,4,6,11,10,0,0,0,0,0,3,15,3,0,0,0,0,0,10,11,0,0,0,7 +0,0,0,8,15,1,0,0,0,0,0,8,16,5,0,0,0,0,0,14,16,4,0,0,0,0,8,16,16,4,0,0,0,2,15,12,16,6,0,0,0,3,8,1,16,10,0,0,0,0,0,5,15,16,1,0,0,0,0,9,16,16,11,0,1 +0,0,1,9,16,16,16,8,0,0,7,15,9,12,13,1,0,0,0,0,0,14,6,0,0,0,0,0,5,15,1,0,0,0,13,16,16,16,8,0,0,1,9,8,14,5,0,0,0,0,0,7,13,0,0,0,0,0,0,14,6,0,0,0,7 +0,2,14,16,9,0,0,0,0,10,15,10,16,0,0,0,0,8,12,0,16,5,0,0,0,3,3,3,16,5,0,0,0,0,0,5,16,1,0,0,0,0,0,11,13,0,0,0,0,0,7,16,13,11,11,1,0,1,15,16,16,16,16,5,2 +0,1,12,16,16,8,0,0,0,8,14,8,16,8,0,0,0,1,2,9,15,3,0,0,0,0,12,15,3,0,0,0,0,0,10,15,9,1,0,0,0,0,1,9,16,11,0,0,0,0,1,0,3,16,4,0,0,2,15,16,16,15,4,0,3 +0,0,1,16,13,0,0,0,0,0,3,16,15,0,0,0,0,0,12,16,16,0,0,0,0,3,16,16,16,0,0,0,0,7,7,12,16,0,0,0,0,0,0,7,16,3,0,0,0,0,0,10,16,14,1,0,0,0,2,13,16,16,12,0,1 +0,0,0,7,15,1,0,0,0,0,1,15,5,2,4,0,0,0,10,10,2,16,6,0,0,4,16,3,9,14,0,0,1,15,14,12,15,10,0,0,5,15,12,11,16,4,0,0,0,0,0,6,15,1,0,0,0,0,0,9,12,0,0,0,4 +0,0,9,15,15,3,0,0,0,8,15,11,16,6,0,0,0,2,1,12,15,1,0,0,0,0,6,16,7,0,0,0,0,0,2,15,15,3,0,0,0,0,0,1,7,15,5,0,0,0,14,1,0,12,12,0,0,0,8,16,16,16,10,0,3 +0,0,11,16,10,0,0,0,0,2,16,14,15,0,0,0,0,0,13,7,16,1,0,0,0,0,0,6,16,1,0,0,0,0,0,10,12,0,0,0,0,0,1,15,8,0,0,0,0,0,12,16,15,10,4,0,0,0,11,11,8,13,16,6,2 +0,0,0,3,15,5,0,0,0,0,1,12,15,1,6,5,0,0,10,16,2,5,16,4,0,2,16,10,0,13,10,0,0,12,16,11,11,16,1,0,0,4,14,16,16,9,0,0,0,0,0,7,16,1,0,0,0,0,0,6,14,0,0,0,4 +0,0,8,16,16,8,0,0,0,6,14,8,16,9,0,0,0,3,3,9,13,3,0,0,0,0,0,11,15,2,0,0,0,0,0,1,13,13,1,0,0,0,0,0,1,16,5,0,0,0,7,8,4,12,7,0,0,0,10,16,16,16,4,0,3 +0,0,0,2,15,5,0,0,0,0,0,10,14,0,3,0,0,0,3,16,3,6,16,2,0,1,14,8,0,14,10,0,1,12,14,8,11,16,5,0,5,16,16,15,16,11,1,0,1,3,0,1,16,6,0,0,0,0,0,5,15,1,0,0,4 +0,0,9,16,15,1,0,0,0,6,14,9,16,4,0,0,0,3,4,11,15,0,0,0,0,0,8,16,1,0,0,0,0,0,3,14,10,0,0,0,0,0,0,2,15,8,0,0,0,0,5,6,5,16,5,0,0,0,13,16,16,16,5,0,3 +0,1,14,15,2,0,0,0,0,9,15,15,12,0,0,0,0,9,12,8,15,0,0,0,0,3,5,9,10,0,0,0,0,0,0,11,11,0,0,0,0,0,1,15,7,0,0,0,0,0,7,16,11,7,4,0,0,1,15,16,16,16,16,5,2 +0,0,12,16,10,0,0,0,0,3,16,6,14,9,0,0,0,7,14,0,3,15,3,0,0,9,12,0,0,8,10,0,0,8,12,0,0,4,12,0,0,7,13,0,0,5,12,0,0,2,16,6,2,13,9,0,0,0,9,16,16,13,0,0,0 +0,0,15,16,13,2,0,0,0,3,15,2,10,15,2,0,0,8,12,0,0,10,8,0,0,8,12,0,0,4,12,0,0,9,12,0,0,4,12,0,0,8,12,0,0,9,8,0,0,6,15,4,11,14,2,0,0,0,12,16,15,3,0,0,0 +0,0,11,16,16,16,6,0,0,4,16,13,10,4,2,0,0,4,16,0,0,0,0,0,0,1,15,10,0,0,0,0,0,0,6,15,9,0,0,0,0,2,1,3,16,1,0,0,0,9,11,5,16,2,0,0,0,1,11,16,14,0,0,0,5 +0,0,2,14,11,0,0,0,0,0,13,7,9,6,0,0,0,0,12,0,1,12,2,0,0,3,12,0,2,15,4,0,0,1,13,14,12,14,7,0,0,0,0,0,0,12,4,0,0,0,0,0,0,10,3,0,0,0,2,14,12,14,0,0,9 +0,0,0,14,7,0,0,0,0,0,0,16,12,0,0,0,0,0,5,16,13,0,0,0,0,1,14,14,16,1,0,0,0,3,3,1,16,5,0,0,0,0,0,0,11,8,0,0,0,0,0,3,13,13,0,0,0,0,0,12,16,16,8,0,1 +0,0,6,16,15,2,0,0,0,1,16,10,7,2,0,0,0,6,15,0,0,0,0,0,0,8,15,0,0,0,0,0,0,9,12,5,11,4,0,0,0,5,15,16,14,14,10,0,0,0,11,15,1,6,16,0,0,0,5,12,16,16,8,0,6 +0,0,0,7,15,6,0,0,0,0,7,16,4,1,3,0,0,2,16,10,0,14,13,0,0,6,16,7,4,16,8,0,0,6,16,16,14,16,6,0,0,1,7,9,16,13,0,0,0,0,0,4,16,7,0,0,0,0,0,10,12,0,0,0,4 +0,4,16,16,6,0,0,0,0,4,14,8,16,0,0,0,0,3,8,5,16,0,0,0,0,0,0,5,16,0,0,0,0,0,0,14,9,0,0,0,0,0,5,15,2,0,0,0,0,1,13,15,11,9,7,0,0,6,16,14,12,12,12,0,2 +0,1,13,16,15,1,0,0,0,5,13,10,16,5,0,0,0,0,0,10,14,1,0,0,0,0,7,16,3,0,0,0,0,0,4,16,13,1,0,0,0,0,0,5,16,12,0,0,0,0,6,6,8,16,6,0,0,0,15,16,16,15,1,0,3 +0,2,13,16,13,9,2,0,0,1,13,5,4,4,2,0,0,0,12,5,0,0,0,0,0,0,14,16,13,3,0,0,0,0,1,4,8,12,0,0,0,0,0,0,3,16,0,0,0,7,8,4,8,13,0,0,0,3,10,14,12,4,0,0,5 +0,0,11,16,16,10,1,0,0,8,16,10,8,10,6,0,0,14,7,0,0,0,0,0,0,9,14,0,0,0,0,0,0,2,15,10,0,0,0,0,0,0,2,15,12,0,0,0,0,3,11,10,16,1,0,0,0,1,11,16,16,2,0,0,5 +0,0,0,13,15,2,0,0,0,0,4,16,14,1,0,0,0,0,15,16,11,0,0,0,0,6,9,16,14,0,0,0,0,0,0,10,15,2,0,0,0,0,0,6,16,4,0,0,0,0,0,1,16,11,0,0,0,0,0,15,16,15,0,0,1 +0,0,11,14,4,0,0,0,0,3,13,2,12,0,0,0,0,4,12,2,8,0,0,0,0,0,12,12,6,0,0,0,0,0,8,16,10,1,0,0,0,2,15,1,5,10,8,0,0,0,10,0,0,2,12,0,0,0,9,11,12,14,4,0,8 +0,0,1,10,16,16,4,0,0,0,7,8,6,16,7,0,0,0,0,0,3,16,5,0,0,1,4,4,10,14,3,0,0,12,16,16,16,10,5,0,0,2,0,9,13,1,0,0,0,0,1,16,7,0,0,0,0,0,0,15,4,0,0,0,7 +0,0,0,10,16,16,16,9,0,0,0,2,5,10,16,12,0,0,0,0,0,6,16,2,0,0,0,0,0,14,10,0,0,1,8,14,16,16,5,0,0,5,10,8,16,10,1,0,0,0,0,5,14,1,0,0,0,0,0,13,9,0,0,0,7 +0,0,0,4,15,6,0,3,0,0,0,13,14,1,11,11,0,0,8,16,4,4,16,4,0,2,16,6,3,11,13,0,0,12,16,16,16,16,7,0,0,11,9,7,13,14,1,0,0,0,0,1,14,5,0,0,0,0,0,7,12,0,0,0,4 +0,1,10,16,16,16,16,5,0,1,16,13,6,1,1,0,0,4,16,15,10,3,0,0,0,1,8,11,16,16,2,0,0,0,1,0,1,14,7,0,0,7,9,0,0,12,8,0,0,5,16,3,0,15,5,0,0,0,9,16,16,13,2,0,5 +0,0,8,16,12,1,0,0,0,3,15,5,13,13,0,0,0,10,12,0,1,15,6,0,0,12,9,0,0,7,12,0,0,12,10,0,0,5,12,0,0,8,14,0,0,7,12,0,0,3,16,9,5,15,5,0,0,0,9,16,16,11,0,0,0 +0,0,6,16,16,10,0,0,0,1,15,11,6,15,3,0,0,7,16,2,0,11,9,0,0,12,14,0,0,9,11,0,0,8,16,0,0,8,12,0,0,4,16,5,0,9,11,0,0,1,14,13,6,16,3,0,0,0,5,15,16,6,0,0,0 +0,0,4,16,14,0,0,0,0,0,8,16,6,0,0,0,0,1,15,16,4,0,0,0,0,10,16,16,4,0,0,0,0,0,5,16,10,0,0,0,0,0,1,16,12,0,0,0,0,0,1,15,16,6,1,0,0,0,6,16,16,16,4,0,1 +0,0,1,11,16,16,15,1,0,0,6,9,8,14,14,0,0,0,0,0,0,12,10,0,0,0,1,4,6,16,4,0,0,0,10,16,16,15,7,0,0,0,3,5,16,6,0,0,0,0,0,9,15,0,0,0,0,0,1,15,4,0,0,0,7 +0,0,12,16,9,0,0,0,0,3,16,7,5,0,0,0,0,11,13,0,0,0,0,0,0,9,12,0,0,0,0,0,0,9,13,10,15,8,0,0,0,5,16,15,8,11,10,0,0,4,16,12,1,5,16,0,0,1,10,15,16,16,10,0,6 +0,0,4,13,15,2,0,0,0,2,16,11,10,8,1,0,0,8,15,1,0,13,10,0,0,8,15,9,13,16,9,0,0,0,10,16,12,16,4,0,0,0,0,0,4,16,4,0,0,0,0,2,5,16,7,0,0,0,4,16,16,16,5,0,9 +0,0,0,3,14,7,0,0,0,0,0,12,12,0,3,8,0,0,9,14,0,2,16,6,0,4,16,2,0,10,14,0,1,15,8,3,8,16,4,0,7,16,16,16,16,12,0,0,2,9,5,2,15,3,0,0,0,0,0,5,14,0,0,0,4 +0,0,13,15,4,0,0,0,0,8,15,8,10,0,0,0,0,11,12,0,0,0,0,0,0,12,7,0,4,1,0,0,0,11,10,11,16,14,3,0,0,4,16,16,6,7,14,0,0,1,16,14,1,1,14,3,0,0,10,13,16,15,15,0,6 +0,0,7,15,13,9,2,0,0,4,15,0,0,10,9,0,0,1,15,5,3,13,5,0,0,0,7,16,13,1,0,0,0,0,10,12,15,4,0,0,0,0,8,1,8,13,0,0,0,11,11,4,1,16,0,0,0,1,9,12,16,12,0,0,8 +0,0,7,13,14,2,0,0,0,5,12,0,4,7,0,0,0,8,8,0,0,16,3,0,0,5,11,0,1,16,1,0,0,0,10,13,14,15,5,0,0,0,0,0,0,10,8,0,0,0,0,0,0,11,6,0,0,0,6,13,12,9,0,0,9 +0,0,7,15,3,0,0,0,0,0,15,6,0,0,0,0,0,6,13,0,0,0,0,0,0,7,13,0,0,0,0,0,0,8,11,7,13,11,2,0,0,3,16,15,5,4,11,0,0,0,16,6,0,7,11,0,0,0,8,12,15,10,1,0,6 +0,2,12,16,16,3,0,0,0,11,10,5,16,4,0,0,0,0,0,11,13,0,0,0,0,0,1,15,9,0,0,0,0,0,0,4,16,5,0,0,0,0,0,0,6,15,3,0,0,0,3,1,0,11,11,0,0,1,16,16,16,16,9,0,3 +0,0,1,15,16,13,1,0,0,3,11,11,3,13,9,0,0,12,13,0,0,8,12,0,0,9,15,0,0,8,12,0,0,4,16,2,0,8,12,0,0,2,15,9,0,8,12,0,0,0,10,16,5,14,8,0,0,0,1,12,16,15,1,0,0 +0,0,6,12,12,2,0,0,0,4,14,0,9,6,0,0,0,5,13,0,12,3,0,0,0,0,7,14,12,0,0,0,0,0,1,12,13,10,0,0,0,0,10,3,1,12,6,0,0,7,10,0,0,11,8,0,0,1,10,13,12,9,1,0,8 +0,0,9,13,16,6,0,0,0,2,16,14,16,2,0,0,0,0,2,14,8,0,0,0,0,0,4,16,13,6,0,0,0,0,0,4,12,16,6,0,0,0,0,0,0,13,7,0,0,0,5,7,12,15,1,0,0,0,12,16,11,1,0,0,3 +0,1,12,16,3,0,0,0,0,5,14,14,4,0,0,0,0,9,7,7,8,0,0,0,0,6,2,8,8,0,0,0,0,0,0,10,5,0,0,0,0,0,0,14,2,0,0,0,0,0,9,16,11,8,5,0,0,2,14,11,12,16,9,0,2 +0,0,8,16,14,4,0,0,0,1,15,7,9,15,2,0,0,8,11,0,0,12,9,0,0,9,5,0,0,6,12,0,0,9,8,0,0,4,12,0,0,8,12,0,0,9,12,0,0,3,16,6,7,15,5,0,0,0,8,16,16,12,0,0,0 +0,0,5,14,9,0,0,0,0,1,14,4,9,0,0,0,0,0,16,0,6,4,0,0,0,0,13,3,13,2,0,0,0,0,1,13,13,0,0,0,0,0,4,14,9,13,3,0,0,0,13,3,0,3,15,0,0,0,5,12,12,15,4,0,8 +0,0,1,15,16,16,15,0,0,0,0,8,8,14,10,0,0,0,0,0,4,15,1,0,0,0,0,3,12,12,2,0,0,2,12,16,16,14,7,0,0,3,6,9,10,0,0,0,0,0,0,13,6,0,0,0,0,0,2,16,2,0,0,0,7 +0,2,15,9,0,0,0,0,0,8,13,15,0,0,0,0,0,10,4,14,3,0,0,0,0,11,2,12,4,0,0,0,0,1,0,12,4,0,0,0,0,0,2,16,0,0,0,0,0,0,11,14,6,6,5,0,0,2,16,16,16,16,9,0,2 +0,0,6,13,12,2,0,0,0,0,14,2,2,10,0,0,0,4,10,0,6,13,0,0,0,4,10,0,8,12,0,0,0,0,11,12,13,13,0,0,0,0,0,3,0,11,0,0,0,0,0,0,0,9,4,0,0,0,5,16,16,13,2,0,9 +0,0,8,12,12,0,0,0,0,5,14,2,9,8,0,0,0,4,14,1,11,4,0,0,0,0,7,14,12,1,0,0,0,0,11,11,12,4,0,0,0,3,14,0,2,12,5,0,0,4,12,0,0,1,12,0,0,0,9,11,12,12,4,0,8 +0,2,15,13,11,8,1,0,0,7,16,14,13,12,3,0,0,8,16,4,0,0,0,0,0,4,16,16,14,3,0,0,0,0,3,4,13,14,0,0,0,0,0,0,1,15,7,0,0,0,7,3,2,13,10,0,0,2,15,16,16,16,9,0,5 +0,0,8,16,9,0,0,0,0,2,15,9,15,3,0,0,0,6,14,0,11,11,0,0,0,7,15,10,16,14,0,0,0,2,13,11,5,16,3,0,0,0,0,0,0,13,6,0,0,0,0,1,4,14,4,0,0,0,10,16,13,5,0,0,9 +0,0,1,11,16,16,4,0,0,0,8,10,8,16,3,0,0,0,0,0,3,14,0,0,0,0,0,0,8,8,0,0,0,0,7,11,16,16,8,0,0,11,13,12,11,0,0,0,0,0,0,8,9,0,0,0,0,0,0,14,2,0,0,0,7 +0,0,8,12,16,15,8,0,0,3,15,3,0,0,0,0,0,4,12,0,0,0,0,0,0,4,14,12,12,3,0,0,0,0,7,5,5,14,2,0,0,3,2,0,0,11,4,0,0,2,11,0,0,13,2,0,0,0,11,13,12,5,0,0,5 +0,0,9,15,2,0,0,0,0,3,16,10,1,0,0,0,0,7,14,0,0,0,0,0,0,9,11,3,8,8,1,0,0,10,11,13,14,15,8,0,0,7,16,14,0,7,12,0,0,3,16,10,4,13,10,0,0,0,10,16,16,13,1,0,6 +0,0,0,8,16,6,0,0,0,0,5,15,16,1,0,0,0,5,16,16,13,0,0,0,0,4,9,16,14,0,0,0,0,0,0,16,14,0,0,0,0,0,0,12,15,0,0,0,0,0,0,10,16,5,0,0,0,0,0,6,16,15,1,0,1 +0,0,12,16,16,15,2,0,0,0,5,8,10,16,7,0,0,0,0,0,8,15,1,0,0,0,10,13,15,15,8,0,0,0,12,16,15,12,5,0,0,0,1,14,6,0,0,0,0,0,6,16,2,0,0,0,0,0,12,11,0,0,0,0,7 +0,0,9,14,8,0,0,0,0,7,12,6,15,3,0,0,0,3,1,0,12,8,0,0,0,0,0,7,15,11,0,0,0,0,0,10,11,15,5,0,0,0,0,0,0,9,8,0,0,0,4,7,4,14,5,0,0,0,8,14,16,14,1,0,3 +0,0,11,12,12,14,4,0,0,0,16,8,4,4,5,0,0,2,16,8,4,0,0,0,0,5,16,15,15,9,0,0,0,1,4,1,4,16,3,0,0,0,0,0,0,14,7,0,0,3,7,6,9,16,2,0,0,2,11,12,13,7,0,0,5 +0,3,13,12,12,13,2,0,0,3,16,8,5,7,3,0,0,3,14,2,3,0,0,0,0,4,16,15,16,9,0,0,0,0,4,0,2,15,2,0,0,0,0,0,0,12,5,0,0,1,6,4,6,15,5,0,0,3,13,16,16,10,0,0,5 +0,0,5,14,11,3,0,0,0,2,16,14,16,14,0,0,0,4,13,4,2,13,4,0,0,8,9,0,0,8,8,0,0,8,10,0,0,7,8,0,0,4,13,0,0,9,8,0,0,0,14,9,4,14,6,0,0,0,4,16,16,8,0,0,0 +0,0,0,3,15,1,0,0,0,0,0,7,14,0,0,0,0,0,0,12,10,0,0,0,0,0,3,15,3,0,0,0,0,0,11,14,8,6,0,0,0,5,16,11,16,11,0,0,1,13,16,16,16,16,4,0,0,4,4,7,16,5,0,0,4 +0,2,12,12,13,12,1,0,0,8,14,8,4,6,0,0,0,8,14,8,7,1,0,0,0,8,15,12,14,12,0,0,0,0,0,0,1,14,4,0,0,0,0,0,0,12,8,0,0,2,8,4,6,15,7,0,0,0,11,16,15,7,0,0,5 +0,1,12,12,1,0,0,0,0,4,14,12,10,0,0,0,0,7,11,4,12,0,0,0,0,2,12,7,12,0,0,0,0,0,0,8,12,0,0,0,0,0,1,14,9,0,0,0,0,0,13,16,16,15,6,0,0,0,9,12,12,12,7,0,2 +0,0,0,5,15,1,0,0,0,0,0,11,13,0,0,0,0,0,0,14,10,0,0,0,0,0,7,15,4,0,0,0,0,1,13,9,13,6,0,0,1,12,16,14,16,14,3,0,1,11,12,14,16,12,1,0,0,0,0,8,15,0,0,0,4 +0,0,0,13,5,0,0,0,0,0,8,15,3,0,0,0,0,0,12,8,0,0,0,0,0,3,16,13,12,5,0,0,0,5,16,12,12,15,5,0,0,4,12,0,0,8,12,0,0,1,13,10,5,12,14,0,0,0,2,11,14,12,2,0,6 +0,0,0,0,11,9,1,0,0,0,0,0,14,15,0,0,0,1,7,11,16,12,0,0,0,6,15,12,16,13,0,0,0,0,0,0,16,12,0,0,0,0,0,0,13,15,1,0,0,0,0,0,12,16,2,0,0,0,0,0,9,16,4,0,1 +0,0,0,9,6,0,0,0,0,0,3,15,5,0,0,0,0,0,14,9,0,0,0,0,0,2,16,6,8,2,0,0,0,5,16,16,12,15,4,0,0,3,16,5,0,5,12,0,0,0,8,14,3,3,16,0,0,0,0,9,16,16,10,0,6 +0,0,1,13,4,0,0,0,0,0,10,11,0,0,0,0,0,1,16,3,0,0,0,0,0,3,16,16,10,3,0,0,0,6,16,11,10,14,1,0,0,4,16,1,0,7,11,0,0,1,13,7,0,9,12,0,0,0,1,13,16,15,5,0,6 +0,1,8,15,13,1,0,0,0,6,14,9,16,4,0,0,0,3,6,1,14,4,0,0,0,0,4,14,16,3,0,0,0,0,9,12,14,16,1,0,0,0,0,0,0,16,8,0,0,3,12,11,10,16,7,0,0,1,10,14,15,10,0,0,3 +0,0,4,13,16,7,0,0,0,1,15,13,16,15,1,0,0,5,13,0,7,14,4,0,0,6,9,0,0,8,7,0,0,5,11,0,0,8,8,0,0,4,13,0,0,8,8,0,0,0,14,12,6,16,4,0,0,0,3,16,15,5,0,0,0 +0,0,7,15,11,0,0,0,0,5,14,4,12,14,3,0,0,8,11,0,0,16,4,0,0,2,15,9,11,16,4,0,0,0,5,10,8,12,8,0,0,0,0,0,0,9,8,0,0,0,12,7,4,15,7,0,0,0,7,16,15,8,0,0,9 +0,0,7,15,16,16,9,0,0,2,15,6,15,15,11,0,0,1,13,0,7,15,3,0,0,1,14,12,15,3,0,0,0,0,10,16,16,4,0,0,0,2,13,2,10,12,0,0,0,1,16,5,8,16,0,0,0,0,8,16,16,7,0,0,8 +0,0,7,15,12,1,0,0,0,3,15,5,10,11,0,0,0,0,8,0,4,16,0,0,0,0,0,5,13,9,0,0,0,0,0,8,13,13,1,0,0,0,0,0,1,14,6,0,0,5,15,3,0,15,8,0,0,0,9,16,16,14,3,0,3 +0,0,0,15,4,0,0,0,0,0,8,14,0,0,0,0,0,0,14,7,0,0,0,0,0,1,16,9,8,2,0,0,0,2,16,16,15,15,2,0,0,3,16,7,0,6,12,0,0,0,11,12,4,7,15,0,0,0,1,9,16,16,6,0,6 +0,0,11,16,16,12,5,0,0,1,16,10,8,8,5,0,0,2,16,10,7,1,0,0,0,3,16,13,14,12,1,0,0,0,5,0,2,15,5,0,0,0,0,0,0,13,7,0,0,0,6,5,10,16,2,0,0,0,12,14,12,6,0,0,5 +0,0,10,16,15,13,10,0,0,0,4,4,6,16,6,0,0,0,0,0,8,13,0,0,0,0,9,16,16,16,6,0,0,0,5,13,14,13,5,0,0,0,1,14,3,0,0,0,0,0,6,16,0,0,0,0,0,0,9,10,0,0,0,0,7 +0,3,16,15,4,0,0,0,0,8,13,12,12,0,0,0,0,9,8,8,12,0,0,0,0,1,5,5,16,0,0,0,0,0,0,11,12,0,0,0,0,0,1,16,8,0,0,0,0,3,14,16,13,15,12,0,0,3,16,16,16,16,15,0,2 +0,0,4,13,7,4,4,0,0,0,12,12,13,16,6,0,0,0,11,0,6,13,0,0,0,0,4,4,13,5,0,0,0,0,16,16,16,16,4,0,0,0,0,12,10,10,1,0,0,0,2,14,2,0,0,0,0,0,6,10,0,0,0,0,7 +0,0,6,15,11,3,0,0,0,1,14,16,16,14,0,0,0,4,16,1,9,16,2,0,0,6,16,0,0,13,7,0,0,5,13,0,0,12,8,0,0,4,16,1,0,14,8,0,0,0,14,10,10,15,1,0,0,0,4,13,16,8,0,0,0 +0,0,2,13,6,0,0,0,0,0,11,11,3,0,0,0,0,1,16,3,0,0,0,0,0,3,16,11,5,0,0,0,0,3,16,15,4,6,3,0,0,2,16,4,0,7,10,0,0,0,13,8,0,8,13,0,0,0,3,12,16,16,6,0,6 +0,0,6,12,13,4,0,0,0,1,16,5,8,15,3,0,0,4,13,0,0,16,5,0,0,2,15,9,11,16,7,0,0,0,1,8,8,14,8,0,0,3,5,0,0,11,8,0,0,2,16,9,6,15,7,0,0,0,3,14,15,6,0,0,9 +0,0,0,1,16,8,0,0,0,0,0,5,16,8,0,0,0,0,5,12,16,4,0,0,0,8,16,16,16,3,0,0,0,0,0,9,16,5,0,0,0,0,0,11,16,6,0,0,0,0,0,6,16,12,0,0,0,0,0,2,16,14,1,0,1 +0,0,12,13,14,5,0,0,0,0,16,9,8,6,0,0,0,3,16,2,3,0,0,0,0,5,16,16,16,8,0,0,0,0,2,0,4,16,3,0,0,0,0,0,0,15,8,0,0,0,3,0,4,16,7,0,0,0,11,16,16,13,1,0,5 +0,0,6,13,13,12,2,0,0,4,15,5,6,11,8,0,0,8,11,0,0,10,6,0,0,3,14,13,13,13,1,0,0,0,8,16,14,9,0,0,0,0,16,4,1,15,0,0,0,0,15,5,6,16,0,0,0,0,5,15,16,9,0,0,8 +0,0,5,15,13,3,0,0,0,0,15,7,10,15,1,0,0,0,16,4,2,15,8,0,0,1,10,16,16,16,4,0,0,0,0,0,0,12,7,0,0,5,2,0,3,16,5,0,0,7,14,8,14,14,1,0,0,1,8,16,11,1,0,0,9 +0,0,8,13,4,0,0,0,0,2,16,11,15,1,0,0,0,0,12,0,11,5,0,0,0,0,4,2,9,9,0,0,0,0,0,0,12,11,0,0,0,0,0,6,15,4,0,0,0,0,6,16,13,5,8,0,0,0,7,16,16,16,16,5,2 +0,0,3,14,12,11,11,0,0,0,7,10,7,13,11,0,0,0,15,3,4,14,0,0,0,0,4,0,10,7,0,0,0,0,11,15,16,16,3,0,0,0,3,10,11,4,0,0,0,0,0,14,5,0,0,0,0,0,3,16,0,0,0,0,7 +0,0,0,8,10,0,0,0,0,0,0,15,7,0,0,0,0,0,6,15,1,0,0,0,0,1,14,7,4,2,0,0,0,6,14,4,15,2,0,0,0,14,15,13,16,12,3,0,0,15,12,15,15,11,1,0,0,0,0,13,8,0,0,0,4 +0,0,0,0,8,13,0,0,0,0,0,0,16,16,0,0,0,0,0,1,16,14,0,0,0,4,12,15,16,12,0,0,0,0,3,4,16,14,0,0,0,0,0,0,15,16,0,0,0,0,0,0,12,16,2,0,0,0,0,0,11,16,4,0,1 +0,1,9,13,14,13,8,0,0,4,14,4,4,4,2,0,0,4,12,0,0,0,0,0,0,5,15,16,16,6,0,0,0,0,4,0,4,16,3,0,0,0,0,0,0,13,7,0,0,0,3,2,5,16,6,0,0,0,10,16,15,8,1,0,5 +0,0,0,1,11,10,0,0,0,0,0,6,16,15,0,0,0,4,13,16,16,11,0,0,0,3,8,10,16,10,0,0,0,0,0,4,16,12,0,0,0,0,0,0,16,14,0,0,0,0,0,0,16,16,5,0,0,0,0,0,12,13,6,0,1 +0,0,7,14,9,3,0,0,0,0,15,7,15,15,1,0,0,4,12,0,3,13,6,0,0,4,12,0,0,8,8,0,0,5,8,0,0,5,8,0,0,4,12,0,0,8,7,0,0,2,15,6,6,15,2,0,0,0,6,15,16,8,0,0,0 +0,0,7,12,14,16,8,0,0,0,16,11,8,8,4,0,0,0,16,1,5,3,0,0,0,5,16,16,16,13,1,0,0,2,9,4,2,14,6,0,0,0,0,0,0,15,5,0,0,0,2,8,10,16,4,0,0,0,6,15,12,6,0,0,5 +0,0,4,13,0,0,0,0,0,0,11,9,0,0,0,0,0,3,16,0,0,0,0,0,0,4,16,13,14,6,0,0,0,8,16,10,7,16,3,0,0,4,15,0,0,8,10,0,0,0,15,8,1,9,12,0,0,0,4,13,16,14,3,0,6 +0,0,12,14,16,12,0,0,0,1,15,9,5,9,0,0,0,4,16,12,12,5,0,0,0,0,5,5,7,16,3,0,0,0,0,0,0,12,8,0,0,0,0,0,0,9,11,0,0,0,11,6,5,14,12,0,0,0,9,16,16,14,3,0,5 +0,0,4,13,13,13,3,0,0,3,16,9,12,16,8,0,0,7,14,0,4,16,5,0,0,2,15,13,15,11,0,0,0,0,10,16,16,10,0,0,0,1,16,5,3,16,4,0,0,0,15,10,5,16,7,0,0,0,5,16,16,9,0,0,8 +0,0,2,12,1,0,0,0,0,0,11,12,0,0,0,0,0,0,16,5,0,0,0,0,0,2,16,5,4,1,0,0,0,2,16,16,16,13,2,0,0,1,15,8,0,7,12,0,0,0,8,9,2,5,15,0,0,0,0,11,16,14,8,0,6 +0,0,5,12,12,3,0,0,0,1,16,8,13,14,3,0,0,2,16,1,3,16,9,0,0,2,14,11,14,16,6,0,0,0,2,7,5,16,8,0,0,2,3,0,1,15,5,0,0,5,16,5,8,15,2,0,0,0,10,16,13,4,0,0,9 +0,0,6,14,15,12,11,0,0,2,15,5,4,4,3,0,0,2,16,11,12,7,0,0,0,5,11,6,6,16,3,0,0,0,0,0,0,13,7,0,0,0,0,0,2,16,3,0,0,4,15,5,12,15,0,0,0,0,9,15,11,1,0,0,5 +0,0,0,14,3,0,0,0,0,0,7,15,3,0,0,0,0,0,15,7,0,0,0,0,0,2,16,9,7,2,0,0,0,2,16,13,12,14,5,0,0,0,15,3,0,3,13,0,0,0,11,10,3,4,15,2,0,0,0,9,16,16,13,1,6 +0,0,9,13,14,1,0,0,0,2,16,4,16,13,0,0,0,3,16,1,11,14,0,0,0,0,10,16,16,4,0,0,0,1,13,13,15,13,1,0,0,5,13,1,4,16,5,0,0,8,12,2,5,16,4,0,0,1,8,16,16,11,1,0,8 +0,0,0,0,12,11,0,0,0,0,0,1,16,9,0,0,0,5,12,13,16,7,0,0,0,2,8,10,16,8,0,0,0,0,0,0,16,9,0,0,0,0,0,0,12,11,0,0,0,0,0,0,11,16,3,0,0,0,0,0,13,16,4,0,1 +0,0,14,16,16,16,9,0,0,0,6,8,10,16,6,0,0,0,0,0,9,14,2,0,0,0,13,14,16,14,4,0,0,0,8,16,16,16,11,0,0,0,3,16,4,3,1,0,0,0,11,12,0,0,0,0,0,0,16,8,0,0,0,0,7 +0,0,6,14,9,1,0,0,0,0,14,9,13,12,0,0,0,2,13,0,2,15,2,0,0,5,8,0,0,8,4,0,0,8,8,0,0,5,8,0,0,6,11,0,0,6,5,0,0,0,15,7,4,13,3,0,0,0,5,12,16,11,0,0,0 +0,0,3,11,16,13,2,0,0,0,14,8,8,15,8,0,0,0,15,11,5,13,12,0,0,0,6,16,16,13,0,0,0,0,13,12,12,15,1,0,0,3,12,0,0,16,5,0,0,1,15,6,7,16,4,0,0,0,6,12,14,7,0,0,8 +0,0,0,0,14,12,0,0,0,0,0,0,16,16,0,0,0,1,6,12,16,16,0,0,0,6,12,10,16,14,0,0,0,0,0,0,16,16,0,0,0,0,0,0,16,15,0,0,0,0,0,2,16,16,7,0,0,0,0,1,13,16,4,0,1 +0,0,0,12,3,0,0,0,0,0,10,14,1,0,0,0,0,0,14,9,1,0,0,0,0,1,16,16,15,6,0,0,0,3,16,10,5,14,6,0,0,2,16,1,0,5,12,0,0,0,12,9,4,7,15,0,0,0,1,10,16,16,7,0,6 +0,1,7,12,12,0,0,0,0,3,12,4,15,3,0,0,0,0,0,1,15,3,0,0,0,0,5,15,13,2,0,0,0,0,1,4,8,15,2,0,0,0,0,0,0,16,5,0,0,3,11,4,5,16,3,0,0,0,10,16,13,7,0,0,3 +0,1,10,16,13,2,0,0,0,6,14,9,16,6,0,0,0,0,1,0,13,11,0,0,0,0,1,13,16,11,1,0,0,0,2,12,11,16,8,0,0,0,5,0,0,15,9,0,0,3,16,12,8,16,7,0,0,0,10,16,16,11,1,0,3 +0,0,13,16,7,0,0,0,0,0,16,13,16,2,0,0,0,0,11,8,13,8,0,0,0,0,1,1,16,8,0,0,0,0,0,3,16,6,0,0,0,0,0,10,15,2,0,0,0,0,12,16,15,9,9,0,0,0,15,16,16,16,16,5,2 +0,0,10,16,16,16,16,4,0,0,4,8,8,14,14,1,0,0,0,0,6,15,4,0,0,0,4,12,15,14,4,0,0,0,11,16,16,16,9,0,0,0,0,14,10,3,0,0,0,0,6,16,5,0,0,0,0,0,12,12,0,0,0,0,7 +0,0,0,1,12,16,2,0,0,0,0,0,16,16,1,0,0,6,12,13,16,16,0,0,0,8,16,15,16,16,0,0,0,0,0,0,16,16,0,0,0,0,0,0,16,16,3,0,0,0,0,0,12,16,7,0,0,0,0,0,14,16,6,0,1 +0,0,5,11,14,7,0,0,0,4,16,6,9,15,1,0,0,7,12,0,1,16,4,0,0,2,16,9,11,16,6,0,0,0,2,5,4,12,8,0,0,0,0,0,0,10,8,0,0,3,15,7,3,14,8,0,0,0,5,14,16,11,2,0,9 +0,1,14,15,4,0,0,0,0,6,15,12,14,0,0,0,0,4,16,4,16,4,0,0,0,0,6,4,16,3,0,0,0,0,0,9,15,0,0,0,0,0,1,14,11,0,0,0,0,0,13,16,10,8,9,1,0,2,14,16,16,16,16,6,2 +0,0,8,12,7,0,0,0,0,0,14,8,15,14,0,0,0,3,14,0,6,15,2,0,0,5,11,0,0,9,8,0,0,8,8,0,0,5,8,0,0,5,11,0,0,8,8,0,0,1,15,2,2,14,5,0,0,0,6,15,16,12,1,0,0 +0,0,0,3,10,0,0,0,0,0,0,9,8,0,0,0,0,0,4,15,0,0,0,0,0,0,9,9,1,0,0,0,0,3,16,2,10,6,0,0,0,13,16,6,14,12,0,0,0,15,16,16,16,13,0,0,0,0,0,4,13,3,0,0,4 +0,0,1,11,4,0,0,0,0,0,5,14,1,0,0,0,0,0,12,7,0,0,0,0,0,1,15,0,4,1,0,0,0,4,15,14,16,14,1,0,0,2,14,5,1,6,11,0,0,1,13,7,0,1,14,0,0,0,3,10,15,16,10,0,6 +0,2,12,16,14,1,0,0,0,12,12,8,16,7,0,0,0,2,1,1,16,8,0,0,0,0,2,14,16,10,0,0,0,0,1,11,12,16,7,0,0,0,0,0,0,15,12,0,0,3,13,6,4,16,11,0,0,1,14,16,16,12,3,0,3 +0,0,8,12,15,12,0,0,0,8,15,7,4,4,0,0,0,6,14,4,3,0,0,0,0,7,16,13,16,8,0,0,0,1,4,0,5,16,5,0,0,0,0,0,0,15,9,0,0,1,7,4,6,15,8,0,0,0,10,15,16,9,0,0,5 +0,0,11,15,7,0,0,0,0,2,16,10,16,14,4,0,0,5,13,0,6,16,6,0,0,4,16,9,10,16,8,0,0,0,7,12,11,14,8,0,0,0,0,0,0,12,8,0,0,1,12,10,10,15,6,0,0,1,10,12,14,10,1,0,9 +0,0,9,16,16,3,0,0,0,5,16,10,14,7,0,0,0,1,10,0,14,10,0,0,0,0,2,15,16,9,0,0,0,0,1,8,11,16,6,0,0,0,0,0,3,16,8,0,0,1,16,11,11,16,4,0,0,0,11,16,16,11,0,0,3 +0,0,3,16,7,0,0,0,0,0,0,13,13,0,0,0,0,0,2,15,11,0,0,0,0,8,16,16,16,12,3,0,0,6,12,15,16,16,12,0,0,0,0,8,16,7,1,0,0,0,0,10,15,2,0,0,0,0,2,16,13,0,0,0,4 +0,1,10,16,16,9,0,0,0,11,15,8,15,12,0,0,0,3,2,2,16,9,0,0,0,0,0,7,16,4,0,0,0,0,0,2,15,11,0,0,0,0,0,0,5,16,6,0,0,0,3,7,6,15,12,0,0,0,12,16,16,14,5,0,3 +0,0,0,7,14,6,0,0,0,0,1,15,11,2,0,0,0,0,8,13,0,0,0,0,0,1,14,13,8,1,0,0,0,3,16,14,12,10,0,0,0,0,15,2,0,7,9,0,0,0,11,13,7,4,15,0,0,0,0,7,12,12,13,2,6 +0,1,10,14,7,0,0,0,0,7,12,5,15,0,0,0,0,4,7,5,15,1,0,0,0,0,2,15,9,0,0,0,0,0,0,9,16,4,0,0,0,0,0,0,4,14,4,0,0,0,10,3,3,10,12,0,0,0,11,16,16,14,4,0,3 +0,0,7,15,10,1,0,0,0,1,16,10,14,7,0,0,0,0,11,2,0,15,0,0,0,0,0,0,0,15,3,0,0,0,0,0,5,16,0,0,0,0,0,3,15,9,0,0,0,0,9,16,16,10,6,0,0,0,9,12,12,12,13,1,2 +0,0,6,14,15,3,0,0,0,0,8,15,12,2,0,0,0,0,8,14,12,11,1,0,0,0,0,0,0,9,6,0,0,0,0,0,0,3,9,0,0,0,3,1,0,1,12,0,0,0,13,8,4,9,11,0,0,0,5,13,16,13,7,0,5 +0,0,9,13,6,0,0,0,0,0,15,16,15,5,0,0,0,4,13,1,3,14,0,0,0,5,11,0,0,10,7,0,0,8,8,0,0,8,6,0,0,4,12,0,0,7,7,0,0,2,13,4,7,16,0,0,0,0,8,16,13,8,0,0,0 +0,0,0,6,16,3,0,0,0,0,5,16,8,1,0,0,0,0,10,14,0,0,0,0,0,0,14,15,8,3,0,0,0,0,14,15,12,15,4,0,0,0,16,6,0,6,11,0,0,0,9,13,5,6,15,0,0,0,1,8,12,15,13,1,6 +0,1,15,10,0,0,0,0,0,9,13,12,9,0,0,0,0,10,8,8,16,10,0,0,0,3,14,16,16,12,0,0,0,0,0,0,6,14,0,0,0,0,0,0,2,14,3,0,0,3,11,5,2,8,12,0,0,1,8,13,16,16,11,0,9 +0,0,7,10,2,0,0,0,0,3,16,16,16,10,0,0,0,5,16,12,11,16,2,0,0,6,12,0,0,9,8,0,0,4,12,0,0,8,8,0,0,4,16,0,0,8,8,0,0,1,16,10,11,15,6,0,0,0,7,16,13,5,0,0,0 +0,0,4,15,10,0,0,0,0,0,14,8,16,7,0,0,0,0,15,0,13,10,0,0,0,1,15,16,16,9,0,0,0,0,0,0,5,12,0,0,0,2,1,0,3,15,0,0,0,8,14,6,4,14,1,0,0,0,6,13,16,15,0,0,9 +0,0,7,14,5,0,0,0,0,3,16,16,16,5,0,0,0,6,16,0,5,14,0,0,0,4,12,0,0,13,3,0,0,8,8,0,0,12,8,0,0,7,9,0,0,12,7,0,0,1,15,8,14,16,3,0,0,0,8,14,12,4,0,0,0 +0,0,14,16,8,3,0,0,0,5,15,8,16,16,3,0,0,9,11,1,12,15,2,0,0,1,13,16,15,3,0,0,0,0,11,14,15,3,0,0,0,1,16,4,4,14,5,0,0,4,14,5,4,11,12,0,0,0,9,12,13,12,8,0,8 +0,0,1,11,13,2,0,0,0,0,9,14,13,3,0,0,0,1,15,12,0,0,0,0,0,3,15,9,2,0,0,0,0,2,16,16,16,11,0,0,0,0,13,7,4,14,7,0,0,0,9,11,9,16,6,0,0,0,1,12,16,7,0,0,6 +0,0,0,10,14,1,0,0,0,0,8,16,10,1,0,0,0,2,16,9,0,0,0,0,0,7,16,7,4,1,0,0,0,5,16,15,12,13,0,0,0,0,15,6,0,4,11,0,0,0,5,13,2,9,13,0,0,0,0,6,15,16,6,0,6 +0,1,9,16,10,0,0,0,0,6,12,4,15,2,0,0,0,0,0,0,13,3,0,0,0,0,0,10,16,1,0,0,0,0,0,6,13,11,0,0,0,0,0,0,0,14,4,0,0,3,10,1,0,10,8,0,0,0,10,16,16,15,3,0,3 +0,0,6,15,2,0,0,0,0,0,3,16,8,0,0,0,0,0,4,16,10,0,0,0,0,0,7,16,13,0,0,0,0,0,0,9,16,2,0,0,0,0,0,1,16,7,0,0,0,0,2,10,15,15,15,6,0,0,4,16,16,16,16,13,1 +0,0,3,12,11,1,0,0,0,0,9,13,10,10,0,0,0,0,5,13,11,13,0,0,0,0,0,7,12,14,5,0,0,0,0,0,0,6,10,0,0,0,0,0,0,1,15,0,0,0,8,8,4,4,13,5,0,0,3,10,16,16,16,5,9 +0,0,14,12,0,0,0,0,0,3,13,16,0,0,0,0,0,12,16,16,4,0,0,0,0,5,10,16,6,0,0,0,0,0,1,15,9,0,0,0,0,0,0,12,15,0,0,0,0,0,7,13,16,9,8,0,0,0,13,16,16,16,16,3,1 +0,0,0,0,11,15,0,0,0,0,0,2,15,11,0,0,0,0,0,9,16,4,0,0,0,0,8,16,8,7,3,0,0,9,16,16,12,16,11,0,0,4,11,12,14,16,5,0,0,0,0,0,9,16,4,0,0,0,0,0,12,15,2,0,4 +0,0,12,16,12,0,0,0,0,4,16,12,16,6,0,0,0,1,8,0,12,8,0,0,0,0,0,0,15,5,0,0,0,0,0,3,16,3,0,0,0,0,8,16,8,0,0,0,0,3,16,16,16,13,2,0,0,1,8,9,14,16,4,0,2 +0,0,10,16,7,0,0,0,0,1,16,16,15,3,0,0,0,4,16,16,16,13,0,0,0,4,16,10,11,14,2,0,0,6,16,0,0,12,4,0,0,5,15,0,0,10,7,0,0,4,16,8,11,16,0,0,0,1,10,15,13,4,0,0,0 +0,0,10,16,11,8,3,0,0,0,6,12,13,16,7,0,0,0,0,0,5,15,1,0,0,0,4,6,13,8,0,0,0,0,15,16,16,16,4,0,0,0,1,14,11,13,4,0,0,0,4,16,1,0,0,0,0,0,12,10,0,0,0,0,7 +0,0,0,3,12,5,0,0,0,0,8,16,12,4,0,0,0,2,16,8,0,0,0,0,0,6,16,14,9,2,0,0,0,4,16,11,10,15,3,0,0,0,14,3,0,6,10,0,0,0,5,15,5,12,9,0,0,0,0,5,15,11,2,0,6 +0,0,11,15,4,0,0,0,0,0,7,16,16,16,11,0,0,0,0,1,7,16,11,0,0,2,4,2,15,12,1,0,0,13,16,16,16,12,3,0,0,2,6,16,11,14,4,0,0,0,7,16,4,0,0,0,0,0,14,14,1,0,0,0,7 +0,1,14,12,1,0,0,0,0,7,16,16,13,0,0,0,0,6,14,3,16,2,0,0,0,2,12,0,14,6,0,0,0,0,0,0,15,8,0,0,0,0,0,8,16,4,0,0,0,2,12,16,16,12,10,0,0,0,15,16,16,16,16,6,2 +0,0,7,14,12,0,0,0,0,5,16,16,16,6,0,0,0,8,16,8,5,15,3,0,0,8,12,0,0,10,7,0,0,8,11,0,0,12,5,0,0,4,13,0,0,12,5,0,0,0,16,12,9,16,3,0,0,0,8,14,12,7,0,0,0 +0,0,9,9,0,0,0,0,0,0,7,16,2,0,0,0,0,0,8,16,3,0,0,0,0,0,6,16,9,0,0,0,0,0,0,6,14,0,0,0,0,0,0,1,15,4,0,0,0,0,6,12,15,14,9,5,0,0,7,16,16,16,16,14,1 +0,4,16,13,0,0,0,0,0,12,11,15,4,0,0,0,0,12,6,10,10,0,0,0,0,1,1,8,10,0,0,0,0,0,0,15,7,0,0,0,0,0,4,16,1,0,0,0,0,0,13,16,12,12,8,0,0,2,16,16,16,16,16,0,2 +0,0,11,8,0,0,0,0,0,0,9,15,0,0,0,0,0,12,15,16,6,0,0,0,0,8,15,16,9,0,0,0,0,0,1,13,13,0,0,0,0,0,0,6,16,2,0,0,0,0,8,13,16,13,12,5,0,0,11,16,16,16,16,11,1 +0,0,5,13,5,0,0,0,0,4,16,11,12,11,2,0,0,7,8,0,12,16,6,0,0,3,15,14,16,12,1,0,0,0,9,14,14,10,0,0,0,0,11,1,2,13,5,0,0,0,9,7,4,8,12,0,0,0,5,14,15,13,7,0,8 +0,0,0,1,11,14,0,0,0,0,0,2,16,10,0,0,0,0,0,11,16,4,0,0,0,0,12,15,3,5,1,0,0,7,16,13,9,16,10,0,0,5,16,16,16,16,5,0,0,0,0,0,12,13,0,0,0,0,0,0,13,13,1,0,4 +0,0,0,0,11,13,0,0,0,0,0,2,16,10,0,0,0,0,0,13,15,2,0,0,0,0,11,15,8,11,4,0,0,9,16,15,14,16,11,0,0,2,7,11,13,16,4,0,0,0,0,0,8,16,3,0,0,0,0,0,15,16,2,0,4 +0,0,0,1,15,8,0,0,0,0,0,2,16,9,0,0,0,0,0,7,16,4,0,0,0,1,7,16,15,15,2,0,0,11,16,16,13,16,5,0,0,7,12,12,14,16,5,0,0,0,0,0,13,15,0,0,0,0,0,4,16,10,0,0,4 +0,0,6,12,12,1,0,0,0,4,16,8,13,7,0,0,0,5,10,0,11,8,0,0,0,0,0,0,14,3,0,0,0,0,0,0,13,8,0,0,0,0,0,0,3,14,2,0,0,0,7,9,4,11,7,0,0,0,7,12,12,13,5,0,3 +0,0,5,14,15,2,0,0,0,4,15,7,11,8,0,0,0,4,6,1,15,5,0,0,0,0,0,4,15,4,0,0,0,0,0,0,7,15,1,0,0,0,0,0,0,9,7,0,0,3,12,5,4,12,7,0,0,0,6,13,16,9,1,0,3 +0,0,10,13,16,5,0,0,0,0,15,15,10,12,0,0,0,3,14,1,0,14,2,0,0,6,12,0,0,9,7,0,0,4,12,0,0,4,8,0,0,0,12,0,0,11,6,0,0,0,16,10,14,16,2,0,0,0,8,13,10,1,0,0,0 +0,0,4,15,8,0,0,0,0,0,12,16,15,3,0,0,0,1,16,11,7,14,2,0,0,4,14,0,0,12,8,0,0,4,12,0,0,14,5,0,0,4,14,1,0,15,5,0,0,0,13,9,9,16,6,0,0,0,7,16,12,5,0,0,0 +0,0,10,9,0,0,0,0,0,0,7,16,2,0,0,0,0,11,14,16,3,0,0,0,0,5,15,16,5,0,0,0,0,0,1,10,10,0,0,0,0,0,0,5,15,0,0,0,0,0,5,12,16,14,12,4,0,0,8,16,16,16,16,13,1 +0,0,4,15,11,1,0,0,0,0,9,13,10,11,0,0,0,0,7,11,3,16,1,0,0,0,1,14,16,16,5,0,0,0,0,0,2,11,6,0,0,0,0,0,0,6,10,0,0,0,8,5,4,8,13,0,0,0,4,12,15,16,8,0,9 +0,0,4,16,16,16,12,0,0,0,1,7,8,13,15,0,0,0,0,0,0,14,10,0,0,0,2,8,11,15,1,0,0,0,4,14,16,16,4,0,0,0,0,8,15,13,4,0,0,0,0,14,10,0,0,0,0,0,6,16,4,0,0,0,7 +0,0,7,13,12,3,0,0,0,2,14,8,14,8,0,0,0,2,15,4,14,8,0,0,0,0,8,12,12,8,0,0,0,0,0,0,4,12,0,0,0,0,0,0,1,15,0,0,0,0,10,10,1,16,3,0,0,0,5,13,16,12,0,0,9 +0,1,6,13,16,5,0,0,0,6,16,9,15,4,0,0,0,4,9,5,16,2,0,0,0,0,0,8,13,0,0,0,0,0,0,3,15,7,0,0,0,0,0,0,6,15,0,0,0,0,4,6,4,14,6,0,0,0,10,16,16,16,5,0,3 +0,0,10,16,16,15,2,0,0,0,5,8,8,16,6,0,0,0,0,0,4,16,3,0,0,0,6,8,12,11,0,0,0,1,16,16,16,15,1,0,0,0,1,13,10,11,3,0,0,0,8,14,0,0,0,0,0,0,13,6,0,0,0,0,7 +0,3,14,16,4,0,0,0,0,8,10,11,11,0,0,0,0,5,9,0,13,2,0,0,0,0,0,0,12,4,0,0,0,0,0,2,15,1,0,0,0,0,0,14,11,0,0,0,0,1,13,16,14,12,6,0,0,2,12,12,12,12,11,0,2 +0,0,7,14,1,0,0,0,0,0,6,16,8,0,0,0,0,8,16,16,10,0,0,0,0,0,11,13,16,1,0,0,0,0,0,3,16,6,0,0,0,0,0,1,13,12,0,0,0,0,10,16,16,16,12,7,0,0,5,15,16,16,16,15,1 +0,0,5,15,10,10,4,0,0,0,12,12,15,14,8,0,0,0,11,7,1,15,5,0,0,0,6,15,8,14,0,0,0,0,4,14,16,2,0,0,0,3,16,11,12,5,0,0,0,7,14,7,6,14,0,0,0,0,7,13,16,14,0,0,8 +0,0,5,16,11,3,0,0,0,0,14,15,10,15,2,0,0,5,13,6,0,11,8,0,0,8,8,0,0,5,8,0,0,8,7,0,0,8,5,0,0,4,10,0,1,13,1,0,0,0,14,9,13,10,0,0,0,0,5,13,9,1,0,0,0 +0,1,14,14,2,0,0,0,0,7,16,16,8,0,0,0,0,11,11,10,14,0,0,0,0,0,0,8,16,0,0,0,0,0,0,10,13,0,0,0,0,0,5,16,12,0,0,0,0,4,16,16,16,16,6,0,0,1,8,6,8,15,11,0,2 +0,0,0,8,11,0,0,0,0,0,3,16,12,0,0,0,0,0,11,15,1,0,0,0,0,1,15,12,5,0,0,0,0,2,16,16,13,12,1,0,0,0,14,9,1,4,13,0,0,0,5,14,3,0,11,6,0,0,0,5,15,16,16,7,6 +0,0,11,11,0,0,0,0,0,0,12,14,0,0,0,0,0,13,16,15,0,0,0,0,0,6,11,16,3,0,0,0,0,0,1,16,4,0,0,0,0,0,0,11,9,0,0,0,0,0,10,15,15,12,7,0,0,0,11,16,16,16,16,4,1 +0,0,0,11,11,0,0,0,0,0,2,16,13,0,0,0,0,0,6,16,5,0,0,0,0,0,11,16,5,0,0,0,0,0,13,16,14,12,1,0,0,0,11,14,2,8,14,0,0,0,6,16,5,12,15,1,0,0,0,8,15,13,6,0,6 +0,0,8,16,7,0,0,0,0,0,11,14,16,4,0,0,0,0,0,1,14,3,0,0,0,0,8,14,16,6,0,0,0,0,6,16,16,16,7,0,0,0,0,12,9,13,5,0,0,0,4,14,2,0,0,0,0,0,9,10,0,0,0,0,7 +0,0,4,14,3,0,0,0,0,0,9,16,16,4,0,0,0,0,14,16,16,14,0,0,0,2,16,6,1,16,4,0,0,5,15,0,0,12,4,0,0,5,13,0,0,12,8,0,0,0,16,10,9,16,7,0,0,0,6,16,15,8,0,0,0 +0,0,11,14,5,0,0,0,0,5,13,4,16,6,0,0,0,3,14,11,15,8,0,0,0,0,2,6,6,13,0,0,0,0,0,0,0,14,2,0,0,0,0,0,0,5,10,0,0,1,4,0,0,7,12,0,0,1,9,15,16,15,4,0,9 +0,0,2,13,10,1,0,0,0,0,8,16,16,14,1,0,0,0,11,8,3,15,1,0,0,0,6,12,9,11,0,0,0,0,9,16,16,2,0,0,0,1,16,7,9,10,0,0,0,2,14,5,4,16,3,0,0,0,4,11,14,13,5,0,8 +0,2,15,10,1,0,0,0,0,8,15,16,9,0,0,0,0,8,10,11,12,0,0,0,0,1,5,7,16,0,0,0,0,0,0,10,13,0,0,0,0,0,2,15,10,0,0,0,0,5,16,16,16,12,8,0,0,3,12,12,11,14,16,1,2 +0,0,3,14,11,1,0,0,0,1,15,10,16,5,0,0,0,2,10,2,16,3,0,0,0,0,0,1,16,6,0,0,0,0,0,0,9,15,3,0,0,0,6,0,0,10,9,0,0,2,16,7,4,13,10,0,0,0,5,11,14,11,1,0,3 +0,0,7,13,9,0,0,0,0,0,13,11,13,11,0,0,0,0,14,8,12,16,2,0,0,0,5,13,13,15,3,0,0,0,0,0,0,11,7,0,0,0,0,0,0,3,13,0,0,0,12,9,4,4,16,0,0,0,7,16,16,16,12,0,9 +0,0,14,14,11,2,0,0,0,1,16,16,16,15,0,0,0,4,16,9,7,14,6,0,0,8,15,0,0,8,8,0,0,8,11,0,0,8,8,0,0,8,12,0,0,12,5,0,0,4,16,10,14,13,0,0,0,0,9,16,15,3,0,0,0 +0,0,2,15,3,0,0,0,0,0,2,14,10,0,0,0,0,0,15,16,14,0,0,0,0,0,6,12,16,0,0,0,0,0,0,1,16,2,0,0,0,0,0,0,13,6,0,0,0,0,8,14,14,14,8,3,0,0,2,11,12,12,13,9,1 +0,1,12,10,2,0,0,0,0,5,14,13,12,0,0,0,0,4,8,3,16,0,0,0,0,0,1,0,15,0,0,0,0,0,0,5,12,0,0,0,0,0,4,15,6,0,0,0,0,4,16,16,15,10,3,0,0,2,11,7,8,11,9,0,2 +0,0,5,14,15,2,0,0,0,0,11,16,12,1,0,0,0,0,7,16,13,2,0,0,0,0,1,11,14,15,2,0,0,0,0,0,0,10,10,0,0,1,8,1,0,2,15,1,0,2,13,10,5,7,16,2,0,0,4,13,16,16,14,0,5 +0,0,10,15,8,2,0,0,0,0,5,13,16,15,1,0,0,0,0,0,4,16,4,0,0,0,1,4,7,16,3,0,0,0,9,16,16,16,1,0,0,0,1,12,15,12,4,0,0,0,3,16,5,0,0,0,0,0,14,14,0,0,0,0,7 +0,0,0,8,13,0,0,0,0,0,6,15,8,0,0,0,0,2,16,10,0,0,0,0,0,2,16,13,10,1,0,0,0,2,16,11,9,13,1,0,0,0,10,6,0,5,13,0,0,0,4,15,1,9,16,2,0,0,0,7,15,16,7,0,6 +0,0,11,8,0,0,0,0,0,0,12,15,1,0,0,0,0,0,13,16,5,0,0,0,0,0,12,16,6,0,0,0,0,0,2,15,7,0,0,0,0,0,0,10,14,1,0,0,0,0,16,16,16,16,11,3,0,0,11,16,16,16,16,11,1 +0,0,1,14,9,0,0,0,0,0,5,15,13,6,0,0,0,0,9,12,4,12,0,0,0,0,6,15,10,16,4,0,0,0,0,10,16,16,10,0,0,0,0,0,0,2,16,0,0,0,1,4,4,6,15,4,0,0,1,13,15,14,11,0,9 +0,0,6,15,16,7,0,0,0,10,16,9,14,11,0,0,0,5,3,1,14,10,0,0,0,0,0,9,16,5,0,0,0,0,0,8,16,16,4,0,0,0,1,0,1,12,8,0,0,3,13,5,4,13,11,0,0,0,8,16,16,12,2,0,3 +0,0,1,13,15,3,0,0,0,6,16,16,16,9,0,0,0,9,15,16,16,6,0,0,0,3,16,16,10,0,0,0,0,0,12,16,11,0,0,0,0,0,14,6,10,15,2,0,0,0,11,12,6,16,13,0,0,0,1,10,15,13,5,0,8 +0,0,3,9,8,0,0,0,0,2,15,14,14,4,0,0,0,9,16,5,13,16,1,0,0,5,16,16,16,16,7,0,0,0,6,8,8,16,6,0,0,0,0,0,0,15,9,0,0,0,7,4,9,16,8,0,0,0,9,16,13,9,1,0,9 +0,0,6,11,11,1,0,0,0,0,9,16,16,14,0,0,0,0,11,16,16,10,0,0,0,0,7,16,16,12,0,0,0,0,12,16,16,11,0,0,0,0,9,16,16,12,0,0,0,0,8,16,16,16,0,0,0,0,5,12,12,12,3,0,1 +0,0,0,1,12,2,0,0,0,0,1,16,16,2,0,0,0,0,10,15,3,0,0,0,0,0,14,11,0,0,0,0,0,1,16,16,15,7,0,0,0,0,14,16,11,15,5,0,0,0,6,16,6,16,9,0,0,0,0,5,12,12,3,0,6 +0,0,7,15,16,15,3,0,0,0,15,8,6,4,0,0,0,0,16,5,4,2,0,0,0,3,16,14,16,10,0,0,0,7,16,13,10,14,0,0,0,0,4,0,6,16,1,0,0,0,8,8,12,14,0,0,0,0,7,13,11,4,0,0,5 +0,0,0,11,13,0,0,0,0,2,12,16,16,7,0,0,0,12,16,8,3,14,2,0,0,6,16,10,0,14,6,0,0,0,15,9,0,11,5,0,0,0,11,13,0,9,9,0,0,0,8,16,13,16,5,0,0,0,0,13,16,10,1,0,0 +0,0,0,2,15,5,0,0,0,0,0,9,15,2,2,0,0,0,3,16,7,5,16,2,0,1,12,16,8,14,13,0,0,11,16,16,16,16,10,0,0,10,10,4,14,14,1,0,0,0,0,0,14,8,0,0,0,0,0,1,15,3,0,0,4 +0,0,0,4,13,1,0,0,0,0,3,16,13,0,0,0,0,0,8,15,4,0,0,0,0,0,12,14,0,0,0,0,0,0,15,11,6,6,0,0,0,0,13,16,16,16,10,0,0,0,8,16,5,11,16,2,0,0,0,6,12,15,10,0,6 +0,0,0,6,14,3,0,0,0,0,2,15,14,4,0,0,0,0,9,16,2,0,0,0,0,0,13,13,0,0,0,0,0,0,14,14,16,16,5,0,0,0,14,16,15,13,11,0,0,0,10,16,6,13,13,0,0,0,1,9,15,13,4,0,6 +0,0,3,14,10,0,0,0,0,2,14,15,13,3,0,0,0,2,16,8,4,10,0,0,0,0,15,14,13,16,4,0,0,0,2,10,14,15,10,0,0,0,3,0,0,11,9,0,0,0,10,8,4,15,8,0,0,0,2,12,13,14,4,0,9 +0,0,0,2,13,0,0,0,0,0,0,11,12,0,0,0,0,0,3,16,6,1,4,0,0,0,11,10,0,12,8,0,0,6,16,7,7,16,1,0,0,9,16,16,16,16,3,0,0,0,3,5,15,8,1,0,0,0,0,2,11,0,0,0,4 +0,0,8,12,13,16,8,0,0,0,9,9,9,16,5,0,0,0,0,0,7,15,0,0,0,0,6,11,15,11,1,0,0,0,9,14,15,15,6,0,0,0,0,16,8,0,0,0,0,0,3,16,3,0,0,0,0,0,8,14,0,0,0,0,7 +0,0,2,10,12,1,0,0,0,0,10,15,14,8,0,0,0,6,16,7,8,8,0,0,0,5,16,16,16,10,0,0,0,0,8,16,16,16,2,0,0,0,11,14,1,13,6,0,0,0,12,13,7,14,4,0,0,0,4,14,12,10,2,0,8 +0,0,0,10,15,2,0,0,0,0,0,13,16,11,0,0,0,0,0,15,16,9,0,0,0,0,6,16,16,7,0,0,0,1,15,16,16,3,0,0,0,1,14,16,16,2,0,0,0,0,0,14,16,8,0,0,0,0,0,6,14,9,0,0,1 +0,0,7,12,14,12,8,0,0,0,10,8,9,16,14,0,0,0,0,0,2,16,6,0,0,0,6,16,13,14,1,0,0,0,3,10,16,10,0,0,0,0,0,14,11,0,0,0,0,0,3,16,3,0,0,0,0,0,8,11,0,0,0,0,7 +0,1,11,14,14,4,0,0,0,3,14,8,10,16,0,0,0,0,0,0,5,16,1,0,0,0,0,3,16,14,0,0,0,0,0,4,12,16,7,0,0,0,0,0,0,9,12,0,0,0,9,8,8,14,13,0,0,0,7,12,12,10,3,0,3 +0,0,9,15,12,10,7,0,0,0,5,8,12,16,12,0,0,0,0,0,4,16,6,0,0,0,1,5,14,10,0,0,0,0,13,16,16,12,0,0,0,1,8,16,10,2,0,0,0,0,9,16,3,0,0,0,0,0,9,13,0,0,0,0,7 +0,0,1,10,9,0,0,0,0,1,10,13,8,7,0,0,0,5,16,2,0,9,0,0,0,0,16,12,14,16,1,0,0,0,11,16,13,13,3,0,0,0,2,15,5,6,3,0,0,0,4,15,4,13,2,0,0,0,1,11,12,4,0,0,8 +0,0,0,1,10,0,0,0,0,0,0,11,8,2,1,0,0,0,3,13,0,8,8,0,0,0,10,8,2,15,3,0,0,6,16,15,16,16,5,0,0,12,15,12,15,14,3,0,0,0,0,0,15,1,0,0,0,0,0,0,12,0,0,0,4 +0,0,0,4,14,4,0,0,0,0,3,16,14,4,0,0,0,0,8,16,5,0,0,0,0,0,15,12,11,8,1,0,0,0,16,16,16,16,8,0,0,2,16,16,4,3,14,0,0,0,10,16,5,10,16,1,0,0,0,7,13,14,10,0,6 +0,0,13,16,16,11,0,0,0,0,10,7,4,16,0,0,0,0,0,0,10,12,0,0,0,0,0,3,13,14,2,0,0,0,0,10,13,14,6,0,0,0,0,0,0,6,11,0,0,1,14,6,4,13,7,0,0,0,9,13,16,12,0,0,3 +0,0,10,15,16,15,1,0,0,7,16,10,4,3,1,0,0,8,13,8,0,0,0,0,0,7,16,16,7,0,0,0,0,1,8,10,8,0,0,0,0,0,0,5,15,0,0,0,0,0,1,8,14,0,0,0,0,0,6,16,10,0,0,0,5 +0,0,3,14,9,1,0,0,0,0,10,15,13,8,0,0,0,0,13,10,4,14,0,0,0,0,12,16,16,16,4,0,0,0,0,6,8,12,10,0,0,0,0,0,0,4,13,0,0,0,3,8,8,13,13,0,0,0,2,10,12,10,5,0,9 +0,0,4,10,16,4,0,0,0,4,16,13,10,10,0,0,0,8,16,8,7,12,0,0,0,3,15,16,16,9,0,0,0,5,16,16,16,16,2,0,0,1,13,12,1,9,9,0,0,0,14,10,2,14,5,0,0,0,2,12,15,11,1,0,8 +0,0,9,13,7,0,0,0,0,2,15,10,14,6,0,0,0,12,13,0,9,16,2,0,0,8,16,12,14,16,6,0,0,1,8,8,10,16,2,0,0,0,0,0,1,16,5,0,0,0,8,4,7,16,2,0,0,0,10,15,16,13,0,0,9 +0,0,0,5,11,0,0,0,0,0,7,16,14,1,0,0,0,3,16,3,9,11,0,0,0,4,16,13,16,14,3,0,0,0,7,16,13,4,4,0,0,0,1,16,6,3,5,0,0,0,5,14,5,10,3,0,0,0,0,9,9,5,0,0,8 +0,0,3,9,9,0,0,0,0,0,9,16,16,5,0,0,0,0,9,16,16,8,0,0,0,0,7,16,16,7,0,0,0,0,12,16,16,7,0,0,0,0,16,16,16,8,0,0,0,0,12,16,16,12,0,0,0,0,2,7,10,4,0,0,1 +0,0,9,15,16,16,14,0,0,1,15,10,8,14,13,0,0,0,0,0,2,15,9,0,0,0,0,10,14,16,4,0,0,0,1,16,16,16,11,0,0,0,0,13,15,4,1,0,0,0,5,16,7,0,0,0,0,0,9,15,0,0,0,0,7 +0,0,4,16,12,0,0,0,0,9,16,16,16,8,0,0,0,9,16,9,6,14,0,0,0,6,16,2,0,12,5,0,0,6,16,1,0,8,9,0,0,3,16,1,0,12,10,0,0,0,12,13,15,16,8,0,0,0,3,12,12,10,1,0,0 +0,0,0,9,7,0,0,0,0,0,3,16,8,0,0,0,0,0,8,14,2,0,0,0,0,0,13,10,0,0,0,0,0,0,15,16,16,15,5,0,0,0,15,16,11,11,12,0,0,0,8,15,6,9,15,0,0,0,0,8,14,16,8,0,6 +0,0,4,8,10,13,8,0,0,0,10,12,12,14,12,0,0,0,0,0,0,13,8,0,0,0,5,12,13,16,6,0,0,0,7,13,16,12,3,0,0,0,0,8,14,1,0,0,0,0,3,16,9,0,0,0,0,0,5,14,2,0,0,0,7 +0,0,6,16,12,12,14,6,0,0,5,8,8,11,15,2,0,0,0,0,1,14,5,0,0,0,0,1,12,12,0,0,0,0,0,11,16,15,1,0,0,0,0,10,11,1,0,0,0,0,1,14,4,0,0,0,0,0,5,12,2,0,0,0,7 +0,0,6,12,12,15,7,0,0,0,14,15,12,16,9,0,0,0,0,0,0,16,8,0,0,0,1,1,9,14,0,0,0,3,15,16,16,16,2,0,0,3,8,14,16,5,0,0,0,0,3,16,8,0,0,0,0,0,7,15,5,0,0,0,7 +0,0,0,9,13,0,0,0,0,0,5,16,16,4,0,0,0,0,15,15,7,0,0,0,0,1,16,13,0,0,0,0,0,3,16,15,16,16,6,0,0,1,14,16,16,16,15,1,0,0,8,16,13,14,16,1,0,0,0,7,12,12,6,0,6 +0,0,1,15,1,0,0,0,0,0,6,16,0,2,0,0,0,0,11,13,2,16,2,0,0,6,16,8,9,14,0,0,0,8,16,16,16,14,3,0,0,0,8,15,16,16,8,0,0,0,0,12,14,3,0,0,0,0,1,16,9,0,0,0,4 +0,0,1,12,11,2,0,0,0,0,2,16,16,3,0,0,0,0,5,16,16,3,0,0,0,1,15,16,14,2,0,0,0,5,16,16,13,0,0,0,0,1,11,16,14,0,0,0,0,0,5,16,16,7,0,0,0,0,1,15,16,9,0,0,1 +0,3,11,15,14,2,0,0,0,10,13,8,14,10,0,0,0,5,1,0,13,8,0,0,0,0,0,9,16,8,0,0,0,0,0,5,9,16,3,0,0,0,0,0,0,16,9,0,0,4,12,8,11,15,3,0,0,1,8,12,11,4,0,0,3 +0,0,7,13,12,12,5,0,0,3,15,8,7,8,3,0,0,4,15,5,6,0,0,0,0,5,16,16,16,2,0,0,0,2,8,1,12,4,0,0,0,0,0,0,12,4,0,0,0,0,3,5,16,2,0,0,0,0,9,15,8,0,0,0,5 +0,0,11,16,15,8,0,0,0,0,9,13,5,3,0,0,0,0,12,13,0,0,0,0,0,0,12,13,8,2,0,0,0,0,15,16,16,7,0,0,0,0,0,0,9,11,0,0,0,0,5,6,15,8,0,0,0,0,11,16,13,2,0,0,5 +0,0,3,11,14,11,8,4,0,0,3,8,8,10,16,6,0,0,0,0,0,13,9,0,0,0,0,3,11,16,9,0,0,0,0,11,16,8,3,0,0,0,0,10,13,0,0,0,0,0,1,16,3,0,0,0,0,0,5,11,0,0,0,0,7 +0,0,10,16,16,14,1,0,0,0,15,9,8,8,2,0,0,4,16,0,0,0,0,0,0,6,16,15,14,0,0,0,0,5,13,12,16,3,0,0,0,0,0,0,13,7,0,0,0,0,9,8,16,7,0,0,0,0,11,16,15,0,0,0,5 +0,0,0,0,13,9,0,0,0,0,0,10,16,16,1,0,0,1,12,16,16,13,0,0,0,7,16,16,16,15,0,0,0,0,7,12,16,13,0,0,0,0,0,4,16,16,0,0,0,0,0,3,16,16,3,0,0,0,0,0,13,16,6,0,1 +0,0,7,15,16,9,0,0,0,1,16,12,8,7,0,0,0,0,14,3,0,0,0,0,0,0,14,16,13,1,0,0,0,0,12,16,15,7,0,0,0,0,0,0,9,9,0,0,0,0,4,5,14,9,0,0,0,0,6,16,15,3,0,0,5 +0,0,8,12,12,14,8,0,0,2,11,12,12,16,12,0,0,0,0,0,3,16,5,0,0,0,3,7,15,13,0,0,0,0,15,16,16,16,4,0,0,0,1,15,9,0,0,0,0,0,10,14,0,0,0,0,0,0,10,11,0,0,0,0,7 +0,0,6,15,10,0,0,0,0,4,16,15,15,5,0,0,0,10,11,1,12,4,0,0,0,11,9,0,15,3,0,0,0,4,8,1,14,4,0,0,0,0,0,8,16,2,1,0,0,0,5,16,16,15,15,2,0,0,8,16,14,11,13,1,2 +0,0,9,15,16,8,0,0,0,0,7,7,9,15,1,0,0,0,0,0,9,12,0,0,0,0,5,13,16,13,3,0,0,0,1,11,14,4,2,0,0,0,0,15,4,0,0,0,0,0,6,16,3,0,0,0,0,0,10,12,0,0,0,0,7 +0,0,5,14,16,13,1,0,0,0,15,14,9,10,2,0,0,1,16,5,0,0,0,0,0,4,16,16,5,0,0,0,0,2,8,12,12,0,0,0,0,0,0,6,15,0,0,0,0,2,13,14,11,0,0,0,0,1,10,12,3,0,0,0,5 +0,0,1,8,12,3,0,0,0,0,9,16,16,12,0,0,0,0,11,16,7,11,0,0,0,0,14,11,0,7,2,0,0,0,14,8,0,9,4,0,0,0,16,7,2,13,1,0,0,0,11,15,11,15,0,0,0,0,1,7,9,1,0,0,0 +0,0,6,16,13,0,0,0,0,7,16,14,15,10,0,0,0,12,16,3,7,16,1,0,0,11,13,0,7,16,1,0,0,0,0,0,14,14,0,0,0,0,2,9,16,10,5,0,0,0,11,16,16,16,16,3,0,0,6,16,11,8,8,2,2 +0,0,0,8,14,4,0,0,0,0,5,16,10,0,0,0,0,0,12,13,1,0,0,0,0,1,16,7,1,0,0,0,0,4,16,16,16,12,1,0,0,2,16,14,9,14,5,0,0,0,12,14,6,16,5,0,0,0,1,8,13,9,2,0,6 +0,0,6,13,16,7,0,0,0,5,16,16,12,14,1,0,0,9,16,11,0,16,4,0,0,6,16,4,0,13,7,0,0,8,13,0,0,12,7,0,0,4,16,0,0,11,8,0,0,2,15,6,9,16,5,0,0,0,6,16,16,11,0,0,0 +0,0,1,9,10,1,0,0,0,0,5,16,16,8,0,0,0,0,6,16,16,10,0,0,0,0,7,16,16,10,0,0,0,0,2,16,16,7,0,0,0,0,1,16,16,10,0,0,0,0,8,16,16,15,1,0,0,0,1,6,9,9,2,0,1 +0,0,13,16,16,15,0,0,0,4,16,13,7,3,0,0,0,5,16,11,1,0,0,0,0,7,16,16,12,0,0,0,0,3,9,6,16,1,0,0,0,0,0,3,16,4,0,0,0,0,7,10,16,3,0,0,0,1,13,16,14,0,0,0,5 +0,0,3,14,10,0,0,0,0,4,15,12,15,2,0,0,0,11,15,1,11,5,0,0,0,9,12,0,10,7,0,0,0,1,1,0,15,7,0,0,0,0,0,7,16,6,2,0,0,0,1,16,16,16,16,3,0,0,2,12,11,8,8,3,2 +0,0,10,16,8,0,0,0,0,3,16,14,14,2,0,0,0,6,13,6,16,2,0,0,0,2,7,8,16,1,0,0,0,0,2,16,10,0,0,0,0,1,12,16,5,2,2,0,0,4,16,16,16,15,14,1,0,0,9,12,11,12,13,1,2 +0,1,13,16,16,10,0,0,0,2,16,13,9,11,2,0,0,5,16,4,0,0,0,0,0,7,16,16,12,0,0,0,0,1,7,7,16,0,0,0,0,0,0,3,16,3,0,0,0,0,14,14,15,3,0,0,0,0,9,12,7,0,0,0,5 +0,0,6,15,6,0,0,0,0,4,16,14,15,2,0,0,0,4,16,7,13,12,0,0,0,3,16,16,16,16,3,0,0,0,5,11,10,14,10,0,0,0,0,0,0,12,10,0,0,0,0,2,12,16,8,0,0,0,5,15,16,11,1,0,9 +0,0,11,16,14,5,0,0,0,0,15,10,8,8,0,0,0,0,12,7,0,0,0,0,0,0,16,16,9,0,0,0,0,0,15,11,15,1,0,0,0,0,0,0,8,5,0,0,0,0,2,3,11,7,0,0,0,0,12,16,15,3,0,0,5 +0,0,8,15,10,0,0,0,0,3,16,16,15,1,0,0,0,9,13,1,16,1,0,0,0,7,11,2,16,1,0,0,0,0,0,9,14,0,0,0,0,0,1,16,9,2,4,0,0,0,13,16,16,16,16,0,0,0,11,16,9,8,9,0,2 +0,0,0,7,14,2,0,0,0,0,5,16,16,4,0,0,0,0,13,16,7,0,0,0,0,0,14,13,0,0,0,0,0,0,16,16,16,5,2,0,0,0,14,16,15,13,15,1,0,0,8,16,11,14,16,1,0,0,0,4,12,14,7,0,6 +0,0,0,5,12,5,0,0,0,0,2,16,14,7,0,0,0,0,8,16,2,0,0,0,0,0,14,7,0,0,0,0,0,0,15,14,16,16,5,0,0,1,16,16,12,6,14,0,0,0,10,16,7,7,16,1,0,0,1,8,14,16,10,1,6 +0,0,6,15,16,13,1,0,0,3,16,16,16,16,6,0,0,7,16,8,12,14,1,0,0,0,12,16,14,1,0,0,0,0,11,14,16,6,0,0,0,0,14,0,2,15,3,0,0,0,16,2,3,13,8,0,0,0,6,14,16,14,2,0,8 +0,0,1,14,15,2,0,0,0,2,12,16,16,5,0,0,5,16,16,16,16,2,0,0,1,8,5,11,14,0,0,0,0,0,0,10,16,1,0,0,0,0,0,8,15,0,0,0,0,0,0,12,14,0,0,0,0,0,0,14,16,1,0,0,1 +0,0,7,16,12,1,0,0,0,0,9,12,9,11,0,0,0,0,5,13,10,16,3,0,0,0,0,9,16,16,9,0,0,0,0,0,0,6,13,0,0,0,0,0,0,3,15,0,0,0,0,0,0,5,16,0,0,0,6,16,16,16,12,0,9 +0,0,6,15,13,2,0,0,0,0,11,16,13,12,0,0,0,0,8,13,6,16,2,0,0,0,0,2,2,16,5,0,0,0,0,0,4,16,4,0,0,0,0,0,10,16,3,0,0,0,2,11,16,15,8,1,0,0,6,14,16,16,16,14,2 +0,1,10,14,15,6,0,0,0,6,13,1,4,9,0,0,0,3,14,2,1,9,3,0,0,0,7,15,16,16,3,0,0,0,0,0,6,10,0,0,0,0,0,0,4,11,0,0,0,0,2,0,4,9,0,0,0,0,8,14,15,9,0,0,9 +0,0,0,12,14,0,0,0,0,0,4,16,14,1,0,0,0,0,10,15,2,0,0,0,0,0,14,14,1,0,0,0,0,0,14,16,15,5,0,0,0,0,10,11,4,14,1,0,0,0,6,12,4,14,7,0,0,0,0,9,16,16,6,0,6 +0,0,0,9,10,2,0,0,0,0,5,16,16,2,0,0,0,4,14,16,16,4,0,0,0,8,11,10,16,6,0,0,0,0,0,7,16,3,0,0,0,0,0,10,16,2,0,0,0,0,0,12,11,0,0,0,0,0,0,9,13,0,0,0,1 +0,0,14,4,4,0,0,0,0,9,16,16,16,9,0,0,0,3,11,5,14,15,0,0,0,0,0,14,16,10,0,0,0,0,0,9,16,16,6,0,0,0,1,0,0,7,16,0,0,2,15,4,5,12,15,0,0,0,10,16,16,15,3,0,3 +0,0,15,16,16,15,2,0,0,0,5,8,11,16,3,0,0,0,0,0,15,8,0,0,0,3,8,8,16,7,2,0,0,6,15,16,16,16,8,0,0,0,2,15,7,3,0,0,0,0,11,13,0,0,0,0,0,0,16,7,0,0,0,0,7 +0,0,5,14,15,4,0,0,0,1,16,2,3,13,2,0,0,7,12,4,0,5,4,0,0,0,13,3,0,1,7,0,0,2,12,0,0,1,8,0,0,0,13,0,0,2,12,0,0,0,14,1,0,13,4,0,0,0,6,16,15,9,1,0,0 +0,0,8,12,11,1,0,0,0,0,13,12,6,12,0,0,0,1,14,4,0,12,0,0,0,2,16,1,0,5,4,0,0,5,11,0,0,8,4,0,0,4,12,0,0,9,5,0,0,2,15,6,8,14,0,0,0,0,5,15,12,3,0,0,0 +0,0,5,13,14,12,1,0,0,0,9,16,7,7,7,0,0,0,16,4,0,1,8,0,0,4,12,0,0,4,8,0,0,4,8,0,0,6,5,0,0,5,8,0,0,11,2,0,0,1,14,5,11,8,0,0,0,0,4,14,11,0,0,0,0 +0,0,9,15,16,4,0,0,0,0,8,7,6,16,0,0,0,0,0,0,10,12,0,0,0,0,3,15,15,1,0,0,0,0,1,9,12,11,2,0,0,0,0,0,0,5,10,0,0,0,12,4,4,6,15,0,0,0,5,11,12,12,6,0,3 +0,0,12,16,15,6,0,0,0,0,15,13,11,16,3,0,0,0,1,7,5,16,5,0,0,0,1,13,16,11,0,0,0,0,1,10,15,15,1,0,0,0,0,0,0,13,11,0,0,2,11,4,4,14,8,0,0,0,11,16,16,14,1,0,3 +0,3,16,16,16,16,8,0,0,0,5,8,12,16,6,0,0,0,0,0,13,12,0,0,0,8,12,12,16,10,1,0,0,8,16,16,16,15,1,0,0,0,8,15,3,0,0,0,0,2,15,8,0,0,0,0,0,2,15,2,0,0,0,0,7 +0,0,0,4,16,5,0,0,0,0,1,12,16,6,0,0,0,5,14,16,16,1,0,0,0,13,16,14,16,3,0,0,0,1,2,9,16,4,0,0,0,0,0,5,16,4,0,0,0,0,0,4,16,6,0,0,0,0,0,5,16,9,0,0,1 +0,0,0,0,15,8,0,0,0,0,0,2,16,8,0,0,0,0,0,11,16,3,0,0,0,0,5,16,16,3,0,0,0,9,16,11,16,1,0,0,0,1,7,1,16,2,0,0,0,0,0,1,16,5,0,0,0,0,0,0,13,8,0,0,1 +0,0,13,16,16,12,0,0,0,0,9,12,8,7,0,0,0,0,11,16,7,0,0,0,0,0,11,15,14,3,0,0,0,0,1,2,6,11,0,0,0,0,0,0,3,14,0,0,0,0,0,0,9,12,0,0,0,0,12,16,16,6,0,0,5 +0,0,8,16,16,7,0,0,0,2,16,14,7,16,0,0,0,3,15,1,5,16,1,0,0,1,14,13,13,16,3,0,0,0,1,8,8,14,6,0,0,0,0,0,0,10,10,0,0,0,0,1,2,15,9,0,0,0,10,16,16,16,4,0,9 +0,0,0,8,11,0,0,0,0,0,3,16,6,0,0,0,0,0,9,10,0,0,0,0,0,0,11,4,1,0,0,0,0,0,13,15,16,14,4,0,0,0,13,10,4,4,14,1,0,0,6,10,2,6,16,2,0,0,1,8,13,14,5,0,6 +0,0,0,9,14,1,0,0,0,0,2,16,11,0,0,0,0,0,7,16,5,0,0,0,0,0,11,14,0,0,0,0,0,0,14,10,4,0,0,0,0,0,16,16,16,13,2,0,0,0,9,15,6,12,10,0,0,0,0,8,16,16,12,0,6 +0,0,0,3,16,9,0,0,0,0,0,8,16,10,0,0,0,0,5,15,16,11,0,0,0,5,16,16,16,12,0,0,0,7,12,3,16,11,0,0,0,0,0,0,15,9,0,0,0,0,0,3,16,9,0,0,0,0,0,4,16,4,0,0,1 +0,0,4,10,14,3,0,0,0,0,16,11,10,12,0,0,0,4,12,0,0,10,3,0,0,5,11,0,0,7,4,0,0,6,8,0,0,10,4,0,0,3,9,0,0,10,3,0,0,0,13,9,11,13,0,0,0,0,2,14,12,3,0,0,0 +0,2,16,14,12,9,2,0,0,0,6,12,16,16,12,0,0,1,1,1,15,11,2,0,0,10,13,12,16,7,0,0,0,3,14,16,16,16,5,0,0,0,10,14,6,6,1,0,0,2,16,8,0,0,0,0,0,2,16,4,0,0,0,0,7 +0,0,3,12,13,3,0,0,0,0,12,14,7,12,0,0,0,1,16,1,0,14,0,0,0,0,14,0,0,15,0,0,0,0,1,0,4,12,0,0,0,0,0,0,14,8,0,0,0,0,2,14,16,12,14,2,0,0,7,16,12,12,12,3,2 +0,0,8,16,15,5,0,0,0,7,16,14,5,15,2,0,0,10,16,8,0,9,9,0,0,10,16,3,0,4,12,0,0,9,14,0,0,6,12,0,0,7,13,0,0,14,7,0,0,2,16,8,12,14,1,0,0,0,10,16,13,3,0,0,0 +0,0,0,11,13,0,0,0,0,0,3,15,9,0,0,0,0,0,10,13,1,5,11,0,0,5,16,4,3,14,11,0,0,10,16,9,15,15,3,0,0,9,16,13,16,9,0,0,0,0,0,6,16,2,0,0,0,0,0,14,9,0,0,0,4 +0,2,11,11,16,16,4,0,0,3,16,12,10,8,0,0,0,7,16,11,1,0,0,0,0,11,16,16,13,0,0,0,0,1,4,0,16,4,0,0,0,0,0,0,12,8,0,0,0,0,4,4,16,5,0,0,0,1,15,16,13,1,0,0,5 +0,2,6,16,11,0,0,0,0,9,16,8,10,10,0,0,0,7,11,0,0,11,1,0,0,8,13,0,0,7,5,0,0,6,13,0,0,8,7,0,0,2,14,0,0,14,4,0,0,0,12,7,12,12,0,0,0,0,2,13,14,4,0,0,0 +0,0,6,16,16,13,1,0,0,0,11,13,7,16,6,0,0,0,1,3,0,16,7,0,0,0,0,2,8,15,2,0,0,0,8,16,16,8,0,0,0,0,0,5,11,15,6,0,0,0,0,0,1,14,12,0,0,0,9,13,16,15,3,0,3 +0,0,0,12,15,2,0,0,0,0,6,16,11,1,0,0,0,0,14,14,0,0,0,0,0,0,16,11,3,0,0,0,0,2,16,16,16,7,0,0,0,0,16,13,4,14,5,0,0,0,10,14,6,15,10,0,0,0,0,9,15,16,5,0,6 +0,0,3,8,14,9,0,0,0,0,11,14,6,13,3,0,0,3,16,3,0,6,6,0,0,6,15,0,0,8,5,0,0,4,12,0,0,11,1,0,0,1,12,0,4,10,0,0,0,0,9,11,14,5,0,0,0,0,2,13,7,0,0,0,0 +0,0,8,16,16,10,0,0,0,1,15,15,9,15,1,0,0,0,14,3,0,15,5,0,0,0,0,0,0,15,6,0,0,0,0,0,5,16,4,0,0,0,0,0,12,14,0,0,0,0,8,15,16,6,4,1,0,0,10,16,16,16,16,9,2 +0,0,0,4,16,5,0,0,0,0,0,11,16,8,0,0,0,2,9,16,16,4,0,0,0,11,16,16,16,4,0,0,0,3,4,6,16,4,0,0,0,0,0,4,16,3,0,0,0,0,0,6,16,6,0,0,0,0,0,6,16,2,0,0,1 +0,0,0,4,12,1,0,0,0,0,2,16,11,3,0,0,0,0,7,15,1,0,0,0,0,0,11,11,4,2,0,0,0,0,13,16,13,15,4,0,0,0,13,7,0,0,15,1,0,0,5,13,2,3,15,1,0,0,0,6,14,15,5,0,6 +0,0,0,10,16,2,0,0,0,0,4,16,13,1,0,0,0,0,10,16,2,0,0,0,0,0,13,10,0,0,0,0,0,0,16,15,10,0,0,0,0,0,14,16,13,10,0,0,0,0,11,16,8,16,1,0,0,0,1,10,16,13,0,0,6 +0,0,0,10,13,0,0,0,0,0,3,16,10,0,0,0,0,0,6,15,1,0,0,0,0,0,10,13,0,0,0,0,0,0,8,16,15,7,0,0,0,0,11,15,12,13,7,0,0,0,5,15,5,7,16,1,0,0,0,6,15,16,15,1,6 +0,0,5,16,16,8,0,0,0,0,6,14,10,16,1,0,0,0,0,4,13,16,1,0,0,0,6,16,16,5,0,0,0,0,1,9,15,10,0,0,0,0,6,0,3,15,5,0,0,0,14,8,11,16,7,0,0,0,5,15,16,13,1,0,3 +0,0,0,8,16,5,0,0,0,0,0,13,16,7,0,0,0,0,5,16,8,0,0,0,0,0,7,16,4,0,0,0,0,0,9,15,8,8,5,0,0,0,15,16,16,16,16,4,0,0,5,15,10,8,16,5,0,0,0,6,15,16,14,0,6 +0,1,7,15,13,6,0,0,0,7,13,2,4,15,0,0,0,4,12,6,13,4,0,0,0,0,10,16,8,0,0,0,0,0,14,12,6,0,0,0,0,2,12,0,12,3,0,0,0,0,14,1,6,8,0,0,0,0,4,13,13,5,0,0,8 +0,0,5,12,14,3,0,0,0,0,9,10,7,12,0,0,0,0,0,0,6,15,0,0,0,0,1,5,15,7,0,0,0,0,2,14,16,1,0,0,0,0,0,0,8,13,1,0,0,0,4,1,1,10,8,0,0,0,7,13,16,13,7,0,3 +0,3,15,16,16,16,4,0,0,1,10,8,12,16,0,0,0,0,0,0,14,10,0,0,0,5,16,13,16,14,9,0,0,3,12,16,13,8,2,0,0,0,9,14,1,0,0,0,0,2,16,7,0,0,0,0,0,5,13,1,0,0,0,0,7 +0,0,0,13,8,0,0,0,0,0,11,13,3,0,0,0,0,2,16,5,0,4,2,0,0,7,14,1,5,16,5,0,0,6,15,12,16,11,0,0,0,0,6,11,16,1,0,0,0,0,0,8,13,0,0,0,0,0,0,15,7,0,0,0,4 +0,0,6,16,14,5,0,0,0,0,9,15,12,15,0,0,0,0,0,6,2,16,5,0,0,0,0,0,6,16,3,0,0,0,0,0,13,15,0,0,0,0,1,9,16,8,0,0,0,0,11,16,16,12,5,0,0,0,6,16,16,16,14,0,2 +0,7,16,16,16,16,3,0,0,3,12,12,13,15,1,0,0,0,0,2,14,6,0,0,0,3,8,11,15,8,5,0,0,5,16,16,14,12,6,0,0,0,7,13,0,0,0,0,0,2,16,3,0,0,0,0,0,7,11,0,0,0,0,0,7 +0,0,5,16,16,7,0,0,0,0,6,15,8,15,3,0,0,0,0,0,0,15,6,0,0,0,0,1,10,16,5,0,0,0,0,10,16,8,0,0,0,0,4,1,13,13,0,0,0,3,15,5,7,16,0,0,0,0,2,15,16,13,1,0,3 +0,0,1,15,10,0,0,0,0,0,11,15,3,0,0,0,0,5,16,7,0,9,5,0,0,11,13,0,8,16,11,0,0,11,15,12,16,12,0,0,0,3,11,16,16,1,0,0,0,0,0,13,12,0,0,0,0,0,0,15,8,0,0,0,4 +0,0,5,16,0,0,0,0,0,0,12,9,2,13,1,0,0,4,16,1,8,15,0,0,0,8,13,1,14,15,11,0,0,7,16,16,16,9,2,0,0,1,8,14,13,0,0,0,0,0,1,15,7,0,0,0,0,0,4,16,0,0,0,0,4 +0,0,0,4,15,12,0,0,0,0,0,10,16,14,0,0,0,2,9,16,16,13,0,0,0,14,16,13,14,14,0,0,0,5,12,0,11,16,0,0,0,0,0,0,13,14,0,0,0,0,0,1,16,11,0,0,0,0,0,4,16,7,0,0,1 +0,0,6,15,12,2,0,0,0,3,16,5,8,12,0,0,0,3,14,4,5,16,0,0,0,0,6,12,12,14,1,0,0,0,0,0,0,6,6,0,0,0,0,0,0,3,9,0,0,0,2,1,1,8,10,0,0,0,5,11,15,14,7,0,9 +0,0,0,12,13,1,0,0,0,0,1,14,16,3,0,0,0,2,13,16,16,2,0,0,0,15,16,16,16,3,0,0,0,5,3,10,16,2,0,0,0,0,0,6,16,5,0,0,0,0,0,9,16,5,0,0,0,0,0,10,16,3,0,0,1 +0,4,16,16,16,16,6,0,0,3,11,8,14,15,2,0,0,0,0,0,15,7,0,0,0,0,2,7,16,8,3,0,0,7,16,16,16,16,11,0,0,1,9,16,3,0,0,0,0,0,13,13,0,0,0,0,0,6,15,4,0,0,0,0,7 +0,2,16,16,16,16,5,0,0,0,4,8,10,16,6,0,0,0,0,0,10,9,0,0,0,3,4,4,15,5,3,0,0,9,16,16,16,16,8,0,0,1,12,14,5,2,0,0,0,1,14,6,0,0,0,0,0,4,14,0,0,0,0,0,7 +0,1,14,16,6,0,0,0,0,4,15,10,13,0,0,0,0,1,3,4,15,0,0,0,0,0,0,2,15,0,0,0,0,0,0,9,9,0,0,0,0,0,1,15,4,0,0,0,0,1,13,14,7,2,0,0,0,3,15,16,16,16,13,1,2 +0,0,0,1,13,2,0,0,0,0,0,8,16,4,0,0,0,0,0,16,8,0,0,0,0,0,3,16,2,0,0,0,0,0,13,16,11,5,0,0,0,1,15,12,6,12,4,0,0,0,1,13,8,12,10,0,0,0,0,2,12,13,8,0,6 +0,1,12,14,16,11,0,0,0,3,15,11,13,16,2,0,0,0,2,11,16,5,0,0,0,0,8,16,16,6,0,0,0,0,0,3,12,16,3,0,0,0,0,0,0,12,15,0,0,3,10,1,3,14,15,0,0,3,11,16,16,16,4,0,3 +0,0,11,12,16,15,2,0,0,0,14,13,4,4,0,0,0,0,14,9,0,0,0,0,0,1,16,16,11,1,0,0,0,0,3,4,14,7,0,0,0,0,0,0,6,12,0,0,0,2,9,2,14,11,0,0,0,0,15,16,15,3,0,0,5 +0,3,16,16,16,16,0,0,0,0,13,12,8,4,0,0,0,0,12,12,0,0,0,0,0,0,16,16,8,0,0,0,0,0,11,5,14,7,0,0,0,0,0,0,8,16,0,0,0,2,11,4,14,13,0,0,0,2,15,16,15,2,0,0,5 +0,0,9,15,16,6,0,0,0,7,16,12,11,14,1,0,0,7,16,4,9,16,5,0,0,0,11,16,16,16,3,0,0,0,0,3,8,16,2,0,0,0,0,0,3,16,4,0,0,0,9,2,5,16,3,0,0,0,10,16,16,13,1,0,9 +0,2,13,16,14,3,0,0,0,11,15,8,14,14,0,0,0,9,6,0,6,15,2,0,0,0,0,0,11,13,0,0,0,0,0,2,16,6,0,0,0,0,1,12,13,0,0,0,0,1,12,16,8,4,4,0,0,2,15,16,16,16,16,5,2 +0,0,9,16,16,8,0,0,0,0,15,13,12,12,0,0,0,0,11,2,13,11,0,0,0,0,0,4,16,8,0,0,0,0,5,15,10,0,0,0,0,3,16,13,0,0,0,0,0,7,16,10,4,4,7,0,0,0,8,16,16,16,14,0,2 +0,3,16,14,12,13,7,0,0,0,7,8,13,15,5,0,0,0,0,3,15,4,0,0,0,0,0,9,10,0,0,0,0,9,14,16,13,12,9,0,0,3,13,13,9,8,5,0,0,2,16,2,0,0,0,0,0,5,14,0,0,0,0,0,7 +0,0,0,14,6,0,0,0,0,0,4,16,6,0,0,0,0,0,8,15,0,0,0,0,0,0,12,11,4,3,0,0,0,0,15,16,16,16,10,0,0,0,16,12,4,3,12,6,0,0,9,13,1,0,11,12,0,0,0,9,15,16,16,7,6 +0,0,0,15,2,0,0,0,0,0,10,12,0,0,0,0,0,5,15,1,0,6,6,0,0,12,9,0,3,16,11,0,0,6,16,12,14,13,1,0,0,0,3,11,16,2,0,0,0,0,0,10,12,0,0,0,0,0,0,15,7,0,0,0,4 +0,0,5,16,16,10,0,0,0,0,14,12,10,13,0,0,0,0,11,12,15,10,0,0,0,0,3,16,14,1,0,0,0,0,7,16,15,0,0,0,0,0,16,9,14,6,0,0,0,1,16,4,10,12,0,0,0,0,5,16,16,8,0,0,8 +0,5,15,15,3,0,0,0,0,12,14,13,12,0,0,0,0,7,9,3,16,2,0,0,0,0,0,0,16,4,0,0,0,0,0,5,16,0,0,0,0,0,0,9,12,0,0,0,0,1,8,16,12,4,1,0,0,8,16,16,16,16,13,0,2 +0,0,7,10,10,2,0,0,0,0,12,16,16,4,0,0,0,0,5,16,16,4,0,0,0,0,4,16,16,5,0,0,0,0,1,15,16,7,0,0,0,0,4,16,16,12,0,0,0,0,9,16,16,12,0,0,0,0,6,12,12,11,0,0,1 +0,7,16,13,2,0,0,0,0,8,14,12,14,0,0,0,0,1,2,4,16,0,0,0,0,0,0,0,16,4,0,0,0,0,0,5,16,2,0,0,0,0,0,11,15,0,0,0,0,1,11,16,16,13,11,0,0,4,16,16,12,12,9,0,2 +0,0,8,15,16,8,0,0,0,5,16,12,11,16,0,0,0,3,8,1,12,13,0,0,0,0,0,11,16,13,1,0,0,0,0,5,8,15,9,0,0,0,2,1,0,9,11,0,0,0,14,12,5,15,7,0,0,0,9,16,16,13,1,0,3 +0,0,8,16,12,2,0,0,0,0,13,4,6,12,0,0,0,3,15,1,3,13,0,0,0,6,14,10,4,16,1,0,0,0,8,12,11,13,4,0,0,0,0,0,0,6,8,0,0,0,6,4,0,6,7,0,0,0,6,14,14,15,1,0,9 +0,0,5,16,16,6,0,0,0,1,15,13,10,15,1,0,0,5,16,9,0,12,7,0,0,8,11,0,0,7,12,0,0,12,9,0,0,4,12,0,0,9,13,0,0,6,13,0,0,1,15,10,4,11,11,0,0,0,6,16,16,13,1,0,0 +0,0,3,11,9,3,0,0,0,0,8,16,16,15,1,0,0,0,1,16,16,16,0,0,0,0,2,16,16,15,2,0,0,0,4,16,16,16,3,0,0,0,6,16,16,16,0,0,0,0,9,16,16,14,0,0,0,0,7,12,11,5,0,0,1 +0,3,16,16,7,0,0,0,0,4,16,11,16,5,0,0,0,0,3,1,12,12,0,0,0,0,0,0,10,12,0,0,0,0,0,0,10,11,0,0,0,0,0,3,15,8,0,0,0,0,4,14,16,11,6,0,0,1,16,16,16,14,16,5,2 +0,1,12,12,13,13,4,0,0,3,14,6,4,2,1,0,0,5,11,6,2,0,0,0,0,6,16,12,15,4,0,0,0,1,1,0,5,14,0,0,0,0,0,0,2,16,0,0,0,0,4,3,7,14,0,0,0,2,14,16,14,3,0,0,5 +0,2,11,16,15,4,0,0,0,11,15,8,15,10,0,0,0,3,3,8,16,7,0,0,0,0,4,16,16,9,0,0,0,0,0,4,7,16,4,0,0,0,0,0,0,13,11,0,0,2,15,6,1,14,10,0,0,2,16,16,16,13,3,0,3 +0,0,9,16,10,1,0,0,0,9,16,9,16,6,0,0,0,16,9,3,16,5,0,0,0,3,6,12,16,10,0,0,0,0,1,10,9,15,8,0,0,0,0,0,0,9,15,0,0,0,3,9,2,13,12,0,0,0,7,16,16,15,4,0,3 +0,0,2,10,13,3,0,0,0,1,13,7,7,13,0,0,0,7,7,0,2,16,1,0,0,2,11,2,0,13,4,0,0,0,6,15,13,16,8,0,0,0,0,0,0,8,8,0,0,0,0,10,4,9,7,0,0,0,0,11,16,14,2,0,9 +0,0,0,1,14,7,0,0,0,0,0,5,16,1,0,0,0,0,2,14,7,0,0,0,0,0,10,15,1,6,12,0,0,3,15,3,1,14,6,0,1,15,10,4,10,15,0,0,10,16,16,16,16,12,0,0,0,2,2,0,16,2,0,0,4 +0,0,1,12,7,0,0,0,0,0,10,13,0,0,0,0,0,2,15,3,0,0,0,0,0,4,15,0,3,0,0,0,0,4,14,15,16,15,2,0,0,0,16,11,2,6,12,0,0,0,13,10,1,5,13,0,0,0,2,10,16,16,6,0,6 +0,0,0,9,15,10,0,0,0,0,6,10,4,14,4,0,0,0,12,2,0,12,5,0,0,5,15,3,6,13,2,0,0,0,5,16,14,1,0,0,0,0,2,14,12,5,0,0,0,0,0,13,1,13,3,0,0,0,0,9,13,13,2,0,8 +0,1,12,16,15,3,0,0,0,11,13,5,13,8,0,0,0,3,1,8,16,5,0,0,0,0,3,16,16,13,1,0,0,0,0,0,1,14,6,0,0,0,1,0,0,9,11,0,0,2,15,1,0,13,9,0,0,0,13,16,15,15,2,0,3 +0,0,8,12,13,14,4,0,0,0,15,8,2,4,1,0,0,1,16,5,2,0,0,0,0,4,12,13,14,8,0,0,0,0,0,0,1,12,4,0,0,0,0,0,0,8,5,0,0,0,6,3,2,14,2,0,0,0,9,16,14,5,0,0,5 +0,0,6,12,0,0,0,0,0,0,12,9,0,0,0,0,0,0,15,3,0,0,0,0,0,1,16,1,0,0,0,0,0,3,16,15,10,4,2,0,0,2,16,9,0,6,12,0,0,0,13,10,0,6,15,0,0,0,6,16,16,15,7,0,6 +0,0,0,1,12,10,0,0,0,0,0,1,16,7,0,0,0,0,0,12,12,0,4,0,0,0,5,15,3,7,13,0,0,2,15,5,1,15,6,0,0,12,14,8,11,16,5,0,1,15,16,15,15,14,2,0,0,0,0,0,12,10,0,0,4 +0,0,0,11,6,0,0,0,0,0,5,16,3,0,0,0,0,0,10,14,0,0,0,0,0,0,14,8,0,0,0,0,0,0,16,15,16,14,5,0,0,0,15,9,0,4,14,0,0,0,9,15,0,3,16,2,0,0,0,9,14,12,7,0,6 +0,0,12,15,16,8,0,0,0,0,11,13,0,2,0,0,0,0,11,6,1,0,0,0,0,2,16,16,15,8,0,0,0,0,3,0,0,11,2,0,0,0,0,0,0,8,8,0,0,4,5,0,1,12,2,0,0,1,11,15,16,8,0,0,5 +0,0,0,10,2,0,0,0,0,0,2,16,5,0,0,0,0,0,6,11,0,0,0,0,0,0,12,13,7,1,0,0,0,1,16,14,12,14,2,0,0,2,16,10,0,6,13,0,0,0,10,14,2,1,16,2,0,0,1,10,16,15,14,2,6 +0,0,6,12,9,4,0,0,0,0,2,14,16,14,0,0,0,0,0,12,16,16,0,0,0,0,0,15,16,13,0,0,0,0,0,16,16,12,0,0,0,0,5,16,16,5,0,0,0,0,7,16,16,5,0,0,0,0,9,12,11,2,0,0,1 +0,0,3,13,0,0,0,0,0,0,11,9,0,0,0,0,0,0,14,6,0,0,0,0,0,0,16,1,0,0,0,0,0,2,16,15,16,8,0,0,0,1,16,13,5,11,10,0,0,0,13,11,0,2,16,3,0,0,2,15,16,16,13,1,6 +0,0,13,16,15,13,4,0,0,0,11,14,4,2,1,0,0,0,6,12,15,7,0,0,0,0,0,0,1,11,2,0,0,0,0,0,0,5,7,0,0,1,3,0,0,5,8,0,0,7,9,2,4,13,4,0,0,1,10,14,14,7,0,0,5 +0,0,9,12,12,12,7,0,0,0,10,12,4,4,2,0,0,0,14,9,5,0,0,0,0,3,16,15,14,12,0,0,0,3,9,0,0,15,5,0,0,0,0,0,0,10,7,0,0,0,7,6,1,15,2,0,0,0,8,16,16,10,0,0,5 +0,0,3,10,15,13,2,0,0,3,14,12,9,12,7,0,0,4,14,4,2,14,2,0,0,0,5,15,14,8,0,0,0,0,3,15,12,13,1,0,0,0,10,8,0,12,7,0,0,0,11,7,0,9,8,0,0,0,3,11,15,14,2,0,8 +0,0,9,16,16,12,14,0,0,0,4,8,11,14,13,0,0,0,0,0,3,15,2,0,0,0,0,6,13,15,8,0,0,0,6,16,15,8,2,0,0,0,1,14,9,0,0,0,0,0,4,16,1,0,0,0,0,0,12,9,0,0,0,0,7 +0,0,13,15,16,16,3,0,0,1,14,4,4,0,0,0,0,2,14,11,7,1,0,0,0,2,15,9,13,11,0,0,0,0,0,0,0,15,2,0,0,0,0,0,0,14,3,0,0,3,13,4,6,14,0,0,0,0,13,16,12,3,0,0,5 +0,0,12,16,12,1,0,0,0,8,15,10,15,6,0,0,0,3,6,1,15,3,0,0,0,0,0,10,16,9,1,0,0,0,2,14,12,16,6,0,0,0,4,0,0,10,9,0,0,0,16,8,6,15,7,0,0,0,10,16,16,13,1,0,3 +0,0,0,0,12,7,0,0,0,0,0,2,16,4,0,0,0,0,0,12,13,9,3,0,0,0,8,15,4,16,1,0,0,1,15,8,4,16,0,0,0,14,14,8,11,15,1,0,0,9,16,16,16,16,4,0,0,0,0,0,13,6,0,0,4 +0,0,10,16,15,14,8,0,0,0,7,9,8,14,13,0,0,0,0,0,3,15,2,0,0,0,0,6,13,13,6,0,0,0,1,16,15,12,4,0,0,0,0,12,6,0,0,0,0,0,6,13,1,0,0,0,0,0,12,6,0,0,0,0,7 +0,0,6,13,15,6,0,0,0,1,16,5,3,13,0,0,0,8,13,0,4,12,0,0,0,3,12,5,15,4,0,0,0,0,7,16,11,0,0,0,0,0,14,7,12,7,0,0,0,0,13,1,1,16,2,0,0,0,4,13,13,10,1,0,8 +0,3,15,15,3,0,0,0,0,6,16,10,14,2,0,0,0,0,4,0,16,5,0,0,0,0,0,0,15,7,0,0,0,0,0,3,16,2,0,0,0,0,0,8,14,0,0,0,0,0,10,16,7,6,7,1,0,2,16,16,16,16,13,2,2 +0,0,0,13,6,0,0,0,0,0,4,15,1,0,0,0,0,0,11,9,0,0,0,0,0,1,15,6,0,0,0,0,0,1,16,12,16,12,3,0,0,0,15,16,5,6,14,0,0,0,12,15,1,1,15,2,0,0,1,12,15,15,10,0,6 +0,0,4,12,9,0,0,0,0,0,8,15,9,9,0,0,0,0,15,6,0,12,2,0,0,3,12,0,0,8,7,0,0,7,11,0,0,5,8,0,0,6,12,0,0,7,8,0,0,3,16,7,5,15,4,0,0,0,5,13,13,8,0,0,0 +0,0,5,12,15,11,2,0,0,7,11,5,5,16,4,0,0,7,6,0,11,13,0,0,0,0,13,14,11,1,0,0,0,0,14,14,13,2,0,0,0,3,13,0,8,12,2,0,0,0,14,3,0,11,12,0,0,0,8,16,16,14,3,0,8 +0,0,6,12,8,4,0,0,0,0,6,16,16,10,0,0,0,0,2,16,16,14,0,0,0,0,0,14,16,16,1,0,0,0,1,14,16,13,0,0,0,0,7,16,16,10,0,0,0,0,8,16,16,7,0,0,0,0,6,12,12,11,0,0,1 +0,2,14,16,10,1,0,0,0,11,15,8,15,9,0,0,0,8,5,0,11,12,0,0,0,0,0,0,11,15,0,0,0,0,0,1,16,7,0,0,0,0,0,8,16,4,0,0,0,0,7,16,14,4,1,0,0,2,16,16,16,16,15,1,2 +0,0,7,16,16,10,0,0,0,0,13,16,16,7,0,0,0,0,9,16,16,14,1,0,0,0,7,16,16,13,0,0,0,0,2,16,16,10,0,0,0,0,6,16,16,10,0,0,0,0,12,16,16,6,0,0,0,0,8,16,16,10,0,0,1 +0,0,3,14,3,0,0,0,0,0,12,11,0,0,0,0,0,0,15,4,0,0,0,0,0,4,16,10,6,0,0,0,0,3,16,15,13,11,1,0,0,2,16,4,0,9,9,0,0,0,13,11,0,6,15,0,0,0,4,14,13,16,7,0,6 +0,0,5,12,14,5,0,0,0,0,6,16,16,13,1,0,0,0,0,16,16,16,0,0,0,0,0,13,16,16,1,0,0,0,0,14,16,10,0,0,0,0,2,16,16,8,0,0,0,0,7,16,16,5,0,0,0,0,9,16,16,7,0,0,1 +0,0,0,6,14,1,0,0,0,0,0,13,7,0,0,0,0,0,6,14,2,0,6,0,0,1,14,5,0,9,15,0,0,9,14,0,2,16,6,0,1,15,15,12,14,15,3,0,1,12,12,10,16,4,0,0,0,0,0,7,13,0,0,0,4 +0,0,2,10,16,13,0,0,0,2,15,9,1,15,3,0,0,8,10,0,6,14,0,0,0,6,14,12,14,3,0,0,0,2,16,13,11,0,0,0,0,7,10,0,11,8,0,0,0,0,14,3,0,13,1,0,0,0,2,14,16,14,0,0,8 +0,0,9,15,12,2,0,0,0,4,15,5,8,15,0,0,0,7,13,0,4,16,1,0,0,2,16,7,8,16,4,0,0,0,5,12,11,14,6,0,0,0,0,0,0,12,8,0,0,0,3,2,1,13,4,0,0,0,11,16,16,10,0,0,9 +0,1,14,15,2,0,0,0,0,2,13,12,12,0,0,0,0,0,0,4,16,3,0,0,0,0,0,1,16,6,0,0,0,0,0,5,16,0,0,0,0,0,0,8,12,0,0,0,0,0,5,16,10,8,7,1,0,1,15,16,16,14,12,2,2 +0,0,4,15,0,0,0,0,0,0,11,9,0,0,0,0,0,0,16,3,0,0,0,0,0,3,16,4,7,0,0,0,0,3,16,16,14,11,1,0,0,3,16,9,0,8,10,0,0,0,14,9,0,2,16,0,0,0,3,14,16,16,10,1,6 +0,2,12,13,15,16,7,0,0,2,16,6,0,0,1,0,0,1,16,12,8,0,0,0,0,2,12,6,11,13,0,0,0,0,0,0,0,11,6,0,0,1,1,0,0,8,8,0,0,6,10,0,3,15,2,0,0,2,15,16,12,5,0,0,5 +0,0,3,12,13,1,0,0,0,1,14,11,10,8,0,0,0,4,16,3,0,12,2,0,0,4,13,1,0,9,6,0,0,6,12,0,0,5,8,0,0,3,13,0,0,4,9,0,0,0,15,8,4,13,8,0,0,0,5,15,15,9,0,0,0 +0,0,7,16,16,4,0,0,0,5,15,14,8,13,0,0,0,8,12,0,0,12,6,0,0,8,8,0,0,6,12,0,0,12,8,0,0,4,12,0,0,8,10,0,0,7,11,0,0,2,16,7,6,14,7,0,0,0,6,15,15,8,0,0,0 +0,0,3,11,13,10,0,0,0,3,15,7,4,14,2,0,0,8,13,1,1,14,2,0,0,7,13,14,10,13,0,0,0,0,1,14,16,1,0,0,0,0,10,11,10,10,0,0,0,0,11,6,3,13,0,0,0,0,3,13,16,9,0,0,8 +0,0,0,4,15,2,0,0,0,0,0,9,12,0,1,2,0,0,1,15,4,0,12,8,0,1,12,9,0,5,15,1,0,8,13,0,0,12,10,0,4,16,13,12,14,15,1,0,7,14,12,12,16,6,0,0,0,0,0,6,14,0,0,0,4 +0,0,0,0,15,11,0,0,0,0,0,1,16,8,0,0,0,0,0,12,13,1,0,0,0,0,7,15,2,12,3,0,0,1,15,7,2,16,4,0,0,10,16,11,11,16,3,0,0,15,12,12,15,16,5,0,0,0,0,0,13,7,0,0,4 +0,0,14,16,16,15,5,0,0,0,13,8,4,5,2,0,0,0,13,6,2,0,0,0,0,0,16,13,14,2,0,0,0,0,0,0,5,11,0,0,0,0,0,0,0,16,0,0,0,4,11,1,5,12,0,0,0,1,11,16,15,3,0,0,5 +0,1,6,14,11,0,0,0,0,7,10,2,15,4,0,0,0,5,10,1,13,9,0,0,0,2,14,4,12,16,0,0,0,0,5,12,5,11,7,0,0,0,0,0,0,8,8,0,0,0,3,1,0,11,6,0,0,0,10,13,12,8,0,0,9 +0,0,6,14,16,9,0,0,0,5,16,10,12,12,0,0,0,1,7,0,9,12,0,0,0,0,2,13,16,8,0,0,0,0,2,13,14,16,7,0,0,0,1,0,0,10,9,0,0,0,11,9,5,15,7,0,0,0,7,16,16,10,0,0,3 +0,0,11,15,0,0,0,0,0,0,9,16,1,0,0,0,0,0,9,16,0,0,0,0,0,0,11,13,0,9,5,0,0,0,13,11,0,11,13,0,0,1,15,10,6,12,16,1,0,4,16,16,16,15,16,4,0,0,7,7,4,0,15,7,4 +0,0,0,10,15,6,0,0,0,0,7,16,10,12,2,0,0,1,15,5,1,11,4,0,0,3,14,0,0,5,6,0,0,7,10,0,0,3,8,0,0,4,12,0,0,4,11,0,0,0,15,4,0,10,8,0,0,0,2,12,13,13,1,0,0 +0,0,0,1,16,6,0,0,0,0,0,9,15,2,0,0,0,0,3,16,4,1,4,0,0,1,13,11,0,11,13,0,0,6,15,2,2,16,7,0,0,13,15,12,15,16,6,0,0,4,8,8,14,11,1,0,0,0,0,0,15,6,0,0,4 +0,0,4,13,14,3,0,0,0,6,15,6,5,11,0,0,0,11,8,0,4,12,0,0,0,8,11,4,16,5,0,0,0,1,10,16,15,2,0,0,0,1,14,7,6,15,4,0,0,1,15,2,0,6,15,0,0,0,5,12,13,15,6,0,8 +0,0,3,16,13,6,0,0,0,1,9,13,12,16,2,0,0,10,16,10,6,16,4,0,0,7,16,16,16,16,8,0,0,0,4,8,5,10,10,0,0,0,0,0,0,8,12,0,0,0,1,10,5,15,8,0,0,0,2,14,16,12,0,0,9 +0,2,12,16,16,13,1,0,0,11,15,4,3,16,7,0,0,7,11,1,13,13,0,0,0,0,0,10,16,15,3,0,0,0,0,1,4,14,11,0,0,1,4,0,0,12,12,0,0,8,14,0,3,16,5,0,0,3,13,16,16,10,0,0,3 +0,0,7,15,16,11,1,0,0,0,0,15,16,16,1,0,0,0,0,13,16,16,6,0,0,0,0,13,16,16,3,0,0,0,0,14,16,14,2,0,0,0,2,16,16,12,0,0,0,0,5,16,16,14,0,0,0,0,8,16,16,8,0,0,1 +0,0,12,16,14,6,0,0,0,2,16,16,16,6,0,0,0,4,16,16,12,2,0,0,0,3,16,16,16,1,0,0,0,2,16,16,16,0,0,0,0,4,16,16,12,0,0,0,0,3,16,16,15,4,0,0,0,2,14,16,12,8,0,0,1 +0,0,0,8,16,5,0,0,0,0,9,13,7,14,0,0,0,0,15,2,0,9,4,0,0,3,13,0,0,4,8,0,0,4,7,0,0,4,8,0,0,3,12,0,0,4,9,0,0,0,11,10,1,7,9,0,0,0,1,9,15,14,3,0,0 +0,0,14,16,12,16,16,0,0,0,8,12,8,16,11,0,0,0,0,0,7,15,3,0,0,0,2,7,14,14,3,0,0,0,12,16,14,12,6,0,0,0,1,15,5,0,0,0,0,0,9,16,0,0,0,0,0,0,16,9,0,0,0,0,7 +0,6,16,12,2,0,0,0,0,1,13,16,12,0,0,0,0,0,0,12,12,0,0,0,0,0,1,15,9,0,0,0,0,0,4,16,1,0,0,0,0,0,14,11,0,0,0,0,0,5,16,15,16,15,4,0,0,4,15,13,12,11,8,0,2 +0,0,0,5,16,4,0,0,0,0,1,13,13,1,0,0,0,0,7,16,5,2,3,0,0,3,16,15,10,15,13,0,0,10,16,16,16,16,8,0,0,1,4,5,16,15,3,0,0,0,0,4,16,11,0,0,0,0,0,7,16,3,0,0,4 +0,0,6,9,13,7,0,0,0,7,16,12,13,14,0,0,0,5,3,1,15,7,0,0,0,0,0,5,16,1,0,0,0,0,0,4,16,6,0,0,0,0,0,0,10,16,10,0,0,0,0,1,7,16,10,0,0,0,4,15,13,8,0,0,3 +0,5,14,15,9,1,0,0,0,7,14,12,16,15,1,0,0,0,1,0,9,16,4,0,0,0,0,0,7,16,4,0,0,0,0,0,12,14,0,0,0,0,1,11,16,4,0,0,0,3,14,16,13,8,2,0,0,4,12,16,12,10,4,0,2 +0,0,3,10,12,0,0,0,0,0,9,16,16,11,0,0,0,0,14,12,2,15,3,0,0,1,16,9,0,9,7,0,0,4,15,1,0,8,8,0,0,3,16,0,0,7,11,0,0,2,15,10,9,15,6,0,0,0,3,15,15,7,0,0,0 +0,0,1,14,10,0,0,0,0,0,10,13,1,0,0,0,0,2,15,4,0,0,0,0,0,5,16,2,0,0,0,0,0,8,15,2,0,0,0,0,0,4,16,16,16,14,2,0,0,0,12,16,14,16,10,0,0,0,2,11,14,13,7,0,6 +0,0,1,10,2,3,0,0,0,0,8,16,15,16,0,0,0,0,12,16,16,4,0,0,0,0,12,16,7,0,0,0,0,3,16,13,13,1,0,0,0,1,16,4,12,10,0,0,0,0,11,12,11,16,2,0,0,0,2,12,14,9,1,0,8 +0,0,9,15,4,0,0,0,0,8,16,11,7,0,0,0,0,11,11,0,2,10,0,0,0,6,16,6,13,14,1,0,0,0,7,16,16,4,0,0,0,0,5,16,16,9,0,0,0,0,10,16,16,14,0,0,0,0,6,16,9,4,0,0,8 +0,0,7,15,10,8,1,0,0,4,16,7,11,16,8,0,0,8,16,2,7,16,5,0,0,0,14,16,16,9,0,0,0,0,6,16,16,3,0,0,0,0,9,14,15,14,2,0,0,0,15,12,11,16,4,0,0,0,7,15,14,7,0,0,8 +0,0,1,11,10,0,0,0,0,0,11,16,13,3,0,0,0,2,16,14,14,16,10,0,0,2,13,16,16,16,8,0,0,0,0,2,0,16,7,0,0,0,0,0,2,16,3,0,0,0,0,4,15,12,0,0,0,0,0,13,8,0,0,0,9 +0,0,11,8,0,0,0,0,0,0,11,12,0,0,0,0,0,0,14,8,0,0,0,0,0,4,16,10,5,1,0,0,0,3,16,16,16,13,2,0,0,0,16,10,10,16,8,0,0,0,16,11,12,16,6,0,0,0,8,16,16,9,0,0,6 +0,1,8,14,13,2,0,0,0,8,15,12,16,10,0,0,0,3,3,3,16,4,0,0,0,0,0,4,14,0,0,0,0,0,0,3,16,5,0,0,0,0,0,0,8,16,5,0,0,0,2,7,9,16,6,0,0,0,7,15,11,5,0,0,3 +0,1,3,15,14,4,0,0,0,6,13,16,14,14,0,0,0,10,6,9,2,14,3,0,0,8,4,0,0,7,8,0,0,7,6,0,0,8,8,0,0,1,13,1,0,13,5,0,0,0,10,11,9,15,1,0,0,0,2,12,16,6,0,0,0 +0,0,0,7,15,0,0,0,0,0,0,14,13,0,0,0,0,0,5,16,3,0,0,0,0,1,13,13,0,6,11,0,0,8,16,9,2,15,10,0,1,14,16,16,16,16,6,0,0,4,8,9,16,14,0,0,0,0,0,7,16,10,0,0,4 +0,0,4,15,10,7,0,0,0,0,8,16,16,16,1,0,0,0,8,16,16,16,0,0,0,0,3,16,16,10,1,0,0,0,10,16,16,4,0,0,0,3,16,14,15,11,0,0,0,3,15,11,13,15,0,0,0,0,5,13,16,8,0,0,8 +0,0,4,12,12,1,0,0,0,1,15,16,16,8,0,0,0,6,16,16,16,16,1,0,0,1,4,5,7,16,4,0,0,0,0,0,1,16,8,0,0,0,0,0,2,16,7,0,0,0,10,8,13,14,1,0,0,0,7,16,9,1,0,0,9 +0,0,5,12,15,10,1,0,0,6,16,16,16,15,2,0,0,4,14,16,16,5,0,0,0,0,5,16,8,0,0,0,0,0,3,16,16,10,1,0,0,0,0,3,16,16,7,0,0,0,4,14,16,12,0,0,0,0,4,14,8,0,0,0,3 +0,0,10,10,7,0,0,0,0,0,13,16,16,12,1,0,0,3,16,14,6,15,7,0,0,6,12,2,0,8,8,0,0,4,8,0,1,14,7,0,0,4,10,0,8,15,1,0,0,2,16,14,15,4,0,0,0,0,10,16,9,0,0,0,0 +0,0,6,9,0,0,0,0,0,0,5,16,12,0,0,0,0,4,5,3,13,11,0,0,0,6,11,0,1,14,5,0,0,3,13,0,0,8,9,0,0,0,15,1,5,12,12,0,0,0,15,14,16,14,4,0,0,0,5,16,10,2,0,0,0 +0,0,3,14,12,0,0,0,0,1,16,16,16,7,0,0,0,6,16,12,16,16,2,0,0,3,16,16,12,16,4,0,0,0,0,0,0,16,6,0,0,0,0,0,2,16,7,0,0,0,1,11,15,15,2,0,0,0,4,13,9,3,0,0,9 +0,0,0,3,13,8,0,0,0,0,0,12,16,10,0,0,0,0,0,15,16,11,0,0,0,1,15,16,16,12,0,0,0,7,16,16,16,12,0,0,0,4,13,14,16,13,0,0,0,0,0,8,16,16,2,0,0,0,0,6,14,11,3,0,1 +0,0,0,9,8,0,0,0,0,0,2,15,16,8,0,0,0,0,9,16,16,8,0,0,0,0,13,16,16,12,0,0,0,1,13,16,16,12,0,0,0,0,1,16,16,15,0,0,0,0,0,16,16,16,3,0,0,0,0,9,12,12,7,0,1 +0,0,6,11,16,8,0,0,0,1,15,15,11,2,0,0,0,8,16,7,0,0,0,0,0,6,16,5,0,0,0,0,0,0,10,16,13,4,0,0,0,0,0,1,14,16,0,0,0,0,2,8,15,10,0,0,0,0,10,14,7,0,0,0,5 +0,0,3,13,16,16,2,0,0,0,10,14,13,16,7,0,0,0,0,0,6,16,4,0,0,0,2,11,15,16,12,0,0,0,9,14,16,9,1,0,0,0,0,9,15,0,0,0,0,0,2,15,6,0,0,0,0,0,7,10,0,0,0,0,7 +0,0,5,15,2,0,0,0,0,0,16,15,0,0,0,0,0,5,16,6,0,0,0,0,0,8,16,11,11,4,0,0,0,8,16,16,16,16,2,0,0,6,16,8,6,16,5,0,0,2,16,14,16,16,2,0,0,0,9,16,14,7,0,0,6 +0,0,0,11,15,6,0,0,0,0,11,16,16,11,0,0,0,2,16,16,16,12,0,0,0,0,12,16,16,9,0,0,0,0,5,16,16,15,1,0,0,0,7,16,3,15,2,0,0,0,1,16,6,16,0,0,0,0,1,11,15,7,0,0,8 +0,0,4,14,16,8,0,0,0,3,16,16,16,16,0,0,0,3,9,4,16,16,4,0,0,0,2,16,16,16,14,0,0,0,2,13,16,12,2,0,0,0,0,7,16,4,0,0,0,0,0,13,13,0,0,0,0,0,5,13,3,0,0,0,7 +0,0,0,0,13,4,0,0,0,0,0,9,12,1,0,0,0,0,4,14,4,0,0,0,0,1,14,12,4,4,1,0,0,7,16,16,16,16,7,0,0,4,7,2,9,16,3,0,0,0,0,0,12,12,0,0,0,0,0,0,14,5,0,0,4 +0,0,9,15,8,2,0,0,0,3,15,15,16,9,0,0,0,0,11,16,16,12,0,0,0,0,0,0,2,14,2,0,0,0,0,0,0,15,8,0,0,0,0,0,0,9,12,0,0,0,14,5,7,13,14,0,0,0,10,16,15,12,1,0,9 +0,0,11,3,8,7,0,0,0,1,15,11,16,16,7,0,0,0,16,4,5,13,10,0,0,3,13,0,0,8,9,0,0,5,13,0,0,8,8,0,0,6,13,0,7,16,2,0,0,2,16,12,16,10,0,0,0,0,9,13,8,0,0,0,0 +0,0,0,1,11,7,0,0,0,0,0,13,16,15,2,0,0,0,4,16,16,16,4,0,0,2,16,16,16,16,0,0,0,1,8,9,16,16,1,0,0,0,0,10,16,16,1,0,0,0,0,9,16,16,0,0,0,0,0,2,11,7,0,0,1 +0,0,0,1,13,9,0,0,0,0,0,8,16,2,0,0,0,0,7,16,4,0,0,0,0,4,16,7,3,11,4,0,0,12,16,13,16,16,5,0,0,11,12,12,16,15,0,0,0,0,0,0,15,13,0,0,0,0,0,1,16,5,0,0,4 +0,0,0,1,13,7,0,0,0,0,0,12,14,0,0,0,0,0,8,15,3,0,2,0,0,6,16,7,4,10,13,0,0,13,16,16,16,16,10,0,0,1,8,8,9,15,2,0,0,0,0,0,10,10,0,0,0,0,0,0,15,7,0,0,4 +0,0,4,15,6,0,0,0,0,3,16,16,16,5,0,0,0,7,11,4,8,14,1,0,0,7,10,0,0,14,6,0,0,7,10,0,0,11,9,0,0,2,14,0,0,9,10,0,0,0,12,10,9,15,6,0,0,0,2,13,16,7,0,0,0 +0,0,8,13,1,0,0,0,0,0,12,16,0,0,0,0,0,0,15,11,0,0,0,0,0,0,15,13,6,3,0,0,0,0,15,16,16,16,5,0,0,0,15,16,14,16,12,0,0,0,13,16,15,16,7,0,0,0,5,15,13,7,0,0,6 +0,1,10,12,16,12,3,0,0,0,12,16,10,11,3,0,0,0,7,14,0,0,0,0,0,0,7,16,7,1,0,0,0,0,1,12,15,12,0,0,0,2,11,13,3,16,2,0,0,8,15,6,15,11,0,0,0,3,13,14,9,1,0,0,5 +0,2,15,12,12,12,7,0,0,0,12,16,14,14,9,0,0,0,10,10,0,0,0,0,0,0,8,13,3,0,0,0,0,0,0,7,15,4,0,0,0,0,0,0,10,11,0,0,0,2,7,9,14,3,0,0,0,4,15,11,1,0,0,0,5 +0,0,3,13,16,15,2,0,0,0,10,12,10,16,10,3,0,0,0,0,8,16,15,4,0,0,0,8,16,15,1,0,0,0,0,2,10,10,0,0,0,0,0,4,15,1,0,0,0,0,0,11,9,0,0,0,0,0,4,15,0,0,0,0,7 +0,0,11,16,14,9,1,0,0,0,15,13,13,16,4,0,0,0,0,0,4,16,5,0,0,0,0,0,10,15,2,0,0,0,0,8,16,6,0,0,0,0,8,16,8,0,0,0,0,6,16,16,16,10,0,0,0,1,8,8,12,13,0,0,2 +0,4,16,13,0,0,0,0,0,10,16,16,2,0,0,0,0,8,7,16,4,0,0,0,0,0,5,16,2,0,0,0,0,0,10,14,0,0,0,0,0,1,15,10,0,0,0,0,0,8,16,16,16,16,6,0,0,3,13,16,16,15,5,0,2 +0,0,8,0,8,7,0,0,0,0,16,4,13,16,2,0,0,2,14,2,7,16,6,0,0,7,12,0,0,13,7,0,0,5,13,0,0,12,8,0,0,5,16,8,4,15,7,0,0,3,16,16,16,16,1,0,0,0,7,14,10,3,0,0,0 +0,4,16,14,1,0,0,0,0,9,16,16,8,0,0,0,0,5,9,12,7,0,0,0,0,0,1,16,3,0,0,0,0,0,6,15,0,0,0,0,0,1,15,10,0,0,0,0,0,6,16,14,10,6,1,0,0,4,15,16,16,16,13,0,2 +0,0,5,16,10,0,0,0,0,0,12,15,15,5,0,0,0,0,10,3,11,9,0,0,0,0,0,0,12,8,0,0,0,0,0,0,15,6,0,0,0,0,2,7,16,2,0,0,0,0,11,16,16,15,10,2,0,0,6,8,4,9,15,3,2 +0,0,5,13,3,0,0,0,0,0,12,16,11,0,0,0,0,0,5,13,16,1,0,0,0,0,0,3,16,3,0,0,0,0,0,9,16,1,0,0,0,0,1,16,9,0,0,0,0,0,7,16,13,8,4,0,0,0,5,16,16,16,16,5,2 +0,0,4,13,12,3,0,0,0,0,15,16,16,7,0,0,0,0,16,16,16,16,3,0,0,0,11,16,16,5,0,0,0,0,15,10,12,15,1,0,0,2,16,4,1,16,10,0,0,1,15,12,11,16,5,0,0,0,6,15,9,1,0,0,8 +0,0,0,6,16,2,0,0,0,0,0,12,13,0,0,0,0,0,8,15,2,0,0,0,0,3,16,8,1,11,7,0,0,14,16,16,16,16,10,0,0,5,12,12,16,16,4,0,0,0,0,3,16,9,0,0,0,0,0,7,16,5,0,0,4 +0,0,6,15,16,9,0,0,0,2,15,14,15,16,0,0,0,0,2,0,13,15,0,0,0,0,2,6,14,15,2,0,0,0,12,16,16,16,10,0,0,0,2,11,16,2,0,0,0,0,2,15,9,0,0,0,0,0,8,13,1,0,0,0,7 +0,0,8,13,16,11,1,0,0,0,16,8,8,12,2,0,0,4,15,0,0,0,0,0,0,4,12,0,0,0,0,0,0,2,15,14,9,1,0,0,0,0,2,7,12,11,1,0,0,2,10,5,7,16,2,0,0,0,10,16,14,5,0,0,5 +0,0,6,14,16,10,0,0,0,3,15,14,16,16,1,0,0,0,3,0,10,16,4,0,0,0,0,5,12,16,8,0,0,0,0,13,16,15,6,0,0,0,0,5,16,10,0,0,0,0,0,11,15,0,0,0,0,0,8,15,2,0,0,0,7 +0,0,1,11,14,10,4,0,0,0,8,16,16,16,12,0,0,1,15,14,14,16,12,0,0,7,16,16,16,16,8,0,0,0,4,3,8,16,1,0,0,0,0,0,16,12,0,0,0,0,0,6,16,9,0,0,0,0,2,16,7,0,0,0,9 +0,0,1,10,14,7,1,0,0,4,16,12,11,16,4,0,0,6,6,0,5,15,1,0,0,0,0,3,16,6,0,0,0,0,0,6,16,11,0,0,0,0,0,0,10,16,4,0,0,0,0,7,10,16,4,0,0,0,0,7,13,5,0,0,3 +0,0,11,15,8,0,0,0,0,4,16,9,15,5,0,0,0,6,15,1,15,13,0,0,0,1,15,15,16,16,4,0,0,0,1,7,5,16,6,0,0,0,0,0,0,16,5,0,0,0,15,9,11,15,3,0,0,1,13,16,14,3,0,0,9 +0,0,5,12,16,9,0,0,0,1,16,15,15,13,0,0,0,0,4,1,13,14,2,0,0,0,0,5,16,16,13,0,0,0,0,12,16,11,4,0,0,0,0,8,15,1,0,0,0,0,0,15,12,0,0,0,0,0,4,15,2,0,0,0,7 +0,0,5,14,10,0,0,0,0,2,15,14,13,0,0,0,0,6,16,3,9,14,1,0,0,8,16,16,16,16,7,0,0,0,8,5,6,16,8,0,0,0,0,0,8,15,6,0,0,0,3,12,16,7,0,0,0,0,6,16,7,0,0,0,9 +0,0,5,10,16,8,0,0,0,1,16,16,16,6,0,0,0,4,16,15,4,0,0,0,0,3,16,10,0,0,0,0,0,0,11,16,6,0,0,0,0,0,1,15,15,2,0,0,0,0,5,13,16,8,0,0,0,0,7,16,13,3,0,0,5 +0,0,5,11,14,2,0,0,0,8,16,16,14,4,0,0,0,12,15,8,1,0,0,0,0,2,15,10,0,0,0,0,0,0,2,13,13,3,0,0,0,0,0,1,9,15,4,0,0,0,2,10,15,15,3,0,0,0,3,12,8,3,0,0,5 +0,1,10,13,16,9,0,0,0,11,16,14,16,16,2,0,0,7,6,3,16,11,0,0,0,0,0,5,16,9,0,0,0,0,0,3,16,12,0,0,0,0,0,0,9,16,5,0,0,0,3,9,16,15,1,0,0,0,12,15,11,1,0,0,3 +0,0,0,8,15,10,0,0,0,0,1,16,16,16,0,0,0,0,6,16,16,15,1,0,0,3,14,16,16,12,0,0,0,8,16,16,16,12,0,0,0,5,7,13,16,13,0,0,0,0,0,8,16,16,4,0,0,0,0,6,15,15,9,0,1 +0,0,7,15,8,0,0,0,0,7,16,16,12,0,0,0,0,12,14,11,11,0,0,0,0,8,3,16,7,0,0,0,0,0,5,16,3,0,0,0,0,0,9,14,0,0,0,0,0,0,12,15,12,8,3,0,0,0,6,16,16,16,11,0,2 +0,0,7,9,15,6,0,0,0,2,16,16,16,16,3,0,0,7,15,8,1,13,8,0,0,8,10,0,0,8,8,0,0,8,9,0,0,8,8,0,0,7,14,0,3,15,5,0,0,2,16,14,16,13,1,0,0,0,8,13,8,0,0,0,0 +0,0,8,12,5,0,0,0,0,3,15,13,15,0,0,0,0,0,0,9,13,0,0,0,0,0,0,13,12,0,0,0,0,0,0,7,16,11,3,0,0,0,0,0,3,14,14,0,0,0,4,7,7,15,12,0,0,0,8,13,12,6,0,0,3 +0,0,0,1,14,4,0,0,0,0,0,9,15,2,0,0,0,0,2,16,8,0,0,0,0,0,13,14,0,3,2,0,0,7,16,7,10,16,9,0,1,14,16,16,16,16,6,0,0,7,8,4,15,14,1,0,0,0,0,0,16,10,0,0,4 +0,1,9,12,14,6,0,0,0,6,16,10,16,12,0,0,0,1,14,13,12,16,0,0,0,0,0,0,2,16,4,0,0,0,0,0,0,15,9,0,0,0,0,0,0,10,9,0,0,0,0,0,5,14,11,0,0,0,10,16,16,7,0,0,9 +0,0,0,11,9,0,0,0,0,0,3,16,3,0,0,0,0,0,10,14,0,1,2,0,0,3,16,5,0,13,9,0,0,10,16,8,10,16,8,0,3,16,16,16,16,15,4,0,0,4,3,7,16,3,0,0,0,0,0,11,11,0,0,0,4 +0,0,0,6,14,3,0,0,0,0,2,14,16,12,0,0,0,0,4,16,16,16,0,0,0,1,11,16,16,12,0,0,0,6,16,16,16,12,0,0,0,0,8,16,16,9,0,0,0,0,4,16,16,10,1,0,0,0,0,9,14,11,2,0,1 +0,0,1,11,14,13,0,0,0,1,13,16,9,5,0,0,0,2,16,9,1,8,6,0,0,0,13,16,15,16,6,0,0,0,1,13,16,13,0,0,0,0,1,16,16,16,2,0,0,0,5,16,16,14,1,0,0,0,2,14,11,1,0,0,8 +0,0,1,11,5,0,0,0,0,0,8,16,1,0,0,0,0,2,15,10,0,0,0,0,0,6,16,5,3,0,0,0,0,8,16,16,16,14,0,0,0,3,16,9,1,16,10,0,0,0,11,16,12,16,6,0,0,0,0,10,14,11,1,0,6 +0,0,3,12,16,10,0,0,0,0,16,10,5,16,1,0,0,4,16,11,10,14,0,0,0,0,14,16,16,13,1,0,0,0,2,8,8,16,7,0,0,0,0,0,1,16,3,0,0,0,0,10,16,7,0,0,0,0,2,10,3,0,0,0,9 +0,0,1,14,6,2,6,0,0,0,10,13,1,10,10,0,0,4,16,3,3,16,5,0,0,10,16,12,14,16,9,0,0,3,16,16,16,10,2,0,0,0,0,5,15,1,0,0,0,0,0,11,10,0,0,0,0,0,0,13,8,0,0,0,4 +0,0,0,14,4,1,1,0,0,0,4,16,1,12,7,0,0,1,15,8,5,16,3,0,0,9,14,0,10,11,0,0,0,11,16,14,16,14,4,0,0,6,8,14,16,14,5,0,0,0,0,11,9,0,0,0,0,0,0,13,4,0,0,0,4 +0,0,0,0,8,16,2,0,0,0,0,6,16,16,3,0,0,0,1,15,16,16,0,0,0,0,13,16,16,12,0,0,0,4,16,7,16,12,0,0,0,5,4,3,16,9,0,0,0,0,0,2,16,8,0,0,0,0,0,0,12,14,0,0,1 +0,0,0,4,14,5,9,0,0,0,4,16,6,11,13,0,0,2,14,9,2,16,4,0,0,7,16,16,16,15,0,0,0,3,16,16,16,13,2,0,0,0,2,9,16,1,0,0,0,0,0,6,16,2,0,0,0,0,0,6,16,5,0,0,4 +0,0,9,12,13,15,16,3,0,1,14,13,12,16,14,1,0,0,0,0,6,15,3,0,0,0,5,13,16,16,10,0,0,1,16,16,15,12,3,0,0,0,5,16,7,0,0,0,0,0,7,16,0,0,0,0,0,0,11,14,0,0,0,0,7 +0,0,6,13,16,14,1,0,0,5,15,4,1,12,4,0,0,1,16,10,15,10,2,0,0,0,10,13,3,0,0,0,0,0,9,15,1,0,0,0,0,0,13,11,10,0,0,0,0,0,15,3,13,6,0,0,0,0,5,12,14,9,0,0,8 +0,0,7,12,15,13,3,0,0,2,16,6,2,11,7,0,0,7,14,9,13,11,1,0,0,2,16,12,6,7,2,0,0,2,16,15,8,2,0,0,0,2,16,14,2,0,0,0,0,0,14,16,8,0,0,0,0,0,4,12,11,0,0,0,8 +0,0,0,0,10,12,1,0,0,0,0,1,15,16,3,0,0,0,2,10,16,11,0,0,0,1,11,16,16,12,0,0,0,5,8,3,16,9,0,0,0,0,0,1,16,10,0,0,0,0,0,0,15,11,0,0,0,0,0,0,7,13,1,0,1 +0,0,0,8,11,3,7,0,0,0,6,15,4,10,9,0,0,3,15,6,1,16,5,0,0,7,16,12,14,16,14,0,0,5,16,16,16,14,7,0,0,0,4,7,16,5,0,0,0,0,0,9,15,0,0,0,0,0,0,7,16,0,0,0,4 +0,0,0,0,2,15,3,0,0,0,0,0,12,16,4,0,0,0,0,6,16,16,0,0,0,0,7,16,16,14,0,0,0,6,16,11,16,12,0,0,0,1,3,0,15,13,0,0,0,0,0,0,10,16,0,0,0,0,0,0,2,14,5,0,1 +0,0,0,7,12,2,0,0,0,1,10,16,15,6,0,0,0,0,15,13,1,0,0,0,0,2,16,8,0,0,0,0,0,2,16,11,12,9,0,0,0,2,16,14,10,14,10,0,0,0,11,15,8,11,16,1,0,0,1,9,16,16,13,1,6 +0,0,4,14,15,6,0,0,0,2,16,12,15,14,0,0,0,7,9,0,0,12,4,0,0,8,6,0,0,6,7,0,0,5,8,0,0,4,8,0,0,2,14,1,0,7,7,0,0,0,12,16,16,16,2,0,0,0,2,11,15,8,0,0,0 +0,0,0,9,16,7,0,0,0,0,7,16,8,0,0,0,0,0,14,10,0,0,0,0,0,1,16,4,0,0,0,0,0,2,16,8,8,2,0,0,0,1,14,14,9,15,7,0,0,0,9,14,4,8,14,0,0,0,0,9,15,15,7,0,6 +0,0,0,0,3,13,3,0,0,0,0,0,11,16,4,0,0,0,0,11,16,16,2,0,0,0,9,16,16,16,0,0,0,3,16,5,13,16,0,0,0,3,2,0,12,15,1,0,0,0,0,0,8,16,0,0,0,0,0,0,2,14,3,0,1 +0,0,5,15,13,2,0,0,0,3,16,16,16,16,0,0,0,8,7,1,3,14,7,0,0,3,1,0,0,5,8,0,0,5,10,0,0,5,8,0,0,3,16,12,8,14,8,0,0,0,13,16,16,16,5,0,0,0,3,14,16,10,0,0,0 +0,0,3,13,0,0,0,0,0,0,11,14,0,0,0,0,0,2,16,3,0,0,0,0,0,7,14,0,0,0,0,0,0,8,13,8,12,7,1,0,0,6,16,10,10,16,6,0,0,0,13,14,12,15,8,0,0,0,3,13,16,12,3,0,6 +0,0,3,15,16,16,9,0,0,0,1,9,14,16,5,0,0,0,0,1,11,15,0,0,0,0,5,16,16,16,14,0,0,0,1,12,14,5,2,0,0,0,1,15,8,0,0,0,0,0,5,16,2,0,0,0,0,0,7,15,2,0,0,0,7 +0,0,0,0,9,10,0,0,0,0,0,5,16,15,0,0,0,0,3,14,16,12,0,0,0,3,15,16,16,10,0,0,0,4,4,8,16,8,0,0,0,0,0,3,16,9,0,0,0,0,0,3,15,10,0,0,0,0,0,0,7,14,2,0,1 +0,0,4,15,8,0,0,0,0,1,14,12,16,1,0,0,0,2,10,1,16,0,0,0,0,0,3,11,8,0,0,0,0,0,0,10,15,6,0,0,0,0,0,2,12,15,2,0,0,0,1,10,8,15,3,0,0,0,2,15,16,11,0,0,3 +0,0,0,4,13,15,0,0,0,0,6,16,9,1,0,0,0,0,15,9,0,0,0,0,0,3,15,0,0,0,0,0,0,8,12,0,0,0,0,0,0,4,15,12,12,7,1,0,0,0,9,15,9,13,11,0,0,0,0,6,12,16,10,0,6 +0,0,0,12,15,4,0,0,0,0,7,9,8,15,0,0,0,2,12,0,0,9,4,0,0,5,11,0,0,4,8,0,0,8,4,0,0,8,6,0,0,2,12,0,1,14,5,0,0,0,13,15,16,12,1,0,0,0,2,10,12,1,0,0,0 +0,0,0,9,14,13,4,0,0,0,6,14,6,10,12,0,0,0,9,12,6,15,7,0,0,1,11,16,15,5,0,0,0,7,16,16,10,0,0,0,0,7,16,9,16,9,0,0,0,1,11,8,7,16,0,0,0,0,2,11,12,13,0,0,8 +0,0,8,15,16,16,12,0,0,0,4,12,13,16,8,0,0,0,0,0,10,14,0,0,0,1,9,12,16,13,4,0,0,5,16,16,16,14,5,0,0,0,2,16,5,0,0,0,0,0,10,14,0,0,0,0,0,0,11,11,0,0,0,0,7 +0,0,2,11,16,9,0,0,0,0,9,14,15,14,0,0,0,0,0,0,13,10,0,0,0,0,4,9,16,12,5,0,0,9,16,16,16,16,11,0,0,3,8,12,11,0,0,0,0,0,0,16,5,0,0,0,0,0,2,16,2,0,0,0,7 +0,0,1,10,16,16,7,0,0,0,13,14,10,7,3,0,0,6,16,2,0,0,0,0,0,10,16,16,16,8,1,0,0,1,6,11,13,16,9,0,0,0,0,0,4,16,8,0,0,0,0,1,9,16,5,0,0,0,2,15,16,8,0,0,5 +0,0,0,6,15,16,6,0,0,0,3,16,5,9,12,0,0,0,4,15,7,13,4,0,0,0,14,16,16,10,0,0,0,5,16,13,11,0,0,0,0,5,15,1,16,3,0,0,0,0,10,13,15,6,0,0,0,0,0,5,13,5,0,0,8 +0,0,1,11,15,2,0,0,0,6,6,16,16,9,0,0,0,8,14,13,3,13,4,0,0,7,16,1,0,6,6,0,0,3,12,0,0,6,9,0,0,0,14,5,2,13,12,0,0,0,3,16,16,16,8,0,0,0,0,9,16,13,2,0,0 +0,0,4,12,13,11,0,0,0,3,15,3,7,16,1,0,0,4,12,9,15,4,0,0,0,0,11,15,1,0,0,0,0,1,13,16,12,0,0,0,0,3,14,1,14,3,0,0,0,0,12,3,4,12,0,0,0,0,3,12,12,13,0,0,8 +0,0,10,15,2,0,0,0,0,5,16,16,8,0,0,0,0,9,13,8,12,0,0,0,0,12,7,4,14,0,0,0,0,3,1,7,12,0,0,0,0,0,0,15,13,1,0,0,0,0,9,16,16,16,6,0,0,0,10,16,16,16,10,0,2 +0,0,2,11,16,16,11,0,0,0,13,11,4,13,13,0,0,0,12,14,15,14,2,0,0,0,12,16,15,5,0,0,0,0,8,16,11,0,0,0,0,2,16,9,16,4,0,0,0,4,16,6,14,10,0,0,0,0,3,12,15,6,0,0,8 +0,0,0,8,13,1,0,0,0,0,6,16,11,1,0,0,0,0,12,12,0,0,0,0,0,2,16,6,0,0,0,0,0,0,15,10,9,6,2,0,0,2,15,12,10,16,11,0,0,0,8,15,10,14,14,0,0,0,0,9,13,12,7,0,6 +0,2,15,14,3,0,0,0,0,7,15,14,13,0,0,0,0,9,11,2,16,4,0,0,0,7,4,0,14,6,0,0,0,0,0,0,14,7,0,0,0,0,0,3,16,3,0,0,0,0,4,15,16,8,4,0,0,1,16,16,16,16,16,5,2 +0,0,0,8,13,10,5,0,0,0,8,16,11,14,15,0,0,0,9,16,16,14,11,0,0,0,3,10,16,16,7,0,0,0,0,0,2,16,4,0,0,0,0,0,4,16,2,0,0,0,0,1,11,11,0,0,0,0,0,14,15,2,0,0,9 +0,0,3,12,13,9,0,0,0,0,12,8,2,12,2,0,0,0,11,8,12,11,0,0,0,0,7,16,5,0,0,0,0,3,12,6,11,0,0,0,0,4,8,0,7,9,0,0,0,1,12,2,0,13,4,0,0,0,2,12,11,12,6,0,8 +0,0,9,15,16,15,8,0,0,5,16,16,13,12,13,1,0,6,16,5,1,0,0,0,0,11,16,16,10,0,0,0,0,8,15,11,16,0,0,0,0,0,0,10,12,0,0,0,0,0,2,15,7,0,0,0,0,0,9,13,0,0,0,0,5 +0,0,7,11,15,13,0,0,0,1,14,12,16,9,0,0,0,0,0,3,16,6,0,0,0,0,2,11,16,8,1,0,0,1,15,16,16,16,10,0,0,0,10,16,9,4,1,0,0,0,8,13,0,0,0,0,0,0,11,9,0,0,0,0,7 +0,0,0,4,13,10,0,0,0,0,0,12,10,14,4,0,0,0,0,14,4,15,4,0,0,1,9,15,14,8,0,0,0,5,14,10,8,0,0,0,0,4,8,2,12,0,0,0,0,0,9,8,12,4,0,0,0,0,1,7,13,2,0,0,8 +0,0,2,14,1,0,0,0,0,0,9,11,0,0,0,0,0,0,16,4,0,0,0,0,0,4,16,0,0,0,0,0,0,3,13,2,8,8,3,0,0,0,16,14,8,12,12,0,0,1,10,12,4,10,13,0,0,0,2,11,14,14,4,0,6 +0,0,0,2,10,16,2,0,0,0,0,10,13,16,6,0,0,0,0,0,5,16,7,0,0,1,7,8,10,16,3,0,0,5,16,13,16,13,3,0,0,0,0,2,16,9,0,0,0,0,0,4,16,5,0,0,0,0,0,0,11,8,0,0,7 +0,0,6,12,16,16,11,0,0,0,5,15,13,4,0,0,0,0,1,14,0,0,0,0,0,0,7,16,14,4,0,0,0,0,1,6,12,15,0,0,0,0,0,0,0,16,2,0,0,0,3,0,3,13,0,0,0,0,10,16,14,4,0,0,5 +0,1,13,16,1,0,0,0,0,7,16,16,3,0,0,0,0,12,9,16,4,0,0,0,0,5,3,15,4,0,0,0,0,0,6,15,1,0,0,0,0,0,14,10,0,0,0,0,0,4,16,13,11,9,3,0,0,1,15,16,16,15,5,0,2 +0,0,0,10,10,1,0,0,0,0,5,16,5,11,7,0,0,2,16,7,3,16,4,0,0,7,16,2,9,15,2,0,0,7,16,12,16,15,7,0,0,1,10,14,16,10,1,0,0,0,0,11,15,1,0,0,0,0,0,12,13,0,0,0,4 +0,0,0,3,10,13,12,4,0,0,3,16,6,5,14,9,0,0,12,16,13,12,14,4,0,0,11,16,16,16,12,0,0,0,0,0,2,15,2,0,0,0,0,0,10,6,0,0,0,0,0,1,16,4,0,0,0,0,0,1,15,1,0,0,9 +0,0,7,15,16,11,0,0,0,0,9,11,13,12,0,0,0,0,0,0,12,8,0,0,0,0,5,8,16,7,0,0,0,10,16,16,16,16,7,0,0,5,6,16,11,4,1,0,0,0,4,16,2,0,0,0,0,0,8,14,1,0,0,0,7 +0,0,7,14,0,0,0,0,0,0,9,16,2,0,0,0,0,1,14,16,4,0,0,0,0,8,16,16,9,0,0,0,0,12,13,9,16,0,0,0,0,1,2,3,16,5,0,0,0,0,4,10,16,13,7,1,0,0,7,16,16,16,16,3,1 +0,2,14,4,0,0,0,0,0,6,16,10,0,0,0,0,0,8,8,14,0,0,0,0,0,2,3,15,0,0,0,0,0,0,4,12,0,0,0,0,0,1,13,8,0,0,0,0,0,6,16,16,16,12,4,0,0,3,15,16,14,7,2,0,2 +0,0,4,13,16,16,5,0,0,0,13,16,12,7,0,0,0,0,8,15,0,0,0,0,0,0,10,16,16,15,2,0,0,0,6,13,15,16,7,0,0,0,0,0,1,15,6,0,0,0,0,7,9,16,2,0,0,0,5,16,15,7,0,0,5 +0,0,1,12,2,0,0,0,0,0,10,8,0,9,9,0,0,1,16,1,0,15,5,0,0,3,16,5,7,16,2,0,0,2,16,16,16,16,7,0,0,0,6,10,16,1,0,0,0,0,0,9,7,0,0,0,0,0,0,14,1,0,0,0,4 +0,1,9,14,16,16,13,0,0,8,16,12,7,4,2,0,0,10,15,4,2,0,0,0,0,8,16,16,15,3,0,0,0,0,3,6,15,13,0,0,0,0,0,0,5,16,2,0,0,0,2,4,8,16,4,0,0,0,13,16,16,10,0,0,5 +0,0,12,9,0,0,0,0,0,4,15,15,5,0,0,0,0,7,10,3,11,0,0,0,0,10,6,0,12,2,0,0,0,5,3,0,12,5,0,0,0,0,1,3,16,4,0,0,0,0,14,16,16,16,14,0,0,0,12,13,10,8,4,0,2 +0,0,1,10,7,0,0,0,0,0,9,15,2,0,0,0,0,0,13,7,0,0,0,0,0,0,16,1,0,0,0,0,0,5,16,16,16,10,0,0,0,2,14,8,5,13,7,0,0,0,4,15,9,12,10,0,0,0,1,9,15,13,3,0,6 +0,1,13,16,7,0,0,0,0,8,15,15,9,0,0,0,0,12,8,8,12,0,0,0,0,10,7,8,12,0,0,0,0,1,0,11,10,0,0,0,0,0,3,16,5,0,0,0,0,0,13,15,6,6,1,0,0,1,16,16,16,16,8,0,2 +0,0,0,4,10,13,12,0,0,0,9,16,13,10,16,0,0,2,15,16,16,16,10,0,0,0,0,0,0,14,6,0,0,0,0,0,5,15,1,0,0,0,0,0,11,9,0,0,0,0,0,0,15,3,0,0,0,0,0,2,11,0,0,0,9 +0,0,6,15,10,0,0,0,0,0,15,16,9,0,0,0,0,5,16,16,3,0,0,0,0,8,16,16,16,9,0,0,0,0,4,8,13,16,4,0,0,0,0,2,4,16,6,0,0,0,10,15,14,14,1,0,0,0,8,16,14,2,0,0,3 +0,0,2,16,16,11,0,0,0,0,13,15,15,16,5,0,0,4,14,3,3,14,9,0,0,8,15,0,0,6,8,0,0,4,12,0,0,6,8,0,0,1,16,11,10,16,7,0,0,0,14,16,16,11,1,0,0,0,2,12,11,2,0,0,0 +0,0,6,16,15,3,0,0,0,0,16,13,8,1,0,0,0,0,12,7,4,0,0,0,0,0,14,16,16,13,2,0,0,0,3,4,4,13,8,0,0,0,0,0,0,9,4,0,0,0,2,8,13,15,3,0,0,0,4,14,12,5,0,0,5 +0,0,0,1,12,14,0,0,0,0,0,5,16,12,0,0,0,0,1,14,16,12,0,0,0,3,15,16,16,8,0,0,0,9,15,7,16,8,0,0,0,1,2,6,16,5,0,0,0,0,0,2,16,10,0,0,0,0,0,0,11,16,4,0,1 +0,0,4,13,16,16,9,0,0,1,15,14,13,16,7,0,0,0,6,0,10,15,2,0,0,0,0,5,16,7,0,0,0,0,0,4,15,15,1,0,0,0,0,1,8,16,5,0,0,0,4,15,13,16,2,0,0,0,3,15,15,5,0,0,3 +0,0,3,12,14,16,2,0,0,0,8,12,15,16,1,0,0,0,0,1,16,8,0,0,0,2,8,13,16,8,3,0,0,9,16,16,16,16,10,0,0,1,9,16,5,4,1,0,0,0,8,16,1,0,0,0,0,0,4,16,3,0,0,0,7 +0,0,13,13,2,0,0,0,0,2,16,15,11,0,0,0,0,2,16,9,16,1,0,0,0,2,16,8,16,0,0,0,0,0,8,9,15,0,0,0,0,0,2,15,13,4,0,0,0,0,12,16,16,16,11,3,0,0,13,11,0,6,9,3,2 +0,0,0,9,10,0,0,0,0,0,6,16,7,0,0,0,0,0,15,7,0,0,0,0,0,3,16,2,0,0,0,0,0,5,16,16,16,7,0,0,0,2,16,12,10,16,4,0,0,0,8,15,9,14,7,0,0,0,0,7,14,16,3,0,6 +0,0,5,9,13,13,0,0,0,0,11,16,9,4,0,0,0,0,7,12,0,0,0,0,0,0,10,15,12,12,2,0,0,0,3,12,8,14,7,0,0,0,0,0,0,13,4,0,0,0,1,9,14,12,1,0,0,0,3,16,10,1,0,0,5 +0,0,9,16,16,16,12,0,0,0,8,12,10,14,10,0,0,0,2,5,4,15,1,0,0,0,9,16,16,16,13,0,0,0,2,14,15,7,1,0,0,0,0,14,7,0,0,0,0,0,6,14,0,0,0,0,0,0,10,9,0,0,0,0,7 +0,0,16,13,16,16,10,0,0,0,11,7,4,2,2,0,0,0,11,1,0,0,0,0,0,5,15,6,1,0,0,0,0,2,8,10,15,4,0,0,0,0,0,0,4,11,0,0,0,2,9,0,8,8,0,0,0,0,11,16,13,1,0,0,5 +0,0,3,14,10,1,0,0,0,0,12,9,9,12,0,0,0,2,16,5,0,8,6,0,0,4,8,1,0,3,7,0,0,5,7,0,0,4,8,0,0,2,12,0,0,7,5,0,0,0,12,7,5,13,2,0,0,0,3,14,15,6,0,0,0 +0,0,8,16,15,4,0,0,0,2,16,5,7,8,0,0,0,0,6,0,5,8,0,0,0,0,0,0,12,5,0,0,0,0,0,9,14,0,0,0,0,1,11,15,4,0,0,0,0,3,16,13,4,0,0,0,0,0,8,13,16,15,5,0,2 +0,0,6,10,16,12,0,0,0,1,16,13,11,12,0,0,0,1,4,0,10,8,1,0,0,0,5,8,15,16,13,0,0,1,16,16,14,8,1,0,0,0,4,16,4,0,0,0,0,0,5,13,1,0,0,0,0,0,9,10,0,0,0,0,7 +0,1,11,13,12,4,0,0,0,1,8,8,12,11,0,0,0,0,0,1,11,10,0,0,0,0,7,12,13,1,0,0,0,7,16,16,8,0,0,0,0,0,4,9,14,12,2,0,0,1,10,7,5,16,7,0,0,2,15,16,15,9,1,0,3 +0,0,0,0,11,16,3,0,0,0,1,11,16,16,8,0,0,3,13,16,16,16,5,0,0,10,16,11,9,16,6,0,0,1,4,0,11,16,4,0,0,0,0,0,12,16,2,0,0,0,0,0,13,15,1,0,0,0,0,0,8,16,5,0,1 +0,0,1,11,12,4,0,0,0,1,13,11,6,15,0,0,0,7,13,0,3,15,0,0,0,1,12,13,15,6,0,0,0,0,4,15,13,11,0,0,0,2,15,4,1,14,6,0,0,3,14,3,0,12,7,0,0,0,4,13,16,15,1,0,8 +0,0,4,14,8,0,0,0,0,0,15,13,15,8,0,0,0,3,14,0,1,14,5,0,0,4,12,0,0,9,8,0,0,4,12,0,0,8,8,0,0,4,13,0,0,14,6,0,0,0,15,10,10,13,1,0,0,0,5,15,12,3,0,0,0 +0,0,10,13,16,16,12,0,0,0,6,12,8,13,11,0,0,0,0,0,2,16,5,0,0,0,6,8,14,16,13,0,0,0,15,16,15,9,1,0,0,0,0,13,6,0,0,0,0,0,6,14,1,0,0,0,0,0,14,9,0,0,0,0,7 +0,6,16,16,13,1,0,0,0,13,11,8,15,9,0,0,0,5,1,0,10,14,0,0,0,0,0,0,12,11,0,0,0,0,0,3,16,2,0,0,0,0,0,13,13,0,0,0,0,3,14,16,12,8,7,0,0,5,16,16,16,16,10,0,2 +0,0,0,9,14,4,0,0,0,0,10,14,4,1,0,0,0,0,14,4,0,0,0,0,0,6,16,16,10,3,0,0,0,3,16,2,5,14,4,0,0,0,14,2,0,12,10,0,0,0,7,12,0,13,9,0,0,0,0,8,16,14,2,0,6 +0,0,0,1,12,14,0,0,0,0,0,6,16,4,0,0,0,0,3,16,4,9,3,0,0,2,13,15,6,16,6,0,0,11,16,16,16,16,9,0,0,3,4,4,10,16,1,0,0,0,0,0,11,13,0,0,0,0,0,0,12,10,0,0,4 +0,0,7,16,16,16,3,0,0,0,8,15,12,16,3,0,0,0,13,12,0,0,0,0,0,1,13,16,14,5,0,0,0,0,1,4,13,13,0,0,0,1,5,0,4,16,3,0,0,4,15,8,11,15,0,0,0,0,7,16,16,8,0,0,5 +0,0,0,4,13,5,0,0,0,0,2,14,12,5,0,0,0,0,7,12,1,0,0,0,0,0,11,7,0,0,0,0,0,0,12,14,12,8,0,0,0,1,14,14,8,12,8,0,0,0,2,14,5,9,14,0,0,0,0,3,15,15,6,0,6 +0,3,13,16,16,12,1,0,0,1,8,4,5,15,6,0,0,0,0,1,9,15,2,0,0,0,6,16,15,5,0,0,0,0,5,14,16,8,0,0,0,0,0,0,8,16,2,0,0,6,12,6,12,15,1,0,0,4,13,12,11,2,0,0,3 +0,0,6,14,16,9,0,0,0,5,15,5,8,16,1,0,0,4,14,1,3,16,6,0,0,0,7,16,15,14,8,0,0,0,0,0,0,8,8,0,0,0,0,0,0,8,9,0,0,0,12,3,0,11,9,0,0,0,8,14,16,13,1,0,9 +0,0,0,4,16,11,0,0,0,0,0,13,16,10,0,0,0,0,12,16,16,7,0,0,0,5,14,13,16,5,0,0,0,0,0,8,16,4,0,0,0,0,0,4,16,7,0,0,0,0,0,4,16,8,0,0,0,0,0,3,14,8,0,0,1 +0,0,1,10,16,9,0,0,0,0,10,13,5,3,0,0,0,1,16,3,0,0,0,0,0,4,13,0,0,0,0,0,0,3,15,15,13,5,0,0,0,1,16,11,4,15,4,0,0,0,8,14,5,14,2,0,0,0,0,8,16,12,1,0,6 +0,0,4,16,16,16,16,12,0,0,2,8,8,11,16,4,0,0,0,0,1,15,8,0,0,0,2,4,10,16,11,0,0,11,16,16,16,6,0,0,0,9,9,13,11,0,0,0,0,0,2,15,2,0,0,0,0,0,8,12,0,0,0,0,7 +0,1,13,16,16,3,0,0,0,0,2,2,10,14,0,0,0,0,0,0,5,14,0,0,0,0,0,11,16,3,0,0,0,0,0,11,15,8,1,0,0,0,0,0,9,16,4,0,0,0,3,4,7,15,1,0,0,0,15,16,12,5,0,0,3 +0,0,10,15,13,8,0,0,0,1,12,4,11,10,0,0,0,0,0,8,13,3,0,0,0,3,13,16,5,0,0,0,0,1,8,13,16,13,2,0,0,0,0,0,1,13,6,0,0,5,10,8,8,16,2,0,0,0,10,16,16,6,0,0,3 +0,0,2,13,16,7,0,0,0,0,7,15,12,16,2,0,0,0,4,16,11,16,8,0,0,0,0,8,16,13,10,0,0,5,4,0,0,7,13,0,0,4,13,0,0,9,11,0,0,1,12,10,4,13,10,0,0,0,2,12,16,16,4,0,9 +0,0,0,5,16,1,0,0,0,0,1,13,8,2,7,0,0,0,9,12,0,10,10,0,0,7,16,4,4,16,11,0,0,11,16,16,16,16,7,0,0,2,8,7,14,8,0,0,0,0,0,2,16,2,0,0,0,0,0,6,12,0,0,0,4 +0,0,6,10,10,15,3,0,0,4,13,6,9,8,8,0,0,3,11,0,7,13,1,0,0,0,14,14,9,0,0,0,0,1,14,13,3,0,0,0,0,3,8,1,14,1,0,0,0,0,13,0,6,9,0,0,0,0,5,15,15,5,0,0,8 +0,0,0,6,15,10,1,0,0,0,0,12,16,14,2,0,0,1,12,16,16,7,0,0,0,7,16,16,16,7,0,0,0,1,4,10,16,4,0,0,0,0,0,5,16,7,0,0,0,0,0,8,16,11,0,0,0,0,0,6,16,12,0,0,1 +0,2,13,16,16,16,2,0,0,1,10,8,10,16,0,0,0,0,7,8,12,15,7,0,0,3,16,16,16,12,5,0,0,0,2,11,14,0,0,0,0,0,3,15,3,0,0,0,0,0,10,12,0,0,0,0,0,0,15,4,0,0,0,0,7 +0,0,0,6,15,2,2,0,0,0,3,15,10,4,15,0,0,2,14,9,0,12,11,0,0,9,16,4,3,16,14,0,0,11,16,16,16,15,7,0,0,1,10,11,16,8,0,0,0,0,0,3,16,1,0,0,0,0,0,8,11,0,0,0,4 +0,2,15,16,10,0,0,0,0,8,16,10,16,2,0,0,0,4,7,0,16,6,0,0,0,0,0,2,16,4,0,0,0,0,0,8,14,0,0,0,0,0,2,16,7,0,0,0,0,2,15,16,9,8,8,0,0,3,16,16,16,16,12,0,2 +0,4,15,16,12,0,0,0,0,12,15,7,16,5,0,0,0,3,3,0,15,4,0,0,0,0,0,0,13,7,0,0,0,0,0,5,15,2,0,0,0,0,1,14,11,0,0,0,0,1,13,16,9,8,3,0,0,3,16,16,16,16,16,1,2 +0,0,7,15,13,3,0,0,0,0,11,6,8,14,0,0,0,0,0,3,8,12,0,0,0,0,0,16,14,2,0,0,0,0,0,10,15,7,0,0,0,0,0,0,2,11,10,0,0,1,15,3,1,11,10,0,0,0,4,15,16,13,2,0,3 +0,0,10,16,12,0,0,0,0,1,14,13,16,4,0,0,0,0,2,3,16,5,0,0,0,0,0,4,16,2,0,0,0,0,0,11,14,0,0,0,0,0,2,16,8,0,3,0,0,1,13,16,14,16,16,3,0,0,12,16,16,13,7,0,2 +0,0,5,16,16,15,1,0,0,0,10,9,10,16,2,0,0,0,0,1,12,10,0,0,0,0,0,9,16,8,0,0,0,0,0,2,14,16,5,0,0,0,0,4,0,16,5,0,0,0,3,14,8,13,0,0,0,0,4,16,13,3,0,0,3 +0,0,11,16,15,2,0,0,0,0,12,10,14,8,0,0,0,0,0,0,11,8,0,0,0,0,0,1,15,3,0,0,0,0,1,11,11,0,0,0,0,6,15,15,2,0,0,0,0,9,16,14,9,3,0,0,0,1,9,12,15,16,13,0,2 +0,0,1,9,15,12,0,0,0,0,13,8,5,14,4,0,0,0,14,1,5,14,1,0,0,0,7,13,16,4,0,0,0,0,11,12,14,12,1,0,0,4,13,0,0,11,7,0,0,1,13,7,2,8,8,0,0,0,1,9,16,13,2,0,8 +0,3,15,16,11,0,0,0,0,10,15,13,16,2,0,0,0,10,7,4,16,0,0,0,0,1,0,9,12,0,0,0,0,0,1,16,6,0,0,0,0,0,10,15,0,0,0,0,0,7,16,14,12,14,11,0,0,6,16,16,16,11,3,0,2 +0,0,9,13,15,10,1,0,0,0,7,4,4,12,13,0,0,0,0,0,0,7,11,0,0,0,2,12,13,12,2,0,0,0,0,10,15,1,0,0,0,0,0,1,11,9,0,0,0,0,12,3,3,15,0,0,0,0,8,16,16,3,0,0,3 +0,2,16,10,9,8,0,0,0,0,10,16,16,12,0,0,0,1,5,10,16,10,6,0,0,11,16,16,16,15,7,0,0,3,5,14,9,2,0,0,0,0,6,16,0,0,0,0,0,0,10,10,0,0,0,0,0,3,16,1,0,0,0,0,7 +0,0,3,14,16,12,1,0,0,3,16,14,3,11,4,0,0,2,13,1,3,15,4,0,0,0,10,16,16,8,0,0,0,2,15,10,14,4,0,0,0,5,14,0,3,14,1,0,0,0,14,4,1,14,2,0,0,0,2,13,16,10,0,0,8 +0,0,0,9,14,0,0,0,0,0,5,16,4,4,9,0,0,4,16,3,0,13,9,0,0,6,16,15,12,16,13,0,0,0,8,11,15,14,3,0,0,0,0,0,16,5,0,0,0,0,0,6,13,0,0,0,0,0,0,9,10,0,0,0,4 +0,0,0,0,8,13,1,0,0,0,1,9,15,14,1,0,0,6,14,16,16,10,0,0,0,5,11,4,16,9,0,0,0,0,0,3,16,5,0,0,0,0,0,3,16,7,0,0,0,0,0,0,16,10,0,0,0,0,0,0,11,13,0,0,1 +0,0,1,13,14,2,0,0,0,0,7,16,10,12,0,0,0,3,15,10,0,12,3,0,0,7,9,0,0,8,7,0,0,5,9,0,0,8,7,0,0,1,13,0,0,11,2,0,0,0,13,7,5,10,0,0,0,0,3,14,15,3,0,0,0 +0,0,0,0,15,15,1,0,0,0,0,2,16,16,4,0,0,0,2,11,16,15,1,0,0,9,16,16,16,12,0,0,0,3,10,8,16,8,0,0,0,0,0,0,16,11,0,0,0,0,0,0,15,13,0,0,0,0,0,0,15,15,0,0,1 +0,0,2,14,15,8,0,0,0,0,8,10,3,15,1,0,0,0,5,11,1,15,4,0,0,0,0,12,15,16,5,0,0,0,0,0,2,8,6,0,0,1,3,0,0,7,5,0,0,1,12,4,0,10,7,0,0,0,4,14,14,13,1,0,9 +0,0,9,15,14,1,0,0,0,2,16,8,15,10,0,0,0,2,14,4,13,14,1,0,0,0,7,15,15,14,6,0,0,0,0,1,1,7,9,0,0,0,0,0,0,3,12,0,0,0,6,0,0,6,12,0,0,0,9,16,16,16,6,0,9 +0,0,7,10,13,5,0,0,0,6,16,12,16,12,0,0,0,6,15,6,16,14,1,0,0,1,13,16,12,16,2,0,0,0,0,0,0,13,4,0,0,0,0,0,0,11,7,0,0,0,11,6,4,13,8,0,0,0,9,16,16,12,3,0,9 +0,0,8,16,16,16,16,2,0,0,8,10,7,12,13,0,0,0,0,0,3,15,2,0,0,0,4,11,15,16,13,0,0,0,11,16,14,6,0,0,0,0,1,14,6,0,0,0,0,0,7,13,1,0,0,0,0,0,12,8,0,0,0,0,7 +0,0,10,12,13,11,2,0,0,0,14,9,8,8,2,0,0,0,15,0,0,0,0,0,0,6,16,6,0,0,0,0,0,3,12,13,15,5,0,0,0,0,0,0,6,13,1,0,0,0,9,5,4,14,0,0,0,0,8,15,15,7,0,0,5 +0,0,2,13,13,4,0,0,0,0,12,13,11,14,0,0,0,0,9,13,13,14,1,0,0,0,0,4,8,13,3,0,0,0,0,0,0,7,9,0,0,0,0,0,0,3,13,0,0,0,14,6,0,6,12,0,0,0,2,12,16,16,7,0,9 +0,0,0,5,12,15,7,0,0,0,3,14,4,13,6,0,0,0,4,13,0,14,7,0,0,0,0,13,14,16,8,0,0,0,0,0,3,8,8,0,0,1,3,0,0,10,8,0,0,5,16,7,0,12,4,0,0,0,2,6,13,15,1,0,9 +0,0,6,12,15,15,9,0,0,0,8,12,4,4,3,0,0,0,12,3,0,0,0,0,0,1,15,6,3,0,0,0,0,8,16,12,16,6,0,0,0,3,4,0,4,11,0,0,0,4,15,5,13,6,0,0,0,0,6,16,10,0,0,0,5 +0,0,3,10,13,5,0,0,0,0,15,12,5,14,1,0,0,4,12,1,0,10,4,0,0,5,8,0,0,8,7,0,0,5,8,0,0,8,8,0,0,4,11,0,0,11,5,0,0,1,14,6,7,12,0,0,0,0,4,15,14,4,0,0,0 +0,0,0,2,15,7,0,0,0,0,0,11,12,1,0,0,0,0,6,15,1,14,2,0,0,5,15,5,3,16,1,0,0,7,16,14,13,16,8,0,0,1,6,12,15,14,3,0,0,0,0,1,13,7,0,0,0,0,0,3,16,1,0,0,4 +0,0,5,13,12,15,2,0,0,0,13,13,8,8,2,0,0,3,16,5,0,0,0,0,0,7,16,13,5,0,0,0,0,1,7,10,16,10,0,0,0,0,0,0,4,16,4,0,0,0,8,6,5,16,4,0,0,0,7,13,13,10,0,0,5 +0,0,0,2,14,5,1,0,0,0,1,12,13,1,15,4,0,0,9,15,2,6,16,2,0,7,16,13,8,14,12,0,0,9,16,16,16,16,10,0,0,0,0,3,11,15,1,0,0,0,0,0,14,10,0,0,0,0,0,2,16,4,0,0,4 +0,0,7,13,12,9,1,0,0,3,15,5,5,16,4,0,0,3,15,2,7,16,1,0,0,0,5,15,16,15,4,0,0,0,0,0,0,11,7,0,0,1,4,0,0,9,7,0,0,3,13,1,0,10,8,0,0,0,9,15,16,16,1,0,9 +0,0,2,14,12,2,0,0,0,0,12,13,7,13,0,0,0,5,16,1,0,12,3,0,0,6,12,2,0,4,8,0,0,7,8,0,0,7,8,0,0,1,14,1,0,9,6,0,0,0,9,11,7,16,1,0,0,0,2,12,16,8,0,0,0 +0,0,2,8,15,7,0,0,0,0,11,15,6,14,3,0,0,0,16,6,0,4,9,0,0,8,4,0,0,4,8,0,0,6,7,0,0,5,8,0,0,3,13,0,0,13,1,0,0,0,10,11,8,11,0,0,0,0,1,13,11,2,0,0,0 +0,3,16,16,10,0,0,0,0,2,12,11,16,3,0,0,0,0,0,2,16,5,0,0,0,0,0,3,16,2,0,0,0,0,1,15,10,0,0,0,0,0,13,16,1,0,0,0,0,7,16,13,10,12,13,0,0,2,15,16,16,12,6,0,2 +0,0,14,16,14,5,0,0,0,0,3,0,2,16,3,0,0,0,0,4,10,15,1,0,0,6,16,16,14,2,0,0,0,1,4,6,12,12,0,0,0,0,0,0,0,12,9,0,0,0,10,0,4,14,5,0,0,0,13,16,13,4,0,0,3 +0,0,0,13,16,16,16,1,0,0,0,7,8,13,11,0,0,0,0,0,0,14,10,1,0,6,12,13,16,14,9,0,0,6,14,10,16,6,0,0,0,0,0,7,13,1,0,0,0,0,0,13,10,0,0,0,0,0,0,15,3,0,0,0,7 +0,0,6,12,13,12,6,0,0,0,6,8,2,5,7,0,0,0,12,2,0,0,0,0,0,7,15,12,7,2,0,0,0,2,8,8,9,15,4,0,0,0,0,0,0,7,7,0,0,0,4,5,4,13,2,0,0,0,4,15,16,6,0,0,5 +0,0,1,8,15,8,0,0,0,0,14,11,14,16,0,0,0,3,12,0,6,9,0,0,0,1,15,10,14,1,0,0,0,0,1,15,16,5,0,0,0,0,5,12,3,15,3,0,0,0,6,11,1,13,4,0,0,0,0,8,16,13,1,0,8 +0,0,5,8,12,12,13,0,0,0,14,12,8,4,4,0,0,0,14,0,2,0,0,0,0,3,16,16,16,10,0,0,0,0,5,1,2,15,2,0,0,0,0,0,0,16,0,0,0,0,4,10,8,9,0,0,0,0,3,16,13,1,0,0,5 +0,0,4,15,10,0,0,0,0,3,16,9,10,7,0,0,0,7,15,3,1,11,2,0,0,8,6,0,0,6,7,0,0,8,5,0,0,4,8,0,0,4,10,0,0,7,8,0,0,0,13,6,5,15,3,0,0,0,4,15,14,6,0,0,0 +0,0,0,3,15,4,0,0,0,0,0,13,9,0,0,0,0,0,8,12,0,7,4,0,0,4,14,3,0,15,5,0,0,11,16,16,16,16,10,0,0,5,9,9,13,12,0,0,0,0,0,0,12,7,0,0,0,0,0,2,16,3,0,0,4 +0,0,0,2,11,1,0,0,0,0,0,7,13,1,0,0,0,0,6,15,2,4,7,0,0,1,14,11,0,14,8,0,0,7,16,16,16,16,3,0,0,0,0,3,13,14,0,0,0,0,0,0,15,9,0,0,0,0,0,3,15,5,0,0,4 +0,0,4,14,5,0,0,0,0,0,12,15,14,0,0,0,0,0,12,8,15,9,0,0,0,0,5,16,15,16,4,0,0,0,0,0,6,12,10,0,0,0,0,0,0,5,15,0,0,0,3,8,4,9,16,4,0,0,5,12,15,12,11,0,9 +0,0,0,3,12,0,0,0,0,0,0,12,9,1,1,0,0,0,3,16,3,10,7,0,0,1,13,10,1,16,3,0,0,8,16,13,13,16,2,0,0,3,6,8,16,14,2,0,0,0,0,2,16,2,0,0,0,0,0,3,13,0,0,0,4 +0,0,2,13,14,2,0,0,0,1,15,16,16,9,0,0,0,7,16,8,5,16,1,0,0,6,16,3,0,11,7,0,0,5,16,4,0,8,8,0,0,0,16,9,0,10,11,0,0,0,10,15,10,16,8,0,0,0,2,13,16,14,2,0,0 +0,0,3,15,8,0,0,0,0,0,8,16,16,7,0,0,0,0,7,11,9,12,0,0,0,0,2,5,9,11,0,0,0,0,0,0,13,7,0,0,0,0,7,10,16,4,0,0,0,0,14,16,16,16,15,0,0,0,1,4,4,7,11,1,2 +0,0,0,6,11,6,0,0,0,0,8,13,9,16,3,0,0,2,15,4,0,13,3,0,0,0,7,14,9,13,0,0,0,0,1,15,16,6,0,0,0,0,11,10,10,12,0,0,0,0,9,10,1,16,3,0,0,0,0,9,12,10,1,0,8 +0,0,4,16,7,1,0,0,0,0,10,13,15,11,0,0,0,2,15,3,4,15,3,0,0,4,16,0,0,12,8,0,0,5,16,1,0,9,8,0,0,4,16,2,1,13,7,0,0,0,14,9,9,14,1,0,0,0,5,14,15,6,0,0,0 +0,0,5,12,6,0,0,0,0,0,11,16,15,6,0,0,0,0,10,11,11,15,2,0,0,0,4,15,15,16,6,0,0,0,0,4,8,13,9,0,0,0,0,0,0,8,13,0,0,0,9,8,8,12,13,0,0,0,5,10,13,12,5,0,9 +0,0,1,13,10,0,0,0,0,0,9,16,11,0,0,0,0,3,16,11,0,0,0,0,0,5,16,11,7,1,0,0,0,4,16,16,16,15,3,0,0,3,16,12,2,12,11,0,0,0,12,14,5,9,15,0,0,0,0,10,15,16,11,0,6 +0,0,2,9,3,0,0,0,0,5,14,9,13,9,0,0,0,7,14,0,10,10,0,0,0,0,9,14,15,6,0,0,0,0,7,16,16,0,0,0,0,0,12,6,7,12,1,0,0,0,15,5,0,14,6,0,0,0,2,8,12,11,3,0,8 +0,0,6,12,8,4,0,0,0,1,14,5,7,16,1,0,0,2,16,4,6,13,0,0,0,0,9,14,15,2,0,0,0,0,7,14,12,1,0,0,0,0,15,2,9,11,0,0,0,0,15,7,6,16,0,0,0,0,3,8,9,6,0,0,8 +0,1,8,15,10,0,0,0,0,6,15,13,16,8,0,0,0,0,0,3,14,12,0,0,0,0,4,15,16,10,0,0,0,0,7,12,13,16,6,0,0,0,0,0,0,14,8,0,0,0,8,10,13,16,3,0,0,0,10,16,12,5,0,0,3 +0,1,13,12,0,0,0,0,0,7,16,16,8,0,0,0,0,8,10,4,14,0,0,0,0,2,6,2,15,0,0,0,0,0,0,9,10,0,0,0,0,0,5,16,5,0,0,0,0,2,16,16,16,16,11,0,0,1,11,11,8,9,9,0,2 +0,0,8,12,9,1,0,0,0,3,16,16,16,10,0,0,0,3,16,16,16,10,0,0,0,0,8,16,16,4,0,0,0,0,12,16,16,8,0,0,0,1,15,16,16,9,0,0,0,0,13,16,16,9,0,0,0,0,6,9,11,3,0,0,1 +0,0,6,14,16,5,0,0,0,3,16,13,13,12,0,0,0,1,4,1,12,12,0,0,0,0,4,14,16,6,0,0,0,0,6,14,16,15,2,0,0,0,0,0,8,16,2,0,0,2,16,10,13,15,0,0,0,0,9,14,8,2,0,0,3 +0,0,6,12,12,8,0,0,0,0,11,16,16,16,0,0,0,0,9,16,16,16,1,0,0,0,12,16,16,15,2,0,0,0,12,16,16,16,4,0,0,0,12,16,16,11,1,0,0,0,8,16,16,1,0,0,0,1,11,12,5,0,0,0,1 +0,0,1,12,14,16,10,0,0,0,3,10,8,16,6,0,0,0,0,0,3,15,1,0,0,0,1,7,14,14,3,0,0,0,4,13,16,12,5,0,0,0,0,7,13,0,0,0,0,0,0,13,7,0,0,0,0,0,2,14,2,0,0,0,7 +0,0,10,16,6,0,0,0,0,0,15,14,16,0,0,0,0,0,14,4,16,0,0,0,0,0,0,2,15,0,0,0,0,0,0,6,10,0,0,0,0,0,1,14,6,0,0,0,0,1,14,16,13,12,6,0,0,0,8,8,8,11,11,0,2 +0,0,0,4,15,0,0,0,0,0,1,13,11,0,0,0,0,0,7,16,7,12,0,0,0,2,16,11,10,16,2,0,0,6,16,16,16,16,3,0,0,0,4,4,15,13,1,0,0,0,0,1,16,7,0,0,0,0,0,4,11,1,0,0,4 +0,0,2,16,15,6,0,0,0,7,11,15,12,16,0,0,0,9,16,10,10,16,1,0,0,0,11,16,16,6,0,0,0,0,15,16,15,2,0,0,0,0,14,5,10,13,1,0,0,0,11,11,6,16,7,0,0,0,3,11,16,12,3,0,8 +0,0,0,4,15,0,0,0,0,0,3,15,10,0,0,0,0,0,11,15,0,4,0,0,0,5,16,8,4,16,3,0,0,3,16,14,13,16,2,0,0,0,4,6,15,14,2,0,0,0,0,0,15,6,0,0,0,0,0,2,15,2,0,0,4 +0,1,9,13,12,6,0,0,0,5,16,13,8,10,2,0,0,4,14,1,0,0,0,0,0,2,16,14,4,0,0,0,0,5,16,15,16,4,0,0,0,0,0,1,11,16,3,0,0,0,6,6,15,15,1,0,0,0,7,13,10,3,0,0,5 +0,0,9,16,15,5,0,0,0,4,16,12,12,16,7,0,0,5,16,4,0,1,1,0,0,8,16,15,11,3,0,0,0,4,14,12,14,15,2,0,0,0,0,0,4,16,4,0,0,0,5,8,15,15,2,0,0,0,11,15,9,2,0,0,5 +0,0,0,0,9,6,0,0,0,0,0,9,14,2,0,0,0,0,3,15,4,3,6,0,0,1,13,11,0,13,7,0,0,7,16,13,13,16,3,0,0,0,4,4,13,13,1,0,0,0,0,1,16,7,0,0,0,0,0,1,14,1,0,0,4 +0,1,15,12,3,0,0,0,0,1,12,16,15,4,0,0,0,0,0,4,16,10,0,0,0,0,0,15,16,5,0,0,0,0,0,12,14,13,1,0,0,0,0,0,0,8,12,0,0,0,7,8,6,13,13,0,0,0,13,16,16,13,7,0,3 +0,3,14,14,4,0,0,0,0,8,15,11,16,6,0,0,0,1,4,4,16,7,0,0,0,0,5,16,16,4,0,0,0,0,2,9,15,16,3,0,0,0,0,0,4,16,8,0,0,0,7,4,11,16,5,0,0,2,14,16,14,7,0,0,3 +0,0,4,14,15,4,0,0,0,1,16,14,15,13,0,0,0,4,16,7,4,16,3,0,0,5,16,3,0,12,4,0,0,4,16,2,0,11,8,0,0,2,16,6,1,15,4,0,0,0,12,13,13,14,1,0,0,0,3,16,15,4,0,0,0 +0,0,6,14,9,0,0,0,0,3,16,13,16,6,0,0,0,2,16,3,9,13,0,0,0,1,16,1,1,15,4,0,0,3,16,0,0,12,6,0,0,0,16,1,0,13,4,0,0,0,16,8,8,15,1,0,0,0,7,15,14,5,0,0,0 +0,0,4,14,11,0,0,0,0,3,16,9,15,7,0,0,0,9,15,0,4,14,2,0,0,8,13,0,0,12,6,0,0,8,12,0,0,11,7,0,0,6,14,1,0,14,4,0,0,0,14,11,8,16,1,0,0,0,5,16,14,6,0,0,0 +0,0,1,8,6,0,0,0,0,0,14,16,16,16,0,0,0,0,16,16,16,16,0,0,0,0,16,16,16,16,0,0,0,0,16,16,16,15,0,0,0,0,16,16,16,13,0,0,0,0,9,16,16,9,0,0,0,0,6,8,6,1,0,0,1 +0,2,10,15,8,0,0,0,0,6,16,15,16,8,0,0,0,8,16,5,13,16,0,0,0,4,16,12,16,16,5,0,0,0,5,11,13,16,7,0,0,0,0,0,1,16,8,0,0,0,2,4,10,16,8,0,0,0,12,16,14,11,0,0,9 +0,2,15,13,2,0,0,0,0,6,16,14,15,1,0,0,0,7,12,0,15,8,0,0,0,4,7,0,16,8,0,0,0,0,0,1,16,3,0,0,0,0,0,9,15,0,0,0,0,2,15,16,16,14,5,0,0,2,10,12,13,16,8,0,2 +0,0,13,14,4,0,0,0,0,4,16,13,14,2,0,0,0,0,16,4,16,14,2,0,0,0,11,14,16,14,0,0,0,0,0,8,11,15,0,0,0,0,0,0,4,16,2,0,0,0,6,6,7,16,2,0,0,0,12,16,16,10,0,0,9 +0,1,12,16,15,11,3,0,0,7,16,14,11,9,4,0,0,3,16,8,2,0,0,0,0,5,16,16,15,3,0,0,0,2,14,16,16,14,0,0,0,0,0,0,12,16,1,0,0,2,12,12,16,16,0,0,0,1,14,16,10,3,0,0,5 +0,0,8,16,15,5,0,0,0,0,7,8,15,10,0,0,0,0,0,0,14,10,0,0,0,0,5,12,16,14,2,0,0,0,16,16,15,10,6,0,0,0,1,14,7,0,0,0,0,0,4,16,0,0,0,0,0,0,4,12,0,0,0,0,7 +0,0,8,12,3,0,0,0,0,5,16,16,14,4,0,0,0,10,15,2,14,16,2,0,0,3,16,10,13,16,7,0,0,0,5,8,10,16,8,0,0,0,0,0,0,12,12,0,0,0,10,7,4,14,12,0,0,0,10,16,16,14,7,0,9 +0,0,3,8,6,1,0,0,0,2,15,16,16,12,0,0,0,0,13,16,16,13,0,0,0,0,10,16,16,16,0,0,0,0,12,16,16,16,0,0,0,0,14,16,16,16,2,0,0,0,12,16,16,15,2,0,0,0,1,6,6,0,0,0,1 +0,0,5,14,12,1,0,0,0,0,14,15,16,10,0,0,0,4,13,1,3,15,0,0,0,5,7,0,0,11,7,0,0,4,13,0,0,6,8,0,0,4,16,2,0,9,8,0,0,0,13,15,10,16,3,0,0,0,4,13,16,11,0,0,0 +0,1,9,13,9,2,0,0,0,6,16,10,16,7,0,0,0,0,0,2,16,6,0,0,0,0,2,13,14,0,0,0,0,0,2,13,16,13,0,0,0,0,0,0,4,15,8,0,0,0,8,4,4,14,11,0,0,1,14,16,16,12,1,0,3 +0,0,8,13,16,10,3,0,0,5,16,10,8,10,6,0,0,6,12,0,0,0,0,0,0,6,15,12,3,0,0,0,0,4,12,14,16,1,0,0,0,0,0,0,16,8,0,0,0,0,4,7,16,7,0,0,0,0,11,14,10,0,0,0,5 +0,0,0,6,7,0,0,0,0,0,2,15,6,0,0,0,0,0,10,11,1,1,0,0,0,4,16,5,10,9,0,0,0,8,16,16,16,15,2,0,0,1,4,7,16,10,0,0,0,0,0,5,16,1,0,0,0,0,0,9,11,0,0,0,4 +0,0,2,12,5,0,0,0,0,1,13,16,13,2,0,0,0,3,16,10,14,11,0,0,0,7,16,8,5,16,4,0,0,7,16,8,1,14,5,0,0,2,15,9,1,15,5,0,0,0,11,14,11,15,2,0,0,0,3,16,15,3,0,0,0 +0,0,1,8,12,7,0,0,0,2,15,8,5,14,0,0,0,0,11,5,3,15,0,0,0,0,1,14,16,12,0,0,0,0,5,15,15,9,0,0,0,1,15,6,1,12,1,0,0,0,12,11,1,12,5,0,0,0,1,8,8,9,3,0,8 +0,0,2,12,16,14,10,0,0,0,1,8,4,11,13,0,0,0,0,0,0,14,5,0,0,0,0,3,9,15,0,0,0,0,5,16,16,15,3,0,0,0,2,10,13,3,0,0,0,0,0,13,7,0,0,0,0,0,1,15,2,0,0,0,7 +0,0,0,12,7,0,0,0,0,0,7,16,3,0,0,0,0,2,15,6,0,0,0,0,0,4,16,10,7,0,0,0,0,5,16,16,16,12,0,0,0,6,16,9,5,16,3,0,0,1,14,15,11,16,3,0,0,0,1,10,16,7,0,0,6 +0,0,9,16,8,1,0,0,0,5,16,9,14,11,1,0,0,10,10,0,8,16,4,0,0,4,16,15,15,16,8,0,0,0,4,8,7,13,8,0,0,0,0,0,0,9,11,0,0,0,11,6,9,16,7,0,0,0,11,13,12,6,0,0,9 +0,0,0,11,12,0,0,0,0,0,7,16,11,1,0,0,0,2,16,12,0,0,0,0,0,2,16,7,0,0,0,0,0,8,16,16,8,1,0,0,0,4,16,13,13,14,3,0,0,0,13,14,7,16,12,0,0,0,0,10,16,12,6,0,6 +0,0,7,14,7,0,0,0,0,0,15,14,16,3,0,0,0,0,15,12,16,6,0,0,0,0,6,16,16,4,0,0,0,0,8,16,16,11,0,0,0,0,15,12,3,16,8,0,0,2,15,11,8,16,10,0,0,0,9,16,16,12,6,0,8 +0,0,1,14,13,1,0,0,0,0,12,16,16,12,0,0,0,1,11,14,11,16,5,0,0,3,8,16,2,8,10,0,0,0,5,14,0,8,7,0,0,0,7,12,0,15,3,0,0,0,7,14,11,11,0,0,0,0,2,16,13,2,0,0,0 +0,3,12,16,15,4,0,0,0,4,11,8,14,11,0,0,0,0,0,6,16,3,0,0,0,0,3,15,15,1,0,0,0,0,1,11,15,14,1,0,0,0,0,0,4,12,6,0,0,1,8,12,15,16,6,0,0,2,12,12,11,7,0,0,3 +0,0,2,6,9,4,0,0,0,3,15,5,8,13,0,0,0,4,15,3,5,16,0,0,0,0,9,13,15,7,0,0,0,0,3,16,12,0,0,0,0,0,9,9,13,6,0,0,0,0,10,8,11,16,1,0,0,0,5,12,12,7,0,0,8 +0,0,7,15,14,3,0,0,0,0,8,11,10,12,0,0,0,0,0,0,5,13,0,0,0,0,0,5,15,9,0,0,0,0,0,5,12,15,6,0,0,0,0,0,0,8,12,0,0,0,8,1,1,12,12,0,0,0,9,15,16,13,1,0,3 +0,1,8,15,16,12,3,0,0,4,16,13,5,6,2,0,0,0,16,7,1,0,0,0,0,6,16,16,15,8,0,0,0,1,12,8,12,16,5,0,0,0,0,0,5,16,5,0,0,0,3,8,15,12,0,0,0,0,8,14,8,0,0,0,5 +0,0,4,15,6,0,0,0,0,0,13,13,16,0,0,0,0,4,15,1,16,2,0,0,0,0,0,2,16,0,0,0,0,0,0,8,12,0,0,0,0,1,12,16,6,4,0,0,0,7,16,16,16,16,6,0,0,0,2,0,4,8,3,0,2 +0,0,0,8,12,1,0,0,0,0,8,16,14,8,0,0,0,6,16,2,2,16,0,0,0,8,16,2,0,10,6,0,0,4,16,3,0,8,8,0,0,0,10,9,0,8,8,0,0,0,4,16,12,16,2,0,0,0,0,7,13,8,0,0,0 +0,0,10,12,13,9,4,0,0,2,16,11,8,5,3,0,0,3,16,1,0,0,0,0,0,7,16,16,15,3,0,0,0,6,12,9,14,15,1,0,0,0,0,0,6,16,2,0,0,0,4,4,13,15,1,0,0,0,13,15,9,2,0,0,5 +0,0,13,15,3,0,0,0,0,2,16,11,15,4,0,0,0,4,16,2,16,16,0,0,0,2,13,16,16,16,2,0,0,0,0,4,5,15,2,0,0,0,0,0,0,12,7,0,0,0,2,4,4,11,12,0,0,0,11,16,16,15,10,0,9 +0,0,12,16,6,0,0,0,0,6,16,11,15,5,0,0,0,8,16,0,11,15,1,0,0,8,14,0,2,16,5,0,0,8,14,0,0,12,7,0,0,5,16,2,1,16,4,0,0,3,15,10,11,14,2,0,0,0,6,12,10,1,0,0,0 +0,0,11,16,12,1,0,0,0,0,16,12,16,12,0,0,0,0,3,2,12,12,0,0,0,0,5,16,16,6,0,0,0,0,4,12,16,15,2,0,0,0,0,0,5,15,7,0,0,1,11,12,12,16,7,0,0,2,12,15,12,5,1,0,3 +0,0,3,11,7,0,0,0,0,4,16,16,16,0,0,0,0,4,16,16,16,0,0,0,0,4,16,16,10,0,0,0,0,1,14,16,16,0,0,0,0,0,12,16,16,6,0,0,0,0,11,16,16,11,0,0,0,0,2,11,12,6,0,0,1 +0,0,6,11,14,3,0,0,0,2,16,12,11,16,0,0,0,4,16,9,7,15,0,0,0,0,14,15,16,12,0,0,0,0,10,16,16,3,0,0,0,1,16,6,11,15,1,0,0,3,16,3,7,16,3,0,0,0,7,14,16,12,1,0,8 +0,2,12,16,16,10,2,0,0,7,16,8,6,8,2,0,0,6,15,0,0,0,0,0,0,6,16,13,8,0,0,0,0,6,15,12,16,6,0,0,0,0,0,0,11,13,0,0,0,0,4,6,16,12,0,0,0,2,15,14,9,0,0,0,5 +0,0,6,16,5,0,0,0,0,6,16,16,16,7,0,0,0,11,15,2,16,14,0,0,0,9,16,10,16,16,7,0,0,2,13,16,16,16,8,0,0,0,0,0,0,13,12,0,0,0,3,8,12,16,7,0,0,0,5,16,12,5,0,0,9 +0,0,3,15,13,2,0,0,0,2,15,14,16,8,0,0,0,5,15,6,4,15,0,0,0,1,15,1,1,15,3,0,0,3,16,2,0,12,5,0,0,3,16,3,0,12,6,0,0,0,12,14,15,15,0,0,0,0,2,14,14,4,0,0,0 +0,0,2,14,1,0,0,0,0,0,11,13,0,0,0,0,0,2,16,1,0,0,0,0,0,4,16,1,3,0,0,0,0,8,12,12,16,13,0,0,0,4,16,9,4,13,6,0,0,0,13,12,8,12,11,0,0,0,2,10,13,14,4,0,6 +0,0,8,12,11,6,0,0,0,1,14,16,16,13,0,0,0,4,16,16,16,8,0,0,0,1,15,16,16,3,0,0,0,3,15,16,16,7,0,0,0,2,13,16,16,8,0,0,0,1,14,16,16,7,0,0,0,0,7,12,9,0,0,0,1 +0,0,1,12,14,1,0,0,0,0,8,15,7,0,0,0,0,1,14,7,0,0,0,0,0,1,16,16,16,10,2,0,0,1,16,15,5,11,10,0,0,1,16,4,0,8,13,0,0,0,11,11,9,16,8,0,0,0,2,11,15,5,0,0,6 +0,1,10,16,12,1,0,0,0,7,16,10,13,5,0,0,0,1,6,0,9,8,0,0,0,0,0,7,15,10,0,0,0,0,0,6,12,14,7,0,0,0,0,0,0,2,14,0,0,0,7,6,4,9,14,0,0,0,7,15,16,13,7,0,3 +0,0,8,15,10,1,0,0,0,0,15,13,15,10,0,0,0,0,16,2,0,14,1,0,0,0,14,5,7,16,2,0,0,0,7,12,11,15,3,0,0,0,0,0,0,13,4,0,0,0,6,6,9,16,2,0,0,0,7,13,14,3,0,0,9 +0,4,16,16,16,16,13,0,0,5,12,12,13,16,14,0,0,0,0,0,10,15,4,0,0,0,0,3,16,9,0,0,0,0,0,7,16,2,0,0,0,0,3,15,9,0,0,0,0,0,13,16,2,0,0,0,0,2,16,12,0,0,0,0,7 +0,0,1,16,15,5,0,0,0,0,9,16,16,12,0,0,0,2,16,16,16,7,0,0,0,0,12,16,16,2,0,0,0,0,8,16,16,5,0,0,0,0,15,16,16,11,0,0,0,0,15,16,16,16,8,0,0,0,2,10,15,11,4,0,1 +0,0,2,16,10,1,0,0,0,0,8,15,13,7,0,0,0,0,3,9,4,13,0,0,0,0,0,0,6,13,0,0,0,0,0,0,12,8,0,0,0,0,0,4,16,3,0,0,0,0,2,15,14,8,14,5,0,0,2,15,16,16,12,7,2 +0,0,1,12,13,2,0,0,0,0,12,16,10,2,0,0,0,2,16,7,0,0,0,0,0,1,16,12,8,2,0,0,0,2,16,13,13,14,2,0,0,0,13,7,0,12,12,0,0,0,9,13,6,15,13,0,0,0,1,11,15,14,6,0,6 +0,0,4,9,9,0,0,0,0,1,16,15,12,10,0,0,0,3,16,5,0,0,0,0,0,6,16,16,7,0,0,0,0,3,16,15,16,1,0,0,0,0,1,0,12,5,0,0,0,0,13,10,16,6,0,0,0,0,8,16,9,0,0,0,5 +0,0,12,14,8,3,0,0,0,3,16,15,16,16,3,0,0,0,14,12,14,13,1,0,0,0,7,16,16,3,0,0,0,0,12,16,16,2,0,0,0,1,15,4,9,14,0,0,0,3,15,4,5,16,1,0,0,1,12,16,16,12,0,0,8 +0,0,2,9,16,6,0,0,0,1,15,16,11,5,0,0,0,2,16,7,0,0,0,0,0,5,16,0,3,0,0,0,0,4,16,16,16,14,3,0,0,2,16,6,1,10,10,0,0,0,10,12,8,15,10,0,0,0,1,11,16,12,4,0,6 +0,1,11,9,14,11,1,0,0,8,16,14,14,16,2,0,0,7,16,0,11,12,0,0,0,1,1,1,15,5,0,0,0,0,0,10,14,0,0,0,0,0,2,15,5,0,0,0,0,0,12,16,4,0,0,0,0,0,15,11,1,0,0,0,7 +0,0,11,15,16,16,16,7,0,0,14,14,12,16,16,3,0,0,1,0,4,16,6,0,0,0,0,0,13,12,0,0,0,0,0,7,15,4,0,0,0,0,3,16,9,0,0,0,0,0,11,16,5,0,0,0,0,0,15,15,0,0,0,0,7 +0,0,11,12,2,0,0,0,0,3,16,12,7,0,0,0,0,2,14,3,10,0,0,0,0,0,0,5,8,0,0,0,0,0,0,11,5,0,0,0,0,0,3,16,1,0,0,0,0,0,11,14,9,15,15,0,0,0,8,13,12,8,10,2,2 +0,0,2,16,11,0,0,0,0,1,11,16,16,7,0,0,0,10,16,16,16,9,0,0,0,2,9,15,16,11,0,0,0,0,0,12,16,8,0,0,0,0,4,16,16,5,0,0,0,0,10,16,16,15,4,0,0,0,3,15,16,12,0,0,1 +0,4,8,8,12,16,5,0,0,8,16,16,16,15,3,0,0,2,3,7,16,7,0,0,0,0,0,12,14,1,0,0,0,0,9,16,7,0,0,0,0,0,14,16,0,0,0,0,0,6,16,12,0,0,0,0,0,6,14,7,0,0,0,0,7 +0,0,9,16,16,7,0,0,0,0,13,15,13,11,0,0,0,1,15,11,1,0,0,0,0,2,16,16,15,3,0,0,0,0,7,12,12,10,0,0,0,0,0,0,9,14,0,0,0,0,4,8,15,15,0,0,0,0,10,16,16,7,0,0,5 +0,0,5,7,13,7,0,0,0,7,16,16,10,15,0,0,0,5,15,5,6,11,0,0,0,1,9,15,15,4,0,0,0,0,5,16,16,3,0,0,0,0,14,7,13,8,0,0,0,0,15,11,13,11,0,0,0,0,5,12,12,4,0,0,8 +0,0,4,15,9,1,0,0,0,0,11,16,16,9,0,0,0,0,1,10,16,11,0,0,0,0,0,8,16,12,0,0,0,0,0,11,16,10,0,0,0,0,1,16,16,7,0,0,0,0,5,16,16,12,0,0,0,0,5,16,14,6,0,0,1 +0,4,15,9,8,8,1,0,0,4,16,16,16,16,8,0,0,9,15,2,15,14,1,0,0,5,5,5,16,4,0,0,0,0,0,12,12,0,0,0,0,0,7,15,2,0,0,0,0,3,16,7,0,0,0,0,0,6,16,4,0,0,0,0,7 +0,0,3,13,14,1,0,0,0,0,8,15,7,2,0,0,0,0,10,12,2,0,0,0,0,0,13,16,16,10,1,0,0,0,15,14,11,10,10,0,0,0,13,5,0,6,14,0,0,0,8,12,8,12,10,0,0,0,1,10,12,12,4,0,6 +0,0,0,10,13,3,0,0,0,0,7,16,12,6,0,0,0,0,12,13,1,0,0,0,0,0,16,16,16,10,0,0,0,2,16,15,1,12,8,0,0,0,16,4,0,6,15,0,0,0,11,14,8,15,14,1,0,0,2,11,16,11,2,0,6 +0,0,10,13,11,10,0,0,0,0,12,16,16,16,0,0,0,0,13,11,0,2,0,0,0,0,14,16,13,1,0,0,0,0,5,10,14,9,0,0,0,0,0,0,10,13,0,0,0,0,14,16,16,11,0,0,0,0,10,16,13,1,0,0,5 +0,0,3,14,3,0,0,0,0,3,15,16,15,1,0,0,0,6,15,5,8,11,0,0,0,7,10,0,0,12,5,0,0,4,11,0,0,6,10,0,0,2,14,0,0,6,12,0,0,0,12,9,5,13,8,0,0,0,2,15,16,13,2,0,0 +0,0,8,12,13,2,0,0,0,7,16,10,10,15,2,0,0,10,11,0,1,16,4,0,0,6,15,12,16,16,7,0,0,0,5,8,4,12,8,0,0,0,0,0,0,12,12,0,0,0,11,10,12,16,8,0,0,0,9,12,15,9,1,0,9 +0,0,5,13,12,2,0,0,0,0,14,12,12,13,0,0,0,0,11,4,2,15,0,0,0,0,8,8,4,16,1,0,0,0,2,14,16,16,6,0,0,0,0,0,0,7,10,0,0,0,8,9,8,15,10,0,0,0,4,16,16,13,2,0,9 +0,0,0,15,12,3,0,0,0,0,0,16,16,8,0,0,0,0,9,16,16,9,0,0,0,0,0,10,16,13,0,0,0,0,0,4,16,16,2,0,0,0,1,8,16,16,10,0,0,0,8,16,16,16,16,5,0,0,1,9,12,14,12,4,1 +0,0,14,15,12,12,6,0,0,1,16,12,12,16,7,0,0,0,6,1,12,12,0,0,0,0,0,8,15,2,0,0,0,0,0,13,7,0,0,0,0,0,6,15,1,0,0,0,0,0,15,10,0,0,0,0,0,0,15,6,0,0,0,0,7 +0,1,12,13,9,5,0,0,0,5,16,11,15,16,0,0,0,4,16,5,8,16,4,0,0,2,13,16,16,16,5,0,0,0,0,3,4,15,6,0,0,0,0,0,1,15,6,0,0,0,11,9,12,16,2,0,0,0,11,16,14,8,0,0,9 +0,0,0,7,8,0,0,0,0,0,0,14,4,5,0,0,0,0,2,14,4,12,0,0,0,0,9,7,7,9,0,0,0,1,16,2,10,12,3,0,0,10,16,16,16,16,3,0,0,4,8,8,15,4,0,0,0,0,0,7,11,0,0,0,4 +0,0,4,16,14,2,0,0,0,0,16,11,11,10,0,0,0,1,4,1,11,10,0,0,0,0,1,11,16,6,0,0,0,0,6,16,14,13,2,0,0,0,1,2,1,10,11,0,0,0,7,11,6,13,14,0,0,0,3,12,16,15,7,0,3 +0,0,9,11,8,5,0,0,0,2,16,14,16,15,0,0,0,6,16,11,2,0,0,0,0,8,16,16,13,2,0,0,0,1,3,0,12,9,0,0,0,0,0,0,9,12,0,0,0,0,5,8,16,6,0,0,0,0,8,15,10,0,0,0,5 +0,0,4,14,10,0,0,0,0,0,14,15,15,6,0,0,0,1,16,5,5,11,0,0,0,0,5,4,6,13,0,0,0,0,0,0,14,6,0,0,0,0,0,3,16,2,0,0,0,0,3,15,16,12,16,3,0,0,4,16,12,12,12,5,2 +0,0,6,15,16,6,0,0,0,5,15,16,16,11,0,0,0,8,16,16,16,9,0,0,0,0,4,16,16,6,0,0,0,0,2,16,16,0,0,0,0,0,8,16,16,2,0,0,0,0,11,16,16,13,2,0,0,0,5,13,11,8,2,0,1 +0,0,5,3,10,10,0,0,0,4,16,13,14,13,0,0,0,4,14,0,8,8,0,0,0,1,13,13,15,0,0,0,0,0,0,14,16,1,0,0,0,0,13,13,8,5,0,0,0,1,13,10,8,10,0,0,0,0,2,13,16,6,0,0,8 +0,0,5,10,12,7,0,0,0,1,14,16,16,16,0,0,0,11,16,4,7,15,0,0,0,5,12,0,11,14,0,0,0,0,0,5,16,9,0,0,0,0,2,14,13,5,1,0,0,0,10,16,16,16,15,0,0,0,7,12,12,9,12,1,2 +0,0,9,14,9,2,0,0,0,2,16,10,14,8,0,0,0,2,16,2,6,16,1,0,0,0,14,9,11,16,5,0,0,0,2,11,11,14,9,0,0,0,0,0,0,13,11,0,0,0,5,9,11,16,6,0,0,0,7,13,12,8,0,0,9 +0,0,11,16,14,7,1,0,0,7,15,7,13,16,4,0,0,10,12,0,5,16,6,0,0,3,16,12,15,16,8,0,0,0,2,4,7,16,6,0,0,0,0,0,3,16,4,0,0,0,7,10,10,15,2,0,0,0,11,16,14,6,0,0,9 +0,0,5,15,12,3,0,0,0,0,9,12,10,3,0,0,0,0,14,6,0,0,0,0,0,0,15,16,16,3,0,0,0,0,7,8,10,10,0,0,0,0,0,0,5,13,0,0,0,0,4,12,16,11,0,0,0,0,6,16,11,2,0,0,5 +0,0,5,12,11,2,0,0,0,1,14,15,13,14,0,0,0,2,15,4,6,16,0,0,0,0,0,2,13,12,0,0,0,0,0,11,16,13,2,0,0,0,0,1,5,12,12,0,0,0,8,11,8,14,12,0,0,0,7,16,12,12,3,0,3 +0,0,0,11,15,5,0,0,0,0,8,16,13,6,0,0,0,0,11,14,0,0,0,0,0,0,15,15,12,8,0,0,0,0,16,14,12,15,9,0,0,0,16,6,0,11,14,0,0,0,10,14,9,16,11,0,0,0,1,11,13,12,1,0,6 +0,0,11,12,16,10,1,0,0,5,16,15,7,15,4,0,0,5,16,6,8,15,1,0,0,0,7,16,16,10,0,0,0,0,6,16,16,7,0,0,0,0,14,10,10,12,0,0,0,4,16,9,12,14,0,0,0,1,11,16,15,5,0,0,8 +0,0,2,13,16,6,0,0,0,0,11,16,11,5,0,0,0,0,15,6,0,0,0,0,0,4,16,4,10,2,0,0,0,3,14,15,16,14,1,0,0,3,16,8,0,14,9,0,0,0,13,8,5,16,5,0,0,0,4,13,16,10,0,0,6 +0,0,0,5,4,1,0,0,0,0,0,14,8,12,0,0,0,0,4,13,4,12,0,0,0,0,12,5,7,9,0,0,0,2,16,4,13,16,7,0,0,10,16,16,16,11,1,0,0,5,6,7,15,0,0,0,0,0,0,7,7,0,0,0,4 +0,0,0,11,8,0,0,0,0,0,0,13,7,0,0,0,0,0,3,16,6,15,0,0,0,0,9,11,7,14,0,0,0,1,15,6,12,13,1,0,0,7,16,16,16,16,11,0,0,5,12,13,16,8,3,0,0,0,0,12,12,0,0,0,4 +0,0,10,16,16,8,0,0,0,0,15,14,9,9,0,0,0,3,16,5,0,0,0,0,0,2,16,16,10,0,0,0,0,0,11,13,15,6,0,0,0,0,0,0,10,11,0,0,0,0,5,11,15,13,0,0,0,0,7,16,15,4,0,0,5 +0,0,5,12,8,2,0,0,0,0,12,6,8,13,0,0,0,2,10,0,0,12,2,0,0,0,14,12,12,10,0,0,0,0,1,15,16,7,0,0,0,0,14,8,0,9,1,0,0,0,14,2,1,12,2,0,0,0,8,12,12,6,0,0,8 +0,0,4,11,4,0,0,0,0,0,12,15,16,14,0,0,0,4,15,0,5,12,6,0,0,6,10,0,0,7,8,0,0,7,8,0,0,8,8,0,0,6,13,0,1,13,3,0,0,2,16,10,12,13,0,0,0,0,6,13,12,1,0,0,0 +0,0,3,16,11,0,0,0,0,0,5,16,16,0,0,0,0,1,11,16,15,0,0,0,0,3,15,16,15,0,0,0,0,0,2,16,14,0,0,0,0,0,0,15,16,0,0,0,0,0,1,16,16,2,0,0,0,0,1,11,14,5,0,0,1 +0,0,3,14,7,1,0,0,0,0,11,15,16,12,0,0,0,0,14,8,0,13,4,0,0,1,16,2,0,12,6,0,0,1,16,2,0,13,7,0,0,2,16,0,4,16,1,0,0,0,13,10,15,13,0,0,0,0,6,15,12,6,0,0,0 +0,0,9,13,4,0,0,0,0,1,16,9,11,0,0,0,0,2,11,0,13,0,0,0,0,0,2,3,13,0,0,0,0,0,0,11,5,0,0,0,0,0,3,14,1,0,0,0,0,0,11,14,10,8,11,0,0,0,11,13,12,12,14,2,2 +0,0,7,11,13,7,0,0,0,1,15,15,13,15,2,0,0,4,16,4,5,14,4,0,0,0,10,16,16,13,2,0,0,0,7,15,16,3,0,0,0,1,16,9,8,15,0,0,0,3,15,6,9,16,0,0,0,0,6,16,15,6,0,0,8 +0,0,4,14,15,8,1,0,0,0,14,14,12,15,7,0,0,3,15,1,0,9,7,0,0,6,12,0,0,8,8,0,0,8,11,0,0,9,8,0,0,8,12,0,0,14,5,0,0,3,16,9,14,11,0,0,0,0,6,14,11,0,0,0,0 +0,2,12,16,16,16,14,0,0,9,16,16,15,16,6,0,0,11,13,0,11,14,0,0,0,0,0,2,16,6,0,0,0,0,0,12,14,1,0,0,0,0,4,16,6,0,0,0,0,0,11,16,1,0,0,0,0,2,15,13,0,0,0,0,7 +0,0,1,7,12,5,0,0,0,0,4,16,9,6,0,0,0,0,11,8,0,0,0,0,0,0,15,8,8,5,0,0,0,0,16,16,12,16,2,0,0,0,15,5,0,15,5,0,0,0,11,9,8,16,4,0,0,0,2,14,15,8,0,0,6 +0,0,5,10,12,2,0,0,0,2,16,13,11,11,0,0,0,7,14,0,4,15,0,0,0,1,6,0,10,12,0,0,0,0,0,2,16,6,0,0,0,0,0,9,12,0,0,0,0,0,4,16,16,16,16,2,0,0,7,15,11,8,8,1,2 +0,0,7,13,16,8,0,0,0,9,16,12,8,16,2,0,0,4,7,0,6,15,1,0,0,0,4,13,16,8,0,0,0,0,6,13,15,16,3,0,0,0,0,0,2,11,12,0,0,0,7,8,4,14,12,0,0,0,10,16,16,14,4,0,3 +0,0,10,15,10,0,0,0,0,10,15,10,16,6,0,0,0,2,2,13,15,1,0,0,0,0,4,16,16,5,0,0,0,0,0,3,7,15,5,0,0,0,0,0,0,9,13,0,0,0,13,7,5,11,14,0,0,0,7,16,16,14,3,0,3 +0,0,0,11,14,2,0,0,0,0,9,16,12,6,0,0,0,2,15,8,0,0,0,0,0,0,16,4,3,1,0,0,0,0,16,14,16,15,3,0,0,0,13,14,2,4,14,1,0,0,7,14,5,8,16,4,0,0,1,9,15,14,7,0,6 +0,0,10,14,11,9,0,0,0,0,9,12,8,9,0,0,0,0,11,9,1,0,0,0,0,0,15,16,14,2,0,0,0,0,12,2,10,6,0,0,0,0,0,0,0,13,0,0,0,0,7,5,8,12,0,0,0,0,10,16,15,4,0,0,5 +0,1,11,14,12,6,0,0,0,8,15,7,11,15,0,0,0,11,9,0,8,16,4,0,0,8,14,13,16,16,7,0,0,0,6,9,4,15,8,0,0,0,0,0,0,11,10,0,0,0,9,8,11,16,3,0,0,0,14,14,11,5,0,0,9 +0,0,8,11,15,4,0,0,0,0,14,14,12,6,0,0,0,0,11,9,0,0,0,0,0,0,10,16,14,2,0,0,0,0,4,8,9,14,2,0,0,0,0,0,0,15,2,0,0,0,4,11,13,16,1,0,0,0,4,15,12,9,0,0,5 +0,0,8,12,14,15,15,1,0,3,15,16,14,16,15,2,0,0,10,2,5,16,7,0,0,0,0,1,14,12,0,0,0,0,0,6,16,5,0,0,0,0,0,15,12,0,0,0,0,0,6,16,7,0,0,0,0,0,11,16,3,0,0,0,7 +0,0,7,8,8,8,0,0,0,0,12,16,14,12,0,0,0,0,15,5,2,0,0,0,0,0,14,16,13,2,0,0,0,0,7,4,12,9,0,0,0,0,0,0,6,12,0,0,0,0,11,12,16,10,0,0,0,0,8,13,8,1,0,0,5 +0,0,3,12,15,3,0,0,0,0,14,15,14,4,0,0,0,1,16,15,1,0,0,0,0,3,16,16,5,2,1,0,0,2,16,11,4,8,10,0,0,2,16,2,0,6,13,0,0,0,11,11,4,11,10,0,0,0,1,10,13,10,3,0,6 +0,0,5,15,11,9,1,0,0,0,10,16,12,14,7,0,0,0,15,2,0,10,8,0,0,2,12,0,0,11,8,0,0,4,10,0,0,12,5,0,0,8,10,0,3,15,1,0,0,2,15,12,14,9,0,0,0,0,6,14,9,1,0,0,0 +0,0,0,10,14,0,0,0,0,0,4,16,7,0,0,0,0,0,8,16,6,8,0,0,0,0,14,9,8,15,0,0,0,4,16,3,11,15,6,0,0,10,16,16,16,16,9,0,0,6,12,13,16,7,0,0,0,0,0,12,16,2,0,0,4 +0,0,1,10,14,13,2,0,0,0,9,15,13,16,5,0,0,0,0,0,0,11,5,0,0,0,0,0,4,13,0,0,0,0,8,16,16,13,2,0,0,0,8,14,16,10,1,0,0,0,0,12,7,0,0,0,0,0,0,14,2,0,0,0,7 +0,0,7,15,8,2,0,0,0,0,12,16,14,5,0,0,0,0,12,16,16,4,0,0,0,0,12,16,16,2,0,0,0,0,13,16,16,2,0,0,0,0,14,16,16,0,0,0,0,1,14,16,16,14,1,0,0,1,8,16,11,8,0,0,1 +0,0,6,9,4,1,0,0,0,0,6,16,16,9,1,0,0,0,4,16,1,13,7,0,0,0,0,14,5,12,8,0,0,1,10,15,16,10,0,0,0,6,15,9,16,1,0,0,0,7,13,8,14,4,0,0,0,0,6,10,13,1,0,0,8 +0,0,5,12,8,1,0,0,0,0,15,16,16,8,0,0,0,0,15,16,16,6,0,0,0,1,14,16,16,4,0,0,0,2,15,16,16,9,0,0,0,0,6,16,16,14,1,0,0,0,8,16,16,15,0,0,0,0,4,12,10,4,0,0,1 +0,0,0,2,15,3,0,0,0,0,0,8,14,0,0,0,0,0,0,12,8,2,4,0,0,0,4,15,2,15,8,0,0,4,13,14,9,16,6,0,3,16,16,16,16,14,0,0,1,6,4,4,13,9,0,0,0,0,0,3,16,4,0,0,4 +0,0,7,16,10,0,0,0,0,1,13,14,16,8,0,0,0,5,16,4,7,13,0,0,0,4,16,4,0,13,5,0,0,7,16,0,0,12,8,0,0,2,15,7,0,10,11,0,0,0,12,15,12,16,5,0,0,0,6,15,16,8,0,0,0 +0,0,4,14,16,11,0,0,0,0,12,12,16,15,0,0,0,0,0,1,16,11,0,0,0,0,2,9,16,9,0,0,0,0,13,16,16,16,9,0,0,0,2,15,12,8,1,0,0,0,4,16,9,0,0,0,0,0,5,15,7,0,0,0,7 +0,0,3,12,15,3,0,0,0,0,9,15,15,7,0,0,0,0,12,14,12,1,0,0,0,0,8,16,16,14,0,0,0,0,0,0,0,13,7,0,0,0,0,0,0,7,12,0,0,2,16,10,8,13,10,0,0,0,6,11,16,14,3,0,5 +0,0,0,3,15,5,0,0,0,0,0,6,16,2,0,0,0,0,0,6,16,3,0,0,0,0,2,13,5,12,2,0,0,5,13,13,9,16,1,0,4,16,16,16,16,15,0,0,2,9,7,4,13,9,0,0,0,0,0,5,16,5,0,0,4 +0,3,13,16,16,6,0,0,0,4,16,9,14,12,0,0,0,0,8,3,14,7,0,0,0,0,0,12,16,3,0,0,0,0,0,5,15,15,1,0,0,0,0,0,1,16,10,0,0,2,12,8,11,16,7,0,0,2,15,16,15,9,0,0,3 +0,1,14,16,9,0,0,0,0,8,16,12,15,6,0,0,0,6,16,7,5,14,0,0,0,4,16,4,0,14,4,0,0,4,16,3,0,10,6,0,0,6,16,1,0,10,8,0,0,3,16,9,7,15,6,0,0,1,15,16,15,9,0,0,0 +0,0,8,16,11,0,0,0,0,1,15,15,14,9,0,0,0,1,15,12,0,12,6,0,0,0,16,7,0,10,4,0,0,0,16,6,0,10,8,0,0,3,16,3,0,15,3,0,0,1,14,14,12,16,0,0,0,0,7,13,16,8,0,0,0 +0,0,8,14,12,2,0,0,0,3,16,12,15,6,0,0,0,3,7,7,13,9,0,0,0,0,5,15,16,3,0,0,0,0,0,7,14,14,1,0,0,0,0,0,3,16,5,0,0,0,7,5,10,16,5,0,0,0,13,10,8,3,0,0,3 +0,1,9,15,5,0,0,0,0,6,16,13,15,3,0,0,0,12,11,0,12,14,0,0,0,5,15,11,15,16,3,0,0,0,4,8,10,16,5,0,0,0,0,0,0,12,12,0,0,0,6,4,7,14,13,0,0,0,7,14,14,12,5,0,9 +0,2,14,16,11,0,0,0,0,8,15,7,16,0,0,0,0,1,2,0,15,1,0,0,0,0,2,12,14,0,0,0,0,0,1,11,14,11,0,0,0,0,0,0,2,14,5,0,0,2,8,3,4,10,12,0,0,1,13,16,16,14,4,0,3 +0,0,3,11,16,16,10,0,0,0,11,10,8,16,7,0,0,0,0,0,4,16,2,0,0,0,1,4,13,11,0,0,0,0,10,16,16,16,9,0,0,0,4,13,13,8,2,0,0,0,2,14,6,0,0,0,0,0,4,12,1,0,0,0,7 +0,0,1,13,12,14,7,0,0,0,5,12,9,16,2,0,0,0,0,0,3,13,0,0,0,0,0,0,10,7,0,0,0,0,7,16,16,10,1,0,0,0,9,14,16,11,1,0,0,0,0,15,5,0,0,0,0,0,1,15,2,0,0,0,7 +0,0,0,0,10,7,0,0,0,0,0,0,15,5,0,0,0,0,0,4,16,3,0,0,0,0,0,7,15,3,0,0,0,3,7,14,12,16,1,0,1,14,16,16,16,15,0,0,0,9,10,14,16,16,0,0,0,0,0,0,12,9,0,0,4 +0,0,4,16,16,8,0,0,0,2,16,10,12,16,4,0,0,4,16,4,10,16,4,0,0,3,16,16,16,16,1,0,0,0,0,0,4,16,4,0,0,0,0,0,2,16,1,0,0,0,0,6,11,16,0,0,0,0,4,13,9,3,0,0,9 +0,1,11,16,15,2,0,0,0,4,12,8,14,8,0,0,0,1,1,2,15,6,0,0,0,0,0,7,16,4,0,0,0,0,0,1,13,13,0,0,0,0,0,0,2,16,7,0,0,0,10,11,8,15,8,0,0,0,8,14,16,12,1,0,3 +0,0,0,8,14,5,0,0,0,0,0,9,16,14,2,0,0,0,0,11,16,13,1,0,0,0,6,16,16,7,0,0,0,3,13,16,16,4,0,0,0,3,11,15,16,12,0,0,0,0,0,10,16,15,3,0,0,0,0,8,16,15,6,0,1 +0,0,3,15,12,2,0,0,0,1,16,14,13,11,0,0,0,5,16,12,0,11,5,0,0,4,11,11,0,3,8,0,0,6,8,0,0,4,8,0,0,4,11,0,0,9,7,0,0,0,15,13,9,13,5,0,0,0,3,12,14,12,0,0,0 +0,0,0,10,13,3,0,0,0,0,0,16,16,8,0,0,0,0,4,16,16,7,0,0,0,2,15,16,16,10,0,0,0,6,16,16,16,13,0,0,0,0,0,8,16,14,0,0,0,0,0,11,16,16,1,0,0,0,0,12,12,7,0,0,1 +0,0,0,9,14,14,12,6,0,0,3,8,8,11,16,7,0,0,0,0,1,12,12,0,0,0,3,4,11,14,0,0,0,0,13,16,16,15,4,0,0,0,0,10,11,0,0,0,0,0,0,14,9,0,0,0,0,0,0,15,4,0,0,0,7 +0,0,2,14,16,13,0,0,0,0,15,9,7,9,0,0,0,0,14,6,5,4,0,0,0,0,11,16,16,16,3,0,0,0,1,5,4,11,8,0,0,0,3,0,0,7,9,0,0,2,16,11,4,8,10,0,0,0,1,10,14,15,3,0,5 +0,0,4,13,16,15,4,0,0,3,16,10,8,16,4,0,0,3,10,0,3,16,4,0,0,0,0,11,16,15,0,0,0,0,0,3,14,16,6,0,0,0,0,0,2,16,9,0,0,0,2,12,8,16,8,0,0,0,3,16,16,13,1,0,3 +0,0,8,14,13,1,0,0,0,3,16,16,16,7,0,0,0,4,16,3,11,8,0,0,0,0,4,0,15,4,0,0,0,0,0,3,15,1,0,0,0,0,7,14,15,5,2,0,0,1,16,16,16,16,15,0,0,0,6,4,6,9,11,0,2 +0,0,6,11,4,0,0,0,0,0,12,16,14,0,0,0,0,0,12,16,6,1,0,0,0,0,5,16,16,5,0,0,0,0,12,16,16,4,0,0,0,0,12,16,16,9,0,0,0,0,13,16,16,12,0,0,0,0,8,12,11,6,0,0,1 +0,0,0,16,11,4,0,0,0,0,2,16,16,12,0,0,0,0,10,16,16,8,0,0,0,2,16,16,16,4,0,0,0,2,11,16,16,4,0,0,0,0,1,16,16,6,0,0,0,0,0,14,16,16,6,0,0,0,0,12,16,15,6,0,1 +0,0,0,9,15,10,0,0,0,0,2,16,16,16,0,0,0,0,0,15,16,16,0,0,0,0,2,16,16,12,0,0,0,0,14,16,16,12,0,0,0,4,16,16,16,11,0,0,0,0,0,12,16,16,4,0,0,0,0,14,15,10,2,0,1 +0,0,13,14,5,0,0,0,0,3,16,15,16,3,0,0,0,7,11,1,13,4,0,0,0,1,2,2,15,2,0,0,0,0,0,9,12,0,0,0,0,0,8,16,4,1,1,0,0,0,16,16,16,16,8,0,0,0,11,12,10,11,7,0,2 +0,0,7,13,14,6,0,0,0,2,16,16,16,14,0,0,0,3,16,9,4,2,0,0,0,2,16,16,14,5,0,0,0,0,6,8,9,15,7,0,0,0,0,0,0,8,12,0,0,0,11,4,5,14,11,0,0,0,8,16,16,13,1,0,5 +0,0,9,16,14,1,0,0,0,4,16,14,15,8,0,0,0,8,16,5,3,15,2,0,0,7,13,0,0,15,4,0,0,8,12,0,0,12,5,0,0,5,14,1,2,14,4,0,0,0,16,16,16,13,1,0,0,0,8,14,11,3,0,0,0 +0,0,0,7,8,2,0,0,0,0,2,15,16,8,0,0,0,0,4,16,16,8,0,0,0,0,8,16,16,9,0,0,0,3,16,16,16,16,0,0,0,1,6,9,16,16,0,0,0,0,0,7,16,16,2,0,0,0,0,7,11,3,0,0,1 +0,0,3,14,8,0,0,0,0,8,16,16,16,6,0,0,0,11,14,4,7,14,0,0,0,9,10,0,0,12,7,0,0,6,11,0,0,6,8,0,0,0,16,4,0,4,12,0,0,0,10,14,10,14,10,0,0,0,2,12,16,13,2,0,0 +0,0,12,13,9,2,0,0,0,3,16,16,16,7,0,0,0,2,16,5,0,0,0,0,0,0,14,16,14,3,0,0,0,0,1,4,10,15,3,0,0,0,0,0,0,9,12,0,0,0,13,12,12,13,13,0,0,0,6,12,14,14,5,0,5 +0,0,2,11,15,8,0,0,0,0,14,13,13,8,0,0,0,0,14,8,8,4,0,0,0,0,7,16,16,14,2,0,0,0,1,4,1,13,6,0,0,0,0,0,0,7,13,0,0,0,10,7,2,7,12,0,0,0,2,10,14,16,7,0,5 +0,0,12,10,2,0,0,0,0,2,16,8,10,2,0,0,0,0,16,0,10,10,0,0,0,0,5,10,12,15,0,0,0,0,0,6,8,14,3,0,0,0,0,0,0,5,12,0,0,0,0,1,4,9,13,0,0,0,10,15,16,15,5,0,9 +0,0,1,12,7,0,0,0,0,0,14,16,6,0,0,0,0,2,16,5,0,0,0,0,0,1,16,2,0,0,0,0,0,3,16,11,5,3,0,0,0,1,16,15,14,16,8,0,0,0,11,16,5,7,16,5,0,0,1,10,16,16,13,1,6 +0,1,11,14,14,3,0,0,0,9,14,10,11,14,0,0,0,8,8,0,9,12,0,0,0,3,3,8,16,6,0,0,0,0,0,8,14,16,2,0,0,0,0,0,0,10,12,0,0,0,5,6,4,12,13,0,0,0,11,12,15,14,6,0,3 +0,0,0,1,16,8,0,0,0,0,0,3,16,6,0,0,0,0,0,7,15,9,3,0,0,0,3,14,8,16,4,0,0,7,15,14,12,16,3,0,3,15,16,16,16,16,1,0,1,4,4,4,14,13,0,0,0,0,0,1,16,7,0,0,4 +0,0,3,15,16,16,6,0,0,0,11,15,12,16,2,0,0,0,0,0,11,13,0,0,0,0,4,8,16,8,2,0,0,0,14,16,16,16,10,0,0,0,3,13,12,6,1,0,0,0,1,16,6,0,0,0,0,0,7,15,2,0,0,0,7 +0,0,0,4,16,1,0,0,0,0,0,6,14,0,0,0,0,0,0,11,10,9,2,0,0,0,4,16,8,16,4,0,0,5,14,11,11,16,2,0,3,15,16,16,16,14,0,0,3,12,11,14,16,13,0,0,0,0,0,7,16,7,0,0,4 +0,0,10,16,14,6,0,0,0,0,14,16,9,15,3,0,0,0,10,16,5,15,4,0,0,0,3,16,16,13,1,0,0,0,12,16,16,10,0,0,0,2,16,5,10,14,0,0,0,7,16,8,7,16,3,0,0,0,9,14,16,11,1,0,8 +0,1,12,8,0,0,0,0,0,9,16,14,9,0,0,0,0,9,8,0,16,0,0,0,0,1,0,4,13,0,0,0,0,0,0,9,7,0,0,0,0,0,1,13,3,0,0,0,0,1,15,15,11,10,7,0,0,0,11,12,12,12,11,0,2 +0,0,0,1,16,5,0,0,0,0,0,4,16,6,0,0,0,0,0,7,15,3,2,0,0,0,1,14,8,16,7,0,0,3,12,15,9,16,2,0,2,15,16,16,16,15,0,0,0,3,7,8,15,12,0,0,0,0,0,1,16,9,0,0,4 +0,0,6,8,0,0,0,0,0,0,11,14,0,0,0,0,0,0,13,7,0,0,0,0,0,0,16,7,0,0,0,0,0,0,16,9,6,0,0,0,0,4,16,16,16,14,2,0,0,2,14,14,8,16,9,0,0,0,6,15,16,16,6,0,6 +0,0,0,9,9,1,0,0,0,0,10,16,10,12,1,0,0,0,5,14,1,12,4,0,0,0,0,11,15,16,2,0,0,0,3,12,16,9,0,0,0,1,12,10,4,16,7,0,0,2,15,5,0,11,12,0,0,0,1,10,13,13,6,0,8 +0,0,13,16,5,0,0,0,0,4,15,13,13,0,0,0,0,7,8,3,16,1,0,0,0,9,3,4,16,0,0,0,0,0,0,8,10,0,0,0,0,0,2,15,5,0,0,0,0,0,12,16,16,16,7,0,0,0,13,12,9,15,7,0,2 +0,0,10,16,14,3,0,0,0,2,16,10,14,12,0,0,0,0,7,4,14,8,0,0,0,0,0,8,16,9,0,0,0,0,0,0,7,16,4,0,0,0,0,0,0,13,7,0,0,8,11,5,4,16,4,0,0,1,11,15,16,8,1,0,3 +0,0,0,9,14,0,0,0,0,0,0,13,11,0,4,0,0,0,2,16,6,6,15,0,0,0,9,14,0,14,10,0,0,12,16,14,13,15,1,0,0,11,14,14,16,12,0,0,0,0,0,7,16,5,0,0,0,0,0,14,13,0,0,0,4 +0,0,11,15,6,0,0,0,0,0,10,16,8,0,0,0,0,0,8,16,16,0,0,0,0,2,12,16,14,2,0,0,0,0,6,16,16,7,0,0,0,0,8,16,16,9,0,0,0,0,12,16,16,15,3,0,0,0,8,15,12,5,1,0,1 +0,0,6,9,13,11,2,0,0,7,16,16,16,16,3,0,0,8,16,10,6,0,0,0,0,5,16,16,16,13,1,0,0,0,0,3,6,16,4,0,0,3,0,0,0,12,5,0,0,5,16,9,8,14,6,0,0,0,5,10,12,10,0,0,5 +0,1,10,10,6,0,0,0,0,4,15,11,15,7,0,0,0,2,14,1,11,14,0,0,0,0,11,16,16,16,0,0,0,0,0,0,0,16,3,0,0,0,0,0,0,11,8,0,0,2,8,8,8,12,12,0,0,2,12,12,16,13,5,0,9 +0,0,8,16,15,4,0,0,0,0,8,16,16,10,0,0,0,0,8,16,16,7,0,0,0,0,8,16,16,4,0,0,0,2,16,16,16,4,0,0,0,0,12,16,16,4,0,0,0,0,7,16,16,11,0,0,0,0,7,16,16,13,4,0,1 +0,0,7,11,15,6,0,0,0,3,16,15,12,4,0,0,0,5,16,13,14,8,0,0,0,6,15,11,8,15,6,0,0,0,0,0,0,8,8,0,0,0,0,0,0,4,8,0,0,0,13,9,8,13,5,0,0,0,6,11,14,9,0,0,5 +0,1,12,16,16,8,0,0,0,4,10,8,8,4,0,0,0,9,11,6,7,0,0,0,0,4,16,16,16,11,0,0,0,0,0,0,1,16,4,0,0,0,0,0,0,16,6,0,0,0,5,8,11,14,2,0,0,0,15,13,10,1,0,0,5 +0,0,7,13,16,8,0,0,0,0,16,14,10,16,2,0,0,0,9,14,4,16,1,0,0,0,1,16,16,14,0,0,0,0,8,16,16,3,0,0,0,2,16,13,14,15,1,0,0,4,16,7,5,16,6,0,0,0,10,15,16,15,2,0,8 +0,0,6,16,8,0,0,0,0,0,14,10,11,7,0,0,0,0,16,1,3,16,1,0,0,0,11,10,13,16,5,0,0,0,2,8,8,9,10,0,0,0,0,0,0,2,14,0,0,0,0,0,1,5,15,1,0,0,6,15,16,14,5,0,9 +0,1,9,15,2,0,0,0,0,10,14,14,7,0,0,0,0,5,3,10,8,0,0,0,0,0,0,14,4,0,0,0,0,0,2,16,0,0,0,0,0,0,10,12,1,0,0,0,0,2,16,16,16,14,8,0,0,0,7,9,14,16,7,0,2 +0,0,5,15,10,0,0,0,0,1,15,12,13,10,0,0,0,5,16,2,1,11,3,0,0,5,14,2,0,4,8,0,0,4,12,0,0,4,8,0,0,3,14,2,0,9,9,0,0,0,12,12,7,14,9,0,0,0,5,16,16,10,0,0,0 +0,0,0,8,11,0,0,0,0,0,5,14,7,0,0,0,0,0,12,10,0,0,0,0,0,4,16,3,0,0,0,0,0,5,15,12,10,3,0,0,0,4,16,12,8,15,5,0,0,0,10,15,6,10,15,0,0,0,0,7,12,12,8,0,6 +0,0,2,14,11,4,0,0,0,1,14,16,13,16,2,0,0,4,16,10,3,15,3,0,0,0,9,16,14,14,2,0,0,1,12,16,16,4,0,0,0,5,16,9,11,13,0,0,0,1,14,13,10,16,7,0,0,0,4,9,13,11,1,0,8 +0,0,9,14,10,1,0,0,0,3,13,2,8,9,0,0,0,4,12,0,4,16,1,0,0,1,14,8,11,16,6,0,0,0,1,6,3,10,7,0,0,0,0,0,0,8,8,0,0,3,8,3,1,10,8,0,0,1,9,11,16,12,1,0,9 +0,0,11,12,4,0,0,0,0,4,16,8,14,7,0,0,0,5,16,7,6,15,1,0,0,0,12,16,16,16,2,0,0,0,0,1,0,7,9,0,0,0,0,0,0,4,12,0,0,0,4,8,10,14,12,0,0,0,10,12,9,7,0,0,9 +0,0,10,16,16,16,5,0,0,0,2,4,10,16,5,0,0,0,0,0,9,12,0,0,0,0,4,14,16,13,6,0,0,0,6,15,16,15,7,0,0,0,0,14,10,0,0,0,0,0,6,16,7,0,0,0,0,0,11,15,0,0,0,0,7 +0,1,12,12,11,5,0,0,0,5,15,8,12,15,2,0,0,7,12,0,9,16,3,0,0,3,15,9,10,16,7,0,0,0,5,11,16,16,8,0,0,0,0,0,1,14,10,0,0,0,7,8,11,16,2,0,0,0,12,16,13,6,0,0,9 +0,1,10,16,13,4,0,0,0,9,13,4,11,15,0,0,0,0,0,0,4,16,1,0,0,0,0,3,13,11,0,0,0,0,0,7,16,14,2,0,0,0,0,0,4,15,8,0,0,0,5,4,5,14,9,0,0,0,15,16,14,9,0,0,3 +0,0,8,16,8,0,0,0,0,0,10,16,16,0,0,0,0,0,6,10,16,4,0,0,0,0,0,0,16,2,0,0,0,0,0,7,13,0,0,0,0,0,1,14,8,0,0,0,0,0,13,16,16,16,14,0,0,0,8,12,11,8,14,0,2 +0,0,3,12,6,0,0,0,0,0,16,16,16,8,0,0,0,0,14,14,7,15,0,0,0,0,5,16,15,8,0,0,0,0,0,13,16,0,0,0,0,2,14,10,13,7,0,0,0,3,15,7,9,15,0,0,0,0,5,12,11,5,0,0,8 +0,1,11,13,5,0,0,0,0,7,12,7,15,4,0,0,0,8,4,0,8,12,0,0,0,2,6,0,0,13,0,0,0,0,0,1,8,3,0,0,0,0,0,11,8,0,0,0,0,1,12,16,13,8,2,0,0,2,12,12,12,12,12,0,2 +0,3,13,16,13,1,0,0,0,11,11,8,14,8,0,0,0,5,1,0,5,14,0,0,0,0,0,0,5,11,0,0,0,0,0,1,14,3,0,0,0,0,0,9,11,0,0,0,0,0,11,16,15,12,7,0,0,1,8,8,9,13,7,0,2 +0,3,14,16,12,2,0,0,0,11,12,4,12,8,0,0,0,1,1,0,12,7,0,0,0,0,0,9,16,6,0,0,0,0,0,4,11,16,2,0,0,0,0,0,0,8,11,0,0,1,8,6,7,14,6,0,0,2,11,16,12,8,0,0,3 +0,0,8,10,16,13,0,0,0,4,16,16,16,16,1,0,0,6,16,12,8,15,5,0,0,6,16,1,0,12,8,0,0,5,13,0,1,14,6,0,0,4,14,2,12,16,2,0,0,2,16,16,16,8,0,0,0,0,9,12,7,0,0,0,0 +0,0,6,15,8,0,0,0,0,0,13,15,16,4,0,0,0,0,15,6,15,13,0,0,0,0,15,12,16,16,1,0,0,0,3,11,7,12,6,0,0,0,0,0,0,6,11,0,0,0,1,4,2,7,14,0,0,0,7,14,16,15,9,0,9 +0,2,16,14,2,0,0,0,0,6,14,12,14,0,0,0,0,7,12,8,15,0,0,0,0,2,5,8,12,0,0,0,0,0,0,15,1,0,0,0,0,0,5,13,0,0,0,0,0,1,13,14,9,8,2,0,0,2,14,15,12,16,10,0,2 +0,1,5,11,13,5,0,0,0,10,16,16,15,3,0,0,0,10,13,0,0,0,0,0,0,10,14,8,8,3,0,0,0,2,12,13,13,16,5,0,0,0,0,0,0,11,14,0,0,0,2,11,8,14,15,0,0,0,2,11,16,14,6,0,5 +0,1,10,12,12,2,0,0,0,7,15,8,12,8,0,0,0,0,2,0,13,7,0,0,0,0,0,7,16,4,0,0,0,0,0,5,13,13,1,0,0,0,0,0,1,11,9,0,0,0,8,4,6,14,8,0,0,2,12,16,14,9,1,0,3 +0,0,14,14,13,15,5,0,0,0,16,14,12,6,0,0,0,4,16,11,8,1,0,0,0,3,16,16,16,9,0,0,0,0,0,0,5,16,2,0,0,0,0,0,3,16,4,0,0,0,6,9,15,14,0,0,0,0,12,16,10,2,0,0,5 +0,0,0,1,16,14,1,0,0,0,0,10,16,14,0,0,0,3,9,16,16,3,0,0,0,5,16,16,16,4,0,0,0,0,0,14,16,4,0,0,0,0,0,9,16,8,0,0,0,0,0,5,16,12,0,0,0,0,0,2,15,14,2,0,1 +0,1,9,12,12,2,0,0,0,7,12,4,11,10,0,0,0,0,1,0,11,8,0,0,0,0,2,15,16,2,0,0,0,0,2,8,12,12,0,0,0,0,0,0,1,16,6,0,0,1,16,3,6,16,6,0,0,1,11,15,12,4,0,0,3 +0,0,2,12,16,10,0,0,0,0,11,12,11,16,4,0,0,0,1,1,1,16,4,0,0,0,0,0,8,14,2,0,0,0,0,4,15,5,0,0,0,0,8,16,5,0,0,0,0,5,16,16,14,9,1,0,0,0,3,8,10,15,3,0,2 +0,0,6,15,12,10,8,0,0,0,11,16,16,16,7,0,0,1,14,11,12,6,0,0,0,4,16,16,16,13,0,0,0,2,11,8,10,16,2,0,0,0,0,0,1,16,1,0,0,0,0,11,13,15,0,0,0,0,4,16,12,1,0,0,5 +0,0,7,12,8,0,0,0,0,0,15,16,15,2,0,0,0,0,11,16,16,2,0,0,0,0,12,16,16,4,0,0,0,0,12,16,16,7,0,0,0,0,9,16,16,11,0,0,0,0,11,16,16,13,0,0,0,0,2,8,12,6,0,0,1 +0,0,12,16,12,2,0,0,0,12,15,9,15,12,0,0,0,12,7,0,8,16,0,0,0,3,1,0,8,14,0,0,0,0,0,4,16,6,0,0,0,0,1,13,15,1,0,0,0,0,11,16,13,12,8,0,0,0,11,12,12,15,14,0,2 +0,0,14,14,16,15,0,0,0,4,16,13,12,8,0,0,0,6,13,0,0,0,0,0,0,5,16,12,4,0,0,0,0,3,14,16,14,0,0,0,0,0,0,0,12,12,0,0,0,0,10,8,15,10,0,0,0,0,15,16,11,1,0,0,5 +0,0,0,2,13,10,0,0,0,0,0,6,16,4,0,0,0,0,0,14,12,0,0,0,0,0,11,15,1,6,1,0,0,6,16,5,6,16,6,0,4,15,16,12,14,16,2,0,1,8,12,13,16,10,0,0,0,0,0,4,15,4,0,0,4 +0,0,6,15,16,14,0,0,0,2,11,5,7,16,2,0,0,0,0,0,4,16,0,0,0,0,8,13,16,6,0,0,0,0,5,13,16,14,3,0,0,0,0,14,8,14,4,0,0,0,6,16,2,0,0,0,0,0,7,9,1,0,0,0,7 +0,0,6,16,16,16,12,0,0,0,6,12,11,15,16,1,0,0,0,0,0,15,12,0,0,0,1,11,12,16,3,0,0,0,3,16,16,16,4,0,0,0,0,9,16,13,3,0,0,0,1,16,9,0,0,0,0,0,7,15,4,0,0,0,7 +0,0,9,15,7,1,0,0,0,5,16,7,12,14,0,0,0,10,10,0,8,16,0,0,0,6,12,0,5,16,4,0,0,0,12,13,15,16,3,0,0,0,0,0,1,13,10,0,0,0,2,4,5,13,11,0,0,0,8,16,12,8,1,0,9 +0,1,7,12,10,1,0,0,0,1,14,16,16,12,0,0,0,0,12,16,16,14,0,0,0,0,7,16,16,14,1,0,0,0,5,16,16,13,0,0,0,0,7,16,16,14,0,0,0,1,14,16,16,15,2,0,0,1,8,12,10,8,0,0,1 +0,0,4,14,13,4,0,0,0,0,13,14,6,15,0,0,0,0,4,15,8,16,0,0,0,0,0,3,14,14,0,0,0,0,0,9,16,12,0,0,0,0,8,15,4,15,3,0,0,0,15,8,4,14,7,0,0,0,4,13,13,12,2,0,8 +0,0,6,13,12,4,0,0,0,2,16,9,12,13,0,0,0,0,9,0,13,10,0,0,0,0,0,6,16,4,0,0,0,0,0,0,8,14,1,0,0,0,0,0,0,14,9,0,0,0,4,9,5,10,13,0,0,0,7,12,13,12,5,0,3 +0,1,15,16,14,2,0,0,0,1,11,12,16,8,0,0,0,0,0,5,16,4,0,0,0,0,2,15,16,3,0,0,0,0,4,16,16,14,4,0,0,0,5,16,12,16,7,0,0,0,11,11,0,0,0,0,0,0,16,6,0,0,0,0,7 +0,0,11,16,13,12,3,0,0,3,16,11,8,8,1,0,0,5,16,9,9,1,0,0,0,8,16,16,16,6,0,0,0,6,11,3,9,11,0,0,0,0,0,0,4,12,0,0,0,0,10,14,11,16,0,0,0,0,9,12,12,6,0,0,5 +0,0,4,6,14,14,1,0,0,0,16,16,16,16,5,0,0,3,16,7,3,12,8,0,0,4,16,0,0,9,8,0,0,4,16,0,0,8,8,0,0,1,15,1,1,13,7,0,0,0,14,13,15,12,0,0,0,0,6,14,14,4,0,0,0 +0,0,5,15,12,2,0,0,0,0,7,16,16,1,0,0,0,0,10,16,16,6,0,0,0,0,11,16,16,2,0,0,0,0,7,16,16,3,0,0,0,0,7,16,16,7,0,0,0,0,8,16,16,8,0,0,0,0,12,16,13,3,0,0,1 +0,1,13,14,6,0,0,0,0,3,16,9,15,4,0,0,0,2,16,8,15,4,0,0,0,0,0,0,16,4,0,0,0,0,0,6,12,0,0,0,0,0,0,14,6,0,0,0,0,0,14,16,9,6,4,0,0,2,11,12,12,12,12,0,2 +0,0,0,8,15,0,0,0,0,0,0,16,10,0,0,0,0,0,4,16,4,0,0,0,0,0,12,12,0,0,0,0,0,9,16,5,7,14,0,0,3,16,16,13,16,13,0,0,1,7,9,15,16,7,0,0,0,0,0,12,14,2,0,0,4 +0,0,0,5,16,2,0,0,0,0,0,12,14,0,0,0,0,0,4,16,5,0,0,0,0,1,12,12,0,4,6,0,0,8,16,3,2,16,10,0,2,16,16,16,15,16,2,0,1,4,7,11,16,11,0,0,0,0,0,7,16,4,0,0,4 +0,0,8,15,11,4,0,0,0,7,15,13,14,16,2,0,0,8,14,0,10,16,4,0,0,0,14,15,16,12,1,0,0,0,9,16,13,1,0,0,0,0,14,16,11,0,0,0,0,0,15,16,16,2,0,0,0,0,8,16,15,3,0,0,8 +0,0,9,12,9,0,0,0,0,0,16,16,16,4,0,0,0,0,13,16,16,4,0,0,0,0,12,16,16,2,0,0,0,0,12,16,16,4,0,0,0,0,13,16,16,4,0,0,0,0,16,16,16,9,0,0,0,0,4,8,10,4,0,0,1 +0,0,7,12,14,7,0,0,0,0,12,11,12,16,0,0,0,0,0,2,4,12,0,0,0,0,0,14,15,14,1,0,0,0,0,12,15,15,6,0,0,0,0,12,8,1,1,0,0,0,5,16,1,0,0,0,0,0,7,7,0,0,0,0,7 +0,0,3,14,13,2,0,0,0,0,16,14,14,11,0,0,0,7,15,2,1,16,1,0,0,5,12,0,0,12,8,0,0,8,10,0,0,11,8,0,0,1,15,0,0,8,11,0,0,0,11,10,7,15,6,0,0,0,3,12,16,11,1,0,0 +0,1,14,13,9,2,0,0,0,2,16,16,16,14,2,0,0,0,14,10,3,16,3,0,0,0,5,15,14,13,0,0,0,0,1,15,15,3,0,0,0,0,8,16,16,2,0,0,0,0,16,13,16,8,0,0,0,0,12,12,9,2,0,0,8 +0,0,9,16,4,0,0,0,0,0,14,14,13,7,0,0,0,4,15,12,16,16,5,0,0,4,12,7,8,14,8,0,0,7,12,0,0,12,8,0,0,5,13,0,0,13,8,0,0,2,16,12,15,16,5,0,0,0,9,14,12,8,0,0,0 +0,0,8,14,10,2,0,0,0,0,15,14,11,13,0,0,0,0,0,0,5,16,1,0,0,0,0,0,11,14,0,0,0,0,0,0,3,15,3,0,0,0,0,0,0,9,13,0,0,0,3,9,8,15,11,0,0,0,6,16,14,7,1,0,3 +0,0,0,8,16,1,0,0,0,0,0,14,16,2,0,0,0,0,3,15,13,0,0,0,0,0,9,16,3,3,1,0,0,1,14,11,4,16,7,0,1,12,16,14,14,16,2,0,1,11,12,14,16,11,0,0,0,0,0,9,16,5,0,0,4 +0,0,0,2,13,7,0,0,0,0,0,9,15,3,0,0,0,0,2,14,10,0,0,0,0,0,13,13,2,6,0,0,0,8,15,2,14,14,0,0,4,15,16,16,16,12,0,0,1,4,8,10,16,7,0,0,0,0,0,5,16,6,0,0,4 +0,0,0,4,15,0,0,0,0,0,0,11,13,0,0,0,0,0,1,15,7,0,0,0,0,0,11,11,0,0,0,0,0,2,16,2,11,8,0,0,2,13,14,10,16,8,0,0,2,6,12,15,16,4,0,0,0,0,0,5,14,1,0,0,4 +0,0,8,16,15,11,0,0,0,0,5,7,11,16,3,0,0,0,0,0,9,13,0,0,0,0,7,15,15,5,0,0,0,0,8,14,16,14,4,0,0,0,0,10,9,4,1,0,0,0,4,14,1,0,0,0,0,0,10,7,0,0,0,0,7 +0,0,0,0,10,13,2,0,0,0,0,4,16,16,2,0,0,0,0,10,16,16,2,0,0,1,13,16,16,16,4,0,0,5,12,10,16,16,0,0,0,0,0,0,15,16,1,0,0,0,0,0,14,16,5,0,0,0,0,0,11,16,6,0,1 +0,0,4,12,10,3,0,0,0,0,9,16,16,5,0,0,0,0,10,16,16,3,0,0,0,0,12,16,16,3,0,0,0,0,4,16,15,2,0,0,0,0,7,16,16,4,0,0,0,0,8,16,16,6,0,0,0,0,6,12,9,4,0,0,1 +0,0,5,14,0,0,0,0,0,0,12,14,0,0,0,0,0,0,15,12,0,0,0,0,0,1,16,9,5,2,0,0,0,5,16,16,16,15,2,0,0,7,16,15,10,16,9,0,0,1,15,13,13,16,5,0,0,0,5,13,13,9,0,0,6 +0,0,8,15,12,2,0,0,0,4,16,13,11,12,1,0,0,9,14,1,13,15,2,0,0,9,15,0,14,15,0,0,0,2,15,16,16,16,0,0,0,0,1,4,4,16,4,0,0,0,4,8,4,16,5,0,0,0,5,14,16,15,5,0,9 +0,1,12,15,16,9,0,0,0,2,16,16,12,9,0,0,0,6,14,1,0,0,0,0,0,7,15,5,1,0,0,0,0,7,16,16,12,0,0,0,0,0,4,4,15,4,0,0,0,0,8,5,16,6,0,0,0,0,13,16,12,1,0,0,5 +0,0,15,14,14,15,10,0,0,0,16,8,11,10,6,0,0,5,14,0,0,0,0,0,0,8,16,16,16,4,0,0,0,4,14,9,12,12,0,0,0,0,0,0,5,15,0,0,0,0,11,4,11,9,0,0,0,0,14,16,14,1,0,0,5 +0,2,14,12,16,13,1,0,0,4,16,15,4,10,7,0,0,2,16,5,2,14,2,0,0,0,9,12,14,6,0,0,0,0,2,16,11,0,0,0,0,0,11,13,12,0,0,0,0,2,13,0,15,0,0,0,0,2,14,15,10,0,0,0,8 +0,0,0,2,16,6,0,0,0,0,0,6,16,2,0,0,0,0,0,13,13,0,0,0,0,0,6,16,4,9,4,0,0,3,15,10,4,16,6,0,2,15,16,16,14,16,5,0,1,8,13,16,16,15,0,0,0,0,0,2,16,9,0,0,4 +0,0,0,7,15,6,0,0,0,0,10,13,14,13,0,0,0,2,13,0,12,6,0,0,0,0,2,3,12,1,0,0,0,0,1,15,3,0,0,0,0,2,13,8,0,0,0,0,0,10,16,14,12,8,0,0,0,2,4,7,9,14,0,0,2 +0,1,8,15,16,11,1,0,0,5,16,13,10,13,7,0,0,4,13,0,0,9,8,0,0,1,13,8,6,15,4,0,0,0,2,15,16,9,0,0,0,0,8,14,15,8,0,0,0,0,14,8,9,14,0,0,0,0,11,16,14,8,0,0,8 +0,0,6,9,16,10,1,0,0,2,15,15,9,15,8,0,0,6,16,1,0,12,8,0,0,0,14,13,14,12,2,0,0,0,4,16,15,1,0,0,0,0,11,11,16,3,0,0,0,2,16,8,13,14,0,0,0,0,8,16,12,10,0,0,8 +0,0,9,16,13,2,0,0,0,3,16,9,12,12,0,0,0,4,16,0,0,16,0,0,0,1,8,0,2,16,0,0,0,0,0,0,5,13,0,0,0,0,0,1,11,9,0,0,0,0,4,11,16,10,7,0,0,0,9,12,8,9,13,0,2 +0,0,5,14,16,14,1,0,0,2,15,4,7,16,1,0,0,0,5,0,12,12,0,0,0,0,0,6,16,3,0,0,0,0,0,0,7,15,1,0,0,0,0,0,2,16,2,0,0,0,1,4,8,16,4,0,0,0,8,15,13,8,0,0,3 +0,0,7,15,13,3,0,0,0,0,16,16,16,16,1,0,0,5,15,7,7,16,5,0,0,8,12,0,0,15,5,0,0,6,16,0,0,13,7,0,0,5,16,1,2,16,4,0,0,3,16,9,14,15,0,0,0,0,9,13,12,3,0,0,0 +0,0,0,7,12,7,0,0,0,0,13,13,13,12,0,0,0,3,12,0,9,9,0,0,0,0,0,7,16,9,0,0,0,0,0,2,8,16,0,0,0,0,0,0,1,15,3,0,0,0,0,12,9,15,0,0,0,0,1,12,15,5,0,0,3 +0,0,7,14,0,0,0,0,0,0,12,15,0,0,0,0,0,0,16,9,0,0,0,0,0,1,15,7,2,0,0,0,0,1,16,16,15,10,2,0,0,3,16,12,4,14,12,0,0,0,12,10,1,14,9,0,0,0,5,16,16,11,3,0,6 +0,0,9,15,16,10,0,0,0,0,16,16,16,16,4,0,0,0,14,11,14,16,2,0,0,0,7,16,16,7,0,0,0,0,10,16,11,0,0,0,0,2,16,15,14,1,0,0,0,4,16,14,16,4,0,0,0,0,9,15,13,1,0,0,8 +0,1,14,16,16,12,1,0,0,0,13,8,4,4,0,0,0,0,12,4,0,0,0,0,0,0,8,16,16,9,0,0,0,0,4,7,5,14,0,0,0,0,0,0,0,13,0,0,0,0,5,0,3,15,0,0,0,2,14,16,14,7,0,0,5 +0,0,0,13,7,0,0,0,0,5,14,16,16,8,0,0,0,12,10,0,6,16,0,0,0,2,14,12,5,16,3,0,0,0,0,11,16,11,0,0,0,0,2,15,9,16,5,0,0,0,10,14,4,13,12,0,0,0,1,9,14,16,11,0,8 +0,0,7,12,14,6,0,0,0,7,16,14,14,6,0,0,0,5,16,10,3,0,0,0,0,6,16,16,16,6,0,0,0,3,7,4,10,14,0,0,0,0,0,0,10,15,1,0,0,0,2,15,16,8,0,0,0,0,5,16,9,0,0,0,5 +0,0,5,15,2,0,0,0,0,0,15,16,16,13,2,0,0,4,16,16,14,16,8,0,0,7,12,0,0,8,8,0,0,4,12,0,0,8,8,0,0,4,15,0,0,9,7,0,0,1,15,5,7,15,4,0,0,0,5,13,12,7,0,0,0 +0,0,2,12,14,5,0,0,0,2,15,6,8,14,6,0,0,5,9,0,5,16,4,0,0,5,9,4,15,12,0,0,0,0,14,13,16,5,0,0,0,0,0,4,12,0,0,0,0,0,0,11,5,0,0,0,0,0,2,15,4,0,0,0,9 +0,0,0,12,15,0,0,0,0,0,5,16,9,0,0,0,0,3,14,10,0,1,0,0,0,10,14,1,4,15,9,0,0,11,15,15,16,16,4,0,0,1,8,10,16,12,0,0,0,0,0,8,16,3,0,0,0,0,0,11,16,0,0,0,4 +0,6,16,16,13,3,0,0,0,12,16,12,15,16,5,0,0,10,14,1,0,4,1,0,0,2,15,8,0,0,0,0,0,0,7,16,2,0,0,0,0,0,2,15,9,0,0,0,0,1,5,15,10,0,0,0,0,7,16,15,1,0,0,0,5 +0,0,11,16,9,0,0,0,0,9,12,6,14,10,0,0,0,11,5,0,13,13,0,0,0,1,11,14,12,15,6,0,0,0,0,0,0,12,8,0,0,0,0,0,0,10,10,0,0,0,1,0,4,13,6,0,0,0,9,14,16,10,0,0,9 +0,0,12,16,16,10,0,0,0,0,15,13,13,16,1,0,0,0,3,14,16,14,1,0,0,0,0,15,14,2,0,0,0,0,0,7,16,5,0,0,0,0,0,0,9,16,2,0,0,0,6,5,11,16,8,0,0,0,10,16,16,15,3,0,3 +0,0,0,4,13,13,0,0,0,0,3,16,14,3,0,0,0,1,15,11,1,1,0,0,0,10,15,0,0,11,9,0,0,7,15,9,9,16,6,0,0,0,4,8,16,15,1,0,0,0,0,2,16,7,0,0,0,0,0,3,16,1,0,0,4 +0,2,13,16,15,7,0,0,0,10,16,15,6,14,3,0,0,8,16,1,0,0,0,0,0,3,14,8,0,0,0,0,0,0,4,15,4,0,0,0,0,0,0,5,16,0,0,0,0,0,5,9,16,3,0,0,0,1,15,16,10,0,0,0,5 +0,0,1,12,9,0,0,0,0,0,7,16,5,0,0,0,0,0,10,9,0,0,0,0,0,0,15,11,11,1,0,0,0,0,13,16,14,14,1,0,0,0,10,13,0,12,5,0,0,0,4,13,2,16,3,0,0,0,0,11,16,8,0,0,6 +0,0,3,16,8,1,0,0,0,0,4,16,16,2,0,0,0,7,16,16,16,0,0,0,0,9,10,15,14,0,0,0,0,0,1,16,13,0,0,0,0,0,0,16,11,0,0,0,0,0,3,16,13,0,0,0,0,0,1,13,15,0,0,0,1 +0,0,8,16,16,12,0,0,0,3,16,9,11,16,3,0,0,4,7,0,8,14,1,0,0,0,5,8,14,12,1,0,0,5,16,16,16,16,10,0,0,6,5,9,11,0,0,0,0,0,4,16,3,0,0,0,0,0,11,8,0,0,0,0,7 +0,2,12,15,16,14,2,0,0,6,16,15,11,12,5,0,0,4,16,2,0,0,0,0,0,1,13,5,0,0,0,0,0,0,11,8,0,0,0,0,0,0,5,16,0,0,0,0,0,2,12,14,0,0,0,0,0,2,16,10,0,0,0,0,5 +0,1,6,12,15,4,0,0,0,6,16,12,12,16,3,0,0,12,8,0,5,16,2,0,0,1,2,0,12,13,0,0,0,0,0,6,16,2,0,0,0,0,0,16,9,0,0,0,0,0,7,16,8,8,7,0,0,0,3,11,12,12,7,0,2 +0,0,7,16,6,5,0,0,0,0,16,12,9,14,1,0,0,4,15,0,0,16,2,0,0,4,14,0,0,15,3,0,0,5,12,0,3,14,0,0,0,3,16,0,6,10,0,0,0,1,15,6,16,3,0,0,0,0,8,15,8,0,0,0,0 +0,0,0,10,12,0,0,0,0,0,3,16,9,0,0,0,0,0,8,12,0,0,0,0,0,0,10,10,1,0,0,0,0,0,12,16,16,15,3,0,0,0,10,15,4,8,16,1,0,0,6,14,0,9,15,0,0,0,1,10,16,15,4,0,6 +0,3,15,16,16,15,1,0,0,14,13,7,8,16,8,0,0,2,1,0,8,16,7,0,0,0,0,7,16,10,0,0,0,0,0,14,13,0,0,0,0,0,0,10,14,2,0,0,0,2,8,5,16,8,0,0,0,2,16,16,13,3,0,0,3 +0,1,10,16,16,13,0,0,0,6,15,6,9,16,3,0,0,3,5,0,8,16,3,0,0,0,1,10,16,14,3,0,0,0,6,16,16,9,8,0,0,0,0,15,9,0,0,0,0,0,6,15,1,0,0,0,0,0,14,5,0,0,0,0,7 +0,3,15,14,5,1,0,0,0,12,16,16,16,16,5,0,0,9,16,15,7,8,3,0,0,1,13,13,0,0,0,0,0,0,6,16,5,0,0,0,0,0,1,15,11,0,0,0,0,0,7,16,10,0,0,0,0,2,16,13,1,0,0,0,5 +0,0,0,13,3,0,0,0,0,0,5,16,7,0,0,0,0,0,9,14,0,0,0,0,0,0,10,14,8,3,0,0,0,0,15,15,12,14,6,0,0,0,15,9,0,4,15,1,0,0,7,14,2,9,16,0,0,0,1,11,15,14,6,0,6 +0,0,0,10,11,0,0,0,0,0,2,16,7,0,0,0,0,0,7,12,0,0,0,0,0,0,10,10,0,0,0,0,0,0,10,16,16,9,0,0,0,0,10,14,2,14,3,0,0,0,6,14,1,14,5,0,0,0,0,7,16,15,1,0,6 +0,0,8,16,15,5,0,0,0,0,14,16,11,15,0,0,0,0,16,2,0,8,4,0,0,2,12,0,0,8,6,0,0,3,11,0,0,11,5,0,0,3,12,0,2,15,1,0,0,1,15,2,14,8,0,0,0,0,7,15,12,0,0,0,0 +0,0,1,16,14,0,0,0,0,0,3,16,16,1,0,0,0,0,1,15,16,4,0,0,0,0,0,14,16,6,0,0,0,0,2,16,16,2,0,0,0,0,1,16,16,1,0,0,0,0,3,16,16,0,0,0,0,0,2,15,16,4,0,0,1 +0,0,6,15,13,3,0,0,0,5,15,6,13,15,3,0,0,10,11,0,13,16,5,0,0,4,15,14,16,15,1,0,0,0,0,1,12,9,0,0,0,0,0,8,15,1,0,0,0,0,1,14,8,0,0,0,0,0,6,15,0,0,0,0,9 +0,0,6,15,11,2,0,0,0,1,16,10,14,16,3,0,0,4,11,0,14,16,3,0,0,3,15,16,12,15,5,0,0,0,1,2,0,12,7,0,0,0,0,0,1,13,6,0,0,0,1,5,13,12,0,0,0,0,8,12,6,0,0,0,9 +0,4,16,16,12,1,0,0,0,8,14,5,15,6,0,0,0,3,5,0,13,8,0,0,0,0,0,3,15,7,0,0,0,0,1,13,13,0,0,0,0,0,11,15,2,0,0,0,0,6,16,9,4,4,1,0,0,4,15,16,16,16,15,1,2 +0,2,14,16,13,1,0,0,0,10,14,10,15,8,0,0,0,5,2,0,12,12,0,0,0,0,0,4,16,6,0,0,0,0,1,15,10,0,0,0,0,0,11,14,1,0,0,0,0,4,16,8,1,0,0,0,0,2,15,16,16,16,8,0,2 +0,1,9,12,15,14,4,0,0,6,14,4,4,14,8,0,0,3,8,0,7,15,2,0,0,0,0,6,14,2,0,0,0,0,0,8,11,0,0,0,0,0,0,1,15,2,0,0,0,0,5,0,12,9,0,0,0,0,11,16,14,3,0,0,3 +0,0,14,11,7,1,0,0,0,2,16,16,16,14,4,0,0,0,15,11,0,6,3,0,0,0,7,14,0,0,0,0,0,0,2,16,4,0,0,0,0,0,1,12,12,0,0,0,0,4,12,14,11,0,0,0,0,0,14,16,6,0,0,0,5 +0,0,15,2,0,0,0,0,0,5,16,15,11,5,0,0,0,7,16,15,12,13,4,0,0,1,15,7,0,0,0,0,0,0,5,16,5,0,0,0,0,0,0,10,14,0,0,0,0,0,1,12,15,0,0,0,0,0,11,16,6,0,0,0,5 +0,0,8,9,15,11,0,0,0,9,15,15,4,15,2,0,0,8,9,0,8,12,0,0,0,2,14,11,14,2,0,0,0,0,11,16,7,0,0,0,0,0,14,2,13,6,0,0,0,2,12,1,8,12,0,0,0,0,8,16,14,5,0,0,8 +0,0,6,15,8,0,0,0,0,2,15,7,16,5,0,0,0,6,11,1,10,14,2,0,0,7,7,0,0,10,6,0,0,7,5,0,0,8,8,0,0,4,10,0,0,10,7,0,0,0,12,8,4,15,2,0,0,0,2,12,12,8,0,0,0 +0,4,15,16,16,12,1,0,0,15,13,5,5,16,8,0,0,7,3,0,10,16,4,0,0,0,1,11,16,10,0,0,0,0,7,16,10,0,0,0,0,0,1,7,16,6,0,0,0,3,8,6,16,10,0,0,0,5,16,16,12,1,0,0,3 +0,0,8,16,16,15,0,0,0,3,16,9,8,16,4,0,0,9,8,0,6,16,2,0,0,1,8,13,16,16,5,0,0,0,15,12,16,13,8,0,0,0,0,9,15,1,0,0,0,0,2,16,4,0,0,0,0,0,11,11,0,0,0,0,7 +0,0,0,5,16,5,0,0,0,0,6,15,16,9,0,0,3,16,16,16,16,3,0,0,0,9,11,13,16,3,0,0,0,0,0,14,14,0,0,0,0,0,0,14,13,0,0,0,0,0,0,11,14,0,0,0,0,0,0,4,15,4,0,0,1 +0,0,6,15,16,5,0,0,0,5,16,13,15,11,0,0,0,10,12,1,16,9,0,0,0,3,3,9,16,2,0,0,0,0,4,16,7,0,0,0,0,0,11,13,0,0,0,0,0,0,12,14,8,8,2,0,0,0,5,16,16,16,11,0,2 +0,0,0,13,11,0,0,0,0,0,6,16,6,0,0,0,0,0,15,13,0,5,1,0,0,6,16,6,4,16,9,0,0,11,16,5,13,16,2,0,0,1,11,16,16,9,0,0,0,0,0,11,15,2,0,0,0,0,0,12,13,0,0,0,4 +0,0,9,13,15,10,0,0,0,2,15,5,6,16,6,0,0,0,3,0,2,16,6,0,0,0,0,0,11,13,0,0,0,0,0,8,14,1,0,0,0,0,5,15,3,0,0,0,0,0,12,6,0,0,0,0,0,0,8,16,16,12,0,0,2 +0,0,6,13,16,11,1,0,0,4,15,6,9,16,5,0,0,6,9,0,7,16,6,0,0,3,15,12,15,15,8,0,0,0,2,5,2,10,8,0,0,0,0,0,0,13,7,0,0,2,9,4,8,15,2,0,0,0,10,13,12,2,0,0,9 +0,0,0,1,15,9,0,0,0,0,0,9,16,11,0,0,0,4,8,16,16,6,0,0,0,15,16,16,16,4,0,0,0,5,9,4,16,4,0,0,0,0,0,5,16,2,0,0,0,0,0,4,16,5,0,0,0,0,0,2,14,9,0,0,1 +0,0,6,13,6,0,0,0,0,0,10,16,14,6,0,0,0,0,14,9,3,16,1,0,0,0,15,2,0,15,5,0,0,0,14,2,0,15,1,0,0,0,12,4,5,15,0,0,0,0,10,9,15,8,0,0,0,0,5,14,10,0,0,0,0 +0,0,9,13,10,1,0,0,0,7,14,4,15,8,0,0,0,10,8,0,10,14,0,0,0,4,14,12,14,15,4,0,0,0,2,4,2,12,6,0,0,0,0,0,1,15,5,0,0,0,0,4,10,15,1,0,0,0,9,12,9,2,0,0,9 +0,0,0,5,16,4,0,0,0,0,0,13,15,1,0,0,0,0,10,16,3,2,0,0,0,5,16,9,0,13,10,0,0,12,16,6,6,16,6,0,0,1,15,16,16,16,1,0,0,0,0,4,16,11,0,0,0,0,0,4,16,11,0,0,4 +0,0,4,13,16,8,0,0,0,5,16,8,10,12,0,0,0,8,9,0,8,12,0,0,0,0,0,2,11,10,2,0,0,0,3,16,16,16,9,0,0,0,0,9,14,0,0,0,0,0,0,13,7,0,0,0,0,0,5,15,2,0,0,0,7 +0,0,2,13,3,0,0,0,0,0,11,15,4,0,0,0,0,0,11,8,0,0,0,0,0,0,14,5,0,0,0,0,0,0,16,13,16,14,4,0,0,0,15,15,9,13,12,0,0,0,13,11,2,15,9,0,0,0,2,14,15,11,1,0,6 +0,0,0,8,16,2,0,0,0,0,8,16,9,1,0,0,0,0,13,10,0,0,0,0,0,0,15,15,8,1,0,0,0,1,16,11,12,10,0,0,0,0,15,5,1,15,1,0,0,0,8,11,3,16,0,0,0,0,1,10,16,7,0,0,6 +0,0,0,11,13,0,0,0,0,0,5,15,13,0,0,0,0,3,16,16,10,0,0,0,0,0,7,16,10,0,0,0,0,0,0,15,10,0,0,0,0,0,0,15,8,0,0,0,0,0,0,16,6,0,0,0,0,0,0,12,11,0,0,0,1 +0,1,11,16,16,5,0,0,0,6,16,6,14,13,0,0,0,4,5,0,14,12,0,0,0,0,7,9,16,10,0,0,0,0,15,16,16,16,8,0,0,0,1,14,11,4,0,0,0,0,10,16,1,0,0,0,0,0,15,9,0,0,0,0,7 +0,0,8,16,16,10,0,0,0,1,16,14,11,16,1,0,0,0,12,8,13,15,1,0,0,0,1,7,16,7,0,0,0,0,0,4,16,6,0,0,0,0,0,0,11,14,0,0,0,0,0,4,13,16,2,0,0,0,10,16,16,12,0,0,3 +0,2,12,15,16,10,1,0,0,3,16,5,10,16,3,0,0,0,0,5,15,7,0,0,0,0,0,14,8,0,0,0,0,0,0,15,4,0,0,0,0,0,0,4,14,2,0,0,0,1,2,2,15,6,0,0,0,2,12,15,12,1,0,0,3 +0,0,6,15,16,10,0,0,0,0,14,11,10,15,0,0,0,0,2,0,9,14,0,0,0,0,5,9,15,14,2,0,0,11,16,16,16,16,11,0,0,1,3,11,13,1,0,0,0,0,5,16,3,0,0,0,0,0,8,12,0,0,0,0,7 +0,0,0,8,12,1,0,0,0,0,6,16,13,0,0,0,0,0,10,14,1,0,0,0,0,0,14,8,0,0,0,0,0,0,15,12,8,8,2,0,0,0,11,16,13,14,13,0,0,0,8,16,5,10,15,0,0,0,0,8,16,13,7,0,6 +0,0,7,15,16,16,8,0,0,4,16,12,5,13,13,0,0,1,5,0,0,16,10,0,0,0,0,1,8,16,6,0,0,0,9,15,16,16,11,0,0,2,12,14,15,4,1,0,0,0,1,15,6,0,0,0,0,0,10,11,0,0,0,0,7 +0,0,6,15,16,13,1,0,0,3,16,10,11,16,3,0,0,5,8,0,4,16,4,0,0,0,0,2,9,16,1,0,0,0,9,16,16,15,6,0,0,0,10,10,16,10,3,0,0,0,0,11,12,0,0,0,0,0,7,13,1,0,0,0,7 +0,0,9,13,11,9,1,0,0,0,11,10,10,14,4,0,0,0,5,14,7,13,0,0,0,0,0,10,15,3,0,0,0,0,8,16,12,0,0,0,0,0,14,3,12,4,0,0,0,0,13,1,12,7,0,0,0,0,10,16,13,1,0,0,8 +0,0,4,12,15,2,0,0,0,0,12,14,14,15,2,0,0,2,15,1,11,16,4,0,0,5,11,0,0,11,7,0,0,5,11,0,0,13,3,0,0,0,15,0,1,15,0,0,0,0,14,7,13,11,0,0,0,0,2,15,12,3,0,0,0 +0,0,0,14,13,1,0,0,0,0,2,16,16,2,0,0,0,0,4,16,15,0,0,0,0,0,3,16,13,0,0,0,0,0,7,16,12,0,0,0,0,0,7,16,12,0,0,0,0,0,6,16,14,0,0,0,0,0,1,11,16,4,0,0,1 +0,0,8,11,16,12,1,0,0,2,16,16,9,16,3,0,0,0,13,11,8,15,2,0,0,0,5,16,16,6,0,0,0,0,9,16,12,0,0,0,0,2,16,12,16,0,0,0,0,3,16,4,16,2,0,0,0,0,12,16,12,0,0,0,8 +0,0,0,12,11,0,0,0,0,0,4,16,5,0,0,0,0,0,9,8,0,0,0,0,0,0,13,7,0,0,0,0,0,0,14,11,12,5,0,0,0,0,12,16,10,15,4,0,0,0,6,13,2,11,9,0,0,0,0,10,13,15,3,0,6 +0,0,1,15,11,0,0,0,0,0,9,16,16,1,0,0,0,8,16,16,15,0,0,0,1,16,16,16,14,0,0,0,0,4,6,15,15,0,0,0,0,0,2,16,12,0,0,0,0,0,1,16,13,0,0,0,0,0,0,14,12,0,0,0,1 +0,0,0,9,11,0,0,0,0,0,2,16,9,0,0,0,0,0,6,14,1,0,0,0,0,0,9,11,0,0,0,0,0,0,11,16,16,9,0,0,0,0,10,15,4,10,8,0,0,0,4,14,3,11,12,0,0,0,0,8,16,12,4,0,6 +0,0,15,16,16,12,1,0,0,5,16,13,11,16,4,0,0,1,16,2,3,15,4,0,0,0,11,11,15,11,0,0,0,0,4,16,14,1,0,0,0,0,13,16,8,0,0,0,0,2,16,14,11,0,0,0,0,0,12,16,7,0,0,0,8 +0,0,2,13,11,5,0,0,0,2,15,10,15,16,0,0,0,4,13,1,0,11,4,0,0,6,6,0,0,6,6,0,0,5,8,0,0,6,7,0,0,0,12,0,0,11,6,0,0,0,10,8,2,16,1,0,0,0,2,11,13,6,0,0,0 +0,0,4,16,9,0,0,0,0,0,15,16,16,9,0,0,0,3,16,15,5,13,3,0,0,5,8,4,0,5,7,0,0,5,6,0,0,7,7,0,0,2,11,0,0,11,6,0,0,0,13,1,6,15,0,0,0,0,5,15,13,3,0,0,0 +0,0,9,13,16,12,1,0,0,5,16,14,16,16,4,0,0,4,16,0,13,13,1,0,0,2,14,14,15,3,0,0,0,0,8,16,4,0,0,0,0,0,15,16,5,0,0,0,0,1,16,16,7,0,0,0,0,0,12,16,6,0,0,0,8 +0,0,8,3,11,11,2,0,0,2,16,6,7,12,8,0,0,1,16,1,7,14,1,0,0,0,11,14,12,1,0,0,0,0,11,16,1,0,0,0,0,3,12,11,8,0,0,0,0,5,10,5,12,0,0,0,0,1,10,16,9,0,0,0,8 +0,0,1,14,7,0,0,0,0,0,10,16,4,0,0,0,0,1,15,7,0,0,0,0,0,2,16,3,0,0,0,0,0,3,16,16,16,14,3,0,0,1,15,16,0,4,13,1,0,0,10,15,3,7,16,5,0,0,1,11,15,15,9,0,6 +0,0,6,14,0,0,12,6,0,0,15,13,0,6,16,6,0,2,16,12,4,16,12,0,0,1,14,16,16,16,7,0,0,0,1,8,16,7,0,0,0,0,0,12,16,2,0,0,0,0,3,16,6,0,0,0,0,0,7,15,0,0,0,0,4 +0,0,9,16,15,8,0,0,0,3,16,13,7,8,0,0,0,10,16,5,0,0,0,0,0,10,16,15,5,0,0,0,0,0,4,9,15,3,0,0,0,0,0,2,16,8,0,0,0,0,2,12,16,3,0,0,0,0,9,16,8,0,0,0,5 +0,0,5,12,15,15,5,0,0,0,8,7,4,13,7,0,0,0,0,0,2,16,1,0,0,0,2,4,12,10,0,0,0,7,16,14,16,12,2,0,0,2,0,11,7,0,0,0,0,0,3,14,1,0,0,0,0,0,8,7,0,0,0,0,7 +0,0,9,16,16,7,0,0,0,0,8,9,11,16,1,0,0,0,0,2,13,16,1,0,0,0,0,13,16,6,0,0,0,0,0,3,13,13,2,0,0,0,0,0,1,13,9,0,0,0,4,2,1,12,13,0,0,0,11,16,16,16,11,0,3 +0,0,9,16,15,4,0,0,0,0,12,14,12,16,1,0,0,0,0,10,16,14,0,0,0,0,2,16,16,10,1,0,0,0,0,4,8,13,13,0,0,0,0,0,0,4,16,2,0,0,9,6,9,15,15,0,0,0,10,16,16,14,3,0,3 +0,0,7,14,16,8,0,0,0,1,16,16,8,6,0,0,0,8,16,10,0,0,0,0,0,14,16,16,10,1,0,0,0,7,12,8,15,6,0,0,0,0,0,0,14,9,0,0,0,0,3,9,16,6,0,0,0,0,12,16,10,1,0,0,5 +0,0,0,5,12,11,1,0,0,0,3,14,6,7,11,0,0,5,16,2,1,9,9,0,0,8,14,8,14,16,1,0,0,1,7,8,13,10,0,0,0,0,0,1,14,4,0,0,0,0,0,5,13,0,0,0,0,0,0,11,7,0,0,0,9 +0,0,1,14,10,0,0,0,0,0,3,16,16,1,0,0,0,0,3,16,13,0,0,0,0,0,5,16,9,0,0,0,0,0,6,16,10,0,0,0,0,0,4,16,9,0,0,0,0,0,3,16,6,0,0,0,0,0,1,12,6,0,0,0,1 +0,1,12,10,0,0,0,0,0,4,16,3,0,9,9,0,0,8,14,0,3,16,6,0,0,9,15,8,13,14,5,0,0,1,12,16,16,15,5,0,0,0,1,14,6,0,0,0,0,0,8,13,0,0,0,0,0,2,12,3,0,0,0,0,4 +0,0,14,16,16,8,0,0,0,0,6,5,8,16,3,0,0,0,0,0,7,16,2,0,0,0,0,7,16,13,0,0,0,0,0,8,12,16,7,0,0,0,0,0,0,8,14,1,0,0,10,4,4,10,16,1,0,0,12,16,16,16,9,0,3 +0,0,2,15,16,12,2,0,0,0,13,12,4,12,12,0,0,6,16,1,3,13,12,0,0,11,16,15,16,16,9,0,0,1,7,9,16,11,0,0,0,0,0,9,15,1,0,0,0,0,0,14,10,0,0,0,0,0,4,16,3,0,0,0,9 +0,0,9,16,16,5,0,0,0,0,13,16,9,6,0,0,0,0,9,12,0,0,0,0,0,0,3,16,3,0,0,0,0,0,0,12,10,0,0,0,0,0,0,5,15,0,0,0,0,0,3,5,16,5,0,0,0,0,7,16,15,1,0,0,5 +0,3,15,16,13,4,0,0,0,4,6,4,10,16,0,0,0,0,0,5,14,11,0,0,0,0,10,16,12,0,0,0,0,0,6,9,15,12,0,0,0,0,0,0,0,12,8,0,0,1,6,4,6,15,8,0,0,4,13,13,11,7,0,0,3 +0,0,1,15,12,0,0,0,0,0,6,16,15,0,0,0,0,0,5,16,9,0,0,0,0,0,5,16,6,0,0,0,0,0,3,16,8,0,0,0,0,0,2,16,8,0,0,0,0,0,1,16,9,0,0,0,0,0,1,13,6,0,0,0,1 +0,0,10,11,9,1,0,0,0,0,14,16,16,6,0,0,0,0,14,12,6,15,0,0,0,1,16,6,0,14,3,0,0,3,16,6,0,12,6,0,0,3,16,0,6,15,4,0,0,1,16,12,16,15,2,0,0,0,7,16,14,5,0,0,0 +0,0,6,14,16,10,0,0,0,0,13,13,10,15,0,0,0,0,5,15,11,14,0,0,0,0,0,10,16,7,0,0,0,0,0,14,16,1,0,0,0,0,8,15,15,2,0,0,0,0,12,14,16,1,0,0,0,0,10,16,11,0,0,0,8 +0,5,14,16,8,1,0,0,0,4,11,4,12,11,0,0,0,1,2,1,11,11,0,0,0,0,1,15,13,1,0,0,0,0,1,10,16,9,0,0,0,0,0,0,1,13,8,0,0,5,5,0,2,12,8,0,0,5,16,16,16,13,1,0,3 +0,0,12,16,16,16,11,0,0,0,8,10,9,16,11,0,0,0,0,0,9,16,2,0,0,0,0,2,15,9,0,0,0,8,16,16,16,14,0,0,0,3,9,16,12,3,0,0,0,0,9,16,2,0,0,0,0,0,14,10,0,0,0,0,7 +0,0,2,10,16,13,1,0,0,8,16,15,5,12,6,0,0,10,16,10,0,7,10,0,0,12,16,12,10,16,8,0,0,3,12,16,16,12,0,0,0,0,0,5,16,4,0,0,0,0,0,7,15,0,0,0,0,0,0,11,11,0,0,0,9 +0,0,3,13,10,0,0,0,0,0,11,11,11,5,0,0,0,2,16,6,0,10,1,0,0,2,14,1,0,6,5,0,0,3,11,0,0,2,9,0,0,2,14,0,0,4,9,0,0,0,12,9,7,12,9,0,0,0,2,11,15,12,4,0,0 +0,0,12,15,16,16,15,2,0,0,10,12,10,14,14,2,0,0,0,0,2,16,7,0,0,0,0,0,12,11,0,0,0,3,12,14,16,13,3,0,0,12,12,16,11,7,0,0,0,0,5,15,2,0,0,0,0,0,12,11,0,0,0,0,7 +0,0,3,16,9,1,0,0,0,0,12,16,16,1,0,0,0,0,15,16,10,0,0,0,0,0,14,16,5,0,0,0,0,0,13,16,4,0,0,0,0,0,13,16,4,0,0,0,0,0,9,16,5,0,0,0,0,0,2,16,6,0,0,0,1 +0,0,5,16,14,3,0,0,0,0,9,15,10,11,0,0,0,0,0,4,8,13,0,0,0,0,0,0,14,10,0,0,0,0,0,4,16,5,0,0,0,0,0,13,16,5,0,0,0,0,4,16,16,16,14,8,0,0,4,16,12,8,9,13,2 +0,0,9,16,16,10,0,0,0,0,15,13,11,16,1,0,0,0,3,1,13,13,0,0,0,0,0,5,16,5,0,0,0,0,1,15,11,0,0,0,0,0,8,16,2,2,2,0,0,0,15,13,14,16,15,3,0,0,9,16,15,10,8,1,2 +0,0,6,15,16,16,9,0,0,0,9,12,8,16,12,0,0,0,0,0,5,16,4,0,0,1,7,8,16,9,2,0,0,9,16,16,16,16,9,0,0,2,7,16,4,0,0,0,0,0,8,14,0,0,0,0,0,0,10,9,0,0,0,0,7 +0,0,0,6,13,0,0,0,0,0,0,13,9,0,0,0,0,0,1,16,3,0,0,0,0,0,3,15,0,0,0,0,0,0,6,14,8,7,1,0,0,0,7,16,12,12,13,2,0,0,4,16,3,3,13,7,0,0,0,7,16,16,11,1,6 +0,0,9,16,15,5,0,0,0,0,4,7,12,15,0,0,0,0,0,1,9,16,3,0,0,0,0,10,16,13,0,0,0,0,0,3,8,15,9,0,0,0,0,0,0,7,14,0,0,0,10,7,4,10,16,1,0,0,7,16,16,16,10,0,3 +0,0,15,16,15,7,0,0,0,2,16,12,11,9,0,0,0,0,12,12,0,0,0,0,0,0,3,15,5,0,0,0,0,0,0,8,14,0,0,0,0,0,0,2,16,2,0,0,0,0,7,8,16,4,0,0,0,0,12,16,14,1,0,0,5 +0,0,2,6,15,16,8,0,0,0,15,16,10,8,16,0,0,4,16,5,0,7,12,0,0,9,16,10,10,16,8,0,0,1,9,16,15,16,4,0,0,0,0,0,9,13,0,0,0,0,0,3,16,4,0,0,0,0,0,7,13,0,0,0,9 +0,1,15,16,16,13,0,0,0,0,7,8,12,16,0,0,0,0,0,0,10,14,0,0,0,1,4,6,16,5,0,0,0,11,16,16,16,15,8,0,0,4,9,16,8,8,2,0,0,0,11,12,0,0,0,0,0,2,16,4,0,0,0,0,7 +0,0,0,10,10,0,0,0,0,0,3,16,10,0,0,0,0,0,10,10,0,0,0,0,0,0,16,5,0,0,0,0,0,0,16,7,8,7,0,0,0,0,12,16,13,9,13,2,0,0,6,16,5,5,13,8,0,0,0,9,14,13,9,1,6 +0,0,15,15,8,1,0,0,0,0,4,6,11,15,2,0,0,0,0,0,0,16,4,0,0,0,4,9,15,13,1,0,0,0,4,12,16,13,1,0,0,0,1,0,2,10,8,0,0,4,11,3,5,12,8,0,0,1,9,15,15,10,0,0,3 +0,0,10,16,0,0,12,6,0,2,15,9,0,8,16,5,0,6,16,6,1,14,13,0,0,7,16,11,11,16,13,0,0,1,11,14,16,12,2,0,0,0,0,11,14,1,0,0,0,0,2,16,5,0,0,0,0,0,13,10,0,0,0,0,4 +0,0,2,16,10,0,0,0,0,0,5,16,15,1,0,0,0,0,4,16,12,0,0,0,0,0,4,16,11,0,0,0,0,0,1,16,12,0,0,0,0,0,1,16,12,0,0,0,0,0,1,16,10,0,0,0,0,0,0,15,5,0,0,0,1 +0,2,10,16,16,16,7,0,0,14,16,15,11,13,5,0,0,12,15,1,0,0,0,0,0,4,16,7,0,0,0,0,0,0,16,8,0,0,0,0,0,0,15,9,0,0,0,0,0,0,12,14,0,0,0,0,0,0,15,7,0,0,0,0,5 +0,0,1,11,16,16,6,0,0,0,13,14,7,10,15,0,0,6,16,4,2,11,14,0,0,7,16,16,16,16,10,0,0,0,5,8,9,16,2,0,0,0,0,2,14,8,0,0,0,0,0,11,13,0,0,0,0,0,0,16,7,0,0,0,9 +0,0,9,15,14,2,0,0,0,3,16,13,15,11,0,0,0,2,9,0,14,9,0,0,0,0,0,5,16,5,0,0,0,0,0,12,14,0,0,0,0,0,5,16,4,0,0,0,0,1,14,15,8,8,9,1,0,0,11,16,16,16,16,7,2 +0,0,0,7,16,16,5,0,0,0,8,15,6,13,11,0,0,2,15,5,0,13,10,0,0,10,16,10,13,16,11,0,0,2,11,16,16,16,6,0,0,0,0,1,16,8,0,0,0,0,0,7,16,1,0,0,0,0,0,10,12,0,0,0,9 +0,0,4,13,10,1,0,0,0,0,11,16,14,7,0,0,0,1,15,9,0,12,1,0,0,2,16,6,0,7,5,0,0,2,16,10,0,8,8,0,0,1,16,5,2,15,5,0,0,0,11,16,16,13,0,0,0,0,4,15,11,2,0,0,0 +0,0,7,16,2,0,4,0,0,0,15,12,0,4,16,3,0,6,16,2,2,15,10,0,0,10,14,0,12,12,1,0,0,9,16,16,16,10,0,0,0,1,8,16,9,1,0,0,0,0,7,14,1,0,0,0,0,0,10,8,0,0,0,0,4 +0,4,16,16,16,12,1,0,0,0,7,8,12,16,2,0,0,0,0,0,8,15,1,0,0,0,3,4,15,11,0,0,0,2,16,16,16,16,6,0,0,0,9,16,10,4,1,0,0,0,10,15,1,0,0,0,0,1,16,8,0,0,0,0,7 +0,0,9,16,16,9,0,0,0,0,13,13,15,16,0,0,0,0,9,16,16,7,0,0,0,0,4,16,12,0,0,0,0,0,10,15,14,1,0,0,0,0,14,7,12,5,0,0,0,0,15,9,16,5,0,0,0,0,9,16,14,1,0,0,8 +0,1,8,16,14,7,0,0,0,5,16,6,8,15,0,0,0,1,13,8,15,9,0,0,0,0,1,15,10,0,0,0,0,0,8,12,11,0,0,0,0,0,13,5,8,1,0,0,0,0,14,1,9,4,0,0,0,0,9,14,14,1,0,0,8 +0,0,9,15,16,7,0,0,0,0,16,9,7,15,0,0,0,0,5,14,13,8,0,0,0,0,0,14,14,0,0,0,0,0,4,16,13,0,0,0,0,0,10,8,12,0,0,0,0,0,13,8,15,1,0,0,0,0,11,16,5,0,0,0,8 +0,1,12,16,14,6,0,0,0,2,13,7,5,15,2,0,0,0,6,15,9,13,0,0,0,0,0,10,15,1,0,0,0,0,4,15,8,0,0,0,0,0,12,4,12,0,0,0,0,3,14,10,11,0,0,0,0,0,14,13,3,0,0,0,8 +0,0,1,16,8,0,0,0,0,0,2,16,16,7,0,0,0,0,2,16,16,1,0,0,0,0,3,16,16,1,0,0,0,0,2,16,12,0,0,0,0,0,3,16,14,0,0,0,0,0,4,16,14,0,0,0,0,0,2,15,10,0,0,0,1 +0,0,0,9,16,10,0,0,0,0,3,15,12,15,4,0,0,1,12,14,1,15,11,0,0,8,16,12,14,16,7,0,0,7,16,16,16,12,0,0,0,0,7,8,16,5,0,0,0,0,0,8,13,0,0,0,0,0,0,9,11,0,0,0,9 +0,4,16,16,16,14,3,0,0,1,8,8,8,15,10,0,0,0,0,0,5,16,6,0,0,0,0,0,13,12,0,0,0,8,16,16,16,15,8,0,0,3,11,16,9,8,3,0,0,0,14,12,0,0,0,0,0,5,16,2,0,0,0,0,7 +0,0,15,7,0,2,11,1,0,3,16,6,1,12,13,2,0,4,16,5,8,15,3,0,0,2,16,16,16,13,0,0,0,0,4,14,15,3,0,0,0,0,7,16,3,0,0,0,0,1,13,10,0,0,0,0,0,1,16,3,0,0,0,0,4 +0,0,3,13,15,6,0,0,0,2,11,15,7,16,3,0,0,16,16,9,8,16,6,0,0,9,16,16,16,16,5,0,0,0,4,8,16,11,0,0,0,0,0,9,16,1,0,0,0,0,0,14,11,0,0,0,0,0,1,16,7,0,0,0,9 +0,0,0,3,15,16,3,0,0,0,0,12,10,15,3,0,0,0,0,2,6,14,0,0,0,0,0,1,15,5,0,0,0,0,1,13,9,0,0,0,0,1,12,12,1,0,0,0,0,9,16,16,16,13,5,0,0,2,4,5,10,13,12,0,2 +0,0,1,11,13,0,0,0,0,0,7,16,8,1,0,0,0,0,15,12,0,0,0,0,0,1,15,7,0,0,0,0,0,0,15,7,4,5,1,0,0,0,15,16,16,16,13,1,0,0,10,16,9,8,16,6,0,0,1,9,14,16,13,2,6 +0,0,4,13,15,4,0,0,0,0,11,13,8,13,0,0,0,0,5,2,4,13,0,0,0,0,0,0,12,7,0,0,0,0,0,6,15,2,0,0,0,0,0,15,5,0,0,0,0,0,9,14,6,11,5,0,0,0,9,16,12,12,10,0,2 +0,0,3,15,16,6,0,0,0,0,6,11,11,16,5,0,0,0,0,0,7,16,7,0,0,0,0,0,13,16,2,0,0,9,13,13,16,12,2,0,0,2,12,14,16,16,15,0,0,0,0,15,13,0,0,0,0,0,3,16,8,0,0,0,7 +0,2,16,16,16,10,0,0,0,0,11,8,12,16,2,0,0,0,0,0,14,13,0,0,0,2,9,12,16,4,0,0,0,11,16,16,16,16,8,0,0,0,10,14,1,4,0,0,0,2,16,7,0,0,0,0,0,2,15,6,0,0,0,0,7 +0,0,0,14,15,4,0,0,0,0,7,16,14,2,0,0,0,0,14,13,1,0,0,0,0,0,3,7,0,0,0,0,0,3,16,6,8,3,0,0,0,2,15,16,16,15,3,0,0,0,9,16,15,15,11,0,0,0,0,10,16,16,11,0,6 +0,1,10,16,11,0,0,0,0,8,16,12,16,3,0,0,0,1,12,3,16,4,0,0,0,0,0,6,15,0,0,0,0,0,0,11,13,0,0,0,0,0,3,16,7,0,0,0,0,0,11,16,16,16,14,5,0,0,11,13,9,12,15,11,2 +0,1,15,8,0,1,2,0,0,8,16,8,0,12,15,0,0,7,16,11,10,16,7,0,0,2,15,16,16,16,2,0,0,0,2,12,16,9,0,0,0,0,1,16,12,0,0,0,0,0,10,15,1,0,0,0,0,0,12,11,0,0,0,0,4 +0,0,0,11,13,0,0,0,0,0,4,16,8,1,0,0,0,0,13,10,0,0,0,0,0,0,15,5,0,0,0,0,0,1,16,3,4,2,0,0,0,0,13,16,15,14,7,0,0,0,7,16,7,4,16,1,0,0,0,9,16,16,11,0,6 +0,0,1,11,16,15,3,0,0,1,13,13,5,13,8,0,0,6,16,8,2,15,8,0,0,10,16,16,16,16,7,0,0,1,1,6,15,14,1,0,0,0,0,3,16,5,0,0,0,0,0,9,14,0,0,0,0,0,1,15,7,0,0,0,9 +0,4,14,16,16,12,2,0,0,10,16,10,8,14,7,0,0,10,12,0,0,0,0,0,0,2,16,7,0,0,0,0,0,0,15,11,0,0,0,0,0,0,9,15,3,0,0,0,0,1,12,16,4,0,0,0,0,5,16,15,1,0,0,0,5 +0,0,6,10,12,4,0,0,0,2,15,10,8,9,0,0,0,0,6,0,3,12,0,0,0,0,0,0,8,7,0,0,0,0,0,6,14,1,0,0,0,0,4,14,6,0,0,0,0,1,16,11,7,11,8,0,0,0,13,16,13,12,12,0,2 +0,0,1,11,6,0,0,0,0,0,7,13,2,0,0,0,0,0,13,6,0,0,0,0,0,1,13,1,0,0,0,0,0,1,12,0,4,3,0,0,0,1,11,11,14,14,10,0,0,0,12,15,5,4,15,3,0,0,1,13,16,12,9,1,6 +0,0,5,13,11,2,0,0,0,2,14,15,15,12,0,0,0,2,16,5,0,7,4,0,0,4,16,3,0,3,6,0,0,3,14,0,0,8,7,0,0,2,16,0,2,14,6,0,0,1,15,15,16,11,0,0,0,0,7,14,10,1,0,0,0 +0,0,0,10,12,0,0,0,0,0,4,16,7,1,0,0,0,0,11,8,0,0,0,0,0,0,12,5,0,0,0,0,0,0,14,2,3,4,1,0,0,0,11,10,16,13,13,0,0,0,7,16,7,0,11,5,0,0,0,8,15,16,13,1,6 +0,1,7,11,15,11,1,0,0,4,9,4,0,13,5,0,0,0,0,0,9,15,1,0,0,0,4,15,11,3,0,0,0,0,3,16,8,2,0,0,0,0,0,2,7,16,6,0,0,0,0,6,9,16,5,0,0,0,13,12,7,3,0,0,3 +0,0,1,16,14,2,0,0,0,0,4,16,16,8,0,0,0,0,7,16,16,3,0,0,0,0,10,16,16,3,0,0,0,0,13,16,12,0,0,0,0,0,15,16,12,0,0,0,0,0,11,16,11,0,0,0,0,0,3,15,16,9,0,0,1 +0,0,0,2,9,15,9,0,0,0,8,14,7,9,12,0,0,3,16,5,4,13,6,0,0,2,10,12,15,14,0,0,0,0,0,0,1,13,3,0,0,0,0,0,2,16,0,0,0,0,0,0,7,12,0,0,0,0,0,0,12,6,0,0,9 +0,3,12,16,16,14,0,0,0,13,16,14,8,5,0,0,0,10,10,0,0,0,0,0,0,5,15,3,0,0,0,0,0,0,14,9,0,0,0,0,0,0,7,14,1,0,0,0,0,0,10,16,4,0,0,0,0,0,15,15,2,0,0,0,5 +0,0,4,11,14,12,0,0,0,4,16,9,5,16,1,0,0,11,8,0,6,13,0,0,0,5,6,1,13,5,0,0,0,0,0,11,9,0,0,0,0,0,4,16,1,0,0,0,0,0,10,9,0,0,1,0,0,0,3,16,16,14,6,0,2 +0,2,12,16,15,8,0,0,0,2,7,4,10,16,2,0,0,0,0,3,16,13,0,0,0,0,0,15,15,1,0,0,0,0,0,10,13,0,0,0,0,0,0,0,14,10,1,0,0,0,1,5,10,16,4,0,0,1,13,13,12,8,0,0,3 +0,0,1,11,8,0,0,0,0,0,8,15,3,0,0,0,0,1,15,4,0,0,0,0,0,4,16,8,15,14,3,0,0,4,14,11,7,5,10,0,0,1,13,0,0,1,13,0,0,0,11,5,0,9,13,0,0,0,2,11,16,12,1,0,6 +0,0,0,10,15,4,0,0,0,0,6,16,10,1,0,0,0,0,11,15,1,0,0,0,0,0,16,10,6,3,0,0,0,4,16,16,16,16,7,0,0,0,15,16,11,6,16,1,0,0,11,13,2,4,16,4,0,0,0,10,16,16,13,1,6 +0,0,5,14,16,14,0,0,0,7,16,15,8,8,0,0,0,14,13,0,0,0,0,0,0,13,16,13,3,0,0,0,0,0,5,11,15,1,0,0,0,0,0,0,16,9,0,0,0,0,5,11,16,8,0,0,0,0,11,16,10,1,0,0,5 +0,0,0,5,15,0,0,0,0,0,0,11,10,3,8,0,0,0,6,15,1,13,9,0,0,3,16,8,9,16,5,0,0,12,16,15,15,13,8,0,0,3,4,0,16,2,0,0,0,0,0,4,11,0,0,0,0,0,0,6,11,0,0,0,4 +0,0,5,14,16,8,0,0,0,6,15,10,12,16,1,0,0,9,5,0,13,8,0,0,0,6,15,13,15,1,0,0,0,0,11,16,4,0,0,0,0,0,15,16,7,0,0,0,0,0,16,13,14,0,0,0,0,0,5,16,15,4,0,0,8 +0,2,16,16,15,3,0,0,0,0,3,3,14,8,0,0,0,0,0,3,16,2,0,0,0,0,0,10,14,0,0,0,0,3,16,16,16,16,8,0,0,0,11,15,4,4,1,0,0,0,14,6,0,0,0,0,0,3,16,3,0,0,0,0,7 +0,1,10,16,16,14,6,0,0,11,16,7,4,16,10,0,0,9,16,10,10,16,4,0,0,0,5,12,16,3,0,0,0,0,0,7,16,0,0,0,0,0,0,13,15,0,0,0,0,0,6,16,7,0,0,0,0,2,15,10,0,0,0,0,9 +0,1,9,16,16,14,2,0,0,11,16,7,5,16,6,0,0,16,14,10,16,16,4,0,0,4,8,10,16,13,0,0,0,0,1,13,13,1,0,0,0,0,6,16,6,0,0,0,0,0,12,10,0,0,0,0,0,0,11,11,0,0,0,0,9 +0,0,5,14,13,2,0,0,0,3,14,5,8,12,0,0,0,3,9,0,12,4,0,0,0,1,14,9,13,0,0,0,0,0,2,14,12,0,0,0,0,0,4,14,14,3,0,0,0,0,10,7,12,6,0,0,0,0,6,16,13,1,0,0,8 +0,0,0,12,8,0,6,3,0,0,4,16,3,3,15,3,0,0,12,10,0,12,8,0,0,8,15,5,8,16,4,0,0,10,16,16,16,16,10,0,0,0,4,5,15,1,0,0,0,0,0,8,11,0,0,0,0,0,0,12,6,0,0,0,4 +0,3,13,16,4,0,0,0,0,7,14,16,8,0,0,0,0,0,1,16,7,0,0,0,0,0,9,15,1,0,0,0,0,1,16,8,0,0,0,0,0,7,16,1,0,0,0,0,0,9,16,4,4,4,4,0,0,4,16,16,16,16,8,0,2 +0,0,2,16,15,7,0,0,0,0,3,12,13,13,0,0,0,0,0,6,16,10,0,0,0,0,0,6,16,3,0,0,0,0,3,15,10,12,0,0,0,1,13,5,0,13,6,0,0,0,16,5,0,7,15,0,0,0,3,11,16,16,12,2,8 +0,0,9,13,16,9,0,0,0,0,4,5,11,15,0,0,0,0,0,6,15,8,0,0,0,0,0,13,10,0,0,0,0,0,0,8,12,1,0,0,0,0,0,0,9,14,2,0,0,0,0,3,12,16,4,0,0,0,11,16,11,3,0,0,3 +0,0,2,13,9,1,0,0,0,1,13,7,15,5,0,0,1,15,9,0,15,2,0,0,0,6,13,7,13,0,0,0,0,0,5,16,8,0,0,0,0,0,1,15,14,2,0,0,0,0,4,12,6,11,0,0,0,0,0,11,16,8,0,0,8 +0,0,2,13,6,0,4,0,0,0,13,11,0,2,15,3,0,4,16,2,0,13,8,0,0,3,16,16,12,16,7,0,0,0,2,7,15,10,1,0,0,0,0,4,13,0,0,0,0,0,0,13,5,0,0,0,0,0,2,12,0,0,0,0,4 +0,4,12,14,12,6,0,0,0,2,4,4,5,16,6,0,0,0,0,0,6,16,4,0,0,0,0,5,16,6,0,0,0,0,0,5,16,3,0,0,0,0,0,0,11,13,0,0,0,0,0,5,13,16,0,0,0,5,16,12,11,1,0,0,3 +0,7,16,16,16,9,0,0,0,3,8,4,13,16,0,0,0,0,0,8,16,6,0,0,0,0,0,4,16,4,0,0,0,0,0,0,11,15,5,0,0,0,0,0,1,13,15,0,0,0,5,9,15,16,10,0,0,4,16,16,10,3,0,0,3 +0,0,1,14,11,0,0,0,0,0,12,13,1,0,0,0,0,5,16,3,0,7,15,1,0,6,16,11,12,16,10,0,0,0,11,14,16,11,0,0,0,0,0,7,15,2,0,0,0,0,0,15,12,0,0,0,0,0,1,15,7,0,0,0,4 +0,0,11,16,15,3,0,0,0,0,2,4,16,7,0,0,0,0,0,4,16,3,0,0,0,2,4,10,14,4,2,0,0,9,16,16,16,16,8,0,0,0,7,15,2,0,0,0,0,0,10,9,0,0,0,0,0,0,12,7,0,0,0,0,7 +0,0,7,15,15,3,0,0,0,0,10,14,12,14,0,0,0,0,1,6,16,12,0,0,0,0,1,15,14,3,0,0,0,0,0,9,15,5,0,0,0,0,0,0,5,15,6,0,0,0,7,6,7,13,16,1,0,0,7,16,16,15,8,0,3 +0,0,1,14,6,0,0,0,0,0,6,15,3,0,0,0,0,1,14,7,0,0,0,0,0,0,16,9,8,3,0,0,0,7,16,16,12,15,3,0,0,5,14,7,0,4,9,0,0,3,15,6,1,12,9,0,0,0,4,13,16,14,3,0,6 +0,0,2,13,14,2,0,0,0,0,10,16,13,4,0,0,0,0,13,15,1,0,0,0,0,1,16,9,0,0,0,0,0,1,16,15,12,2,0,0,0,1,15,16,11,13,0,0,0,0,11,12,8,16,1,0,0,0,2,13,16,15,0,0,6 +0,0,0,10,14,0,3,1,0,0,5,16,6,1,14,5,0,2,15,8,3,10,13,0,0,8,16,14,16,16,8,0,0,5,11,6,15,13,3,0,0,0,0,1,15,0,0,0,0,0,0,7,10,0,0,0,0,0,0,13,3,0,0,0,4 +0,1,8,16,16,11,0,0,0,2,10,6,12,12,0,0,0,0,0,0,11,11,0,0,0,0,0,4,16,8,3,0,0,3,15,16,16,16,11,0,0,4,10,16,6,6,2,0,0,0,9,13,0,0,0,0,0,1,14,7,0,0,0,0,7 +0,0,0,12,14,2,0,0,0,0,6,15,16,4,0,0,0,0,13,16,12,0,0,0,0,1,14,16,8,0,0,0,0,1,15,16,2,0,0,0,0,1,15,16,4,0,0,0,0,0,10,16,11,4,0,0,0,0,0,11,13,5,0,0,1 +0,0,9,16,13,3,0,0,0,2,16,16,16,12,0,0,0,0,0,6,16,7,0,0,0,0,1,15,12,0,0,0,0,0,13,15,2,0,0,0,0,4,16,7,0,0,0,0,0,8,16,8,6,8,3,0,0,1,11,16,16,12,4,0,2 +0,0,10,16,16,9,0,0,0,4,13,5,9,12,0,0,0,0,0,1,13,5,0,0,0,0,5,13,16,16,9,0,0,0,11,16,11,11,9,0,0,0,1,13,3,0,0,0,0,0,8,10,0,0,0,0,0,0,11,8,0,0,0,0,7 +0,0,1,12,16,3,0,0,0,0,8,8,4,0,0,0,0,0,14,1,0,0,0,0,0,2,16,9,8,6,1,0,0,4,16,14,11,12,6,0,0,2,16,4,1,7,11,0,0,0,11,11,5,13,9,0,0,0,0,10,13,10,3,0,6 +0,0,5,16,16,6,0,0,0,0,16,9,11,13,0,0,0,0,11,11,14,8,0,0,0,0,1,15,12,1,0,0,0,0,3,16,15,4,0,0,0,0,12,10,7,13,1,0,0,0,15,4,3,16,6,0,0,0,8,16,16,13,1,0,8 +0,0,1,11,16,15,2,0,0,2,14,15,11,16,6,0,0,11,14,2,8,15,1,0,0,8,16,12,16,16,9,0,0,0,3,7,9,16,8,0,0,0,0,0,13,15,1,0,0,0,0,7,16,4,0,0,0,0,0,11,13,0,0,0,9 +0,0,3,11,15,15,4,0,0,2,14,8,4,15,5,0,0,7,11,1,13,15,1,0,0,1,11,12,13,16,5,0,0,0,0,0,8,12,0,0,0,0,0,3,15,2,0,0,0,0,0,11,7,0,0,0,0,0,0,13,4,0,0,0,9 +0,0,1,8,15,16,7,0,0,1,13,14,9,16,8,0,0,8,16,5,11,15,2,0,0,5,16,16,16,10,1,0,0,0,1,4,10,16,9,0,0,0,0,0,3,16,7,0,0,0,0,3,14,14,1,0,0,0,0,11,15,3,0,0,9 +0,2,12,16,16,9,0,0,0,2,15,10,7,16,4,0,0,0,0,0,5,16,6,0,0,0,0,6,16,13,0,0,0,0,0,5,13,16,6,0,0,0,0,0,0,9,16,0,0,0,6,4,5,12,16,2,0,2,13,16,16,16,10,0,3 +0,0,5,16,14,1,0,0,0,0,13,10,9,10,0,0,0,1,16,4,0,16,0,0,0,3,16,7,0,13,6,0,0,5,12,0,0,13,8,0,0,4,16,0,0,14,5,0,0,0,15,13,14,12,0,0,0,0,5,14,10,4,0,0,0 +0,0,0,9,12,0,0,0,0,0,5,16,6,0,0,0,0,0,10,11,0,0,0,0,0,0,12,8,4,0,0,0,0,0,13,16,15,11,1,0,0,0,13,14,1,6,6,0,0,0,5,12,1,11,6,0,0,0,0,9,15,10,0,0,6 +0,1,12,16,16,8,0,0,0,1,8,5,9,12,0,0,0,0,0,0,11,8,0,0,0,0,5,14,14,10,5,0,0,0,3,15,14,12,5,0,0,0,2,16,3,0,0,0,0,0,10,9,0,0,0,0,0,0,13,2,0,0,0,0,7 +0,0,1,14,13,0,0,0,0,0,6,16,8,0,0,0,0,0,12,9,0,0,0,0,0,0,14,10,16,12,1,0,0,1,16,10,14,8,13,0,0,0,13,9,10,0,12,4,0,0,8,10,0,5,16,5,0,0,1,11,16,16,11,0,6 +0,2,12,16,15,9,0,0,0,1,4,7,15,14,0,0,0,0,3,15,14,3,0,0,0,0,6,14,5,0,0,0,0,0,1,11,16,7,0,0,0,0,0,0,6,16,7,0,0,0,3,9,14,15,5,0,0,1,14,12,8,1,0,0,3 +0,0,4,14,16,7,0,0,0,1,16,14,8,5,0,0,0,8,10,0,0,0,0,0,0,3,13,0,0,0,0,0,0,0,12,6,0,0,0,0,0,0,4,13,0,0,0,0,0,0,0,16,5,0,0,0,0,0,7,16,4,0,0,0,5 +0,0,0,12,14,5,0,0,0,0,3,16,16,12,0,0,0,0,14,16,16,2,0,0,0,1,15,16,8,0,0,0,0,4,16,15,4,0,0,0,0,3,16,16,1,0,0,0,0,0,10,16,13,0,0,0,0,0,0,11,16,5,0,0,1 +0,0,6,13,16,16,13,0,0,6,16,10,7,15,14,0,0,1,4,0,10,16,6,0,0,0,0,10,16,4,0,0,0,0,3,16,9,0,0,0,0,0,15,11,0,0,0,0,0,0,16,10,0,0,0,0,0,0,9,16,16,12,1,0,2 +0,0,1,16,10,0,0,0,0,0,3,16,16,4,0,0,0,0,6,16,14,0,0,0,0,0,6,16,14,0,0,0,0,0,8,16,11,0,0,0,0,0,6,16,12,0,0,0,0,0,6,16,11,0,0,0,0,0,1,11,13,0,0,0,1 +0,0,3,13,16,16,3,0,0,0,8,10,5,5,0,0,0,7,15,0,0,0,0,0,0,10,13,2,0,0,0,0,0,3,15,14,1,0,0,0,0,0,0,11,11,0,0,0,0,0,0,7,13,0,0,0,0,0,3,16,11,0,0,0,5 +0,5,15,13,0,0,0,0,0,3,10,16,5,0,0,0,0,0,0,14,4,0,0,0,0,0,6,14,0,0,0,0,0,1,14,7,0,0,0,0,0,7,13,1,0,0,0,0,0,13,14,10,11,9,5,0,0,5,15,16,14,10,3,0,2 +0,1,10,16,16,15,2,0,0,9,16,11,4,16,11,0,0,6,16,15,12,16,7,0,0,0,4,9,16,14,1,0,0,0,0,1,15,8,0,0,0,0,0,13,15,2,0,0,0,0,9,16,5,0,0,0,0,3,16,7,0,0,0,0,9 +0,0,1,11,13,1,0,0,0,0,11,13,9,11,0,0,0,2,16,2,0,10,2,0,0,5,12,0,0,5,7,0,0,8,8,0,0,4,8,0,0,2,12,0,0,7,7,0,0,0,13,5,8,16,2,0,0,0,2,15,14,5,0,0,0 +0,0,3,15,10,1,0,0,0,0,11,14,10,7,0,0,0,2,16,3,0,11,1,0,0,4,15,0,0,7,5,0,0,3,12,0,0,3,9,0,0,2,11,0,0,7,9,0,0,0,11,8,9,16,4,0,0,0,2,10,16,9,0,0,0 +0,0,1,15,14,0,0,0,0,0,6,16,16,8,0,0,0,0,12,16,16,2,0,0,0,0,12,16,13,0,0,0,0,0,15,16,7,0,0,0,0,0,14,16,7,0,0,0,0,0,11,16,11,0,0,0,0,0,2,14,13,0,0,0,1 +0,0,2,15,12,1,0,0,0,0,6,16,15,1,0,0,0,0,14,16,8,0,0,0,0,2,16,16,2,0,0,0,0,3,16,15,0,0,0,0,0,2,16,13,0,0,0,0,0,0,9,16,2,0,0,0,0,0,2,13,12,0,0,0,1 +0,0,2,14,14,4,0,0,0,0,15,16,16,15,1,0,0,6,14,16,5,12,6,0,0,2,11,12,0,6,8,0,0,0,12,4,0,9,6,0,0,0,11,5,0,12,3,0,0,0,8,12,11,15,2,0,0,0,1,12,10,2,0,0,0 +0,0,0,8,15,1,0,0,0,0,2,16,7,0,0,0,0,0,7,15,1,0,0,0,0,0,10,11,0,0,0,0,0,0,14,16,16,13,2,0,0,0,15,16,5,6,14,1,0,0,9,12,2,4,14,5,0,0,0,8,15,16,13,4,6 +0,0,7,13,0,0,3,8,0,0,15,7,0,3,15,7,0,0,16,12,6,14,9,0,0,0,6,12,16,12,0,0,0,0,0,2,14,2,0,0,0,0,0,9,9,0,0,0,0,0,2,13,1,0,0,0,0,0,8,5,0,0,0,0,4 +0,0,3,12,16,10,0,0,0,3,14,5,0,12,0,0,0,9,8,1,9,16,4,0,0,5,15,15,15,14,2,0,0,0,0,0,15,3,0,0,0,0,0,8,10,0,0,0,0,0,0,11,6,0,0,0,0,0,3,15,2,0,0,0,9 +0,0,1,13,16,5,0,0,0,0,6,16,11,2,0,0,0,0,13,15,0,0,0,0,0,3,16,9,0,0,0,0,0,4,16,5,5,10,3,0,0,4,16,5,16,14,15,4,0,0,13,11,7,8,16,7,0,0,2,12,16,16,12,2,6 +0,0,0,8,14,0,0,0,0,0,9,16,5,0,0,0,0,2,14,12,4,7,8,0,0,5,16,16,16,16,13,0,0,1,7,10,12,16,6,0,0,0,0,2,15,10,0,0,0,0,0,10,16,2,0,0,0,0,0,10,15,3,0,0,4 +0,0,3,16,14,3,0,0,0,0,6,16,16,5,0,0,0,0,12,16,9,0,0,0,0,0,16,16,5,0,0,0,0,2,16,16,1,0,0,0,0,1,16,16,1,0,0,0,0,0,13,16,1,0,0,0,0,0,4,15,0,0,0,0,1 +0,0,4,12,13,2,0,0,0,0,16,9,12,9,0,0,0,2,12,1,15,5,0,0,0,0,1,6,14,1,0,0,0,0,0,15,7,0,0,0,0,0,9,12,0,0,0,0,0,0,9,9,4,8,14,2,0,0,4,13,13,9,2,0,2 +0,0,2,15,15,6,0,0,0,0,9,13,4,15,3,0,0,0,14,7,0,8,4,0,0,0,15,4,0,5,8,0,0,4,13,0,0,7,6,0,0,2,12,0,0,12,5,0,0,0,15,9,12,14,0,0,0,0,3,13,13,0,0,0,0 +0,0,4,12,15,6,0,0,0,4,16,8,8,15,0,0,0,10,9,0,3,15,2,0,0,6,15,7,10,16,4,0,0,0,7,8,8,13,8,0,0,0,0,0,0,7,9,0,0,0,2,4,4,10,11,0,0,0,8,16,15,10,2,0,9 +0,0,0,0,7,13,1,0,0,0,0,0,9,16,2,0,0,0,1,9,16,16,0,0,0,4,12,16,16,16,0,0,0,5,10,3,12,15,0,0,0,0,0,0,10,15,0,0,0,0,0,0,12,15,0,0,0,0,0,0,9,12,0,0,1 +0,3,13,14,7,1,0,0,0,6,12,8,13,6,0,0,0,1,0,0,10,7,0,0,0,0,0,9,16,4,0,0,0,0,0,9,14,15,4,0,0,0,0,0,0,13,8,0,0,1,8,8,10,16,3,0,0,4,14,16,11,2,0,0,3 +0,0,2,11,0,0,0,0,0,0,11,13,0,0,0,0,0,0,16,6,0,0,0,0,0,0,15,7,4,1,0,0,0,4,16,16,16,15,3,0,0,1,16,13,8,14,12,0,0,0,14,15,12,15,12,0,0,0,2,15,16,14,4,0,6 +0,0,0,10,13,0,0,0,0,0,1,16,11,2,0,0,0,0,8,16,0,0,0,0,0,0,13,12,0,0,0,0,0,0,14,16,14,9,0,0,0,0,12,15,5,9,13,0,0,0,3,16,4,6,16,2,0,0,0,8,13,14,9,0,6 +0,0,5,13,16,3,0,0,0,1,15,15,16,8,0,0,0,5,8,5,16,11,0,0,0,2,12,16,16,16,10,0,0,2,14,16,15,10,5,0,0,0,1,16,11,0,0,0,0,0,5,16,8,0,0,0,0,0,8,14,2,0,0,0,7 +0,0,11,15,10,0,0,0,0,3,15,6,16,3,0,0,0,0,0,3,14,2,0,0,0,0,1,14,13,2,0,0,0,0,3,13,14,15,3,0,0,0,0,0,0,14,7,0,0,0,8,1,7,15,2,0,0,0,12,16,14,4,0,0,3 +0,0,3,13,13,4,0,0,0,1,14,13,10,16,1,0,0,6,12,1,3,16,6,0,0,7,15,4,10,16,8,0,0,2,11,15,12,16,8,0,0,0,0,0,0,15,8,0,0,0,5,10,4,16,8,0,0,0,2,12,16,11,1,0,9 +0,1,14,13,0,0,0,0,0,9,16,16,2,0,0,0,0,7,11,16,8,0,0,0,0,0,1,13,8,0,0,0,0,0,2,16,5,0,0,0,0,0,6,16,4,0,0,0,0,1,15,16,16,12,6,0,0,0,16,16,16,16,16,3,2 +0,0,10,16,15,1,0,0,0,5,15,8,15,6,0,0,0,3,6,1,14,4,0,0,0,0,0,10,16,13,0,0,0,0,0,1,7,16,7,0,0,0,1,2,0,15,7,0,0,0,12,13,6,16,5,0,0,0,9,16,16,9,0,0,3 +0,0,4,13,14,1,0,0,0,1,16,9,16,3,0,0,0,2,5,0,12,6,0,0,0,0,3,6,16,4,0,0,0,6,16,16,16,16,6,0,0,4,6,13,12,4,2,0,0,0,0,14,4,0,0,0,0,0,5,11,0,0,0,0,7 +0,0,6,14,13,1,0,0,0,2,15,15,16,7,0,0,0,1,5,4,16,6,0,0,0,0,0,7,16,10,3,0,0,1,9,16,16,16,10,0,0,1,15,16,13,8,1,0,0,0,2,16,8,0,0,0,0,0,8,13,0,0,0,0,7 +0,0,8,15,15,5,0,0,0,4,16,13,16,9,0,0,0,1,4,4,16,6,0,0,0,0,0,11,16,5,0,0,0,1,13,16,16,15,5,0,0,5,13,16,13,12,5,0,0,0,9,16,3,0,0,0,0,0,12,10,0,0,0,0,7 +0,0,5,13,16,15,3,0,0,3,16,16,15,16,5,0,0,0,4,0,6,16,2,0,0,0,3,12,16,16,4,0,0,0,9,16,16,16,8,0,0,0,2,13,15,2,0,0,0,0,2,16,9,0,0,0,0,0,5,14,1,0,0,0,7 +0,0,6,14,3,0,0,0,0,0,12,14,14,0,0,0,0,0,14,7,14,2,0,0,0,0,10,5,10,6,0,0,0,0,0,0,13,5,0,0,0,0,0,2,14,5,3,0,0,0,8,16,16,16,16,0,0,0,9,14,8,8,8,2,2 +0,0,3,15,4,0,0,0,0,0,8,13,15,7,0,0,0,2,16,6,5,16,3,0,0,4,14,0,0,11,8,0,0,6,12,0,0,8,8,0,0,4,15,1,0,9,7,0,0,1,15,11,9,16,3,0,0,0,4,10,15,8,0,0,0 +0,1,8,15,13,2,0,0,0,5,14,8,14,7,0,0,0,1,2,2,15,2,0,0,0,0,0,15,15,3,0,0,0,0,0,9,14,16,3,0,0,0,0,0,0,13,8,0,0,0,6,8,8,15,4,0,0,0,11,12,12,5,0,0,3 +0,0,14,11,1,0,0,0,0,7,15,14,8,0,0,0,0,7,8,4,8,0,0,0,0,0,4,0,12,0,0,0,0,0,0,4,10,0,0,0,0,0,0,8,6,0,0,0,0,1,13,16,13,8,4,0,0,1,14,12,16,16,10,0,2 +0,0,5,14,16,10,0,0,0,3,15,13,8,15,6,0,0,0,14,13,7,16,9,0,0,0,10,16,16,12,3,0,0,0,6,16,16,3,0,0,0,0,14,13,16,7,0,0,0,0,14,12,14,9,0,0,0,0,6,14,11,2,0,0,8 +0,0,3,10,14,9,0,0,0,3,15,11,9,8,0,0,0,6,12,8,7,1,0,0,0,4,16,14,16,15,1,0,0,2,8,4,3,14,8,0,0,0,0,0,0,8,9,0,0,0,0,7,8,13,9,0,0,0,0,14,14,9,0,0,5 +0,0,0,4,13,2,0,0,0,0,0,14,8,0,0,0,0,0,5,14,0,9,7,0,0,2,16,3,5,16,5,0,0,7,14,0,12,13,0,0,0,7,16,13,15,11,0,0,0,0,5,13,16,16,2,0,0,0,0,3,14,2,0,0,4 +0,0,4,15,7,0,0,0,0,2,15,5,13,5,0,0,0,5,11,0,2,13,2,0,0,5,8,0,0,6,8,0,0,7,8,0,0,5,8,0,0,3,11,0,0,9,7,0,0,0,14,10,6,15,2,0,0,0,6,12,13,6,0,0,0 +0,0,4,16,1,0,0,0,0,0,10,12,0,0,0,0,0,1,14,8,0,0,0,0,0,3,16,16,14,4,0,0,0,3,16,8,6,15,5,0,0,1,14,3,0,7,12,0,0,0,11,11,7,16,8,0,0,0,5,14,14,9,0,0,6 +0,0,9,13,16,5,0,0,0,7,16,12,9,12,0,0,0,6,14,1,1,7,1,0,0,1,13,16,16,12,3,0,0,0,9,16,12,0,0,0,0,1,16,8,16,2,0,0,0,4,14,2,16,4,0,0,0,0,13,16,10,0,0,0,8 +0,0,3,11,0,0,0,0,0,0,11,10,0,0,0,0,0,0,16,5,0,0,0,0,0,2,16,5,4,2,0,0,0,3,16,16,16,16,6,0,0,2,16,6,0,6,13,0,0,0,15,7,3,12,11,0,0,0,4,14,16,11,3,0,6 +0,0,9,16,11,5,0,0,0,5,16,10,9,16,3,0,0,8,12,0,0,12,2,0,0,5,16,12,15,16,7,0,0,0,11,16,16,5,0,0,0,1,15,11,13,10,0,0,0,3,16,6,11,12,0,0,0,0,10,16,13,4,0,0,8 +0,0,0,9,4,0,0,0,0,0,0,16,3,0,0,0,0,0,8,12,0,1,0,0,0,4,15,2,1,15,3,0,0,8,13,0,8,16,3,0,0,8,16,16,16,13,4,0,0,0,4,10,13,0,0,0,0,0,0,7,12,0,0,0,4 +0,0,3,10,13,1,0,0,0,2,15,10,16,3,0,0,0,2,4,1,16,4,0,0,0,0,0,7,16,0,0,0,0,0,7,14,16,12,3,0,0,2,13,15,13,9,2,0,0,0,0,13,8,0,0,0,0,0,1,15,3,0,0,0,7 +0,0,0,0,15,16,3,0,0,0,0,0,15,16,2,0,0,0,0,4,16,16,2,0,0,0,8,16,16,13,0,0,0,8,16,16,16,9,0,0,0,1,8,6,16,12,0,0,0,0,0,3,16,13,0,0,0,0,0,0,13,13,0,0,1 +0,0,0,0,13,12,0,0,0,0,0,2,16,16,2,0,0,0,0,7,16,16,2,0,0,4,14,16,16,15,0,0,0,9,16,13,16,10,0,0,0,0,0,4,16,12,0,0,0,0,0,1,16,15,0,0,0,0,0,0,13,16,4,0,1 +0,2,15,13,0,0,0,0,0,12,16,16,6,0,0,0,0,14,7,11,10,0,0,0,0,3,1,10,12,0,0,0,0,0,1,15,6,0,0,0,0,0,12,14,0,0,0,0,0,4,16,15,12,12,5,0,0,2,15,16,16,16,13,0,2 +0,0,3,11,12,12,1,0,0,0,15,12,4,14,2,0,0,0,15,12,5,16,4,0,0,0,6,16,16,13,1,0,0,0,8,15,14,13,1,0,0,1,15,4,3,16,6,0,0,3,16,5,6,15,1,0,0,0,9,14,13,6,0,0,8 +0,0,8,15,16,3,0,0,0,3,15,14,16,5,0,0,0,0,3,6,16,2,0,0,0,0,3,12,16,8,3,0,0,1,14,16,16,16,10,0,0,0,9,16,12,5,0,0,0,0,8,16,4,0,0,0,0,0,11,11,0,0,0,0,7 +0,0,0,8,15,1,0,0,0,0,1,15,11,0,0,0,0,0,12,14,1,8,15,1,0,7,16,6,4,16,10,0,0,9,16,16,16,16,1,0,0,1,5,12,16,11,1,0,0,0,0,10,15,1,0,0,0,0,0,10,10,0,0,0,4 +0,0,0,10,16,7,0,0,0,0,0,9,16,16,2,0,0,0,0,11,16,14,0,0,0,0,0,14,16,12,0,0,0,0,7,16,16,8,0,0,0,2,15,16,16,7,0,0,0,3,10,16,16,8,0,0,0,0,0,11,16,15,3,0,1 +0,0,6,12,14,15,2,0,0,0,14,9,8,5,0,0,0,3,14,6,0,0,0,0,0,6,16,16,15,9,0,0,0,0,4,4,7,15,7,0,0,0,0,0,0,11,8,0,0,0,2,9,13,16,4,0,0,0,7,15,12,6,0,0,5 +0,0,6,13,15,9,0,0,0,4,15,8,4,16,4,0,0,8,9,0,0,15,8,0,0,7,15,9,10,16,8,0,0,0,5,8,5,12,8,0,0,0,0,0,0,12,8,0,0,0,11,9,3,14,3,0,0,0,4,15,16,7,0,0,9 +0,0,8,12,1,0,0,0,0,0,15,16,11,0,0,0,0,0,10,3,14,1,0,0,0,0,0,0,11,2,0,0,0,0,0,0,14,3,0,0,0,0,0,4,16,1,0,0,0,0,6,16,16,16,11,1,0,0,5,16,13,12,12,6,2 +0,0,9,16,10,0,0,0,0,1,15,15,16,3,0,0,0,1,16,6,13,8,0,0,0,0,10,5,11,9,0,0,0,0,0,0,14,8,0,0,0,0,0,3,16,5,0,0,0,0,9,16,16,16,14,4,0,0,9,16,14,12,16,8,2 +0,0,0,15,3,0,0,0,0,0,3,16,2,0,0,0,0,0,7,15,0,0,0,0,0,0,10,12,3,0,0,0,0,0,14,16,16,11,2,0,0,1,15,11,1,11,11,0,0,0,12,13,4,8,13,0,0,0,2,11,14,13,5,0,6 +0,0,5,14,16,6,0,0,0,2,15,10,7,15,0,0,0,0,3,0,9,7,0,0,0,0,0,6,16,5,0,0,0,0,0,2,11,16,3,0,0,0,0,0,0,11,7,0,0,0,8,8,7,15,3,0,0,0,7,15,13,6,0,0,3 +0,0,4,11,0,0,0,0,0,0,8,9,0,0,0,0,0,0,15,4,0,0,0,0,0,2,16,8,7,2,0,0,0,6,16,12,13,14,4,0,0,5,16,2,1,10,11,0,0,0,16,9,4,16,9,0,0,0,7,14,15,8,0,0,6 +0,0,9,15,15,7,0,0,0,3,16,7,7,11,0,0,0,0,16,10,3,6,0,0,0,0,7,16,16,15,4,0,0,0,9,16,16,3,0,0,0,1,15,4,11,10,0,0,0,4,14,3,9,11,0,0,0,0,8,15,15,4,0,0,8 +0,1,8,16,14,0,0,0,0,4,15,9,16,3,0,0,0,1,4,7,16,0,0,0,0,0,0,11,16,9,0,0,0,0,0,0,5,15,5,0,0,0,0,0,0,15,5,0,0,0,15,9,9,15,2,0,0,0,6,13,10,4,0,0,3 +0,0,0,0,7,7,0,0,0,0,0,4,15,1,0,0,0,0,0,13,6,9,1,0,0,0,8,12,0,14,3,0,0,3,16,3,2,16,0,0,0,5,16,16,13,16,4,0,0,0,1,7,12,14,2,0,0,0,0,0,9,10,0,0,4 +0,0,7,12,16,16,7,0,0,0,14,14,11,16,7,0,0,0,2,0,7,16,3,0,0,0,2,11,15,16,4,0,0,0,13,16,16,16,7,0,0,0,4,15,12,0,0,0,0,0,6,16,3,0,0,0,0,0,9,15,2,0,0,0,7 +0,0,0,6,16,0,0,0,0,0,0,9,15,0,0,0,0,0,2,15,8,14,0,0,0,0,12,14,8,16,0,0,0,9,16,12,14,14,3,0,0,8,16,16,16,15,8,0,0,1,4,10,16,5,0,0,0,0,0,5,16,4,0,0,4 +0,0,6,13,12,9,1,0,0,4,16,11,6,13,7,0,0,7,16,6,2,13,8,0,0,1,14,15,16,13,3,0,0,0,7,16,16,5,0,0,0,2,16,9,13,14,0,0,0,1,15,8,7,16,3,0,0,0,6,16,15,7,0,0,8 +0,2,10,13,3,0,0,0,0,10,15,10,14,8,0,0,0,11,14,4,13,14,0,0,0,2,9,12,12,16,4,0,0,0,0,0,0,16,7,0,0,0,0,0,0,12,12,0,0,2,12,4,5,15,11,0,0,0,9,13,12,11,1,0,9 +0,0,2,15,8,0,0,0,0,0,9,14,16,6,0,0,0,0,15,9,3,14,3,0,0,4,16,2,0,9,7,0,0,8,12,0,0,7,8,0,0,3,14,1,0,8,9,0,0,0,15,15,13,15,7,0,0,0,3,14,15,9,0,0,0 +0,0,0,7,14,1,0,0,0,0,0,9,11,0,0,0,0,0,0,12,7,4,8,0,0,0,5,15,2,16,5,0,0,3,16,7,6,16,0,0,0,11,16,15,15,16,2,0,0,0,2,8,16,9,0,0,0,0,0,5,16,2,0,0,4 +0,3,15,16,11,1,0,0,0,12,13,10,16,4,0,0,0,5,1,8,16,1,0,0,0,0,0,16,16,6,0,0,0,0,0,7,13,16,5,0,0,0,0,0,1,15,13,0,0,4,11,8,8,15,13,0,0,2,14,16,16,10,1,0,3 +0,1,12,14,6,0,0,0,0,8,14,5,15,7,0,0,0,6,11,1,11,15,2,0,0,1,14,16,13,16,8,0,0,0,0,0,0,12,8,0,0,1,3,0,0,11,9,0,0,4,13,2,1,13,6,0,0,0,10,16,16,9,0,0,9 +0,0,0,12,11,0,0,0,0,0,5,16,8,0,0,0,0,0,12,11,0,0,0,0,0,0,15,8,4,1,0,0,0,3,16,16,16,15,5,0,0,2,16,3,1,9,13,0,0,0,12,9,4,13,13,0,0,0,0,11,16,13,3,0,6 +0,0,4,15,15,5,0,0,0,1,16,12,11,15,3,0,0,7,16,2,4,16,4,0,0,5,16,16,16,16,5,0,0,0,0,1,0,16,8,0,0,0,0,0,0,14,8,0,0,0,10,13,8,16,5,0,0,0,7,13,14,9,1,0,9 +0,0,5,15,8,1,0,0,0,1,15,13,15,9,0,0,0,8,15,1,3,14,1,0,0,4,14,0,0,11,8,0,0,5,12,0,0,12,8,0,0,5,15,1,0,12,10,0,0,1,16,10,9,15,3,0,0,0,5,15,15,4,0,0,0 +0,0,0,2,13,2,0,0,0,0,0,14,10,0,0,0,0,0,11,10,0,10,4,0,0,4,16,3,1,14,3,0,0,6,16,16,16,16,5,0,0,0,5,8,14,11,1,0,0,0,0,0,15,2,0,0,0,0,0,1,13,0,0,0,4 +0,0,0,1,15,5,0,0,0,0,0,11,15,0,7,1,0,0,8,15,2,5,16,0,0,3,15,8,4,12,11,0,0,10,16,16,16,16,7,0,0,2,8,8,13,16,1,0,0,0,0,0,14,10,0,0,0,0,0,2,14,3,0,0,4 +0,2,16,10,1,0,0,0,0,3,16,16,8,0,0,0,0,1,11,11,12,0,0,0,0,0,0,4,16,0,0,0,0,0,0,9,12,0,0,0,0,0,5,15,8,0,0,0,0,5,16,16,16,16,13,0,0,2,16,16,16,16,14,0,2 +0,1,7,9,12,14,1,0,0,11,16,15,10,5,1,0,0,2,16,7,0,0,0,0,0,0,9,16,16,9,0,0,0,0,0,2,8,13,6,0,0,0,0,0,0,8,11,0,0,0,7,13,14,16,4,0,0,0,10,16,12,3,0,0,5 +0,1,10,16,14,3,0,0,0,4,16,13,15,14,0,0,0,1,1,0,9,16,2,0,0,0,5,14,16,13,0,0,0,0,7,16,16,15,2,0,0,0,0,0,4,15,9,0,0,1,13,8,13,16,5,0,0,1,14,16,14,7,0,0,3 +0,0,0,11,16,8,0,0,0,0,9,15,5,3,0,0,0,0,14,6,0,0,0,0,0,4,14,0,0,0,0,0,0,5,11,2,4,1,0,0,0,4,14,15,15,15,3,0,0,1,11,16,11,11,15,0,0,0,1,11,15,16,12,0,6 +0,3,16,14,10,5,0,0,0,1,8,8,11,16,2,0,0,0,2,6,12,11,1,0,0,0,12,16,16,6,0,0,0,0,0,3,11,16,2,0,0,0,0,0,0,14,7,0,0,1,8,9,14,13,0,0,0,3,16,12,9,1,0,0,3 +0,0,2,11,13,1,0,0,0,0,11,15,8,1,0,0,0,1,16,5,0,0,0,0,0,4,15,0,0,0,0,0,0,3,16,10,12,10,2,0,0,2,16,15,11,12,14,0,0,0,14,12,7,11,16,2,0,0,3,11,16,14,6,0,6 +0,0,7,13,5,0,0,0,0,4,14,4,14,13,0,0,0,0,13,8,12,16,4,0,0,0,5,12,11,6,6,0,0,0,0,0,0,5,9,0,0,0,0,0,0,9,7,0,0,0,2,2,8,16,2,0,0,0,10,16,12,3,0,0,9 +0,0,8,15,15,1,0,0,0,4,16,16,16,10,0,0,0,7,12,0,1,16,0,0,0,6,11,0,0,9,7,0,0,8,12,0,0,9,4,0,0,4,16,1,2,15,8,0,0,0,15,16,16,16,1,0,0,0,7,16,16,8,0,0,0 +0,3,15,16,3,0,0,0,0,11,16,15,12,0,0,0,0,1,1,6,16,0,0,0,0,0,0,5,16,0,0,0,0,0,0,14,12,0,0,0,0,0,9,16,9,8,5,0,0,3,16,16,16,16,11,0,0,5,16,16,13,8,1,0,2 +0,0,7,16,16,16,13,0,0,0,6,9,11,15,13,0,0,0,0,0,2,16,5,0,0,0,0,5,13,16,4,0,0,0,9,16,16,16,8,0,0,0,3,14,11,0,0,0,0,0,3,16,5,0,0,0,0,0,7,15,0,0,0,0,7 +0,0,4,12,15,3,0,0,0,3,16,14,14,13,0,0,0,5,16,1,2,15,5,0,0,8,16,0,0,9,11,0,0,5,16,0,0,8,12,0,0,0,15,2,0,11,13,0,0,0,10,12,12,16,9,0,0,0,4,16,16,9,0,0,0 +0,0,6,11,15,16,12,0,0,0,6,8,5,12,10,0,0,0,0,0,2,15,2,0,0,0,7,12,13,15,4,0,0,0,6,10,16,9,4,0,0,0,0,13,7,0,0,0,0,0,5,15,2,0,0,0,0,0,10,10,0,0,0,0,7 +0,0,2,11,12,12,15,8,0,0,5,12,12,13,15,2,0,0,0,0,0,7,10,0,0,0,0,4,12,16,8,0,0,0,1,16,16,12,3,0,0,0,0,4,13,1,0,0,0,0,1,12,6,0,0,0,0,0,2,14,1,0,0,0,7 +0,0,9,15,16,16,10,0,0,0,15,11,11,16,4,0,0,0,0,0,12,11,0,0,0,0,2,8,16,16,12,0,0,0,14,16,13,8,2,0,0,0,5,16,6,0,0,0,0,0,8,14,0,0,0,0,0,0,13,9,0,0,0,0,7 +0,0,8,13,11,4,0,0,0,3,16,8,8,15,2,0,0,6,16,8,9,16,8,0,0,0,10,16,13,13,8,0,0,0,0,0,0,11,7,0,0,0,0,0,2,16,3,0,0,0,3,9,15,7,0,0,0,0,10,10,3,0,0,0,9 +0,3,12,11,6,0,0,0,0,11,15,8,16,8,0,0,0,10,11,1,13,15,1,0,0,2,14,16,15,15,8,0,0,0,1,4,0,14,2,0,0,0,0,0,3,16,1,0,0,1,11,9,14,8,0,0,0,2,15,13,10,0,0,0,9 +0,0,7,16,15,5,0,0,0,0,15,7,6,15,2,0,0,3,15,4,5,13,2,0,0,2,15,16,16,15,0,0,0,0,8,16,15,15,3,0,0,0,11,13,1,6,7,0,0,0,16,12,8,11,4,0,0,0,7,15,16,12,2,0,8 +0,0,4,14,15,9,0,0,0,0,14,9,0,2,0,0,0,2,15,1,0,0,0,0,0,5,9,0,0,0,0,0,0,7,12,10,15,7,0,0,0,4,16,15,3,11,6,0,0,0,13,15,14,11,10,0,0,0,5,14,16,11,2,0,6 +0,0,6,12,15,10,0,0,0,5,15,5,4,11,6,0,0,7,11,4,5,14,6,0,0,2,16,16,16,6,0,0,0,0,15,15,12,14,1,0,0,0,14,3,0,9,6,0,0,0,16,7,6,14,6,0,0,0,7,15,12,9,0,0,8 +0,4,14,15,7,1,0,0,0,8,14,12,16,4,0,0,0,0,0,0,12,8,0,0,0,0,2,9,16,7,0,0,0,0,3,13,13,16,5,0,0,0,0,0,0,12,8,0,0,3,11,7,12,16,4,0,0,4,14,16,11,4,0,0,3 +0,0,5,14,15,6,0,0,0,1,16,8,8,15,2,0,0,0,16,10,10,15,5,0,0,0,11,16,16,9,0,0,0,0,6,16,16,16,3,0,0,0,9,13,0,10,7,0,0,0,12,10,8,15,2,0,0,0,7,16,13,5,0,0,8 +0,1,8,14,16,5,0,0,0,5,15,7,11,11,0,0,0,0,1,0,11,9,0,0,0,0,3,11,16,3,0,0,0,0,12,16,16,15,3,0,0,0,2,1,1,12,8,0,0,0,5,9,14,15,3,0,0,0,11,12,8,1,0,0,3 +0,0,3,14,13,1,0,0,0,0,12,12,3,2,0,0,0,1,16,2,0,0,0,0,0,4,14,0,0,0,0,0,0,2,14,0,2,2,0,0,0,2,14,14,16,16,8,0,0,0,11,16,11,11,16,2,0,0,1,10,12,12,9,0,6 +0,3,7,13,16,12,2,0,0,10,16,12,6,2,0,0,0,9,16,8,1,0,0,0,0,4,11,16,15,6,0,0,0,0,0,1,8,15,5,0,0,0,0,0,1,14,7,0,0,0,11,13,16,15,1,0,0,0,10,12,8,3,0,0,5 +0,0,5,11,15,5,0,0,0,6,13,2,3,15,3,0,0,7,13,8,11,16,8,0,0,1,9,12,8,8,8,0,0,0,0,0,1,13,4,0,0,0,0,0,9,12,0,0,0,0,0,8,13,0,0,0,0,0,7,10,1,0,0,0,9 +0,0,0,6,15,1,0,0,0,0,6,16,8,3,7,0,0,1,16,12,0,12,11,0,0,5,16,16,13,16,12,0,0,3,12,15,16,16,7,0,0,0,0,0,14,10,0,0,0,0,0,3,16,3,0,0,0,0,0,8,14,1,0,0,4 +0,0,0,10,14,9,0,0,0,0,10,14,6,4,0,0,0,1,16,5,0,0,0,0,0,6,12,0,0,0,0,0,0,7,10,1,4,1,0,0,0,2,15,16,14,14,1,0,0,0,12,12,4,14,11,0,0,0,1,10,13,12,4,0,6 +0,1,9,12,12,13,16,4,0,1,11,12,12,16,13,1,0,0,0,0,7,16,2,0,0,0,8,12,15,13,0,0,0,0,16,16,16,12,0,0,0,0,0,13,10,0,0,0,0,0,7,16,5,0,0,0,0,0,12,15,2,0,0,0,7 +0,0,6,16,16,13,7,0,0,0,14,12,16,16,9,0,0,0,0,0,5,15,1,0,0,0,0,4,11,14,6,0,0,0,3,16,16,16,10,0,0,0,1,12,13,4,1,0,0,0,3,15,5,0,0,0,0,0,7,15,0,0,0,0,7 +0,0,3,15,13,1,0,0,0,1,15,12,13,10,0,0,0,3,16,1,1,12,2,0,0,6,12,0,0,4,6,0,0,4,11,0,0,4,8,0,0,0,13,3,5,13,8,0,0,0,10,15,16,16,5,0,0,0,2,14,12,5,0,0,0 +0,0,0,10,10,0,0,0,0,0,2,15,7,0,7,1,0,1,13,13,0,5,16,3,0,6,16,12,8,14,14,0,0,4,15,16,16,16,9,0,0,0,0,3,15,13,0,0,0,0,0,6,16,4,0,0,0,0,0,12,8,0,0,0,4 +0,0,8,16,8,0,0,0,0,6,14,5,13,6,0,0,0,7,12,2,7,16,2,0,0,0,12,16,16,16,6,0,0,0,0,3,3,9,8,0,0,0,0,0,5,16,3,0,0,0,3,9,16,8,0,0,0,0,10,11,4,0,0,0,9 +0,1,15,15,1,0,0,0,0,7,16,16,11,0,0,0,0,3,7,8,13,0,0,0,0,0,0,7,16,2,0,0,0,0,2,15,9,0,0,0,0,0,8,16,5,0,3,0,0,3,15,16,16,16,16,0,0,2,16,16,16,16,10,0,2 +0,0,7,12,12,7,0,0,0,0,11,16,16,11,0,0,0,0,10,16,16,7,0,0,0,0,12,16,16,6,0,0,0,0,12,16,16,7,0,0,0,0,13,16,16,11,0,0,0,0,16,16,16,13,0,0,0,0,6,10,8,3,0,0,1 +0,0,7,12,13,1,0,0,0,6,16,6,8,7,0,0,0,0,16,10,11,8,0,0,0,0,8,15,16,5,0,0,0,0,10,15,12,14,1,0,0,0,15,7,0,11,7,0,0,0,15,8,8,15,7,0,0,0,6,12,12,8,1,0,8 +0,0,8,15,11,0,0,0,0,7,16,15,15,9,0,0,0,10,14,0,3,12,2,0,0,5,12,0,0,5,7,0,0,4,11,0,0,7,8,0,0,3,11,0,6,16,6,0,0,0,14,13,16,14,1,0,0,0,8,15,11,2,0,0,0 +0,0,9,16,14,2,0,0,0,0,10,16,16,4,0,0,0,4,16,16,16,0,0,0,0,0,14,16,16,2,0,0,0,1,16,16,16,3,0,0,0,1,16,16,16,0,0,0,0,3,16,16,16,7,0,0,0,1,9,15,16,8,0,0,1 +0,0,9,15,2,0,0,0,0,3,16,14,13,1,0,0,0,0,6,0,14,2,0,0,0,0,0,1,15,0,0,0,0,0,0,5,14,0,0,0,0,0,1,12,9,0,0,0,0,0,12,16,16,16,16,2,0,0,8,13,11,8,7,0,2 +0,0,4,12,7,0,0,0,0,2,13,16,16,6,0,0,0,5,16,3,5,15,0,0,0,8,13,0,0,11,1,0,0,7,12,0,0,8,4,0,0,5,10,0,0,9,5,0,0,1,15,13,13,16,3,0,0,0,5,14,16,10,0,0,0 +0,0,8,15,14,6,0,0,0,4,16,5,6,16,3,0,0,7,16,2,3,16,8,0,0,2,14,16,16,16,8,0,0,0,0,2,0,9,8,0,0,0,0,0,5,16,2,0,0,0,0,8,14,5,0,0,0,0,9,8,1,0,0,0,9 +0,0,11,12,7,0,0,0,0,0,5,16,16,9,0,0,0,0,4,16,16,12,0,0,0,0,4,16,16,13,0,0,0,0,3,15,16,12,0,0,0,0,2,13,16,12,0,0,0,0,11,16,16,5,0,0,0,0,8,10,8,0,0,0,1 +0,0,5,8,8,10,13,4,0,0,10,12,12,14,14,1,0,0,0,0,0,12,5,0,0,0,2,4,7,16,0,0,0,0,10,16,16,10,0,0,0,0,0,8,11,0,0,0,0,0,0,16,7,0,0,0,0,0,7,16,3,0,0,0,7 +0,0,7,13,10,0,0,0,0,0,16,16,16,9,0,0,0,3,12,2,9,16,4,0,0,6,9,0,0,13,7,0,0,8,12,0,0,8,8,0,0,7,15,4,2,12,8,0,0,2,16,16,16,16,5,0,0,0,7,14,12,7,0,0,0 +0,0,3,12,11,7,0,0,0,0,8,16,16,6,0,0,0,3,16,16,16,6,0,0,0,4,16,16,15,1,0,0,0,1,8,9,16,4,0,0,0,1,15,16,16,9,0,0,0,0,8,16,16,12,0,0,0,0,4,11,11,6,0,0,1 +0,0,10,16,16,16,11,0,0,0,9,11,8,16,9,0,0,0,0,0,5,16,3,0,0,0,2,11,15,16,11,0,0,0,6,16,16,10,5,0,0,0,0,13,11,0,0,0,0,0,7,16,3,0,0,0,0,0,13,11,0,0,0,0,7 +0,0,9,15,8,0,0,0,0,6,14,2,7,10,0,0,0,9,10,0,2,16,3,0,0,5,16,16,16,14,2,0,0,6,16,16,16,14,1,0,0,5,16,0,0,9,11,0,0,1,16,9,7,14,6,0,0,0,9,12,13,8,0,0,8 +0,0,3,11,12,4,0,0,0,6,15,6,5,13,0,0,0,7,13,0,2,16,1,0,0,3,16,16,16,14,0,0,0,0,15,13,8,13,6,0,0,0,12,1,0,3,9,0,0,0,9,12,8,14,11,0,0,0,5,13,12,9,1,0,8 +0,0,0,10,16,6,0,0,0,0,4,16,16,4,0,0,0,0,14,16,16,4,0,0,0,0,16,16,16,8,0,0,0,0,16,16,16,8,0,0,0,0,15,16,16,14,3,0,0,0,10,16,16,16,8,0,0,0,1,12,12,15,7,0,1 +0,0,0,4,15,0,0,0,0,0,2,13,12,0,3,0,0,0,11,15,2,6,16,0,0,8,16,12,8,15,12,0,0,7,16,16,16,16,7,0,0,0,4,7,14,13,0,0,0,0,0,1,16,9,0,0,0,0,0,3,16,3,0,0,4 +0,3,12,15,16,14,2,0,0,7,16,11,4,7,1,0,0,9,16,6,0,0,0,0,0,4,15,16,11,0,0,0,0,0,0,6,16,6,0,0,0,0,0,0,9,12,0,0,0,1,12,12,16,6,0,0,0,3,16,16,7,0,0,0,5 +0,0,7,16,13,2,0,0,0,2,15,16,16,11,0,0,0,7,14,4,6,16,0,0,0,4,12,0,0,7,7,0,0,6,12,0,1,11,8,0,0,2,15,8,13,16,9,0,0,0,15,16,16,15,2,0,0,0,5,14,11,1,0,0,0 +0,0,8,14,5,0,0,0,0,8,13,5,14,5,0,0,0,8,13,2,5,14,3,0,0,4,16,14,15,16,8,0,0,0,2,5,7,10,7,0,0,0,0,0,3,15,3,0,0,0,0,4,14,10,0,0,0,0,10,13,6,0,0,0,9 +0,1,8,15,10,0,0,0,0,4,16,16,16,9,0,0,0,0,12,16,16,12,0,0,0,0,10,16,16,8,0,0,0,0,8,16,16,10,0,0,0,0,9,16,16,13,0,0,0,0,8,16,16,12,0,0,0,0,5,15,16,9,1,0,1 +0,3,9,14,15,6,0,0,0,7,13,7,8,16,0,0,0,0,0,0,7,12,0,0,0,0,1,9,16,10,0,0,0,0,5,15,12,15,7,0,0,0,0,0,0,13,9,0,0,0,8,8,14,16,4,0,0,3,16,14,9,1,0,0,3 +0,3,11,13,8,1,0,0,0,3,11,8,15,7,0,0,0,0,0,2,14,6,0,0,0,0,7,16,16,11,0,0,0,0,5,12,11,16,8,0,0,0,0,0,6,16,7,0,0,2,12,16,16,7,0,0,0,4,13,11,2,0,0,0,3 +0,0,5,14,12,2,0,0,0,1,16,11,5,11,0,0,0,4,14,0,0,9,4,0,0,8,10,0,0,5,8,0,0,8,8,0,0,8,8,0,0,4,11,0,0,10,5,0,0,2,16,11,12,14,0,0,0,0,5,14,14,3,0,0,0 +0,0,4,15,12,2,0,0,0,0,13,16,16,14,0,0,0,7,16,3,2,15,2,0,0,8,16,4,0,4,8,0,0,8,15,3,0,6,8,0,0,4,15,4,2,13,10,0,0,0,13,16,16,16,6,0,0,0,4,15,16,9,0,0,0 +0,0,0,10,12,2,0,0,0,0,11,16,16,10,0,0,0,3,16,5,6,12,2,0,0,5,12,0,0,6,8,0,0,4,16,0,0,6,8,0,0,2,15,7,2,13,8,0,0,0,9,16,14,16,5,0,0,0,0,12,16,10,0,0,0 +0,0,4,16,3,0,0,0,0,0,8,16,2,0,1,0,0,0,11,13,3,15,5,0,0,2,16,6,11,15,0,0,0,11,16,12,16,13,4,0,1,12,12,15,15,11,2,0,0,0,1,16,5,0,0,0,0,0,6,16,0,0,0,0,4 +0,0,0,14,7,1,6,0,0,0,6,15,2,11,15,0,0,2,14,10,2,16,8,0,0,7,16,10,13,16,14,0,0,6,16,16,16,11,5,0,0,0,0,7,15,1,0,0,0,0,0,10,10,0,0,0,0,0,1,15,4,0,0,0,4 +0,0,0,8,12,0,0,0,0,0,0,15,5,11,7,0,0,0,8,12,1,16,3,0,0,3,15,5,6,14,0,0,0,12,15,10,15,16,7,0,0,12,16,16,16,10,3,0,0,0,2,7,13,0,0,0,0,0,0,11,10,0,0,0,4 +0,0,4,13,15,6,0,0,0,4,15,6,7,15,2,0,0,8,14,0,4,16,5,0,0,2,15,10,14,7,0,0,0,0,7,16,14,0,0,0,0,0,12,9,15,6,0,0,0,0,12,8,11,8,0,0,0,0,3,15,15,3,0,0,8 +0,0,4,8,13,6,0,0,0,5,16,11,9,12,1,0,0,7,13,0,12,16,4,0,0,6,14,9,15,12,3,0,0,0,8,11,10,14,0,0,0,0,0,0,16,6,0,0,0,0,0,8,12,0,0,0,0,0,6,10,0,0,0,0,9 +0,0,7,12,15,4,0,0,0,4,16,9,7,12,0,0,0,0,5,0,10,8,0,0,0,0,0,13,16,4,0,0,0,0,0,10,9,16,3,0,0,0,0,0,0,11,7,0,0,0,8,6,6,15,3,0,0,0,8,15,12,3,0,0,3 +0,0,11,13,1,0,0,0,0,5,16,14,10,0,0,0,0,9,10,8,12,0,0,0,0,5,3,10,7,0,0,0,0,0,2,15,2,0,0,0,0,0,11,10,1,5,3,0,0,0,16,12,14,16,9,0,0,0,13,16,11,3,0,0,2 +0,0,6,12,14,16,16,4,0,3,16,15,12,15,16,4,0,1,5,0,1,16,11,0,0,0,0,0,9,15,3,0,0,0,0,4,16,8,0,0,0,0,0,12,16,0,0,0,0,0,6,16,6,0,0,0,0,0,10,15,0,0,0,0,7 +0,0,4,16,13,13,5,0,0,0,9,15,7,4,2,0,0,3,14,9,0,0,0,0,0,5,16,16,13,5,0,0,0,0,0,2,10,15,1,0,0,0,0,0,5,15,1,0,0,0,1,4,14,6,0,0,0,0,2,16,10,0,0,0,5 +0,0,0,16,14,5,0,0,0,0,7,16,16,7,0,0,0,0,7,16,16,1,0,0,0,0,12,16,13,0,0,0,0,0,14,16,10,0,0,0,0,0,14,16,8,0,0,0,0,0,11,16,12,0,0,0,0,0,2,10,16,7,0,0,1 +0,1,15,16,16,16,15,2,0,0,12,10,9,16,14,2,0,0,0,0,9,16,4,0,0,0,0,5,15,6,0,0,0,0,0,13,12,0,0,0,0,0,9,15,2,0,0,0,0,1,16,10,0,0,0,0,0,3,16,7,0,0,0,0,7 +0,0,2,13,16,4,0,0,0,0,12,12,10,13,0,0,0,4,16,2,0,15,2,0,0,5,16,1,0,8,8,0,0,8,12,0,0,8,8,0,0,5,15,1,0,9,8,0,0,1,15,11,8,16,3,0,0,0,4,15,14,5,0,0,0 +0,0,8,16,9,0,0,0,0,4,15,10,16,0,0,0,0,5,5,7,12,0,0,0,0,0,0,16,14,9,0,0,0,0,0,8,8,15,7,0,0,0,0,0,0,11,8,0,0,0,15,5,7,15,3,0,0,0,6,13,13,6,0,0,3 +0,0,0,0,7,16,11,0,0,0,0,1,16,16,7,0,0,0,4,15,16,5,0,0,0,4,16,16,15,0,0,0,0,1,8,16,16,3,0,0,0,0,0,10,16,9,0,0,0,0,0,4,16,12,0,0,0,0,0,0,7,12,0,0,1 +0,0,5,14,16,9,0,0,0,0,8,15,12,14,3,0,0,3,15,12,0,0,0,0,0,12,16,7,0,0,0,0,0,8,14,16,10,1,0,0,0,0,1,8,16,4,0,0,0,0,3,11,16,3,0,0,0,0,5,16,12,0,0,0,5 +0,0,0,11,13,1,3,0,0,0,0,15,8,13,13,0,0,0,9,15,4,16,7,0,0,5,16,13,12,16,8,0,0,8,16,16,16,15,7,0,0,0,0,6,16,4,0,0,0,0,0,7,13,0,0,0,0,0,0,11,7,0,0,0,4 +0,1,7,15,10,0,0,0,0,6,14,7,16,2,0,0,0,6,14,12,16,13,0,0,0,1,12,16,11,0,0,0,0,0,0,13,16,3,0,0,0,0,4,12,3,14,2,0,0,0,8,9,0,11,8,0,0,0,6,16,16,11,2,0,8 +0,0,0,10,8,0,0,0,0,0,2,16,11,1,0,0,0,0,6,15,1,0,0,0,0,0,10,11,0,0,0,0,0,0,13,12,8,6,0,0,0,0,13,16,16,16,12,1,0,0,9,16,13,11,16,4,0,0,0,10,15,12,5,0,6 +0,0,0,9,12,2,0,0,0,0,2,13,16,3,0,0,0,0,9,16,16,1,0,0,0,5,15,14,16,5,0,0,0,2,1,12,16,5,0,0,0,0,0,10,16,4,0,0,0,0,0,12,15,3,0,0,0,0,0,7,15,13,1,0,1 +0,0,2,8,12,13,9,1,0,0,15,16,14,16,16,0,0,4,16,14,13,16,12,0,0,0,13,14,11,16,10,0,0,0,0,0,5,16,7,0,0,0,0,0,13,15,3,0,0,0,0,10,15,2,0,0,0,0,1,16,5,0,0,0,9 +0,0,9,15,6,0,0,0,0,1,16,6,14,2,0,0,0,0,14,1,8,8,0,0,0,0,7,7,5,9,0,0,0,0,0,0,9,5,0,0,0,0,0,1,14,2,0,0,0,0,5,15,12,11,6,0,0,0,15,14,12,8,8,0,2 +0,0,11,16,16,13,4,0,0,2,16,14,9,8,8,0,0,7,16,4,0,0,0,0,0,12,16,16,12,1,0,0,0,2,8,10,16,9,0,0,0,0,1,0,12,12,0,0,0,0,15,12,16,6,0,0,0,0,13,16,9,0,0,0,5 +0,1,11,16,13,1,0,0,0,9,16,10,15,8,0,0,0,7,13,1,12,11,0,0,0,0,0,5,15,9,0,0,0,0,0,16,16,16,7,0,0,0,0,7,3,13,12,0,0,0,7,8,10,16,5,0,0,0,13,16,16,6,0,0,3 +0,2,13,15,5,0,0,0,0,9,16,13,14,0,0,0,0,7,6,2,16,0,0,0,0,0,0,3,16,1,0,0,0,0,0,11,12,0,0,0,0,0,5,16,7,0,0,0,0,2,16,16,9,11,11,1,0,2,15,16,16,16,11,1,2 +0,0,0,9,13,1,0,0,0,0,3,15,6,12,0,0,0,1,10,9,0,10,3,0,0,4,16,5,0,5,7,0,0,5,16,3,0,6,8,0,0,0,16,5,0,6,9,0,0,0,8,14,7,15,3,0,0,0,0,10,16,9,0,0,0 +0,0,2,8,12,5,0,0,0,0,3,16,16,12,0,0,0,0,5,16,16,8,0,0,0,0,5,16,16,11,0,0,0,0,9,16,16,5,0,0,0,0,8,16,16,8,0,0,0,0,6,16,16,10,0,0,0,0,1,10,10,7,0,0,1 +0,0,11,16,13,2,0,0,0,7,16,11,12,14,0,0,0,9,13,0,11,14,0,0,0,0,0,11,16,14,2,0,0,0,0,10,9,15,10,0,0,0,0,0,0,9,12,0,0,0,14,8,9,16,6,0,0,0,11,16,16,10,0,0,3 +0,0,0,4,9,15,2,0,0,0,6,15,11,13,4,0,0,3,16,4,4,15,0,0,0,0,15,16,16,16,1,0,0,0,2,4,3,15,6,0,0,0,0,0,1,16,1,0,0,0,0,3,12,5,0,0,0,0,0,7,10,0,0,0,9 +0,0,0,0,8,16,7,0,0,0,0,6,16,16,12,0,0,0,8,16,16,16,12,0,0,5,12,8,12,16,8,0,0,0,0,0,12,16,5,0,0,0,0,0,15,16,1,0,0,0,0,0,16,16,0,0,0,0,0,0,11,16,2,0,1 +0,2,15,11,1,0,0,0,0,13,15,15,8,0,0,0,0,16,9,8,11,0,0,0,0,7,1,10,9,0,0,0,0,0,0,13,7,0,0,0,0,0,2,16,5,0,0,0,0,1,14,15,16,16,12,2,0,2,13,16,14,11,6,0,2 +0,0,0,9,16,10,1,0,0,0,9,13,4,14,8,0,0,4,15,6,10,16,7,0,0,5,16,14,11,16,4,0,0,0,0,0,5,15,1,0,0,0,0,1,14,7,0,0,0,0,0,7,12,0,0,0,0,0,0,11,8,0,0,0,9 +0,0,4,11,16,11,1,0,0,3,15,7,5,14,4,0,0,8,13,0,14,16,5,0,0,0,15,16,11,16,5,0,0,0,1,2,5,13,0,0,0,0,0,1,13,4,0,0,0,0,1,13,7,0,0,0,0,0,6,14,0,0,0,0,9 +0,0,1,12,15,5,0,0,0,0,12,11,4,3,0,0,0,1,15,2,0,0,0,0,0,5,12,0,0,0,0,0,0,3,16,16,11,2,0,0,0,2,16,15,8,12,0,0,0,0,10,9,1,15,5,0,0,0,0,12,16,10,0,0,6 +0,0,1,12,16,14,1,0,0,0,4,16,16,16,4,0,0,2,14,16,16,16,6,0,0,2,12,16,16,10,0,0,0,0,8,16,16,8,0,0,0,0,11,16,16,5,0,0,0,0,8,16,16,4,0,0,0,0,1,15,16,8,0,0,1 +0,0,0,10,10,0,0,0,0,0,3,15,5,5,0,0,0,0,11,10,8,12,0,0,0,5,16,5,13,10,2,0,0,11,16,16,16,15,8,0,0,0,4,9,14,1,0,0,0,0,0,9,9,0,0,0,0,0,0,13,4,0,0,0,4 +0,0,2,14,7,0,0,0,0,1,9,12,13,6,0,0,0,5,16,8,2,14,0,0,0,6,16,2,0,12,6,0,0,5,15,1,0,9,9,0,0,0,16,1,0,12,8,0,0,0,11,12,8,15,1,0,0,0,3,13,15,7,0,0,0 +0,0,0,14,4,5,1,0,0,0,6,13,1,15,5,0,0,0,12,7,2,16,2,0,0,7,15,6,10,16,6,0,0,12,16,16,16,13,6,0,0,2,7,8,15,3,0,0,0,0,0,8,10,0,0,0,0,0,0,16,4,0,0,0,4 +0,1,10,14,16,16,15,1,0,1,12,11,8,11,15,2,0,0,0,0,1,14,8,0,0,0,0,0,11,11,1,0,0,0,0,5,16,2,0,0,0,0,1,16,6,0,0,0,0,0,7,14,1,0,0,0,0,0,15,11,0,0,0,0,7 +0,0,5,12,10,0,0,0,0,2,16,9,11,2,0,0,0,0,16,5,8,14,0,0,0,1,14,16,16,3,0,0,0,0,3,16,16,4,0,0,0,0,11,12,15,11,0,0,0,2,16,7,2,16,2,0,0,0,9,15,13,11,1,0,8 +0,0,5,14,0,0,0,0,0,0,9,11,1,3,0,0,0,2,14,4,10,11,0,0,0,8,16,12,15,14,6,0,0,9,16,13,16,10,5,0,0,0,0,9,11,0,0,0,0,0,0,15,5,0,0,0,0,0,3,16,3,0,0,0,4 +0,0,2,11,16,16,16,12,0,0,7,15,9,8,12,13,0,0,0,0,0,1,14,5,0,0,0,0,0,11,9,0,0,0,0,0,6,13,0,0,0,0,0,1,14,3,0,0,0,0,0,10,9,0,0,0,0,0,1,16,5,0,0,0,7 +0,0,4,14,11,1,0,0,0,4,16,12,10,8,0,0,0,9,14,1,7,7,0,0,0,1,2,0,15,9,0,0,0,0,0,0,7,15,2,0,0,0,0,0,0,5,11,0,0,0,12,3,4,10,11,0,0,0,6,15,15,11,1,0,3 +0,0,1,11,15,16,5,0,0,0,13,15,13,16,9,0,0,1,16,14,15,16,8,0,0,0,13,16,13,15,15,0,0,0,0,0,1,14,9,0,0,0,0,0,9,14,1,0,0,0,0,9,15,2,0,0,0,0,0,15,7,0,0,0,9 +0,0,5,11,13,10,3,0,0,1,13,11,5,6,3,0,0,6,15,0,0,0,0,0,0,7,13,4,1,0,0,0,0,1,14,16,15,4,0,0,0,0,0,0,8,13,0,0,0,0,8,5,5,15,0,0,0,0,4,12,16,9,0,0,5 +0,2,11,16,16,16,13,0,0,5,12,10,8,13,16,0,0,0,0,0,1,15,11,0,0,0,0,0,13,13,0,0,0,0,0,8,16,2,0,0,0,0,3,15,8,0,0,0,0,0,11,15,0,0,0,0,0,2,16,10,0,0,0,0,7 +0,0,0,6,14,9,0,0,0,0,8,15,8,2,0,0,0,3,15,9,0,0,0,0,0,7,15,0,0,0,0,0,0,7,16,4,6,3,0,0,0,1,16,15,12,15,5,0,0,0,7,14,6,11,14,0,0,0,0,5,13,15,8,0,6 +0,0,8,11,16,14,3,0,0,0,16,14,8,8,3,0,0,4,16,8,0,0,0,0,0,4,16,14,4,0,0,0,0,0,0,9,16,7,0,0,0,0,0,0,14,12,0,0,0,3,15,5,15,7,0,0,0,0,11,16,11,1,0,0,5 +0,0,12,8,2,0,0,0,0,0,16,6,14,6,0,0,0,2,16,10,16,0,0,0,0,10,13,14,10,4,1,0,0,8,16,16,16,16,10,0,0,0,13,15,4,4,1,0,0,0,12,12,0,0,0,0,0,1,14,5,0,0,0,0,4 +0,0,0,3,13,14,1,0,0,0,3,15,16,16,0,0,0,5,16,16,16,14,0,0,0,0,0,4,16,14,0,0,0,0,0,4,16,14,0,0,0,0,0,4,16,10,0,0,0,0,0,4,16,8,0,0,0,0,0,2,12,16,5,0,1 +0,0,7,12,14,11,3,0,0,0,12,13,5,5,3,0,0,1,16,7,0,0,0,0,0,5,16,16,12,1,0,0,0,0,0,1,12,10,0,0,0,0,0,0,1,14,0,0,0,0,11,4,8,11,0,0,0,0,10,16,11,2,0,0,5 +0,0,7,15,15,5,0,0,0,0,15,13,12,15,5,0,0,0,10,14,12,16,3,0,0,0,5,16,16,9,0,0,0,2,15,16,16,3,0,0,0,7,14,2,12,14,1,0,0,3,15,9,12,16,4,0,0,0,5,12,13,8,1,0,8 +0,0,9,16,8,0,0,0,0,9,13,4,13,0,0,0,0,7,6,1,14,0,0,0,0,0,0,9,16,9,0,0,0,0,0,8,6,13,6,0,0,0,0,0,0,4,11,0,0,0,6,0,3,12,5,0,0,0,9,16,16,7,0,0,3 +0,0,6,15,16,16,5,0,0,3,16,14,8,4,1,0,0,10,16,11,3,0,0,0,0,11,16,16,15,3,0,0,0,1,2,1,14,12,0,0,0,0,0,0,7,16,1,0,0,0,5,13,11,15,0,0,0,0,7,16,15,3,0,0,5 +0,0,1,7,12,14,5,0,0,0,8,13,1,8,8,0,0,2,16,6,16,16,9,0,0,0,7,8,6,12,10,0,0,0,0,0,0,12,7,0,0,0,0,0,0,16,2,0,0,0,0,0,9,10,0,0,0,0,0,8,11,4,0,0,9 +0,0,0,9,15,13,2,0,0,0,10,12,4,11,8,0,0,3,15,3,6,15,6,0,0,5,16,14,12,15,7,0,0,0,0,0,0,14,5,0,0,0,0,0,9,12,0,0,0,0,0,8,12,0,0,0,0,0,0,12,5,0,0,0,9 +0,0,4,16,13,2,0,0,0,0,11,7,2,13,0,0,0,3,16,4,0,9,1,0,0,5,13,2,0,5,7,0,0,4,8,0,0,4,8,0,0,4,14,0,0,6,8,0,0,0,13,10,1,14,3,0,0,0,4,12,16,10,0,0,0 +0,1,12,16,6,0,0,0,0,9,16,14,14,3,0,0,0,12,12,0,16,7,0,0,0,2,0,0,15,6,0,0,0,0,0,4,15,1,0,0,0,0,1,12,14,0,0,0,0,2,15,16,14,9,2,0,0,1,13,16,16,16,16,3,2 +0,0,0,13,4,0,0,0,0,0,4,16,9,9,0,0,0,0,12,8,10,9,0,0,0,7,15,4,15,8,1,0,0,11,16,16,16,16,10,0,0,1,4,10,12,0,0,0,0,0,0,12,8,0,0,0,0,0,0,12,5,0,0,0,4 +0,0,2,12,14,1,0,0,0,0,14,9,11,14,7,0,0,5,12,0,8,11,1,0,0,4,14,8,13,1,0,0,0,0,9,16,4,0,0,0,0,0,7,13,13,1,0,0,0,0,8,7,14,6,0,0,0,0,4,14,12,4,0,0,8 +0,0,2,12,8,0,0,0,0,0,13,11,1,0,0,0,0,2,15,1,0,0,0,0,0,6,13,0,0,0,0,0,0,8,14,10,11,5,0,0,0,4,16,15,8,12,7,0,0,0,15,11,4,11,10,0,0,0,3,13,15,12,3,0,6 +0,0,1,14,10,0,0,0,0,0,9,16,6,0,0,0,0,0,13,16,1,0,0,0,0,0,16,11,0,0,0,0,0,2,16,15,11,6,1,0,0,3,16,16,16,16,11,0,0,0,12,16,14,16,11,0,0,0,1,10,15,11,2,0,6 +0,0,2,14,9,1,0,0,0,1,14,10,11,10,0,0,0,4,14,1,0,12,3,0,0,7,8,0,0,3,6,0,0,7,7,0,0,1,9,0,0,3,12,0,0,5,8,0,0,0,11,5,3,12,6,0,0,0,2,14,16,9,0,0,0 +0,0,0,6,13,9,0,0,0,1,15,16,7,3,0,0,0,7,15,3,0,0,0,0,0,7,16,16,8,0,0,0,0,2,11,5,12,7,0,0,0,0,0,0,2,14,0,0,0,0,0,5,11,16,1,0,0,0,0,4,15,9,0,0,5 +0,0,1,15,4,0,0,0,0,0,9,12,0,0,0,0,0,1,14,6,0,0,0,0,0,1,16,4,3,3,0,0,0,2,16,8,16,15,2,0,0,1,16,16,10,5,12,0,0,0,13,15,8,12,10,0,0,0,0,13,15,10,1,0,6 +0,0,8,13,6,0,0,0,0,2,16,12,11,10,0,0,0,0,16,16,14,8,1,0,0,0,13,11,0,0,0,0,0,2,15,4,0,0,0,0,0,0,16,13,0,0,0,0,0,2,15,16,2,0,0,0,0,0,11,16,4,0,0,0,8 +0,0,8,13,11,5,0,0,0,4,16,14,16,10,0,0,0,1,14,16,7,0,0,0,0,0,11,16,14,2,0,0,0,2,16,3,8,15,1,0,0,6,15,2,1,16,5,0,0,5,16,9,11,16,3,0,0,0,9,15,15,7,0,0,8 +0,0,6,13,10,0,0,0,0,3,16,11,14,6,0,0,0,9,11,0,12,12,0,0,0,4,4,0,9,12,0,0,0,0,0,4,16,3,0,0,0,0,0,13,15,3,0,0,0,0,8,16,15,16,11,0,0,0,4,14,10,2,0,0,2 +0,0,5,16,4,0,0,0,0,0,13,16,1,0,0,0,0,2,16,11,0,0,0,0,0,8,16,12,10,16,6,0,0,2,13,16,16,16,6,0,0,0,1,14,16,6,0,0,0,0,5,16,11,0,0,0,0,0,5,16,7,0,0,0,4 +0,0,0,10,15,4,0,0,0,0,5,16,14,3,0,0,0,0,12,15,1,0,0,0,0,1,15,8,0,0,0,0,0,4,16,12,2,0,0,0,0,3,16,16,15,4,0,0,0,0,12,14,16,10,0,0,0,0,1,9,15,10,0,0,6 +0,0,0,13,7,0,0,0,0,0,3,16,9,0,0,0,0,0,11,15,2,6,4,0,0,4,16,14,13,16,12,0,0,11,16,16,16,16,3,0,0,3,8,10,16,9,0,0,0,0,0,11,16,2,0,0,0,0,1,13,13,0,0,0,4 +0,0,10,13,16,15,4,0,0,0,0,6,9,15,12,0,0,0,0,0,0,16,8,0,0,0,0,0,8,16,2,0,0,1,4,9,15,12,0,0,0,10,16,16,15,1,0,0,0,3,9,16,6,0,0,0,0,0,10,13,0,0,0,0,7 +0,0,2,16,14,2,0,0,0,0,7,15,15,11,0,0,0,0,5,16,15,16,2,0,0,0,0,6,15,16,8,0,0,0,0,0,2,12,11,0,0,0,0,0,0,9,14,0,0,0,0,0,7,15,13,0,0,0,1,12,16,14,7,0,9 +0,0,2,12,14,4,0,0,0,0,9,15,13,13,0,0,0,2,15,3,1,14,4,0,0,4,13,0,0,12,6,0,0,5,9,0,0,12,8,0,0,5,9,0,0,13,5,0,0,1,13,9,13,14,1,0,0,0,4,9,16,6,0,0,0 +0,0,6,16,8,0,0,0,0,3,16,6,16,1,0,0,0,3,7,2,16,2,0,0,0,0,0,7,13,0,0,0,0,0,0,12,8,0,0,0,0,0,2,15,4,1,0,0,0,0,8,12,4,13,6,0,0,0,5,16,15,8,1,0,2 +0,2,11,3,0,2,0,0,0,2,13,12,5,15,6,0,0,0,11,14,14,1,0,0,0,0,7,15,1,0,0,0,0,0,15,12,5,0,0,0,0,4,10,4,8,0,0,0,0,7,9,7,8,0,0,0,0,2,14,15,5,0,0,0,8 +0,0,7,12,9,0,0,0,0,0,13,13,14,9,0,0,0,0,11,12,14,14,0,0,0,0,1,10,12,15,4,0,0,0,0,0,0,11,9,0,0,0,0,0,0,9,9,0,0,0,0,0,1,14,6,0,0,0,5,12,14,11,1,0,9 +0,0,0,1,12,4,0,0,0,0,1,14,16,11,0,0,0,0,9,13,5,15,4,0,0,2,16,2,0,16,3,0,0,1,16,7,7,16,4,0,0,0,13,16,16,16,1,0,0,0,1,13,16,13,0,0,0,0,0,3,12,5,0,0,0 +0,0,7,15,15,8,3,0,0,3,14,3,0,13,8,0,0,6,10,1,6,14,8,0,0,0,8,12,6,8,8,0,0,0,0,0,0,8,6,0,0,0,0,0,0,12,3,0,0,0,0,0,7,13,0,0,0,0,7,15,14,2,0,0,9 +0,0,0,2,14,10,0,0,0,0,1,12,16,13,0,0,0,6,13,16,16,6,0,0,0,5,10,12,16,5,0,0,0,0,0,7,16,5,0,0,0,0,0,4,16,6,0,0,0,0,0,4,16,7,0,0,0,0,0,2,11,14,0,0,1 +0,3,15,12,11,12,2,0,0,12,16,16,16,16,6,0,0,12,13,0,3,4,0,0,0,3,15,13,2,0,0,0,0,0,5,15,11,0,0,0,0,0,0,6,16,3,0,0,0,0,5,12,16,3,0,0,0,4,16,16,12,0,0,0,5 +0,0,2,15,11,1,0,0,0,0,6,16,16,14,0,0,0,0,3,15,16,16,5,0,0,0,0,5,7,11,9,0,0,0,0,0,0,13,13,0,0,0,0,0,1,16,8,0,0,0,0,2,13,16,5,0,0,0,5,15,16,6,0,0,9 +0,0,0,9,12,0,0,0,0,0,2,16,10,0,0,0,0,0,9,16,3,0,0,0,0,0,14,12,0,0,0,0,0,1,16,5,0,2,0,0,0,2,16,13,16,16,6,0,0,0,8,16,8,12,16,3,0,0,0,5,12,16,15,4,6 +0,0,2,11,16,10,0,0,0,0,10,15,2,14,4,0,0,3,15,1,0,10,8,0,0,7,10,0,0,12,5,0,0,8,8,0,0,14,4,0,0,3,12,0,9,14,1,0,0,0,13,10,16,5,0,0,0,0,2,10,12,2,0,0,0 +0,0,0,2,11,16,8,0,0,1,9,15,16,16,12,0,0,3,15,16,13,16,10,0,0,0,0,3,16,16,6,0,0,0,0,0,16,16,4,0,0,0,0,4,16,16,3,0,0,0,0,4,16,16,1,0,0,0,0,1,13,16,1,0,1 +0,0,3,10,13,4,0,0,0,0,9,13,14,10,0,0,0,0,9,11,16,15,0,0,0,0,1,11,12,16,0,0,0,0,0,0,0,15,3,0,0,0,0,0,1,16,3,0,0,0,0,0,6,16,1,0,0,0,4,16,16,10,0,0,9 +0,2,4,9,13,13,0,0,0,5,15,11,12,16,0,0,0,0,0,0,8,13,0,0,0,0,0,0,12,12,0,0,0,0,0,0,9,16,1,0,0,0,0,0,0,15,8,0,0,0,9,14,11,16,6,0,0,0,1,10,15,9,0,0,3 +0,0,0,5,10,0,0,0,0,0,1,14,12,0,0,0,0,0,4,16,5,0,0,0,0,0,9,16,2,0,0,0,0,0,11,10,0,0,0,0,0,0,11,16,13,12,5,0,0,0,6,16,16,16,16,2,0,0,0,3,12,15,11,3,6 +0,8,12,16,13,0,0,0,0,2,10,10,16,8,0,0,0,0,0,0,10,16,4,0,0,0,0,0,10,16,4,0,0,0,0,4,16,14,0,0,0,0,0,0,12,16,8,0,0,0,4,8,15,16,8,0,0,10,16,16,11,3,0,0,3 +0,0,11,12,12,13,16,8,0,0,9,12,12,13,16,4,0,0,0,0,2,16,11,0,0,0,0,0,13,14,1,0,0,0,0,0,14,14,0,0,0,0,0,0,6,16,6,0,0,0,1,4,9,16,4,0,0,0,14,16,15,6,0,0,3 +0,0,0,9,16,5,0,0,0,1,12,15,15,8,0,0,0,7,13,5,15,4,0,0,0,0,1,6,16,0,0,0,0,0,0,8,16,0,0,0,0,0,0,9,13,0,0,0,0,0,0,12,8,4,3,0,0,0,0,11,16,15,5,0,2 +0,1,6,11,16,6,0,0,0,0,5,13,11,16,6,0,0,0,4,16,8,0,0,0,0,0,5,15,0,0,0,0,0,1,13,10,4,0,0,0,0,6,10,4,8,0,0,0,0,8,7,12,7,0,0,0,0,2,13,14,0,0,0,0,8 +0,0,1,16,14,1,0,0,0,0,3,16,16,4,0,0,0,0,1,16,16,5,0,0,0,0,0,16,16,7,0,0,0,0,7,16,16,0,0,0,0,0,8,16,14,0,0,0,0,0,7,16,15,0,0,0,0,0,2,13,16,0,0,0,1 +0,0,2,15,12,0,0,0,0,0,3,16,14,10,0,0,0,0,0,5,13,16,2,0,0,0,0,0,0,12,8,0,0,0,0,0,0,7,11,0,0,0,0,0,0,7,13,0,0,0,2,0,2,11,11,0,0,0,3,13,15,16,6,0,9 +0,0,0,2,14,0,0,0,0,0,1,13,14,1,0,0,0,0,5,16,4,0,0,0,0,0,11,8,0,0,0,0,0,0,12,8,1,0,0,0,0,0,8,16,16,14,5,0,0,0,4,16,4,8,16,3,0,0,0,4,11,15,14,6,6 +0,0,10,16,15,6,0,0,0,0,0,4,16,15,0,0,0,0,0,0,14,13,0,0,0,0,0,0,14,16,3,0,0,0,0,0,3,15,12,0,0,0,0,0,1,15,15,0,0,0,0,2,12,16,8,0,0,0,7,15,15,7,0,0,3 +0,0,0,0,11,8,0,0,0,0,0,2,16,8,0,0,0,0,0,6,16,2,0,0,0,0,0,7,15,0,0,0,0,0,0,10,14,0,0,0,0,0,13,16,16,11,0,0,0,0,7,7,16,16,10,0,0,0,0,0,8,16,15,0,6 +0,1,13,16,16,16,12,1,0,0,0,3,4,12,16,2,0,0,0,0,4,16,7,0,0,0,0,2,15,12,0,0,0,1,8,13,16,16,5,0,0,3,15,16,12,7,0,0,0,0,13,10,0,0,0,0,0,2,15,7,0,0,0,0,7 +0,2,11,16,10,1,0,0,0,7,14,7,16,14,2,0,0,6,9,7,15,1,0,0,0,1,13,16,7,0,0,0,0,0,9,16,9,0,0,0,0,0,12,16,11,0,0,0,0,3,16,16,3,0,0,0,0,0,14,9,0,0,0,0,8 +0,0,0,10,16,10,0,0,0,0,9,16,12,8,0,0,0,1,15,13,1,0,0,0,0,2,16,5,0,0,0,0,0,2,16,8,0,0,0,0,0,2,16,16,9,0,0,0,0,1,12,16,16,3,0,0,0,0,0,12,16,7,0,0,6 +0,0,1,14,12,1,0,0,0,0,8,16,9,0,0,0,0,0,15,16,0,0,0,0,0,0,16,12,0,0,0,0,0,1,16,11,0,0,0,0,0,0,16,16,15,4,0,0,0,0,11,13,13,16,1,0,0,0,2,12,16,14,2,0,6 +0,0,13,16,12,0,0,0,0,8,16,14,16,0,0,0,0,12,13,7,16,1,0,0,0,5,8,10,15,0,0,0,0,0,1,16,9,0,0,0,0,0,8,16,3,0,0,0,0,0,14,15,8,9,6,0,0,0,16,16,15,11,3,0,2 +0,0,9,16,11,0,0,0,0,5,16,16,16,7,0,0,0,3,16,16,16,15,0,0,0,0,2,6,3,11,5,0,0,0,0,0,0,11,7,0,0,0,0,0,0,13,11,0,0,0,4,5,11,16,5,0,0,0,5,12,12,6,0,0,9 +0,0,1,11,15,2,0,0,0,0,11,14,10,13,0,0,0,2,15,4,0,15,3,0,0,3,13,0,0,9,7,0,0,6,9,0,0,10,8,0,0,2,12,0,0,11,8,0,0,0,13,5,6,16,3,0,0,0,2,14,16,11,1,0,0 +0,0,9,15,1,0,0,0,0,2,16,14,10,0,0,0,0,9,13,8,12,0,0,0,0,9,8,12,9,0,0,0,0,1,1,14,6,0,0,0,0,0,2,16,2,0,0,0,0,0,9,14,12,15,8,0,0,0,7,16,15,5,2,0,2 +0,2,8,9,14,10,0,0,0,3,11,9,13,16,0,0,0,0,0,0,15,14,0,0,0,0,0,0,16,8,0,0,0,0,0,0,15,13,1,0,0,0,0,0,6,16,8,0,0,0,1,6,15,14,3,0,0,0,14,11,7,1,0,0,3 +0,0,3,14,13,1,0,0,0,0,12,15,11,9,0,0,0,1,16,4,0,15,0,0,0,5,13,0,0,13,6,0,0,7,9,0,0,13,8,0,0,4,12,0,0,13,9,0,0,3,13,4,10,16,5,0,0,0,3,8,13,15,2,0,0 +0,2,5,10,16,6,0,0,0,4,12,13,16,5,0,0,0,0,0,5,16,0,0,0,0,0,0,11,11,0,0,0,0,0,0,11,13,2,0,0,0,0,0,1,12,15,4,0,0,0,0,0,7,16,8,0,0,0,1,13,16,11,2,0,3 +0,0,5,14,12,8,2,0,0,0,1,10,16,16,12,0,0,0,5,14,16,16,2,0,0,0,10,16,16,12,0,0,0,6,16,16,14,4,0,0,0,9,16,16,11,0,0,0,0,10,16,16,6,0,0,0,0,1,10,14,12,4,0,0,1 +0,0,9,16,13,15,5,0,0,4,16,11,10,13,5,0,0,12,11,0,0,0,0,0,0,8,14,3,0,0,0,0,0,0,10,15,0,0,0,0,0,0,0,15,5,0,0,0,0,0,2,16,7,0,0,0,0,0,14,13,1,0,0,0,5 +0,3,16,15,7,0,0,0,0,5,16,16,16,13,2,0,0,0,7,8,10,16,6,0,0,0,0,0,2,16,4,0,0,0,0,0,4,16,1,0,0,0,0,0,12,9,0,0,0,0,4,11,15,3,0,0,0,2,15,12,3,0,0,0,9 +0,0,13,16,16,16,2,0,0,1,16,16,12,9,0,0,0,8,16,8,0,0,0,0,0,10,16,1,0,0,0,0,0,7,16,13,0,0,0,0,0,0,9,16,10,0,0,0,0,0,11,16,12,0,0,0,0,0,15,14,6,0,0,0,5 +0,0,0,3,10,10,0,0,0,8,14,16,14,15,4,0,0,0,3,1,1,15,4,0,0,0,0,7,14,16,6,0,0,0,9,16,15,10,0,0,0,0,2,2,14,4,0,0,0,0,0,7,11,0,0,0,0,0,0,9,4,0,0,0,7 +0,0,12,16,6,0,0,0,0,3,15,12,12,0,0,0,0,6,12,8,12,0,0,0,0,3,14,11,10,0,0,0,0,0,5,16,3,0,0,0,0,0,13,12,0,0,0,0,0,8,16,12,7,5,2,0,0,0,12,13,10,10,4,0,2 +0,0,0,8,15,1,0,0,0,0,1,15,15,1,0,0,0,0,8,16,5,0,0,0,0,0,12,15,1,0,0,0,0,0,15,7,0,0,0,0,0,0,14,14,12,7,0,0,0,0,8,16,12,16,7,0,0,0,0,7,15,16,13,0,6 +0,0,3,15,14,12,12,5,0,0,0,9,12,14,16,7,0,0,0,0,1,13,14,0,0,4,9,10,11,16,13,0,0,3,12,14,16,14,5,0,0,0,0,8,16,4,0,0,0,0,1,15,8,0,0,0,0,0,4,15,0,0,0,0,7 +0,0,10,7,0,0,0,0,0,1,14,8,0,0,0,0,0,5,16,3,2,8,4,0,0,7,16,14,16,15,5,0,0,0,4,8,16,12,0,0,0,0,0,6,16,2,0,0,0,0,5,16,7,0,0,0,0,0,11,9,0,0,0,0,4 +0,0,0,8,15,2,0,0,0,0,0,13,16,3,0,0,0,0,4,16,14,0,0,0,0,0,7,16,7,0,0,0,0,0,9,16,2,0,0,0,0,0,12,16,4,0,0,0,0,0,8,16,16,8,0,0,0,0,0,6,15,15,0,0,6 +0,1,7,13,16,16,8,0,0,5,16,12,16,16,5,0,0,0,1,4,16,8,0,0,0,0,0,11,16,9,1,0,0,0,0,0,9,15,10,0,0,0,0,0,0,13,9,0,0,0,0,5,11,16,5,0,0,0,6,16,14,7,0,0,3 +0,0,6,16,12,1,0,0,0,8,15,5,16,4,0,0,0,13,6,4,16,3,0,0,0,3,3,5,16,0,0,0,0,0,0,13,12,0,0,0,0,0,3,16,9,0,0,0,0,0,10,16,6,4,1,0,0,0,4,15,16,16,14,0,2 +0,2,15,16,16,12,2,0,0,0,2,10,16,16,5,0,0,0,0,11,16,5,0,0,0,0,0,11,16,10,0,0,0,0,0,1,10,16,7,0,0,0,0,0,8,16,9,0,0,0,5,11,16,15,2,0,0,3,16,14,10,2,0,0,3 +0,2,14,13,16,13,0,0,0,0,7,8,14,14,0,0,0,0,0,0,13,12,0,0,0,0,11,13,16,14,5,0,0,0,8,16,16,12,5,0,0,0,3,16,7,0,0,0,0,0,11,14,1,0,0,0,0,2,16,8,0,0,0,0,7 +0,0,6,12,14,16,14,0,0,1,12,12,10,12,16,0,0,0,0,0,1,11,15,0,0,0,0,9,16,16,10,0,0,0,0,9,16,11,0,0,0,0,0,7,15,0,0,0,0,0,1,16,5,0,0,0,0,0,8,13,0,0,0,0,7 +0,0,3,12,0,0,0,0,0,0,7,16,2,0,0,0,0,0,13,11,1,7,1,0,0,6,16,16,16,16,8,0,0,2,11,14,16,13,0,0,0,0,0,13,15,3,0,0,0,0,4,16,12,0,0,0,0,0,5,12,6,0,0,0,4 +0,0,11,16,10,0,0,0,0,0,3,16,16,9,0,0,0,0,0,14,16,9,0,0,0,0,2,16,16,4,0,0,0,0,7,16,16,3,0,0,0,0,8,16,13,1,0,0,0,0,12,16,12,0,0,0,0,0,9,14,16,0,0,0,1 +0,0,5,10,14,8,0,0,0,8,16,13,16,15,0,0,0,0,0,1,16,8,0,0,0,0,0,6,16,2,0,0,0,0,0,5,16,14,2,0,0,0,0,0,6,16,8,0,0,0,1,10,14,15,1,0,0,0,2,13,10,2,0,0,3 +0,0,4,13,4,0,0,0,0,0,11,10,11,7,0,0,0,0,14,3,1,15,0,0,0,0,15,1,0,12,5,0,0,1,15,0,0,13,5,0,0,0,16,0,4,16,4,0,0,0,11,10,15,11,0,0,0,0,3,12,14,1,0,0,0 +0,0,2,15,12,1,0,0,0,0,7,15,15,9,0,0,0,0,5,15,7,16,2,0,0,0,1,13,14,16,7,0,0,0,0,4,13,15,10,0,0,0,0,0,0,14,11,0,0,0,0,1,8,16,10,0,0,0,2,13,16,15,3,0,9 +0,0,0,12,16,2,0,0,0,0,4,16,12,1,0,0,0,0,14,16,2,0,0,0,0,0,16,10,0,0,0,0,0,1,16,8,5,0,0,0,0,0,14,16,16,13,1,0,0,0,7,15,7,15,13,0,0,0,1,9,16,16,16,3,6 +0,0,3,12,15,2,0,0,0,2,15,6,0,0,0,0,0,4,14,2,9,14,8,0,0,1,13,16,14,4,0,0,0,0,8,16,6,0,0,0,0,0,11,11,15,0,0,0,0,0,11,5,12,2,0,0,0,0,3,12,15,3,0,0,8 +0,0,5,15,16,7,0,0,0,0,8,12,16,16,1,0,0,0,2,7,13,15,0,0,0,0,10,16,16,15,8,0,0,0,2,15,16,16,9,0,0,0,0,8,16,2,0,0,0,0,0,15,9,0,0,0,0,0,4,16,1,0,0,0,7 +0,3,15,16,8,0,0,0,0,10,16,15,13,0,0,0,0,6,9,12,12,0,0,0,0,0,0,14,8,0,0,0,0,0,5,16,4,0,0,0,0,0,13,12,0,1,4,0,0,4,16,14,12,15,9,0,0,3,16,16,13,8,0,0,2 +0,0,8,12,15,16,6,0,0,6,16,16,14,8,0,0,0,12,14,4,1,0,0,0,0,8,15,9,1,0,0,0,0,0,13,16,10,0,0,0,0,0,1,9,15,0,0,0,0,0,3,11,14,0,0,0,0,0,11,15,3,0,0,0,5 +0,0,7,10,0,0,0,0,0,0,12,13,11,6,0,0,0,3,16,16,16,16,6,0,0,8,13,4,5,15,6,0,0,8,12,0,0,10,8,0,0,6,14,1,5,15,4,0,0,4,16,16,16,13,0,0,0,1,12,14,9,1,0,0,0 +0,0,9,15,6,0,0,0,0,3,14,16,16,3,0,0,0,7,11,1,15,4,0,0,0,1,2,0,14,4,0,0,0,0,0,9,13,0,0,0,0,0,3,16,4,0,0,0,0,0,14,16,12,11,4,0,0,0,9,15,14,12,5,0,2 +0,0,6,12,11,6,0,0,0,0,8,16,16,16,3,0,0,0,8,16,16,13,0,0,0,0,8,16,16,12,0,0,0,0,10,16,16,12,0,0,0,3,15,16,16,7,0,0,0,3,15,16,16,2,0,0,0,0,3,8,9,5,0,0,1 +0,0,0,4,11,12,5,0,0,0,1,16,16,16,1,0,0,0,8,16,16,13,0,0,0,1,14,16,16,4,0,0,0,4,16,16,16,4,0,0,0,7,16,16,16,1,0,0,0,1,12,16,16,3,0,0,0,0,0,7,12,8,0,0,1 +0,7,16,15,4,0,0,0,0,11,10,7,13,0,0,0,0,2,3,4,12,0,0,0,0,0,0,12,12,0,0,0,0,0,4,16,6,0,0,0,0,0,13,13,0,0,0,0,0,6,16,16,16,16,8,0,0,4,15,16,16,13,3,0,2 +0,0,8,16,14,2,0,0,0,1,14,6,11,8,0,0,0,8,16,0,4,16,0,0,0,2,11,15,15,16,6,0,0,0,0,6,7,12,6,0,0,0,0,0,0,8,8,0,0,0,12,6,4,13,10,0,0,0,6,14,16,10,2,0,9 +0,0,6,15,15,2,0,0,0,0,9,16,16,5,0,0,0,0,9,16,16,1,0,0,0,0,11,16,16,1,0,0,0,0,14,16,14,2,0,0,0,0,14,16,14,0,0,0,0,0,15,16,15,4,0,0,0,0,5,16,12,0,0,0,1 +0,0,1,10,13,8,0,0,0,1,14,10,7,15,0,0,0,1,16,7,7,16,3,0,0,0,7,16,13,10,8,0,0,0,0,0,0,6,8,0,0,0,0,0,0,12,4,0,0,0,1,10,5,13,3,0,0,0,1,11,16,7,0,0,9 +0,0,0,6,15,1,0,0,0,0,2,15,11,0,0,0,0,0,8,15,1,0,0,0,0,7,16,3,0,6,6,0,0,9,16,16,13,15,12,0,0,1,6,10,16,16,8,0,0,0,0,1,16,10,1,0,0,0,0,6,16,2,0,0,4 +0,0,11,16,6,0,0,0,0,2,16,15,16,3,0,0,0,0,8,4,16,4,0,0,0,0,0,7,14,1,0,0,0,0,0,14,9,0,0,0,0,0,11,11,0,0,0,0,0,1,16,9,2,5,1,0,0,0,9,14,12,9,0,0,2 +0,2,13,11,5,0,0,0,0,1,8,13,16,8,0,0,0,0,0,0,12,16,0,0,0,0,0,7,16,11,0,0,0,0,0,9,16,7,0,0,0,0,0,1,9,16,5,0,0,2,8,5,7,16,6,0,0,3,14,16,13,8,0,0,3 +0,0,0,8,15,0,0,0,0,0,3,16,10,0,0,0,0,1,14,15,0,0,0,0,0,4,16,10,0,0,0,0,0,2,14,16,16,16,6,0,0,0,3,12,16,15,2,0,0,0,0,3,16,10,0,0,0,0,0,4,13,3,0,0,4 +0,0,1,13,11,0,0,0,0,0,11,16,3,0,0,0,0,2,16,11,0,1,2,0,0,8,16,13,8,14,10,0,0,3,15,16,16,16,3,0,0,0,3,9,16,11,0,0,0,0,0,12,16,6,0,0,0,0,0,15,13,3,0,0,4 +0,0,11,15,8,0,0,0,0,5,16,16,16,11,0,0,0,8,13,2,9,16,3,0,0,8,15,0,0,15,4,0,0,8,12,0,0,13,6,0,0,8,15,0,1,16,3,0,0,4,16,14,16,9,0,0,0,0,11,16,12,0,0,0,0 +0,0,7,7,4,4,0,0,0,0,16,16,16,16,4,0,0,1,16,16,8,12,8,0,0,6,15,7,0,12,5,0,0,6,12,0,0,13,4,0,0,5,12,0,8,14,1,0,0,4,16,11,15,7,0,0,0,0,8,15,7,0,0,0,0 +0,0,3,12,2,0,0,0,0,0,9,16,3,0,0,0,0,0,16,10,0,0,0,0,0,1,16,6,0,0,0,0,0,5,16,10,8,3,0,0,0,6,16,16,16,16,5,0,0,0,13,16,8,16,4,0,0,0,1,11,15,14,0,0,6 +0,0,4,8,16,13,0,0,0,3,16,16,14,16,2,0,0,4,16,6,0,12,4,0,0,4,12,0,0,12,6,0,0,5,12,0,0,12,3,0,0,4,16,6,6,14,0,0,0,0,12,16,16,7,0,0,0,0,4,12,8,0,0,0,0 +0,0,7,16,7,0,0,0,0,4,16,9,10,11,2,0,0,11,10,0,11,14,1,0,0,6,14,7,14,7,0,0,0,0,11,16,12,0,0,0,0,0,13,12,15,1,0,0,0,4,16,7,14,2,0,0,0,0,8,16,10,0,0,0,8 +0,0,7,16,15,0,0,0,0,5,16,16,16,0,0,0,0,3,8,8,16,0,0,0,0,0,2,15,8,0,0,0,0,0,5,16,3,0,0,0,0,0,15,10,0,2,1,0,0,0,15,16,12,16,8,0,0,0,6,16,16,15,3,0,2 +0,0,0,9,15,0,0,0,0,0,5,16,12,0,0,0,0,0,13,15,2,0,0,0,0,5,16,8,0,0,0,0,0,11,16,2,2,8,3,0,0,13,16,14,15,16,6,0,0,3,15,16,16,12,0,0,0,0,2,12,16,5,0,0,4 +0,0,1,11,13,0,0,0,0,0,5,16,3,0,0,0,0,1,15,9,0,0,0,0,0,6,15,1,0,4,6,0,0,9,16,11,9,16,8,0,0,1,9,12,16,12,1,0,0,0,0,4,16,4,0,0,0,0,0,14,11,0,0,0,4 +0,0,0,13,7,0,0,0,0,0,9,16,4,0,0,0,0,1,14,11,0,0,0,0,0,7,16,7,6,16,5,0,0,0,10,14,16,14,1,0,0,0,0,8,16,6,0,0,0,0,0,15,6,0,0,0,0,0,0,16,9,0,0,0,4 +0,0,12,13,9,6,2,0,0,4,16,16,16,16,7,0,0,7,13,3,5,3,0,0,0,7,14,5,0,0,0,0,0,4,16,16,5,0,0,0,0,1,9,16,12,0,0,0,0,0,5,12,12,0,0,0,0,0,8,15,5,0,0,0,5 +0,0,0,6,14,2,0,0,0,0,2,14,12,0,0,0,0,0,7,15,1,0,0,0,0,0,13,10,0,0,0,0,0,1,16,11,8,4,0,0,0,1,15,16,16,16,8,0,0,0,8,13,2,4,15,1,0,0,0,7,14,16,14,1,6 +0,0,8,16,10,0,0,0,0,2,16,13,16,0,0,0,0,2,8,4,14,0,0,0,0,0,0,8,10,0,0,0,0,0,0,13,6,0,0,0,0,0,6,15,0,0,0,0,0,0,12,15,12,8,2,0,0,0,8,15,10,8,1,0,2 +0,0,3,11,12,4,0,0,0,2,15,10,16,12,0,0,0,5,11,0,11,14,4,0,0,8,8,0,0,5,8,0,0,6,8,0,0,4,8,0,0,4,10,0,0,10,5,0,0,1,14,5,7,14,0,0,0,0,7,15,10,1,0,0,0 +0,0,0,7,10,0,0,0,0,0,7,16,5,0,0,0,0,0,14,11,0,0,0,0,0,4,16,5,4,8,0,0,0,3,16,16,16,14,0,0,0,1,6,8,16,7,0,0,0,0,0,3,16,5,0,0,0,0,0,9,13,2,0,0,4 +0,0,4,12,0,0,0,0,0,0,14,6,0,0,0,0,0,4,16,4,0,0,0,0,0,7,16,1,0,0,0,0,0,8,16,16,16,13,1,0,0,5,16,7,9,16,5,0,0,1,14,12,4,16,5,0,0,0,3,15,16,8,0,0,6 +0,0,8,12,14,10,1,0,0,3,16,12,9,15,8,0,0,1,6,0,8,14,4,0,0,0,0,4,16,7,0,0,0,0,0,2,15,7,0,0,0,0,0,0,6,16,1,0,0,0,8,7,8,16,3,0,0,0,6,14,11,6,0,0,3 +0,0,4,16,14,3,0,0,0,3,16,16,16,15,2,0,0,8,16,2,1,14,6,0,0,8,16,0,0,5,8,0,0,5,15,0,0,4,8,0,0,0,16,6,0,9,7,0,0,0,14,14,8,16,3,0,0,0,3,14,16,13,0,0,0 +0,2,12,13,11,1,0,0,0,4,12,12,16,7,0,0,0,0,0,5,16,5,0,0,0,0,0,16,16,1,0,0,0,0,0,4,15,15,0,0,0,0,0,0,1,16,5,0,0,2,8,5,8,16,3,0,0,2,10,16,14,8,0,0,3 +0,0,0,8,15,4,0,0,0,0,0,13,16,13,0,0,0,1,1,10,3,13,6,0,0,5,8,0,0,6,8,0,0,7,11,0,0,4,8,0,0,3,15,7,0,4,9,0,0,0,7,16,13,11,12,0,0,0,0,6,15,16,4,0,0 +0,0,4,13,16,12,0,0,0,0,7,9,13,15,0,0,0,0,0,3,11,11,0,0,0,0,7,16,16,14,6,0,0,0,5,13,16,12,5,0,0,0,1,11,4,0,0,0,0,0,6,14,0,0,0,0,0,0,6,9,0,0,0,0,7 +0,0,3,13,15,16,16,3,0,0,10,16,13,13,11,1,0,0,11,16,11,4,0,0,0,0,4,13,16,15,1,0,0,0,0,2,13,16,1,0,0,0,0,0,6,16,0,0,0,0,0,5,15,7,0,0,0,0,3,16,9,0,0,0,5 +0,0,0,8,14,15,2,0,0,0,9,12,4,13,7,0,0,0,14,13,8,14,3,0,0,0,3,16,16,16,2,0,0,0,0,0,0,15,0,0,0,0,3,0,1,16,1,0,0,0,14,13,9,14,0,0,0,0,2,10,12,4,0,0,9 +0,5,14,10,4,0,0,0,0,3,12,16,16,6,0,0,0,0,1,13,16,5,0,0,0,0,8,16,13,2,0,0,0,0,5,16,13,2,0,0,0,0,0,6,16,15,4,0,0,1,8,9,15,16,5,0,0,4,15,14,8,4,0,0,3 +0,0,7,16,16,7,0,0,0,3,16,16,16,11,0,0,0,1,6,4,16,7,0,0,0,0,0,9,15,2,0,0,0,0,1,14,8,0,0,0,0,0,7,16,5,4,1,0,0,0,10,16,16,16,5,0,0,0,7,16,16,7,0,0,2 +0,1,8,16,15,1,0,0,0,8,12,14,16,0,0,0,0,0,1,16,11,0,0,0,0,0,4,16,12,2,0,0,0,0,1,9,15,16,2,0,0,0,2,0,0,15,8,0,0,1,16,14,5,15,7,0,0,0,7,13,14,10,1,0,3 +0,3,15,16,13,5,0,0,0,7,16,12,14,15,1,0,0,1,6,0,11,15,1,0,0,0,0,16,16,5,0,0,0,0,0,6,15,15,2,0,0,0,0,0,2,16,7,0,0,3,8,2,6,16,4,0,0,2,12,16,16,9,0,0,3 +0,0,3,12,14,16,14,0,0,0,1,8,7,10,14,0,0,0,0,5,4,13,9,0,0,0,5,16,16,16,10,0,0,0,3,8,16,5,0,0,0,0,0,11,9,0,0,0,0,0,3,16,1,0,0,0,0,0,7,9,0,0,0,0,7 +0,0,3,13,7,0,0,0,0,0,12,16,16,11,0,0,0,1,16,16,10,16,3,0,0,7,16,14,0,14,4,0,0,1,16,9,0,12,5,0,0,0,15,8,4,16,4,0,0,0,12,16,16,12,1,0,0,0,5,15,11,1,0,0,0 +0,0,7,16,15,5,0,0,0,0,5,8,11,15,4,0,0,0,0,1,14,13,1,0,0,0,0,10,16,3,0,0,0,0,0,5,16,5,0,0,0,0,2,0,11,12,0,0,0,3,15,11,12,15,0,0,0,0,8,13,11,3,0,0,3 +0,0,3,9,14,4,0,0,0,1,16,15,13,10,2,0,0,1,15,5,1,13,12,0,0,0,15,12,11,16,3,0,0,0,4,16,16,9,0,0,0,0,11,16,16,7,0,0,0,0,15,16,16,8,0,0,0,0,3,13,15,4,0,0,8 +0,2,10,15,11,4,0,0,0,2,10,6,13,12,0,0,0,0,0,2,13,9,0,0,0,0,0,15,16,0,0,0,0,0,0,11,16,9,0,0,0,0,0,0,2,15,11,0,0,0,1,3,11,14,2,0,0,2,15,16,11,1,0,0,3 +0,0,5,15,8,0,0,0,0,2,15,9,10,3,1,0,0,6,9,7,11,14,1,0,0,2,15,13,8,2,0,0,0,1,15,6,0,0,0,0,0,1,16,12,0,0,0,0,0,1,16,13,1,0,0,0,0,0,8,15,3,0,0,0,8 +0,0,5,14,11,0,0,0,0,0,13,16,14,0,0,0,0,0,6,9,12,0,0,0,0,0,0,10,8,0,0,0,0,0,1,15,3,0,0,0,0,0,8,14,0,0,0,0,0,0,11,16,12,15,1,0,0,0,6,16,16,7,0,0,2 +0,0,4,15,3,0,0,0,0,0,8,16,1,0,0,0,0,0,14,13,0,0,0,0,0,3,16,10,4,3,0,0,0,8,16,16,16,16,5,0,0,8,16,6,4,14,8,0,0,5,16,11,8,16,5,0,0,0,6,15,16,11,0,0,6 +0,0,7,14,4,0,0,0,0,1,16,16,16,7,0,0,0,4,16,16,16,14,0,0,0,0,13,16,16,11,0,0,0,0,8,16,16,1,0,0,0,0,10,16,16,6,0,0,0,0,11,16,16,13,0,0,0,0,6,12,13,10,0,0,8 +0,0,2,11,11,3,0,0,0,0,2,16,16,16,4,0,0,0,1,16,16,16,4,0,0,0,3,16,16,16,3,0,0,0,7,16,16,15,2,0,0,0,10,16,16,14,1,0,0,0,8,16,16,12,0,0,0,0,1,5,8,9,2,0,1 +0,1,7,12,12,2,0,0,0,10,16,16,16,10,0,0,0,0,2,1,16,8,0,0,0,0,0,1,16,9,0,0,0,0,0,2,16,14,0,0,0,0,0,0,4,15,5,0,0,0,3,7,7,16,10,0,0,0,7,16,16,12,0,0,3 +0,0,5,12,14,5,0,0,0,2,16,13,16,6,0,0,0,0,0,3,16,4,0,0,0,0,0,15,16,5,0,0,0,0,0,4,11,16,4,0,0,0,0,0,1,16,4,0,0,0,6,12,13,15,1,0,0,0,8,12,11,3,0,0,3 +0,0,5,11,15,8,0,0,0,4,14,8,10,16,0,0,0,8,13,1,15,12,0,0,0,4,16,15,16,13,0,0,0,0,4,12,13,16,4,0,0,0,4,1,0,14,8,0,0,0,13,13,6,14,7,0,0,0,2,14,14,9,2,0,9 +0,0,9,16,7,0,0,0,0,0,16,16,14,0,0,0,0,0,3,13,16,0,0,0,0,0,7,16,16,12,8,0,0,0,8,16,16,16,9,0,0,0,0,14,11,0,0,0,0,0,4,16,5,0,0,0,0,0,7,12,0,0,0,0,7 +0,1,14,16,14,4,0,0,0,3,16,8,8,14,0,0,0,0,15,14,13,9,0,0,0,0,3,11,16,4,0,0,0,0,0,0,12,12,0,0,0,0,0,0,4,16,3,0,0,0,3,4,3,16,1,0,0,0,12,14,16,14,1,0,9 +0,0,8,15,16,12,0,0,0,5,16,12,15,14,0,0,0,1,5,1,15,8,0,0,0,0,4,14,16,4,0,0,0,0,3,16,16,14,1,0,0,0,0,0,7,16,4,0,0,0,4,15,16,14,1,0,0,0,9,16,12,3,0,0,3 +0,0,1,7,11,13,11,5,0,0,7,16,16,13,16,4,0,0,14,8,0,0,0,0,0,4,16,9,8,5,0,0,0,8,16,16,16,16,2,0,0,2,4,4,12,15,0,0,0,0,0,7,16,5,0,0,0,0,0,12,5,0,0,0,5 +0,0,6,15,16,7,0,0,0,1,16,12,15,13,0,0,0,0,0,3,16,11,0,0,0,0,5,16,11,0,0,0,0,0,5,13,16,12,0,0,0,0,0,0,9,15,1,0,0,0,5,9,14,15,0,0,0,0,5,16,11,4,0,0,3 +0,1,11,16,15,6,0,0,0,2,16,7,6,13,2,0,0,0,10,13,14,16,3,0,0,0,2,9,9,12,3,0,0,0,0,0,0,12,4,0,0,0,0,0,0,9,7,0,0,0,0,0,3,14,3,0,0,0,9,16,16,11,2,0,9 +0,0,9,16,6,0,0,0,0,0,15,10,15,2,0,0,0,0,5,2,16,2,0,0,0,0,2,7,16,3,0,0,0,7,16,16,16,16,8,0,0,1,5,14,6,0,1,0,0,0,9,12,0,0,0,0,0,0,10,8,0,0,0,0,7 +0,0,0,7,15,0,0,0,0,0,7,16,10,0,0,0,0,1,16,9,0,12,8,0,0,9,14,1,5,16,7,0,0,8,15,8,12,16,9,0,0,3,15,16,16,11,1,0,0,0,0,7,16,1,0,0,0,0,0,7,13,0,0,0,4 +0,0,6,14,14,2,0,0,0,0,15,11,9,10,0,0,0,3,14,0,0,7,5,0,0,4,12,0,0,4,8,0,0,4,13,0,0,11,8,0,0,5,13,0,4,16,3,0,0,0,16,14,16,7,0,0,0,0,10,15,7,0,0,0,0 +0,0,9,16,14,4,0,0,0,1,10,8,16,13,0,0,0,0,0,0,15,11,0,0,0,0,1,12,16,3,0,0,0,0,2,14,16,13,0,0,0,0,0,0,7,16,2,0,0,0,1,4,9,15,2,0,0,0,11,16,13,3,0,0,3 +0,0,2,10,12,14,16,12,0,0,8,16,16,16,14,4,0,0,2,16,12,4,0,0,0,0,1,16,15,2,0,0,0,0,0,8,16,11,0,0,0,0,0,0,13,16,0,0,0,0,0,7,16,11,0,0,0,0,1,16,11,1,0,0,5 +0,0,0,9,14,16,12,0,0,0,10,14,6,11,15,1,0,0,11,15,16,16,8,0,0,0,0,0,1,15,9,0,0,0,0,0,5,15,0,0,0,0,0,1,14,5,0,0,0,0,0,6,12,0,0,0,0,0,0,12,5,0,0,0,9 +0,0,3,15,12,1,0,0,0,0,11,14,8,11,0,0,0,0,15,1,0,13,1,0,0,5,14,0,0,9,5,0,0,5,12,0,0,11,2,0,0,0,16,1,2,15,2,0,0,0,14,13,14,12,0,0,0,0,2,14,8,1,0,0,0 +0,0,5,14,16,9,0,0,0,0,8,16,14,16,5,0,0,0,7,16,13,16,4,0,0,8,13,16,16,12,0,0,0,3,15,16,12,2,0,0,0,0,14,16,12,1,0,0,0,0,15,16,16,4,0,0,0,0,7,16,11,1,0,0,8 +0,0,0,5,8,0,0,0,0,0,4,16,2,0,14,0,0,0,9,8,0,8,8,0,0,2,15,0,1,15,0,0,0,10,9,4,9,15,1,0,0,11,16,16,16,11,1,0,0,3,4,7,9,0,0,0,0,0,0,8,5,0,0,0,4 +0,0,3,14,16,9,0,0,0,0,8,14,11,16,1,0,0,10,11,15,15,11,0,0,0,4,13,16,12,1,0,0,0,2,16,14,14,0,0,0,0,4,14,0,13,8,0,0,0,2,13,4,13,16,0,0,0,0,3,14,12,7,0,0,8 +0,0,9,16,12,3,0,0,0,0,9,16,16,7,0,0,0,0,8,16,14,2,0,0,0,0,14,16,14,0,0,0,0,0,15,16,16,1,0,0,0,2,15,16,14,1,0,0,0,0,14,16,8,0,0,0,0,0,12,16,10,0,0,0,1 +0,0,7,13,16,5,0,0,0,6,8,8,14,9,0,0,0,0,0,0,15,5,0,0,0,0,0,12,11,0,0,0,0,0,6,15,1,0,0,0,0,0,12,4,0,0,0,0,0,0,11,10,8,8,4,0,0,0,6,15,16,12,4,0,2 +0,0,4,12,14,6,0,0,0,5,16,11,9,16,2,0,0,12,16,2,4,16,3,0,0,6,16,14,14,14,0,0,0,0,1,11,16,1,0,0,0,0,1,14,16,9,0,0,0,0,5,16,9,15,1,0,0,0,2,12,13,8,0,0,8 +0,0,0,13,9,1,0,0,0,0,9,16,16,11,0,0,0,0,8,14,5,16,3,0,0,1,7,10,0,12,6,0,0,6,14,14,2,10,9,0,0,4,16,16,12,16,10,0,0,0,11,16,16,15,4,0,0,0,2,13,16,7,0,0,0 +0,0,8,16,16,8,0,0,0,5,15,8,16,14,0,0,0,1,2,2,15,11,0,0,0,0,3,13,16,9,1,0,0,0,8,16,15,15,10,0,0,0,2,2,3,16,6,0,0,0,7,14,16,12,1,0,0,0,14,16,7,0,0,0,3 +0,0,10,16,16,9,0,0,0,2,15,12,14,16,1,0,0,1,4,4,13,16,5,0,0,0,10,16,16,16,13,0,0,0,5,15,16,6,1,0,0,0,0,16,11,0,0,0,0,0,9,16,3,0,0,0,0,0,11,12,0,0,0,0,7 +0,0,3,13,1,0,0,0,0,0,13,12,0,0,0,0,0,3,16,1,0,0,0,0,0,2,14,1,1,4,0,0,0,5,14,6,14,16,7,0,0,1,16,16,11,5,15,0,0,0,10,16,14,16,11,0,0,0,2,12,16,13,0,0,6 +0,0,7,15,16,16,13,0,0,2,16,9,8,14,16,0,0,1,2,0,4,16,8,0,0,0,0,0,10,15,2,0,0,0,0,0,7,16,8,0,0,0,0,0,3,16,8,0,0,0,0,5,15,13,1,0,0,0,9,16,11,2,0,0,3 +0,0,3,14,16,8,0,0,0,2,15,15,16,14,5,0,0,3,16,16,16,16,14,1,0,0,6,5,8,16,6,0,0,0,0,0,12,13,0,0,0,0,0,5,16,3,0,0,0,0,0,14,8,0,0,0,0,0,4,15,1,0,0,0,9 +0,0,2,15,16,14,5,0,0,0,5,15,8,13,11,0,0,2,11,12,7,15,8,0,0,7,16,16,16,8,1,0,0,2,15,16,8,0,0,0,0,0,7,16,12,0,0,0,0,0,4,16,16,0,0,0,0,0,2,14,14,0,0,0,8 +0,0,6,8,10,12,11,0,0,1,16,16,16,12,3,0,0,4,16,8,1,0,0,0,0,6,14,0,0,0,0,0,0,1,14,12,3,0,0,0,0,0,5,15,16,6,0,0,0,0,0,4,16,10,0,0,0,0,6,14,7,1,0,0,5 +0,0,5,10,12,15,5,0,0,1,13,16,15,8,2,0,0,7,16,5,0,0,0,0,0,1,13,12,3,0,0,0,0,0,1,9,16,5,0,0,0,0,0,0,11,12,0,0,0,0,0,8,15,6,0,0,0,0,6,12,3,0,0,0,5 +0,0,8,15,15,5,0,0,0,6,16,7,5,14,2,0,0,8,16,6,6,16,3,0,0,0,9,16,15,9,0,0,0,1,12,16,13,0,0,0,0,3,15,4,13,7,0,0,0,3,8,0,10,9,0,0,0,0,7,14,14,4,0,0,8 +0,0,12,4,0,0,0,0,0,0,15,4,0,0,0,0,0,2,15,2,0,0,0,0,0,2,15,6,12,8,0,0,0,7,16,14,9,10,6,0,0,4,16,14,3,1,10,0,0,3,16,13,15,16,5,0,0,0,10,15,10,3,0,0,6 +0,0,1,9,15,16,14,1,0,0,12,15,11,14,13,1,0,0,16,16,16,16,6,0,0,0,4,8,8,14,14,0,0,0,0,0,3,14,7,0,0,0,0,1,12,11,0,0,0,0,0,8,14,1,0,0,0,0,0,15,7,0,0,0,9 +0,0,11,3,0,0,0,0,0,2,16,4,0,0,0,0,0,4,15,0,0,0,0,0,0,4,14,6,12,11,1,0,0,7,16,16,15,14,8,0,0,4,16,15,3,9,11,0,0,1,16,16,16,16,6,0,0,0,7,14,13,5,0,0,6 +0,0,0,3,15,6,0,0,0,0,1,13,13,1,0,0,0,0,10,16,9,8,1,0,0,3,16,10,12,16,1,0,0,9,16,5,16,13,3,0,0,7,16,16,16,16,11,0,0,0,0,7,16,7,0,0,0,0,0,3,16,8,0,0,4 +0,0,6,15,16,16,16,11,0,0,10,15,16,14,10,2,0,0,3,16,8,0,0,0,0,0,3,16,13,1,0,0,0,0,0,9,16,9,0,0,0,0,0,0,15,15,1,0,0,0,0,10,15,7,0,0,0,0,5,15,3,0,0,0,5 +0,0,2,11,13,16,11,0,0,0,9,16,13,16,10,0,0,0,14,16,16,16,16,4,0,0,6,11,9,14,14,0,0,0,0,0,6,16,4,0,0,0,0,2,15,7,0,0,0,0,0,13,13,0,0,0,0,0,3,15,4,0,0,0,9 +0,0,6,15,12,1,0,0,0,8,13,4,10,8,0,0,0,2,0,0,15,6,0,0,0,0,0,6,16,5,0,0,0,0,0,16,16,16,5,0,0,0,4,5,1,16,7,0,0,2,16,10,14,15,0,0,0,0,6,13,9,1,0,0,3 +0,0,5,10,13,16,9,0,0,2,16,16,11,8,2,0,0,1,15,9,0,0,0,0,0,0,9,16,9,1,0,0,0,0,0,1,13,11,0,0,0,0,0,0,2,15,0,0,0,0,0,4,15,5,0,0,0,0,7,14,3,0,0,0,5 +0,0,7,16,13,2,0,0,0,2,16,12,13,12,0,0,0,14,15,4,15,8,0,0,0,5,15,16,15,4,0,0,0,0,10,16,13,0,0,0,0,0,15,10,14,2,0,0,0,0,13,3,12,6,0,0,0,0,4,15,15,3,0,0,8 +0,0,0,3,11,15,1,0,0,0,6,16,16,16,9,0,0,2,16,16,16,13,14,0,0,0,11,8,1,9,9,0,0,0,0,0,1,15,0,0,0,0,0,0,6,10,0,0,0,0,0,0,12,3,0,0,0,0,0,0,13,0,0,0,9 +0,0,7,8,12,16,12,0,0,1,16,16,16,15,6,0,0,10,16,7,1,0,0,0,0,8,16,9,1,0,0,0,0,0,9,16,12,0,0,0,0,0,0,8,16,4,0,0,0,0,5,14,14,1,0,0,0,0,9,15,3,0,0,0,5 +0,0,5,15,12,1,0,0,0,0,14,14,14,13,0,0,0,3,13,0,1,14,3,0,0,4,11,0,0,10,8,0,0,5,15,0,0,5,8,0,0,2,15,2,0,7,8,0,0,0,12,14,13,16,4,0,0,0,4,14,16,8,0,0,0 +0,2,15,16,6,0,0,0,0,3,14,14,16,2,0,0,0,0,0,6,16,2,0,0,0,0,0,11,16,1,0,0,0,8,14,16,16,16,5,0,0,10,16,15,11,12,5,0,0,5,16,7,0,0,0,0,0,5,14,1,0,0,0,0,7 +0,2,15,16,11,0,0,0,0,4,15,14,16,3,0,0,0,0,0,10,16,2,0,0,0,0,0,13,13,0,0,0,0,0,6,16,4,0,0,0,0,1,11,13,0,0,0,0,0,8,16,14,13,16,6,0,0,3,16,16,11,6,0,0,2 +0,0,2,15,9,2,0,0,0,0,5,16,15,0,0,0,0,0,8,16,10,0,0,0,0,0,7,16,9,0,0,0,0,0,13,16,10,0,0,0,0,0,8,16,8,0,0,0,0,0,8,16,10,0,0,0,0,0,4,14,8,0,0,0,1 +0,0,1,8,15,16,9,0,0,1,12,15,9,13,14,0,0,5,13,2,0,13,11,0,0,3,5,0,1,15,7,0,0,0,0,2,10,16,9,0,0,0,0,14,16,11,1,0,0,0,0,13,14,0,0,0,0,0,0,12,5,0,0,0,7 +0,0,2,15,13,3,0,0,0,0,2,16,16,6,0,0,0,0,3,16,16,5,0,0,0,0,4,16,16,0,0,0,0,0,7,16,13,0,0,0,0,0,9,16,7,0,0,0,0,0,11,16,9,0,0,0,0,0,4,13,12,1,0,0,1 +0,0,6,15,16,16,7,0,0,0,8,13,15,16,7,0,0,0,1,15,14,8,0,0,0,0,2,16,16,13,2,0,0,0,0,3,11,16,5,0,0,0,0,0,7,16,4,0,0,0,1,10,15,11,0,0,0,0,6,16,9,0,0,0,3 +0,0,7,16,15,4,0,0,0,0,14,6,5,15,2,0,0,1,16,0,6,15,0,0,0,2,16,10,16,8,0,0,0,0,9,16,13,0,0,0,0,0,10,12,15,7,0,0,0,1,16,3,5,13,0,0,0,0,9,15,14,7,0,0,8 +0,0,4,14,4,0,0,0,0,0,13,10,1,0,0,0,0,6,15,1,0,0,0,0,0,7,12,0,0,0,0,0,0,8,7,10,16,14,0,0,0,5,14,16,14,16,7,0,0,2,16,16,12,16,8,0,0,0,6,15,14,8,1,0,6 +0,0,10,16,12,0,0,0,0,7,16,13,16,1,0,0,0,6,5,6,15,2,0,0,0,0,0,10,12,0,0,0,0,0,0,14,9,0,0,0,0,0,6,15,5,0,0,0,0,0,16,15,16,12,3,0,0,0,12,16,15,12,5,0,2 +0,0,2,8,9,12,12,0,0,0,10,16,15,10,4,0,0,0,13,14,1,0,0,0,0,3,16,12,2,0,0,0,0,0,3,10,15,12,0,0,0,0,0,0,8,16,0,0,0,0,0,8,14,6,0,0,0,0,0,13,4,0,0,0,5 +0,1,7,12,14,5,0,0,0,3,13,8,14,10,0,0,0,0,0,3,16,2,0,0,0,0,0,13,12,3,0,0,0,0,0,8,13,16,4,0,0,0,0,0,4,14,3,0,0,0,1,12,16,9,0,0,0,0,9,13,5,0,0,0,3 +0,0,1,15,13,2,0,0,0,0,8,16,13,9,0,0,0,0,9,11,2,12,2,0,0,5,16,7,0,9,6,0,0,4,16,8,0,9,9,0,0,1,15,4,0,13,8,0,0,0,10,14,12,16,5,0,0,0,1,13,15,8,0,0,0 +0,0,8,16,14,1,0,0,0,0,6,10,16,8,0,0,0,0,0,0,14,7,0,0,0,0,0,1,16,9,2,0,0,4,15,16,16,16,10,0,0,1,8,15,11,4,1,0,0,0,7,15,1,0,0,0,0,0,11,11,0,0,0,0,7 +0,0,0,7,16,11,0,0,0,0,9,16,16,16,16,7,0,0,10,16,16,12,16,4,0,0,3,8,4,13,9,0,0,0,0,0,7,14,0,0,0,0,0,0,15,4,0,0,0,0,0,5,13,0,0,0,0,0,0,8,16,1,0,0,9 +0,0,6,16,0,0,0,0,0,0,13,7,0,0,0,0,0,2,16,0,0,0,0,0,0,0,16,3,0,0,0,0,0,4,15,10,10,4,0,0,0,5,16,14,8,13,8,0,0,2,16,13,11,15,11,0,0,0,5,12,13,7,0,0,6 +0,0,7,16,14,0,0,0,0,0,11,16,16,0,0,0,0,0,1,10,15,0,0,0,0,0,0,13,11,0,0,0,0,0,2,16,3,0,0,0,0,0,6,15,0,2,0,0,0,0,10,16,16,14,0,0,0,0,6,16,16,9,0,0,2 +0,0,4,15,16,5,0,0,0,0,7,16,16,13,0,0,0,0,15,16,16,9,0,0,0,0,13,16,16,7,0,0,0,2,14,16,16,7,0,0,0,0,10,16,16,5,0,0,0,0,6,16,16,10,2,0,0,0,2,10,14,3,0,0,1 +0,0,4,14,1,0,0,0,0,0,12,10,0,0,0,0,0,0,15,5,0,0,0,0,0,3,16,2,8,2,0,0,0,4,16,16,13,15,2,0,0,4,16,12,0,8,9,0,0,1,13,13,8,14,8,0,0,0,3,12,12,8,0,0,6 +0,0,2,11,15,16,10,0,0,0,13,14,10,15,11,0,0,0,11,2,1,16,3,0,0,0,0,0,8,15,2,0,0,0,0,11,16,16,10,0,0,0,1,16,13,4,1,0,0,0,0,14,6,0,0,0,0,0,2,16,2,0,0,0,7 +0,0,0,3,14,8,0,0,0,0,0,13,12,1,2,0,0,1,11,13,1,7,14,0,0,5,15,3,0,13,8,0,0,13,14,8,11,16,7,0,0,10,16,16,16,12,3,0,0,0,0,3,16,5,0,0,0,0,0,3,15,0,0,0,4 +0,0,3,12,15,9,1,0,0,2,16,13,9,14,10,0,0,6,16,8,0,11,10,0,0,4,16,16,14,16,4,0,0,0,4,12,16,15,0,0,0,0,1,11,16,16,2,0,0,0,7,16,16,14,0,0,0,0,3,15,13,2,0,0,8 +0,2,11,15,16,10,0,0,0,5,12,9,16,15,0,0,0,0,0,11,15,5,0,0,0,0,11,16,3,0,0,0,0,0,8,15,16,8,0,0,0,0,0,2,9,16,5,0,0,0,7,11,15,14,3,0,0,0,15,13,7,0,0,0,3 +0,0,8,14,14,4,0,0,0,0,6,14,16,11,1,0,0,0,3,15,16,15,3,0,0,0,1,16,16,13,0,0,0,0,2,16,16,8,0,0,0,0,10,16,16,6,0,0,0,1,14,16,15,2,0,0,0,0,12,16,8,0,0,0,1 +0,0,7,16,16,1,0,0,0,0,7,16,16,7,0,0,0,0,9,16,16,9,0,0,0,0,11,16,16,13,0,0,0,0,6,16,16,16,2,0,0,0,2,16,16,15,1,0,0,0,6,16,16,16,2,0,0,0,6,14,15,9,2,0,1 +0,0,0,11,16,2,7,0,0,0,7,16,7,10,13,0,0,2,16,7,4,16,9,0,0,6,16,13,12,16,15,0,0,1,11,16,16,12,3,0,0,0,0,2,16,2,0,0,0,0,0,7,12,0,0,0,0,0,0,11,9,0,0,0,4 +0,0,5,16,11,2,0,0,0,6,16,8,10,14,2,0,0,2,16,16,16,16,8,0,0,0,9,12,7,16,4,0,0,0,0,0,0,16,0,0,0,0,0,0,7,11,0,0,0,0,0,7,15,3,0,0,0,0,8,13,1,0,0,0,9 +0,0,5,13,15,5,0,0,0,0,13,16,12,16,2,0,0,5,16,4,0,15,4,0,0,6,16,1,0,11,8,0,0,8,16,1,1,14,6,0,0,4,16,8,6,16,6,0,0,0,14,16,16,10,0,0,0,0,7,15,14,0,0,0,0 +0,0,8,12,0,0,0,0,0,1,13,8,0,0,0,0,0,4,13,0,0,0,0,0,0,2,14,0,0,0,0,0,0,5,12,7,12,8,0,0,0,5,16,16,16,16,8,0,0,1,14,16,13,15,8,0,0,0,6,15,16,11,0,0,6 +0,5,16,12,0,0,0,0,0,11,16,16,5,0,0,0,0,0,2,16,8,0,0,0,0,0,2,16,8,0,0,0,0,0,7,16,4,0,0,0,0,2,15,11,0,0,0,0,0,10,16,16,16,16,9,0,0,8,16,14,9,8,3,0,2 +0,0,2,14,8,0,0,0,0,0,10,16,16,2,0,0,0,0,9,16,16,1,0,0,0,0,10,16,14,1,0,0,0,0,7,16,16,4,0,0,0,0,5,16,13,2,0,0,0,0,4,16,15,1,0,0,0,0,0,8,15,2,0,0,1 +0,0,3,14,9,1,0,0,0,0,8,16,13,0,0,0,0,0,10,16,15,1,0,0,0,0,8,16,15,0,0,0,0,0,10,16,15,1,0,0,0,0,6,16,16,1,0,0,0,0,3,16,15,2,0,0,0,0,2,12,16,3,0,0,1 +0,0,9,13,13,9,1,0,0,0,16,9,4,14,6,0,0,0,2,0,8,13,0,0,0,0,0,10,16,7,0,0,0,0,0,2,6,15,4,0,0,0,0,0,0,8,8,0,0,6,4,0,2,15,4,0,0,2,10,15,15,7,0,0,3 +0,0,10,16,16,15,5,0,0,0,5,5,7,15,10,0,0,0,0,0,1,16,7,0,0,0,6,12,13,16,3,0,0,0,7,14,16,13,7,0,0,0,0,13,11,0,0,0,0,0,6,16,2,0,0,0,0,0,13,11,0,0,0,0,7 +0,0,7,16,16,16,5,0,0,0,3,6,4,13,14,0,0,0,0,0,0,11,12,0,0,0,0,0,3,15,3,0,0,0,7,16,16,16,9,0,0,0,2,10,14,4,1,0,0,0,2,16,6,0,0,0,0,0,9,14,0,0,0,0,7 +0,0,4,13,15,8,0,0,0,2,13,4,0,12,0,0,0,6,9,0,2,10,0,0,0,3,13,1,4,15,5,0,0,0,5,14,14,3,0,0,0,0,3,16,13,0,0,0,0,0,8,6,14,4,0,0,0,0,6,13,15,3,0,0,8 +0,0,5,12,4,0,0,0,0,2,16,14,13,2,0,0,0,0,16,7,14,15,0,0,0,0,8,15,16,16,3,0,0,0,0,0,0,13,6,0,0,0,0,0,0,10,9,0,0,0,3,4,7,16,8,0,0,0,9,14,12,8,1,0,9 +0,0,15,16,7,0,0,0,0,3,16,6,16,3,0,0,0,0,12,6,12,9,0,0,0,0,0,0,12,7,0,0,0,0,0,0,15,4,0,0,0,0,0,6,15,2,0,0,0,1,11,15,13,2,8,1,0,0,13,14,15,16,16,3,2 +0,0,0,0,12,13,1,0,0,0,0,2,16,16,3,0,0,0,0,5,16,16,4,0,0,0,0,12,16,16,5,0,0,0,5,16,16,16,4,0,0,3,15,14,15,16,3,0,0,3,8,2,13,16,0,0,0,0,0,0,12,11,1,0,1 +0,6,15,16,10,0,0,0,0,15,13,9,16,2,0,0,0,7,9,0,14,7,0,0,0,0,0,0,12,8,0,0,0,0,0,2,16,5,0,0,0,0,0,11,15,1,0,0,0,1,11,16,12,2,0,0,0,6,16,16,16,16,14,0,2 +0,0,13,16,5,0,0,0,0,0,16,7,15,5,0,0,0,0,15,3,11,9,0,0,0,0,7,6,9,11,0,0,0,0,0,0,9,11,0,0,0,0,0,0,12,7,0,0,0,0,7,9,16,3,0,0,0,0,11,16,16,16,16,8,2 +0,1,10,16,12,1,0,0,0,8,15,5,12,11,0,0,0,11,12,3,13,16,3,0,0,4,14,16,13,14,9,0,0,0,0,4,0,8,13,0,0,0,1,0,0,3,16,1,0,2,15,1,0,8,15,1,0,0,9,16,16,16,6,0,9 +0,1,10,16,16,8,0,0,0,8,13,6,14,8,0,0,0,1,1,10,15,2,0,0,0,0,3,16,15,8,0,0,0,0,0,2,7,15,6,0,0,0,0,0,0,8,12,0,0,0,2,1,2,13,10,0,0,0,11,16,16,11,1,0,3 +0,0,10,8,0,0,0,0,0,0,13,11,0,0,0,0,0,1,16,7,0,0,0,0,0,4,16,3,0,0,0,0,0,4,16,9,11,10,1,0,0,8,16,16,16,16,7,0,0,4,16,16,16,16,5,0,0,0,8,15,16,12,0,0,6 +0,0,3,14,10,0,0,0,0,0,13,14,15,10,0,0,0,2,16,5,8,16,3,0,0,4,14,0,4,16,1,0,0,4,16,0,3,15,1,0,0,4,16,1,0,12,4,0,0,1,14,11,9,16,3,0,0,0,3,12,12,4,0,0,0 +0,0,11,16,11,0,0,0,0,0,13,12,14,6,0,0,0,0,6,14,10,10,0,0,0,0,0,1,6,14,0,0,0,0,0,0,6,14,0,0,0,0,0,0,9,14,0,0,0,0,9,9,15,12,2,0,0,0,10,16,16,16,16,4,2 +0,0,4,8,0,0,0,0,0,0,13,10,0,0,0,0,0,3,16,5,0,0,0,0,0,2,16,3,0,0,0,0,0,5,16,10,12,7,1,0,0,0,16,16,16,16,8,0,0,0,12,16,13,16,9,0,0,0,3,9,13,10,0,0,6 +0,0,0,2,16,12,0,0,0,0,0,6,16,15,0,0,0,0,0,11,16,11,0,0,0,1,8,16,16,11,0,0,0,9,16,16,16,10,0,0,0,1,8,8,16,8,0,0,0,0,0,4,16,9,0,0,0,0,0,1,16,10,0,0,1 +0,0,11,16,15,3,0,0,0,0,6,8,14,12,0,0,0,0,0,0,7,10,0,0,0,0,0,3,13,9,5,0,0,0,4,16,16,15,8,0,0,0,1,14,11,2,0,0,0,0,1,16,2,0,0,0,0,0,8,12,0,0,0,0,7 +0,2,15,16,13,1,0,0,0,8,16,9,15,8,0,0,0,8,16,0,8,12,0,0,0,0,5,1,7,13,0,0,0,0,0,0,12,10,0,0,0,0,0,4,16,7,0,0,0,5,15,15,16,2,0,0,0,3,11,12,16,16,13,2,2 +0,0,2,15,16,16,10,0,0,0,1,6,4,11,13,0,0,0,0,0,0,9,8,0,0,0,0,4,8,15,3,0,0,0,3,16,16,16,9,0,0,0,0,3,14,0,0,0,0,0,0,13,3,0,0,0,0,0,4,12,0,0,0,0,7 +0,4,16,16,6,0,0,0,0,7,13,8,15,1,0,0,0,0,0,6,16,2,0,0,0,0,3,16,14,1,0,0,0,0,1,11,15,13,1,0,0,0,0,0,3,14,8,0,0,5,6,0,0,7,16,0,0,3,14,16,16,16,10,0,3 +0,0,6,12,14,10,1,0,0,2,13,4,0,10,9,0,0,2,14,0,3,14,6,0,0,1,14,11,14,5,0,0,0,0,9,16,6,0,0,0,0,2,14,10,12,0,0,0,0,2,11,1,14,0,0,0,0,0,11,13,9,0,0,0,8 +0,0,3,15,15,5,0,0,0,0,13,13,10,15,0,0,0,0,12,14,13,16,5,0,0,0,1,8,8,14,6,0,0,0,0,0,0,8,12,0,0,0,1,0,0,3,16,1,0,1,14,2,0,3,16,2,0,0,3,15,16,16,13,1,9 +0,0,12,12,0,0,0,0,0,1,15,11,0,0,0,0,0,6,16,3,0,0,0,0,0,6,16,1,0,0,0,0,0,11,13,3,14,15,3,0,0,9,15,16,13,13,15,0,0,5,16,7,1,11,15,0,0,1,11,16,16,15,6,0,6 +0,0,7,13,0,0,0,0,0,2,15,11,0,0,0,0,0,9,16,2,0,0,0,0,0,7,16,0,0,0,0,0,0,9,16,2,4,4,0,0,0,5,16,16,16,16,12,0,0,3,16,16,9,13,16,0,0,0,5,13,16,16,7,0,6 +0,0,0,0,1,14,4,0,0,0,0,0,2,16,8,0,0,0,0,0,8,16,6,0,0,0,0,0,15,16,5,0,0,0,0,8,16,16,3,0,0,0,6,16,9,16,0,0,0,2,16,8,4,16,0,0,0,2,4,0,2,15,3,0,1 +0,0,1,12,16,16,12,0,0,0,0,7,4,11,13,0,0,0,0,0,0,8,11,0,0,0,0,1,4,12,8,0,0,0,0,12,16,16,8,0,0,0,0,3,12,6,0,0,0,0,0,6,14,0,0,0,0,0,0,15,5,0,0,0,7 +0,0,8,16,16,16,12,3,0,0,2,2,0,5,16,8,0,0,0,0,0,5,15,1,0,0,0,0,1,13,8,0,0,1,8,8,10,16,1,0,0,3,12,15,16,11,3,0,0,0,2,15,7,0,0,0,0,0,11,11,0,0,0,0,7 +0,0,10,14,16,16,8,0,0,0,16,12,5,5,11,0,0,4,15,11,6,0,0,0,0,10,16,16,16,11,0,0,0,5,7,0,5,15,0,0,0,0,0,0,7,14,0,0,0,0,5,4,14,7,0,0,0,0,8,16,14,1,0,0,5 +0,0,8,16,5,0,0,0,0,0,16,9,0,0,0,0,0,6,16,2,0,0,0,0,0,7,16,0,1,0,0,0,0,10,13,9,16,14,3,0,0,8,15,16,9,9,15,0,0,4,16,13,0,9,16,1,0,0,6,16,16,16,10,0,6 +0,0,4,15,15,4,0,0,0,0,9,16,16,9,0,0,0,0,7,16,16,9,0,0,0,0,7,16,16,13,0,0,0,0,10,16,16,10,0,0,0,1,15,16,16,4,0,0,0,0,13,16,16,3,0,0,0,0,4,10,16,4,0,0,1 +0,0,10,12,2,0,0,0,0,4,15,13,13,0,0,0,0,5,12,0,14,2,0,0,0,2,10,0,13,3,0,0,0,0,0,2,14,0,0,0,0,0,0,9,9,0,0,0,0,0,11,16,8,4,2,0,0,0,13,15,16,16,7,0,2 +0,0,3,14,16,16,7,0,0,0,11,9,4,12,15,0,0,0,0,0,0,8,14,0,0,0,0,0,0,12,9,0,0,0,5,11,12,16,5,0,0,0,11,16,16,12,3,0,0,0,0,9,13,1,0,0,0,0,3,16,2,0,0,0,7 +0,0,8,15,8,0,0,0,0,4,16,16,16,5,0,0,0,8,16,2,6,15,0,0,0,3,15,4,4,16,0,0,0,0,1,0,7,15,0,0,0,0,0,3,15,7,0,0,0,2,11,16,15,7,4,0,0,1,13,16,15,12,4,0,2 +0,0,7,14,16,16,7,0,0,0,14,13,8,5,1,0,0,3,16,10,3,0,0,0,0,10,16,16,16,2,0,0,0,3,4,0,11,8,0,0,0,0,0,0,10,8,0,0,0,0,14,5,16,4,0,0,0,0,9,16,14,0,0,0,5 +0,0,0,6,15,1,0,0,0,0,4,16,3,0,0,0,0,0,12,8,0,0,0,0,0,7,15,1,0,5,9,0,0,9,16,0,6,15,10,0,0,10,16,16,16,14,0,0,0,2,5,7,16,7,0,0,0,0,0,6,15,1,0,0,4 +0,0,8,12,12,1,0,0,0,6,12,2,6,8,0,0,0,5,11,0,3,16,7,0,0,0,14,6,10,10,1,0,0,0,6,16,8,0,0,0,0,0,7,16,5,0,0,0,0,0,12,10,10,0,0,0,0,0,11,14,9,0,0,0,8 +0,0,6,15,8,0,0,0,0,4,16,13,16,2,0,0,0,6,15,0,13,13,0,0,0,8,10,0,7,16,5,0,0,8,8,0,6,16,3,0,0,7,11,0,7,15,0,0,0,0,14,10,13,11,0,0,0,0,6,14,12,2,0,0,0 +0,0,11,16,13,2,0,0,0,0,10,15,11,13,0,0,0,0,3,15,7,15,5,0,0,0,0,1,1,13,6,0,0,0,0,0,0,16,5,0,0,0,0,0,9,16,2,0,0,0,15,16,16,11,0,0,0,0,9,15,13,16,16,5,2 +0,0,3,12,7,0,0,0,0,0,14,11,11,6,0,0,0,4,12,0,2,16,0,0,0,5,9,0,0,10,7,0,0,6,8,0,0,8,8,0,0,3,12,0,0,3,10,0,0,1,13,5,3,13,5,0,0,0,3,13,16,9,1,0,0 +0,0,8,14,11,2,0,0,0,2,16,10,13,9,0,0,0,4,16,4,1,15,5,0,0,2,14,12,11,16,7,0,0,0,6,16,16,6,0,0,0,0,12,16,12,0,0,0,0,0,16,16,15,0,0,0,0,0,8,15,8,0,0,0,8 +0,0,15,8,0,0,0,0,0,2,16,15,2,0,0,0,0,0,16,8,10,0,0,0,0,0,12,8,12,0,0,0,0,0,0,8,8,0,0,0,0,0,0,12,8,0,0,0,0,0,12,16,15,15,5,0,0,0,15,11,6,2,0,0,2 +0,0,4,13,13,8,0,0,0,0,16,1,1,11,2,0,0,4,13,0,0,6,7,0,0,3,14,0,9,14,2,0,0,0,9,15,14,1,0,0,0,0,5,16,9,0,0,0,0,0,8,12,15,2,0,0,0,0,4,15,13,2,0,0,8 +0,0,14,16,11,1,0,0,0,1,12,16,16,6,0,0,0,0,0,4,16,7,0,0,0,0,1,10,16,13,2,0,0,0,5,16,16,16,8,0,0,0,1,16,9,1,0,0,0,0,9,16,1,0,0,0,0,0,13,12,0,0,0,0,7 +0,1,14,16,16,16,5,0,0,4,16,7,4,5,3,0,0,7,16,5,1,0,0,0,0,8,16,16,15,1,0,0,0,0,2,4,15,7,0,0,0,0,0,0,10,13,0,0,0,6,9,1,13,14,0,0,0,1,14,16,15,3,0,0,5 +0,0,2,13,1,0,0,0,0,0,5,15,0,0,0,0,0,0,13,6,0,0,0,0,0,5,14,0,0,0,0,0,0,8,12,3,7,6,1,0,0,7,16,14,8,14,7,0,0,2,15,9,5,12,7,0,0,0,4,9,13,13,1,0,6 +0,2,15,16,15,3,0,0,0,4,16,7,13,13,0,0,0,0,7,12,16,8,0,0,0,0,7,16,15,6,0,0,0,0,0,4,11,16,4,0,0,1,0,0,0,14,11,0,0,9,9,0,6,14,9,0,0,3,14,16,16,13,1,0,3 +0,0,8,16,16,9,0,0,0,0,15,9,6,14,2,0,0,0,16,5,1,16,8,0,0,0,9,16,16,16,11,0,0,0,0,6,7,10,12,0,0,1,3,0,0,8,14,0,0,4,13,5,0,11,12,0,0,0,9,16,16,16,6,0,9 +0,0,0,8,14,0,0,0,0,0,5,16,3,0,0,0,0,0,15,9,0,0,2,0,0,8,15,1,0,11,14,0,0,11,13,4,7,16,5,0,0,8,16,16,16,13,0,0,0,0,0,5,16,3,0,0,0,0,0,10,14,0,0,0,4 +0,0,0,15,10,0,0,0,0,0,4,16,16,7,0,0,0,0,3,16,16,10,0,0,0,0,3,16,16,14,0,0,0,0,4,16,16,16,2,0,0,0,10,16,16,16,5,0,0,0,9,16,16,16,9,0,0,0,1,8,3,7,16,2,1 +0,0,3,13,0,0,0,0,0,0,12,6,0,0,0,0,0,2,16,1,0,0,0,0,0,5,12,0,0,0,0,0,0,8,10,5,11,7,0,0,0,6,16,16,9,12,8,0,0,0,14,9,4,11,8,0,0,0,3,12,13,9,1,0,6 +0,1,13,16,16,6,0,0,0,3,13,6,12,13,0,0,0,0,0,5,15,7,0,0,0,0,2,16,15,2,0,0,0,0,0,4,14,14,1,0,0,0,0,0,3,15,5,0,0,1,10,1,2,13,7,0,0,1,13,16,16,13,1,0,3 +0,0,1,14,15,3,0,0,0,0,0,15,16,9,0,0,0,0,0,15,16,7,0,0,0,0,2,15,16,5,0,0,0,0,4,16,15,1,0,0,0,0,7,16,10,0,0,0,0,0,8,16,8,0,0,0,0,0,2,12,16,5,0,0,1 +0,0,2,10,12,13,6,0,0,0,15,14,8,7,0,0,0,4,16,0,0,0,0,0,0,7,16,16,15,2,0,0,0,3,14,8,15,6,0,0,0,0,0,0,12,7,0,0,0,0,0,6,14,4,0,0,0,0,0,13,12,0,0,0,5 +0,0,6,16,6,0,0,0,0,2,15,14,16,5,0,0,0,6,15,1,9,14,0,0,0,4,16,0,1,16,5,0,0,7,13,0,1,16,4,0,0,5,15,2,0,14,5,0,0,0,14,10,12,13,1,0,0,0,4,14,14,3,0,0,0 +0,0,7,16,16,4,0,0,0,5,16,7,8,13,1,0,0,10,13,0,6,16,7,0,0,5,16,12,15,16,9,0,0,0,5,8,3,11,12,0,0,0,0,0,0,9,12,0,0,0,1,4,0,13,12,0,0,0,9,16,16,13,3,0,9 +0,0,8,16,16,12,4,0,0,0,12,10,8,8,4,0,0,4,16,13,3,0,0,0,0,6,16,16,15,1,0,0,0,0,0,0,12,6,0,0,0,0,0,0,9,6,0,0,0,0,4,7,16,3,0,0,0,0,4,16,6,0,0,0,5 +0,0,15,15,4,0,0,0,0,0,16,13,15,2,0,0,0,0,9,15,12,8,0,0,0,0,0,2,11,10,0,0,0,0,0,0,13,8,0,0,0,0,0,1,14,5,0,0,0,0,16,16,16,6,1,0,0,1,11,15,12,15,15,4,2 +0,0,5,13,12,1,0,0,0,2,15,5,6,7,0,0,0,6,9,0,0,16,4,0,0,6,10,0,0,14,6,0,0,0,9,13,14,12,9,0,0,0,0,0,0,0,12,0,0,0,3,0,0,3,14,0,0,0,5,12,13,14,5,0,9 +0,0,1,10,10,1,0,0,0,0,7,16,16,5,0,0,0,0,13,16,16,3,0,0,0,0,14,16,16,8,0,0,0,0,14,16,7,2,0,0,0,0,10,16,16,6,0,0,0,0,7,16,16,5,0,0,0,0,2,10,12,5,0,0,1 +0,1,6,9,15,16,14,0,0,4,16,16,10,7,2,0,0,8,16,13,3,0,0,0,0,10,14,14,14,0,0,0,0,1,1,1,16,0,0,0,0,0,0,1,16,0,0,0,0,0,3,10,14,0,0,0,0,0,15,14,3,0,0,0,5 +0,0,3,14,16,6,0,0,0,1,13,5,4,13,0,0,0,5,12,0,0,9,4,0,0,5,9,0,0,5,8,0,0,8,7,0,0,3,8,0,0,6,9,0,0,2,9,0,0,0,15,3,1,9,8,0,0,0,5,15,14,9,0,0,0 +0,0,5,15,16,16,10,0,0,0,11,14,8,4,1,0,0,4,16,16,15,3,0,0,0,8,15,9,13,13,1,0,0,0,2,0,3,16,4,0,0,0,0,0,3,16,2,0,0,0,0,3,13,9,0,0,0,0,5,16,12,1,0,0,5 +0,0,6,15,16,15,2,0,0,0,4,6,6,15,7,0,0,0,0,0,0,16,3,0,0,0,2,8,10,16,4,0,0,0,7,12,16,13,5,0,0,0,0,5,14,0,0,0,0,0,1,15,5,0,0,0,0,0,8,12,0,0,0,0,7 +0,0,4,14,5,0,0,0,0,0,13,7,11,4,2,0,0,2,13,0,6,14,4,0,0,3,9,0,3,14,7,0,0,5,8,0,4,11,8,0,0,4,8,0,0,1,12,0,0,1,14,2,0,10,7,0,0,0,3,15,16,13,2,0,0 +0,1,12,16,12,0,0,0,0,11,13,5,16,3,8,0,0,10,13,4,16,15,3,0,0,2,16,12,15,6,0,0,0,0,14,14,2,0,0,0,0,2,16,16,3,0,0,0,0,4,15,12,11,0,0,0,0,0,14,16,13,0,0,0,8 +0,0,7,14,15,13,10,0,0,1,14,8,3,0,2,0,0,4,16,10,15,6,0,0,0,4,16,12,11,16,3,0,0,2,7,0,0,12,6,0,0,0,0,0,1,14,3,0,0,0,9,9,11,12,0,0,0,0,8,13,8,0,0,0,5 +0,0,0,11,9,0,0,0,0,0,6,16,8,0,0,0,0,0,13,12,0,0,0,0,0,0,16,6,4,2,0,0,0,0,16,16,15,15,4,0,0,0,15,6,0,0,15,1,0,0,9,13,0,6,14,5,0,0,0,9,16,16,10,0,6 +0,0,3,13,9,2,0,0,0,0,5,16,16,7,0,0,0,0,10,16,16,0,0,0,0,1,15,16,14,1,0,0,0,6,16,16,14,0,0,0,0,0,12,16,10,0,0,0,0,0,7,16,14,0,0,0,0,0,3,15,16,12,0,0,1 +0,0,0,1,16,9,0,0,0,0,0,6,16,11,0,0,0,0,0,11,16,6,0,0,0,1,7,15,14,1,0,0,0,6,14,16,15,0,0,0,0,0,1,13,15,0,0,0,0,0,0,14,16,4,0,0,0,0,0,0,12,15,6,0,1 +0,0,9,14,15,4,0,0,0,0,16,9,9,16,0,0,0,0,0,1,13,12,0,0,0,0,2,15,16,3,0,0,0,0,2,12,12,12,1,0,0,0,0,0,0,9,9,0,0,0,14,9,5,13,12,0,0,0,9,16,16,14,1,0,3 +0,0,7,14,15,2,0,0,0,0,8,4,4,15,0,0,0,0,0,0,8,11,0,0,0,0,2,16,16,4,0,0,0,0,1,8,13,13,0,0,0,0,0,0,0,7,10,0,0,2,14,2,3,12,9,0,0,1,12,16,16,10,1,0,3 +0,0,0,1,13,13,0,0,0,0,5,15,15,16,0,0,0,3,15,8,7,14,0,0,0,11,15,9,15,16,9,0,0,8,16,14,15,15,5,0,0,0,0,0,12,8,0,0,0,0,0,0,12,6,0,0,0,0,0,0,12,4,0,0,4 +0,0,0,12,4,0,0,0,0,0,6,16,10,0,0,0,0,0,14,11,0,0,0,0,0,0,13,7,0,0,0,0,0,0,14,16,15,9,1,0,0,0,12,11,4,10,13,0,0,0,4,14,4,10,16,3,0,0,0,9,16,14,9,0,6 +0,0,0,2,12,6,14,0,0,0,1,15,13,8,10,0,0,2,15,7,0,15,4,0,0,9,15,7,7,16,5,0,0,8,16,16,16,16,10,0,0,1,4,4,13,8,0,0,0,0,0,3,15,1,0,0,0,0,0,3,14,0,0,0,4 +0,0,0,4,13,12,1,0,0,0,3,16,9,15,14,2,0,0,14,8,2,10,14,0,0,3,16,12,13,16,7,0,0,0,8,3,2,16,2,0,0,0,0,0,8,12,0,0,0,0,0,0,14,6,0,0,0,0,0,3,14,2,0,0,9 +0,0,4,14,10,0,0,0,0,3,16,9,8,7,0,0,0,8,16,0,2,11,0,0,0,6,14,0,0,6,6,0,0,5,14,0,0,3,9,0,0,0,15,1,0,5,13,0,0,0,12,9,2,13,10,0,0,0,2,14,15,10,1,0,0 +0,0,8,13,12,2,0,0,0,4,16,3,2,13,0,0,0,10,13,5,12,12,0,0,0,5,14,16,16,3,0,0,0,0,8,15,9,9,0,0,0,0,12,6,0,8,5,0,0,0,11,8,4,12,6,0,0,0,5,12,13,10,0,0,8 +0,0,6,13,12,2,0,0,0,7,14,4,9,10,0,0,0,8,3,0,10,7,0,0,0,0,2,9,16,2,0,0,0,0,10,12,12,14,1,0,0,0,0,0,0,6,12,0,0,0,11,5,4,12,10,0,0,0,6,13,13,10,1,0,3 +0,0,2,8,11,16,16,14,0,0,10,15,10,11,16,7,0,0,0,0,0,13,9,0,0,0,4,8,11,16,4,0,0,0,13,16,16,11,5,0,0,0,0,4,16,1,0,0,0,0,0,12,11,0,0,0,0,0,0,15,5,0,0,0,7 +0,0,5,13,12,3,0,0,0,0,14,9,8,13,0,0,0,3,16,4,0,13,6,0,0,4,14,1,0,8,9,0,0,1,15,0,0,4,8,0,0,2,13,1,2,13,4,0,0,0,15,13,16,11,0,0,0,0,8,11,5,0,0,0,0 +0,0,0,12,13,1,0,0,0,0,7,16,10,1,0,0,0,0,11,13,0,0,0,0,0,0,13,10,4,1,0,0,0,0,13,16,16,13,3,0,0,2,16,14,6,10,15,2,0,2,13,14,8,13,15,1,0,0,1,13,16,14,3,0,6 +0,0,0,7,13,14,4,0,0,0,15,13,4,0,1,0,0,1,16,2,0,0,0,0,0,5,16,9,14,13,1,0,0,7,15,8,1,11,4,0,0,0,0,0,0,13,4,0,0,0,0,5,7,12,0,0,0,0,0,8,14,3,0,0,5 +0,0,7,11,4,0,0,0,0,0,15,16,15,5,0,0,0,3,16,5,3,10,2,0,0,4,16,2,0,7,7,0,0,8,10,0,0,6,8,0,0,5,9,0,0,10,6,0,0,0,15,4,10,14,2,0,0,0,7,16,10,2,0,0,0 +0,0,2,12,6,0,0,0,0,1,12,10,11,3,0,0,0,1,16,7,3,13,1,0,0,2,16,1,0,9,5,0,0,2,16,2,0,2,10,0,0,0,14,8,0,7,13,0,0,0,10,7,7,16,5,0,0,0,2,13,12,6,0,0,0 +0,0,3,10,13,16,7,0,0,0,10,10,8,12,13,0,0,0,0,0,0,10,10,0,0,5,12,12,12,15,8,0,0,5,12,12,15,11,1,0,0,0,0,5,15,3,0,0,0,0,0,11,9,0,0,0,0,0,3,16,2,0,0,0,7 +0,0,6,12,16,16,13,0,0,0,13,12,8,14,16,2,0,0,1,5,7,15,11,0,0,0,13,16,16,16,11,0,0,0,3,9,16,3,0,0,0,0,0,11,13,0,0,0,0,0,5,16,7,0,0,0,0,0,9,15,2,0,0,0,7 +0,0,4,16,13,16,13,0,0,0,12,11,5,4,2,0,0,1,16,1,9,8,2,0,0,8,16,14,10,13,6,0,0,1,4,0,0,9,6,0,0,0,0,0,5,11,1,0,0,0,1,6,14,4,0,0,0,0,6,15,5,0,0,0,5 +0,0,0,2,12,13,8,1,0,0,3,12,5,14,16,2,0,0,11,1,0,12,14,0,0,2,11,3,7,14,3,0,0,3,15,11,5,10,0,0,0,0,0,0,9,6,0,0,0,0,0,1,14,2,0,0,0,0,0,0,14,1,0,0,9 +0,0,1,16,12,2,0,0,0,0,4,15,16,5,0,0,0,0,8,16,13,0,0,0,0,1,12,16,11,0,0,0,0,5,16,16,11,0,0,0,0,0,9,16,9,0,0,0,0,0,4,16,12,1,0,0,0,0,1,14,16,10,0,0,1 +0,0,0,1,13,12,3,0,0,0,6,13,11,12,10,0,0,6,16,11,4,13,7,0,0,8,16,16,16,16,10,0,0,0,4,4,7,16,5,0,0,0,0,0,12,8,0,0,0,0,0,0,13,7,0,0,0,0,0,0,15,4,0,0,4 +0,4,15,16,7,0,0,0,0,4,16,11,16,2,0,0,0,2,6,4,16,3,0,0,0,0,0,1,16,4,0,0,0,0,0,8,14,1,0,0,0,0,2,15,10,0,0,0,0,3,15,16,13,12,11,0,0,5,16,16,15,12,12,0,2 +0,0,3,11,16,15,5,0,0,0,10,6,4,12,10,0,0,0,0,0,0,12,8,0,0,4,12,10,12,16,3,0,0,2,8,5,16,9,0,0,0,0,0,6,11,0,0,0,0,0,0,13,6,0,0,0,0,0,0,15,1,0,0,0,7 +0,0,7,15,6,0,0,0,0,1,15,12,15,0,0,0,0,3,15,1,12,2,0,0,0,0,11,0,13,3,0,0,0,0,0,1,14,1,0,0,0,0,0,7,12,0,0,0,0,0,7,16,16,16,14,3,0,0,6,12,8,8,8,3,2 +0,1,8,13,16,8,0,0,0,11,13,6,8,15,1,0,0,2,1,9,14,5,0,0,0,0,14,16,14,3,0,0,0,0,8,4,10,15,1,0,0,0,0,0,0,13,7,0,0,2,15,8,9,15,1,0,0,1,10,13,13,4,0,0,3 +0,0,0,0,8,16,11,0,0,0,0,13,9,8,14,1,0,0,10,8,0,5,16,4,0,2,15,8,14,14,12,0,0,2,12,9,2,10,6,0,0,0,0,0,2,14,0,0,0,0,0,0,9,7,0,0,0,0,0,0,13,1,0,0,9 +0,0,9,14,12,8,0,0,0,8,13,0,2,16,2,0,0,11,14,14,14,6,0,0,0,1,14,16,13,1,0,0,0,3,15,4,6,12,0,0,0,1,15,0,0,8,9,0,0,3,16,1,3,13,6,0,0,0,8,16,13,6,0,0,8 +0,0,7,16,15,11,5,0,0,0,14,11,8,8,5,0,0,1,16,2,8,5,0,0,0,8,15,15,15,15,3,0,0,8,15,5,0,12,4,0,0,0,0,0,2,15,1,0,0,0,6,8,13,8,0,0,0,0,8,15,10,0,0,0,5 +0,0,0,2,15,2,0,0,0,0,0,12,9,0,0,0,0,0,5,15,2,2,0,0,0,1,13,6,0,14,3,0,0,5,15,0,8,16,1,0,0,9,16,16,16,16,3,0,0,0,4,4,16,7,0,0,0,0,0,3,15,4,0,0,4 +0,0,0,7,15,14,2,0,0,4,13,9,8,16,4,0,0,7,16,6,3,16,3,0,0,0,7,16,16,8,0,0,0,0,1,16,14,14,0,0,0,0,7,11,0,15,8,0,0,0,6,15,5,15,5,0,0,0,0,9,14,7,0,0,8 +0,0,8,14,9,0,0,0,0,6,15,12,16,3,0,0,0,1,3,0,8,4,0,0,0,0,0,0,9,5,0,0,0,0,0,1,15,3,0,0,0,0,0,12,11,0,0,0,0,0,10,16,13,12,6,0,0,0,11,12,12,9,4,0,2 +0,0,0,6,12,12,0,0,0,0,0,15,16,13,0,0,0,0,7,16,16,12,0,0,0,3,16,16,16,11,0,0,0,3,12,16,16,9,0,0,0,0,0,11,16,9,0,0,0,0,0,13,16,10,0,0,0,0,0,9,13,11,0,0,1 +0,0,4,12,12,6,0,0,0,3,16,9,8,15,0,0,0,9,16,3,0,8,6,0,0,6,14,0,0,6,8,0,0,4,11,0,0,9,5,0,0,4,10,0,0,15,2,0,0,0,15,5,9,14,0,0,0,0,7,14,13,2,0,0,0 +0,0,0,1,15,2,0,0,0,0,0,8,16,0,0,0,0,0,2,16,6,6,6,0,0,0,11,13,2,14,9,0,0,6,16,9,10,16,10,0,0,15,16,16,16,16,5,0,0,2,4,4,16,9,0,0,0,0,0,2,16,9,0,0,4 +0,0,7,12,8,0,0,0,0,6,16,11,15,3,0,0,0,10,8,0,11,5,0,0,0,0,0,0,11,3,0,0,0,0,0,0,14,1,0,0,0,0,0,7,13,0,0,0,0,0,8,16,16,14,9,0,0,0,11,13,11,8,9,0,2 +0,0,1,8,13,12,1,0,0,1,15,8,4,14,6,0,0,6,10,0,1,10,8,0,0,7,12,5,11,16,7,0,0,2,11,12,10,16,2,0,0,0,0,0,9,10,0,0,0,0,0,4,16,1,0,0,0,0,0,12,7,0,0,0,9 +0,2,5,7,10,13,4,0,0,3,16,13,12,11,3,0,0,0,16,1,0,0,0,0,0,6,16,16,15,2,0,0,0,3,8,4,10,9,0,0,0,0,0,0,8,11,0,0,0,0,1,9,15,3,0,0,0,2,16,12,2,0,0,0,5 +0,1,10,16,11,0,0,0,0,5,16,9,14,6,0,0,0,1,3,0,11,8,0,0,0,0,0,0,11,8,0,0,0,0,0,4,15,0,0,0,0,0,0,9,11,0,0,0,0,0,11,16,10,12,8,0,0,0,11,12,12,12,14,0,2 +0,0,0,4,13,0,0,0,0,0,0,13,9,0,0,0,0,0,4,15,2,2,4,0,0,1,15,6,0,9,10,0,0,10,16,8,9,16,4,0,0,3,12,12,16,12,0,0,0,0,0,2,16,6,0,0,0,0,0,5,16,3,0,0,4 +0,0,6,9,12,12,8,0,0,2,16,12,8,8,6,0,0,0,15,8,4,0,0,0,0,0,14,16,16,13,1,0,0,0,9,7,5,16,7,0,0,0,0,0,2,16,5,0,0,0,0,6,15,9,0,0,0,0,15,15,7,0,0,0,5 +0,0,2,14,16,15,9,0,0,1,14,11,5,9,14,0,0,4,14,0,3,12,11,0,0,6,13,9,15,15,1,0,0,2,13,15,15,11,0,0,0,0,0,4,15,0,0,0,0,0,1,13,11,0,0,0,0,0,3,16,5,0,0,0,9 +0,0,4,14,12,7,1,0,0,4,13,9,7,16,4,0,0,12,12,0,9,15,1,0,0,3,16,10,15,6,0,0,0,0,5,16,16,3,0,0,0,0,5,13,5,15,3,0,0,0,8,8,7,16,4,0,0,0,3,15,16,9,0,0,8 +0,0,1,9,15,13,1,0,0,0,11,12,6,16,4,0,0,10,15,1,7,14,0,0,0,3,15,14,15,3,0,0,0,0,1,12,15,3,0,0,0,0,0,12,8,13,1,0,0,0,0,12,6,16,2,0,0,0,0,9,15,6,0,0,8 +0,0,4,15,14,7,0,0,0,2,14,10,8,15,2,0,0,8,16,2,1,16,5,0,0,1,14,14,14,12,0,0,0,0,1,15,16,7,0,0,0,0,7,14,8,15,2,0,0,0,8,9,3,14,7,0,0,0,5,16,16,11,1,0,8 +0,0,6,10,11,15,4,0,0,0,10,15,12,12,3,0,0,0,14,6,0,0,0,0,0,5,16,9,9,7,0,0,0,2,12,12,12,16,7,0,0,0,0,0,1,13,7,0,0,0,0,7,13,13,0,0,0,0,9,12,10,1,0,0,5 +0,0,1,9,16,13,1,0,0,0,13,9,5,14,8,0,0,3,14,0,0,13,12,0,0,2,15,12,16,16,3,0,0,0,3,8,11,12,0,0,0,0,0,3,14,2,0,0,0,0,0,12,9,0,0,0,0,0,1,15,3,0,0,0,9 +0,0,0,8,16,10,0,0,0,0,1,14,16,14,0,0,0,1,10,16,16,5,0,0,0,6,16,16,16,3,0,0,0,1,12,16,16,0,0,0,0,0,4,16,15,0,0,0,0,0,4,16,16,4,0,0,0,0,0,11,12,11,0,0,1 +0,0,3,14,9,0,0,0,0,1,16,15,13,10,0,0,0,7,16,2,1,15,2,0,0,7,16,2,0,9,7,0,0,5,16,0,0,9,8,0,0,0,16,4,0,9,11,0,0,0,13,11,9,16,5,0,0,0,3,14,16,9,1,0,0 +0,0,13,12,1,0,0,0,0,11,16,14,12,0,0,0,0,5,8,1,16,1,0,0,0,0,0,0,16,3,0,0,0,0,0,8,16,0,0,0,0,0,3,14,9,0,0,0,0,0,15,16,13,13,11,0,0,0,11,12,12,14,16,2,2 +0,0,0,9,15,7,0,0,0,0,7,16,10,6,0,0,0,0,14,8,0,0,0,0,0,1,16,5,11,8,0,0,0,3,16,16,10,15,9,0,0,1,15,9,0,4,13,0,0,0,11,14,6,11,15,0,0,0,0,10,14,13,8,0,6 +0,0,9,12,14,16,8,0,0,1,12,12,10,14,11,0,0,0,0,0,3,16,2,0,0,1,10,8,12,16,7,0,0,2,16,16,16,16,10,0,0,0,2,14,10,5,1,0,0,0,6,16,2,0,0,0,0,0,11,11,0,0,0,0,7 +0,0,4,14,16,11,1,0,0,0,15,10,4,13,11,0,0,3,16,3,4,12,10,0,0,1,13,16,16,16,7,0,0,0,0,0,5,16,1,0,0,0,0,1,13,9,0,0,0,0,2,13,14,0,0,0,0,0,5,16,7,0,0,0,9 +0,0,10,16,16,9,0,0,0,1,6,4,5,15,4,0,0,0,0,0,3,15,2,0,0,1,4,0,12,8,0,0,0,3,16,16,16,16,8,0,0,0,2,13,7,0,0,0,0,0,7,12,0,0,0,0,0,0,12,6,0,0,0,0,7 +0,0,1,14,6,0,0,0,0,0,9,15,3,0,0,0,0,0,12,6,0,0,0,0,0,0,16,3,0,0,0,0,0,3,16,16,16,14,4,0,0,1,16,7,4,6,13,0,0,0,10,10,0,4,14,0,0,0,1,11,14,16,7,0,6 +0,0,0,3,14,12,0,0,0,0,1,11,16,14,0,0,0,0,8,16,16,14,0,0,0,6,16,16,16,10,0,0,0,1,5,16,16,8,0,0,0,0,0,15,16,9,0,0,0,0,0,10,16,14,5,0,0,0,0,2,15,15,7,0,1 +0,1,11,13,7,0,0,0,0,6,15,13,15,4,0,0,0,2,2,0,9,9,0,0,0,0,0,0,8,8,0,0,0,0,0,0,13,5,0,0,0,0,0,8,12,0,0,0,0,0,8,16,10,9,3,0,0,0,11,15,15,16,7,0,2 +0,0,4,10,12,15,16,5,0,0,15,14,12,13,16,7,0,0,3,0,0,8,15,2,0,0,1,8,8,14,9,0,0,0,5,16,16,16,4,0,0,0,0,7,16,4,0,0,0,0,0,12,14,0,0,0,0,0,3,16,5,0,0,0,7 +0,2,8,12,12,15,8,0,0,4,16,12,7,5,2,0,0,4,15,0,0,0,0,0,0,7,15,9,5,0,0,0,0,1,8,12,15,5,0,0,0,0,0,0,11,12,0,0,0,2,8,9,16,4,0,0,0,3,14,14,6,0,0,0,5 +0,0,1,11,15,1,0,0,0,0,11,15,5,0,0,0,0,3,16,6,0,0,0,0,0,6,7,0,5,3,0,0,0,8,10,4,13,15,2,0,0,4,16,4,0,8,10,0,0,0,13,10,5,14,9,0,0,0,1,10,13,13,4,0,6 +0,0,2,11,11,2,0,0,0,0,13,13,11,12,0,0,0,7,13,1,0,12,1,0,0,7,9,0,0,7,5,0,0,6,10,0,0,7,8,0,0,2,13,0,0,10,7,0,0,0,10,10,6,16,3,0,0,0,2,11,15,9,0,0,0 +0,0,5,12,12,3,0,0,0,0,9,16,16,12,0,0,0,0,9,16,16,15,0,0,0,0,11,16,16,10,0,0,0,0,11,16,16,9,0,0,0,0,13,16,16,3,0,0,0,0,12,16,15,2,0,0,0,0,2,9,11,2,0,0,1 +0,0,7,16,16,13,0,0,0,0,13,11,11,16,4,0,0,0,0,0,5,16,3,0,0,0,0,0,9,15,0,0,0,1,10,16,16,15,3,0,0,3,12,16,15,12,4,0,0,0,3,16,7,0,0,0,0,0,11,13,0,0,0,0,7 +0,0,6,12,13,7,0,0,0,0,13,13,8,7,0,0,0,2,16,1,0,0,0,0,0,5,16,12,12,7,0,0,0,2,9,8,8,15,1,0,0,0,0,0,0,13,4,0,0,0,4,4,12,14,1,0,0,0,7,13,11,3,0,0,5 +0,0,5,15,5,0,0,0,0,0,13,11,12,0,0,0,0,0,14,7,2,4,4,0,0,0,9,15,14,13,5,0,0,1,13,16,13,0,0,0,0,7,14,4,14,9,0,0,0,0,15,4,2,14,7,0,0,0,3,13,16,16,7,0,8 +0,0,2,12,12,3,0,0,0,0,4,16,16,13,0,0,0,0,3,16,16,11,0,0,0,0,5,16,16,11,0,0,0,0,9,16,16,9,0,0,0,0,9,16,16,8,0,0,0,0,7,16,16,10,0,0,0,0,1,11,12,9,0,0,1 +0,0,6,15,12,0,0,0,0,0,13,16,16,13,0,0,0,2,15,16,7,13,4,0,0,5,11,7,0,5,7,0,0,8,7,0,0,7,8,0,0,4,12,0,1,11,9,0,0,2,13,8,12,15,1,0,0,0,5,15,12,3,0,0,0 +0,0,6,15,16,16,9,0,0,2,16,11,8,15,12,0,0,0,2,0,0,14,11,0,0,0,3,8,8,16,7,0,0,3,16,16,16,16,10,0,0,0,6,6,15,4,0,0,0,0,0,15,7,0,0,0,0,0,8,12,0,0,0,0,7 +0,0,4,12,12,0,0,0,0,2,16,8,12,10,0,0,0,7,10,4,14,14,0,0,0,2,13,14,11,14,0,0,0,0,0,0,0,13,2,0,0,0,0,0,0,6,7,0,0,0,4,2,0,7,8,0,0,0,5,14,16,11,2,0,9 +0,0,0,5,15,5,0,0,0,0,3,15,11,1,0,0,0,1,13,14,1,2,6,0,0,6,16,6,0,12,15,0,0,13,15,0,4,16,6,0,0,13,16,16,16,16,1,0,0,0,8,11,16,8,0,0,0,0,0,7,16,3,0,0,4 +0,3,11,16,7,0,0,0,0,5,12,8,16,0,0,0,0,0,0,10,14,0,0,0,0,0,10,14,3,0,0,0,0,0,10,16,16,15,3,0,0,0,0,0,3,10,8,0,0,0,4,7,10,15,5,0,0,5,13,12,10,3,0,0,3 +0,0,7,16,12,3,0,0,0,0,12,16,16,8,0,0,0,0,15,16,16,1,0,0,0,1,14,16,16,1,0,0,0,1,15,16,16,4,0,0,0,0,13,16,16,3,0,0,0,0,11,16,16,9,0,0,0,0,4,9,14,6,0,0,1 +0,2,11,13,14,11,1,0,0,1,10,5,4,16,4,0,0,0,0,1,10,13,0,0,0,0,0,7,16,1,0,0,0,0,0,5,16,13,2,0,0,0,0,0,3,12,7,0,0,5,8,4,6,15,4,0,0,3,12,14,13,9,0,0,3 +0,1,11,16,14,8,0,0,0,1,16,15,12,15,3,0,0,2,16,10,0,2,0,0,0,5,16,8,2,0,0,0,0,3,15,16,15,3,0,0,0,0,2,4,13,11,0,0,0,0,6,8,15,11,0,0,0,0,13,16,16,5,0,0,5 +0,0,5,15,3,0,0,0,0,0,12,16,14,8,0,0,0,1,16,16,12,16,3,0,0,3,14,1,0,6,7,0,0,6,8,0,0,4,8,0,0,4,12,0,0,10,12,0,0,1,14,10,13,16,5,0,0,0,6,16,14,8,0,0,0 +0,1,9,12,13,7,0,0,0,6,13,8,7,13,0,0,0,0,2,0,5,12,0,0,0,0,0,5,16,5,0,0,0,0,0,6,16,9,0,0,0,0,0,0,3,13,6,0,0,1,7,3,6,12,6,0,0,0,12,13,13,8,1,0,3 +0,0,0,5,13,0,0,0,0,0,0,14,6,0,0,0,0,0,9,13,0,2,0,0,0,1,16,4,5,14,0,0,0,9,14,0,7,10,0,0,0,10,16,12,14,14,0,0,0,2,8,12,15,1,0,0,0,0,0,4,11,0,0,0,4 +0,0,10,16,9,0,0,0,0,2,16,16,16,2,0,0,0,0,8,1,15,8,0,0,0,0,0,0,14,9,0,0,0,0,0,1,16,10,0,0,0,0,0,9,16,16,7,0,0,0,8,16,15,12,15,5,0,0,13,15,1,0,5,6,2 +0,0,0,6,12,0,0,0,0,0,2,16,10,0,0,0,0,0,7,14,1,0,0,0,0,0,11,10,0,0,0,0,0,0,14,11,1,0,0,0,0,0,15,15,15,16,10,1,0,0,8,14,3,7,16,7,0,0,0,8,15,16,12,3,6 +0,0,6,16,16,15,5,0,0,0,8,6,7,16,11,0,0,0,0,0,3,16,9,0,0,0,0,0,9,15,0,0,0,1,8,8,15,14,3,0,0,3,16,16,16,16,4,0,0,0,3,16,8,0,0,0,0,0,8,15,2,0,0,0,7 +0,0,2,13,11,0,0,0,0,0,10,14,2,0,0,0,0,0,12,5,0,0,0,0,0,0,14,4,0,0,0,0,0,0,15,8,8,8,2,0,0,0,14,16,12,12,13,0,0,0,10,10,4,6,16,0,0,0,3,11,15,12,7,0,6 +0,0,5,16,16,10,1,0,0,8,14,14,8,9,1,0,0,14,16,9,1,0,0,0,0,12,16,16,15,3,0,0,0,1,10,12,13,13,0,0,0,0,0,0,4,16,0,0,0,0,1,4,12,14,0,0,0,0,4,15,16,4,0,0,5 +0,0,6,11,7,2,0,0,0,0,15,16,15,12,0,0,0,3,16,7,0,10,3,0,0,4,8,0,0,2,8,0,0,8,5,0,0,5,8,0,0,5,7,0,0,12,5,0,0,0,14,5,13,13,1,0,0,0,7,16,11,0,0,0,0 +0,0,8,7,0,0,0,0,0,0,15,16,16,7,0,0,0,2,16,16,14,14,2,0,0,4,16,0,0,11,6,0,0,3,13,0,0,11,9,0,0,3,15,0,5,16,7,0,0,0,15,12,14,16,3,0,0,0,5,16,14,7,0,0,0 +0,0,1,13,10,0,0,0,0,0,6,16,4,0,0,0,0,0,10,12,0,0,0,0,0,0,14,10,0,0,0,0,0,0,16,14,12,12,2,0,0,4,16,14,12,13,14,0,0,0,11,14,4,7,16,1,0,0,1,12,16,16,13,0,6 +0,1,8,14,13,2,0,0,0,4,16,6,12,8,0,0,0,3,16,4,3,4,0,0,0,0,9,15,4,11,6,0,0,0,5,16,16,13,6,0,0,4,15,12,16,7,0,0,0,7,14,2,12,11,0,0,0,1,10,16,14,5,0,0,8 +0,0,6,9,15,14,2,0,0,8,13,12,6,4,1,0,0,10,13,8,3,0,0,0,0,2,11,15,16,5,0,0,0,0,0,0,7,12,0,0,0,0,0,0,1,13,0,0,0,0,0,1,9,12,0,0,0,0,8,12,10,1,0,0,5 +0,0,1,11,16,11,0,0,0,0,14,15,8,9,1,0,0,3,15,1,0,0,0,0,0,7,14,8,8,0,0,0,0,4,16,16,16,4,0,0,0,0,0,0,8,8,0,0,0,0,1,6,15,5,0,0,0,0,1,14,9,0,0,0,5 +0,0,6,15,16,13,1,0,0,0,12,12,14,16,1,0,0,0,0,0,13,14,0,0,0,0,4,4,16,13,2,0,0,8,16,16,16,16,10,0,0,5,8,15,14,4,1,0,0,0,3,16,8,0,0,0,0,0,7,16,0,0,0,0,7 +0,2,12,16,13,5,0,0,0,5,12,12,16,8,0,0,0,0,0,4,16,4,0,0,0,0,0,12,14,1,0,0,0,0,0,3,14,16,2,0,0,0,0,0,1,10,8,0,0,0,8,4,6,13,6,0,0,0,12,16,14,9,0,0,3 +0,0,3,14,16,5,0,0,0,2,14,11,14,9,0,0,0,5,16,7,14,15,0,0,0,2,16,16,16,16,6,0,0,0,1,4,2,16,7,0,0,0,0,0,0,15,6,0,0,0,0,5,7,16,2,0,0,0,4,16,16,9,0,0,9 +0,0,2,13,13,2,0,0,0,0,9,16,6,13,0,0,0,1,14,1,0,9,4,0,0,4,9,0,0,6,7,0,0,8,8,0,0,4,8,0,0,4,9,0,0,4,8,0,0,0,15,8,4,12,7,0,0,0,2,10,16,15,2,0,0 +0,0,9,15,16,13,0,0,0,0,10,5,13,14,0,0,0,0,0,0,12,12,0,0,0,0,0,2,16,6,0,0,0,1,12,16,16,13,7,0,0,5,14,16,12,8,3,0,0,0,5,16,2,0,0,0,0,0,11,12,0,0,0,0,7 +0,0,2,12,0,0,0,0,0,0,11,11,0,0,0,0,0,5,12,1,0,0,0,0,0,4,10,0,0,0,0,0,0,5,9,2,11,12,3,0,0,1,15,16,16,14,11,0,0,0,11,13,6,15,8,0,0,0,3,15,14,8,0,0,6 +0,0,0,0,12,3,0,0,0,0,0,2,15,0,0,0,0,0,0,9,9,0,0,0,0,0,3,15,0,3,10,0,0,1,14,3,0,14,6,0,0,10,16,15,13,16,1,0,0,9,10,8,14,11,0,0,0,0,0,0,12,2,0,0,4 +0,0,0,9,8,0,0,0,0,0,7,14,1,0,0,0,0,0,14,3,0,0,0,0,0,3,14,0,0,0,0,0,0,7,10,1,5,3,0,0,0,2,16,12,16,16,6,0,0,0,8,16,11,11,13,0,0,0,0,7,12,16,9,0,6 +0,0,0,10,15,3,0,0,0,2,11,15,12,12,0,0,0,5,16,4,0,10,4,0,0,7,14,0,0,6,6,0,0,7,11,0,0,9,5,0,0,0,16,1,0,11,5,0,0,0,9,13,8,15,2,0,0,0,1,11,16,8,0,0,0 +0,0,5,13,14,5,0,0,0,0,9,8,9,12,0,0,0,3,12,2,11,9,0,0,0,3,13,15,14,1,0,0,0,0,0,11,12,12,2,0,0,0,4,10,0,9,9,0,0,0,10,5,2,8,13,0,0,0,5,14,15,11,2,0,8 +0,0,0,7,16,0,0,0,0,0,0,14,12,0,0,0,0,0,5,15,4,2,3,0,0,1,15,8,1,15,8,0,0,10,15,8,10,16,3,0,2,16,16,16,16,16,1,0,0,6,6,10,16,3,0,0,0,0,0,9,11,0,0,0,4 +0,0,0,4,14,0,0,0,0,0,2,15,7,0,0,0,0,0,10,11,0,0,0,0,0,3,15,2,0,0,0,0,0,6,12,7,10,4,0,0,0,5,15,16,13,15,4,0,0,0,8,16,8,13,12,0,0,0,0,8,14,13,6,0,6 +0,0,3,14,13,1,0,0,0,0,13,16,15,9,0,0,0,6,16,3,0,13,0,0,0,6,13,0,0,7,6,0,0,8,8,0,0,4,8,0,0,0,14,0,0,2,12,0,0,0,12,11,8,15,8,0,0,0,2,14,16,13,3,0,0 +0,0,7,13,3,0,0,0,0,0,5,15,3,0,0,0,0,0,12,7,0,0,0,0,0,3,14,0,0,0,0,0,0,4,13,8,12,10,1,0,0,5,16,14,10,13,8,0,0,2,15,11,5,13,7,0,0,0,5,13,14,11,1,0,6 +0,0,0,6,12,0,0,0,0,0,0,11,11,0,0,0,0,0,1,16,3,4,3,0,0,1,11,12,0,14,7,0,0,8,16,10,9,15,2,0,0,10,16,16,16,15,0,0,0,0,0,5,16,3,0,0,0,0,0,5,14,0,0,0,4 +0,0,10,15,16,6,0,0,0,1,13,5,7,15,2,0,0,0,0,0,7,12,2,0,0,0,0,6,15,3,0,0,0,0,7,12,1,0,0,0,0,5,14,2,0,0,0,0,0,9,14,10,11,7,1,0,0,1,7,8,8,11,4,0,2 +0,0,0,1,15,10,0,0,0,0,0,6,16,7,0,0,0,0,0,11,16,0,0,0,0,0,2,16,12,15,11,0,0,1,15,14,6,16,8,0,0,8,16,15,16,16,6,0,0,5,12,12,16,13,3,0,0,0,0,2,14,3,0,0,4 +0,0,0,6,14,16,7,0,0,0,10,15,6,15,7,0,0,4,16,3,2,12,0,0,0,3,15,11,7,16,3,0,0,0,2,9,15,13,0,0,0,0,0,4,15,5,0,0,0,0,0,8,16,0,0,0,0,0,0,5,16,0,0,0,9 +0,0,6,12,16,10,0,0,0,0,12,8,11,13,0,0,0,5,13,3,15,5,0,0,0,4,15,16,11,0,0,0,0,0,8,14,15,11,0,0,0,0,11,5,2,15,7,0,0,0,12,5,4,13,8,0,0,0,6,15,16,14,3,0,8 +0,0,0,0,2,14,4,0,0,0,0,1,11,16,5,0,0,0,6,14,16,16,1,0,0,5,16,12,16,13,0,0,0,3,10,0,14,13,0,0,0,0,0,0,15,14,0,0,0,0,0,0,8,16,1,0,0,0,0,0,4,15,0,0,1 +0,0,2,12,13,4,0,0,0,0,12,11,8,13,0,0,0,0,13,0,13,5,0,0,0,11,14,7,11,0,0,0,0,0,7,16,5,0,0,0,0,0,7,12,12,4,0,0,0,0,9,6,10,10,0,0,0,0,2,14,15,6,0,0,8 +0,3,15,13,2,0,0,0,0,11,14,12,11,0,0,0,0,8,2,4,15,0,0,0,0,0,0,4,15,0,0,0,0,0,0,13,7,0,0,0,0,0,6,15,1,0,0,0,0,2,14,14,12,15,8,0,0,2,13,13,12,12,5,0,2 +0,0,4,6,14,10,2,0,0,0,15,16,12,12,12,0,0,4,14,1,0,0,0,0,0,5,16,16,12,1,0,0,0,0,4,8,15,10,0,0,0,0,0,0,5,9,0,0,0,0,0,5,14,3,0,0,0,0,5,14,5,0,0,0,5 +0,0,1,13,13,2,0,0,0,0,5,15,13,13,0,0,0,1,13,5,0,14,1,0,0,3,16,7,0,8,8,0,0,4,9,0,0,8,8,0,0,2,13,1,0,7,11,0,0,0,14,14,9,16,7,0,0,0,3,12,16,9,0,0,0 +0,3,15,16,13,0,0,0,0,10,14,15,16,4,0,0,0,3,3,1,16,8,0,0,0,0,0,6,16,4,0,0,0,0,1,14,12,0,0,0,0,0,10,16,3,0,0,0,0,3,16,15,12,11,1,0,0,2,16,16,16,16,8,0,2 +0,0,5,15,9,0,0,0,0,2,13,8,15,5,0,0,0,2,2,0,8,5,0,0,0,0,5,9,15,2,0,0,0,0,11,15,15,11,0,0,0,0,0,0,1,14,1,0,0,0,4,9,4,11,3,0,0,0,5,12,13,9,0,0,3 +0,0,3,9,14,8,0,0,0,0,13,16,14,16,0,0,0,0,10,0,12,10,0,0,0,0,0,7,16,8,0,0,0,0,0,3,13,15,1,0,0,0,0,1,1,13,4,0,0,0,3,13,11,14,1,0,0,0,3,16,10,1,0,0,3 +0,0,0,0,12,16,2,0,0,0,3,11,16,16,5,0,0,3,15,16,16,14,0,0,0,4,12,12,16,14,0,0,0,0,0,5,16,11,0,0,0,0,0,7,16,11,0,0,0,0,0,4,16,13,0,0,0,0,0,1,13,13,0,0,1 +0,2,12,11,1,0,0,0,0,6,14,16,8,0,0,0,0,5,5,10,11,0,0,0,0,0,0,11,6,0,0,0,0,0,3,16,1,0,0,0,0,1,15,7,2,1,0,0,0,5,16,16,16,16,7,0,0,3,12,12,8,6,3,0,2 +0,0,0,1,8,15,10,0,0,0,3,15,15,16,12,0,0,0,16,9,6,16,2,0,0,4,16,9,13,16,1,0,0,0,10,12,15,16,0,0,0,0,0,0,10,12,0,0,0,0,0,0,10,11,0,0,0,0,0,0,6,6,0,0,9 +0,0,2,14,15,3,0,0,0,0,11,14,14,13,0,0,0,0,16,4,1,13,4,0,0,3,16,4,0,7,8,0,0,5,13,0,0,4,8,0,0,3,13,1,0,8,9,0,0,0,12,11,10,15,6,0,0,0,4,15,15,8,0,0,0 +0,0,0,1,9,13,5,0,0,0,3,13,10,12,9,0,0,0,11,8,0,13,3,0,0,1,15,14,11,16,2,0,0,0,1,8,10,14,0,0,0,0,0,0,7,13,0,0,0,0,0,0,10,9,0,0,0,0,0,0,13,5,0,0,9 +0,1,14,16,8,0,0,0,0,7,12,9,16,0,0,0,0,4,2,0,16,0,0,0,0,0,1,10,9,0,0,0,0,0,7,13,1,0,0,0,0,0,16,1,0,0,0,0,0,3,13,4,7,8,5,0,0,1,13,16,15,12,10,0,2 +0,0,2,10,16,14,3,0,0,3,13,16,11,8,2,0,0,6,16,6,0,0,0,0,0,2,14,16,15,7,0,0,0,0,0,4,10,16,5,0,0,0,0,0,2,15,4,0,0,0,5,7,15,12,0,0,0,0,3,15,10,1,0,0,5 +0,1,13,11,0,0,0,0,0,5,14,16,6,0,0,0,0,7,3,12,6,0,0,0,0,0,0,14,0,0,0,0,0,0,6,12,0,0,0,0,0,2,15,4,0,0,0,0,0,4,16,9,8,10,5,0,0,1,13,16,16,15,7,0,2 +0,0,0,0,6,14,2,0,0,0,0,0,10,16,4,0,0,0,0,6,16,16,2,0,0,0,8,15,16,15,0,0,0,4,16,11,12,16,0,0,0,1,4,1,8,15,0,0,0,0,0,0,8,12,0,0,0,0,0,0,6,16,2,0,1 +0,0,0,13,3,0,0,0,0,0,7,15,3,0,0,0,0,0,15,8,0,0,0,0,0,2,16,1,0,0,0,0,0,3,14,1,4,4,0,0,0,2,16,15,16,16,9,0,0,0,11,16,12,16,15,2,0,0,1,10,15,13,10,0,6 +0,0,3,11,14,16,4,0,0,9,16,16,16,9,3,0,0,15,14,6,2,0,0,0,0,10,16,5,0,0,0,0,0,3,13,15,5,0,0,0,0,0,1,10,15,6,0,0,0,0,0,7,15,15,0,0,0,0,2,15,16,15,0,0,5 +0,0,6,12,16,14,3,0,0,6,14,6,3,15,8,0,0,7,14,9,16,16,0,0,0,0,5,11,10,14,0,0,0,0,0,0,10,8,0,0,0,0,0,3,16,3,0,0,0,0,1,15,9,0,0,0,0,0,10,11,0,0,0,0,9 +0,0,1,15,11,1,0,0,0,0,5,16,16,7,0,0,0,0,0,14,16,14,0,0,0,0,0,14,16,16,2,0,0,0,2,16,16,16,2,0,0,0,0,14,16,15,1,0,0,0,1,16,16,16,3,0,0,0,2,12,16,15,5,0,1 +0,0,10,7,12,3,0,0,0,0,16,16,16,11,0,0,0,0,15,5,0,12,7,0,0,8,8,0,0,11,8,0,0,8,8,0,0,8,8,0,0,9,9,0,2,15,5,0,0,5,16,12,15,11,0,0,0,0,13,16,10,1,0,0,0 +0,0,0,2,11,13,2,0,0,0,2,13,16,16,2,0,0,3,15,14,13,16,0,0,0,0,6,0,15,9,0,0,0,0,0,0,16,6,0,0,0,0,0,4,16,0,0,0,0,0,0,5,16,3,0,0,0,0,0,3,16,6,0,0,1 +0,0,4,11,14,7,0,0,0,0,15,12,16,3,0,0,0,0,8,16,10,0,0,0,0,0,9,16,7,0,0,0,0,3,15,4,14,4,0,0,0,2,13,0,3,15,6,0,0,0,8,9,4,8,14,0,0,0,0,7,12,12,8,0,8 +0,0,3,12,16,14,3,0,0,0,7,10,11,16,10,0,0,0,0,0,0,16,12,0,0,0,0,2,7,16,11,0,0,3,10,16,16,16,6,0,0,10,12,10,16,10,0,0,0,0,1,15,15,0,0,0,0,0,5,15,5,0,0,0,7 +0,0,7,16,15,6,0,0,0,0,2,9,15,14,0,0,0,0,0,0,13,12,0,0,0,0,0,3,16,14,8,0,0,0,9,16,16,16,9,0,0,0,5,14,14,2,0,0,0,0,2,15,6,0,0,0,0,0,9,15,0,0,0,0,7 +0,0,6,13,16,16,7,0,0,9,16,15,10,8,1,0,0,13,15,3,0,0,0,0,0,4,16,13,1,0,0,0,0,0,7,16,12,0,0,0,0,0,0,9,16,7,0,0,0,0,4,16,16,12,0,0,0,0,5,16,15,5,0,0,5 +0,4,15,15,8,1,0,0,0,4,16,11,16,15,0,0,0,1,5,0,11,16,4,0,0,0,0,6,16,13,1,0,0,0,0,12,16,1,0,0,0,0,0,6,16,5,0,0,0,0,0,4,16,8,0,0,0,4,16,16,16,6,0,0,3 +0,0,7,12,12,0,0,0,0,9,14,10,14,7,0,0,0,1,0,0,12,12,0,0,0,0,0,5,16,5,0,0,0,0,2,15,10,0,0,0,0,0,10,14,1,0,0,0,0,0,12,16,8,11,10,0,0,0,8,14,13,7,0,0,2 +0,0,1,12,11,0,0,0,0,0,9,15,3,0,0,0,0,2,16,9,0,0,4,0,0,7,16,10,4,11,15,0,0,4,16,16,16,16,9,0,0,0,5,9,15,13,0,0,0,0,0,8,16,3,0,0,0,0,0,11,13,0,0,0,4 +0,0,7,12,15,5,0,0,0,0,10,8,13,12,0,0,0,0,0,0,12,10,0,0,0,0,0,5,16,14,7,0,0,3,16,16,15,9,2,0,0,0,4,13,10,0,0,0,0,0,5,14,1,0,0,0,0,0,13,6,0,0,0,0,7 +0,0,8,13,12,2,0,0,0,0,10,12,13,10,0,0,0,0,1,0,8,12,0,0,0,0,0,0,9,12,0,0,0,0,0,3,16,5,0,0,0,0,1,13,12,2,3,0,0,0,12,16,16,16,9,0,0,0,10,12,9,2,0,0,2 +0,0,6,13,15,5,0,0,0,7,16,11,11,16,0,0,0,10,12,0,3,16,2,0,0,4,3,0,5,13,0,0,0,0,0,0,14,6,0,0,0,0,0,6,14,1,0,0,0,0,2,16,14,16,13,0,0,0,8,16,12,8,0,0,2 +0,0,0,5,14,16,16,5,0,0,10,16,15,16,16,7,0,4,15,6,1,16,12,0,0,0,2,0,5,16,7,0,0,0,0,0,4,16,2,0,0,0,0,0,6,16,1,0,0,0,0,2,15,14,0,0,0,0,0,3,15,7,0,0,3 +0,0,6,14,16,13,0,0,0,6,14,3,0,13,4,0,0,5,12,0,4,16,3,0,0,0,14,14,15,5,0,0,0,1,14,12,1,0,0,0,0,5,16,13,2,0,0,0,0,2,16,16,13,0,0,0,0,0,2,12,12,0,0,0,8 +0,0,0,7,16,5,0,0,0,0,3,15,16,11,0,0,0,2,15,16,16,1,0,0,0,13,16,16,12,0,0,0,0,1,11,16,11,0,0,0,0,0,6,16,11,0,0,0,0,0,0,14,15,3,0,0,0,0,0,4,15,12,0,0,1 +0,0,0,6,6,0,0,0,0,0,0,13,9,0,0,0,0,0,5,16,4,0,0,0,0,0,7,15,0,0,0,0,0,0,8,12,12,8,2,0,0,0,12,16,12,12,14,1,0,0,6,16,6,8,16,6,0,0,0,4,14,16,15,1,6 +0,2,8,9,6,0,0,0,0,0,6,16,16,14,1,0,0,0,4,16,16,16,4,0,0,0,4,16,16,16,4,0,0,0,4,16,16,16,3,0,0,0,7,16,16,12,0,0,0,1,14,16,16,11,0,0,0,2,10,9,5,1,0,0,1 +0,0,2,13,13,0,0,0,0,1,14,13,2,0,0,0,0,6,16,3,0,0,0,0,0,7,16,6,0,10,7,0,0,1,14,16,14,16,10,0,0,0,5,15,16,11,0,0,0,0,0,11,16,0,0,0,0,0,3,16,8,0,0,0,4 +0,0,9,15,16,12,1,0,0,0,9,10,14,16,9,0,0,0,0,0,2,16,10,0,0,0,0,0,8,15,2,0,0,0,0,10,15,4,0,0,0,0,6,16,6,0,0,0,0,0,15,16,12,12,0,0,0,0,14,16,13,5,0,0,2 +0,0,3,10,13,16,12,0,0,3,15,13,8,10,12,0,0,1,12,13,16,16,10,0,0,0,0,0,4,16,5,0,0,0,0,0,10,16,0,0,0,0,0,1,15,12,0,0,0,0,0,9,15,0,0,0,0,0,0,14,9,0,0,0,9 +0,0,1,11,12,0,0,0,0,0,8,16,3,3,1,0,0,1,16,11,0,15,8,0,0,11,15,5,10,16,1,0,0,6,16,16,16,16,8,0,0,0,3,12,15,12,4,0,0,0,0,13,11,0,0,0,0,0,0,14,5,0,0,0,4 +0,1,10,16,16,12,2,0,0,8,11,8,5,12,8,0,0,1,0,0,5,15,7,0,0,0,10,15,16,9,0,0,0,0,5,9,16,9,0,0,0,0,0,0,3,16,0,0,0,1,8,2,5,16,0,0,0,2,14,16,12,5,0,0,3 +0,0,0,0,3,16,6,0,0,0,0,5,15,16,9,0,0,1,10,16,15,16,8,0,0,10,16,12,2,16,5,0,0,1,6,0,4,16,4,0,0,0,0,0,5,16,1,0,0,0,0,0,6,16,3,0,0,0,0,0,3,16,9,0,1 +0,0,1,15,8,1,0,0,0,0,5,16,16,1,0,0,0,0,2,16,16,3,0,0,0,0,5,16,16,3,0,0,0,0,4,16,16,4,0,0,0,0,3,16,16,5,0,0,0,0,2,15,16,9,0,0,0,0,2,11,16,13,0,0,1 +0,1,8,16,16,6,0,0,0,12,16,15,12,6,0,0,1,16,13,2,0,0,0,0,0,7,16,2,0,0,0,0,0,0,7,15,7,0,0,0,0,0,0,4,14,11,0,0,0,0,1,8,14,16,2,0,0,0,5,16,16,9,0,0,5 +0,0,6,12,13,16,14,1,0,0,6,8,6,10,15,1,0,0,0,0,0,10,12,0,0,0,0,0,5,16,2,0,0,0,1,14,16,14,3,0,0,0,2,13,13,1,0,0,0,0,2,16,6,0,0,0,0,0,9,12,0,0,0,0,7 +0,0,5,15,16,16,9,0,0,0,4,6,5,8,16,2,0,0,0,0,0,9,12,0,0,0,0,6,12,16,11,0,0,0,0,12,16,9,0,0,0,0,0,5,13,1,0,0,0,0,3,14,5,0,0,0,0,0,6,9,0,0,0,0,7 +0,0,0,11,10,1,0,0,0,0,10,16,9,3,0,0,0,0,15,14,0,0,0,0,0,2,16,7,0,0,0,0,0,0,16,5,4,0,0,0,0,0,15,16,16,13,1,0,0,0,6,16,16,16,11,0,0,0,0,6,16,16,9,0,6 +0,1,11,16,11,11,0,0,0,10,14,4,8,14,0,0,0,5,16,1,13,11,0,0,0,0,8,16,15,1,0,0,0,0,7,16,10,0,0,0,0,0,14,11,16,0,0,0,0,2,16,11,12,0,0,0,0,0,12,15,4,0,0,0,8 +0,1,10,15,15,6,0,0,0,8,16,15,14,15,3,0,0,3,6,3,15,15,0,0,0,0,0,12,16,5,0,0,0,0,0,8,16,2,0,0,0,0,0,4,16,8,0,0,0,0,4,15,16,4,0,0,0,0,14,16,7,0,0,0,3 +0,0,7,15,15,4,0,0,0,9,16,10,16,10,0,0,0,0,3,0,14,12,0,0,0,0,0,2,16,5,0,0,0,0,1,14,11,0,0,0,0,0,12,16,0,0,0,0,0,0,12,16,5,4,3,0,0,0,5,14,16,15,8,0,2 +0,0,5,11,16,13,6,0,0,0,5,8,8,14,15,0,0,0,0,0,0,12,15,0,0,0,2,8,12,16,11,0,0,0,8,16,16,16,4,0,0,0,0,8,16,6,0,0,0,0,6,13,15,0,0,0,0,0,6,14,5,0,0,0,7 +0,0,6,14,11,3,0,0,0,1,15,10,16,14,0,0,0,5,14,0,0,11,6,0,0,7,11,0,0,8,8,0,0,8,8,0,0,12,8,0,0,7,9,0,4,16,2,0,0,0,16,12,15,12,0,0,0,0,6,16,11,1,0,0,0 +0,0,0,4,15,0,0,0,0,0,1,14,8,0,0,0,0,0,7,14,0,0,0,0,0,4,16,6,0,11,4,0,0,9,16,0,8,15,2,0,0,13,16,16,16,11,0,0,0,4,8,6,16,8,0,0,0,0,0,2,14,0,0,0,4 +0,0,5,12,13,1,0,0,0,0,7,16,16,10,0,0,0,0,1,13,16,13,0,0,0,0,0,0,10,15,0,0,0,0,0,0,9,15,0,0,0,0,2,5,15,12,0,0,0,0,11,16,16,7,0,0,0,0,4,13,9,0,0,0,9 +0,0,0,6,11,14,3,0,0,0,9,13,8,15,12,0,0,0,0,0,0,10,11,0,0,0,0,0,0,13,9,0,0,0,4,11,13,16,4,0,0,0,3,8,14,12,0,0,0,0,0,3,16,3,0,0,0,0,0,9,14,0,0,0,7 +0,0,6,15,15,4,0,0,0,4,15,16,16,14,0,0,0,12,16,13,16,16,1,0,0,9,16,15,14,16,2,0,0,0,4,2,7,16,1,0,0,0,0,2,15,14,0,0,0,0,1,14,16,6,0,0,0,0,5,15,6,0,0,0,9 +0,0,7,16,10,0,0,0,0,1,16,14,16,9,0,0,0,0,11,16,16,15,0,0,0,0,0,7,13,14,0,0,0,0,0,0,11,14,0,0,0,0,0,0,13,12,0,0,0,0,1,11,15,3,0,0,0,0,6,16,6,0,0,0,9 +0,0,0,10,10,0,0,0,0,1,8,2,12,4,0,0,0,7,14,1,2,12,0,0,0,7,10,0,0,12,3,0,0,3,12,0,0,11,8,0,0,1,15,1,1,15,8,0,0,0,6,14,13,16,4,0,0,0,0,9,14,7,0,0,0 +0,0,3,15,9,1,0,0,0,0,11,14,16,14,1,0,0,4,14,1,3,11,8,0,0,8,9,0,0,8,8,0,0,8,8,0,1,15,4,0,0,7,9,0,7,13,0,0,0,3,14,10,15,3,0,0,0,0,5,13,4,0,0,0,0 +0,2,7,14,16,12,0,0,0,9,16,16,15,13,2,0,0,3,16,7,2,0,0,0,0,0,13,9,0,0,0,0,0,0,7,15,1,0,0,0,0,0,2,15,5,0,0,0,0,1,16,16,7,0,0,0,0,0,9,16,4,0,0,0,5 +0,0,0,11,8,0,0,0,0,0,4,16,15,5,0,0,0,0,6,11,4,15,0,0,0,0,6,4,0,14,4,0,0,0,16,2,0,14,6,0,0,0,14,3,4,16,5,0,0,0,9,14,15,16,1,0,0,0,1,11,14,4,0,0,0 +0,0,9,15,16,9,0,0,0,11,16,9,11,16,2,0,0,5,3,1,14,14,1,0,0,0,0,7,16,5,0,0,0,0,0,4,16,5,0,0,0,0,0,0,10,16,0,0,0,0,1,6,15,13,0,0,0,0,14,15,9,1,0,0,3 +0,0,2,11,11,2,0,0,0,0,11,16,16,5,0,0,0,0,10,16,16,5,0,0,0,0,2,11,14,9,0,0,0,0,0,0,8,12,0,0,0,0,0,0,8,16,0,0,0,0,0,8,15,10,0,0,0,0,1,14,14,3,0,0,9 +0,0,3,12,16,16,16,12,0,0,9,12,8,8,16,11,0,0,1,0,1,8,16,6,0,0,1,12,16,16,14,1,0,0,0,12,15,14,3,0,0,0,0,4,16,6,0,0,0,0,0,15,13,0,0,0,0,0,5,16,3,0,0,0,7 +0,0,8,16,2,3,0,0,0,0,15,11,5,16,1,0,0,0,16,4,9,16,1,0,0,0,8,13,15,12,0,0,0,0,8,16,13,0,0,0,0,0,13,16,7,0,0,0,0,1,16,16,7,0,0,0,0,0,8,13,1,0,0,0,8 +0,0,1,13,15,5,0,0,0,0,10,15,8,6,0,0,0,2,16,6,0,0,0,0,0,4,15,1,4,0,0,0,0,5,13,3,16,11,1,0,0,0,15,1,5,15,5,0,0,0,11,12,8,16,6,0,0,0,0,10,16,10,0,0,6 +0,0,7,9,15,12,1,0,0,8,15,10,9,16,4,0,0,6,3,1,12,14,0,0,0,0,0,6,16,5,0,0,0,0,0,5,15,8,0,0,0,0,0,0,8,16,3,0,0,0,1,7,15,9,0,0,0,0,7,16,8,0,0,0,3 +0,0,3,9,16,13,0,0,0,4,15,12,12,16,0,0,0,8,4,4,14,9,0,0,0,0,0,8,16,7,0,0,0,0,0,1,16,15,0,0,0,0,0,0,0,16,4,0,0,0,0,7,14,15,2,0,0,0,1,15,12,2,0,0,3 +0,0,0,9,15,2,0,0,0,0,8,16,9,1,0,0,0,1,15,12,0,0,0,0,0,3,16,4,0,0,0,0,0,1,16,2,5,0,0,0,0,0,14,16,16,11,1,0,0,0,5,16,16,16,7,0,0,0,0,8,12,13,1,0,6 +0,0,1,8,12,1,0,0,0,0,7,16,11,0,0,0,0,0,13,14,0,0,0,0,0,2,16,10,0,0,0,0,0,1,16,8,5,3,0,0,0,0,14,16,16,16,10,0,0,0,7,16,9,15,16,2,0,0,0,6,12,14,7,0,6 +0,0,10,16,16,16,3,0,0,0,14,16,16,13,5,0,0,0,8,16,4,0,0,0,0,0,1,15,9,0,0,0,0,0,0,11,12,0,0,0,0,0,3,9,16,0,0,0,0,4,16,15,16,0,0,0,0,0,10,16,10,0,0,0,5 +0,0,5,14,16,8,0,0,0,3,16,14,15,15,0,0,0,0,8,3,14,11,0,0,0,0,0,9,15,3,0,0,0,0,3,15,8,0,0,0,0,0,10,14,1,0,0,0,0,0,16,13,4,9,8,0,0,0,5,14,15,12,3,0,2 +0,0,0,12,9,1,0,0,0,0,7,14,12,10,0,0,0,0,11,3,3,16,1,0,0,6,10,0,1,16,2,0,0,5,13,0,2,16,3,0,0,2,14,2,7,16,1,0,0,0,10,14,16,11,0,0,0,0,1,12,12,1,0,0,0 +0,1,8,15,16,8,0,0,0,14,16,16,11,4,0,0,0,4,15,12,0,0,0,0,0,0,6,16,3,0,0,0,0,0,0,10,12,0,0,0,0,0,0,6,16,1,0,0,0,0,12,15,16,1,0,0,0,0,9,16,13,1,0,0,5 +0,0,3,15,13,2,0,0,0,1,13,16,16,0,0,0,0,9,16,16,16,1,0,0,0,0,3,15,16,1,0,0,0,0,0,14,16,6,0,0,0,0,0,9,16,11,0,0,0,0,5,12,16,9,0,0,0,0,4,15,10,1,0,0,1 +0,0,0,1,15,3,0,0,0,0,0,14,15,0,0,0,0,0,10,13,1,3,4,0,0,3,13,3,0,11,9,0,0,9,12,0,7,15,3,0,0,8,16,16,16,10,0,0,0,1,7,6,16,5,0,0,0,0,0,1,12,2,0,0,4 +0,0,2,13,13,3,0,0,0,0,11,12,12,12,0,0,0,2,15,0,1,15,4,0,0,7,9,0,0,13,5,0,0,6,11,0,0,11,8,0,0,5,12,0,0,16,4,0,0,0,15,9,8,15,1,0,0,0,5,15,13,2,0,0,0 +0,0,4,13,16,7,0,0,0,0,11,9,15,11,0,0,0,0,0,0,12,9,0,0,0,0,0,4,16,3,0,0,0,0,12,16,16,16,5,0,0,0,6,14,16,15,2,0,0,0,1,16,3,0,0,0,0,0,6,14,2,0,0,0,7 +0,0,0,4,13,7,0,0,0,0,0,5,15,16,2,0,0,0,1,14,16,16,0,0,0,2,14,16,16,16,0,0,0,5,12,10,16,16,0,0,0,0,0,4,16,16,0,0,0,0,0,5,16,16,4,0,0,0,0,4,14,15,4,0,1 +0,0,5,13,16,6,0,0,0,0,12,11,5,16,1,0,0,0,10,8,5,16,6,0,0,0,2,16,16,14,0,0,0,0,7,16,16,5,0,0,0,4,16,7,8,13,0,0,0,2,16,3,2,16,6,0,0,0,6,15,16,15,4,0,8 +0,0,0,6,16,4,0,0,0,0,0,16,16,5,0,0,0,0,5,16,16,4,0,0,0,5,15,16,16,4,0,0,0,8,16,16,16,7,0,0,0,0,0,7,16,16,0,0,0,0,0,6,16,16,5,0,0,0,0,3,16,16,3,0,1 +0,0,6,13,14,3,0,0,0,7,15,8,9,12,0,0,0,6,15,5,4,16,1,0,0,0,8,16,16,16,6,0,0,0,0,11,16,12,0,0,0,0,5,15,9,15,0,0,0,0,10,13,1,16,4,0,0,0,5,12,16,15,2,0,8 +0,3,10,16,14,14,3,0,0,8,16,9,12,12,3,0,0,7,15,8,7,1,0,0,0,5,16,16,16,11,0,0,0,0,1,0,4,15,3,0,0,0,2,0,4,16,4,0,0,2,15,10,14,16,2,0,0,1,11,12,12,4,0,0,5 +0,0,2,14,16,7,0,0,0,0,8,16,8,16,2,0,0,0,4,16,13,16,6,0,0,0,0,15,16,9,0,0,0,0,5,16,16,6,0,0,0,1,15,8,9,12,0,0,0,1,16,11,9,16,2,0,0,0,3,12,13,11,0,0,8 +0,0,5,13,0,0,0,0,0,0,12,11,0,0,0,0,0,3,16,2,0,0,0,0,0,5,15,0,0,0,0,0,0,7,14,0,4,1,0,0,0,5,16,16,16,13,0,0,0,3,16,16,10,16,5,0,0,0,6,13,16,12,0,0,6 +0,0,15,8,0,0,0,0,0,6,15,16,3,0,0,0,0,6,9,13,5,0,0,0,0,0,1,8,8,0,0,0,0,0,0,13,7,0,0,0,0,0,5,15,4,0,2,0,0,4,16,16,16,16,10,0,0,1,8,8,11,12,13,0,2 +0,0,2,14,16,14,2,0,0,0,0,11,13,16,3,0,0,0,0,0,10,14,0,0,0,0,2,8,14,12,2,0,0,0,10,16,16,16,9,0,0,0,6,16,14,12,2,0,0,0,3,15,11,0,0,0,0,0,6,15,3,0,0,0,7 +0,0,2,12,13,3,0,0,0,0,14,12,12,14,2,0,0,4,16,2,8,16,7,0,0,3,15,14,16,7,0,0,0,0,3,15,16,9,0,0,0,0,10,13,9,16,0,0,0,0,11,11,7,16,2,0,0,0,2,13,12,8,1,0,8 +0,0,0,9,7,0,0,0,0,0,2,16,2,5,1,0,0,0,9,8,3,15,1,0,0,3,14,1,8,12,0,0,0,6,16,12,16,16,5,0,0,8,15,13,16,9,0,0,0,0,0,5,14,0,0,0,0,0,0,10,7,0,0,0,4 +0,0,0,11,4,0,0,0,0,0,0,15,1,0,0,0,0,0,5,9,3,7,0,0,0,0,13,5,7,10,0,0,0,7,15,2,11,15,1,0,0,9,16,16,16,14,2,0,0,0,4,8,16,1,0,0,0,0,0,8,12,0,0,0,4 +0,0,1,13,13,0,0,0,0,0,11,14,15,11,0,0,0,4,16,4,2,15,5,0,0,8,9,0,0,12,7,0,0,5,10,0,0,12,7,0,0,5,14,1,2,15,1,0,0,1,14,15,12,12,0,0,0,0,2,13,12,3,0,0,0 +0,0,0,10,14,6,0,0,0,0,0,8,16,10,1,0,0,0,0,11,16,6,0,0,0,0,6,16,16,4,0,0,0,4,12,14,16,4,0,0,0,0,0,8,16,4,0,0,0,0,0,11,16,5,0,0,0,0,0,6,16,16,7,0,1 +0,0,0,1,13,8,0,0,0,0,0,6,16,9,0,0,0,0,2,15,16,8,0,0,0,3,16,16,16,10,0,0,0,0,3,3,16,12,0,0,0,0,0,0,16,12,0,0,0,0,0,2,16,16,4,0,0,0,0,1,16,14,3,0,1 +0,0,4,11,14,12,2,0,0,0,11,12,4,8,0,0,0,1,16,6,0,0,0,0,0,3,16,8,7,2,0,0,0,5,16,16,16,15,2,0,0,0,3,0,1,15,6,0,0,0,3,8,9,15,9,0,0,0,7,16,12,10,2,0,5 +0,0,4,12,2,0,0,0,0,0,14,8,0,0,0,0,0,5,16,2,0,0,0,0,0,8,12,0,0,0,0,0,0,8,13,9,14,10,0,0,0,7,16,13,9,16,5,0,0,0,14,11,5,16,7,0,0,0,4,14,14,8,0,0,6 +0,2,14,14,3,0,0,0,0,8,15,15,11,0,0,0,0,9,14,10,14,2,0,0,0,1,7,8,16,0,0,0,0,0,0,11,14,0,0,0,0,0,0,16,10,0,0,0,0,2,13,16,13,10,7,0,0,4,16,16,16,16,15,0,2 +0,0,7,15,12,4,0,0,0,5,15,4,9,14,0,0,0,10,8,0,4,16,2,0,0,6,11,2,12,11,0,0,0,0,13,16,16,6,0,0,0,0,5,16,16,8,0,0,0,0,10,14,5,16,7,0,0,0,9,15,16,16,8,0,8 +0,1,12,15,3,0,0,0,0,8,15,14,13,0,0,0,0,3,5,9,14,0,0,0,0,0,2,16,14,2,0,0,0,0,3,16,16,15,2,0,0,0,1,0,2,14,5,0,0,0,14,11,9,16,6,0,0,3,16,16,14,7,1,0,3 +0,0,10,15,6,0,0,0,0,7,15,11,14,0,0,0,0,3,9,2,16,0,0,0,0,0,0,6,16,0,0,0,0,0,0,14,16,14,1,0,0,0,0,1,5,16,5,0,0,3,11,2,7,16,5,0,0,1,13,16,13,7,0,0,3 +0,0,2,13,15,5,0,0,0,0,8,13,12,14,1,0,0,1,14,5,0,11,6,0,0,6,16,1,0,9,7,0,0,6,16,2,0,13,4,0,0,3,16,8,4,14,0,0,0,0,8,16,16,8,0,0,0,0,0,11,10,0,0,0,0 +0,0,10,15,9,0,0,0,0,6,15,11,16,0,0,0,0,2,4,4,16,0,0,0,0,0,0,11,16,7,0,0,0,0,0,7,12,16,4,0,0,0,0,0,0,16,7,0,0,3,16,12,10,16,3,0,0,1,10,14,12,6,0,0,3 +0,0,5,14,4,0,0,0,0,0,12,12,0,0,0,0,0,0,16,9,0,0,0,0,0,8,16,0,0,0,0,0,0,8,16,13,16,9,1,0,0,11,16,14,10,16,2,0,0,7,16,14,12,15,1,0,0,1,9,13,10,3,0,0,6 +0,0,0,14,6,0,0,0,0,0,6,16,5,5,0,0,0,0,14,10,10,10,0,0,0,4,16,3,13,10,0,0,0,11,16,9,16,15,7,0,0,16,16,16,16,15,4,0,0,4,4,13,16,2,0,0,0,0,1,15,11,0,0,0,4 +0,0,0,3,10,0,0,0,0,0,0,10,8,0,0,0,0,0,5,14,0,7,0,0,0,1,14,5,1,15,0,0,0,8,10,0,6,10,0,0,0,14,14,12,14,15,1,0,0,6,12,9,16,6,0,0,0,0,0,4,14,0,0,0,4 +0,0,12,15,9,0,0,0,0,8,13,10,16,2,0,0,0,4,4,0,15,6,0,0,0,0,0,7,16,9,0,0,0,0,0,5,10,16,4,0,0,0,0,0,0,8,9,0,0,0,8,5,4,9,12,0,0,1,14,14,16,14,3,0,3 +0,0,9,6,0,0,0,0,0,0,16,6,0,0,0,0,0,1,16,0,0,0,0,0,0,4,16,0,0,0,0,0,0,4,15,12,13,9,1,0,0,6,16,11,8,14,5,0,0,3,16,12,10,15,6,0,0,0,8,15,14,7,0,0,6 +0,0,0,9,8,0,0,0,0,0,1,15,5,0,0,0,0,0,9,11,1,2,0,0,0,1,16,3,9,11,0,0,0,10,14,4,14,13,3,0,0,15,16,16,16,16,5,0,0,2,4,10,13,2,0,0,0,0,0,11,12,0,0,0,4 +0,0,4,14,16,15,1,0,0,0,6,9,14,16,0,0,0,0,0,0,14,12,2,0,0,0,6,13,16,16,12,0,0,0,6,15,15,11,5,0,0,0,1,15,9,0,0,0,0,0,2,16,7,0,0,0,0,0,7,14,2,0,0,0,7 +0,0,13,15,0,0,0,0,0,4,16,4,0,0,0,0,0,7,16,0,0,0,0,0,0,10,14,0,3,1,0,0,0,9,12,10,16,14,3,0,0,8,16,13,5,7,15,0,0,2,16,9,0,7,16,0,0,0,13,16,16,16,7,0,6 +0,0,9,15,5,0,0,0,0,7,16,13,14,0,0,0,0,14,10,4,16,0,0,0,0,7,6,2,16,1,0,0,0,0,0,6,13,0,0,0,0,0,0,9,11,0,0,0,0,0,5,16,15,9,4,0,0,0,10,14,11,15,16,2,2 +0,0,11,16,8,0,0,0,0,5,16,12,15,0,0,0,0,10,12,9,11,0,0,0,0,7,15,15,6,0,0,0,0,0,14,16,11,1,0,0,0,1,14,11,12,15,3,0,0,7,16,2,0,11,12,0,0,1,10,15,16,16,6,0,8 +0,3,12,12,3,0,0,0,0,8,10,11,9,0,0,0,0,0,2,14,4,0,0,0,0,0,12,13,1,0,0,0,0,0,3,13,15,3,0,0,0,0,0,0,7,14,1,0,0,2,4,4,3,11,7,0,0,5,12,12,14,16,8,0,3 +0,0,5,16,10,0,0,0,0,0,8,16,12,0,0,0,0,0,8,16,9,0,0,0,0,5,15,16,8,0,0,0,0,3,12,16,9,0,0,0,0,0,0,13,16,2,0,0,0,0,0,9,16,11,2,0,0,0,4,16,16,16,11,0,1 +0,0,2,15,11,0,0,0,0,0,8,16,11,0,0,0,0,1,13,16,7,0,0,0,0,8,16,16,7,0,0,0,0,11,9,16,12,0,0,0,0,0,0,7,16,6,0,0,0,0,0,5,16,15,3,0,0,0,3,16,16,16,15,1,1 +0,0,0,5,13,0,0,0,0,0,1,14,5,0,10,3,0,0,8,15,0,8,16,1,0,2,16,6,0,13,11,0,1,13,16,10,12,16,4,0,6,15,14,12,15,12,0,0,0,0,0,1,15,5,0,0,0,0,0,8,12,0,0,0,4 +0,2,15,15,2,0,0,0,0,10,11,16,6,0,0,0,0,9,5,12,9,0,0,0,0,0,0,13,8,0,0,0,0,0,1,16,3,0,0,0,0,0,9,13,0,0,0,0,0,1,14,16,15,10,5,0,0,5,15,8,12,15,12,0,2 +0,0,0,2,15,15,2,0,0,0,1,13,16,14,0,0,0,0,8,16,16,10,0,0,0,7,16,16,16,6,0,0,0,1,0,14,15,1,0,0,0,0,0,14,15,0,0,0,0,0,0,10,16,4,0,0,0,0,0,6,16,9,0,0,1 +0,0,0,8,15,16,7,0,0,1,14,9,1,0,8,0,0,6,12,0,0,10,8,0,0,4,14,12,13,14,8,0,0,0,0,0,1,4,8,0,0,0,0,0,0,4,8,0,0,0,0,0,0,7,8,0,0,0,0,8,12,15,6,0,9 +0,0,2,13,16,14,2,0,0,0,12,13,8,15,8,0,0,8,14,0,0,11,12,0,0,7,16,13,11,16,13,0,0,0,6,8,8,14,10,0,0,0,0,0,0,15,8,0,0,0,3,12,5,15,5,0,0,0,1,12,16,14,2,0,9 +0,0,7,10,13,10,0,0,0,0,14,11,4,11,3,0,0,2,15,1,0,5,8,0,0,7,9,0,0,3,8,0,0,5,12,0,0,4,8,0,0,5,11,0,0,8,4,0,0,0,12,2,2,12,0,0,0,0,6,15,14,2,0,0,0 +0,7,16,15,2,0,0,0,0,13,10,12,11,0,0,0,0,13,4,8,12,0,0,0,0,0,1,7,13,0,0,0,0,0,1,14,8,0,0,0,0,0,5,16,1,0,0,0,0,1,13,15,12,11,7,0,0,5,16,16,15,14,16,1,2 +0,0,0,5,14,16,16,1,0,0,8,14,8,13,16,1,0,0,1,0,0,11,12,0,0,0,0,2,4,14,8,0,0,2,11,16,16,16,8,0,0,7,10,5,11,10,0,0,0,0,0,1,15,3,0,0,0,0,0,8,12,0,0,0,7 +0,0,13,16,16,16,6,0,0,6,16,8,5,5,5,0,0,11,16,0,0,0,0,0,0,5,16,14,9,1,0,0,0,0,1,6,14,10,0,0,0,0,0,0,5,16,0,0,0,0,4,2,13,12,0,0,0,1,13,16,14,1,0,0,5 +0,0,0,2,16,1,0,0,0,0,1,13,7,0,4,5,0,0,8,12,0,1,15,5,0,4,16,2,0,11,10,0,0,12,15,12,12,16,1,0,0,9,12,12,14,13,0,0,0,0,0,1,14,5,0,0,0,0,0,4,12,0,0,0,4 +0,0,9,15,16,3,0,0,0,5,15,10,16,4,0,0,0,0,2,7,13,0,0,0,0,0,4,16,5,0,0,0,0,0,2,15,16,11,1,0,0,0,0,0,6,15,6,0,0,0,5,5,4,13,8,0,0,0,14,16,16,15,3,0,3 +0,0,4,16,16,6,0,0,0,3,11,11,5,15,2,0,0,8,16,8,0,13,7,0,0,11,16,3,0,10,11,0,0,9,16,0,0,4,12,0,0,6,16,2,0,7,11,0,0,1,16,10,5,15,5,0,0,0,4,15,16,10,0,0,0 +0,0,2,11,15,5,0,0,0,0,12,8,9,9,0,0,0,0,12,5,9,7,0,0,0,0,9,15,10,0,0,0,0,0,6,15,11,1,0,0,0,8,13,2,11,11,0,0,0,7,13,7,0,13,3,0,0,0,2,11,15,13,2,0,8 +0,0,6,9,12,16,10,0,0,0,11,13,5,4,4,0,0,0,12,4,0,0,0,0,0,3,15,3,4,2,0,0,0,2,16,14,12,14,7,0,0,0,1,0,0,10,4,0,0,12,13,0,1,13,2,0,0,0,6,16,16,7,0,0,5 +0,0,10,16,15,3,0,0,0,6,16,6,9,14,1,0,0,8,16,3,0,13,6,0,0,11,14,0,0,7,10,0,0,8,12,0,0,4,12,0,0,8,13,0,0,9,9,0,0,4,16,8,9,16,3,0,0,0,10,16,15,6,0,0,0 +0,0,1,12,15,6,0,0,0,0,5,13,1,12,0,0,0,0,2,11,1,15,0,0,0,0,0,10,11,9,0,0,0,0,0,6,16,6,0,0,0,0,10,15,4,13,3,0,0,0,12,2,0,1,14,0,0,0,1,10,10,8,15,1,8 +0,0,0,5,16,1,0,0,0,0,2,15,10,1,9,2,0,0,12,14,1,8,15,1,0,4,16,6,1,15,8,0,0,13,16,16,16,16,9,0,0,8,10,8,16,13,0,0,0,0,0,1,16,9,0,0,0,0,0,5,16,2,0,0,4 +0,3,15,16,12,1,0,0,0,12,14,12,16,7,0,0,0,12,6,0,14,10,0,0,0,1,1,1,16,8,0,0,0,0,0,10,15,1,0,0,0,0,5,16,7,0,0,0,0,3,14,16,9,6,4,0,0,3,15,16,16,16,12,0,2 +0,0,12,14,12,12,3,0,0,0,15,12,10,9,1,0,0,2,16,6,7,2,0,0,0,4,16,16,16,15,1,0,0,0,4,4,4,16,8,0,0,0,1,0,0,10,7,0,0,4,12,0,3,14,4,0,0,1,13,16,16,6,0,0,5 +0,3,16,9,0,0,0,0,0,7,15,16,2,0,0,0,0,8,9,16,4,0,0,0,0,2,4,13,7,0,0,0,0,0,0,14,7,0,0,0,0,0,1,15,7,0,0,0,0,1,14,16,16,13,9,1,0,4,16,11,8,8,11,2,2 +0,2,12,16,16,8,0,0,0,8,10,9,16,6,0,0,0,0,1,13,13,0,0,0,0,0,8,16,5,0,0,0,0,0,2,14,15,3,0,0,0,0,0,0,9,15,3,0,0,0,0,3,4,14,11,0,0,3,15,16,16,16,9,0,3 +0,0,0,0,14,9,0,0,0,0,0,8,15,3,7,1,0,0,6,15,4,5,16,6,0,5,16,5,0,13,14,0,0,11,16,12,12,16,8,0,0,6,9,8,12,15,2,0,0,0,0,0,10,14,0,0,0,0,0,0,15,7,0,0,4 +0,0,0,10,16,15,3,0,0,0,6,13,10,16,2,0,0,0,3,1,2,14,0,0,0,0,0,0,8,8,0,0,0,0,1,8,14,15,8,0,0,1,15,16,13,3,0,0,0,0,2,9,7,0,0,0,0,0,0,14,1,0,0,0,7 +0,2,15,9,10,8,6,0,0,10,16,16,16,12,8,0,0,8,16,6,0,0,0,0,0,1,12,16,12,1,0,0,0,0,1,6,15,12,0,0,0,0,0,0,5,16,2,0,0,10,12,6,10,16,1,0,0,1,13,16,14,5,0,0,5 +0,0,0,10,6,0,0,0,0,0,5,16,9,0,0,0,0,0,10,16,10,0,0,0,0,4,15,12,12,0,0,0,0,2,4,3,16,2,0,0,0,0,0,0,11,9,0,0,0,0,0,7,12,15,4,0,0,0,0,12,16,16,8,0,1 +0,0,12,16,15,1,0,0,0,2,16,6,16,2,0,0,0,1,16,13,13,0,0,0,0,0,10,16,5,0,0,0,0,2,15,16,12,1,0,0,0,10,12,2,11,12,0,0,0,8,13,4,0,13,9,0,0,0,7,15,16,16,6,0,8 +0,0,14,8,0,0,0,0,0,3,16,15,4,0,0,0,0,1,10,14,9,0,0,0,0,0,0,9,13,0,0,0,0,0,0,8,13,0,0,0,0,0,1,12,15,3,0,0,0,0,11,16,16,16,12,2,0,0,13,14,2,1,3,1,2 +0,0,0,1,13,10,0,0,0,0,0,3,16,15,0,0,0,0,3,9,16,15,0,0,0,7,16,15,16,12,0,0,0,0,0,1,16,15,0,0,0,0,0,4,16,16,0,0,0,0,0,4,16,16,3,0,0,0,0,0,12,16,4,0,1 +0,0,3,15,15,12,12,4,0,0,9,10,2,9,15,2,0,0,7,2,2,14,2,0,0,0,3,4,11,10,0,0,0,0,13,16,16,16,5,0,0,0,0,10,9,0,0,0,0,0,2,16,3,0,0,0,0,0,7,13,0,0,0,0,7 +0,0,12,15,6,0,0,0,0,8,14,7,16,3,0,0,0,8,6,0,8,8,0,0,0,5,8,0,8,10,0,0,0,0,1,0,12,9,0,0,0,0,0,7,15,3,0,0,0,0,7,16,12,5,7,0,0,0,11,16,16,16,15,0,2 +0,0,7,12,12,6,0,0,0,0,16,11,15,15,2,0,0,4,12,0,1,10,6,0,0,8,10,0,0,10,6,0,0,4,12,0,0,8,8,0,0,3,13,0,0,9,7,0,0,2,15,4,5,15,2,0,0,0,9,16,16,9,0,0,0 +0,0,6,15,10,0,0,0,0,0,16,9,16,7,0,0,0,4,10,0,6,15,0,0,0,8,8,0,0,11,3,0,0,8,8,0,0,8,5,0,0,7,10,0,0,5,6,0,0,1,15,4,1,12,4,0,0,0,8,16,16,11,0,0,0 +0,0,0,0,12,15,1,0,0,3,8,8,14,16,0,0,0,11,16,16,16,16,0,0,0,1,4,4,15,16,0,0,0,0,0,0,12,16,4,0,0,0,0,0,12,16,3,0,0,0,0,0,12,16,10,0,0,0,0,0,9,16,11,0,1 +0,0,13,13,4,0,0,0,0,4,14,7,14,0,0,0,0,7,8,0,10,4,0,0,0,1,4,0,13,3,0,0,0,0,0,1,15,0,0,0,0,0,0,11,9,0,0,0,0,0,11,16,12,12,4,0,0,0,14,14,12,12,6,0,2 +0,0,0,4,14,0,0,0,0,0,0,12,12,0,0,0,0,0,1,16,9,0,0,0,0,0,10,16,7,13,0,0,0,3,16,9,9,16,0,0,0,14,16,13,15,16,5,0,0,5,13,14,16,15,3,0,0,0,0,3,16,10,0,0,4 +0,0,7,14,10,2,0,0,0,1,16,12,15,8,0,0,0,7,13,0,5,13,2,0,0,8,12,0,0,10,6,0,0,8,12,0,0,8,8,0,0,6,13,0,0,10,8,0,0,0,14,10,6,14,6,0,0,0,5,16,16,9,0,0,0 +0,0,1,13,14,11,0,0,0,0,10,12,12,16,4,0,0,0,15,0,2,9,6,0,0,5,13,0,0,6,9,0,0,5,12,0,0,5,7,0,0,1,13,2,0,9,4,0,0,0,12,11,7,11,0,0,0,0,2,15,12,3,0,0,0 +0,1,8,16,16,11,0,0,0,6,15,10,14,16,4,0,0,6,13,2,7,15,2,0,0,0,11,16,16,7,0,0,0,0,11,15,15,8,0,0,0,1,16,3,6,16,4,0,0,0,14,9,4,13,8,0,0,0,5,15,16,15,4,0,8 +0,0,7,16,15,1,0,0,0,1,16,5,12,16,6,0,0,2,15,1,4,16,6,0,0,1,13,16,16,16,5,0,0,0,0,1,0,11,8,0,0,0,0,0,0,11,7,0,0,7,14,9,10,16,3,0,0,1,9,14,15,9,0,0,9 +0,0,0,6,16,0,0,0,0,0,0,14,11,0,0,0,0,0,4,16,3,0,0,0,0,0,12,11,3,0,0,0,0,4,16,5,16,7,0,0,1,15,16,13,16,14,4,0,0,9,15,15,16,15,1,0,0,0,0,6,15,0,0,0,4 +0,0,0,10,16,0,0,0,0,0,6,14,2,0,0,0,0,0,12,7,0,0,0,0,0,4,16,8,7,1,0,0,0,3,16,15,13,13,3,0,0,0,16,2,0,8,11,0,0,0,9,9,1,4,14,0,0,0,1,10,16,16,8,0,6 +0,0,6,15,9,0,0,0,0,1,15,14,16,7,0,0,0,4,13,0,7,16,1,0,0,6,12,0,0,13,8,0,0,7,12,0,0,12,8,0,0,6,13,0,0,12,8,0,0,2,15,10,8,15,4,0,0,0,5,12,13,6,0,0,0 +0,0,0,4,16,2,0,0,0,0,0,9,13,0,0,0,0,0,2,16,5,0,0,0,0,0,9,13,4,0,0,0,0,3,16,6,15,5,0,0,0,10,15,9,16,10,1,0,1,16,16,16,16,16,5,0,0,3,4,9,16,4,0,0,4 +0,0,4,10,12,12,11,1,0,0,8,10,11,16,10,0,0,0,0,2,7,15,1,0,0,0,5,16,16,15,5,0,0,0,3,11,13,8,4,0,0,0,1,14,3,0,0,0,0,0,6,14,0,0,0,0,0,0,9,7,0,0,0,0,7 +0,0,6,13,13,6,0,0,0,1,14,5,7,13,0,0,0,0,0,0,2,15,0,0,0,0,0,10,14,9,0,0,0,0,0,8,9,16,3,0,0,1,5,0,0,16,4,0,0,1,16,4,5,15,3,0,0,0,6,16,14,5,0,0,3 +0,0,3,13,10,8,9,2,0,0,10,11,8,11,15,0,0,0,13,3,1,12,6,0,0,0,5,3,7,14,0,0,0,0,9,16,16,16,5,0,0,0,0,7,11,1,0,0,0,0,0,14,6,0,0,0,0,0,4,15,2,0,0,0,7 +0,0,0,1,12,8,0,0,0,0,0,4,16,4,0,0,0,0,0,9,16,1,0,0,0,0,5,16,8,1,0,0,0,0,10,16,11,15,0,0,0,5,16,11,15,15,3,0,0,12,16,16,16,16,5,0,0,0,0,0,15,14,0,0,4 +0,0,0,7,12,0,0,0,0,0,0,11,11,0,0,0,0,0,2,16,3,0,0,0,0,0,9,12,9,3,0,0,0,1,15,7,16,7,0,0,0,9,16,14,16,16,8,0,0,8,12,14,16,12,5,0,0,0,0,7,15,0,0,0,4 +0,0,5,14,16,16,14,0,0,0,14,14,12,15,13,0,0,0,9,2,5,16,5,0,0,0,1,9,15,16,7,0,0,0,3,16,16,16,9,0,0,0,0,10,14,4,0,0,0,0,0,15,7,0,0,0,0,0,5,15,2,0,0,0,7 +0,0,3,13,13,14,6,0,0,2,15,8,16,15,11,0,0,3,14,3,5,15,4,0,0,1,13,16,16,3,0,0,0,0,13,13,14,9,0,0,0,4,12,0,6,11,0,0,0,3,14,5,7,15,0,0,0,0,7,15,15,9,0,0,8 +0,0,9,13,3,0,0,0,0,1,15,14,12,0,0,0,0,0,16,0,16,2,0,0,0,0,3,2,16,2,0,0,0,0,0,2,16,3,0,0,0,0,2,11,15,2,0,0,0,0,13,16,14,8,7,0,0,0,8,13,12,12,13,0,2 +0,0,1,7,16,12,0,0,0,0,3,15,16,12,0,0,0,5,13,16,16,9,0,0,0,5,14,16,16,8,0,0,0,0,4,16,16,7,0,0,0,0,0,11,16,12,0,0,0,0,0,8,16,16,5,0,0,0,0,6,14,13,5,0,1 +0,0,0,0,7,15,2,0,0,0,0,0,8,16,5,0,0,0,0,1,13,14,0,0,0,8,16,16,16,12,0,0,0,0,3,0,16,12,0,0,0,0,0,0,12,15,0,0,0,0,0,0,9,16,2,0,0,0,0,0,6,16,8,0,1 +0,4,13,16,16,12,0,0,0,5,14,4,4,3,0,0,0,2,16,8,6,1,0,0,0,2,16,13,14,11,0,0,0,0,5,0,0,15,5,0,0,0,0,0,0,11,9,0,0,0,1,0,0,11,12,0,0,2,15,16,16,14,3,0,5 +0,0,9,12,9,0,0,0,0,3,15,10,16,1,0,0,0,0,1,1,16,4,0,0,0,0,3,13,16,2,0,0,0,0,7,12,13,14,1,0,0,3,1,0,4,16,5,0,0,12,12,4,11,15,2,0,0,4,13,15,11,3,0,0,3 +0,0,8,14,11,0,0,0,0,0,14,4,11,8,0,0,0,0,0,0,9,8,0,0,0,0,2,10,16,3,0,0,0,0,5,9,9,15,2,0,0,0,0,0,0,10,7,0,0,7,12,1,1,14,7,0,0,0,8,16,16,10,1,0,3 +0,0,4,15,15,8,1,0,0,2,12,3,10,14,11,0,0,3,14,5,3,11,11,0,0,0,7,15,16,14,2,0,0,0,5,16,11,15,2,0,0,0,13,1,2,16,1,0,0,0,10,11,8,15,0,0,0,0,3,13,14,3,0,0,8 +0,1,10,15,14,2,0,0,0,7,10,3,11,10,0,0,0,1,1,0,8,14,0,0,0,0,3,9,15,9,0,0,0,0,4,8,9,15,2,0,0,0,0,0,0,14,6,0,0,0,6,1,0,13,8,0,0,0,9,16,16,12,3,0,3 +0,0,1,9,12,8,0,0,0,1,12,9,13,13,0,0,0,6,11,0,2,15,0,0,0,1,14,14,14,8,0,0,0,0,0,11,16,13,1,0,0,0,4,13,2,14,6,0,0,0,8,9,2,14,7,0,0,0,4,16,16,8,1,0,8 +0,0,10,14,12,0,0,0,0,2,16,8,3,0,0,0,0,5,16,12,11,1,0,0,0,2,12,9,10,13,1,0,0,0,0,0,0,12,6,0,0,0,0,0,0,4,12,0,0,2,14,4,4,13,9,0,0,0,10,14,15,11,1,0,5 +0,0,9,15,12,6,0,0,0,3,16,15,13,8,0,0,0,2,16,4,12,5,0,0,0,0,13,16,10,0,0,0,0,0,8,14,11,1,0,0,0,1,12,0,14,6,0,0,0,0,13,5,2,16,2,0,0,0,7,14,16,16,2,0,8 +0,0,0,12,16,10,0,0,0,0,10,15,15,13,0,0,0,0,9,6,16,7,0,0,0,0,0,8,15,3,0,0,0,0,5,16,6,0,0,0,0,8,15,16,3,0,0,0,5,16,16,16,16,6,0,0,1,4,4,9,16,11,0,0,2 +0,0,8,14,14,2,0,0,0,5,15,5,9,15,6,0,0,6,15,6,4,16,6,0,0,0,5,13,16,16,4,0,0,0,0,0,0,14,4,0,0,0,0,0,0,11,4,0,0,0,8,6,1,10,8,0,0,0,10,16,16,16,4,0,9 +0,0,5,15,7,0,0,0,0,1,16,16,10,7,0,0,0,8,13,0,3,16,3,0,0,9,10,0,0,9,7,0,0,8,12,0,0,8,8,0,0,4,14,0,0,5,12,0,0,2,15,5,6,14,6,0,0,0,6,13,15,10,2,0,0 +0,1,10,16,10,1,0,0,0,8,14,5,10,13,2,0,0,1,4,0,11,15,2,0,0,0,0,0,16,7,0,0,0,0,0,0,14,11,0,0,0,0,0,0,0,14,11,0,0,0,10,5,4,10,13,0,0,0,10,14,16,14,9,0,3 +0,2,6,11,8,0,0,0,0,9,16,14,5,0,0,0,0,9,16,12,8,2,0,0,0,8,15,8,11,13,1,0,0,0,0,0,0,8,10,0,0,0,0,0,0,5,14,0,0,0,6,6,4,8,14,0,0,0,5,15,16,15,7,0,5 +0,0,3,12,15,6,0,0,0,1,14,16,14,16,5,0,0,6,16,9,0,13,8,0,0,8,16,0,0,9,8,0,0,7,16,0,0,9,8,0,0,5,16,0,3,15,2,0,0,2,14,15,15,9,0,0,0,0,4,14,9,1,0,0,0 +0,0,0,7,15,0,0,0,0,0,0,9,15,0,0,0,0,0,2,15,7,0,0,0,0,0,9,16,11,5,0,0,0,9,16,16,16,15,7,0,0,5,12,14,16,16,8,0,0,0,0,8,16,0,0,0,0,0,0,8,15,1,0,0,4 +0,0,9,12,2,0,0,0,0,3,16,16,16,16,3,0,0,4,15,13,14,16,6,0,0,2,15,11,16,11,1,0,0,1,14,16,11,0,0,0,0,5,14,6,16,5,0,0,0,7,15,8,13,16,0,0,0,1,7,11,16,13,0,0,8 +0,0,9,14,9,0,0,0,0,6,15,5,16,6,0,0,0,5,14,1,6,13,0,0,0,0,10,14,13,16,0,0,0,0,0,5,5,12,2,0,0,0,0,0,0,9,5,0,0,0,16,9,7,10,8,0,0,0,6,10,14,16,5,0,9 +0,2,15,16,9,0,0,0,0,7,15,12,16,2,0,0,0,9,9,2,16,2,0,0,0,1,0,7,16,0,0,0,0,0,0,14,10,0,0,0,0,1,12,15,2,0,0,0,0,8,16,14,12,12,3,0,0,3,14,16,16,16,7,0,2 +0,0,12,10,0,0,0,0,0,3,13,16,4,0,0,0,0,6,16,16,7,0,0,0,0,0,8,15,9,0,0,0,0,0,0,10,14,0,0,0,0,0,0,6,16,6,0,0,0,0,9,13,16,13,8,3,0,0,9,16,16,16,16,9,1 +0,0,0,10,16,3,0,0,0,0,2,16,7,0,0,0,0,0,9,15,0,0,0,0,0,0,15,12,4,2,0,0,0,1,16,10,0,11,2,0,0,0,16,6,0,5,13,0,0,0,7,14,5,8,16,1,0,0,0,3,12,12,9,0,6 +0,0,2,8,13,13,2,0,0,6,14,14,11,15,3,0,0,10,14,4,10,6,0,0,0,1,14,12,13,0,0,0,0,0,9,15,11,0,0,0,0,0,13,2,11,8,0,0,0,0,8,10,4,14,3,0,0,0,1,8,14,14,1,0,8 +0,0,6,15,15,4,0,0,0,0,11,10,4,2,0,0,0,0,13,11,2,0,0,0,0,0,12,14,12,5,0,0,0,0,0,0,3,15,3,0,0,0,0,0,0,4,11,0,0,0,15,8,3,2,15,0,0,0,5,12,16,16,12,0,5 +0,0,0,11,11,0,0,0,0,0,0,14,11,0,0,0,0,1,12,15,2,0,0,0,0,8,16,8,11,6,2,0,0,5,15,16,16,16,11,0,0,0,1,9,16,7,1,0,0,0,0,8,16,3,0,0,0,0,0,9,16,1,0,0,4 +0,0,5,15,14,12,9,0,0,0,2,4,4,9,15,0,0,0,0,0,1,13,6,0,0,0,3,11,12,16,1,0,0,0,5,13,16,16,4,0,0,0,0,10,12,2,0,0,0,0,4,15,4,0,0,0,0,0,8,9,0,0,0,0,7 +0,0,12,7,0,0,0,0,0,0,9,15,0,0,0,0,0,0,11,16,5,0,0,0,0,0,10,16,7,0,0,0,0,0,0,11,14,0,0,0,0,0,0,6,16,2,0,0,0,0,14,16,16,13,12,3,0,0,8,15,15,14,16,11,1 +0,1,13,10,0,0,0,0,0,7,16,16,7,0,0,0,0,3,13,8,16,0,0,0,0,0,1,5,16,2,0,0,0,0,0,5,16,3,0,0,0,0,4,13,15,1,0,0,0,4,16,16,16,16,8,0,0,2,15,16,14,16,16,5,2 +0,0,8,12,13,3,0,0,0,0,11,15,7,1,0,0,0,0,15,16,14,3,0,0,0,0,4,5,6,13,2,0,0,0,0,0,0,9,7,0,0,0,6,0,0,2,12,0,0,3,15,8,4,10,11,0,0,0,8,12,13,12,3,0,5 +0,0,8,15,7,0,0,0,0,2,16,6,9,3,0,0,0,4,14,1,12,10,0,0,0,0,8,16,16,15,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,8,7,0,0,1,8,4,1,4,13,0,0,0,5,13,16,16,13,0,9 +0,0,8,12,12,0,0,0,0,0,8,11,2,0,0,0,0,0,15,13,11,3,0,0,0,1,13,8,10,15,1,0,0,0,0,0,0,9,7,0,0,1,1,0,0,8,8,0,0,6,13,4,4,11,8,0,0,1,7,14,16,13,2,0,5 +0,0,7,15,3,0,0,0,0,0,1,16,9,0,0,0,0,0,7,15,15,0,0,0,0,0,10,16,13,0,0,0,0,0,0,6,16,2,0,0,0,0,0,4,16,6,0,0,0,0,9,13,16,13,8,3,0,0,6,15,16,16,16,12,1 +0,0,1,12,14,3,0,0,0,0,10,15,7,0,0,0,0,0,13,8,0,0,0,0,0,0,15,16,11,5,0,0,0,0,14,13,9,14,7,0,0,0,14,6,0,3,14,0,0,0,10,11,4,7,16,1,0,0,1,11,15,14,7,1,6 +0,0,5,15,13,8,3,0,0,0,14,14,10,16,11,0,0,0,11,13,5,12,12,0,0,0,6,16,16,8,1,0,0,1,13,13,14,0,0,0,0,6,10,1,15,7,0,0,0,5,15,7,11,13,0,0,0,0,6,12,16,10,0,0,8 +0,0,5,15,13,7,0,0,0,4,15,8,7,16,4,0,0,6,12,1,10,14,1,0,0,0,0,12,12,2,0,0,0,0,0,14,11,3,0,0,0,0,0,1,10,14,0,0,0,0,0,0,4,14,8,0,0,0,4,16,16,14,4,0,3 +0,0,9,13,7,0,0,0,0,7,14,4,14,4,0,0,0,2,2,3,15,2,0,0,0,0,0,16,11,0,0,0,0,0,0,8,15,11,0,0,0,0,0,0,0,12,7,0,0,0,10,4,2,10,8,0,0,0,7,13,16,16,4,0,3 +0,0,7,14,12,4,0,0,0,0,10,12,12,12,2,0,0,0,9,5,3,14,3,0,0,0,4,13,15,9,0,0,0,0,8,16,8,0,0,0,0,0,15,4,14,1,0,0,0,2,13,5,9,7,0,0,0,0,4,12,16,10,0,0,8 +0,0,0,0,15,7,0,0,0,0,0,2,16,8,0,0,0,0,0,10,16,3,0,0,0,7,16,16,4,12,12,0,0,1,13,16,16,16,9,0,0,0,1,7,14,16,0,0,0,0,0,0,13,15,0,0,0,0,0,0,16,11,0,0,4 +0,0,11,16,10,1,0,0,0,0,16,8,11,13,1,0,0,0,13,7,7,16,1,0,0,0,4,13,14,16,1,0,0,0,0,0,0,15,4,0,0,0,0,0,0,8,11,0,0,1,7,4,5,14,12,0,0,0,8,15,14,11,3,0,9 +0,0,6,14,15,1,0,0,0,0,11,12,4,0,0,0,0,0,15,10,4,1,0,0,0,0,13,13,14,12,1,0,0,0,0,0,0,10,10,0,0,0,0,0,0,4,14,0,0,0,0,2,1,9,14,0,0,0,6,16,16,16,6,0,5 +0,0,0,8,10,0,0,0,0,0,6,16,12,0,0,0,0,0,12,12,1,0,0,0,0,0,15,5,0,0,0,0,0,1,16,3,13,14,2,0,0,0,14,16,15,14,13,0,0,0,9,16,12,10,15,1,0,0,0,6,12,15,8,0,6 +0,0,6,16,2,0,0,0,0,3,16,13,11,0,0,0,0,12,14,2,14,0,0,0,0,2,2,0,16,0,0,0,0,0,0,2,16,1,0,0,0,0,0,10,14,4,3,0,0,0,5,16,16,16,16,0,0,0,6,15,10,8,11,2,2 +0,0,5,13,12,1,0,0,0,2,15,14,15,9,0,0,0,7,16,4,13,16,1,0,0,6,16,14,16,16,6,0,0,0,12,14,9,16,7,0,0,0,0,0,1,16,7,0,0,0,3,4,8,15,5,0,0,0,8,15,15,9,0,0,9 +0,1,14,15,2,0,0,0,0,7,16,16,7,0,0,0,0,14,9,12,11,0,0,0,0,7,4,13,10,0,0,0,0,0,4,16,4,0,0,0,0,0,10,15,2,0,0,0,0,3,16,14,12,12,10,0,0,0,12,16,16,16,16,3,2 +0,0,2,12,16,4,0,0,0,1,13,15,11,12,0,0,0,8,16,8,4,16,0,0,0,2,5,0,6,13,0,0,0,0,0,0,11,8,0,0,0,0,0,7,16,8,1,0,0,0,1,16,16,16,12,0,0,0,2,8,5,6,13,0,2 +0,0,7,15,14,2,0,0,0,0,9,16,16,11,0,0,0,0,3,16,16,11,0,0,0,0,5,16,16,13,0,0,0,0,6,16,16,14,0,0,0,0,6,16,16,12,0,0,0,0,7,16,16,15,6,0,0,0,4,16,16,15,7,0,1 +0,0,4,13,4,0,0,0,0,2,13,16,15,1,0,0,0,9,16,5,10,9,0,0,0,11,16,1,1,14,1,0,0,6,16,3,0,6,9,0,0,2,16,2,0,5,11,0,0,0,12,12,8,15,9,0,0,0,2,12,13,9,1,0,0 +0,0,6,13,15,6,0,0,0,1,15,16,16,14,0,0,0,4,16,8,4,14,4,0,0,4,16,2,0,9,7,0,0,4,15,0,0,13,5,0,0,2,16,0,5,15,5,0,0,1,15,13,16,11,0,0,0,0,6,15,12,3,0,0,0 +0,0,5,15,16,7,0,0,0,0,15,15,12,15,0,0,0,0,8,1,5,16,2,0,0,0,0,0,8,16,0,0,0,0,0,2,15,16,8,0,0,0,0,0,3,12,13,0,0,0,14,12,13,16,8,0,0,0,5,12,13,10,1,0,3 +0,0,3,10,12,16,5,0,0,0,8,8,8,15,11,0,0,0,0,0,2,16,7,0,0,0,1,6,14,16,6,0,0,0,4,14,16,12,4,0,0,0,0,9,13,2,0,0,0,0,2,16,6,0,0,0,0,0,5,16,2,0,0,0,7 +0,0,0,8,4,0,0,0,0,0,0,12,4,1,0,0,0,0,4,13,2,16,1,0,0,0,14,7,7,14,4,0,0,5,16,16,16,16,6,0,0,3,10,7,15,6,0,0,0,0,0,5,10,0,0,0,0,0,0,7,10,0,0,0,4 +0,0,6,16,6,0,0,0,0,2,16,14,15,0,0,0,0,9,13,0,14,3,0,0,0,4,6,1,16,5,0,0,0,0,0,8,16,3,0,0,0,0,3,16,14,0,2,0,0,0,13,16,16,16,15,0,0,0,7,14,12,12,12,1,2 +0,0,0,7,13,5,0,0,0,0,2,16,15,3,0,0,0,0,10,15,4,0,0,0,0,1,16,8,0,0,0,0,0,5,16,14,16,14,2,0,0,3,16,14,12,14,10,0,0,0,11,13,5,12,14,0,0,0,1,8,14,15,5,0,6 +0,0,5,14,16,9,0,0,0,6,16,9,13,12,0,0,0,3,7,1,14,12,0,0,0,0,1,15,16,15,2,0,0,0,1,11,9,14,11,0,0,0,1,3,0,13,9,0,0,0,10,13,9,16,6,0,0,0,3,16,16,10,0,0,3 +0,0,2,12,9,1,0,0,0,0,7,16,16,7,0,0,0,0,12,16,16,2,0,0,0,1,16,16,16,1,0,0,0,0,14,16,15,1,0,0,0,0,8,16,16,3,0,0,0,0,8,16,16,7,0,0,0,0,3,12,16,16,5,0,1 +0,0,0,10,13,0,0,0,0,0,5,16,8,3,0,0,0,0,11,13,7,16,0,0,0,7,16,10,14,15,8,0,0,11,16,16,16,16,9,0,0,1,4,10,16,3,0,0,0,0,0,12,15,0,0,0,0,0,0,13,8,0,0,0,4 +0,0,9,12,15,10,0,0,0,6,16,14,12,10,0,0,0,5,12,1,6,0,0,0,0,7,15,15,16,4,0,0,0,6,13,11,13,9,0,0,0,0,0,0,12,9,0,0,0,0,6,8,16,10,0,0,0,0,13,15,11,2,0,0,5 +0,0,0,5,12,0,0,0,0,0,5,16,14,2,0,0,0,0,15,15,1,0,0,0,0,2,16,9,0,0,0,0,0,0,16,10,13,15,4,0,0,0,13,16,16,16,15,1,0,0,5,16,16,13,16,4,0,0,0,5,14,16,12,0,6 +0,0,2,11,15,2,0,0,0,1,15,14,11,10,0,0,0,7,16,2,1,16,0,0,0,6,16,12,14,16,1,0,0,0,10,16,16,16,4,0,0,0,4,16,12,9,10,0,0,0,7,16,5,15,8,0,0,0,2,12,12,13,2,0,8 +0,0,2,13,9,0,0,0,0,0,8,16,16,0,0,0,0,14,16,16,8,0,0,0,0,6,12,16,6,0,0,0,0,0,8,16,6,0,0,0,0,0,6,16,7,0,0,0,0,0,4,16,11,0,0,0,0,0,1,14,14,0,0,0,1 +0,1,10,14,5,0,0,0,0,9,16,14,16,0,0,0,0,3,5,9,16,0,0,0,0,0,5,16,16,14,0,0,0,0,8,14,13,16,6,0,0,0,0,0,0,16,11,0,0,0,11,5,11,16,7,0,0,1,12,16,14,5,0,0,3 +0,0,0,13,13,1,0,0,0,0,11,14,15,8,0,0,0,6,16,3,10,7,0,0,0,8,16,4,12,8,0,0,0,1,3,4,15,1,0,0,0,0,2,13,12,0,0,0,0,0,11,16,16,12,7,0,0,0,3,9,8,11,13,0,2 +0,0,2,10,7,0,0,0,0,3,16,11,8,6,0,0,0,4,13,0,0,8,0,0,0,4,16,9,11,11,0,0,0,0,10,16,16,15,2,0,0,0,4,16,4,3,8,0,0,0,8,9,0,2,9,0,0,0,3,11,8,8,4,0,8 +0,0,9,16,16,13,12,1,0,0,6,6,8,14,15,0,0,0,0,0,5,16,4,0,0,0,2,8,15,16,3,0,0,0,2,13,15,8,3,0,0,0,0,15,7,0,0,0,0,0,6,16,1,0,0,0,0,0,11,8,0,0,0,0,7 +0,0,8,14,15,8,0,0,0,5,15,11,8,10,0,0,0,7,12,0,0,0,0,0,0,9,16,16,4,0,0,0,0,3,8,9,13,0,0,0,0,0,0,1,13,0,0,0,0,0,6,9,14,0,0,0,0,0,7,13,8,0,0,0,5 +0,0,0,7,3,0,0,0,0,0,0,15,5,0,0,0,0,0,6,14,2,8,0,0,0,0,12,8,5,14,0,0,0,6,16,12,14,15,4,0,0,2,10,15,16,14,5,0,0,0,0,12,11,0,0,0,0,0,0,11,9,0,0,0,4 +0,0,5,15,10,2,0,0,0,0,9,16,16,3,0,0,0,0,13,16,16,3,0,0,0,0,13,16,16,1,0,0,0,0,12,16,16,2,0,0,0,0,10,16,16,2,0,0,0,0,5,16,16,11,0,0,0,0,4,15,16,15,3,0,1 +0,0,0,3,14,0,0,0,0,0,1,14,12,8,2,0,0,0,9,15,6,16,4,0,0,3,16,14,12,16,5,0,0,3,13,16,16,16,8,0,0,0,0,0,16,11,1,0,0,0,0,2,16,4,0,0,0,0,0,3,12,4,0,0,4 +0,0,7,11,15,10,0,0,0,4,16,15,13,12,4,0,0,0,7,1,5,16,4,0,0,0,0,6,15,14,0,0,0,0,0,15,16,16,4,0,0,1,2,3,5,16,8,0,0,3,14,5,7,16,6,0,0,0,8,15,16,12,1,0,3 +0,0,0,2,16,2,0,0,0,0,0,9,13,0,2,0,0,0,4,16,5,11,11,0,0,0,12,13,3,14,10,0,0,5,16,14,13,16,5,0,0,15,16,16,16,16,8,0,0,3,4,2,12,13,1,0,0,0,0,1,16,7,0,0,4 +0,0,4,11,12,15,13,0,0,0,10,11,11,14,14,0,0,0,1,4,5,16,8,0,0,0,13,16,16,16,6,0,0,0,0,7,16,7,1,0,0,0,0,9,12,0,0,0,0,0,2,15,7,0,0,0,0,0,3,14,2,0,0,0,7 +0,0,5,11,12,0,0,0,0,0,16,16,16,13,7,0,0,0,12,16,16,15,4,0,0,0,4,15,14,8,0,0,0,0,12,4,1,4,0,0,0,1,14,0,3,7,0,0,0,1,16,12,16,4,0,0,0,0,3,9,5,0,0,0,8 +0,1,11,16,16,12,0,0,0,7,15,10,6,3,0,0,0,10,10,3,0,0,0,0,0,11,16,16,7,0,0,0,0,10,11,6,15,1,0,0,0,0,0,0,12,6,0,0,0,0,0,3,15,3,0,0,0,0,14,16,10,0,0,0,5 +0,0,3,16,16,9,0,0,0,0,13,14,9,16,3,0,0,0,15,6,5,16,3,0,0,0,8,16,16,16,3,0,0,0,0,6,7,16,2,0,0,0,1,0,3,16,3,0,0,1,14,3,6,15,1,0,0,0,3,15,16,13,0,0,9 +0,0,10,16,5,0,0,0,0,6,16,13,11,0,0,0,0,7,9,4,12,0,0,0,0,0,0,5,15,0,0,0,0,0,0,8,11,0,0,0,0,0,3,15,8,0,0,0,0,4,16,16,10,8,7,1,0,0,7,11,16,16,15,2,2 +0,0,2,13,15,4,0,0,0,0,9,10,5,11,0,0,0,0,10,7,13,10,0,0,0,0,7,12,16,3,0,0,0,0,6,16,7,0,0,0,0,0,14,13,2,0,0,0,0,0,11,2,10,0,0,0,0,0,0,13,15,0,0,0,8 +0,0,3,12,15,3,0,0,0,0,15,16,6,11,0,0,0,7,16,5,0,12,1,0,0,5,14,0,0,9,7,0,0,8,10,0,0,13,8,0,0,4,12,0,2,15,6,0,0,1,14,8,13,12,0,0,0,0,3,15,10,3,0,0,0 +0,0,5,15,16,6,0,0,0,0,13,12,11,11,0,0,0,0,13,11,16,5,0,0,0,0,7,16,14,1,0,0,0,0,10,16,4,0,0,0,0,1,15,12,13,0,0,0,0,0,15,5,15,5,0,0,0,0,5,16,16,6,0,0,8 +0,0,1,15,10,0,0,0,0,0,11,16,4,0,0,0,0,3,16,8,0,10,9,0,0,10,16,3,5,16,10,0,0,10,16,12,15,13,1,0,0,1,10,13,16,9,0,0,0,0,0,9,16,2,0,0,0,0,0,16,9,0,0,0,4 +0,0,3,16,0,0,0,0,0,0,10,10,3,5,0,0,0,0,16,5,12,10,0,0,0,4,15,2,15,4,0,0,0,10,13,9,15,10,11,0,0,3,14,16,14,8,2,0,0,0,0,15,6,0,0,0,0,0,3,15,0,0,0,0,4 +0,0,2,12,11,1,0,0,0,1,16,13,9,8,0,0,0,4,14,1,0,11,1,0,0,6,9,0,0,7,5,0,0,4,9,0,0,6,7,0,0,2,12,0,0,10,6,0,0,0,10,9,13,16,2,0,0,0,2,12,14,3,0,0,0 +0,1,10,8,12,15,1,0,0,0,16,16,16,9,1,0,0,0,13,13,3,0,0,0,0,1,16,16,13,2,0,0,0,0,3,1,13,10,0,0,0,0,0,0,8,16,0,0,0,0,8,1,8,16,1,0,0,2,13,16,16,11,0,0,5 +0,0,5,15,16,12,0,0,0,1,16,9,5,16,2,0,0,2,16,2,11,13,0,0,0,0,13,15,15,2,0,0,0,0,12,16,6,0,0,0,0,2,16,8,13,0,0,0,0,2,16,0,12,6,0,0,0,0,6,15,14,8,0,0,8 +0,0,5,14,16,13,2,0,0,0,15,10,5,15,4,0,0,0,0,1,7,15,1,0,0,0,0,11,16,6,0,0,0,0,0,2,10,14,1,0,0,0,2,0,0,9,8,0,0,1,14,6,4,11,9,0,0,0,5,12,14,10,1,0,3 +0,0,4,15,13,1,0,0,0,1,15,11,9,7,0,0,0,3,16,3,5,16,3,0,0,0,8,16,16,16,3,0,0,0,0,2,4,14,6,0,0,0,0,0,0,9,11,0,0,0,0,0,0,7,15,0,0,0,5,12,12,16,8,0,9 +0,0,6,15,1,0,0,0,0,2,16,7,0,0,0,0,0,8,15,0,0,7,3,0,0,11,13,4,8,16,5,0,0,5,16,16,16,10,0,0,0,0,1,14,15,0,0,0,0,0,4,16,6,0,0,0,0,0,10,13,0,0,0,0,4 +0,0,0,4,16,2,0,0,0,0,0,14,11,8,5,0,0,0,10,13,1,12,8,0,0,6,16,1,0,14,6,0,0,10,14,8,13,16,11,0,0,3,11,12,13,14,2,0,0,0,0,1,14,6,0,0,0,0,0,8,13,1,0,0,4 +0,0,4,14,15,5,0,0,0,7,14,7,6,16,0,0,0,10,6,0,0,11,3,0,0,4,2,0,1,14,0,0,0,0,0,0,10,7,0,0,0,0,0,5,13,1,0,0,0,0,6,16,8,0,0,0,0,0,5,9,12,12,16,2,2 +0,0,5,12,14,7,0,0,0,5,16,7,5,16,2,0,0,8,10,0,4,16,4,0,0,2,11,15,11,13,4,0,0,0,0,0,0,8,4,0,0,0,0,0,0,5,8,0,0,1,7,1,0,11,5,0,0,0,5,13,10,9,1,0,9 +0,0,9,16,16,10,0,0,0,2,16,11,6,6,0,0,0,3,16,2,9,15,1,0,0,0,12,13,15,16,2,0,0,0,0,8,9,16,4,0,0,0,0,0,0,16,4,0,0,0,0,0,6,16,3,0,0,0,8,16,16,13,1,0,9 +0,0,0,15,5,0,0,0,0,0,8,16,1,0,0,0,0,1,16,11,0,2,1,0,0,9,16,2,2,15,11,0,0,11,14,9,15,15,3,0,0,3,12,13,16,6,0,0,0,0,0,7,16,1,0,0,0,0,0,13,13,0,0,0,4 +0,6,16,16,16,12,0,0,0,0,7,9,15,13,0,0,0,0,0,4,16,5,0,0,0,6,12,14,16,9,5,0,0,8,13,16,11,11,5,0,0,0,8,14,0,0,0,0,0,2,16,6,0,0,0,0,0,5,16,1,0,0,0,0,7 +0,0,1,15,16,8,0,0,0,0,6,16,15,13,0,0,0,0,5,16,16,11,0,0,0,0,0,15,16,7,0,0,0,0,6,16,15,0,0,0,0,0,13,11,11,7,0,0,0,0,13,10,5,16,0,0,0,0,3,13,16,12,0,0,8 +0,0,15,16,16,16,12,0,0,0,4,8,11,16,9,0,0,0,0,0,11,13,0,0,0,2,4,3,16,4,0,0,0,10,16,16,16,16,4,0,0,1,8,16,9,7,0,0,0,0,8,13,0,0,0,0,0,0,15,6,0,0,0,0,7 +0,0,5,16,16,6,0,0,0,0,13,12,11,13,0,0,0,0,13,10,14,8,0,0,0,0,7,16,14,1,0,0,0,0,8,16,6,0,0,0,0,0,16,13,14,0,0,0,0,0,15,3,12,9,0,0,0,0,5,15,16,7,0,0,8 +0,0,1,14,11,0,0,0,0,0,11,10,8,8,0,0,0,3,16,1,0,10,2,0,0,6,11,0,0,6,6,0,0,7,9,0,0,3,9,0,0,3,13,0,0,5,10,0,0,0,12,5,4,12,4,0,0,0,1,13,16,7,0,0,0 +0,0,1,16,13,0,0,0,0,0,6,16,15,0,0,0,0,7,16,16,12,0,0,0,1,12,16,16,12,0,0,0,0,0,2,10,14,0,0,0,0,0,0,8,16,1,0,0,0,0,0,10,16,0,0,0,0,0,0,12,16,0,0,0,1 +0,0,6,10,15,11,1,0,0,1,15,9,6,12,4,0,0,5,16,0,0,5,6,0,0,4,14,0,0,6,8,0,0,6,9,0,0,12,2,0,0,7,9,0,6,11,0,0,0,3,15,8,15,4,0,0,0,0,10,14,5,0,0,0,0 +0,0,0,3,16,7,0,0,0,0,2,12,16,12,0,0,0,6,15,16,16,8,0,0,0,12,15,6,16,7,0,0,0,0,2,0,15,9,0,0,0,0,0,0,16,7,0,0,0,0,0,2,16,9,0,0,0,0,0,2,15,10,0,0,1 +0,0,1,12,16,7,0,0,0,0,8,16,11,4,0,0,0,0,13,12,0,0,0,0,0,2,16,6,0,0,0,0,0,4,16,13,12,5,0,0,0,6,16,14,12,15,4,0,0,0,11,14,5,14,11,0,0,0,0,12,16,16,8,0,6 +0,0,0,12,16,1,0,0,0,0,2,16,8,2,0,0,0,0,8,16,5,15,0,0,0,0,15,7,8,9,0,0,0,7,16,12,15,16,11,0,0,9,15,10,15,7,5,0,0,0,0,10,7,0,0,0,0,0,0,16,4,0,0,0,4 +0,3,16,16,16,16,3,0,0,0,7,8,12,14,1,0,0,0,0,1,15,4,0,0,0,1,8,11,15,8,8,0,0,10,16,16,16,12,5,0,0,0,5,15,1,0,0,0,0,0,13,7,0,0,0,0,0,3,15,1,0,0,0,0,7 +0,0,7,16,15,3,0,0,0,0,14,13,13,13,1,0,0,0,12,11,8,16,6,0,0,0,2,14,16,16,8,0,0,0,0,2,4,13,9,0,0,0,0,0,0,12,10,0,0,1,13,4,4,14,10,0,0,0,5,15,16,16,8,0,9 +0,0,1,9,12,8,0,0,0,0,11,13,8,12,0,0,0,0,11,7,14,6,0,0,0,0,5,16,9,0,0,0,0,0,10,15,5,0,0,0,0,0,13,1,10,1,0,0,0,0,13,1,8,5,0,0,0,0,3,14,15,3,0,0,8 +0,5,16,15,5,0,0,0,0,3,9,13,15,1,0,0,0,0,0,1,15,6,0,0,0,0,0,2,16,4,0,0,0,0,0,10,14,0,0,0,0,0,3,16,7,0,0,0,0,0,12,16,13,10,7,0,0,7,16,14,12,12,9,0,2 +0,0,0,9,6,0,0,0,0,0,2,15,6,0,0,0,0,0,6,10,0,0,0,0,0,0,11,8,7,2,0,0,0,0,14,15,8,14,5,0,0,0,16,5,0,4,13,0,0,0,9,13,0,7,13,0,0,0,0,11,16,14,2,0,6 +0,0,8,16,14,1,0,0,0,0,6,16,16,8,0,0,0,0,4,16,16,8,0,0,0,0,2,16,16,14,0,0,0,0,1,16,16,11,0,0,0,0,5,16,16,10,0,0,0,0,9,16,16,9,0,0,0,0,13,16,16,9,0,0,1 +0,6,16,15,4,0,0,0,0,8,15,14,14,1,0,0,0,0,2,2,16,6,0,0,0,0,0,1,16,7,0,0,0,0,0,6,15,1,0,0,0,0,1,15,8,0,0,0,0,1,12,16,9,12,8,0,0,10,16,16,15,12,7,0,2 +0,2,14,16,14,2,0,0,0,9,16,5,12,11,0,0,0,2,11,0,8,16,0,0,0,0,0,0,7,16,0,0,0,0,0,1,13,10,0,0,0,0,0,5,16,5,0,0,0,0,7,16,13,8,8,1,0,2,16,16,16,13,11,1,2 +0,0,10,16,16,16,16,6,0,0,4,9,8,11,15,2,0,0,0,0,1,14,6,0,0,0,0,4,8,15,6,0,0,0,8,16,16,11,5,0,0,0,3,14,11,0,0,0,0,0,4,16,1,0,0,0,0,0,12,6,0,0,0,0,7 +0,1,9,15,14,3,0,0,0,7,12,2,10,12,0,0,0,8,11,0,10,16,1,0,0,3,16,8,9,16,3,0,0,0,4,11,11,16,5,0,0,0,0,0,0,9,8,0,0,0,6,4,0,9,8,0,0,0,8,15,16,12,2,0,9 +0,0,7,13,9,1,0,0,0,1,13,5,9,8,0,0,0,7,10,0,5,12,0,0,0,5,15,8,10,16,4,0,0,0,7,9,7,5,8,0,0,0,0,0,0,1,11,0,0,0,3,0,0,5,10,0,0,0,9,16,16,14,3,0,9 +0,0,4,16,16,16,16,16,0,0,3,8,8,8,15,9,0,0,0,0,0,6,14,1,0,0,0,0,1,15,3,0,0,0,3,15,16,13,0,0,0,0,2,11,15,12,2,0,0,0,2,14,6,0,0,0,0,0,8,12,0,0,0,0,7 +0,0,4,12,16,7,0,0,0,0,15,5,4,14,0,0,0,0,15,0,3,15,4,0,0,5,16,4,5,16,4,0,0,0,10,12,11,11,8,0,0,0,0,0,0,11,5,0,0,0,2,8,0,15,1,0,0,0,2,13,15,5,0,0,9 +0,0,9,16,16,5,0,0,0,2,16,7,9,15,1,0,0,6,15,0,0,13,7,0,0,3,16,0,0,8,12,0,0,8,16,4,0,8,12,0,0,11,13,1,0,10,11,0,0,5,16,6,5,15,7,0,0,0,10,16,16,9,0,0,0 +0,0,6,16,16,16,16,12,0,0,4,9,8,8,16,7,0,0,0,0,0,11,13,0,0,0,0,2,6,16,3,0,0,0,9,16,16,16,8,0,0,0,3,9,15,2,0,0,0,0,0,15,8,0,0,0,0,0,8,16,0,0,0,0,7 +0,0,0,0,10,8,0,0,0,0,0,4,15,2,0,0,0,0,1,13,9,5,3,0,0,0,8,14,2,14,6,0,0,5,15,4,1,16,3,0,0,14,15,14,16,16,4,0,0,8,12,9,12,13,0,0,0,0,0,0,12,8,0,0,4 +0,0,4,15,16,16,16,15,0,0,2,8,8,5,13,9,0,0,0,0,0,5,15,2,0,0,0,0,3,14,8,0,0,0,2,15,16,16,8,0,0,0,3,9,16,4,0,0,0,0,0,11,13,0,0,0,0,0,5,15,3,0,0,0,7 +0,0,4,12,10,1,0,0,0,0,14,9,9,13,0,0,0,7,16,0,6,16,4,0,0,4,15,10,4,16,8,0,0,0,5,12,12,14,8,0,0,0,0,0,0,5,11,0,0,0,2,3,0,7,11,0,0,0,5,14,16,16,4,0,9 +0,0,9,12,14,14,4,0,0,0,15,7,4,4,2,0,0,0,14,0,0,0,0,0,0,3,15,14,14,6,0,0,0,4,7,2,2,15,0,0,0,0,0,0,0,12,2,0,0,0,9,2,2,14,0,0,0,0,11,16,16,5,0,0,5 +0,0,6,14,9,0,0,0,0,2,16,8,13,8,0,0,0,6,12,0,2,12,2,0,0,4,9,0,0,7,7,0,0,6,12,0,0,4,8,0,0,6,15,0,0,3,9,0,0,0,15,6,4,8,11,0,0,0,6,14,16,12,1,0,0 +0,0,14,9,0,0,0,0,0,0,8,16,0,0,0,0,0,0,8,12,0,2,1,0,0,0,12,8,0,13,9,0,0,1,16,4,0,16,8,0,0,5,16,0,0,15,6,0,0,8,16,16,16,16,12,0,0,1,8,8,8,13,7,0,4 +0,0,11,12,8,4,0,0,0,0,11,16,16,8,0,0,0,0,10,16,16,8,0,0,0,0,12,16,16,6,0,0,0,0,12,16,16,4,0,0,0,0,13,16,16,6,0,0,0,0,16,16,16,8,0,0,0,0,12,12,12,7,0,0,1 +0,0,7,16,13,4,0,0,0,1,11,0,6,15,3,0,0,6,9,0,0,13,4,0,0,2,14,5,8,16,5,0,0,0,4,12,10,6,8,0,0,0,0,0,0,4,8,0,0,0,2,4,0,8,7,0,0,0,10,16,16,10,1,0,9 +0,1,13,16,16,11,0,0,0,6,16,7,8,6,0,0,0,4,16,3,4,3,0,0,0,5,16,16,16,16,3,0,0,8,13,5,0,11,12,0,0,0,0,0,0,10,11,0,0,2,11,4,6,16,4,0,0,1,15,16,16,8,0,0,5 +0,0,1,14,15,9,1,0,0,1,9,14,1,7,8,0,0,7,16,6,0,11,6,0,0,0,8,14,13,10,0,0,0,0,1,14,15,7,0,0,0,0,11,7,1,16,1,0,0,0,11,6,0,13,3,0,0,0,3,13,14,11,0,0,8 +0,0,7,16,16,16,12,0,0,0,4,9,8,16,6,0,0,0,0,0,6,12,0,0,0,0,0,4,13,9,0,0,0,0,8,16,16,16,9,0,0,0,5,16,8,4,1,0,0,0,3,13,1,0,0,0,0,0,10,8,0,0,0,0,7 +0,0,8,14,13,3,0,0,0,6,13,3,6,11,0,0,0,1,2,0,4,13,0,0,0,0,0,11,16,11,0,0,0,0,0,3,4,11,7,0,0,0,0,0,0,5,9,0,0,2,13,1,0,7,10,0,0,0,9,16,13,12,1,0,3 +0,0,3,11,15,5,0,0,0,1,11,9,10,15,1,0,0,4,14,7,6,16,3,0,0,1,14,8,11,16,2,0,0,0,1,4,3,9,8,0,0,0,0,0,0,8,8,0,0,0,0,2,1,13,2,0,0,0,2,14,13,11,0,0,9 +0,0,8,16,15,4,0,0,0,2,15,16,13,15,0,0,0,9,15,4,0,15,5,0,0,8,13,0,0,6,12,0,0,10,12,0,0,4,12,0,0,7,15,0,0,4,14,0,0,3,16,8,3,12,11,0,0,0,8,16,16,15,1,0,0 +0,0,5,16,16,16,16,7,0,0,3,8,8,8,14,10,0,0,0,0,0,4,15,2,0,0,0,0,2,14,7,0,0,0,3,13,16,16,9,0,0,0,4,13,15,3,0,0,0,0,0,12,10,0,0,0,0,0,8,15,2,0,0,0,7 +0,1,9,15,12,2,0,0,0,8,10,0,7,14,0,0,0,8,4,0,0,16,4,0,0,2,12,9,9,16,7,0,0,0,1,5,5,9,8,0,0,0,0,0,0,4,11,0,0,0,6,1,0,10,8,0,0,0,9,13,14,11,2,0,9 +0,0,1,9,15,15,2,0,0,1,15,9,5,16,2,0,0,8,10,1,12,8,0,0,0,7,15,12,11,1,0,0,0,0,7,16,13,3,0,0,0,0,6,8,8,15,3,0,0,0,2,15,2,12,8,0,0,0,0,10,16,13,4,0,8 +0,0,5,15,14,1,0,0,0,0,15,5,8,9,0,0,0,4,14,0,0,11,2,0,0,8,16,0,0,6,7,0,0,8,12,2,0,4,8,0,0,4,10,0,0,4,9,0,0,0,14,2,1,10,5,0,0,0,6,14,13,8,0,0,0 +0,3,16,15,2,0,0,0,0,4,14,10,11,0,0,0,0,2,8,4,16,0,0,0,0,0,0,0,16,2,0,0,0,0,0,4,15,0,0,0,0,0,0,9,13,0,0,0,0,0,7,16,14,12,11,0,0,3,16,16,12,9,7,0,2 +0,1,7,12,13,7,0,0,0,4,14,4,5,16,2,0,0,8,9,0,8,13,1,0,0,3,11,14,15,3,0,0,0,0,11,13,16,2,0,0,0,0,16,1,6,11,0,0,0,0,14,1,5,14,0,0,0,0,7,15,15,7,0,0,8 +0,0,0,10,7,1,0,0,0,0,7,16,16,9,0,0,0,2,15,16,16,13,0,0,0,1,9,16,16,12,0,0,0,0,0,16,16,12,0,0,0,0,1,16,16,12,0,0,0,0,3,16,16,16,0,0,0,0,0,9,12,11,0,0,1 +0,0,4,12,8,3,0,0,0,0,7,16,16,3,0,0,0,0,6,16,16,6,0,0,0,0,7,16,16,7,0,0,0,0,7,16,16,9,0,0,0,0,5,16,16,12,0,0,0,0,6,16,16,10,0,0,0,0,5,12,12,4,0,0,1 +0,0,5,13,10,2,0,0,0,3,16,7,10,11,0,0,0,10,16,8,15,16,0,0,0,4,12,12,10,16,4,0,0,0,0,0,0,16,5,0,0,0,0,0,0,12,8,0,0,0,8,5,7,15,6,0,0,0,6,15,15,9,0,0,9 +0,0,5,8,0,0,0,0,0,0,12,6,12,7,0,0,0,2,10,6,9,15,1,0,0,7,8,0,0,12,8,0,0,8,8,0,0,12,6,0,0,5,13,0,0,13,4,0,0,0,14,10,10,13,0,0,0,0,4,12,11,1,0,0,0 +0,0,10,16,14,5,0,0,0,0,7,16,16,5,0,0,0,0,3,16,16,9,0,0,0,0,3,16,16,13,0,0,0,0,2,16,16,16,0,0,0,0,2,16,16,16,2,0,0,0,6,16,16,14,2,0,0,0,4,15,16,6,0,0,1 +0,0,0,11,12,6,0,0,0,0,4,16,16,16,0,0,0,0,9,16,16,16,0,0,0,0,13,16,16,16,4,0,0,0,5,16,16,16,4,0,0,0,1,16,16,16,2,0,0,0,2,16,16,16,5,0,0,0,0,5,11,9,2,0,1 +0,2,9,12,13,4,0,0,0,14,16,13,16,11,0,0,0,5,4,7,16,4,0,0,0,0,0,10,16,2,0,0,0,0,0,6,16,6,0,0,0,0,0,0,14,16,1,0,0,0,7,11,16,13,1,0,0,0,8,12,7,2,0,0,3 +0,0,3,14,0,0,0,0,0,0,3,16,1,0,0,0,0,0,8,13,0,0,0,0,0,0,7,13,0,0,0,0,0,0,11,11,8,4,0,0,0,0,14,16,16,16,8,0,0,0,12,16,7,10,15,0,0,0,2,14,16,15,8,0,6 +0,0,3,12,15,11,0,0,0,0,14,15,15,16,1,0,0,0,3,2,14,16,12,1,0,0,0,13,16,14,9,0,0,0,0,2,14,8,0,0,0,0,0,2,16,5,0,0,0,0,0,13,13,0,0,0,0,0,3,16,3,0,0,0,7 +0,1,15,6,0,0,0,0,0,7,16,16,0,0,0,0,0,11,14,16,0,0,0,0,0,2,8,15,0,0,0,0,0,0,14,10,0,0,0,0,0,5,16,3,0,0,0,0,0,10,16,16,16,12,5,0,0,3,15,16,12,14,14,0,2 +0,0,10,1,0,0,0,0,0,0,16,0,0,0,0,0,0,3,13,0,0,0,0,0,0,5,12,1,4,2,0,0,0,8,14,15,16,15,4,0,0,8,15,5,5,14,8,0,0,8,14,4,10,16,4,0,0,1,12,15,10,2,0,0,6 +0,0,4,14,10,6,0,0,0,0,12,16,16,16,4,0,0,7,16,4,15,16,4,0,0,7,16,16,16,16,4,0,0,0,5,8,14,16,5,0,0,0,0,0,12,16,2,0,0,0,6,16,16,9,0,0,0,0,5,16,11,0,0,0,9 +0,1,8,12,3,0,0,0,0,1,16,16,14,2,0,0,0,2,16,16,16,16,1,0,0,0,12,16,16,4,0,0,0,0,12,16,16,7,0,0,0,2,16,16,16,14,0,0,0,4,16,16,15,4,0,0,0,1,12,10,5,0,0,0,8 +0,0,2,11,15,13,0,0,0,5,15,15,13,15,0,0,0,9,13,3,16,7,0,0,0,3,3,4,16,4,0,0,0,0,0,2,16,11,0,0,0,0,0,0,7,16,6,0,0,0,0,9,8,15,12,0,0,0,0,13,16,13,3,0,3 +0,0,8,14,12,2,0,0,0,10,14,12,14,0,0,0,0,6,2,11,7,0,0,0,0,0,0,12,5,0,0,0,0,0,0,8,15,2,0,0,0,0,0,0,9,14,3,0,0,0,6,2,7,14,8,0,0,0,14,15,11,4,0,0,3 +0,2,10,15,16,15,0,0,0,4,16,9,4,6,0,0,0,8,15,0,0,0,0,0,0,6,14,5,0,0,0,0,0,1,12,16,13,5,0,0,0,0,0,2,9,16,9,0,0,0,9,8,10,16,9,0,0,0,7,16,12,8,1,0,5 +0,0,8,4,0,0,0,0,0,0,15,3,0,0,0,0,0,2,13,0,0,0,0,0,0,4,14,11,9,1,0,0,0,4,9,13,16,14,1,0,0,2,12,0,0,9,11,0,0,0,16,8,5,13,11,0,0,0,4,15,16,12,1,0,6 +0,1,13,16,8,1,0,0,0,8,14,10,16,8,0,0,0,11,14,15,16,9,0,0,0,0,8,7,6,16,4,0,0,0,0,0,0,16,4,0,0,0,1,0,0,14,8,0,0,3,16,2,10,15,1,0,0,2,13,16,12,1,0,0,9 +0,0,6,12,14,14,2,0,0,12,16,11,4,5,2,0,0,4,14,5,0,0,0,0,0,0,9,13,1,0,0,0,0,0,2,13,15,6,0,0,0,0,0,2,11,15,6,0,0,0,8,6,6,15,8,0,0,0,6,16,16,9,0,0,5 +0,0,3,14,16,11,0,0,0,2,15,13,14,13,0,0,0,0,4,0,8,14,5,0,0,0,5,13,16,16,14,0,0,0,12,15,16,9,2,0,0,0,1,9,13,0,0,0,0,0,0,16,6,0,0,0,0,0,4,13,0,0,0,0,7 +0,0,2,11,8,1,0,0,0,0,9,16,16,9,0,0,0,0,11,16,16,8,0,0,0,0,15,16,16,10,0,0,0,0,13,16,16,8,0,0,0,0,7,16,16,9,0,0,0,0,5,16,16,13,0,0,0,0,1,11,12,10,0,0,1 +0,0,5,15,15,6,0,0,0,0,13,11,16,16,4,0,0,3,14,0,8,11,7,0,0,8,7,0,0,7,8,0,0,7,7,0,0,5,8,0,0,9,9,0,1,11,4,0,0,7,13,8,14,12,0,0,0,0,6,15,10,1,0,0,0 +0,0,11,13,4,0,0,0,0,7,16,16,15,0,0,0,0,7,11,5,15,0,0,0,0,0,2,10,11,0,0,0,0,0,2,15,2,0,0,0,0,0,11,8,0,2,3,0,0,1,16,13,14,16,12,0,0,1,12,13,10,5,0,0,2 +0,0,8,2,0,0,0,0,0,0,12,6,0,0,0,0,0,2,16,4,0,0,0,0,0,0,16,8,0,0,0,0,0,4,16,14,11,6,0,0,0,2,16,16,16,16,9,0,0,0,15,16,6,16,12,0,0,0,7,15,16,14,3,0,6 +0,0,0,2,15,8,0,0,0,0,0,10,16,3,0,0,0,0,6,15,4,0,0,0,0,3,15,10,4,10,8,0,0,12,16,16,16,16,10,0,0,13,12,12,15,16,6,0,0,0,0,0,14,15,1,0,0,0,0,2,16,9,0,0,4 +0,0,5,9,12,15,9,0,0,0,13,16,16,16,9,0,0,0,3,3,5,16,3,0,0,0,0,3,12,16,10,0,0,0,2,16,16,13,5,0,0,0,3,15,13,0,0,0,0,0,3,16,5,0,0,0,0,0,7,11,0,0,0,0,7 +0,0,3,14,13,2,0,0,0,1,15,16,16,3,0,0,0,2,14,15,5,0,0,0,0,0,5,15,6,0,0,0,0,0,0,5,16,7,0,0,0,0,0,0,4,15,7,0,0,0,3,8,12,16,13,0,0,0,3,12,15,14,4,0,5 +0,0,6,10,0,0,0,0,0,0,10,11,0,0,0,0,0,1,15,4,0,0,0,0,0,4,16,5,4,0,0,0,0,6,16,16,16,14,3,0,0,7,16,4,6,14,8,0,0,2,15,8,9,14,3,0,0,0,5,14,13,3,0,0,6 +0,2,8,15,15,3,0,0,0,11,16,12,15,12,0,0,0,4,7,0,10,14,0,0,0,0,0,0,8,16,0,0,0,0,0,0,7,16,2,0,0,0,0,0,1,15,8,0,0,0,1,6,11,16,7,0,0,0,8,16,10,5,0,0,3 +0,0,4,13,6,0,0,0,0,4,16,13,4,3,0,0,0,12,10,1,12,16,3,0,0,7,14,9,16,5,0,0,0,0,12,16,13,1,0,0,0,0,5,15,15,15,3,0,0,0,7,14,12,16,10,0,0,0,3,16,11,8,2,0,8 +0,0,4,10,16,12,1,0,0,3,15,14,8,16,5,0,0,4,7,0,5,15,2,0,0,0,0,0,13,8,0,0,0,0,0,3,16,10,0,0,0,0,0,0,10,16,4,0,0,0,4,9,11,15,2,0,0,0,5,13,11,1,0,0,3 +0,0,3,14,8,4,0,0,0,0,15,14,14,16,0,0,0,4,16,13,16,16,0,0,0,3,11,11,10,16,3,0,0,0,0,0,3,15,5,0,0,0,1,5,1,13,6,0,0,0,6,15,16,11,0,0,0,0,2,14,9,1,0,0,9 +0,0,12,7,0,0,0,0,0,3,16,16,3,0,0,0,0,7,11,8,6,0,0,0,0,3,14,7,8,0,0,0,0,0,0,14,3,0,0,0,0,0,4,15,0,0,0,0,0,2,15,16,16,13,9,0,0,0,12,16,16,16,7,0,2 +0,0,3,12,16,15,4,0,0,0,14,16,14,16,6,0,0,1,11,3,13,13,1,0,0,0,1,14,12,1,0,0,0,0,3,16,14,3,0,0,0,0,1,6,16,14,1,0,0,0,14,13,14,15,0,0,0,0,4,16,15,5,0,0,3 +0,0,0,7,12,0,12,5,0,0,8,14,1,7,14,0,0,1,16,5,0,14,6,0,0,5,16,12,13,16,10,0,0,3,15,15,16,13,5,0,0,0,0,4,15,1,0,0,0,0,0,9,10,0,0,0,0,0,0,11,4,0,0,0,4 +0,0,1,12,12,13,8,0,0,0,12,10,4,7,13,0,0,0,15,10,4,9,14,0,0,0,5,12,12,16,9,0,0,0,0,0,2,15,0,0,0,0,0,0,11,7,0,0,0,0,0,4,14,0,0,0,0,0,0,15,2,0,0,0,9 +0,0,2,7,12,8,0,0,0,0,11,16,16,6,0,0,0,0,6,12,0,0,0,0,0,0,13,8,4,0,0,0,0,0,14,16,16,14,1,0,0,0,3,8,9,16,2,0,0,0,0,9,10,14,0,0,0,0,0,13,13,4,0,0,5 +0,0,1,11,16,6,0,0,0,0,12,14,11,16,3,0,0,0,11,5,2,16,5,0,0,0,0,0,7,14,2,0,0,0,0,7,16,7,0,0,0,0,0,3,16,10,0,0,0,0,0,12,16,15,0,0,0,0,1,12,16,7,0,0,3 +0,0,0,2,13,8,4,0,0,0,0,11,13,8,16,2,0,0,7,16,3,14,10,0,0,6,16,8,7,16,6,0,0,11,16,16,16,16,7,0,0,2,8,9,16,9,0,0,0,0,0,2,16,6,0,0,0,0,0,1,16,2,0,0,4 +0,0,8,12,16,16,5,0,0,0,9,16,16,16,1,0,0,0,0,1,13,10,0,0,0,0,3,7,16,13,8,0,0,2,16,16,16,12,7,0,0,0,6,16,4,0,0,0,0,0,11,16,0,0,0,0,0,0,11,13,0,0,0,0,7 +0,0,1,12,16,16,4,0,0,0,10,16,13,8,1,0,0,0,10,13,0,0,0,0,0,0,13,12,4,0,0,0,0,0,14,16,16,6,0,0,0,0,6,9,15,14,0,0,0,0,0,0,9,14,0,0,0,0,4,12,15,6,0,0,5 +0,0,0,2,12,11,0,0,0,0,0,13,16,10,0,0,0,0,9,16,16,9,0,0,0,4,16,14,16,6,0,0,0,1,6,7,16,6,0,0,0,0,0,5,16,6,0,0,0,0,0,3,16,4,0,0,0,0,0,0,12,4,0,0,1 +0,0,3,15,1,0,0,0,0,0,13,13,0,0,0,0,0,2,16,1,0,0,0,0,0,7,10,0,1,3,0,0,0,8,9,4,15,15,2,0,0,3,16,6,0,9,8,0,0,0,16,14,9,15,7,0,0,0,3,13,16,9,0,0,6 +0,0,9,0,0,0,0,0,0,2,16,6,8,5,0,0,0,4,16,16,16,15,3,0,0,6,16,10,12,15,8,0,0,4,11,0,0,7,8,0,0,4,16,9,10,15,8,0,0,2,15,16,16,16,3,0,0,0,8,16,15,7,0,0,0 +0,0,0,4,12,0,0,10,0,0,3,15,5,0,11,13,0,0,11,12,0,4,16,4,0,2,16,12,3,14,10,0,0,4,16,16,16,15,1,0,0,0,10,10,16,8,0,0,0,0,0,3,15,2,0,0,0,0,0,7,11,0,0,0,4 +0,0,5,12,16,13,4,0,0,0,12,13,8,8,6,0,0,2,15,3,0,0,0,0,0,4,16,12,12,6,0,0,0,4,16,15,13,14,0,0,0,0,0,0,4,15,0,0,0,0,3,4,13,8,0,0,0,0,6,12,13,1,0,0,5 +0,0,0,0,13,5,0,0,0,0,0,8,15,2,2,0,0,0,9,15,5,13,7,0,0,5,16,6,2,16,4,0,0,11,16,15,11,16,3,0,0,3,12,13,15,15,8,0,0,0,0,0,12,9,0,0,0,0,0,0,10,9,0,0,4 +0,0,2,13,13,2,0,0,0,1,15,13,15,4,0,0,0,5,13,1,14,2,0,0,0,0,2,12,14,0,0,0,0,0,0,8,14,14,1,0,0,0,0,0,0,13,7,0,0,0,3,8,8,15,2,0,0,0,2,14,11,4,0,0,3 +0,0,0,5,9,13,14,6,0,0,4,16,8,4,10,9,0,0,6,16,16,16,15,4,0,0,1,11,12,16,6,0,0,0,0,0,2,16,2,0,0,0,0,0,10,6,0,0,0,0,0,2,15,0,0,0,0,0,0,5,14,0,0,0,9 +0,0,1,6,12,14,4,0,0,0,4,13,8,3,0,0,0,0,13,4,0,0,0,0,0,3,15,5,2,0,0,0,0,8,16,16,16,12,0,0,0,2,4,3,7,15,3,0,0,0,0,2,7,14,1,0,0,0,0,8,12,4,0,0,5 +0,0,0,15,2,0,0,0,0,0,9,13,0,0,0,0,0,0,16,6,0,0,0,0,0,4,14,2,0,0,0,0,0,4,16,6,6,7,1,0,0,4,16,15,8,14,6,0,0,0,9,15,8,15,7,0,0,0,2,14,15,8,0,0,6 +0,0,0,1,14,12,0,0,0,4,6,15,14,12,0,0,0,5,16,16,13,13,1,0,0,0,11,16,16,16,4,0,0,0,0,3,2,9,8,0,0,0,0,0,0,9,8,0,0,0,0,0,11,16,4,0,0,0,0,0,11,5,1,0,9 +0,1,8,14,13,3,0,0,0,10,13,5,10,12,0,0,0,3,0,2,14,9,0,0,0,0,0,15,8,0,0,0,0,0,0,13,13,1,0,0,0,0,0,0,7,14,4,0,0,1,6,1,0,11,10,0,0,0,10,12,16,16,7,0,3 +0,0,0,0,3,14,1,0,0,0,0,1,14,15,0,0,0,0,0,9,16,10,0,0,0,0,9,16,16,8,0,0,0,4,16,7,16,8,0,0,0,1,4,0,15,10,0,0,0,0,0,0,11,12,0,0,0,0,0,0,3,15,3,0,1 +0,0,2,8,15,11,0,0,0,2,15,14,12,16,4,0,0,2,7,0,10,15,0,0,0,0,0,3,15,3,0,0,0,0,0,13,9,0,0,0,0,0,0,4,14,5,0,0,0,2,10,8,7,16,6,0,0,0,4,9,13,15,7,0,3 +0,0,0,8,8,8,13,8,0,0,6,15,13,14,15,1,0,0,13,6,0,12,5,0,0,0,14,5,5,10,0,0,0,0,1,10,16,14,2,0,0,0,0,12,12,6,1,0,0,0,0,14,4,0,0,0,0,0,0,12,1,0,0,0,7 +0,0,13,16,13,1,0,0,0,3,16,13,16,8,0,0,0,0,9,11,16,7,0,0,0,0,0,14,14,0,0,0,0,0,0,11,15,12,1,0,0,0,0,0,1,14,10,0,0,1,11,6,4,8,16,3,0,0,13,16,16,16,16,2,3 +0,0,6,16,9,0,0,0,0,2,15,16,14,0,0,0,0,2,10,16,6,0,0,0,0,2,16,16,9,2,0,0,0,0,3,10,16,14,1,0,0,0,0,0,10,16,5,0,0,0,4,8,8,16,7,0,0,0,8,16,16,13,0,0,3 +0,1,0,6,16,8,0,0,0,11,7,13,3,13,2,0,0,11,14,4,0,8,4,0,0,9,7,0,0,5,7,0,0,6,9,0,0,5,8,0,0,0,13,4,0,11,9,0,0,0,6,14,11,16,7,0,0,0,0,5,16,10,0,0,0 +0,0,0,0,4,16,5,0,0,0,0,2,15,16,4,0,0,0,1,13,16,16,0,0,0,1,14,12,14,12,0,0,0,6,14,1,10,13,0,0,0,4,2,0,9,13,0,0,0,0,0,0,8,12,0,0,0,0,0,0,4,14,0,0,1 +0,1,13,16,10,0,0,0,0,2,15,13,16,2,0,0,0,0,5,1,14,5,0,0,0,0,0,0,14,7,0,0,0,0,0,4,16,3,0,0,0,0,0,13,13,1,0,0,0,1,14,16,13,12,11,1,0,1,15,16,14,10,8,1,2 +0,0,7,11,16,13,4,0,0,4,16,16,16,16,11,0,0,4,16,16,16,16,8,0,0,0,4,11,14,13,1,0,0,0,0,0,8,12,0,0,0,0,0,0,9,11,0,0,0,0,4,10,16,5,0,0,0,0,9,12,7,0,0,0,9 +0,0,5,12,12,0,0,0,0,3,16,12,15,4,0,0,0,4,14,0,14,6,0,0,0,1,12,11,12,0,0,0,0,0,7,16,8,1,0,0,0,0,1,5,12,13,1,0,0,0,8,4,0,16,4,0,0,0,8,15,15,11,1,0,3 +0,4,9,11,13,16,7,0,0,9,16,13,14,16,1,0,0,0,0,0,13,10,0,0,0,5,12,13,16,15,8,0,0,5,12,16,13,6,2,0,0,0,5,16,3,0,0,0,0,0,12,13,0,0,0,0,0,3,16,4,0,0,0,0,7 +0,3,15,16,7,0,0,0,0,8,15,14,12,0,0,0,0,1,3,3,14,0,0,0,0,0,0,5,15,0,0,0,0,0,0,11,10,0,0,0,0,0,5,16,6,0,1,0,0,5,16,16,13,16,15,0,0,4,16,16,14,12,9,0,2 +0,0,1,8,16,8,0,0,0,2,14,13,12,16,2,0,0,10,9,0,7,15,1,0,0,3,14,9,15,2,0,0,0,0,10,16,12,2,0,0,0,0,12,6,5,15,3,0,0,0,9,8,0,13,7,0,0,0,0,8,16,12,1,0,8 +0,0,9,13,4,0,0,0,0,4,15,9,14,1,0,0,0,2,7,0,10,6,0,0,0,0,0,0,9,5,0,0,0,0,0,3,16,2,0,0,0,0,0,14,13,0,0,0,0,0,11,16,9,8,14,0,0,0,10,16,12,12,7,1,2 +0,0,0,15,14,6,0,0,0,0,3,16,16,6,0,0,0,2,13,16,12,0,0,0,0,8,16,16,7,0,0,0,0,1,10,16,5,0,0,0,0,0,7,16,6,0,0,0,0,0,5,16,9,0,0,0,0,0,0,13,15,0,0,0,1 +0,0,0,7,12,2,0,0,0,0,5,14,9,3,0,0,0,0,16,8,0,0,0,0,0,3,13,0,0,0,0,0,0,4,9,12,14,7,0,0,0,1,11,12,8,7,11,1,0,0,4,10,2,0,11,5,0,0,0,6,13,14,14,1,6 +0,0,0,6,13,0,0,0,0,0,1,15,5,1,2,0,0,0,12,8,0,13,7,0,0,6,14,0,1,15,4,0,0,13,13,8,11,16,9,0,0,8,16,16,16,8,0,0,0,0,0,7,13,0,0,0,0,0,0,8,8,0,0,0,4 +0,0,8,14,12,9,1,0,0,0,15,5,8,8,1,0,0,0,14,1,0,0,0,0,0,0,15,4,0,0,0,0,0,5,16,16,16,9,0,0,0,1,4,0,1,15,5,0,0,0,1,4,4,14,3,0,0,0,5,16,15,3,0,0,5 +0,0,4,15,15,14,1,0,0,1,15,16,10,15,6,0,0,5,16,11,0,6,11,0,0,8,12,0,0,4,12,0,0,10,12,0,0,5,12,0,0,6,16,1,0,9,9,0,0,0,14,14,8,15,6,0,0,0,4,15,16,11,1,0,0 +0,0,1,14,16,12,1,0,0,0,14,7,3,12,4,0,0,2,14,0,1,16,1,0,0,0,12,10,16,6,0,0,0,0,11,14,15,4,0,0,0,4,14,0,5,14,2,0,0,1,15,6,1,11,7,0,0,0,3,8,14,15,1,0,8 +0,0,1,12,16,5,0,0,0,0,9,16,9,2,0,0,0,3,16,6,0,0,0,0,0,6,16,8,1,0,0,0,0,10,16,11,13,3,0,0,0,4,16,1,9,13,1,0,0,0,9,11,9,16,1,0,0,0,1,12,16,12,0,0,6 +0,0,0,5,14,0,0,0,0,0,0,13,7,2,8,0,0,0,5,13,1,11,10,0,0,2,14,4,1,16,4,0,0,12,14,8,13,16,10,0,0,15,16,16,16,12,2,0,0,3,3,5,15,0,0,0,0,0,0,6,13,0,0,0,4 +0,0,3,13,8,1,0,0,0,0,11,16,14,9,0,0,0,4,16,5,4,14,0,0,0,4,15,0,0,12,8,0,0,5,12,0,0,12,8,0,0,2,15,0,0,12,6,0,0,0,14,10,8,15,2,0,0,0,4,13,16,6,0,0,0 +0,0,0,4,15,3,0,0,0,0,0,12,8,0,10,1,0,0,8,11,0,6,14,0,0,5,15,13,14,16,15,0,0,7,15,12,10,16,3,0,0,0,0,0,9,9,0,0,0,0,0,0,15,1,0,0,0,0,0,6,12,0,0,0,4 +0,0,10,16,16,16,16,5,0,0,5,9,8,15,12,1,0,0,0,0,7,13,1,0,0,0,6,8,16,16,13,0,0,0,14,16,12,8,1,0,0,0,2,16,3,0,0,0,0,0,7,13,0,0,0,0,0,0,13,6,0,0,0,0,7 +0,0,4,11,16,12,5,0,0,0,6,7,3,11,9,0,0,0,0,0,0,12,4,0,0,0,0,3,10,10,0,0,0,0,4,16,15,2,0,0,0,0,1,7,12,15,1,0,0,0,8,7,5,15,0,0,0,0,7,12,15,6,0,0,3 +0,0,0,8,16,5,0,0,0,0,5,15,10,2,0,0,0,2,15,6,0,0,0,0,0,5,12,0,0,0,0,0,0,4,15,14,16,14,5,0,0,0,16,15,1,5,12,0,0,0,7,15,1,7,14,0,0,0,0,8,16,15,4,0,6 +0,1,11,12,12,12,4,0,0,0,15,8,8,11,7,0,0,0,12,0,0,0,0,0,0,0,13,1,0,0,0,0,0,3,16,15,11,5,0,0,0,0,4,4,10,15,2,0,0,0,12,5,5,14,3,0,0,0,11,13,12,8,0,0,5 +0,0,7,16,16,16,11,0,0,0,12,12,8,8,5,0,0,3,16,2,0,0,0,0,0,9,16,16,10,1,0,0,0,3,7,8,14,13,0,0,0,0,0,0,4,16,3,0,0,0,9,7,11,15,0,0,0,0,7,16,15,3,0,0,5 +0,0,0,3,11,16,5,0,0,0,0,11,16,16,5,0,0,3,13,16,16,14,0,0,0,3,11,12,16,12,0,0,0,0,0,9,16,9,0,0,0,0,0,12,16,10,0,0,0,0,0,9,16,11,0,0,0,0,0,5,13,8,0,0,1 +0,0,0,5,15,2,0,0,0,0,1,15,8,0,0,0,0,0,8,12,0,0,0,0,0,0,12,8,0,0,0,0,0,0,16,10,8,7,0,0,0,1,14,12,8,9,10,0,0,0,5,13,1,8,14,0,0,0,0,6,14,15,5,0,6 +0,0,10,16,16,13,0,0,0,0,13,6,11,16,3,0,0,0,0,6,16,8,0,0,0,0,11,16,12,0,0,0,0,0,6,16,16,13,0,0,0,0,0,0,7,16,7,0,0,3,15,5,10,16,3,0,0,0,14,16,15,5,0,0,3 +0,0,0,4,12,11,1,0,0,0,12,15,10,13,4,0,0,8,10,0,3,15,1,0,0,3,14,5,15,6,0,0,0,0,13,16,11,0,0,0,0,3,15,6,11,12,3,0,0,1,14,10,2,10,10,0,0,0,1,6,11,16,6,0,8 +0,0,4,10,14,3,0,0,0,6,14,8,11,15,0,0,0,8,8,0,7,14,0,0,0,3,14,12,14,2,0,0,0,1,15,15,8,0,0,0,0,3,13,0,10,10,0,0,0,0,11,3,1,15,3,0,0,0,3,12,13,14,0,0,8 +0,0,1,9,14,5,0,0,0,0,8,15,9,14,1,0,0,0,3,14,0,16,4,0,0,0,0,8,14,16,4,0,0,0,0,0,3,13,5,0,0,0,3,0,0,8,7,0,0,3,15,6,2,14,6,0,0,0,1,10,14,14,1,0,9 +0,0,0,10,16,3,0,0,0,0,5,15,5,0,0,0,0,0,12,9,0,0,0,0,0,0,15,3,0,0,0,0,0,2,15,10,8,2,0,0,0,1,13,13,10,14,0,0,0,0,5,12,2,14,6,0,0,0,0,7,16,16,2,0,6 +0,0,0,10,12,4,0,0,0,0,7,12,6,13,0,0,0,0,7,9,2,13,0,0,0,0,1,15,15,6,0,0,0,0,7,15,11,9,0,0,0,0,16,1,0,10,9,0,0,0,13,5,0,0,15,0,0,0,1,9,14,14,12,0,8 +0,3,16,15,3,0,0,0,0,2,10,12,10,0,0,0,0,0,0,7,12,0,0,0,0,0,0,8,12,0,0,0,0,0,1,15,6,0,0,0,0,0,5,15,1,0,0,0,0,2,15,12,7,4,0,0,0,2,15,16,16,16,16,3,2 +0,0,12,12,16,16,9,0,0,0,8,6,4,13,8,0,0,0,0,4,8,16,6,0,0,3,16,16,16,11,4,0,0,0,3,9,9,0,0,0,0,0,3,14,0,0,0,0,0,0,11,7,0,0,0,0,0,0,14,3,0,0,0,0,7 +0,0,0,1,15,3,0,0,0,0,1,11,11,1,0,0,0,0,10,13,0,10,0,0,0,6,16,6,6,15,3,0,0,8,16,16,16,16,9,0,0,0,2,4,15,4,0,0,0,0,0,1,16,1,0,0,0,0,0,2,15,0,0,0,4 +0,0,0,8,14,3,4,0,0,0,1,15,8,9,10,0,0,0,11,12,1,15,4,0,0,4,16,4,5,16,4,0,0,11,16,16,16,16,10,0,0,4,12,13,16,8,1,0,0,0,0,6,14,0,0,0,0,0,0,11,8,0,0,0,4 +0,0,0,11,13,1,0,0,0,0,12,14,9,2,0,0,0,4,15,3,0,0,0,0,0,7,12,12,12,6,1,0,0,7,16,13,8,12,7,0,0,4,16,6,0,7,11,0,0,0,12,10,5,16,6,0,0,0,2,10,15,10,0,0,6 +0,0,7,13,3,0,0,0,0,2,16,16,10,0,0,0,0,6,14,8,14,0,0,0,0,7,9,4,16,1,0,0,0,0,0,7,12,0,0,0,0,0,4,15,10,6,1,0,0,0,8,16,16,16,6,0,0,0,5,11,12,15,3,0,2 +0,0,9,16,9,1,0,0,0,2,16,13,16,11,0,0,0,9,13,0,10,16,4,0,0,4,15,12,13,16,6,0,0,0,5,12,13,16,7,0,0,0,0,0,0,15,8,0,0,0,9,6,11,16,2,0,0,0,9,13,11,3,0,0,9 +0,3,14,14,2,0,0,0,0,10,15,14,11,0,0,0,0,11,7,5,16,0,0,0,0,1,2,0,16,3,0,0,0,0,0,7,12,0,0,0,0,0,1,12,15,7,0,0,0,1,15,16,16,16,11,0,0,1,10,10,5,6,7,0,2 +0,1,12,16,11,4,0,0,0,4,16,10,11,12,0,0,0,0,16,6,0,0,0,0,0,2,16,16,7,0,0,0,0,0,3,9,15,6,0,0,0,0,0,0,8,13,0,0,0,0,2,4,9,15,0,0,0,0,11,16,15,6,0,0,5 +0,0,4,11,0,0,0,0,0,0,11,13,0,0,0,0,0,2,15,8,0,0,0,0,0,3,16,4,0,0,0,0,0,7,16,16,16,9,0,0,0,4,16,12,9,16,7,0,0,2,15,9,6,16,9,0,0,0,4,15,16,13,2,0,6 +0,0,0,14,6,0,0,0,0,0,4,16,6,0,0,0,0,0,10,14,1,0,0,0,0,0,14,15,3,0,0,0,0,2,16,16,16,13,1,0,0,3,16,13,6,15,11,0,0,0,10,16,5,15,13,0,0,0,1,10,16,15,7,0,6 +0,0,2,14,6,0,0,0,0,0,11,16,7,0,0,0,0,0,16,16,0,0,0,0,0,3,16,12,0,0,0,0,0,5,16,16,16,11,1,0,0,4,16,16,14,16,6,0,0,0,12,16,11,16,10,0,0,0,1,11,15,11,2,0,6 +0,0,7,14,13,13,8,0,0,0,9,12,14,16,13,0,0,0,0,0,9,16,3,0,0,0,2,10,16,15,7,0,0,1,14,16,16,13,7,0,0,0,3,16,13,0,0,0,0,0,5,16,7,0,0,0,0,0,9,13,2,0,0,0,7 +0,0,6,14,6,0,0,0,0,1,16,13,16,6,0,0,0,4,16,6,14,16,2,0,0,0,11,16,16,16,6,0,0,0,0,3,6,15,9,0,0,0,0,0,0,10,13,0,0,0,15,9,6,13,11,0,0,0,4,14,15,10,3,0,9 +0,5,12,14,16,14,4,0,0,7,16,11,7,4,2,0,0,7,16,0,0,0,0,0,0,8,16,16,13,1,0,0,0,5,12,12,16,8,0,0,0,0,0,0,9,15,0,0,0,2,8,8,15,11,0,0,0,3,14,11,6,0,0,0,5 +0,0,0,7,16,3,0,0,0,0,0,15,12,0,0,0,0,0,9,16,2,7,13,0,0,4,15,7,1,15,8,0,0,9,16,16,16,16,7,0,0,3,8,8,15,13,1,0,0,0,0,3,16,4,0,0,0,0,0,10,9,0,0,0,4 +0,0,0,7,8,0,0,0,0,0,3,16,4,0,0,0,0,0,14,10,0,6,3,0,0,4,16,9,4,16,8,0,0,3,16,16,16,16,6,0,0,0,4,6,16,12,0,0,0,0,0,3,16,6,0,0,0,0,0,7,13,0,0,0,4 +0,0,6,16,16,14,4,0,0,0,4,7,8,16,7,0,0,0,0,0,4,16,5,0,0,0,9,16,16,16,3,0,0,0,14,15,16,10,4,0,0,0,1,14,10,0,0,0,0,0,5,16,6,0,0,0,0,0,7,15,2,0,0,0,7 +0,0,6,12,12,9,0,0,0,0,11,16,16,13,0,0,0,0,9,16,16,12,0,0,0,0,14,16,16,12,0,0,0,0,12,16,16,12,0,0,0,2,16,16,16,9,0,0,0,1,15,16,15,2,0,0,0,0,7,11,3,0,0,0,1 +0,0,6,14,15,0,0,0,0,1,16,9,16,3,0,0,0,0,1,3,16,2,0,0,0,0,4,16,15,4,0,0,0,0,4,8,14,16,3,0,0,0,0,0,0,16,4,0,0,0,3,7,10,15,2,0,0,0,7,15,9,1,0,0,3 +0,0,9,16,16,16,5,0,0,0,6,8,11,16,10,0,0,0,0,0,9,16,5,0,0,0,1,8,15,15,3,0,0,0,9,16,16,16,7,0,0,0,2,15,14,0,0,0,0,0,9,16,7,0,0,0,0,0,9,16,5,0,0,0,7 +0,3,11,16,15,6,0,0,0,5,10,8,14,11,0,0,0,0,0,1,15,6,0,0,0,0,0,11,16,5,0,0,0,0,0,1,13,15,2,0,0,0,0,0,0,13,6,0,0,1,7,5,4,14,7,0,0,2,12,12,15,11,1,0,3 +0,0,1,11,15,4,0,0,0,1,13,13,14,16,0,0,0,8,15,1,10,16,4,0,0,1,15,14,16,14,2,0,0,0,6,16,15,2,0,0,0,0,8,15,14,9,0,0,0,0,11,16,10,16,3,0,0,0,2,12,13,12,1,0,8 +0,0,11,16,16,14,4,0,0,3,16,10,11,9,5,0,0,5,15,3,0,0,0,0,0,4,16,16,15,3,0,0,0,3,13,12,15,14,0,0,0,0,0,0,6,16,0,0,0,0,4,12,16,10,0,0,0,0,9,11,6,0,0,0,5 +0,0,9,14,16,16,5,0,0,0,6,16,7,11,10,0,0,0,0,15,7,9,9,0,0,0,0,12,15,15,3,0,0,0,7,16,16,6,0,0,0,3,14,2,16,2,0,0,0,5,15,5,16,4,0,0,0,1,11,16,12,0,0,0,8 +0,0,6,11,16,14,2,0,0,1,16,15,7,4,1,0,0,4,16,13,2,0,0,0,0,6,16,16,16,8,0,0,0,1,6,5,11,16,3,0,0,0,0,0,0,16,5,0,0,0,1,5,11,16,2,0,0,0,7,15,10,5,0,0,5 +0,0,5,14,7,1,0,0,0,2,15,14,16,10,0,0,0,2,16,6,12,16,0,0,0,1,11,5,10,16,0,0,0,0,0,0,16,9,0,0,0,0,3,5,16,5,0,0,0,0,13,16,15,7,5,0,0,0,5,8,10,13,13,0,2 +0,0,0,11,4,0,0,0,0,0,6,16,9,0,0,0,0,0,12,15,1,0,0,0,0,0,16,10,3,0,0,0,0,2,16,16,16,10,1,0,0,0,15,14,4,11,11,0,0,0,9,15,2,6,16,0,0,0,0,9,16,16,12,2,6 +0,0,0,8,15,0,0,0,0,0,3,16,10,0,6,0,0,1,13,14,2,10,15,0,0,7,16,16,13,15,13,0,0,3,10,14,16,16,10,0,0,0,0,0,13,15,0,0,0,0,0,3,16,8,0,0,0,0,0,9,15,1,0,0,4 +0,0,5,12,13,11,3,0,0,2,16,12,8,6,3,0,0,4,16,9,3,0,0,0,0,7,16,16,16,7,0,0,0,3,8,4,11,14,0,0,0,0,0,0,4,16,2,0,0,0,2,6,12,16,2,0,0,0,8,12,8,3,0,0,5 +0,0,9,16,7,0,0,0,0,0,13,15,14,7,0,0,0,0,15,10,7,15,0,0,0,0,5,4,5,13,0,0,0,0,0,0,8,12,0,0,0,0,1,4,13,10,0,0,0,0,12,16,16,13,8,1,0,0,4,12,9,9,12,5,2 +0,0,10,15,14,4,0,0,0,1,15,13,13,15,2,0,0,0,0,4,14,14,0,0,0,0,6,16,16,6,0,0,0,0,3,10,16,12,0,0,0,0,0,0,9,16,8,0,0,1,15,8,13,16,4,0,0,0,10,14,11,6,1,0,3 +0,0,4,12,15,16,16,2,0,0,3,6,4,13,15,1,0,0,0,0,2,15,6,0,0,0,0,1,12,13,0,0,0,4,16,16,16,12,4,0,0,1,3,12,14,0,0,0,0,0,3,16,6,0,0,0,0,0,5,15,2,0,0,0,7 +0,0,0,5,14,1,0,0,0,0,1,14,7,0,3,0,0,0,9,14,1,6,15,0,0,3,16,12,4,12,8,0,0,0,7,10,14,16,4,0,0,0,0,0,10,13,0,0,0,0,0,1,16,3,0,0,0,0,0,6,12,0,0,0,4 +0,1,15,15,2,0,0,0,0,9,15,12,10,0,0,0,0,8,10,8,12,0,0,0,0,1,1,5,15,0,0,0,0,0,0,6,14,0,0,0,0,0,0,10,14,1,0,0,0,0,11,16,16,16,10,0,0,0,14,13,8,10,7,0,2 +0,0,3,14,12,15,11,0,0,0,2,8,8,15,14,0,0,0,0,0,0,15,5,0,0,0,2,4,9,16,1,0,0,4,15,16,16,16,6,0,0,2,4,11,13,1,0,0,0,0,1,16,7,0,0,0,0,0,4,14,2,0,0,0,7 +0,1,11,16,14,7,0,0,0,6,16,10,8,4,0,0,0,0,16,1,0,0,0,0,0,1,16,9,5,0,0,0,0,0,12,12,15,2,0,0,0,0,0,0,9,8,0,0,0,0,9,8,14,9,0,0,0,0,9,15,9,1,0,0,5 +0,1,10,14,2,0,0,0,0,4,16,14,6,0,0,0,0,5,9,8,8,0,0,0,0,0,1,13,7,0,0,0,0,0,3,16,1,0,0,0,0,0,6,14,0,2,2,0,0,0,16,16,16,16,12,0,0,0,15,13,10,9,8,0,2 +0,0,11,13,8,0,0,0,0,5,16,11,16,16,6,0,0,7,16,1,9,15,3,0,0,1,13,14,13,14,0,0,0,0,4,16,16,5,0,0,0,0,14,10,13,10,0,0,0,4,16,6,10,12,0,0,0,1,11,16,15,5,0,0,8 +0,0,0,13,9,0,0,0,0,0,8,16,13,0,0,0,0,4,16,16,15,1,0,0,0,0,3,10,16,4,0,0,0,0,0,14,16,3,0,0,0,0,0,15,16,3,0,0,0,0,5,16,16,10,0,0,0,0,0,10,12,11,1,0,1 +0,0,0,6,9,0,0,0,0,0,0,12,12,8,0,0,0,0,0,16,7,12,0,0,0,0,6,11,7,10,0,0,0,1,14,4,13,13,5,0,0,5,16,16,16,13,5,0,0,0,4,6,16,9,0,0,0,0,0,8,16,7,0,0,4 +0,0,9,14,7,1,0,0,0,6,16,10,15,5,0,0,0,7,15,0,12,8,0,0,0,0,0,3,16,4,0,0,0,0,0,7,14,0,0,0,0,0,2,16,5,0,1,0,0,0,8,15,13,16,15,2,0,0,8,14,11,6,10,2,2 +0,0,3,12,11,1,0,0,0,0,12,15,11,10,0,0,0,0,12,7,2,14,0,0,0,0,0,0,3,16,0,0,0,0,0,0,13,11,0,0,0,0,0,10,15,1,0,0,0,0,5,16,12,9,8,1,0,0,3,11,15,12,16,4,2 +0,0,2,11,12,4,0,0,0,0,11,15,14,11,0,0,0,0,3,9,12,11,0,0,0,0,0,10,16,14,2,0,0,0,0,2,5,13,7,0,0,0,6,0,0,9,11,0,0,0,14,13,8,14,9,0,0,0,5,15,14,11,1,0,3 +0,0,0,6,10,0,0,0,0,0,0,12,5,0,0,0,0,0,2,14,2,5,0,0,0,0,12,7,4,14,0,0,0,3,16,4,10,14,5,0,0,7,16,16,16,12,5,0,0,0,0,2,16,1,0,0,0,0,0,5,14,1,0,0,4 +0,0,0,11,7,0,0,0,0,0,0,16,8,2,0,0,0,0,6,16,9,15,0,0,0,0,11,10,11,15,0,0,0,4,16,12,16,16,9,0,0,11,16,16,16,13,6,0,0,1,4,11,16,0,0,0,0,0,0,11,13,0,0,0,4 +0,0,2,14,13,5,0,0,0,0,8,16,16,10,0,0,0,6,16,16,16,7,0,0,0,9,16,16,16,3,0,0,0,0,7,16,16,2,0,0,0,0,8,16,16,6,0,0,0,0,10,16,16,11,0,0,0,0,3,13,14,7,0,0,1 +0,0,3,15,12,3,0,0,0,0,9,16,16,5,0,0,0,3,14,16,16,5,0,0,0,7,16,16,16,3,0,0,0,0,3,16,16,3,0,0,0,0,0,16,16,4,0,0,0,0,7,15,16,6,0,0,0,0,6,16,14,3,0,0,1 +0,0,5,15,8,2,0,0,0,0,15,16,14,12,0,0,0,5,13,2,0,14,3,0,0,5,11,0,0,8,8,0,0,8,8,0,0,12,5,0,0,3,14,0,0,15,4,0,0,0,13,12,10,14,0,0,0,0,4,14,15,4,0,0,0 +0,0,0,9,16,4,0,0,0,0,6,16,16,4,0,0,0,0,11,15,1,0,0,0,0,0,14,13,0,0,0,0,0,0,16,16,16,9,0,0,0,1,16,12,8,14,5,0,0,0,11,15,9,15,9,0,0,0,0,10,13,15,3,0,6 +0,0,9,13,7,0,0,0,0,2,16,12,15,12,2,0,0,8,11,0,4,16,4,0,0,8,13,1,8,16,7,0,0,1,15,16,13,15,8,0,0,0,0,2,0,9,12,0,0,0,6,9,9,15,9,0,0,0,6,16,14,8,1,0,9 +0,2,11,14,12,9,0,0,0,8,13,6,9,14,4,0,0,5,16,5,1,14,6,0,0,0,8,16,16,13,1,0,0,0,3,16,16,10,0,0,0,1,14,9,7,13,0,0,0,1,16,5,7,16,1,0,0,0,14,16,16,9,0,0,8 +0,0,6,12,14,2,0,0,0,1,16,14,13,11,0,0,0,0,5,3,9,13,0,0,0,0,3,9,16,8,0,0,0,0,4,12,12,15,7,0,0,0,0,0,0,9,14,0,0,0,4,8,6,12,14,1,0,0,6,16,16,15,4,0,3 +0,0,4,12,10,1,0,0,0,1,15,9,14,4,0,0,0,0,7,3,13,4,0,0,0,0,0,16,16,6,0,0,0,0,0,2,5,14,4,0,0,0,3,0,0,8,12,0,0,0,15,8,5,15,9,0,0,0,3,13,16,10,0,0,3 +0,0,10,15,11,2,0,0,0,2,16,7,14,10,0,0,0,3,16,1,9,16,1,0,0,0,11,14,15,16,5,0,0,0,1,4,5,10,7,0,0,0,0,0,0,11,9,0,0,0,9,8,9,15,6,0,0,0,7,15,12,6,0,0,9 +0,0,5,15,4,0,0,0,0,0,15,14,15,0,0,0,0,2,15,3,14,2,0,0,0,0,3,0,14,4,0,0,0,0,0,2,15,0,0,0,0,0,0,10,10,0,0,0,0,0,6,16,8,10,14,0,0,0,7,12,12,12,12,1,2 +0,0,0,11,4,0,0,0,0,0,0,14,5,1,0,0,0,0,4,13,8,8,0,0,0,0,10,5,10,6,0,0,0,3,16,5,14,12,3,0,0,8,16,16,15,14,6,0,0,0,4,8,8,0,0,0,0,0,0,8,9,0,0,0,4 +0,0,5,11,15,5,0,0,0,0,14,13,9,6,0,0,0,1,15,9,1,0,0,0,0,4,16,16,14,2,0,0,0,0,6,3,6,10,0,0,0,0,0,0,1,14,0,0,0,0,5,10,10,14,0,0,0,0,5,15,16,9,0,0,5 +0,0,0,6,8,0,0,0,0,0,0,15,3,0,0,0,0,0,2,14,2,11,0,0,0,0,9,8,5,10,0,0,0,0,14,2,8,7,2,0,0,4,16,9,14,16,8,0,0,2,8,12,16,5,2,0,0,0,0,6,13,2,0,0,4 +0,0,2,8,14,2,0,0,0,0,9,16,10,1,0,0,0,1,16,9,1,0,0,0,0,2,16,16,14,3,0,0,0,0,15,10,7,14,0,0,0,0,14,1,0,13,4,0,0,0,7,10,5,15,4,0,0,0,0,9,16,12,0,0,6 +0,0,0,2,9,0,0,0,0,0,0,5,10,0,0,0,0,0,0,12,4,8,0,0,0,0,4,12,6,13,0,0,0,0,10,8,8,10,0,0,0,7,16,13,16,15,2,0,0,6,10,11,16,8,0,0,0,0,0,4,13,0,0,0,4 +0,0,11,10,7,4,0,0,0,2,15,8,16,16,2,0,0,9,11,0,8,16,1,0,0,5,16,9,15,16,6,0,0,0,5,10,4,12,8,0,0,0,0,0,0,10,9,0,0,0,6,7,10,15,5,0,0,1,12,15,12,3,0,0,9 +0,0,8,14,8,0,0,0,0,6,15,12,15,4,0,0,0,6,8,0,11,8,0,0,0,0,0,14,16,8,0,0,0,0,1,11,10,15,6,0,0,0,0,0,0,8,12,0,0,0,7,8,6,13,14,0,0,0,12,14,16,13,1,0,3 +0,0,2,12,14,1,0,0,0,0,13,16,12,12,0,0,0,1,16,5,0,15,4,0,0,3,16,3,0,11,7,0,0,6,12,0,0,12,5,0,0,4,16,3,2,15,7,0,0,0,15,12,14,13,1,0,0,0,4,15,13,2,0,0,0 +0,0,0,5,15,0,0,0,0,0,1,15,11,0,0,0,0,0,3,16,16,7,0,0,0,0,10,15,13,12,0,0,0,1,15,14,15,16,9,0,0,8,16,16,16,14,6,0,0,2,10,10,16,7,0,0,0,0,0,6,15,2,0,0,4 +0,0,3,14,12,1,0,0,0,0,14,15,13,12,0,0,0,0,16,2,0,14,2,0,0,1,14,0,0,10,6,0,0,2,15,0,0,13,5,0,0,3,16,1,1,15,6,0,0,0,13,10,13,15,1,0,0,0,2,12,14,6,0,0,0 +0,8,16,16,16,16,9,0,0,5,12,12,14,16,9,0,0,0,0,2,15,13,0,0,0,0,0,9,16,5,0,0,0,0,2,16,13,0,0,0,0,0,11,16,4,0,0,0,0,7,16,15,0,0,0,0,0,6,16,11,0,0,0,0,7 +0,0,0,11,15,4,0,0,0,3,7,16,16,8,0,0,0,12,16,16,16,5,0,0,0,3,8,13,16,5,0,0,0,0,0,8,16,10,0,0,0,0,0,8,16,14,1,0,0,0,0,10,16,16,2,0,0,0,0,6,14,12,5,0,1 +0,0,9,12,14,7,0,0,0,0,12,14,9,8,0,0,0,0,12,8,1,0,0,0,0,0,15,16,14,1,0,0,0,0,6,8,10,10,0,0,0,0,0,0,8,11,0,0,0,0,7,13,16,8,0,0,0,0,8,15,8,0,0,0,5 +0,0,5,16,14,3,0,0,0,0,2,14,16,10,0,0,0,0,0,12,16,8,0,0,0,0,0,14,16,6,0,0,0,0,1,16,16,3,0,0,0,0,3,15,16,0,0,0,0,0,0,15,16,9,0,0,0,0,0,13,16,11,0,0,1 +0,2,13,10,0,0,0,0,0,8,15,14,7,0,0,0,0,8,5,4,12,0,0,0,0,2,3,2,15,0,0,0,0,0,0,4,9,0,0,0,0,0,0,11,9,0,0,0,0,0,9,16,14,12,10,0,0,0,16,13,12,14,11,0,2 +0,0,13,14,2,0,0,0,0,5,16,15,10,0,0,0,0,4,8,0,16,0,0,0,0,0,2,3,12,0,0,0,0,0,0,5,11,0,0,0,0,0,2,13,6,0,0,0,0,0,13,16,14,15,14,0,0,0,8,8,8,12,15,0,2 +0,0,0,0,13,8,0,0,0,0,0,2,16,6,0,0,0,0,0,7,16,7,3,0,0,0,1,13,12,15,7,0,0,3,12,14,5,16,3,0,2,15,16,16,16,16,1,0,5,12,13,16,16,15,0,0,0,0,0,0,15,6,0,0,4 +0,0,0,1,12,1,0,0,0,0,0,4,15,0,0,0,0,0,0,6,11,8,2,0,0,0,1,13,4,16,1,0,0,4,12,10,9,14,0,0,0,15,16,16,16,12,0,0,1,8,7,7,15,9,0,0,0,0,0,0,16,5,0,0,4 +0,0,4,16,15,7,0,0,0,0,6,16,10,13,2,0,0,0,2,15,12,15,6,0,0,0,3,14,15,13,2,0,0,2,15,15,15,9,0,0,0,6,16,2,7,13,0,0,0,3,16,11,8,16,4,0,0,0,4,12,16,13,1,0,8 +0,0,10,16,10,3,0,0,0,1,16,9,13,12,0,0,0,1,13,2,7,16,3,0,0,0,12,16,16,16,8,0,0,0,1,4,2,8,8,0,0,0,0,0,0,7,9,0,0,1,14,9,8,14,6,0,0,0,8,13,13,7,0,0,9 +0,2,12,13,1,0,0,0,0,10,15,14,11,0,0,0,0,12,9,5,12,0,0,0,0,4,5,4,16,0,0,0,0,0,0,9,9,0,0,0,0,0,1,13,8,0,1,0,0,1,16,16,14,14,11,0,0,2,13,12,12,12,6,0,2 +0,0,1,14,4,0,0,0,0,0,7,14,2,0,0,0,0,0,10,14,0,0,0,0,0,0,13,9,0,0,0,0,0,0,14,10,8,1,0,0,0,0,14,16,16,14,4,0,0,0,12,16,9,14,15,0,0,0,1,11,15,13,11,2,6 +0,0,15,16,12,4,0,0,0,0,11,16,11,16,4,0,0,0,4,16,11,16,6,0,0,0,3,16,16,14,1,0,0,0,12,14,16,5,0,0,0,3,16,1,14,12,0,0,0,6,15,4,11,13,0,0,0,1,10,16,11,2,0,0,8 +0,0,0,3,14,5,0,0,0,0,0,6,16,13,1,0,0,0,0,8,16,13,1,0,0,0,0,9,16,11,0,0,0,5,14,16,16,12,0,0,0,0,2,9,16,16,4,0,0,0,0,5,16,16,8,0,0,0,0,3,13,14,5,0,1 +0,0,4,15,0,0,0,0,0,0,13,5,0,0,0,0,0,0,16,0,0,0,0,0,0,4,13,0,0,0,0,0,0,7,15,8,9,3,0,0,0,6,16,12,13,16,4,0,0,0,15,8,2,14,7,0,0,0,4,15,13,6,0,0,6 +0,0,3,12,16,8,0,0,0,0,12,12,15,12,0,0,0,0,0,0,7,12,0,0,0,0,0,0,13,7,0,0,0,0,4,15,16,9,1,0,0,0,5,15,16,16,3,0,0,0,0,14,14,5,0,0,0,0,4,15,3,0,0,0,7 +0,0,0,14,5,0,0,0,0,0,9,13,1,0,0,0,0,0,15,6,0,0,0,0,0,3,16,3,0,0,0,0,0,7,12,8,16,8,0,0,0,4,16,15,9,15,4,0,0,0,12,12,4,10,12,0,0,0,1,12,15,16,6,0,6 +0,0,14,15,16,7,0,0,0,4,15,8,12,14,0,0,0,0,2,4,15,9,0,0,0,0,0,8,16,8,0,0,0,0,0,1,11,16,3,0,0,0,0,0,0,10,10,0,0,1,11,11,8,14,11,0,0,1,10,12,14,13,4,0,3 +0,0,6,11,2,0,0,0,0,1,16,12,14,3,0,0,0,0,13,3,11,15,0,0,0,0,6,16,15,16,5,0,0,0,0,4,8,10,10,0,0,0,0,0,0,1,15,0,0,0,1,1,2,7,15,5,0,0,4,12,16,16,13,2,9 +0,0,7,11,12,11,3,0,0,0,15,16,7,10,11,0,0,0,10,12,5,13,9,0,0,0,3,16,16,10,1,0,0,0,8,13,13,3,0,0,0,0,15,0,11,5,0,0,0,1,13,0,10,9,0,0,0,0,10,16,12,1,0,0,8 +0,0,0,0,13,7,0,0,0,0,0,0,14,7,0,0,0,0,0,3,15,2,0,0,0,0,1,13,7,11,2,0,0,5,14,16,10,16,2,0,0,15,16,16,16,15,1,0,0,0,0,0,10,12,0,0,0,0,0,0,15,7,0,0,4 +0,1,9,13,11,4,0,0,0,2,16,12,12,15,0,0,0,0,14,13,13,11,0,0,0,0,12,16,13,0,0,0,0,0,14,16,11,0,0,0,0,6,13,7,16,4,0,0,0,7,14,1,13,15,1,0,0,1,13,15,12,9,0,0,8 +0,0,5,16,15,6,0,0,0,0,9,15,13,10,0,0,0,0,11,15,11,4,0,0,0,0,2,12,13,16,2,0,0,0,0,0,0,7,10,0,0,0,0,0,0,4,13,0,0,0,13,11,8,14,11,0,0,0,4,11,15,15,4,0,5 +0,1,13,12,4,0,0,0,0,1,16,12,16,6,0,0,0,0,16,7,14,10,0,0,0,0,9,14,16,13,0,0,0,0,0,7,9,15,1,0,0,0,0,0,0,10,8,0,0,0,4,5,4,10,15,0,0,0,6,13,16,14,6,0,9 +0,0,11,15,13,1,0,0,0,5,15,9,15,12,0,0,0,8,12,0,12,16,0,0,0,5,16,9,15,16,0,0,0,0,4,10,13,16,2,0,0,0,0,0,1,16,6,0,0,0,1,5,8,16,7,0,0,0,12,12,10,7,1,0,9 +0,0,9,15,15,3,0,0,0,0,11,9,16,10,0,0,0,0,0,0,14,10,0,0,0,0,2,5,16,6,0,0,0,1,15,16,16,13,5,0,0,1,10,16,16,15,5,0,0,0,5,16,11,2,0,0,0,0,9,16,5,0,0,0,7 +0,0,6,13,14,6,0,0,0,0,16,13,6,16,3,0,0,0,13,10,5,16,2,0,0,0,4,16,16,12,1,0,0,0,12,16,13,0,0,0,0,2,16,7,14,9,0,0,0,3,16,5,9,14,0,0,0,0,8,15,16,8,0,0,8 +0,0,3,10,14,15,3,0,0,0,15,16,14,11,1,0,0,3,16,11,8,2,0,0,0,3,16,16,16,13,0,0,0,0,0,0,0,15,7,0,0,0,1,0,0,10,7,0,0,0,14,13,9,16,4,0,0,0,4,13,15,7,0,0,5 +0,0,11,16,6,0,0,0,0,9,15,14,16,0,0,0,0,10,12,3,16,2,0,0,0,5,6,0,15,2,0,0,0,0,0,7,11,0,0,0,0,0,4,16,8,4,1,0,0,0,11,16,16,16,9,0,0,0,8,12,10,13,7,0,2 +0,1,10,13,15,11,1,0,0,4,16,12,8,7,0,0,0,5,16,4,5,1,0,0,0,3,16,16,15,15,2,0,0,0,0,1,5,14,5,0,0,0,0,0,0,15,5,0,0,7,11,6,6,16,4,0,0,2,10,14,14,8,0,0,5 +0,0,9,16,10,0,0,0,0,0,16,13,16,5,0,0,0,3,16,4,7,14,0,0,0,4,15,3,0,12,7,0,0,7,12,0,0,10,8,0,0,5,13,0,4,15,3,0,0,2,16,13,16,9,0,0,0,0,8,13,9,1,0,0,0 +0,0,7,16,12,1,0,0,0,0,16,11,13,11,0,0,0,3,16,2,4,14,0,0,0,7,13,0,0,13,1,0,0,4,15,0,0,12,6,0,0,2,16,4,0,10,7,0,0,1,15,10,8,14,2,0,0,0,5,16,15,5,0,0,0 +0,0,12,16,13,11,3,0,0,4,16,6,12,16,5,0,0,0,15,11,14,16,6,0,0,0,6,8,8,13,8,0,0,0,0,0,0,8,8,0,0,0,0,0,0,9,8,0,0,6,13,5,6,16,3,0,0,0,9,13,12,6,0,0,9 +0,0,3,16,6,0,0,0,0,0,10,14,4,0,0,0,0,0,15,10,0,0,0,0,0,2,16,4,0,0,0,0,0,5,16,15,14,6,0,0,0,3,16,16,12,16,4,0,0,0,15,14,6,13,12,0,0,0,3,10,13,10,4,0,6 +0,0,2,11,16,15,5,0,0,0,13,14,11,16,6,0,0,0,2,0,1,15,1,0,0,0,0,2,10,11,0,0,0,0,5,16,16,16,6,0,0,0,4,12,15,9,2,0,0,0,1,14,9,0,0,0,0,0,3,15,4,0,0,0,7 +0,1,11,14,12,3,0,0,0,7,16,13,16,15,0,0,0,8,16,5,14,16,2,0,0,6,16,10,15,16,6,0,0,0,5,8,12,16,6,0,0,0,0,0,2,16,11,0,0,0,5,8,7,16,9,0,0,0,8,16,15,10,1,0,9 +0,0,5,13,9,7,0,0,0,0,14,16,16,16,6,0,0,2,14,7,6,10,10,0,0,5,11,0,0,8,8,0,0,4,12,0,0,8,8,0,0,4,15,1,0,9,7,0,0,0,14,9,6,16,2,0,0,0,5,15,16,7,0,0,0 +0,0,1,12,0,0,0,0,0,0,4,14,0,0,0,0,0,0,11,9,0,0,0,0,0,0,13,10,2,0,0,0,0,4,16,16,16,13,1,0,0,1,16,10,4,13,11,0,0,0,8,13,2,11,11,0,0,0,0,11,16,14,3,0,6 +0,0,2,13,1,0,0,0,0,0,7,16,6,0,0,0,0,0,12,16,3,0,0,0,0,0,9,16,5,0,0,0,0,0,15,16,16,15,5,0,0,0,13,16,10,14,15,0,0,0,10,14,9,16,14,0,0,0,2,11,15,15,4,0,6 +0,0,7,14,12,5,0,0,0,0,10,16,16,12,0,0,0,0,10,16,16,8,0,0,0,0,10,16,16,6,0,0,0,0,9,16,16,3,0,0,0,0,12,16,16,2,0,0,0,0,12,16,16,11,0,0,0,0,14,16,13,8,0,0,1 +0,0,2,9,15,12,0,0,0,1,13,13,10,16,1,0,0,0,10,2,8,16,0,0,0,0,0,4,16,14,1,0,0,0,0,0,4,15,4,0,0,0,0,0,0,15,5,0,0,0,0,12,15,16,1,0,0,0,0,9,13,7,0,0,3 +0,0,0,1,12,12,0,0,0,0,0,2,16,16,2,0,0,0,0,4,16,16,0,0,0,6,16,16,16,13,0,0,0,1,12,14,16,12,0,0,0,0,0,0,16,15,0,0,0,0,0,0,15,16,2,0,0,0,0,0,8,13,5,0,1 +0,0,9,11,0,0,0,0,0,7,16,16,8,0,0,0,0,8,11,7,11,0,0,0,0,2,4,8,11,0,0,0,0,0,1,14,3,0,0,0,0,0,9,12,0,0,0,0,0,0,14,16,15,14,7,0,0,0,8,12,12,15,10,0,2 +0,0,6,15,2,0,0,0,0,0,14,11,0,0,0,0,0,0,16,8,0,0,0,0,0,4,16,4,0,0,0,0,0,6,16,11,8,3,0,0,0,7,16,16,14,15,3,0,0,0,16,13,8,16,7,0,0,0,7,16,16,10,1,0,6 +0,0,15,16,16,14,0,0,0,0,3,4,13,13,0,0,0,0,0,0,14,10,0,0,0,0,5,13,16,5,0,0,0,0,10,16,16,16,8,0,0,0,2,16,7,7,1,0,0,0,8,16,0,0,0,0,0,0,14,11,0,0,0,0,7 +0,0,0,1,15,11,0,0,0,0,0,6,16,6,0,0,0,0,0,13,14,1,0,0,0,0,8,15,5,9,4,0,0,7,16,8,6,16,7,0,0,13,16,16,16,16,5,0,0,0,0,7,15,15,0,0,0,0,0,1,16,10,0,0,4 +0,0,6,11,0,0,0,0,0,0,8,16,1,0,0,0,0,0,11,16,1,0,0,0,0,0,14,13,2,0,0,0,0,0,13,16,16,13,3,0,0,0,15,14,8,14,12,0,0,0,14,11,7,15,10,0,0,0,4,13,16,10,2,0,6 +0,0,6,15,11,8,3,0,0,2,16,16,16,16,11,0,0,0,15,11,7,16,8,0,0,0,8,16,15,4,0,0,0,0,5,16,7,0,0,0,0,0,15,15,13,0,0,0,0,1,16,11,16,2,0,0,0,0,12,16,12,0,0,0,8 +0,0,13,12,10,12,8,0,0,2,16,16,16,14,5,0,0,3,16,5,2,0,0,0,0,7,16,10,7,0,0,0,0,5,12,12,16,15,1,0,0,0,0,0,7,16,4,0,0,0,4,6,7,15,3,0,0,0,10,16,16,8,0,0,5 +0,0,8,16,14,4,0,0,0,5,16,11,12,13,1,0,0,8,16,0,8,16,0,0,0,6,16,7,8,16,5,0,0,0,8,12,12,16,8,0,0,0,0,0,0,12,9,0,0,0,5,10,1,15,5,0,0,0,10,16,14,12,2,0,9 +0,0,0,7,16,0,0,0,0,0,0,16,11,0,0,0,0,0,6,16,6,0,0,0,0,0,14,13,0,11,7,0,0,11,16,2,8,16,1,0,5,16,16,16,16,14,0,0,0,4,9,14,16,7,0,0,0,0,0,9,15,1,0,0,4 +0,0,2,14,9,2,0,0,0,0,10,16,16,13,0,0,0,0,12,6,3,14,4,0,0,1,13,2,0,6,8,0,0,7,14,0,0,7,7,0,0,2,16,2,0,12,4,0,0,0,11,12,12,15,0,0,0,0,2,14,14,2,0,0,0 +0,1,16,16,15,3,0,0,0,0,7,6,16,10,0,0,0,0,0,1,16,8,0,0,0,0,6,15,15,2,0,0,0,0,7,16,16,11,3,0,0,0,5,16,14,16,7,0,0,0,10,12,1,4,1,0,0,0,16,6,0,0,0,0,7 +0,0,0,2,12,1,0,0,0,0,0,10,13,0,0,0,0,0,0,15,4,0,0,0,0,0,8,12,0,7,1,0,0,5,16,2,7,16,4,0,0,15,16,13,16,11,0,0,1,6,8,9,16,7,0,0,0,0,0,3,15,1,0,0,4 +0,0,14,16,15,10,0,0,0,0,8,12,14,16,0,0,0,0,0,0,12,12,0,0,0,0,7,12,16,9,0,0,0,0,16,16,16,16,7,0,0,0,2,16,12,10,3,0,0,0,8,16,1,0,0,0,0,1,16,11,0,0,0,0,7 +0,0,0,4,8,0,0,0,0,0,0,11,8,0,0,0,0,0,0,13,6,0,0,0,0,0,2,15,2,3,0,0,0,0,11,10,4,16,2,0,0,11,14,0,9,16,2,0,0,9,15,12,15,16,0,0,0,0,2,6,15,6,0,0,4 +0,0,11,16,16,9,0,0,0,0,10,8,12,11,0,0,0,0,0,0,12,6,0,0,0,0,8,13,16,6,0,0,0,0,7,14,14,13,6,0,0,0,0,13,3,0,0,0,0,0,6,15,0,0,0,0,0,0,11,10,0,0,0,0,7 +0,0,5,12,11,4,0,0,0,3,16,16,16,16,1,0,0,9,13,1,14,16,1,0,0,4,16,4,13,16,3,0,0,0,12,16,16,16,4,0,0,0,0,0,1,13,7,0,0,0,3,8,6,14,8,0,0,0,5,16,16,14,3,0,9 +0,0,4,12,14,12,3,0,0,0,15,14,10,16,3,0,0,2,16,8,8,5,0,0,0,7,16,16,16,15,2,0,0,3,7,2,2,14,5,0,0,0,0,0,2,15,3,0,0,0,3,11,14,13,0,0,0,0,8,15,11,4,0,0,5 +0,1,13,16,16,15,4,0,0,0,7,8,10,16,4,0,0,0,0,0,9,16,0,0,0,0,8,12,16,16,7,0,0,0,12,15,16,14,10,0,0,0,0,14,8,0,0,0,0,0,8,14,2,0,0,0,0,2,16,9,0,0,0,0,7 +0,1,9,15,12,5,0,0,0,10,16,14,16,16,1,0,0,2,14,12,15,14,0,0,0,0,7,16,15,5,0,0,0,0,7,16,12,0,0,0,0,0,14,16,13,0,0,0,0,0,16,15,16,4,0,0,0,0,12,16,13,2,0,0,8 +0,0,5,16,16,7,0,0,0,1,9,16,16,8,0,0,0,0,4,16,16,12,0,0,0,0,8,16,16,8,0,0,0,0,5,16,16,12,0,0,0,0,7,16,16,13,0,0,0,0,8,16,16,16,4,0,0,0,7,13,15,10,2,0,1 +0,1,12,16,16,16,6,0,0,0,7,8,6,16,13,0,0,0,0,0,3,16,7,0,0,0,3,7,11,14,1,0,0,0,10,16,16,15,3,0,0,0,1,15,15,15,3,0,0,0,7,16,6,0,0,0,0,0,15,13,0,0,0,0,7 +0,0,9,11,4,1,0,0,0,1,16,16,16,1,0,0,0,0,16,16,16,0,0,0,0,1,15,16,16,4,0,0,0,0,14,16,16,4,0,0,0,1,16,16,16,7,0,0,0,0,16,16,16,8,0,0,0,0,6,12,12,7,2,0,1 +0,0,7,16,16,16,8,0,0,0,9,7,4,14,11,0,0,0,0,2,5,16,3,0,0,0,1,15,16,16,5,0,0,0,0,13,16,15,9,0,0,0,0,10,10,3,1,0,0,0,3,16,3,0,0,0,0,0,9,12,0,0,0,0,7 +0,0,14,3,0,0,0,0,0,6,16,2,0,0,0,0,0,8,16,0,0,0,0,0,0,8,16,4,3,0,0,0,0,8,16,16,16,14,1,0,0,8,16,5,5,16,8,0,0,4,16,2,7,16,2,0,0,0,12,16,15,7,0,0,6 +0,0,14,11,1,0,0,0,0,11,14,15,8,0,0,0,0,6,6,1,16,0,0,0,0,0,0,2,15,0,0,0,0,0,0,5,15,0,0,0,0,0,0,8,11,0,0,0,0,0,8,16,13,8,7,0,0,0,11,16,15,13,15,1,2 +0,0,6,12,13,5,0,0,0,0,14,9,8,16,2,0,0,0,5,1,6,15,3,0,0,0,0,3,16,8,0,0,0,0,2,14,8,0,0,0,0,0,11,13,0,0,0,0,0,1,16,5,0,0,0,0,0,0,9,15,13,10,8,0,2 +0,0,0,13,14,0,0,0,0,0,5,16,7,0,0,0,0,0,8,13,0,0,0,0,0,0,12,11,0,0,0,0,0,1,16,16,14,2,0,0,0,0,10,11,10,14,0,0,0,0,7,13,9,15,0,0,0,0,1,11,15,8,0,0,6 +0,4,12,13,16,16,4,0,0,12,16,16,11,8,5,0,0,16,13,5,0,0,0,0,0,8,14,0,0,0,0,0,0,1,15,6,0,0,0,0,0,0,7,15,0,0,0,0,0,0,8,16,0,0,0,0,0,3,16,12,0,0,0,0,5 +0,0,0,9,12,0,0,0,0,0,4,16,6,0,0,0,0,3,15,10,0,10,7,0,0,10,14,0,6,16,4,0,0,10,15,12,14,13,0,0,0,0,5,9,16,7,0,0,0,0,0,5,16,2,0,0,0,0,0,9,12,0,0,0,4 +0,0,0,16,14,1,0,0,0,0,7,16,15,2,0,0,0,9,16,16,11,0,0,0,1,15,15,16,10,0,0,0,0,2,3,16,9,0,0,0,0,0,0,16,9,0,0,0,0,0,2,16,6,0,0,0,0,0,0,16,8,0,0,0,1 +0,1,11,16,16,4,0,0,0,8,12,4,14,8,0,0,0,5,2,0,12,8,0,0,0,0,1,7,16,11,1,0,0,0,10,16,16,16,11,0,0,0,0,14,8,0,1,0,0,0,6,15,1,0,0,0,0,1,15,4,0,0,0,0,7 +0,0,7,13,16,11,4,0,0,1,16,5,2,13,12,0,0,4,14,0,4,15,4,0,0,2,15,13,16,8,0,0,0,0,1,8,15,1,0,0,0,0,1,13,5,0,0,0,0,0,9,10,0,0,0,0,0,0,12,6,0,0,0,0,9 +0,0,0,4,16,9,0,0,0,0,0,11,16,9,0,0,0,0,6,16,16,3,0,0,0,7,15,16,16,2,0,0,0,9,16,13,15,0,0,0,0,0,0,10,13,0,0,0,0,0,0,10,15,0,0,0,0,0,0,6,16,6,0,0,1 +0,3,12,16,16,16,4,0,0,8,11,6,4,12,15,0,0,1,0,0,6,15,10,0,0,0,0,7,16,7,0,0,0,0,0,10,14,1,0,0,0,0,0,1,15,9,0,0,0,0,6,2,13,12,0,0,0,2,16,16,14,3,0,0,3 +0,0,2,15,12,0,0,0,0,0,10,15,3,0,0,0,0,8,16,4,0,13,7,0,0,10,16,1,2,16,10,0,0,9,16,12,14,14,1,0,0,0,6,12,16,7,0,0,0,0,0,14,13,0,0,0,0,0,2,16,8,0,0,0,4 +0,0,4,14,11,3,0,0,0,1,14,15,16,14,0,0,0,3,16,2,3,11,4,0,0,6,11,0,0,6,6,0,0,7,13,0,0,8,7,0,0,2,15,0,0,14,3,0,0,0,12,6,11,13,0,0,0,0,4,12,13,2,0,0,0 +0,0,5,14,1,0,0,0,0,0,10,14,0,0,0,0,0,1,16,6,1,9,3,0,0,8,16,0,11,15,1,0,0,10,16,8,16,6,0,0,0,0,6,14,16,4,0,0,0,0,0,13,16,2,0,0,0,0,2,16,9,1,0,0,4 +0,0,0,12,12,1,0,0,0,0,6,16,8,0,0,0,0,0,11,11,0,0,0,0,0,0,13,6,0,0,0,0,0,0,15,16,16,15,5,0,0,0,14,14,4,5,15,1,0,0,8,14,2,6,16,3,0,0,0,8,13,15,9,0,6 +0,2,13,16,16,13,1,0,0,14,15,8,10,16,4,0,0,5,2,0,6,16,4,0,0,0,8,16,16,16,9,0,0,0,5,14,16,9,2,0,0,0,2,16,11,0,0,0,0,0,8,16,3,0,0,0,0,2,16,9,0,0,0,0,7 +0,0,5,9,16,13,4,0,0,1,15,8,5,14,12,0,0,0,4,0,4,14,8,0,0,0,0,2,14,10,0,0,0,0,0,12,14,0,0,0,0,0,7,14,3,0,0,0,0,0,15,10,0,0,0,0,0,0,4,12,12,6,0,0,2 +0,0,11,14,4,0,0,0,0,6,15,14,16,5,0,0,0,11,10,12,16,12,0,0,0,2,12,10,3,14,6,0,0,0,0,0,0,12,8,0,0,0,0,0,0,11,10,0,0,0,0,1,7,14,4,0,0,0,12,16,12,4,0,0,9 +0,0,4,14,15,6,0,0,0,1,15,9,8,15,0,0,0,5,12,0,12,15,3,0,0,3,16,16,12,16,6,0,0,0,1,3,0,13,8,0,0,0,0,0,2,16,5,0,0,0,0,4,14,11,0,0,0,0,10,16,9,0,0,0,9 +0,2,15,16,15,7,0,0,0,8,16,14,12,14,3,0,0,6,16,5,0,0,0,0,0,2,15,11,0,0,0,0,0,0,5,16,6,0,0,0,0,0,0,12,15,0,0,0,0,0,6,14,14,0,0,0,0,2,13,16,4,0,0,0,5 +0,0,0,9,13,0,0,0,0,0,5,16,13,4,0,0,0,0,12,12,0,0,0,0,0,0,14,5,0,0,0,0,0,0,16,11,15,11,0,0,0,0,13,16,13,15,8,0,0,0,7,16,5,13,10,0,0,0,0,9,13,13,3,0,6 +0,0,0,0,15,5,0,0,0,0,0,5,16,9,0,0,0,0,1,15,16,5,0,0,0,6,13,16,16,7,0,0,0,7,16,10,16,6,0,0,0,0,0,2,16,6,0,0,0,0,0,2,16,6,0,0,0,0,0,1,15,12,0,0,1 +0,0,0,7,12,1,0,0,0,0,3,16,12,0,0,0,0,0,8,13,0,0,0,0,0,0,14,9,1,0,0,0,0,0,14,16,16,16,5,0,0,0,12,16,11,6,16,1,0,0,6,15,2,7,15,2,0,0,0,8,13,12,6,0,6 +0,0,10,15,10,5,0,0,0,0,14,16,16,16,3,0,0,0,13,14,1,1,0,0,0,0,5,14,2,0,0,0,0,0,0,13,8,0,0,0,0,0,0,7,14,1,0,0,0,0,9,13,16,2,0,0,0,0,12,16,15,1,0,0,5 +0,0,1,10,9,0,0,0,0,0,7,16,14,0,0,0,0,4,14,16,15,0,0,0,0,12,14,16,16,0,0,0,0,0,0,15,16,2,0,0,0,0,0,14,16,2,0,0,0,0,0,12,15,0,0,0,0,0,0,6,16,2,0,0,1 +0,3,13,16,16,12,0,0,0,13,16,15,8,10,3,0,0,12,16,2,0,0,0,0,0,3,15,9,0,0,0,0,0,0,7,16,2,0,0,0,0,0,1,16,8,0,0,0,0,0,10,16,5,0,0,0,0,3,16,13,0,0,0,0,5 +0,0,0,9,12,0,0,0,0,0,1,14,12,0,0,0,0,0,11,13,0,6,8,0,0,4,16,4,2,15,7,0,0,10,14,4,11,14,1,0,0,3,14,16,16,6,0,0,0,0,0,10,14,0,0,0,0,0,0,10,10,0,0,0,4 +0,0,2,10,16,13,2,0,0,0,13,10,4,13,9,0,0,1,16,1,0,12,12,0,0,0,13,16,14,16,9,0,0,0,0,0,1,11,8,0,0,0,0,0,0,15,4,0,0,0,0,0,4,16,3,0,0,0,3,13,15,6,0,0,9 +0,0,8,16,6,1,0,0,0,0,13,16,16,15,0,0,0,3,16,2,0,13,3,0,0,6,14,0,0,11,6,0,0,3,13,0,0,13,5,0,0,0,16,0,6,15,1,0,0,0,13,10,15,9,0,0,0,0,4,12,11,0,0,0,0 +0,1,10,12,14,9,0,0,0,11,11,5,5,16,4,0,0,4,3,0,2,16,2,0,0,0,0,0,12,9,0,0,0,0,1,13,10,0,0,0,0,0,9,13,0,0,0,0,0,2,16,4,0,0,0,0,0,1,13,15,12,2,0,0,2 +0,0,6,14,6,1,0,0,0,0,14,16,14,9,0,0,0,3,16,3,1,15,1,0,0,4,13,0,0,11,5,0,0,2,14,0,0,11,8,0,0,2,16,2,2,16,4,0,0,0,12,9,12,12,0,0,0,0,4,13,11,1,0,0,0 +0,4,16,16,15,5,0,0,0,7,15,13,13,14,5,0,0,2,15,8,0,0,1,0,0,0,6,15,4,0,0,0,0,0,0,15,7,0,0,0,0,0,0,11,12,0,0,0,0,0,2,15,9,0,0,0,0,1,16,15,3,0,0,0,5 +0,0,0,14,12,0,0,0,0,0,1,16,16,2,0,0,0,0,0,16,15,0,0,0,0,0,1,16,15,0,0,0,0,0,0,15,16,0,0,0,0,0,1,16,15,0,0,0,0,0,1,16,14,0,0,0,0,0,0,11,16,3,0,0,1 +0,3,13,15,16,6,0,0,0,15,16,13,9,16,5,0,0,11,16,2,0,4,3,0,0,2,15,9,0,0,0,0,0,0,7,16,1,0,0,0,0,0,3,15,5,0,0,0,0,0,6,16,5,0,0,0,0,4,16,10,0,0,0,0,5 +0,0,5,16,1,0,0,0,0,0,11,11,0,0,6,5,0,0,14,7,0,2,15,6,0,3,16,10,1,12,12,0,0,0,14,16,16,16,4,0,0,0,1,11,16,7,0,0,0,0,0,13,10,0,0,0,0,0,7,15,1,0,0,0,4 +0,0,11,13,12,7,0,0,0,2,15,4,5,16,4,0,0,0,16,5,13,11,0,0,0,0,7,16,10,1,0,0,0,0,8,16,4,0,0,0,0,0,15,5,11,0,0,0,0,3,13,4,12,0,0,0,0,1,11,16,8,0,0,0,8 +0,0,0,8,16,13,1,0,0,0,4,16,11,13,9,0,0,6,15,10,0,11,11,0,0,8,16,13,14,16,9,0,0,1,9,8,11,16,4,0,0,0,0,0,12,12,0,0,0,0,0,6,16,2,0,0,0,0,0,11,10,0,0,0,9 +0,0,2,8,12,13,2,0,0,2,16,15,6,8,8,0,0,7,11,5,2,13,7,0,0,6,15,13,15,15,1,0,0,0,3,2,9,6,0,0,0,0,0,1,14,1,0,0,0,0,0,7,10,0,0,0,0,0,0,13,3,0,0,0,9 +0,2,15,16,13,2,0,0,0,3,15,10,14,9,0,0,0,0,0,0,14,10,0,0,0,0,0,10,16,2,0,0,0,0,2,16,10,0,0,0,0,0,12,13,1,0,0,0,0,2,16,12,8,10,13,2,0,2,13,16,16,16,16,3,2 +0,0,6,15,13,1,0,0,0,1,16,10,5,12,0,0,0,8,16,5,0,9,1,0,0,7,15,0,0,7,5,0,0,5,14,0,0,5,9,0,0,2,14,0,1,12,6,0,0,0,10,11,15,16,3,0,0,0,2,11,11,4,0,0,0 +0,0,1,16,9,0,0,0,0,0,3,16,16,2,0,0,0,0,5,16,15,1,0,0,0,0,4,16,11,0,0,0,0,0,3,16,15,0,0,0,0,0,1,16,12,0,0,0,0,0,2,16,14,0,0,0,0,0,0,14,9,0,0,0,1 +0,0,3,14,10,0,0,0,0,0,10,13,11,9,0,0,0,0,15,9,0,9,1,0,0,0,16,6,0,6,5,0,0,0,15,4,0,10,6,0,0,0,14,0,2,16,3,0,0,0,12,11,15,12,0,0,0,0,2,13,10,1,0,0,0 +0,0,12,11,0,0,0,0,0,8,16,4,0,5,3,0,0,11,16,0,4,16,9,0,0,11,16,8,13,14,1,0,0,2,13,16,16,8,0,0,0,0,1,15,10,0,0,0,0,0,5,16,3,0,0,0,0,0,12,12,0,0,0,0,4 +0,0,0,16,7,0,0,0,0,0,0,15,16,3,0,0,0,0,0,14,16,4,0,0,0,0,0,16,16,2,0,0,0,0,0,15,16,2,0,0,0,0,3,16,14,0,0,0,0,0,0,14,14,0,0,0,0,0,0,13,12,0,0,0,1 +0,0,2,13,9,1,0,0,0,0,13,14,10,10,0,0,0,0,14,3,0,8,0,0,0,0,15,9,0,5,3,0,0,0,16,8,0,7,5,0,0,0,16,7,2,16,2,0,0,0,11,9,13,14,1,0,0,0,2,15,13,1,0,0,0 +0,0,5,15,13,1,0,0,0,0,12,16,12,10,0,0,0,0,14,8,0,13,3,0,0,0,16,9,0,10,9,0,0,0,16,13,0,8,9,0,0,1,16,6,2,16,9,0,0,0,13,12,15,15,2,0,0,0,6,16,16,7,0,0,0 +0,0,11,14,10,2,0,0,0,0,12,13,13,15,0,0,0,0,3,11,12,12,1,0,0,0,0,15,12,0,0,0,0,0,5,15,10,0,0,0,0,0,12,4,11,0,0,0,0,0,14,1,12,2,0,0,0,0,10,16,10,0,0,0,8 +0,0,4,10,13,3,0,0,0,0,16,15,14,11,0,0,0,0,5,1,10,12,0,0,0,0,0,2,16,5,0,0,0,0,0,11,13,0,0,0,0,0,4,16,3,0,0,0,0,0,9,14,4,4,4,1,0,0,4,14,16,15,12,5,2 +0,0,0,10,13,5,0,0,0,0,11,12,8,15,2,0,0,6,16,1,1,14,4,0,0,6,16,8,13,16,5,0,0,2,13,12,16,12,0,0,0,0,0,0,14,6,0,0,0,0,0,4,14,1,0,0,0,0,0,11,5,0,0,0,9 +0,0,11,16,16,8,0,0,0,0,11,12,8,13,0,0,0,0,7,10,0,0,0,0,0,0,3,13,0,0,0,0,0,0,0,14,2,0,0,0,0,0,0,13,6,0,0,0,0,0,6,12,10,0,0,0,0,0,11,16,9,0,0,0,5 +0,0,0,12,1,0,0,0,0,0,4,16,3,0,0,0,0,0,7,14,0,0,0,0,0,0,9,10,4,2,0,0,0,0,12,16,16,15,5,0,0,0,10,9,0,0,12,1,0,0,7,11,1,5,15,2,0,0,0,11,16,15,8,0,6 +0,0,12,12,0,0,5,1,0,2,16,9,0,4,16,5,0,1,16,13,5,13,12,0,0,0,7,16,16,16,6,0,0,0,0,3,16,9,0,0,0,0,1,12,12,0,0,0,0,0,7,16,1,0,0,0,0,0,12,7,0,0,0,0,4 +0,0,10,13,11,2,0,0,0,0,9,11,10,15,0,0,0,0,0,1,12,14,2,0,0,0,0,15,16,5,0,0,0,0,0,4,7,14,6,0,0,0,0,0,0,6,12,0,0,0,11,4,5,14,10,0,0,0,13,16,16,10,0,0,3 +0,0,11,16,16,16,9,0,0,0,3,8,8,15,12,0,0,0,0,0,4,16,6,0,0,3,8,8,12,14,0,0,0,10,16,16,16,15,1,0,0,1,4,11,15,2,0,0,0,0,1,15,8,0,0,0,0,0,10,13,1,0,0,0,7 +0,0,0,10,13,0,0,0,0,0,4,16,7,0,0,0,0,0,12,13,0,0,0,0,0,0,13,8,0,0,0,0,0,0,13,6,3,8,1,0,0,0,12,14,16,14,14,2,0,0,7,16,13,6,11,8,0,0,0,10,15,16,13,2,6 +0,0,12,16,12,16,3,0,0,0,14,14,8,8,1,0,0,0,7,15,1,0,0,0,0,0,1,14,11,0,0,0,0,0,0,6,16,1,0,0,0,0,0,0,12,11,0,0,0,2,7,6,14,13,0,0,0,0,12,16,16,6,0,0,5 +0,1,15,10,0,0,0,0,0,7,16,5,1,13,6,0,0,9,16,3,9,16,4,0,0,3,15,16,16,11,0,0,0,0,2,14,15,3,0,0,0,0,3,16,8,0,0,0,0,0,12,15,0,0,0,0,0,1,16,5,0,0,0,0,4 +0,0,2,16,7,0,0,0,0,0,5,16,16,2,0,0,0,0,4,16,16,1,0,0,0,0,3,16,16,0,0,0,0,0,1,16,16,0,0,0,0,0,3,16,16,0,0,0,0,0,4,16,16,0,0,0,0,0,0,14,16,0,0,0,1 +0,2,16,16,16,16,4,0,0,1,8,8,12,16,6,0,0,0,0,1,14,13,0,0,0,1,4,6,16,8,0,0,0,9,16,16,16,13,2,0,0,2,11,16,10,7,0,0,0,0,12,13,0,0,0,0,0,2,16,7,0,0,0,0,7 +0,0,8,15,11,1,0,0,0,1,14,14,14,10,0,0,0,3,16,3,0,9,3,0,0,5,14,0,0,6,6,0,0,5,13,0,0,7,7,0,0,4,12,0,0,13,6,0,0,1,16,13,16,12,0,0,0,0,7,16,12,1,0,0,0 +0,0,0,7,12,1,0,0,0,0,7,16,9,1,0,0,0,0,12,11,0,0,0,0,0,0,14,4,0,0,0,0,0,0,15,1,0,0,0,0,0,0,11,13,16,16,8,0,0,0,9,16,13,11,16,3,0,0,0,9,12,13,9,0,6 +0,0,2,16,11,1,0,0,0,0,0,16,16,2,0,0,0,0,1,16,16,6,0,0,0,0,0,15,16,3,0,0,0,0,1,15,16,2,0,0,0,0,2,16,15,1,0,0,0,0,1,16,14,0,0,0,0,0,2,16,8,0,0,0,1 +0,1,14,7,0,0,0,0,0,8,16,2,0,3,5,0,0,10,12,0,1,14,11,0,0,9,15,1,9,16,3,0,0,1,15,16,16,8,0,0,0,0,5,16,13,5,0,0,0,0,12,13,0,0,0,0,0,2,16,6,0,0,0,0,4 +0,2,16,16,16,7,0,0,0,0,7,8,11,16,3,0,0,0,0,2,12,16,4,0,0,0,1,16,16,6,0,0,0,0,2,12,16,10,0,0,0,0,0,0,4,16,4,0,0,0,10,8,11,16,7,0,0,1,14,16,16,12,1,0,3 +0,0,9,14,16,13,2,0,0,8,16,16,14,9,1,0,0,15,16,14,4,0,0,0,0,4,12,13,16,6,0,0,0,0,0,0,8,15,0,0,0,0,0,0,5,16,3,0,0,1,11,10,15,11,0,0,0,0,9,16,13,3,0,0,5 +0,2,7,16,15,8,0,0,0,5,16,11,15,12,0,0,0,0,11,15,13,0,0,0,0,0,3,16,1,0,0,0,0,0,9,16,1,0,0,0,0,0,13,10,5,0,0,0,0,0,14,9,8,0,0,0,0,0,6,16,5,0,0,0,8 +0,0,3,12,10,0,0,0,0,0,10,11,5,10,0,0,0,1,16,7,0,10,2,0,0,2,16,2,0,6,6,0,0,3,15,10,0,7,7,0,0,0,12,7,0,10,5,0,0,0,13,2,6,15,1,0,0,0,3,15,14,7,0,0,0 +0,0,0,12,16,13,0,0,0,0,6,16,10,5,0,0,1,14,2,6,0,0,0,0,0,12,11,0,0,0,0,0,0,3,14,11,1,0,0,0,0,0,3,13,11,0,0,0,0,0,0,5,16,6,0,0,0,0,0,13,16,6,0,0,5 +0,0,1,16,12,1,0,0,0,0,0,16,16,5,0,0,0,0,2,16,16,7,0,0,0,0,4,16,16,0,0,0,0,0,9,16,12,0,0,0,0,0,13,16,8,0,0,0,0,0,10,16,10,0,0,0,0,0,2,15,16,5,0,0,1 +0,0,3,12,15,7,0,0,0,0,4,9,3,12,0,0,0,0,13,5,11,5,0,0,0,0,3,16,11,0,0,0,0,0,2,16,9,0,0,0,0,0,10,6,11,6,0,0,0,0,13,3,7,12,0,0,0,0,4,15,13,8,0,0,8 +0,0,0,13,9,0,5,1,0,0,11,13,1,4,15,2,0,4,16,1,0,13,10,0,0,11,14,8,10,16,4,0,0,5,15,16,16,13,2,0,0,0,0,7,14,0,0,0,0,0,0,11,8,0,0,0,0,0,0,14,2,0,0,0,4 +0,0,4,14,11,1,0,0,0,0,9,8,10,9,0,0,0,0,7,0,14,4,0,0,0,0,9,11,14,1,0,0,0,0,1,15,8,0,0,0,0,0,3,13,12,5,0,0,0,0,8,8,4,14,0,0,0,0,3,15,16,13,0,0,8 +0,0,9,16,16,10,1,0,0,10,16,9,4,16,4,0,0,13,12,0,8,14,5,0,0,7,16,15,16,12,0,0,0,0,1,4,15,13,0,0,0,0,0,10,16,6,0,0,0,0,5,16,10,0,0,0,0,0,9,16,1,0,0,0,9 +0,0,2,13,16,16,9,0,0,0,12,10,4,7,12,0,0,1,15,8,2,6,4,0,0,0,4,10,16,9,0,0,0,0,0,0,2,16,0,0,0,0,0,0,9,11,0,0,0,0,0,5,15,4,0,0,0,0,0,13,9,0,0,0,9 +0,0,3,13,12,2,0,0,0,0,14,16,13,12,0,0,0,2,16,12,0,12,4,0,0,6,15,0,0,10,6,0,0,3,13,0,0,7,9,0,0,3,12,0,1,12,6,0,0,1,13,9,13,16,2,0,0,0,5,14,13,4,0,0,0 +0,0,0,9,9,0,0,0,0,0,7,15,3,0,0,0,0,0,11,8,0,0,0,0,0,0,14,4,2,3,0,0,0,0,12,12,16,12,10,0,0,0,9,14,8,0,8,4,0,0,4,9,1,2,14,5,0,0,0,6,11,14,8,0,6 +0,0,2,7,14,14,2,0,0,2,15,9,5,15,3,0,0,2,16,8,8,15,0,0,0,0,4,7,11,15,2,0,0,0,0,0,0,16,6,0,0,0,0,0,10,13,1,0,0,0,0,6,14,2,0,0,0,0,0,10,4,0,0,0,9 +0,0,0,11,12,1,0,0,0,0,2,14,16,3,0,0,0,0,5,16,11,0,0,0,0,0,7,16,8,0,0,0,0,0,6,16,8,0,0,0,0,0,10,16,7,0,0,0,0,0,7,16,9,4,0,0,0,0,0,10,16,12,1,0,1 +0,3,13,16,16,16,15,1,0,3,9,5,6,16,11,0,0,0,0,0,10,14,1,0,0,0,0,6,16,3,0,0,0,0,0,14,11,0,0,0,0,0,8,15,2,0,0,0,0,1,14,12,0,0,0,0,0,5,16,5,0,0,0,0,7 +0,1,13,16,16,7,0,0,0,0,10,8,15,12,0,0,0,0,0,1,16,7,0,0,0,1,13,15,16,13,7,0,0,0,8,16,14,12,8,0,0,0,8,16,1,0,0,0,0,0,14,11,0,0,0,0,0,1,16,4,0,0,0,0,7 +0,0,2,13,12,1,0,0,0,0,15,12,9,10,0,0,0,2,16,2,0,9,1,0,0,4,16,1,0,4,5,0,0,2,16,4,0,2,9,0,0,0,14,0,0,6,8,0,0,0,12,7,5,15,4,0,0,0,2,13,15,6,0,0,0 +0,0,3,14,3,0,1,8,0,0,9,15,0,1,13,11,0,0,14,14,0,9,14,2,0,0,14,16,16,16,7,0,0,0,3,13,16,11,1,0,0,0,0,7,15,1,0,0,0,0,0,14,10,0,0,0,0,0,2,15,4,0,0,0,4 +0,0,1,11,7,0,2,1,0,0,10,13,0,1,13,6,0,4,16,3,0,10,12,0,0,3,16,11,9,16,3,0,0,0,9,16,16,8,0,0,0,0,0,9,11,0,0,0,0,0,0,14,3,0,0,0,0,0,0,14,4,0,0,0,4 +0,0,2,15,9,0,0,0,0,0,3,16,12,8,0,0,0,0,8,14,3,10,2,0,0,0,14,5,0,6,6,0,0,2,15,0,0,3,9,0,0,1,15,0,0,1,12,0,0,0,12,9,5,11,11,0,0,0,2,14,16,10,3,0,0 +0,0,3,15,14,3,0,0,0,0,13,8,2,11,0,0,0,3,16,7,0,10,1,0,0,4,14,0,0,7,5,0,0,2,12,0,0,5,7,0,0,2,13,0,0,10,5,0,0,0,13,3,6,14,1,0,0,0,3,15,14,4,0,0,0 +0,0,2,13,1,0,0,0,0,0,11,10,1,0,0,0,0,2,15,1,0,0,0,0,0,6,11,1,4,1,0,0,0,7,10,9,16,14,1,0,0,4,12,7,6,7,10,0,0,1,13,6,2,10,14,0,0,0,3,12,13,13,3,0,6 +0,0,5,14,12,5,0,0,0,0,3,16,16,9,0,0,0,0,2,16,16,7,0,0,0,0,5,16,16,3,0,0,0,0,10,16,16,4,0,0,0,0,9,16,14,1,0,0,0,0,8,16,16,2,0,0,0,0,8,15,13,4,0,0,1 +0,0,11,14,10,3,0,0,0,0,6,8,12,15,3,0,0,0,0,0,6,16,5,0,0,0,0,6,15,12,1,0,0,0,5,16,13,1,0,0,0,0,15,13,0,0,0,0,0,5,16,8,4,2,0,0,0,1,10,16,16,10,0,0,2 +0,0,2,10,16,16,2,0,0,1,16,15,5,1,0,0,0,8,13,0,0,0,0,0,0,12,11,0,0,0,0,0,0,5,16,7,0,0,0,0,0,0,3,13,12,0,0,0,0,0,0,4,16,0,0,0,0,0,0,14,12,0,0,0,5 +0,0,0,3,15,1,0,0,0,0,0,12,11,1,5,0,0,0,6,16,2,6,16,0,0,3,16,7,0,16,6,0,0,10,16,12,14,16,5,0,0,11,14,12,16,13,1,0,0,0,0,0,16,6,0,0,0,0,0,5,14,2,0,0,4 +0,1,8,12,15,6,0,0,0,2,12,8,13,15,2,0,0,0,0,0,13,16,1,0,0,0,0,6,16,10,0,0,0,0,6,16,11,0,0,0,0,0,13,16,3,0,0,0,0,0,16,12,2,6,0,0,0,0,8,14,16,10,2,0,2 +0,0,13,16,16,8,0,0,0,0,7,5,10,8,0,0,0,0,0,0,14,4,0,0,0,0,7,10,16,13,7,0,0,0,15,16,13,15,10,0,0,0,0,16,1,0,0,0,0,0,8,12,0,0,0,0,0,0,16,3,0,0,0,0,7 +0,1,10,13,13,12,1,0,0,6,13,4,4,13,8,0,0,0,1,1,7,16,3,0,0,0,0,15,13,4,0,0,0,0,0,8,15,3,0,0,0,0,0,0,10,13,1,0,0,0,0,1,9,16,2,0,0,1,13,16,12,7,0,0,3 +0,3,11,16,16,12,0,0,0,5,9,10,16,16,3,0,0,0,0,8,16,7,0,0,0,0,0,12,14,0,0,0,0,0,0,4,16,9,0,0,0,0,0,0,5,16,6,0,0,0,6,8,12,16,7,0,0,1,13,11,8,3,0,0,3 +0,5,16,16,16,3,0,0,0,8,7,5,16,2,0,0,0,3,5,10,14,0,0,0,0,7,16,16,15,12,6,0,0,0,9,14,11,14,6,0,0,0,16,6,0,0,0,0,0,3,15,2,0,0,0,0,0,7,12,0,0,0,0,0,7 +0,0,0,9,16,7,0,0,0,0,3,16,16,7,0,0,0,0,11,16,16,0,0,0,0,1,14,16,16,1,0,0,0,0,14,16,14,0,0,0,0,0,10,16,13,1,0,0,0,0,6,16,16,6,0,0,0,0,3,8,15,13,1,0,1 +0,0,8,15,16,11,0,0,0,13,16,12,6,4,0,0,1,16,7,0,0,0,0,0,0,12,14,1,0,0,0,0,0,1,14,10,0,0,0,0,0,0,4,16,4,0,0,0,0,0,2,14,8,0,0,0,0,0,11,15,3,0,0,0,5 +0,0,13,3,0,0,0,0,0,4,16,4,0,6,12,0,0,9,15,0,6,16,7,0,0,6,16,11,16,9,0,0,0,0,9,16,14,3,0,0,0,0,6,15,3,0,0,0,0,0,13,9,0,0,0,0,0,0,15,7,0,0,0,0,4 +0,0,0,0,12,15,2,0,0,0,0,0,12,16,4,0,0,2,4,6,16,16,2,0,0,9,16,16,16,16,0,0,0,1,8,5,15,16,0,0,0,0,0,0,16,16,4,0,0,0,0,0,15,16,5,0,0,0,0,0,9,16,8,0,1 +0,0,8,13,12,6,0,0,0,1,14,13,12,6,0,0,0,4,12,2,2,0,0,0,0,5,16,16,16,13,1,0,0,0,3,1,2,15,7,0,0,0,0,0,0,12,8,0,0,0,6,8,8,15,4,0,0,0,7,13,12,5,0,0,5 +0,0,6,14,16,16,4,0,0,0,11,12,13,16,7,0,0,0,0,0,9,15,3,0,0,0,1,6,14,14,2,0,0,0,10,16,16,16,9,0,0,0,2,15,11,4,1,0,0,0,4,16,5,0,0,0,0,0,9,13,0,0,0,0,7 +0,0,8,11,14,10,0,0,0,0,14,15,7,8,0,0,0,1,14,4,0,0,0,0,0,8,16,9,8,3,0,0,0,4,12,12,14,16,4,0,0,0,0,0,0,13,8,0,0,0,4,8,12,15,4,0,0,0,15,14,12,3,0,0,5 +0,2,10,15,9,1,0,0,0,10,13,9,16,4,0,0,0,2,0,5,16,0,0,0,0,0,0,12,16,9,0,0,0,0,0,3,7,16,7,0,0,0,0,0,0,11,9,0,0,0,12,9,9,16,8,0,0,0,13,12,12,6,0,0,3 +0,0,0,0,9,7,0,0,0,0,0,9,12,0,0,0,0,0,3,15,2,2,1,0,0,2,15,4,0,14,4,0,0,7,14,0,4,16,0,0,0,8,16,16,16,16,4,0,0,0,0,3,16,5,0,0,0,0,0,0,13,6,0,0,4 +0,0,3,16,14,3,0,0,0,0,12,14,14,12,0,0,0,2,16,6,0,15,2,0,0,7,10,0,0,10,4,0,0,8,8,0,0,11,5,0,0,7,10,0,0,13,3,0,0,5,16,13,15,11,0,0,0,0,7,14,13,1,0,0,0 +0,0,0,0,8,11,1,0,0,0,0,0,10,16,4,0,0,0,0,8,16,16,0,0,0,0,6,15,16,16,0,0,0,5,15,8,16,14,0,0,0,0,0,0,13,15,0,0,0,0,0,0,12,16,1,0,0,0,0,0,7,15,9,0,1 +0,0,4,12,12,6,0,0,0,0,3,6,4,8,0,0,0,3,13,6,8,3,0,0,0,4,16,13,12,15,2,0,0,0,3,0,0,10,8,0,0,0,0,0,0,8,6,0,0,0,7,6,5,14,2,0,0,0,3,14,15,6,0,0,5 +0,0,9,14,13,6,0,0,0,4,12,5,8,16,0,0,0,0,0,0,4,13,0,0,0,0,0,3,15,7,0,0,0,0,0,10,16,15,2,0,0,0,0,0,0,13,7,0,0,0,12,4,5,13,2,0,0,0,13,16,14,6,0,0,3 +0,0,8,16,12,0,0,0,0,6,16,15,10,0,0,0,0,4,16,14,14,16,6,0,0,0,12,16,15,5,0,0,0,0,12,16,11,0,0,0,0,0,16,10,16,6,0,0,0,0,16,5,13,12,0,0,0,0,10,15,11,1,0,0,8 +0,0,0,0,8,11,0,0,0,0,0,2,14,6,0,0,0,0,0,11,9,2,3,0,0,0,9,12,1,11,9,0,0,4,16,6,0,13,5,0,0,8,16,16,16,16,0,0,0,0,0,3,8,15,0,0,0,0,0,0,6,12,0,0,4 +0,0,10,16,16,15,0,0,0,3,15,9,8,4,0,0,0,9,13,6,2,0,0,0,0,8,16,16,16,11,0,0,0,1,8,5,10,16,7,0,0,0,0,0,0,12,9,0,0,0,4,9,14,16,4,0,0,0,11,13,11,5,0,0,5 +0,0,9,14,6,0,0,0,0,0,16,14,16,6,0,0,0,0,13,12,13,16,1,0,0,0,6,13,10,16,2,0,0,0,0,0,0,12,7,0,0,0,5,1,0,12,8,0,0,1,16,10,8,15,7,0,0,0,8,15,16,11,1,0,9 +0,0,7,13,11,3,0,0,0,2,15,8,8,2,0,0,0,6,11,3,3,0,0,0,0,7,16,16,16,13,2,0,0,1,8,5,3,11,10,0,0,0,0,0,0,2,14,0,0,0,2,5,4,10,15,0,0,0,8,16,16,14,4,0,5 +0,0,0,10,9,0,0,0,0,0,0,15,9,0,0,0,0,0,6,14,2,11,4,0,0,2,14,7,9,15,2,0,0,11,16,8,14,13,1,0,0,12,16,16,16,16,6,0,0,2,4,10,16,3,0,0,0,0,0,11,15,0,0,0,4 +0,0,7,16,10,1,0,0,0,4,16,11,14,13,0,0,0,6,16,4,10,16,5,0,0,2,15,16,12,14,8,0,0,0,0,0,0,12,8,0,0,0,0,0,0,10,8,0,0,0,4,8,8,14,8,0,0,0,8,15,14,11,0,0,9 +0,0,3,10,16,7,0,0,0,0,15,12,8,15,0,0,0,5,14,2,10,16,6,0,0,2,14,16,11,3,0,0,0,0,6,16,6,0,0,0,0,0,12,14,15,2,0,0,0,0,11,8,16,8,0,0,0,0,3,14,14,3,0,0,8 +0,0,12,16,15,5,0,0,0,3,15,8,7,5,0,0,0,9,13,8,8,2,0,0,0,4,15,12,15,16,3,0,0,0,0,0,1,14,8,0,0,0,1,0,0,11,10,0,0,2,16,9,9,15,3,0,0,0,8,11,13,4,0,0,5 +0,1,10,16,6,0,0,0,0,4,12,8,12,0,0,0,0,1,0,2,11,0,0,0,0,0,0,9,16,9,0,0,0,0,0,5,8,16,5,0,0,0,0,0,0,8,9,0,0,2,8,8,7,11,9,0,0,1,13,16,16,10,1,0,3 +0,0,9,14,14,9,1,0,0,7,16,10,4,12,0,0,0,8,16,6,6,15,4,0,0,0,11,16,16,9,1,0,0,0,7,16,16,0,0,0,0,1,15,13,15,12,0,0,0,4,16,8,12,15,0,0,0,1,8,13,13,4,0,0,8 +0,0,0,8,14,0,0,0,0,0,0,12,12,0,0,0,0,0,1,15,9,5,0,0,0,0,11,15,15,12,0,0,0,3,16,11,16,10,2,0,0,13,16,16,16,16,8,0,0,5,8,12,16,7,0,0,0,0,0,8,16,2,0,0,4 +0,0,6,15,13,2,0,0,0,5,15,7,9,10,0,0,0,11,9,1,10,12,0,0,0,2,13,16,13,15,4,0,0,0,0,0,0,13,8,0,0,0,0,0,0,8,11,0,0,0,6,8,4,11,11,0,0,0,5,14,16,14,1,0,9 +0,0,13,16,10,0,0,0,0,3,16,11,16,5,0,0,0,0,15,13,15,13,0,0,0,0,5,12,11,16,2,0,0,0,0,0,0,14,10,0,0,0,0,0,0,5,16,0,0,0,7,8,11,13,16,2,0,0,15,16,16,16,11,0,9 +0,0,10,16,9,0,0,0,0,8,14,8,16,0,0,0,0,10,2,7,13,0,0,0,0,0,0,10,16,9,0,0,0,0,0,2,9,16,5,0,0,0,0,0,0,9,11,0,0,1,11,5,7,16,6,0,0,0,11,15,14,7,0,0,3 +0,0,8,14,13,7,0,0,0,1,16,12,9,16,2,0,0,0,11,11,1,9,0,0,0,0,3,16,16,12,0,0,0,0,6,16,16,2,0,0,0,1,15,10,13,8,0,0,0,1,16,5,9,12,0,0,0,0,11,12,11,3,0,0,8 +0,0,0,0,8,15,1,0,0,0,0,0,14,16,5,0,0,0,2,10,16,16,4,0,0,9,16,16,16,16,5,0,0,5,7,4,12,16,4,0,0,0,0,0,8,16,5,0,0,0,0,0,9,16,4,0,0,0,0,0,7,16,5,0,1 +0,0,0,0,10,9,0,0,0,0,0,5,15,3,0,0,0,0,3,16,6,2,1,0,0,0,13,9,0,11,6,0,0,6,16,1,1,16,4,0,0,8,16,14,14,16,4,0,0,1,6,7,15,13,0,0,0,0,0,0,10,9,0,0,4 +0,0,0,6,14,10,0,0,0,0,5,16,11,7,0,0,0,0,12,13,0,0,0,0,0,5,16,3,0,0,0,0,0,4,16,16,15,7,0,0,0,4,16,11,8,15,5,0,0,1,13,14,8,11,14,0,0,0,0,7,14,16,9,0,6 +0,0,0,0,9,13,0,0,0,0,0,7,16,3,0,0,0,0,5,16,4,3,5,0,0,3,13,7,0,10,14,0,0,10,16,16,16,16,11,0,0,5,8,10,13,16,7,0,0,0,0,0,7,16,4,0,0,0,0,0,10,11,0,0,4 +0,0,9,14,11,1,0,0,0,3,15,5,5,10,0,0,0,6,14,4,7,12,0,0,0,1,10,16,16,7,0,0,0,0,11,14,12,13,1,0,0,0,16,1,0,8,6,0,0,0,16,5,4,10,8,0,0,0,7,16,16,12,1,0,8 +0,0,0,1,15,3,0,0,0,0,0,12,15,0,0,0,0,0,8,16,1,5,12,0,0,4,15,11,3,12,13,0,0,11,16,16,16,16,7,0,0,3,8,10,15,16,1,0,0,0,0,0,15,10,0,0,0,0,0,1,16,5,0,0,4 +0,2,8,12,15,15,2,0,0,13,16,16,13,12,3,0,0,12,16,9,0,0,0,0,0,6,16,16,7,0,0,0,0,0,4,10,15,0,0,0,0,0,2,4,16,4,0,0,0,4,16,16,13,0,0,0,0,0,15,15,5,0,0,0,5 +0,1,9,16,13,2,0,0,0,7,13,7,8,12,0,0,0,10,11,0,4,16,0,0,0,2,16,16,16,12,0,0,0,0,14,15,16,14,1,0,0,0,15,6,0,9,9,0,0,0,13,12,8,12,9,0,0,0,6,13,12,11,4,0,8 +0,0,0,9,12,0,0,0,0,0,4,15,6,1,8,0,0,1,13,12,0,11,14,1,0,10,16,6,5,16,9,0,0,9,16,16,16,16,10,0,0,0,1,5,16,11,0,0,0,0,0,4,16,5,0,0,0,0,0,12,11,0,0,0,4 +0,1,8,10,14,15,6,0,0,6,16,11,7,5,2,0,0,3,16,8,0,0,0,0,0,2,16,16,10,0,0,0,0,0,1,3,13,2,0,0,0,0,0,0,9,4,0,0,0,0,11,11,14,1,0,0,0,0,11,9,2,0,0,0,5 +0,0,4,12,7,0,0,0,0,0,13,11,12,4,0,0,0,0,8,0,3,10,0,0,0,0,0,0,2,13,0,0,0,0,0,0,9,8,0,0,0,0,0,3,16,1,0,0,0,0,8,16,15,14,14,1,0,0,6,12,12,10,10,2,2 +0,0,5,16,16,10,0,0,0,0,9,16,16,15,0,0,0,0,7,16,16,11,0,0,0,0,9,16,16,11,0,0,0,0,8,16,16,12,0,0,0,0,10,16,16,10,0,0,0,0,14,16,16,12,0,0,0,0,8,13,16,8,0,0,1 +0,0,3,4,4,2,0,0,0,1,14,16,15,2,0,0,0,4,16,16,14,0,0,0,0,4,16,16,15,0,0,0,0,2,15,16,15,1,0,0,0,0,11,16,16,5,0,0,0,0,5,16,16,12,0,0,0,0,6,12,8,5,0,0,1 +0,3,14,14,2,0,0,0,0,11,16,16,13,0,0,0,0,3,2,4,14,6,0,0,0,0,0,0,13,8,0,0,0,0,0,7,15,2,0,0,0,0,6,15,7,0,0,0,0,4,16,16,10,8,5,0,0,2,11,15,16,15,12,0,2 +0,0,6,16,11,1,0,0,0,0,16,13,16,11,0,0,0,5,10,0,3,13,2,0,0,7,8,0,0,4,7,0,0,8,8,0,0,6,8,0,0,4,11,2,4,15,5,0,0,0,14,16,16,15,1,0,0,0,5,12,10,2,0,0,0 +0,0,8,16,8,0,0,0,0,2,15,16,16,6,0,0,0,4,13,1,6,15,1,0,0,7,13,0,0,13,5,0,0,8,12,0,0,8,8,0,0,7,14,1,4,12,8,0,0,2,15,16,16,16,4,0,0,0,7,16,16,10,0,0,0 +0,0,4,10,14,14,8,0,0,7,16,15,11,8,4,0,0,2,15,9,3,0,0,0,0,0,12,16,16,7,0,0,0,0,0,4,9,16,4,0,0,0,0,0,0,13,8,0,0,0,7,12,11,16,5,0,0,0,4,16,10,5,0,0,5 +0,0,7,14,16,16,7,0,0,0,12,14,9,8,2,0,0,0,14,4,0,0,0,0,0,3,16,16,9,0,0,0,0,8,16,14,16,3,0,0,0,2,2,0,12,8,0,0,0,0,6,15,15,2,0,0,0,0,11,11,3,0,0,0,5 +0,0,3,10,11,5,0,0,0,8,16,10,8,5,0,0,0,11,11,0,0,0,0,0,0,8,14,11,8,4,0,0,0,0,3,7,9,14,9,0,0,0,0,0,0,5,13,0,0,0,1,4,10,16,5,0,0,0,2,13,10,2,0,0,5 +0,2,13,13,1,0,0,0,0,3,14,13,13,0,0,0,0,0,2,0,16,0,0,0,0,0,0,0,16,0,0,0,0,0,0,9,8,0,0,0,0,0,4,15,3,0,0,0,0,2,16,16,9,7,6,0,0,0,11,12,15,16,12,0,2 +0,0,8,14,15,11,0,0,0,1,16,14,8,8,0,0,0,2,16,10,2,0,0,0,0,1,12,16,14,2,0,0,0,0,0,0,11,10,0,0,0,0,0,0,7,13,0,0,0,0,14,14,16,3,0,0,0,0,12,10,5,0,0,0,5 +0,0,2,11,16,13,1,0,0,1,16,11,4,12,8,0,0,0,14,8,3,11,8,0,0,0,13,16,16,14,1,0,0,0,10,16,10,15,3,0,0,0,14,7,0,10,5,0,0,0,11,12,8,14,4,0,0,0,3,14,12,10,1,0,8 +0,0,1,10,11,0,0,0,0,0,7,15,3,0,9,1,0,2,16,7,0,6,15,2,0,8,16,7,4,13,11,0,0,8,16,16,16,16,2,0,0,1,4,7,16,12,0,0,0,0,0,4,16,4,0,0,0,0,0,14,9,0,0,0,4 +0,1,10,16,16,15,0,0,0,2,15,10,12,15,0,0,0,0,0,1,15,8,0,0,0,0,8,16,16,16,13,0,0,0,6,16,10,4,2,0,0,0,4,16,4,0,0,0,0,0,9,16,1,0,0,0,0,0,13,13,0,0,0,0,7 +0,0,10,15,10,1,0,0,0,5,16,4,11,13,1,0,0,5,16,9,12,16,7,0,0,1,10,12,8,14,8,0,0,0,0,0,0,14,5,0,0,0,0,0,10,13,1,0,0,0,5,12,15,2,0,0,0,0,13,10,2,0,0,0,9 +0,0,1,12,12,11,0,0,0,0,12,16,16,7,0,0,0,0,12,16,16,4,0,0,0,0,12,16,16,0,0,0,0,0,12,16,16,4,0,0,0,0,7,16,16,7,0,0,0,0,4,16,16,15,1,0,0,0,3,10,10,4,0,0,1 +0,0,3,8,10,12,6,0,0,0,13,15,11,8,2,0,0,0,12,11,2,0,0,0,0,0,13,16,16,9,1,0,0,0,0,4,6,13,6,0,0,0,0,0,0,13,4,0,0,0,5,5,13,11,0,0,0,0,13,14,7,0,0,0,5 +0,1,9,15,5,0,0,0,0,4,16,15,13,0,0,0,0,1,2,1,15,2,0,0,0,0,0,0,16,0,0,0,0,0,0,5,13,0,0,0,0,0,4,15,3,0,1,0,0,0,12,16,12,14,4,0,0,0,11,16,13,9,2,0,2 +0,3,8,12,13,3,0,0,0,5,11,8,12,10,0,0,0,0,0,0,13,7,0,0,0,0,5,14,16,13,0,0,0,0,7,13,7,14,8,0,0,0,0,0,2,16,7,0,0,1,6,11,16,7,0,0,0,3,13,11,3,0,0,0,3 +0,2,12,15,6,0,0,0,0,6,13,12,16,5,0,0,0,0,3,9,16,5,0,0,0,0,7,16,16,12,0,0,0,0,0,4,8,16,5,0,0,0,0,0,0,13,8,0,0,2,13,8,14,15,3,0,0,2,15,15,10,1,0,0,3 +0,0,0,7,12,5,0,0,0,0,5,16,16,12,0,0,0,0,12,16,16,15,0,0,0,0,8,16,16,15,0,0,0,0,10,16,16,10,0,0,0,0,6,16,16,10,0,0,0,0,0,13,16,6,0,0,0,0,0,8,6,0,0,0,1 +0,0,8,13,13,16,8,0,0,1,15,15,12,16,4,0,0,0,0,0,10,14,0,0,0,0,1,5,16,10,3,0,0,0,8,16,16,13,6,0,0,0,1,13,9,0,0,0,0,0,4,16,7,0,0,0,0,0,9,13,1,0,0,0,7 +0,0,9,16,7,2,0,0,0,1,16,10,15,11,0,0,0,4,16,4,1,15,3,0,0,6,16,2,0,10,6,0,0,4,14,0,0,9,8,0,0,4,14,0,0,14,6,0,0,2,16,12,15,12,0,0,0,0,6,15,13,1,0,0,0 +0,0,2,7,13,12,2,0,0,0,10,13,6,12,6,0,0,2,16,4,1,13,8,0,0,0,14,14,15,15,10,0,0,0,0,3,1,12,5,0,0,0,0,0,5,11,0,0,0,0,0,2,14,2,0,0,0,0,0,9,8,0,0,0,9 +0,0,4,15,16,5,0,0,0,4,16,10,10,15,0,0,0,10,16,7,4,15,0,0,0,1,4,3,13,9,0,0,0,0,0,9,16,16,4,0,0,0,0,1,0,11,11,0,0,0,2,12,4,13,9,0,0,0,4,16,16,12,1,0,3 +0,0,9,12,14,14,12,1,0,0,15,15,12,14,16,6,0,0,3,0,0,10,15,3,0,0,0,0,4,16,6,0,0,0,0,3,15,9,0,0,0,0,2,13,12,0,0,0,0,0,10,14,1,0,0,0,0,0,13,9,0,0,0,0,7 +0,0,5,15,14,2,0,0,0,2,15,14,13,15,0,0,0,7,16,2,1,16,5,0,0,8,14,0,0,12,10,0,0,8,13,0,0,8,12,0,0,8,15,0,0,14,10,0,0,2,16,11,10,16,2,0,0,0,8,16,16,6,0,0,0 +0,0,0,13,7,0,0,0,0,0,5,13,3,13,2,0,0,0,14,6,4,16,0,0,0,5,16,9,14,16,10,0,0,5,16,15,16,7,2,0,0,0,0,10,11,0,0,0,0,0,0,13,6,0,0,0,0,0,0,16,1,0,0,0,4 +0,0,3,16,15,4,0,0,0,0,11,13,10,16,2,0,0,6,16,6,0,12,8,0,0,10,16,3,0,11,10,0,0,10,16,1,0,13,8,0,0,4,16,4,3,15,4,0,0,0,12,11,14,15,0,0,0,0,3,15,16,4,0,0,0 +0,0,6,12,16,16,15,3,0,0,14,16,12,14,16,8,0,0,0,0,0,14,16,3,0,0,0,0,3,16,11,0,0,0,0,0,12,16,2,0,0,0,0,6,16,11,0,0,0,0,2,15,14,1,0,0,0,0,6,16,9,0,0,0,7 +0,0,10,16,16,16,15,4,0,0,10,12,8,11,16,7,0,0,0,0,1,12,14,1,0,0,0,1,13,13,1,0,0,0,0,13,12,1,0,0,0,0,6,14,3,0,0,0,0,0,10,10,0,0,0,0,0,0,14,2,0,0,0,0,7 +0,0,12,16,9,0,0,0,0,6,16,12,15,5,0,0,0,7,16,7,10,8,0,0,0,1,11,4,10,9,0,0,0,0,0,1,16,3,0,0,0,0,0,8,14,1,0,0,0,1,12,16,14,12,6,0,0,1,15,16,16,16,16,3,2 +0,0,4,11,13,6,0,0,0,2,16,12,5,12,0,0,0,2,14,0,8,12,0,0,0,0,11,14,13,3,0,0,0,0,14,14,11,0,0,0,0,2,13,1,10,6,0,0,0,0,14,4,7,11,0,0,0,0,2,13,15,5,0,0,8 +0,0,8,13,4,0,0,0,0,1,14,10,15,3,0,0,0,2,16,1,4,14,0,0,0,4,16,0,0,12,5,0,0,4,13,0,0,7,9,0,0,4,16,1,0,10,8,0,0,1,15,8,9,15,3,0,0,0,7,15,14,4,0,0,0 +0,0,9,14,0,0,0,0,0,4,16,8,0,0,0,0,0,7,14,1,0,0,0,0,0,9,11,0,4,5,1,0,0,9,12,13,16,16,11,0,0,4,16,15,8,11,14,0,0,3,16,11,9,16,6,0,0,0,8,16,16,8,0,0,6 +0,1,11,12,15,16,13,1,0,1,15,11,8,10,16,2,0,0,0,0,0,13,10,0,0,0,0,0,11,12,1,0,0,0,0,8,16,2,0,0,0,0,3,16,5,0,0,0,0,0,9,13,0,0,0,0,0,0,14,5,0,0,0,0,7 +0,0,2,10,14,2,0,0,0,0,10,13,6,0,0,0,0,0,14,4,0,0,0,0,0,1,16,0,0,0,0,0,0,4,16,16,16,11,0,0,0,5,16,6,5,13,9,0,0,0,15,9,6,13,7,0,0,0,4,14,14,6,0,0,6 +0,0,12,16,8,0,0,0,0,7,16,10,16,2,0,0,0,10,12,0,15,7,0,0,0,5,8,0,15,6,0,0,0,0,0,1,16,2,0,0,0,0,0,8,12,0,0,0,0,0,6,16,9,4,2,0,0,0,12,16,16,16,9,0,2 +0,0,2,12,7,0,0,0,0,0,11,16,5,0,0,0,0,0,16,9,0,0,0,0,0,0,15,11,4,3,0,0,0,0,16,16,16,16,6,0,0,0,15,13,0,10,14,0,0,0,11,15,8,16,11,0,0,0,1,11,14,11,1,0,6 +0,0,5,14,3,0,0,0,0,0,15,15,3,0,0,0,0,2,16,4,0,0,0,0,0,4,16,4,3,1,0,0,0,6,16,9,16,15,3,0,0,4,16,16,9,13,11,0,0,0,14,15,5,16,6,0,0,0,4,14,14,9,0,0,6 +0,0,0,8,13,2,0,0,0,0,12,14,9,0,0,0,0,1,15,5,0,0,0,0,0,5,15,0,0,0,0,0,0,3,15,13,16,15,4,0,0,2,16,11,4,8,13,0,0,0,9,12,5,11,12,0,0,0,0,9,14,13,4,0,6 +0,3,15,13,0,0,0,0,0,12,14,16,6,0,0,0,0,14,9,10,9,0,0,0,0,2,1,12,8,0,0,0,0,0,0,16,2,0,0,0,0,0,6,15,0,0,0,0,0,1,13,14,12,12,11,0,0,4,16,16,16,15,5,0,2 +0,0,0,8,13,16,9,0,0,0,9,12,4,13,8,0,0,1,14,4,10,16,3,0,0,1,15,16,10,14,9,0,0,0,0,0,0,11,7,0,0,0,0,0,3,14,1,0,0,0,0,1,14,6,0,0,0,0,0,11,8,0,0,0,9 +0,0,12,16,6,0,0,0,0,7,12,4,14,0,0,0,0,11,14,0,6,4,0,0,0,6,8,0,7,4,0,0,0,0,0,0,10,3,0,0,0,0,0,5,13,0,0,0,0,0,3,14,10,0,0,0,0,0,14,13,12,15,13,1,2 +0,0,9,16,16,16,7,0,0,5,16,13,6,2,1,0,0,11,15,14,8,0,0,0,0,6,15,12,16,8,0,0,0,0,0,0,10,15,0,0,0,1,13,3,5,15,0,0,0,3,16,9,15,9,0,0,0,0,8,16,14,1,0,0,5 +0,2,14,16,10,1,0,0,0,15,15,8,16,8,0,0,0,9,10,0,13,10,0,0,0,0,1,5,16,3,0,0,0,0,0,13,14,0,0,0,0,0,4,16,5,0,0,0,0,1,14,16,13,8,4,0,0,2,13,16,16,16,16,5,2 +0,0,7,16,16,10,1,0,0,0,8,16,16,13,1,0,0,0,6,16,16,12,0,0,0,0,4,16,16,8,0,0,0,0,7,16,16,5,0,0,0,1,14,16,16,3,0,0,0,4,16,16,8,0,0,0,0,2,14,16,14,2,0,0,1 +0,0,9,12,16,7,0,0,0,10,11,2,0,16,0,0,0,3,2,0,10,8,0,0,0,0,0,7,15,5,0,0,0,0,0,7,9,14,2,0,0,0,0,0,0,8,10,0,0,0,2,2,4,14,5,0,0,0,14,16,14,2,0,0,3 +0,0,2,11,13,11,3,0,0,0,12,11,6,14,13,0,0,3,16,0,10,16,4,0,0,2,16,16,16,13,0,0,0,0,2,4,9,8,0,0,0,0,0,3,15,0,0,0,0,0,0,11,7,0,0,0,0,0,1,14,0,0,0,0,9 +0,0,7,15,12,3,0,0,0,0,6,16,16,8,0,0,0,0,7,16,16,7,0,0,0,0,7,16,16,4,0,0,0,0,6,16,16,6,0,0,0,0,2,16,16,9,0,0,0,0,4,16,16,6,0,0,0,0,5,16,10,3,0,0,1 +0,0,7,16,16,16,13,2,0,0,8,11,8,11,16,7,0,0,0,0,0,12,14,0,0,0,0,0,6,15,3,0,0,0,0,2,14,7,0,0,0,0,0,9,14,1,0,0,0,0,3,16,4,0,0,0,0,0,9,15,0,0,0,0,7 +0,0,6,14,9,0,0,0,0,3,16,9,15,12,3,0,0,1,16,6,16,10,0,0,0,0,6,16,16,6,0,0,0,1,13,10,10,15,1,0,0,3,16,1,0,11,11,0,0,1,15,2,3,14,5,0,0,0,5,15,15,6,0,0,8 +0,0,3,8,12,4,0,0,0,5,16,13,9,13,0,0,0,8,13,9,16,13,0,0,0,0,13,16,12,0,0,0,0,3,15,9,16,10,0,0,0,3,11,0,2,13,11,0,0,2,14,2,4,12,12,0,0,0,6,13,13,11,3,0,8 +0,0,15,12,7,15,6,0,0,8,16,16,16,14,5,0,0,10,16,5,1,0,0,0,0,8,16,9,1,0,0,0,0,0,5,15,13,0,0,0,0,0,0,5,16,4,0,0,0,0,0,10,15,0,0,0,0,0,15,16,6,0,0,0,5 +0,0,5,10,15,11,1,0,0,6,16,8,7,16,5,0,0,6,15,2,8,14,1,0,0,0,11,14,16,3,0,0,0,0,0,13,15,1,0,0,0,0,5,15,15,6,0,0,0,0,11,16,16,7,0,0,0,0,6,13,11,2,0,0,8 +0,0,0,9,12,0,0,0,0,0,4,16,11,0,0,0,0,0,12,15,2,1,5,0,0,5,16,8,1,14,13,0,0,10,16,12,15,16,9,0,0,6,14,14,16,14,0,0,0,0,0,3,16,9,0,0,0,0,0,7,16,4,0,0,4 +0,1,13,16,16,12,1,0,0,2,16,16,16,15,2,0,0,0,5,12,16,14,0,0,0,0,0,0,10,15,0,0,0,0,0,0,12,12,0,0,0,0,0,3,16,9,0,0,0,1,8,13,15,2,0,0,0,2,13,16,6,0,0,0,9 +0,2,12,15,16,12,1,0,0,0,8,16,6,14,2,0,0,0,14,11,0,0,0,0,0,1,15,6,0,0,0,0,0,0,8,14,4,0,0,0,0,0,0,8,15,0,0,0,0,0,2,10,16,2,0,0,0,0,12,16,6,0,0,0,5 +0,0,3,15,10,0,0,0,0,0,9,16,6,0,0,0,0,2,16,13,3,8,1,0,0,11,16,16,16,16,10,0,0,3,12,11,16,15,2,0,0,0,0,4,16,9,0,0,0,0,0,11,16,3,0,0,0,0,2,15,16,0,0,0,4 +0,0,4,16,16,4,0,0,0,0,10,16,16,13,0,0,0,0,3,13,11,16,2,0,0,0,0,0,0,13,8,0,0,0,0,0,0,11,9,0,0,0,0,0,0,13,9,0,0,0,0,0,8,16,5,0,0,0,3,16,16,11,0,0,9 +0,0,2,13,12,1,0,0,0,0,10,14,9,12,0,0,0,1,15,1,0,11,1,0,0,4,13,0,0,10,5,0,0,3,10,0,0,11,8,0,0,2,10,0,3,16,5,0,0,0,11,13,16,8,0,0,0,0,3,13,8,0,0,0,0 +0,2,16,16,12,12,11,0,0,0,3,9,13,16,14,2,0,0,0,1,13,16,4,0,0,0,0,14,15,3,0,0,0,0,0,9,16,6,0,0,0,0,0,1,16,12,0,0,0,0,3,9,16,10,0,0,0,4,16,16,7,0,0,0,3 +0,0,10,16,2,0,0,0,0,0,11,15,0,0,0,0,0,3,16,7,0,0,0,0,0,10,16,5,7,12,5,0,0,5,16,16,16,15,3,0,0,0,4,16,15,3,0,0,0,0,7,16,3,0,0,0,0,0,8,15,2,0,0,0,4 +0,1,9,14,5,0,0,0,0,7,9,7,11,8,4,0,0,4,9,5,14,7,2,0,0,0,11,14,4,0,0,0,0,0,10,15,5,0,0,0,0,2,14,2,15,1,0,0,0,4,13,2,15,2,0,0,0,0,12,16,7,0,0,0,8 +0,0,4,14,8,0,0,0,0,0,14,12,15,9,0,0,0,0,14,10,15,9,1,0,0,0,4,16,11,0,0,0,0,0,9,16,12,0,0,0,0,0,12,12,16,1,0,0,0,1,15,14,16,1,0,0,0,0,7,16,8,0,0,0,8 +0,0,7,16,15,3,0,0,0,0,14,16,16,13,0,0,0,0,9,16,16,16,3,0,0,0,0,6,9,16,9,0,0,0,0,0,0,16,11,0,0,0,0,0,6,16,8,0,0,0,0,4,14,16,2,0,0,0,8,16,15,7,0,0,9 +0,0,1,8,12,1,0,0,0,0,4,15,16,12,0,0,0,0,0,10,16,15,0,0,0,0,5,16,16,11,0,0,0,0,7,16,16,13,0,0,0,0,4,16,16,11,0,0,0,0,0,16,16,9,0,0,0,0,1,11,12,10,5,0,1 +0,0,6,16,13,11,5,0,0,0,1,8,10,16,15,0,0,0,0,0,1,14,13,0,0,0,0,0,7,16,5,0,0,3,4,1,12,16,0,0,0,12,16,16,16,13,2,0,0,0,3,14,15,0,0,0,0,0,6,16,7,0,0,0,7 +0,0,5,12,15,14,10,3,0,0,4,7,4,5,14,3,0,0,0,0,0,9,11,0,0,0,0,0,4,16,4,0,0,2,8,9,16,10,0,0,0,3,12,16,9,0,0,0,0,0,5,14,0,0,0,0,0,0,6,9,0,0,0,0,7 +0,0,8,15,11,3,0,0,0,4,15,8,16,16,6,0,0,7,13,4,15,11,1,0,0,0,14,14,10,1,0,0,0,0,8,16,6,0,0,0,0,0,11,13,16,3,0,0,0,0,16,6,16,4,0,0,0,0,11,16,13,2,0,0,8 +0,0,0,12,16,0,0,0,0,0,3,16,13,0,0,0,0,1,11,15,1,2,6,0,0,6,16,6,1,13,13,0,0,5,16,14,12,16,5,0,0,0,7,14,16,7,0,0,0,0,0,11,13,0,0,0,0,0,0,15,8,0,0,0,4 +0,0,2,15,11,10,16,2,0,0,10,16,16,16,9,0,0,2,16,11,5,2,0,0,0,12,15,4,2,0,0,0,0,5,16,16,15,5,0,0,0,0,2,7,16,11,0,0,0,0,0,3,16,10,0,0,0,0,4,15,12,2,0,0,5 +0,0,10,15,9,12,5,0,0,8,16,14,16,16,5,0,0,12,14,3,0,0,0,0,0,3,15,16,9,0,0,0,0,0,0,8,16,3,0,0,0,0,0,3,16,5,0,0,0,0,1,10,15,3,0,0,0,0,10,16,4,0,0,0,5 +0,0,6,8,11,15,0,0,0,0,15,16,16,10,0,0,0,0,15,12,4,0,0,0,0,0,7,15,3,0,0,0,0,0,0,7,12,0,0,0,0,0,0,4,15,0,0,0,0,0,0,11,11,0,0,0,0,0,10,16,6,0,0,0,5 +0,0,6,11,0,0,0,0,0,0,13,7,0,5,16,2,0,1,16,7,11,16,9,0,0,0,6,12,16,11,0,0,0,0,0,7,14,2,0,0,0,0,0,14,8,0,0,0,0,0,6,16,2,0,0,0,0,0,9,13,0,0,0,0,4 +0,0,9,4,10,15,7,0,0,3,16,16,16,12,3,0,0,7,16,9,2,0,0,0,0,10,16,3,0,0,0,0,0,5,15,15,2,0,0,0,0,0,2,12,12,0,0,0,0,0,5,12,11,0,0,0,0,0,9,16,4,0,0,0,5 +0,0,9,16,11,1,0,0,0,1,16,9,12,13,7,0,0,2,16,5,13,11,6,0,0,0,9,16,12,0,0,0,0,0,5,16,6,0,0,0,0,0,13,15,10,0,0,0,0,0,16,16,8,0,0,0,0,0,11,15,1,0,0,0,8 +0,0,2,7,12,16,11,0,0,0,10,15,16,16,16,0,0,0,0,10,16,16,12,0,0,0,0,4,16,16,4,0,0,0,0,5,16,15,4,0,0,0,0,10,16,12,0,0,0,0,2,15,16,3,0,0,0,0,1,13,14,1,0,0,1 +0,0,0,11,2,0,0,0,0,0,4,16,0,0,0,0,0,0,11,7,0,2,0,0,0,3,15,2,7,15,9,0,0,9,16,16,16,15,3,0,0,6,8,6,16,8,0,0,0,0,0,8,10,0,0,0,0,0,0,15,5,0,0,0,4 +0,0,6,12,16,15,1,0,0,0,0,4,6,16,3,0,0,0,0,0,6,16,2,0,0,0,2,5,12,15,4,0,0,1,11,15,16,12,4,0,0,0,0,8,12,0,0,0,0,0,0,16,6,0,0,0,0,0,5,13,0,0,0,0,7 +0,0,7,12,15,15,2,0,0,3,16,12,8,16,5,0,0,0,0,0,6,16,0,0,0,0,0,0,14,15,6,0,0,0,1,15,16,16,9,0,0,0,0,13,10,2,0,0,0,0,2,16,3,0,0,0,0,0,10,15,0,0,0,0,7 +0,0,0,10,16,5,0,0,0,0,2,16,13,2,0,0,0,0,7,16,4,0,0,0,0,0,8,16,1,0,0,0,0,0,11,16,10,1,0,0,0,0,13,16,16,13,4,0,0,0,7,16,7,15,14,0,0,0,0,9,16,16,12,0,6 +0,0,8,16,6,0,0,0,0,3,15,13,1,0,0,0,0,8,16,4,0,2,1,0,0,9,16,8,10,16,11,0,0,1,13,16,16,15,5,0,0,0,4,16,16,3,0,0,0,0,8,16,10,0,0,0,0,0,7,16,10,0,0,0,4 +0,0,0,9,14,4,0,0,0,10,14,16,16,6,0,0,0,3,9,16,16,2,0,0,0,0,0,16,10,0,0,0,0,0,0,14,9,0,0,0,0,0,2,16,9,0,0,0,0,0,1,16,12,0,0,0,0,0,0,7,15,1,0,0,1 +0,0,1,15,0,0,0,0,0,0,7,16,0,0,0,0,0,0,10,11,0,0,0,0,0,0,14,10,0,0,0,0,0,0,16,13,12,9,1,0,0,1,16,15,12,15,11,0,0,0,9,14,4,13,10,0,0,0,0,11,16,14,2,0,6 +0,0,7,14,16,12,0,0,0,0,15,11,15,15,0,0,0,0,0,1,16,10,0,0,0,0,7,13,16,14,10,0,0,0,12,16,15,12,4,0,0,0,5,16,6,0,0,0,0,0,7,16,2,0,0,0,0,0,10,14,0,0,0,0,7 +0,0,7,9,13,14,4,0,0,0,9,7,6,16,8,0,0,0,0,1,13,10,0,0,0,0,0,14,14,1,0,0,0,0,0,10,16,4,0,0,0,0,0,2,11,14,0,0,0,1,10,9,8,16,3,0,0,1,9,13,12,8,0,0,3 +0,0,5,12,0,0,0,0,0,0,11,12,0,0,0,0,0,0,14,10,0,0,0,0,0,0,14,14,7,1,0,0,0,3,16,16,16,14,1,0,0,5,16,16,10,15,5,0,0,0,13,16,11,15,11,0,0,0,4,12,14,14,3,0,6 +0,0,4,12,12,15,7,0,0,0,7,12,12,15,9,0,0,0,0,0,1,13,5,0,0,0,3,8,10,16,1,0,0,0,8,16,16,16,5,0,0,0,0,6,12,1,0,0,0,0,0,14,5,0,0,0,0,0,3,15,0,0,0,0,7 +0,0,12,16,14,2,0,0,0,7,11,2,7,12,0,0,0,11,15,12,14,11,0,0,0,2,8,8,13,12,0,0,0,0,0,0,7,14,0,0,0,0,0,0,1,14,5,0,0,0,12,12,5,10,12,0,0,0,8,8,12,16,6,0,9 +0,0,0,8,12,11,0,0,0,0,7,16,16,8,0,0,0,0,7,16,16,9,0,0,0,0,8,16,16,6,0,0,0,0,5,16,16,8,0,0,0,0,9,16,16,8,0,0,0,0,5,16,16,11,0,0,0,0,0,12,12,5,0,0,1 +0,2,11,16,5,0,0,0,0,11,16,16,9,0,0,0,0,2,6,16,9,0,0,0,0,0,8,16,5,0,0,0,0,0,14,11,0,0,0,0,0,6,16,5,0,0,2,0,0,6,16,14,13,15,12,0,0,0,15,16,13,11,3,0,2 +0,0,3,9,15,8,0,0,0,1,15,16,16,7,0,0,0,0,5,16,16,10,4,0,0,0,3,16,16,16,9,0,0,0,0,15,14,4,0,0,0,0,0,13,5,0,0,0,0,0,1,15,3,0,0,0,0,0,4,13,0,0,0,0,7 +0,0,4,15,16,7,0,0,0,0,13,12,16,9,0,0,0,0,2,1,16,7,0,0,0,0,0,5,15,3,0,0,0,0,0,14,10,0,0,0,0,0,6,16,2,0,0,0,0,0,9,16,11,7,0,0,0,0,5,16,16,10,0,0,2 +0,0,4,13,11,2,0,0,0,0,2,13,16,7,0,0,0,0,0,7,16,15,0,0,0,0,0,3,16,15,4,0,0,0,0,7,16,16,1,0,0,0,0,10,16,14,0,0,0,0,1,13,15,4,0,0,0,0,8,16,14,0,0,0,1 +0,0,4,14,16,10,0,0,0,0,3,8,14,15,2,0,0,0,0,0,10,16,5,0,0,0,6,11,15,16,7,0,0,0,7,14,16,13,7,0,0,0,0,12,12,0,0,0,0,0,4,16,6,0,0,0,0,0,5,14,3,0,0,0,7 +0,0,4,14,3,0,0,0,0,0,12,11,0,0,0,0,0,1,16,4,0,0,0,0,0,0,16,0,0,0,0,0,0,4,16,9,12,10,2,0,0,6,16,9,8,14,9,0,0,0,13,7,4,16,9,0,0,0,3,15,16,8,0,0,6 +0,0,0,11,16,14,3,0,0,0,8,16,16,16,6,0,0,1,16,6,0,0,0,0,0,5,16,8,7,1,0,0,0,3,16,16,16,11,0,0,0,0,1,5,10,16,2,0,0,0,0,7,12,14,0,0,0,0,0,13,15,3,0,0,5 +0,0,8,15,16,8,0,0,0,0,16,13,14,16,4,0,0,5,14,1,2,16,6,0,0,7,12,0,0,12,5,0,0,4,16,1,0,11,8,0,0,1,16,9,4,13,6,0,0,1,16,16,16,11,0,0,0,0,7,15,16,2,0,0,0 +0,0,0,12,12,0,0,0,0,0,2,16,10,0,0,0,0,0,9,16,6,0,0,0,0,3,16,11,3,12,2,0,0,12,16,16,16,16,7,0,0,3,8,13,16,13,1,0,0,0,0,9,16,9,0,0,0,0,0,11,16,5,0,0,4 +0,0,0,10,6,0,0,0,0,0,7,16,16,4,0,0,0,0,12,16,16,3,0,0,0,0,13,16,16,3,0,0,0,2,15,16,16,2,0,0,0,0,12,16,16,5,0,0,0,0,4,16,16,14,1,0,0,0,0,6,12,9,1,0,1 +0,0,4,15,7,0,0,0,0,3,16,11,2,0,0,0,0,11,11,0,8,11,1,0,0,7,14,11,15,2,0,0,0,1,13,16,4,0,0,0,0,0,8,16,8,0,0,0,0,0,8,16,16,2,0,0,0,0,2,12,15,5,0,0,8 +0,0,8,13,9,0,0,0,0,5,16,13,12,2,0,0,0,8,13,2,7,10,2,0,0,4,15,15,16,9,0,0,0,1,14,16,0,0,0,0,0,0,16,14,5,0,0,0,0,2,13,10,12,0,0,0,0,0,9,16,8,0,0,0,8 +0,0,3,11,16,16,13,0,0,0,15,16,13,11,5,0,0,3,16,8,1,0,0,0,0,5,16,11,4,0,0,0,0,1,8,15,16,3,0,0,0,0,0,5,16,5,0,0,0,0,1,10,16,2,0,0,0,0,5,11,7,0,0,0,5 +0,0,3,15,12,12,6,0,0,0,13,13,13,16,12,0,0,5,11,0,0,0,1,0,0,5,16,15,7,0,0,0,0,0,9,14,16,0,0,0,0,0,0,1,15,2,0,0,0,0,1,12,11,0,0,0,0,0,3,14,1,0,0,0,5 +0,0,5,12,9,1,0,0,0,4,16,9,11,6,1,0,0,1,15,4,1,12,4,0,0,0,6,6,13,14,0,0,0,0,3,16,16,4,0,0,0,0,7,16,16,1,0,0,0,0,10,16,16,7,0,0,0,0,8,12,12,4,0,0,8 +0,0,0,2,13,11,0,0,0,0,4,11,16,16,6,0,0,2,15,16,16,14,0,0,0,0,3,10,16,12,0,0,0,0,0,12,16,10,0,0,0,0,0,6,16,8,0,0,0,0,0,4,16,13,0,0,0,0,0,3,14,15,0,0,1 +0,0,4,13,11,0,0,0,0,0,1,13,16,7,0,0,0,0,0,0,14,9,0,0,0,0,1,6,16,14,5,0,0,1,13,16,16,10,3,0,0,2,8,13,10,0,0,0,0,0,1,16,2,0,0,0,0,0,4,13,1,0,0,0,7 +0,0,7,16,16,16,2,0,0,0,14,14,9,16,2,0,0,2,16,6,0,9,7,0,0,4,16,1,0,8,8,0,0,6,16,0,0,12,8,0,0,5,16,4,2,16,5,0,0,0,15,16,16,13,0,0,0,0,8,13,10,2,0,0,0 +0,0,13,16,7,0,0,0,0,2,16,16,15,0,0,0,0,0,4,14,16,0,0,0,0,0,0,12,13,0,0,0,0,0,0,15,12,0,0,0,0,0,11,16,1,1,1,0,0,0,16,16,16,16,6,0,0,0,13,16,16,12,0,0,2 +0,0,4,15,16,16,10,0,0,0,13,15,12,14,14,0,0,0,14,7,0,0,0,0,0,0,16,16,12,8,0,0,0,0,2,7,10,16,4,0,0,0,0,0,2,16,3,0,0,0,1,9,12,13,0,0,0,0,2,16,11,3,0,0,5 +0,0,3,14,16,16,6,0,0,0,8,16,16,16,9,0,0,0,0,0,7,16,6,0,0,0,0,0,12,15,1,0,0,0,2,13,16,16,7,0,0,0,2,16,16,14,2,0,0,0,6,16,9,0,0,0,0,0,7,14,2,0,0,0,7 +0,0,0,1,15,10,0,0,0,0,0,9,16,4,0,0,0,0,1,15,12,0,0,0,0,0,9,16,3,0,0,0,0,1,15,10,0,15,11,0,0,8,16,16,16,16,8,0,0,0,4,8,16,12,0,0,0,0,0,4,16,7,0,0,4 +0,1,11,16,16,13,2,0,0,6,16,16,8,13,8,0,0,0,10,16,14,15,8,0,0,0,0,7,12,14,5,0,0,0,0,0,0,8,4,0,0,0,0,0,0,11,4,0,0,0,8,8,9,15,4,0,0,2,13,16,16,9,1,0,9 +0,0,0,8,12,15,6,0,0,0,8,16,6,5,14,0,0,0,14,15,12,16,13,0,0,0,3,6,7,15,8,0,0,0,0,0,1,15,2,0,0,0,0,0,10,9,0,0,0,0,0,4,13,1,0,0,0,0,0,11,2,0,0,0,9 +0,0,8,13,16,14,0,0,0,3,16,10,16,13,0,0,0,0,2,11,16,6,0,0,0,0,2,16,15,8,0,0,0,0,1,13,16,16,7,0,0,0,0,1,8,16,7,0,0,0,5,16,16,11,0,0,0,0,9,14,5,0,0,0,3 +0,0,1,7,14,15,2,0,0,2,12,14,8,14,7,0,0,3,14,13,5,14,5,0,0,0,5,15,16,15,2,0,0,0,0,10,16,10,0,0,0,0,2,14,14,12,0,0,0,0,4,16,14,12,0,0,0,0,0,12,13,6,0,0,8 +0,0,8,12,14,3,0,0,0,10,16,14,15,13,0,0,0,7,5,0,9,15,0,0,0,0,0,0,12,11,0,0,0,0,0,0,9,16,2,0,0,0,0,0,0,14,9,0,0,0,11,12,14,16,5,0,0,0,8,14,9,4,0,0,3 +0,0,2,14,5,0,0,0,0,0,10,13,4,0,0,0,0,0,12,9,0,0,0,0,0,0,12,8,0,0,0,0,0,0,12,14,16,15,3,0,0,0,13,14,6,9,10,0,0,0,13,12,7,14,8,0,0,0,2,15,15,10,1,0,6 +0,0,4,16,5,0,0,0,0,0,9,16,2,0,0,0,0,0,11,13,0,0,0,0,0,0,11,12,0,0,0,0,0,0,15,15,13,12,5,0,0,0,13,16,15,11,15,2,0,0,12,16,9,14,15,2,0,0,5,15,16,13,2,0,6 +0,2,11,14,4,0,0,0,0,0,16,13,16,4,0,0,0,0,0,3,16,3,0,0,0,0,0,11,12,0,0,0,0,0,4,16,5,0,0,0,0,1,15,12,0,0,0,0,0,5,16,10,8,11,2,0,0,2,14,16,13,9,5,0,2 +0,0,7,15,13,3,0,0,0,0,16,8,9,13,0,0,0,3,13,0,1,13,3,0,0,7,14,4,0,5,8,0,0,8,16,8,0,10,8,0,0,7,16,9,0,15,3,0,0,2,16,16,11,14,1,0,0,0,5,15,15,3,0,0,0 +0,0,4,15,10,2,0,0,0,0,3,16,15,4,0,0,0,0,6,16,16,2,0,0,0,0,7,16,11,0,0,0,0,0,9,16,7,0,0,0,0,0,12,16,4,0,0,0,0,0,9,16,4,0,0,0,0,0,4,15,8,0,0,0,1 +0,0,1,12,15,2,0,0,0,0,11,13,8,10,0,0,0,4,16,13,1,10,0,0,0,8,15,3,0,6,3,0,0,8,12,0,0,8,5,0,0,2,16,8,0,8,8,0,0,0,10,11,7,15,4,0,0,0,0,13,15,6,0,0,0 +0,0,7,15,14,3,0,0,0,4,16,13,14,13,0,0,0,8,16,5,0,12,4,0,0,8,16,8,0,8,8,0,0,8,16,5,0,9,8,0,0,5,16,10,7,16,3,0,0,0,13,16,16,7,0,0,0,0,6,15,11,1,0,0,0 +0,0,0,13,14,0,0,0,0,0,8,15,3,5,3,0,0,2,16,8,1,16,10,0,0,11,16,8,10,16,6,0,0,5,16,16,16,16,6,0,0,0,0,9,15,0,0,0,0,0,0,12,10,0,0,0,0,0,0,16,2,0,0,0,4 +0,1,4,8,14,16,6,0,0,11,16,16,16,13,3,0,0,9,16,5,2,0,0,0,0,3,16,11,3,0,0,0,0,0,6,16,16,5,0,0,0,0,0,2,16,8,0,0,0,0,0,12,16,3,0,0,0,0,0,12,11,0,0,0,5 +0,3,9,12,12,16,10,0,0,7,16,16,16,14,6,0,0,7,16,11,2,0,0,0,0,10,16,10,2,0,0,0,0,1,11,16,16,4,0,0,0,0,1,12,16,5,0,0,0,0,13,16,6,0,0,0,0,2,15,9,0,0,0,0,5 +0,0,5,10,12,16,16,2,0,0,10,16,16,10,6,0,0,0,5,16,5,0,0,0,0,0,0,12,16,2,0,0,0,0,0,0,12,12,0,0,0,0,0,0,13,9,0,0,0,0,1,12,13,2,0,0,0,0,7,13,2,0,0,0,5 +0,0,0,12,13,0,0,0,0,0,4,16,16,9,0,0,0,0,2,16,6,14,0,0,0,4,3,13,7,9,5,0,0,8,12,3,16,12,8,0,0,0,14,5,1,12,8,0,0,0,10,14,13,16,2,0,0,0,0,13,16,6,0,0,0 +0,0,0,5,14,13,3,0,0,0,11,16,16,16,9,0,0,2,16,16,16,16,11,0,0,0,6,8,8,13,8,0,0,0,0,0,1,14,3,0,0,0,0,0,9,9,0,0,0,0,0,1,13,2,0,0,0,0,0,5,6,0,0,0,9 +0,0,6,16,16,16,9,0,0,0,5,8,4,14,13,0,0,0,0,0,0,15,9,0,0,0,0,4,11,16,2,0,0,0,0,14,16,16,10,0,0,0,0,16,12,8,2,0,0,0,8,16,3,0,0,0,0,0,9,14,1,0,0,0,7 +0,0,6,16,15,3,0,0,0,4,16,13,16,6,0,0,0,3,6,0,16,5,0,0,0,0,0,6,16,1,0,0,0,0,0,14,11,0,0,0,0,0,8,14,1,0,0,0,0,0,12,14,8,6,2,0,0,0,8,16,16,16,3,0,2 +0,0,12,13,3,0,0,0,0,4,16,14,10,0,0,0,0,2,4,6,10,0,0,0,0,0,0,9,11,0,0,0,0,0,2,15,1,0,0,0,0,0,10,11,0,0,0,0,0,4,16,10,6,4,3,0,0,1,10,15,16,16,9,0,2 +0,1,9,16,16,7,0,0,0,7,15,12,15,12,0,0,0,0,2,0,9,14,0,0,0,0,0,0,15,8,0,0,0,0,5,13,16,14,6,0,0,5,16,16,14,8,2,0,0,0,11,16,6,0,0,0,0,0,11,13,2,0,0,0,7 +0,0,0,2,15,2,0,0,0,0,0,10,8,0,8,3,0,0,6,12,0,3,15,0,0,2,14,1,0,12,8,0,0,9,15,12,16,16,5,0,0,3,8,5,11,10,0,0,0,0,0,1,15,4,0,0,0,0,0,3,9,0,0,0,4 +0,0,1,13,3,0,0,0,0,0,4,15,1,0,0,0,0,0,9,10,0,0,0,0,0,0,10,11,0,0,0,0,0,0,11,12,6,5,0,0,0,0,14,16,16,16,8,0,0,0,16,16,11,12,13,0,0,0,3,8,14,10,6,0,6 +0,0,4,11,12,16,10,0,0,3,16,16,14,7,0,0,0,0,16,8,0,0,0,0,0,0,12,10,0,0,0,0,0,0,2,14,10,1,0,0,0,0,0,1,14,11,0,0,0,0,0,1,13,14,2,0,0,0,7,14,10,2,0,0,5 +0,0,3,10,14,14,3,0,0,0,15,6,9,16,4,0,0,0,1,5,14,6,0,0,0,0,7,16,9,1,0,0,0,0,2,9,13,15,1,0,0,0,0,0,4,16,2,0,0,0,0,7,15,6,0,0,0,0,0,14,7,0,0,0,3 +0,0,0,5,8,12,10,0,0,0,11,14,4,15,8,0,0,4,16,14,11,16,7,0,0,7,16,16,16,14,7,0,0,1,8,6,0,7,8,0,0,0,0,0,0,5,8,0,0,0,0,0,3,13,6,0,0,0,0,4,16,8,0,0,9 +0,1,13,5,0,0,0,0,0,4,14,0,0,0,0,0,0,4,8,0,1,6,1,0,0,5,8,1,11,16,5,0,0,4,10,7,16,16,7,0,0,4,14,10,16,16,3,0,0,1,16,16,16,10,0,0,0,0,10,14,9,2,0,0,6 +0,0,2,13,15,1,0,0,0,2,13,15,5,9,0,0,0,7,15,2,0,8,0,0,0,8,15,0,0,5,4,0,0,7,16,0,0,9,6,0,0,0,14,7,0,13,7,0,0,0,10,15,14,14,0,0,0,0,0,14,14,5,0,0,0 +0,0,10,15,16,6,0,0,0,7,16,14,16,11,0,0,0,6,5,11,16,3,0,0,0,0,0,12,16,6,0,0,0,0,0,1,13,16,5,0,0,0,0,0,2,16,9,0,0,0,3,8,13,16,7,0,0,0,12,16,15,4,0,0,3 +0,0,6,14,14,3,0,0,0,9,16,3,8,13,0,0,0,8,15,13,16,16,2,0,0,1,12,12,9,14,5,0,0,0,0,0,0,10,6,0,0,0,0,0,0,10,5,0,0,0,0,1,6,13,2,0,0,0,7,12,7,2,0,0,9 +0,1,12,14,2,0,0,0,0,8,16,16,7,0,0,0,0,2,8,10,8,0,0,0,0,0,0,13,8,0,0,0,0,0,3,16,2,0,0,0,0,0,14,8,0,0,0,0,0,0,16,10,9,10,6,0,0,2,15,13,12,8,1,0,2 +0,0,5,15,10,2,0,0,0,0,5,16,16,11,0,0,0,0,1,16,16,16,1,0,0,0,3,14,16,11,0,0,0,0,6,16,16,10,0,0,0,0,6,16,16,2,0,0,0,0,5,16,16,9,0,0,0,0,4,16,14,10,0,0,1 +0,0,15,15,2,0,0,0,0,4,16,11,13,1,0,0,0,3,15,5,15,6,0,0,0,0,3,1,8,12,0,0,0,0,0,0,6,15,0,0,0,0,0,0,9,12,0,0,0,0,5,9,16,10,1,0,0,0,13,16,16,16,16,6,2 +0,0,0,9,13,0,0,0,0,0,5,16,6,0,0,0,0,1,13,11,1,0,2,0,0,8,16,1,0,10,13,0,0,11,16,4,10,16,5,0,0,12,16,16,16,10,0,0,0,0,4,5,16,3,0,0,0,0,0,10,16,0,0,0,4 +0,0,5,15,8,0,0,0,0,0,13,8,12,5,0,0,0,1,16,1,6,15,2,0,0,1,16,1,2,16,6,0,0,3,14,0,0,11,9,0,0,3,16,1,0,7,10,0,0,0,15,4,2,15,6,0,0,0,5,14,15,10,1,0,0 +0,0,12,7,0,0,0,0,0,3,16,4,0,0,0,0,0,7,15,0,0,0,0,0,0,8,12,3,8,2,0,0,0,12,14,16,16,16,4,0,0,9,16,13,3,9,13,0,0,3,16,9,5,13,12,0,0,0,10,16,16,13,1,0,6 +0,0,4,10,16,16,11,1,0,0,12,13,11,8,7,0,0,3,16,16,16,4,0,0,0,5,16,9,15,6,0,0,0,0,0,0,6,11,0,0,0,0,0,0,11,8,0,0,0,0,1,8,13,2,0,0,0,0,4,13,4,0,0,0,5 +0,0,0,9,13,0,0,0,0,0,8,15,1,0,0,0,0,2,14,10,0,2,15,3,0,6,16,4,1,13,13,0,0,2,16,15,13,14,3,0,0,0,5,11,16,8,0,0,0,0,0,10,14,1,0,0,0,0,0,13,12,0,0,0,4 +0,0,8,16,16,12,0,0,0,4,16,16,16,15,2,0,0,2,11,16,16,11,0,0,0,0,0,15,16,7,0,0,0,0,0,0,11,16,2,0,0,0,7,0,0,15,5,0,0,2,16,7,7,15,7,0,0,0,8,14,12,5,0,0,3 +0,0,10,7,0,0,0,0,0,0,15,9,0,0,0,0,0,2,16,2,0,0,0,0,0,7,16,1,5,3,0,0,0,8,16,13,16,15,2,0,0,8,16,16,12,16,8,0,0,3,16,15,12,16,4,0,0,0,9,16,15,8,0,0,6 +0,2,13,16,16,11,0,0,0,7,13,4,11,14,0,0,0,0,1,9,16,7,0,0,0,0,7,16,14,1,0,0,0,0,0,9,15,13,1,0,0,0,0,0,3,16,5,0,0,0,6,0,5,15,7,0,0,0,15,16,16,10,1,0,3 +0,0,5,16,5,0,0,0,0,0,11,15,2,0,0,0,0,1,16,10,0,0,0,0,0,6,16,3,0,0,0,0,0,8,16,15,16,14,2,0,0,8,16,12,8,13,11,0,0,8,16,12,2,13,9,0,0,0,5,13,16,15,1,0,6 +0,1,11,16,16,15,1,0,0,3,15,7,10,16,3,0,0,0,0,4,16,8,0,0,0,0,0,5,15,10,0,0,0,0,0,0,3,16,8,0,0,0,0,0,0,11,13,0,0,4,11,1,1,11,14,0,0,1,13,16,16,15,6,0,3 +0,0,7,16,14,2,0,0,0,0,13,13,9,13,0,0,0,0,11,10,0,14,5,0,0,0,5,14,0,12,9,0,0,0,0,0,0,12,8,0,0,0,0,0,4,16,5,0,0,0,2,8,14,15,0,0,0,0,8,16,16,16,16,4,2 +0,0,8,15,16,13,0,0,0,3,16,4,1,16,4,0,0,0,15,5,1,16,8,0,0,0,14,11,16,8,0,0,0,0,6,16,8,0,0,0,0,0,10,16,8,0,0,0,0,0,16,14,16,2,0,0,0,0,8,16,15,2,0,0,8 +0,1,12,16,15,2,0,0,0,6,16,8,14,10,0,0,0,1,8,3,15,7,0,0,0,0,1,16,16,5,0,0,0,0,0,3,13,16,4,0,0,0,6,0,2,14,8,0,0,1,16,7,7,15,7,0,0,0,12,16,16,10,1,0,3 +0,2,16,15,4,0,0,0,0,4,16,14,9,3,5,0,0,4,15,7,16,16,8,0,0,1,15,16,15,6,0,0,0,0,10,16,4,0,0,0,0,1,14,16,2,0,0,0,0,7,10,16,0,0,0,0,0,2,14,11,0,0,0,0,8 +0,1,14,16,9,0,0,0,0,1,11,15,15,2,0,0,0,0,1,13,16,2,0,0,0,0,10,16,16,15,9,0,0,0,9,16,13,10,3,0,0,0,5,16,1,0,0,0,0,0,15,13,0,0,0,0,0,1,16,4,0,0,0,0,7 +0,0,12,16,14,4,0,0,0,2,16,16,16,6,0,0,0,0,12,16,16,4,0,0,0,0,15,16,16,5,0,0,0,0,16,16,16,4,0,0,0,4,16,16,15,3,0,0,0,1,14,16,16,5,0,0,0,0,10,16,16,10,0,0,1 +0,0,2,14,16,16,14,0,0,0,2,6,4,9,16,6,0,0,0,0,0,4,16,4,0,0,0,0,0,8,15,1,0,0,2,15,13,15,8,0,0,0,3,8,14,14,2,0,0,0,0,5,16,1,0,0,0,0,2,16,6,0,0,0,7 +0,3,14,16,7,1,0,0,0,12,16,9,16,11,0,0,0,12,16,11,16,12,0,0,0,1,10,12,15,16,0,0,0,0,0,0,4,16,4,0,0,0,0,0,0,13,11,0,0,1,6,4,8,16,11,0,0,2,9,16,16,10,1,0,9 +0,0,12,16,15,1,0,0,0,5,16,7,11,13,0,0,0,4,16,1,7,16,8,0,0,2,13,16,16,16,8,0,0,0,1,6,2,8,13,0,0,0,0,0,0,4,16,0,0,1,5,2,4,8,16,1,0,1,11,16,16,16,8,0,9 +0,0,0,12,16,15,3,0,0,0,4,10,12,11,2,0,0,6,14,0,0,0,0,0,0,8,16,15,5,0,0,0,0,1,8,13,16,2,0,0,0,0,0,0,10,9,0,0,0,0,5,11,13,11,0,0,0,0,1,14,12,2,0,0,5 +0,3,14,15,16,15,8,0,0,4,16,12,8,7,2,0,0,6,16,15,8,0,0,0,0,8,16,13,16,4,0,0,0,1,1,0,10,10,0,0,0,0,0,0,8,12,0,0,0,5,8,6,15,10,0,0,0,3,16,16,13,1,0,0,5 +0,0,5,15,15,11,0,0,0,0,16,5,0,11,5,0,0,0,15,3,0,12,10,0,0,0,5,13,0,12,6,0,0,0,0,10,16,9,0,0,0,0,0,10,16,4,0,0,0,0,6,12,8,9,0,0,0,0,6,16,12,4,0,0,8 +0,0,0,0,14,15,2,0,0,0,0,6,16,16,4,0,0,0,3,13,16,16,0,0,0,1,13,16,16,16,3,0,0,9,16,6,14,16,0,0,0,2,4,0,14,15,0,0,0,0,0,0,16,12,0,0,0,0,0,0,14,13,0,0,1 +0,0,0,10,12,0,0,0,0,0,6,16,3,0,0,0,0,0,14,10,0,0,3,1,0,6,16,2,0,6,16,2,0,14,16,5,9,15,6,0,0,10,16,14,16,12,0,0,0,0,0,4,16,2,0,0,0,0,0,10,11,0,0,0,4 +0,0,5,12,16,16,9,0,0,3,16,9,3,0,0,0,0,7,16,11,5,0,0,0,0,2,11,9,16,1,0,0,0,0,0,0,9,9,0,0,0,0,0,0,9,9,0,0,0,0,9,6,16,6,0,0,0,0,5,12,6,0,0,0,5 +0,0,0,9,9,0,0,0,0,0,4,16,2,0,0,0,0,0,12,9,0,1,12,4,0,8,15,1,0,11,12,1,0,10,14,6,13,16,5,0,0,8,16,11,16,10,0,0,0,0,0,5,15,2,0,0,0,0,0,12,7,0,0,0,4 +0,0,5,12,14,3,0,0,0,3,14,2,4,11,0,0,0,4,12,0,5,16,4,0,0,1,14,6,14,4,0,0,0,0,7,16,4,0,0,0,0,0,7,16,4,0,0,0,0,0,10,13,11,0,0,0,0,0,7,16,11,0,0,0,8 +0,0,5,14,15,5,0,0,0,2,16,9,9,15,2,0,0,2,10,0,6,16,3,0,0,0,2,14,16,7,0,0,0,0,0,11,16,14,1,0,0,0,6,3,6,16,1,0,0,0,13,11,10,15,0,0,0,0,9,16,16,7,0,0,3 +0,0,0,1,11,10,0,0,0,0,0,5,16,16,2,0,0,0,0,6,16,16,2,0,0,0,0,12,16,16,0,0,0,0,7,16,16,8,0,0,0,6,16,16,16,9,0,0,0,2,12,12,16,11,0,0,0,0,0,0,12,11,0,0,1 +0,0,6,16,3,0,0,0,0,0,13,13,0,0,0,0,0,1,16,8,0,0,0,0,0,6,16,1,0,0,0,0,0,8,14,6,9,6,0,0,0,7,16,15,12,14,12,0,0,2,16,9,0,1,16,3,0,0,5,15,16,16,14,1,6 +0,3,16,16,16,16,3,0,0,6,16,10,6,2,0,0,0,10,14,2,0,0,0,0,0,11,16,16,5,0,0,0,0,1,2,10,13,0,0,0,0,0,0,1,16,3,0,0,0,1,6,6,16,2,0,0,0,2,13,16,11,0,0,0,5 +0,0,0,0,11,12,0,0,0,0,0,0,11,16,2,0,0,0,0,0,15,13,0,0,0,0,6,16,16,12,0,0,0,7,16,16,16,8,0,0,0,1,4,7,16,11,0,0,0,0,0,2,15,13,0,0,0,0,0,0,8,16,4,0,1 +0,0,0,1,10,16,6,0,0,0,3,14,8,6,16,6,0,0,11,4,0,6,16,2,0,4,12,2,5,16,11,0,0,6,16,15,8,15,4,0,0,0,0,0,4,9,0,0,0,0,0,0,10,4,0,0,0,0,0,1,13,1,0,0,9 +0,0,1,15,16,15,5,0,0,0,8,13,4,3,1,0,0,2,14,9,7,1,0,0,0,5,16,16,15,10,0,0,0,0,0,0,1,14,3,0,0,0,0,0,1,14,2,0,0,0,1,4,13,10,0,0,0,0,1,14,9,1,0,0,5 +0,0,4,10,12,12,13,4,0,0,6,8,4,6,15,2,0,0,0,0,0,12,6,0,0,0,0,0,7,12,0,0,0,5,16,16,16,13,1,0,0,2,4,13,10,0,0,0,0,0,3,16,2,0,0,0,0,0,7,14,0,0,0,0,7 +0,0,6,15,14,4,0,0,0,0,16,6,4,15,0,0,0,5,16,6,13,11,0,0,0,1,16,16,16,13,1,0,0,6,15,7,0,11,8,0,0,4,12,0,0,9,11,0,0,0,16,4,9,15,2,0,0,0,8,14,9,2,0,0,8 +0,0,0,0,6,11,0,0,0,0,0,3,15,12,0,0,0,0,5,14,10,12,0,0,0,2,15,6,5,9,0,0,0,6,15,12,15,16,8,0,0,1,8,8,12,12,2,0,0,0,0,0,8,8,0,0,0,0,0,0,5,11,0,0,4 +0,2,8,16,11,1,0,0,0,10,15,5,11,5,0,0,0,11,10,13,16,6,0,0,0,9,16,16,12,13,2,0,0,1,15,8,0,6,9,0,0,0,12,5,0,5,11,0,0,0,9,9,7,16,8,0,0,0,4,14,15,6,0,0,8 +0,2,9,15,12,1,0,0,0,6,16,10,14,6,0,0,0,1,1,6,16,4,0,0,0,3,13,16,16,8,0,0,0,3,12,7,7,16,4,0,0,0,0,0,0,13,9,0,0,3,15,6,12,16,4,0,0,0,10,16,14,7,1,0,3 +0,0,4,16,16,15,4,0,0,0,10,10,5,3,0,0,0,6,14,1,4,2,0,0,0,8,15,14,15,15,2,0,0,5,12,5,0,9,8,0,0,0,0,0,0,8,8,0,0,0,5,9,7,14,2,0,0,0,3,16,11,4,0,0,5 +0,4,15,16,16,16,5,0,0,7,16,13,6,4,1,0,0,10,16,6,2,0,0,0,0,9,16,16,16,4,0,0,0,0,0,1,14,10,0,0,0,0,0,0,12,12,0,0,0,3,9,11,16,7,0,0,0,3,15,16,6,0,0,0,5 +0,0,3,15,11,4,0,0,0,0,11,7,6,13,0,0,0,0,12,2,0,15,1,0,0,0,11,1,4,16,5,0,0,0,3,15,13,12,6,0,0,0,0,0,0,8,8,0,0,0,6,2,3,13,5,0,0,0,4,13,16,11,0,0,9 +0,0,4,14,15,2,0,0,0,4,16,7,8,9,0,0,0,6,14,0,0,12,2,0,0,3,16,0,0,7,7,0,0,0,14,0,0,4,8,0,0,0,11,4,0,5,9,0,0,0,6,15,8,14,7,0,0,0,1,13,15,10,0,0,0 +0,0,2,12,13,4,0,0,0,5,16,6,1,14,0,0,0,13,11,5,12,15,0,0,0,9,16,16,16,12,0,0,0,1,12,14,3,7,7,0,0,0,8,7,0,0,13,0,0,0,8,9,1,12,13,0,0,0,0,14,14,12,3,0,8 +0,0,8,16,10,1,0,0,0,7,16,11,15,8,0,0,0,15,13,0,11,12,0,0,0,10,7,0,11,10,0,0,0,0,0,1,15,8,0,0,0,0,0,9,15,1,0,0,0,0,7,16,10,6,8,3,0,0,9,16,16,16,16,7,2 +0,0,5,15,4,0,0,0,0,2,16,11,1,0,0,0,0,6,14,0,0,0,0,0,0,6,12,4,4,1,0,0,0,7,16,16,16,15,3,0,0,0,16,9,0,9,13,0,0,0,10,12,8,12,9,0,0,0,2,12,14,8,1,0,6 +0,0,7,15,16,12,0,0,0,0,9,6,3,15,2,0,0,0,0,4,13,9,0,0,0,0,9,16,16,6,0,0,0,0,5,5,6,15,4,0,0,0,8,0,0,4,12,0,0,1,13,1,3,12,8,0,0,0,8,16,15,10,0,0,3 +0,0,6,16,12,3,0,0,0,3,15,7,6,14,1,0,0,7,13,0,1,14,1,0,0,4,13,8,13,13,0,0,0,2,15,15,7,13,5,0,0,0,12,5,0,9,8,0,0,0,12,5,6,14,2,0,0,0,5,16,12,3,0,0,8 +0,2,15,16,7,0,0,0,0,7,16,13,16,3,0,0,0,3,16,1,16,7,0,0,0,0,2,5,16,3,0,0,0,0,0,11,13,0,0,0,0,0,3,15,7,0,0,0,0,0,13,16,12,10,7,0,0,2,15,16,16,16,16,5,2 +0,0,4,10,0,0,0,0,0,0,2,15,2,0,0,0,0,0,2,16,4,0,0,0,0,0,7,16,9,0,0,0,0,0,14,14,14,0,0,0,0,0,3,3,16,3,0,0,0,0,1,7,14,11,4,0,0,0,4,14,16,13,14,8,1 +0,0,9,14,2,0,0,0,0,0,5,16,7,0,0,0,0,0,6,16,10,0,0,0,0,2,15,16,10,0,0,0,0,6,16,15,14,0,0,0,0,0,0,10,16,2,0,0,0,0,9,15,16,14,12,3,0,0,6,15,16,16,12,3,1 +0,0,4,14,4,0,0,0,0,0,13,12,2,0,0,0,0,0,16,2,0,0,0,0,0,1,16,16,16,9,0,0,0,2,16,5,0,8,9,0,0,0,14,4,0,1,14,0,0,0,10,9,4,12,13,0,0,0,4,15,15,9,2,0,6 +0,0,7,16,6,0,0,0,0,0,16,13,1,0,0,0,0,2,16,3,0,0,0,0,0,3,16,4,4,2,0,0,0,5,16,16,16,15,4,0,0,4,16,6,4,7,16,1,0,0,16,8,4,12,15,2,0,0,6,16,16,14,6,0,6 +0,1,8,14,16,12,0,0,0,1,12,9,11,16,0,0,0,0,0,9,15,5,0,0,0,2,14,16,7,0,0,0,0,1,11,13,16,9,0,0,0,0,0,0,2,16,4,0,0,0,11,8,14,13,0,0,0,0,11,14,11,1,0,0,3 +0,0,0,6,15,2,11,2,0,0,2,16,10,5,16,3,0,1,15,10,0,9,13,0,0,9,16,9,9,16,12,0,0,7,16,16,15,15,7,0,0,0,3,2,14,8,0,0,0,0,0,6,14,1,0,0,0,0,0,11,9,0,0,0,4 +0,2,13,16,14,3,0,0,0,2,8,11,16,8,0,0,0,0,3,16,12,1,0,0,0,0,1,11,14,2,0,0,0,0,0,0,7,13,0,0,0,0,0,0,0,11,7,0,0,7,11,4,4,13,7,0,0,2,11,16,12,10,0,0,3 +0,0,0,1,14,1,14,0,0,0,0,12,11,6,13,0,0,0,8,13,2,10,9,0,0,6,14,2,1,14,11,0,0,11,15,12,16,16,9,0,0,7,14,12,14,12,0,0,0,0,0,0,12,8,0,0,0,0,0,0,14,3,0,0,4 +0,0,2,12,16,16,11,0,0,0,12,15,9,6,8,0,0,3,16,5,3,2,0,0,0,10,16,15,16,13,0,0,0,8,16,13,10,16,0,0,0,0,3,0,11,13,0,0,0,0,0,9,16,5,0,0,0,0,1,16,11,0,0,0,5 +0,0,4,10,14,5,0,0,0,0,13,10,0,14,4,0,0,6,15,0,9,15,2,0,0,6,16,16,16,13,0,0,0,0,12,15,7,12,4,0,0,0,10,9,0,10,7,0,0,0,11,9,7,15,3,0,0,0,5,15,12,3,0,0,8 +0,1,13,16,12,1,0,0,0,6,16,11,14,7,0,0,0,5,16,0,10,10,0,0,0,0,2,0,13,10,0,0,0,0,0,2,16,7,0,0,0,0,0,11,15,1,0,0,0,0,11,16,16,12,12,2,0,0,15,16,12,12,12,3,2 +0,1,9,14,14,3,0,0,0,2,12,10,12,11,0,0,0,0,0,3,14,11,0,0,0,0,11,16,16,7,0,0,0,0,10,7,11,16,5,0,0,0,0,0,0,5,12,0,0,0,8,5,6,12,12,0,0,0,12,16,15,10,3,0,3 +0,0,3,9,8,0,0,0,0,0,15,9,1,0,0,0,0,4,16,8,2,0,0,0,0,5,16,12,12,11,1,0,0,4,12,0,0,5,10,0,0,1,14,3,0,1,14,1,0,0,10,6,5,14,13,0,0,0,4,13,14,8,0,0,6 +0,1,6,8,13,8,0,0,0,5,16,10,5,15,0,0,0,8,15,6,13,13,0,0,0,5,16,16,14,15,1,0,0,0,14,9,0,6,6,0,0,0,16,1,0,6,8,0,0,0,16,5,9,15,2,0,0,0,11,16,10,2,0,0,8 +0,0,3,16,3,0,0,0,0,0,8,14,2,0,0,0,0,0,14,7,0,0,0,0,0,1,16,4,3,0,0,0,0,4,16,16,16,15,2,0,0,3,16,9,1,10,13,0,0,0,15,10,4,9,13,0,0,0,3,12,14,14,7,0,6 +0,0,2,9,15,16,7,0,0,0,15,9,8,15,9,0,0,2,15,3,4,16,2,0,0,4,15,15,16,2,0,0,0,0,1,16,16,9,0,0,0,0,10,10,10,16,3,0,0,0,10,12,10,16,4,0,0,0,2,13,12,8,1,0,8 +0,0,10,13,2,0,0,0,0,6,16,14,9,0,0,0,0,3,12,2,13,0,0,0,0,0,0,4,14,0,0,0,0,0,0,8,14,0,0,0,0,0,1,15,6,0,0,0,0,0,10,16,9,8,10,2,0,0,6,12,13,12,11,2,2 +0,0,3,10,14,7,0,0,0,4,16,12,14,12,0,0,0,0,7,0,11,9,0,0,0,0,0,11,16,3,0,0,0,0,0,12,16,16,5,0,0,0,0,0,0,14,8,0,0,0,1,8,11,15,3,0,0,0,5,13,10,2,0,0,3 +0,0,3,12,15,2,0,0,0,5,16,12,9,13,0,0,0,7,15,2,0,12,4,0,0,6,12,0,0,7,6,0,0,6,13,0,0,7,7,0,0,0,16,0,0,10,7,0,0,0,10,10,8,16,1,0,0,0,2,10,16,6,0,0,0 +0,0,5,13,16,7,0,0,0,2,16,7,8,11,0,0,0,8,15,5,10,8,0,0,0,2,6,15,16,7,0,0,0,0,1,13,10,16,4,0,0,0,4,13,1,9,12,0,0,0,7,10,5,15,7,0,0,0,2,13,15,8,1,0,8 +0,0,6,14,12,6,0,0,0,3,16,11,10,15,5,0,0,6,16,4,3,16,4,0,0,0,9,16,16,12,0,0,0,0,2,15,7,15,6,0,0,0,8,6,0,14,8,0,0,0,13,10,13,13,1,0,0,0,5,12,9,1,0,0,8 +0,0,1,11,14,3,0,0,0,0,6,16,12,2,0,0,0,0,13,11,0,0,0,0,0,1,14,9,3,0,0,0,0,6,16,16,15,7,0,0,0,4,16,11,7,15,6,0,0,0,12,14,6,11,10,0,0,0,3,11,16,15,6,0,6 +0,0,0,5,16,12,0,0,0,0,1,15,16,16,0,0,0,2,10,16,16,11,0,0,0,7,16,16,16,7,0,0,0,6,12,16,16,8,0,0,0,0,0,12,16,12,0,0,0,0,0,11,16,13,0,0,0,0,0,2,15,12,0,0,1 +0,0,0,7,13,5,0,0,0,0,6,15,11,5,0,0,0,0,15,8,0,0,0,0,0,4,16,3,7,7,1,0,0,3,16,16,15,15,6,0,0,1,15,8,0,8,11,0,0,0,11,13,7,14,10,0,0,0,0,9,16,12,3,0,6 +0,0,0,0,13,12,0,0,0,0,0,4,16,6,0,0,0,0,1,13,10,1,8,0,0,0,7,15,2,9,15,0,0,6,16,4,4,14,9,0,2,15,16,16,16,16,6,0,2,8,8,4,13,13,0,0,0,0,0,0,15,8,0,0,4 +0,0,2,13,7,0,0,0,0,0,12,14,4,0,0,0,0,0,15,3,0,0,0,0,0,6,14,2,6,0,0,0,0,7,16,16,16,13,1,0,0,4,16,8,1,12,10,0,0,1,12,9,4,9,15,0,0,0,2,11,16,15,4,0,6 +0,2,11,10,12,13,7,0,0,7,16,13,11,12,5,0,0,4,16,4,4,1,0,0,0,4,16,16,16,16,5,0,0,0,2,2,1,13,8,0,0,0,0,0,0,14,6,0,0,0,9,5,13,14,0,0,0,0,15,12,9,2,0,0,5 +0,1,10,16,16,16,4,0,0,1,14,9,8,16,4,0,0,0,0,0,6,15,0,0,0,2,11,12,15,15,9,0,0,0,13,16,16,13,6,0,0,0,0,16,8,0,0,0,0,0,9,15,1,0,0,0,0,0,13,9,0,0,0,0,7 +0,0,6,8,12,15,4,0,0,1,15,13,12,12,4,0,0,0,16,5,0,0,0,0,0,3,16,16,16,14,1,0,0,2,11,5,5,15,8,0,0,0,0,0,0,16,4,0,0,0,5,8,13,11,0,0,0,0,8,15,10,1,0,0,5 +0,0,8,14,11,3,0,0,0,4,16,9,13,10,0,0,0,2,5,0,8,11,0,0,0,0,0,0,12,8,0,0,0,0,0,5,15,0,0,0,0,0,8,16,6,0,0,0,0,4,16,15,8,4,1,0,0,0,9,12,14,16,5,0,2 +0,0,2,13,15,7,0,0,0,3,15,9,10,15,3,0,0,8,12,0,1,15,6,0,0,6,15,9,12,16,7,0,0,1,8,8,10,16,0,0,0,0,0,0,12,12,0,0,0,0,0,8,16,5,0,0,0,0,0,14,13,0,0,0,9 +0,0,2,12,16,13,2,0,0,1,14,12,6,15,7,0,0,7,16,2,7,15,6,0,0,2,16,16,16,16,3,0,0,0,3,6,10,13,0,0,0,0,0,1,14,5,0,0,0,0,0,8,14,1,0,0,0,0,0,15,9,0,0,0,9 +0,2,10,13,10,1,0,0,0,11,12,10,15,9,0,0,0,0,0,0,12,9,0,0,0,0,3,11,16,6,0,0,0,0,12,16,16,16,4,0,0,0,0,0,0,13,9,0,0,0,1,7,9,16,5,0,0,0,13,15,11,3,0,0,3 +0,1,10,12,14,16,5,0,0,0,14,10,8,8,1,0,0,0,16,4,3,1,0,0,0,2,16,16,16,15,3,0,0,0,2,0,0,11,8,0,0,0,0,0,3,14,3,0,0,0,3,11,15,9,0,0,0,1,12,7,3,0,0,0,5 +0,0,4,12,10,1,0,0,0,0,13,12,11,10,0,0,0,2,16,3,0,12,3,0,0,4,13,0,0,10,6,0,0,6,11,0,0,10,7,0,0,4,12,0,1,14,4,0,0,0,11,9,12,13,0,0,0,0,4,12,11,3,0,0,0 +0,2,13,12,13,16,4,0,0,4,15,9,8,8,0,0,0,4,12,0,0,0,0,0,0,6,15,12,5,0,0,0,0,2,11,10,16,2,0,0,0,0,0,0,12,6,0,0,0,0,2,9,15,3,0,0,0,1,13,10,2,0,0,0,5 +0,0,0,13,7,0,0,0,0,0,8,15,5,0,0,0,0,1,16,7,0,0,0,0,0,2,16,4,7,6,0,0,0,4,16,16,16,16,8,0,0,3,16,9,0,9,11,0,0,0,11,12,5,12,12,0,0,0,1,10,13,13,5,0,6 +0,0,8,12,16,16,2,0,0,1,16,14,13,16,1,0,0,0,3,0,8,14,0,0,0,0,5,16,16,16,4,0,0,0,7,16,15,14,8,0,0,0,1,14,9,0,0,0,0,0,5,16,2,0,0,0,0,0,11,11,0,0,0,0,7 +0,0,4,11,14,6,0,0,0,4,14,7,6,15,0,0,0,4,5,0,5,11,0,0,0,0,0,7,15,11,0,0,0,0,0,11,10,15,4,0,0,0,0,0,0,10,8,0,0,0,1,4,7,15,1,0,0,0,3,16,11,2,0,0,3 +0,0,3,13,13,2,0,0,0,4,16,15,14,10,0,0,0,3,16,2,0,16,4,0,0,6,13,0,0,13,6,0,0,4,13,0,0,11,9,0,0,0,16,0,0,11,7,0,0,0,9,12,9,15,2,0,0,0,2,11,15,7,0,0,0 +0,2,9,13,16,12,2,0,0,5,16,10,7,16,5,0,0,0,0,0,11,15,1,0,0,0,0,9,16,11,1,0,0,0,0,1,10,15,8,0,0,0,0,0,3,15,8,0,0,0,2,9,16,11,1,0,0,1,12,11,3,0,0,0,3 +0,0,5,8,13,13,10,1,0,0,7,8,5,12,14,1,0,0,0,0,0,14,8,0,0,0,11,16,16,16,9,0,0,0,2,6,16,11,5,0,0,0,0,10,8,0,0,0,0,0,3,16,1,0,0,0,0,0,8,10,0,0,0,0,7 +0,0,8,13,11,2,0,0,0,7,15,9,13,11,0,0,0,12,15,0,0,15,4,0,0,4,16,0,0,13,6,0,0,5,13,0,0,11,7,0,0,4,16,0,0,14,7,0,0,1,15,8,10,16,4,0,0,0,6,15,16,10,0,0,0 +0,0,0,4,16,9,2,0,0,0,1,14,16,16,7,0,0,5,14,16,16,16,6,0,0,5,12,13,16,16,5,0,0,0,0,4,16,16,1,0,0,0,0,7,16,16,1,0,0,0,0,8,16,16,3,0,0,0,0,5,14,16,6,0,1 +0,0,2,13,13,0,0,0,0,0,10,14,3,0,0,0,0,1,15,6,0,0,0,0,0,4,16,3,4,2,0,0,0,5,16,16,13,16,3,0,0,2,16,6,0,6,12,0,0,0,11,8,0,8,12,0,0,0,1,13,16,14,3,0,6 +0,0,0,0,10,14,2,0,0,0,0,2,16,16,3,0,0,0,1,15,16,16,2,0,0,3,16,16,16,16,1,0,0,2,8,5,16,16,0,0,0,0,0,0,15,16,1,0,0,0,0,0,12,16,6,0,0,0,0,0,10,15,6,0,1 +0,1,11,16,15,1,0,0,0,5,16,16,16,5,0,0,0,0,3,8,16,2,0,0,0,0,0,15,11,0,0,0,0,0,7,16,6,0,0,0,0,0,14,12,0,0,0,0,0,1,16,15,9,8,2,0,0,1,12,16,16,16,3,0,2 +0,0,3,10,8,1,0,0,0,0,11,16,16,9,0,0,0,3,16,16,16,3,0,0,0,4,16,16,16,3,0,0,0,4,16,16,12,0,0,0,0,1,16,16,15,0,0,0,0,0,9,16,16,0,0,0,0,0,2,10,12,1,0,0,1 +0,1,8,12,12,6,0,0,0,9,16,11,8,10,1,0,0,6,14,0,0,0,0,0,0,1,12,15,8,0,0,0,0,0,0,7,15,5,0,0,0,0,0,0,4,14,0,0,0,0,0,0,11,11,0,0,0,0,12,16,12,4,0,0,5 +0,0,4,14,16,14,1,0,0,1,16,13,14,16,5,0,0,1,5,0,3,16,3,0,0,0,0,0,7,15,0,0,0,2,13,16,16,14,4,0,0,3,10,14,14,8,1,0,0,0,2,14,8,0,0,0,0,0,6,14,1,0,0,0,7 +0,0,10,15,3,0,0,0,0,0,16,7,8,0,0,0,0,0,11,5,4,10,3,0,0,0,3,13,15,10,1,0,0,0,2,15,14,1,0,0,0,0,13,9,10,6,0,0,0,1,14,4,8,8,0,0,0,0,8,14,13,4,0,0,8 +0,0,2,12,12,2,0,0,0,0,8,16,16,5,0,0,0,0,10,16,16,8,0,0,0,0,12,16,16,4,0,0,0,0,11,16,16,6,0,0,0,0,7,16,16,8,0,0,0,0,3,16,16,13,0,0,0,0,2,8,12,7,0,0,1 +0,0,7,13,6,0,0,0,0,3,16,8,13,0,0,0,0,2,14,8,0,0,0,0,0,0,6,16,16,14,6,0,0,1,14,14,13,0,1,0,0,4,16,2,13,6,0,0,0,1,15,4,4,13,0,0,0,0,5,14,16,10,0,0,8 +0,0,0,0,6,9,0,0,0,0,0,7,15,4,0,0,0,0,3,14,7,0,0,0,0,0,9,9,0,13,3,0,0,2,16,2,2,16,0,0,0,8,16,14,16,16,6,0,0,2,4,4,12,8,0,0,0,0,0,0,9,11,0,0,4 +0,0,0,1,15,5,0,0,0,0,0,11,12,0,0,0,0,0,8,15,1,0,0,0,0,3,15,6,2,13,3,0,0,11,12,0,5,16,4,0,0,15,14,14,16,15,5,0,0,3,8,9,16,6,0,0,0,0,0,1,16,5,0,0,4 +0,0,7,14,5,0,0,0,0,3,16,15,12,0,0,0,0,0,12,0,12,0,0,0,0,0,1,0,12,2,0,0,0,0,0,1,16,2,0,0,0,0,1,10,15,3,0,0,0,0,13,16,16,15,7,0,0,0,9,6,3,4,13,1,2 +0,0,15,16,16,15,3,0,0,0,7,8,10,16,6,0,0,0,0,0,4,16,4,0,0,0,0,0,11,12,1,0,0,3,13,16,16,14,8,0,0,3,13,16,13,9,3,0,0,0,6,15,2,0,0,0,0,0,15,12,0,0,0,0,7 +0,1,9,13,15,3,0,0,0,8,14,8,12,16,0,0,0,1,1,0,10,14,0,0,0,0,0,6,15,7,0,0,0,0,10,16,16,10,2,0,0,0,5,8,8,14,8,0,0,1,6,4,7,15,6,0,0,1,11,15,12,4,0,0,3 +0,0,8,16,6,0,0,0,0,2,14,8,12,2,0,0,0,7,14,9,15,9,0,0,0,1,12,13,7,14,0,0,0,0,0,0,0,11,4,0,0,0,0,0,0,8,8,0,0,0,2,4,5,13,7,0,0,0,6,15,15,10,0,0,9 +0,0,9,11,12,12,1,0,0,0,11,12,13,16,4,0,0,0,0,0,12,10,0,0,0,0,2,11,14,2,0,0,0,0,9,16,5,0,0,0,0,0,6,15,16,14,2,0,0,0,3,6,14,16,1,0,0,0,11,16,13,5,0,0,3 +0,0,9,16,16,16,13,1,0,0,14,8,8,11,16,5,0,0,0,0,0,7,16,2,0,0,3,4,5,15,10,0,0,0,16,16,16,16,8,0,0,0,3,11,15,8,1,0,0,0,3,16,8,0,0,0,0,0,12,14,0,0,0,0,7 +0,0,8,16,16,7,0,0,0,2,15,3,10,16,0,0,0,2,15,12,14,11,0,0,0,0,8,9,8,12,0,0,0,0,0,0,1,15,0,0,0,0,0,0,0,14,2,0,0,0,1,1,5,16,4,0,0,0,9,13,16,11,0,0,9 +0,0,9,15,16,10,0,0,0,3,16,13,14,16,1,0,0,0,0,0,12,16,0,0,0,0,4,6,14,14,2,0,0,1,16,16,16,16,11,0,0,0,4,15,12,4,1,0,0,0,6,16,3,0,0,0,0,0,10,11,0,0,0,0,7 +0,0,4,12,1,0,0,0,0,0,11,16,16,7,0,0,0,0,15,15,10,16,4,0,0,3,16,2,0,10,6,0,0,3,16,1,0,7,9,0,0,2,16,2,2,12,12,0,0,0,13,11,14,16,7,0,0,0,3,12,13,5,0,0,0 +0,0,0,6,11,0,0,0,0,0,1,16,14,1,0,0,0,0,5,16,2,0,0,0,0,0,8,14,0,0,0,0,0,0,8,12,2,0,0,0,0,0,11,16,16,16,11,2,0,0,2,16,4,3,12,10,0,0,0,4,14,16,13,8,6 +0,2,14,16,13,7,0,0,0,3,12,8,13,16,0,0,0,0,0,5,14,11,0,0,0,2,12,16,10,1,0,0,0,3,16,15,9,1,0,0,0,0,3,11,15,15,1,0,0,3,6,4,5,16,8,0,0,3,13,16,14,12,2,0,3 +0,0,2,12,3,0,0,0,0,0,11,14,0,0,0,0,0,0,15,6,0,0,0,0,0,0,15,2,4,0,0,0,0,2,16,16,16,12,2,0,0,2,15,7,1,8,9,0,0,0,12,9,1,9,12,0,0,0,1,15,16,13,4,0,6 +0,0,7,15,15,5,0,0,0,4,16,16,16,13,1,0,0,2,16,16,16,6,0,0,0,0,16,16,16,6,0,0,0,0,16,16,16,0,0,0,0,1,16,16,16,1,0,0,0,4,16,16,15,3,0,0,0,1,8,15,12,2,0,0,1 +0,0,3,12,14,2,0,0,0,0,13,9,11,12,0,0,0,1,16,12,13,16,0,0,0,0,7,8,9,13,0,0,0,0,0,0,0,13,3,0,0,0,0,0,0,10,4,0,0,0,1,3,4,12,3,0,0,0,4,16,15,8,0,0,9 +0,0,8,15,11,0,0,0,0,3,15,13,16,3,0,0,0,8,16,6,15,13,0,0,0,4,15,16,16,15,0,0,0,0,2,4,5,16,5,0,0,0,0,0,1,16,5,0,0,0,0,0,4,16,5,0,0,0,10,16,16,10,0,0,9 +0,0,5,16,6,0,0,0,0,0,14,16,3,0,0,0,0,0,16,13,0,0,0,0,0,0,3,4,0,0,0,0,0,1,8,10,12,11,3,0,0,4,16,14,12,14,14,2,0,0,14,13,8,13,16,4,0,0,5,15,16,16,13,1,6 +0,0,1,11,16,9,0,0,0,0,13,11,6,10,0,0,0,4,14,0,9,11,0,0,0,7,15,15,16,15,1,0,0,0,7,6,0,14,8,0,0,0,0,0,0,15,5,0,0,0,0,3,10,15,2,0,0,0,2,12,10,1,0,0,9 +0,0,4,11,1,0,0,0,0,0,8,13,0,0,0,0,0,0,12,10,0,0,0,0,0,0,14,4,0,0,0,0,0,1,16,13,13,8,0,0,0,0,14,13,8,10,10,0,0,0,11,11,0,2,16,0,0,0,2,11,16,16,12,1,6 +0,0,2,11,14,5,0,0,0,1,14,13,13,8,0,0,0,5,16,0,1,1,0,0,0,3,16,6,12,16,5,0,0,0,9,16,16,8,2,0,0,2,14,12,12,13,1,0,0,2,15,7,2,14,7,0,0,0,3,13,14,12,1,0,8 +0,0,5,12,12,3,0,0,0,4,16,7,8,10,0,0,0,8,13,1,3,14,4,0,0,2,14,16,16,12,4,0,0,0,3,16,16,8,0,0,0,0,11,12,5,15,2,0,0,0,16,5,1,16,4,0,0,0,7,15,16,10,0,0,8 +0,0,5,14,13,4,0,0,0,0,12,16,16,10,0,0,0,0,11,16,16,7,0,0,0,0,8,16,16,5,0,0,0,0,8,16,16,6,0,0,0,0,11,16,15,4,0,0,0,0,11,16,16,5,0,0,0,0,5,12,16,11,3,0,1 +0,0,4,15,5,0,0,0,0,0,12,16,14,1,0,0,0,1,15,12,13,14,0,0,0,4,13,0,0,9,4,0,0,3,10,0,0,3,9,0,0,4,12,0,0,2,12,0,0,0,13,10,10,16,14,0,0,0,4,13,16,14,3,0,0 +0,0,3,10,14,15,2,0,0,1,15,11,8,16,4,0,0,0,5,0,8,12,0,0,0,0,0,0,15,2,0,0,0,0,0,0,16,7,0,0,0,0,0,0,9,16,5,0,0,0,1,4,6,16,2,0,0,0,2,16,15,7,0,0,3 +0,0,0,9,15,5,0,0,0,0,8,16,16,6,0,0,0,2,16,16,16,4,0,0,0,1,14,16,16,3,0,0,0,0,0,12,16,4,0,0,0,0,0,16,16,1,0,0,0,0,0,13,16,7,0,0,0,0,0,7,12,12,0,0,1 +0,1,10,5,0,0,0,0,0,2,16,16,12,1,0,0,0,0,10,5,11,6,0,0,0,0,0,1,12,7,0,0,0,0,6,16,16,6,0,0,0,0,3,8,10,15,8,0,0,0,3,4,7,14,15,0,0,0,6,15,15,11,4,0,3 +0,0,5,12,10,4,0,0,0,0,15,13,14,12,0,0,0,0,2,0,12,7,0,0,0,0,2,13,15,2,0,0,0,0,2,13,16,13,0,0,0,0,0,0,3,16,3,0,0,0,12,5,8,15,1,0,0,0,7,13,10,4,0,0,3 +0,0,3,13,9,2,0,0,0,0,14,16,16,11,0,0,0,5,16,7,4,16,3,0,0,4,14,1,0,12,8,0,0,5,12,0,0,8,8,0,0,1,16,4,0,9,11,0,0,0,11,16,12,16,7,0,0,0,3,11,15,10,0,0,0 +0,0,8,15,8,0,0,0,0,2,16,12,16,1,0,0,0,3,9,0,14,2,0,0,0,0,1,3,13,0,0,0,0,0,0,10,7,0,0,0,0,0,5,14,1,0,0,0,0,0,13,16,12,11,1,0,0,0,6,9,12,13,3,0,2 +0,0,3,10,7,15,13,0,0,0,12,9,16,16,13,0,0,1,16,12,14,8,4,0,0,1,14,16,16,16,10,0,0,0,0,1,0,11,6,0,0,0,0,0,4,15,2,0,0,0,2,13,15,3,0,0,0,0,4,13,3,0,0,0,5 +0,0,0,8,14,15,9,0,0,0,8,15,9,8,9,0,0,0,16,1,0,0,0,0,0,7,9,0,0,0,0,0,0,5,16,16,16,15,1,0,0,0,4,3,5,16,3,0,0,0,0,7,9,12,0,0,0,0,0,13,12,1,0,0,5 +0,0,3,13,16,13,1,0,0,0,12,13,8,16,3,0,0,2,15,16,16,8,0,0,0,6,8,11,15,10,0,0,0,0,4,14,4,16,0,0,0,0,8,9,1,13,7,0,0,0,7,9,0,14,6,0,0,0,3,14,14,14,1,0,8 +0,0,0,4,15,1,0,0,0,0,2,16,14,4,0,0,0,0,11,15,2,0,0,0,0,1,16,9,0,0,0,0,0,2,16,16,16,9,1,0,0,2,15,16,13,16,11,0,0,0,8,16,10,12,16,0,0,0,0,6,14,15,8,0,6 +0,0,3,14,11,1,0,0,0,0,14,15,13,10,0,0,0,1,16,5,1,14,2,0,0,3,16,2,0,7,6,0,0,4,13,1,0,7,8,0,0,3,13,0,0,11,7,0,0,0,14,6,7,14,1,0,0,0,4,16,14,4,0,0,0 +0,0,0,6,13,0,0,0,0,0,0,13,12,0,0,0,0,0,1,13,4,0,0,0,0,0,9,9,1,9,1,0,0,4,13,0,4,14,0,0,2,16,16,16,16,13,0,0,3,8,7,9,14,2,0,0,0,0,0,10,5,0,0,0,4 +0,0,0,0,5,14,3,0,0,0,0,0,11,16,8,0,0,0,0,6,16,16,2,0,0,0,7,16,16,16,0,0,0,3,16,14,14,16,0,0,0,4,12,4,13,15,0,0,0,0,0,0,14,16,0,0,0,0,0,0,6,16,1,0,1 +0,0,3,10,14,2,0,0,0,0,15,16,14,10,0,0,0,6,16,5,0,14,2,0,0,8,15,1,0,8,6,0,0,8,8,0,0,6,8,0,0,5,13,0,0,6,8,0,0,0,15,12,8,14,5,0,0,0,3,15,16,11,0,0,0 +0,0,4,15,13,1,0,0,0,0,11,12,13,9,0,0,0,2,15,4,1,16,1,0,0,4,16,3,0,10,6,0,0,3,15,2,0,8,8,0,0,3,16,1,0,12,6,0,0,0,13,11,9,15,1,0,0,0,4,13,13,2,0,0,0 +0,1,13,16,10,3,0,0,0,2,13,8,13,15,0,0,0,0,1,8,11,14,2,0,0,0,0,16,16,7,0,0,0,0,0,0,9,16,1,0,0,0,1,0,0,14,7,0,0,5,12,4,7,16,3,0,0,1,11,14,12,5,0,0,3 +0,0,3,15,10,1,0,0,0,2,14,9,13,11,0,0,0,7,15,0,2,16,2,0,0,4,16,2,0,6,8,0,0,6,9,0,0,6,8,0,0,5,11,0,0,10,8,0,0,0,15,6,7,16,3,0,0,0,3,12,16,5,0,0,0 +0,0,7,10,8,11,16,3,0,0,5,10,9,14,11,0,0,0,0,0,2,15,3,0,0,0,0,0,8,10,0,0,0,4,15,16,16,16,7,0,0,2,4,11,10,3,0,0,0,0,2,16,1,0,0,0,0,0,8,12,0,0,0,0,7 +0,0,0,0,12,5,0,0,0,0,0,2,16,2,0,0,0,0,0,9,12,0,0,0,0,0,6,14,1,5,4,0,0,3,15,3,2,14,6,0,0,12,16,14,16,16,1,0,0,7,8,5,14,10,0,0,0,0,0,0,12,6,0,0,4 +0,0,1,11,11,1,0,0,0,0,9,16,15,3,0,0,0,1,15,6,0,0,0,0,0,5,14,0,0,0,0,0,0,7,11,0,1,2,0,0,0,4,14,5,15,16,7,0,0,1,12,16,16,12,15,0,0,0,2,11,16,14,9,0,6 +0,0,0,5,12,16,8,0,0,1,10,16,15,16,5,0,0,10,16,13,13,16,2,0,0,3,12,13,14,16,2,0,0,0,0,0,8,15,0,0,0,0,0,0,12,15,0,0,0,0,0,1,16,8,0,0,0,0,0,6,16,7,0,0,9 +0,0,5,8,14,14,8,0,0,4,16,13,12,9,4,0,0,4,16,0,0,0,0,0,0,5,16,8,2,0,0,0,0,0,6,14,16,8,0,0,0,0,0,0,6,16,2,0,0,0,11,8,10,15,1,0,0,0,8,13,10,1,0,0,5 +0,0,1,6,14,16,12,0,0,1,15,16,8,16,11,0,0,10,15,4,5,16,10,0,0,3,12,16,16,16,8,0,0,0,0,0,11,16,0,0,0,0,0,0,13,11,0,0,0,0,0,5,16,5,0,0,0,0,0,9,16,3,0,0,9 +0,2,0,0,9,7,0,0,0,15,16,16,16,9,0,0,0,1,5,4,16,7,0,0,0,1,4,4,16,7,0,0,0,5,16,16,16,15,2,0,0,0,3,1,14,6,0,0,0,0,0,0,13,7,0,0,0,0,0,0,10,11,0,0,7 +0,1,10,13,5,0,0,0,0,10,14,12,14,0,0,0,0,5,4,2,15,0,0,0,0,0,0,7,10,0,0,0,0,0,2,15,4,0,0,0,0,0,6,15,0,0,0,0,0,1,16,13,12,13,12,0,0,0,15,12,12,10,4,0,2 +0,0,9,14,10,4,0,0,0,1,15,12,14,16,2,0,0,0,1,0,0,15,4,0,0,0,0,14,16,8,0,0,0,0,0,6,13,14,1,0,0,0,0,0,1,16,5,0,0,0,5,4,6,15,4,0,0,0,12,15,12,6,0,0,3 +0,0,7,12,8,10,15,2,0,0,4,8,10,16,8,0,0,0,0,0,9,10,0,0,0,0,0,5,14,2,0,0,0,6,16,16,16,13,1,0,0,3,6,16,0,0,0,0,0,0,7,13,0,0,0,0,0,0,10,10,0,0,0,0,7 +0,0,0,6,12,2,0,0,0,0,2,15,10,4,0,0,0,0,13,9,0,0,0,0,0,1,15,1,0,0,0,0,0,2,13,6,9,2,0,0,0,0,15,16,12,13,1,0,0,0,6,12,8,15,4,0,0,0,0,7,15,11,1,0,6 +0,0,6,12,14,6,0,0,0,12,14,6,4,15,0,0,0,11,1,0,5,12,0,0,0,6,11,5,15,3,0,0,0,0,7,15,12,2,0,0,0,0,5,13,8,13,1,0,0,0,11,5,6,14,7,0,0,0,5,16,12,7,1,0,8 +0,0,9,16,15,5,0,0,0,0,15,14,16,9,0,0,0,0,5,1,13,10,0,0,0,0,0,0,13,7,0,0,0,0,0,8,13,0,0,0,0,0,3,15,7,1,0,0,0,0,10,16,16,15,0,0,0,0,13,16,12,12,0,0,2 +0,0,1,12,15,12,1,0,0,0,2,12,10,16,4,0,0,6,10,6,14,9,0,0,0,5,13,16,16,7,0,0,0,0,5,15,10,15,0,0,0,0,5,12,0,11,7,0,0,0,4,14,4,13,7,0,0,0,2,12,15,10,1,0,8 +0,0,0,12,1,0,0,0,0,0,5,14,1,0,0,0,0,0,12,9,0,0,0,0,0,3,16,2,0,0,0,0,0,3,13,6,8,8,2,0,0,2,16,16,12,13,14,0,0,0,11,14,6,5,16,2,0,0,1,10,14,14,10,0,6 +0,0,5,14,16,10,0,0,0,0,15,15,14,13,0,0,0,0,4,3,15,8,0,0,0,0,1,16,16,15,2,0,0,0,1,7,6,16,6,0,0,0,0,1,0,14,6,0,0,0,2,15,12,14,1,0,0,0,7,16,12,1,0,0,3 +0,0,0,5,16,10,0,0,0,0,0,13,14,0,0,0,0,0,3,16,9,0,0,0,0,0,7,16,4,0,0,0,0,0,10,16,12,8,1,0,0,2,16,16,16,16,11,0,0,1,9,16,11,12,16,1,0,0,0,6,16,16,10,0,6 +0,0,2,12,16,3,0,0,0,3,16,6,8,8,0,0,0,5,14,11,15,2,0,0,0,0,0,10,11,0,0,0,0,0,1,16,14,2,0,0,0,0,8,12,6,9,0,0,0,0,10,6,6,16,1,0,0,0,3,14,13,8,0,0,8 +0,0,0,9,14,14,1,0,0,2,15,16,16,11,1,0,0,6,16,15,5,0,0,0,0,12,16,9,1,0,0,0,0,3,13,16,13,2,0,0,0,0,0,3,13,15,0,0,0,0,0,11,10,16,7,0,0,0,0,10,16,15,1,0,5 +0,1,6,14,12,0,0,0,0,10,15,12,16,2,0,0,0,7,4,4,16,0,0,0,0,0,0,9,10,0,0,0,0,0,3,16,1,0,0,0,0,0,9,10,0,0,0,0,0,0,14,12,8,11,14,0,0,0,5,16,15,9,8,1,2 +0,0,9,15,16,12,1,0,0,0,3,4,4,15,6,0,0,0,0,0,0,16,4,0,0,0,0,0,8,13,0,0,0,3,12,16,16,15,4,0,0,3,5,12,9,0,0,0,0,0,3,15,1,0,0,0,0,0,11,6,0,0,0,0,7 +0,0,5,11,0,0,0,0,0,1,14,5,0,0,0,0,0,4,12,0,0,0,0,0,0,4,12,3,4,0,0,0,0,8,16,16,16,14,1,0,0,5,16,4,3,9,8,0,0,0,14,7,5,11,12,0,0,0,5,15,16,11,4,0,6 +0,0,3,13,11,1,0,0,0,10,15,10,11,10,0,0,0,6,16,1,1,13,2,0,0,4,8,0,0,8,8,0,0,4,8,0,0,8,8,0,0,3,12,0,0,14,8,0,0,0,13,10,12,16,3,0,0,0,4,15,10,3,0,0,0 +0,0,5,11,15,16,5,0,0,6,16,10,5,16,8,0,0,0,2,1,12,16,1,0,0,0,1,14,15,2,0,0,0,0,2,9,16,10,0,0,0,0,2,0,7,16,3,0,0,0,13,10,5,16,7,0,0,0,7,13,14,15,2,0,3 +0,0,6,15,16,12,1,0,0,3,16,9,8,15,7,0,0,3,3,0,0,15,6,0,0,0,0,0,6,15,2,0,0,0,0,1,15,6,0,0,0,0,1,12,10,0,0,0,0,0,8,16,13,13,6,0,0,0,6,14,12,4,0,0,2 +0,0,1,8,14,13,2,0,0,2,13,12,8,15,2,0,0,6,15,2,11,6,0,0,0,0,5,14,15,1,0,0,0,0,0,12,13,0,0,0,0,0,0,15,15,5,0,0,0,0,0,16,9,15,0,0,0,0,0,9,16,10,0,0,8 +0,0,6,12,16,16,9,0,0,0,12,5,1,14,10,0,0,0,0,2,12,12,0,0,0,0,4,15,14,1,0,0,0,0,4,13,16,8,0,0,0,0,0,0,2,16,1,0,0,0,4,8,12,13,0,0,0,0,7,14,7,1,0,0,3 +0,0,4,12,15,9,1,0,0,7,14,8,13,15,2,0,0,5,7,12,11,1,0,0,0,0,4,16,5,0,0,0,0,0,10,15,10,0,0,0,0,0,9,6,12,4,0,0,0,0,7,9,10,9,0,0,0,0,2,12,16,7,0,0,8 +0,0,2,10,15,5,0,0,0,3,9,12,7,16,0,0,0,12,9,5,6,14,1,0,0,4,12,6,16,4,0,0,0,0,8,16,10,0,0,0,0,0,8,14,16,7,0,0,0,0,12,6,5,16,11,0,0,0,3,14,16,13,7,0,8 +0,0,4,15,15,16,16,15,0,0,5,12,12,11,16,11,0,0,0,0,0,7,16,3,0,0,2,11,16,16,14,0,0,0,14,12,16,13,0,0,0,0,6,4,15,1,0,0,0,0,1,12,10,0,0,0,0,0,8,14,1,0,0,0,7 +0,0,1,10,13,16,14,0,0,0,9,15,12,15,16,1,0,0,1,2,0,10,14,0,0,0,0,2,4,16,12,0,0,0,5,16,16,16,5,0,0,0,7,12,16,8,0,0,0,0,0,6,16,3,0,0,0,0,0,13,11,1,0,0,7 +0,0,2,9,12,13,3,0,0,1,14,10,4,14,5,0,0,6,12,4,8,16,2,0,0,1,11,12,12,16,3,0,0,0,0,0,8,11,0,0,0,0,0,1,14,4,0,0,0,0,0,5,14,0,0,0,0,0,0,7,10,0,0,0,9 +0,0,15,16,16,11,3,0,0,0,11,7,7,13,13,0,0,0,0,0,5,15,6,0,0,0,2,11,16,7,0,0,0,0,3,16,16,13,1,0,0,0,0,0,8,16,2,0,0,0,14,12,15,11,0,0,0,0,11,9,6,0,0,0,3 +0,0,0,1,7,15,5,0,0,0,5,13,16,16,8,0,0,4,15,11,5,16,7,0,0,1,4,0,3,16,4,0,0,0,0,0,4,16,0,0,0,0,0,0,8,16,0,0,0,0,0,0,12,16,0,0,0,0,0,0,12,12,0,0,1 +0,0,6,16,15,3,0,0,0,2,16,11,13,13,0,0,0,7,14,1,1,14,4,0,0,8,12,0,0,8,12,0,0,9,11,0,0,8,12,0,0,8,13,1,0,14,11,0,0,1,16,13,14,16,6,0,0,0,6,16,15,7,0,0,0 +0,2,12,14,16,12,1,0,0,1,16,16,14,11,1,0,0,0,12,13,0,0,0,0,0,0,7,15,3,0,0,0,0,0,0,15,9,0,0,0,0,0,0,9,15,0,0,0,0,0,3,8,16,3,0,0,0,1,15,16,16,3,0,0,5 +0,0,1,12,10,2,0,0,0,0,0,9,16,9,0,0,0,0,0,14,16,12,0,0,0,0,1,16,16,12,0,0,0,0,4,16,16,10,0,0,0,0,6,16,16,10,0,0,0,0,1,16,16,12,0,0,0,0,3,11,13,12,3,0,1 +0,0,0,1,12,4,0,0,0,0,0,9,14,0,0,0,0,0,2,16,5,0,0,0,0,0,5,16,9,2,0,0,0,2,16,16,13,16,8,0,0,0,9,15,0,2,15,0,0,0,0,15,8,8,16,3,0,0,0,3,11,13,10,0,6 +0,0,0,9,10,0,0,0,0,0,6,16,5,0,0,0,0,1,15,9,0,0,0,0,0,4,16,4,0,0,0,0,0,7,16,14,15,6,0,0,0,2,15,5,2,12,6,0,0,0,9,14,3,6,16,0,0,0,0,8,14,14,12,0,6 +0,1,13,16,10,0,0,0,0,2,14,15,16,2,0,0,0,0,0,8,16,4,0,0,0,0,0,13,16,1,0,0,0,0,1,16,11,0,0,0,0,0,11,16,3,5,1,0,0,1,15,16,14,16,4,0,0,1,13,16,15,5,0,0,2 +0,0,2,16,10,0,0,0,0,0,7,16,14,1,0,0,0,0,9,16,13,0,0,0,0,0,12,16,8,0,0,0,0,0,14,16,9,0,0,0,0,0,14,16,6,0,0,0,0,0,7,16,10,0,0,0,0,0,1,11,16,4,0,0,1 +0,0,1,13,10,0,0,0,0,0,9,16,4,0,0,0,0,1,16,8,0,4,0,0,0,4,16,16,16,16,6,0,0,0,8,8,12,16,5,0,0,0,0,1,16,13,0,0,0,0,0,13,16,3,0,0,0,0,2,15,5,0,0,0,4 +0,0,5,13,2,0,0,0,0,1,15,16,15,3,0,0,0,4,14,1,5,14,2,0,0,4,9,0,0,6,8,0,0,5,8,0,0,4,8,0,0,3,10,0,0,9,9,0,0,0,15,10,10,16,6,0,0,0,7,15,14,5,0,0,0 +0,0,8,16,13,0,0,0,0,1,13,9,16,4,0,0,0,0,0,0,14,7,5,0,0,0,3,8,15,16,12,0,0,11,16,16,14,7,1,0,0,7,2,15,4,0,0,0,0,0,5,15,2,0,0,0,0,0,12,11,0,0,0,0,7 +0,0,2,16,14,1,0,0,0,0,6,16,16,5,0,0,0,2,16,16,16,3,0,0,0,0,3,9,16,4,0,0,0,0,0,2,16,10,0,0,0,0,0,0,15,16,0,0,0,0,2,9,16,16,3,0,0,0,2,14,16,10,1,0,1 +0,1,12,13,16,15,1,0,0,3,16,16,10,5,0,0,0,0,12,12,0,0,0,0,0,0,8,14,1,0,0,0,0,0,3,15,5,0,0,0,0,0,0,13,8,0,0,0,0,0,11,16,9,0,0,0,0,1,13,16,3,0,0,0,5 +0,0,0,9,13,3,0,0,0,0,9,14,14,4,0,0,0,1,14,13,0,0,0,0,0,4,16,6,0,0,0,0,0,4,16,6,4,2,0,0,0,2,16,16,16,15,2,0,0,0,7,16,13,16,10,0,0,0,0,7,13,10,5,0,6 +0,0,7,12,14,10,1,0,0,6,16,11,10,16,4,0,0,3,5,0,8,13,1,0,0,0,0,5,15,5,0,0,0,0,2,15,11,0,0,0,0,0,13,14,1,0,0,0,0,1,15,11,6,8,3,0,0,0,7,14,12,12,6,0,2 +0,0,2,7,14,12,1,0,0,2,14,15,10,16,7,0,0,4,9,0,5,16,3,0,0,0,0,2,16,13,0,0,0,0,0,2,15,11,0,0,0,0,0,0,6,16,4,0,0,0,0,5,16,13,0,0,0,0,0,9,11,2,0,0,3 +0,0,0,8,15,4,0,0,0,0,4,16,11,4,0,0,0,0,15,13,0,0,0,0,0,5,16,7,0,0,0,0,0,7,16,13,12,12,1,0,0,3,16,15,12,15,12,0,0,0,11,15,9,15,13,0,0,0,0,8,15,13,6,0,6 +0,1,13,16,13,5,2,0,0,2,16,9,13,14,0,0,0,0,11,13,15,5,0,0,0,0,3,16,13,0,0,0,0,0,3,16,8,0,0,0,0,0,10,16,12,0,0,0,0,1,15,16,12,0,0,0,0,1,16,10,1,0,0,0,8 +0,1,8,14,16,8,0,0,0,3,12,9,11,14,0,0,0,0,0,0,7,14,0,0,0,0,0,0,14,11,0,0,0,0,0,9,13,2,0,0,0,0,7,16,3,0,0,0,0,0,15,16,8,8,3,0,0,0,9,13,12,8,3,0,2 +0,1,11,16,13,3,0,0,0,11,16,11,16,12,0,0,0,4,5,0,13,15,0,0,0,0,0,0,16,10,0,0,0,0,0,8,15,1,0,0,0,0,5,16,5,0,0,0,0,0,14,16,10,8,5,0,0,0,13,16,13,12,5,0,2 +0,0,0,0,6,13,1,0,0,0,0,3,16,7,0,0,0,0,0,15,13,1,0,0,0,0,12,15,1,0,0,0,0,5,16,7,7,14,5,0,0,6,16,16,16,15,3,0,0,0,0,1,14,12,0,0,0,0,0,0,9,11,0,0,4 +0,0,7,16,16,10,0,0,0,4,16,13,16,15,0,0,0,0,8,2,16,12,0,0,0,0,0,8,16,6,0,0,0,0,4,16,9,0,0,0,0,1,13,15,2,0,0,0,0,1,16,15,12,12,5,0,0,0,9,16,14,7,1,0,2 +0,0,8,16,16,12,0,0,0,5,16,15,10,3,0,0,0,3,16,7,0,0,0,0,0,0,7,16,2,0,0,0,0,0,0,15,8,0,0,0,0,0,0,11,13,0,0,0,0,2,12,15,10,0,0,0,0,0,11,15,3,0,0,0,5 +0,0,0,13,7,0,0,0,0,0,9,16,15,0,0,0,0,8,16,16,14,0,0,0,0,7,8,11,16,4,0,0,0,0,0,2,16,10,0,0,0,0,0,0,10,16,4,0,0,0,1,7,11,16,12,0,0,0,0,11,16,14,8,0,1 +0,0,1,9,15,7,1,0,0,4,13,15,13,16,4,0,0,11,13,1,11,14,0,0,0,0,0,0,16,12,0,0,0,0,0,0,14,12,0,0,0,0,0,0,9,14,1,0,0,0,0,3,13,15,0,0,0,0,0,12,13,3,0,0,3 +0,0,0,2,15,6,0,0,0,0,0,13,15,1,0,0,0,0,9,16,4,2,1,0,0,4,16,9,2,14,11,0,0,10,15,5,13,16,4,0,0,15,16,16,16,14,0,0,0,9,12,8,16,8,0,0,0,0,0,2,15,3,0,0,4 +0,0,1,15,10,0,0,0,0,0,9,10,10,7,0,0,0,2,15,1,2,14,0,0,0,2,16,1,0,11,4,0,0,2,15,0,0,9,7,0,0,0,13,4,1,15,7,0,0,0,8,12,11,16,3,0,0,0,1,12,16,7,0,0,0 +0,0,0,5,15,16,6,0,0,1,15,15,10,15,14,0,0,0,1,0,0,8,12,0,0,0,0,2,5,15,9,0,0,0,0,8,16,16,6,0,0,0,0,0,8,13,0,0,0,0,0,0,12,8,0,0,0,0,0,2,13,2,0,0,7 +0,0,1,8,12,12,2,0,0,0,13,12,12,15,9,0,0,0,0,0,0,14,10,0,0,0,0,2,8,16,8,0,0,0,2,16,16,15,5,0,0,0,0,1,16,8,0,0,0,0,0,8,16,4,0,0,0,0,0,9,12,0,0,0,7 +0,0,3,9,13,2,0,0,0,6,16,16,16,8,0,0,0,9,5,3,16,6,0,0,0,0,0,7,15,1,0,0,0,0,0,13,10,0,0,0,0,0,6,16,3,0,0,0,0,0,8,16,14,12,7,0,0,0,3,13,16,14,8,0,2 +0,0,2,16,9,0,0,0,0,0,8,16,15,0,0,0,0,6,16,16,15,1,0,0,0,0,0,5,16,8,0,0,0,0,0,0,14,14,0,0,0,0,0,0,2,16,6,0,0,0,1,4,7,16,13,0,0,0,0,11,16,16,15,1,1 +0,0,2,13,16,3,0,0,0,4,15,14,15,12,0,0,0,4,7,1,14,7,0,0,0,0,0,6,16,3,0,0,0,0,4,16,5,0,0,0,0,1,13,10,0,0,0,0,0,0,14,12,5,8,3,0,0,0,3,12,16,10,2,0,2 +0,0,5,13,8,3,0,0,0,0,14,16,16,12,0,0,0,3,15,1,3,12,5,0,0,4,10,0,0,7,8,0,0,4,8,0,0,9,8,0,0,4,11,1,1,15,6,0,0,0,15,12,11,13,0,0,0,0,3,13,13,3,0,0,0 +0,0,0,11,13,1,0,0,0,1,2,15,13,8,0,0,0,5,13,0,3,13,0,0,0,9,11,0,0,15,1,0,0,6,11,0,0,14,5,0,0,2,15,2,3,16,4,0,0,0,8,14,14,16,2,0,0,0,0,7,14,6,0,0,0 +0,0,9,15,16,12,1,0,0,11,16,16,13,8,1,0,0,3,15,11,1,0,0,0,0,0,8,14,0,0,0,0,0,0,3,16,4,0,0,0,0,0,1,16,7,0,0,0,0,3,15,16,4,0,0,0,0,0,13,15,0,0,0,0,5 +0,2,14,14,1,0,0,0,0,4,16,11,4,6,0,0,0,4,16,4,12,16,2,0,0,0,13,16,16,6,0,0,0,0,13,16,10,0,0,0,0,0,15,16,4,0,0,0,0,4,16,16,7,0,0,0,0,2,13,15,1,0,0,0,8 +0,0,0,2,13,2,0,0,0,0,0,9,15,1,0,0,0,0,1,15,7,0,0,0,0,0,11,12,0,6,6,0,0,7,15,7,5,16,4,0,0,10,16,16,16,13,0,0,0,0,4,3,14,7,0,0,0,0,0,3,12,0,0,0,4 +0,0,0,1,12,6,0,0,0,0,0,6,16,2,0,0,0,0,1,16,5,0,0,0,0,0,11,12,0,2,0,0,0,6,15,2,2,16,3,0,0,11,14,9,15,15,0,0,0,3,11,14,16,8,0,0,0,0,0,1,13,2,0,0,4 +0,1,8,12,11,4,0,0,0,12,16,11,15,14,0,0,0,3,3,3,16,11,0,0,0,0,1,13,16,1,0,0,0,0,0,5,16,7,0,0,0,0,0,0,12,14,0,0,0,0,0,7,16,9,0,0,0,0,10,13,7,0,0,0,3 +0,0,6,12,8,0,0,0,0,7,16,8,15,1,0,0,0,9,4,0,13,4,0,0,0,0,0,1,16,1,0,0,0,0,0,6,10,0,0,0,0,0,0,12,8,0,0,0,0,0,6,15,6,4,6,0,0,0,7,16,15,12,12,0,2 +0,0,1,8,12,16,15,0,0,1,14,16,15,15,13,0,0,0,4,1,0,14,11,0,0,0,1,7,14,16,14,0,0,0,8,16,16,14,2,0,0,0,1,8,16,6,0,0,0,0,0,8,16,0,0,0,0,0,0,15,7,0,0,0,7 +0,0,6,16,13,2,0,0,0,1,11,16,15,3,0,0,0,11,16,16,16,2,0,0,0,6,11,11,16,12,1,0,0,0,0,1,14,16,7,0,0,0,0,0,6,16,13,0,0,0,0,0,7,16,16,3,0,0,2,12,16,15,8,1,1 +0,0,4,15,12,3,0,0,0,3,16,16,16,13,0,0,0,5,16,16,16,13,0,0,0,0,2,2,7,16,1,0,0,0,0,0,7,16,3,0,0,0,0,1,15,13,0,0,0,0,2,13,16,8,0,0,0,0,6,15,5,0,0,0,9 +0,1,11,12,12,13,2,0,0,7,16,9,8,8,2,0,0,7,14,8,8,1,0,0,0,3,15,11,11,13,0,0,0,0,0,0,4,16,0,0,0,0,0,0,9,13,0,0,0,0,2,11,14,4,0,0,0,0,12,9,1,0,0,0,5 +0,0,6,13,16,14,0,0,0,0,14,12,14,13,0,0,0,0,0,0,13,7,0,0,0,0,0,3,16,10,4,0,0,0,13,16,16,16,8,0,0,0,10,15,14,12,4,0,0,0,3,16,5,0,0,0,0,0,6,14,0,0,0,0,7 +0,0,0,9,12,5,0,0,0,0,0,16,9,15,2,0,0,0,0,16,6,16,4,0,0,0,0,4,16,16,8,0,0,0,0,0,2,15,4,0,0,0,0,0,0,10,7,0,0,0,16,11,3,13,4,0,0,0,3,6,13,15,2,0,9 +0,0,0,3,13,2,0,0,0,0,0,9,16,2,0,0,0,0,2,15,16,4,0,0,0,2,13,16,16,2,0,0,0,4,12,8,16,7,0,0,0,0,0,4,16,9,0,0,0,0,0,5,16,16,0,0,0,0,0,2,15,9,0,0,1 +0,0,5,14,11,2,0,0,0,3,16,10,15,13,0,0,0,8,13,0,14,16,1,0,0,3,16,13,16,15,3,0,0,0,3,16,16,3,0,0,0,0,1,16,16,14,0,0,0,0,6,16,13,16,4,0,0,0,5,15,12,12,1,0,8 +0,0,0,6,16,6,0,0,0,0,0,12,16,13,0,0,0,0,9,16,16,16,0,0,0,8,16,16,16,13,0,0,0,0,4,3,16,14,0,0,0,0,0,3,16,13,0,0,0,0,0,4,16,16,2,0,0,0,0,4,16,11,2,0,1 +0,0,2,13,15,6,0,0,0,0,8,14,10,16,3,0,0,0,15,6,0,10,8,0,0,6,15,1,0,11,7,0,0,8,13,0,0,10,8,0,0,7,14,1,0,16,2,0,0,1,15,14,11,14,0,0,0,0,3,15,12,1,0,0,0 +0,0,2,15,15,7,0,0,0,0,10,12,6,16,5,0,0,2,16,3,0,8,10,0,0,7,16,0,0,8,8,0,0,3,16,4,0,12,5,0,0,0,16,7,1,15,3,0,0,0,12,13,13,10,0,0,0,0,2,12,13,1,0,0,0 +0,1,10,15,9,0,0,0,0,5,16,8,16,7,0,0,0,5,14,0,12,9,0,0,0,1,12,13,16,16,2,0,0,0,1,9,12,14,7,0,0,0,0,0,0,4,13,0,0,0,14,12,8,11,16,0,0,0,7,16,16,16,9,0,9 +0,0,5,11,0,0,0,0,0,0,13,6,0,0,0,0,0,2,16,4,0,0,0,0,0,8,13,0,0,0,0,0,0,8,16,15,12,6,0,0,0,8,16,14,12,16,2,0,0,2,16,12,5,16,4,0,0,0,5,14,15,10,0,0,6 +0,0,4,13,1,0,0,0,0,0,10,12,0,0,0,0,0,0,13,9,0,0,0,0,0,0,15,6,0,0,0,0,0,2,16,9,4,2,0,0,0,3,16,15,12,15,6,0,0,1,14,15,4,13,14,0,0,0,3,13,16,14,5,0,6 +0,4,16,9,0,0,0,0,0,9,16,16,3,0,0,0,0,12,8,16,4,0,0,0,0,5,2,16,5,0,0,0,0,0,0,16,4,0,0,0,0,0,3,16,4,0,0,0,0,5,15,16,13,12,8,0,0,4,16,16,16,16,12,0,2 +0,1,7,6,12,14,7,0,0,4,16,13,7,4,1,0,0,5,14,4,2,0,0,0,0,5,16,16,15,3,0,0,0,1,7,3,14,8,0,0,0,0,0,0,12,6,0,0,0,1,3,5,15,2,0,0,0,1,13,13,5,0,0,0,5 +0,0,10,16,10,2,0,0,0,5,15,5,16,15,0,0,0,6,14,2,10,16,2,0,0,0,12,16,16,16,4,0,0,0,0,3,8,14,7,0,0,0,0,0,0,11,9,0,0,2,14,9,8,13,12,0,0,1,9,14,14,12,3,0,9 +0,0,3,14,1,0,0,0,0,0,10,14,1,7,0,0,0,0,15,9,7,16,0,0,0,5,16,4,13,13,0,0,0,12,16,16,16,16,9,0,0,3,8,15,16,12,2,0,0,0,1,16,11,0,0,0,0,0,3,16,5,0,0,0,4 +0,0,1,15,6,0,0,0,0,0,8,15,2,0,0,0,0,0,15,9,0,0,0,0,0,2,16,10,0,0,0,0,0,8,16,11,10,4,0,0,0,5,16,16,11,16,6,0,0,2,15,16,11,16,11,0,0,0,3,11,15,13,1,0,6 +0,0,0,8,13,0,0,0,0,0,5,16,5,6,2,0,0,0,14,10,2,16,6,0,0,6,16,3,9,15,0,0,0,11,16,16,16,16,6,0,0,4,12,14,16,12,2,0,0,0,0,8,16,4,0,0,0,0,0,11,11,1,0,0,4 +0,0,2,12,12,2,0,0,0,3,14,13,11,11,0,0,0,5,16,2,2,14,4,0,0,7,14,2,0,12,7,0,0,4,12,0,0,12,5,0,0,2,14,1,2,15,2,0,0,0,11,10,13,8,0,0,0,0,3,13,12,2,0,0,0 +0,0,3,13,16,15,1,0,0,0,6,12,13,16,4,0,0,0,0,0,9,16,1,0,0,0,2,5,14,14,3,0,0,0,14,16,16,16,10,0,0,0,5,10,16,7,1,0,0,0,1,16,11,0,0,0,0,0,5,16,5,0,0,0,7 +0,0,4,15,10,5,0,0,0,0,15,10,13,16,2,0,0,4,15,1,9,16,4,0,0,0,14,14,16,16,4,0,0,0,1,4,2,15,5,0,0,0,1,0,0,13,6,0,0,7,16,8,9,15,5,0,0,0,6,12,13,11,1,0,9 +0,0,7,15,13,1,0,0,0,0,15,9,13,10,0,0,0,0,16,5,12,12,0,0,0,0,4,15,16,12,0,0,0,0,0,2,7,15,1,0,0,0,0,0,0,15,4,0,0,4,12,7,5,14,4,0,0,0,7,13,13,9,1,0,9 +0,0,1,14,7,0,0,0,0,1,13,16,14,11,0,0,0,4,16,12,0,14,3,0,0,6,14,14,0,8,8,0,0,4,16,14,0,8,8,0,0,1,15,3,0,10,8,0,0,0,7,15,10,16,1,0,0,0,1,9,16,8,0,0,0 +0,0,7,12,13,12,10,0,0,1,16,10,4,8,4,0,0,1,16,9,8,2,0,0,0,7,15,12,14,9,0,0,0,0,0,0,6,12,0,0,0,0,0,0,7,14,0,0,0,0,6,8,12,12,0,0,0,0,12,13,9,0,0,0,5 +0,1,11,12,0,0,0,0,0,7,14,15,6,0,0,0,0,7,3,12,10,0,0,0,0,0,0,15,14,5,0,0,0,0,0,8,10,16,2,0,0,0,0,0,0,12,8,0,0,0,9,8,8,15,10,0,0,0,13,16,14,8,1,0,3 +0,1,10,15,8,0,0,0,0,4,16,10,13,14,1,0,0,8,16,0,10,16,4,0,0,1,16,12,15,16,4,0,0,0,2,11,10,16,6,0,0,0,0,0,0,16,6,0,0,0,12,11,11,16,4,0,0,0,7,12,12,7,0,0,9 +0,0,0,8,10,0,0,0,0,0,4,15,2,0,0,0,0,0,11,9,0,12,3,0,0,2,15,0,3,15,0,0,0,6,16,6,10,14,0,0,0,2,16,16,16,14,0,0,0,0,0,6,15,1,0,0,0,0,0,10,9,0,0,0,4 +0,0,7,15,4,0,0,0,0,0,14,16,12,0,0,0,0,0,12,6,16,0,0,0,0,0,0,10,16,2,0,0,0,0,0,8,14,15,3,0,0,0,0,0,0,9,11,0,0,0,5,9,9,15,11,0,0,0,9,14,12,9,1,0,3 +0,0,4,14,0,0,0,0,0,0,15,7,0,0,0,0,0,3,13,0,0,0,0,0,0,6,12,4,3,0,0,0,0,8,16,16,16,13,1,0,0,4,16,8,2,16,6,0,0,2,16,13,10,16,4,0,0,0,5,15,13,3,0,0,6 +0,1,11,14,6,0,0,0,0,7,12,5,15,0,0,0,0,6,6,1,16,0,0,0,0,0,0,5,16,0,0,0,0,0,0,13,16,12,0,0,0,0,0,0,3,14,6,0,0,0,5,7,6,14,8,0,0,0,14,16,16,11,1,0,3 +0,5,16,5,0,0,0,0,0,9,16,14,0,0,0,0,0,12,14,14,0,0,0,0,0,7,13,12,0,0,0,0,0,0,11,8,0,3,1,0,0,0,16,8,7,14,11,0,0,8,16,16,16,16,11,0,0,6,16,16,11,6,1,0,2 +0,0,9,12,11,0,0,0,0,7,13,4,14,2,0,0,0,2,6,0,14,4,0,0,0,0,0,8,14,0,0,0,0,0,0,10,14,13,1,0,0,0,0,0,1,14,5,0,0,0,8,5,10,14,2,0,0,1,15,16,11,1,0,0,3 +0,0,10,15,9,1,0,0,0,5,16,10,16,9,0,0,0,7,10,0,14,10,0,0,0,3,15,7,12,15,1,0,0,0,4,11,14,16,4,0,0,0,0,0,0,12,11,0,0,0,11,9,5,12,13,0,0,0,10,12,12,15,11,0,9 +0,0,5,15,15,9,0,0,0,0,15,8,5,13,5,0,0,6,16,9,0,6,10,0,0,8,16,4,0,6,12,0,0,0,16,4,0,4,12,0,0,0,16,3,0,9,9,0,0,0,15,8,7,15,1,0,0,0,6,16,16,6,0,0,0 +0,0,1,15,14,0,0,0,0,0,6,16,8,0,0,0,0,0,14,16,13,0,0,0,0,9,15,14,16,1,0,0,0,5,3,6,16,5,0,0,0,0,0,3,16,11,0,0,0,0,0,5,14,15,0,0,0,0,3,15,16,16,6,0,1 +0,0,5,12,8,0,0,0,0,0,13,6,1,0,0,0,0,2,14,1,0,0,0,0,0,4,14,0,3,1,0,0,0,7,11,13,13,13,5,0,0,4,16,8,0,2,12,0,0,1,15,6,2,12,3,0,0,0,6,13,12,4,0,0,6 +0,0,13,16,16,5,0,0,0,1,10,8,16,6,0,0,0,0,0,10,14,0,0,0,0,0,7,16,6,0,0,0,0,0,3,13,16,6,0,0,0,0,0,0,10,15,1,0,0,0,2,5,7,16,7,0,0,0,15,16,16,14,2,0,3 +0,0,0,11,16,10,0,0,0,0,6,11,2,12,0,0,0,0,5,15,3,11,0,0,0,0,1,13,16,1,0,0,0,0,1,12,16,7,0,0,0,7,13,3,7,14,0,0,0,10,13,8,5,10,0,0,0,0,0,14,15,3,0,0,8 +0,0,6,15,16,13,0,0,0,5,16,8,6,15,3,0,0,10,16,4,0,9,8,0,0,5,16,4,0,7,11,0,0,4,16,3,0,6,12,0,0,3,16,3,0,9,9,0,0,0,15,11,8,16,3,0,0,0,11,16,14,5,0,0,0 +0,0,2,9,15,12,0,0,0,0,12,15,14,13,0,0,0,0,2,0,11,7,0,0,0,0,0,1,16,9,4,0,0,1,9,16,16,11,5,0,0,4,13,13,9,0,0,0,0,0,0,13,3,0,0,0,0,0,2,14,1,0,0,0,7 +0,0,7,13,16,13,2,0,0,2,16,15,5,13,10,0,0,6,16,3,0,7,11,0,0,11,14,0,0,5,12,0,0,8,16,0,0,4,12,0,0,8,15,0,0,7,11,0,0,7,16,8,8,15,2,0,0,0,12,16,16,5,0,0,0 +0,0,10,16,16,16,14,0,0,4,16,11,7,3,2,0,0,10,11,0,0,0,0,0,0,7,15,9,8,2,0,0,0,1,11,12,15,14,1,0,0,0,0,0,1,15,6,0,0,2,12,6,0,15,6,0,0,0,9,14,16,15,2,0,5 +0,0,7,16,4,0,0,0,0,0,9,16,6,0,0,0,0,1,13,16,6,0,0,0,0,9,16,16,11,0,0,0,0,8,6,10,16,1,0,0,0,0,0,2,16,11,0,0,0,0,1,7,16,16,7,0,0,0,4,15,16,15,15,3,1 +0,0,9,16,13,0,0,0,0,0,15,11,0,0,0,0,0,4,16,6,0,0,0,0,0,3,16,7,4,3,0,0,0,2,16,16,16,16,10,0,0,0,15,16,2,3,14,2,0,0,15,16,6,4,16,3,0,0,8,6,15,16,8,0,6 +0,0,0,13,16,16,16,8,0,0,0,7,4,6,15,9,0,0,0,0,0,6,15,0,0,0,0,2,4,14,6,0,0,0,3,16,16,16,6,0,0,0,0,1,15,4,0,0,0,0,0,8,12,0,0,0,0,0,1,15,4,0,0,0,7 +0,0,0,6,14,16,8,0,0,0,4,13,8,16,9,0,0,0,0,0,0,15,6,0,0,0,0,5,9,16,9,0,0,0,10,16,16,15,4,0,0,1,9,3,13,7,0,0,0,0,0,1,16,2,0,0,0,0,0,8,9,0,0,0,7 +0,0,0,7,15,0,0,0,0,0,0,14,11,3,11,0,0,0,6,16,2,14,9,0,0,2,14,7,6,16,2,0,1,14,15,11,15,16,4,0,2,13,12,11,16,7,0,0,0,0,0,5,16,1,0,0,0,0,0,11,12,0,0,0,4 +0,0,10,16,16,6,0,0,0,3,13,6,16,4,0,0,0,0,0,10,12,0,0,0,0,0,6,16,5,0,0,0,0,0,1,12,15,5,0,0,0,0,0,0,10,15,3,0,0,0,0,2,7,16,5,0,0,1,13,16,14,10,0,0,3 +0,0,7,16,15,3,0,0,0,1,15,10,12,4,0,0,0,7,16,1,0,0,0,0,0,8,13,0,4,3,0,0,0,9,14,13,16,16,5,0,0,7,16,13,2,9,12,0,0,2,16,10,1,11,12,0,0,0,8,15,16,15,3,0,6 +0,0,0,6,16,1,0,0,0,0,3,16,8,4,15,0,0,1,14,11,0,10,14,0,0,9,16,13,12,16,12,0,0,3,12,11,12,16,6,0,0,0,0,0,12,12,1,0,0,0,0,2,16,6,0,0,0,0,0,7,13,1,0,0,4 +0,0,0,7,14,7,0,0,0,0,3,13,4,12,1,0,0,0,13,3,0,12,4,0,0,1,13,0,6,16,6,0,0,0,15,15,9,12,3,0,0,0,1,1,0,9,2,0,0,0,2,9,2,12,0,0,0,0,1,9,15,7,0,0,9 +0,0,7,13,15,3,0,0,0,3,16,4,8,4,0,0,0,1,16,3,10,0,0,0,0,0,8,16,6,0,0,0,0,0,6,11,11,10,1,0,0,1,13,3,0,9,6,0,0,2,14,1,0,9,3,0,0,0,4,13,14,9,0,0,8 +0,2,16,13,1,0,0,0,0,7,14,10,3,0,0,0,0,8,12,0,0,0,0,0,0,9,13,1,6,3,0,0,0,7,14,15,16,15,4,0,0,4,16,11,4,5,16,0,0,1,16,11,0,7,15,1,0,2,14,13,16,16,4,0,6 +0,0,3,11,13,8,0,0,0,1,14,5,1,12,3,0,0,4,12,0,0,8,11,0,0,1,15,6,4,15,4,0,0,0,2,8,10,14,4,0,0,0,0,0,0,11,4,0,0,0,0,2,2,11,4,0,0,0,2,14,11,7,1,0,9 +0,0,8,16,10,1,0,0,0,0,15,6,13,4,0,0,0,0,15,3,12,2,0,0,0,0,6,16,13,0,0,0,0,0,2,14,14,6,0,0,0,1,13,3,1,13,3,0,0,3,14,0,0,5,7,0,0,0,6,13,12,15,5,0,8 +0,0,2,11,16,15,1,0,0,0,4,8,10,16,4,0,0,0,0,0,3,16,4,0,0,0,0,3,8,16,3,0,0,5,12,16,16,16,7,0,0,8,8,8,16,3,0,0,0,0,0,14,9,0,0,0,0,0,3,16,2,0,0,0,7 +0,1,10,16,15,4,0,0,0,9,16,7,7,15,2,0,0,12,13,0,0,12,8,0,0,12,12,0,0,6,11,0,0,10,12,0,0,4,12,0,0,7,14,0,0,6,11,0,0,2,16,5,3,14,4,0,0,0,10,16,16,12,0,0,0 +0,0,4,16,13,0,0,0,0,0,14,9,15,0,0,0,0,5,14,2,16,6,0,0,0,9,12,7,15,10,0,0,0,3,16,15,9,16,1,0,0,0,4,0,1,14,5,0,0,0,0,6,6,12,7,0,0,0,2,16,16,13,1,0,9 +0,3,16,16,16,16,14,0,0,9,15,9,7,3,2,0,0,10,12,0,0,0,0,0,0,10,16,15,9,0,0,0,0,1,8,9,16,7,0,0,0,0,0,0,10,12,0,0,0,5,7,0,8,14,0,0,0,4,16,16,16,8,0,0,5 +0,1,12,16,14,5,0,0,0,3,13,9,16,12,0,0,0,0,1,11,16,6,0,0,0,0,8,16,9,0,0,0,0,0,3,15,13,0,0,0,0,0,0,2,15,9,0,0,0,0,3,8,12,16,3,0,0,0,14,16,16,16,7,0,3 +0,0,11,15,10,0,0,0,0,2,13,0,9,3,0,0,0,3,12,0,10,0,0,0,0,0,11,12,12,0,0,0,0,0,0,11,14,6,0,0,0,1,9,5,0,10,5,0,0,4,10,0,0,8,4,0,0,1,14,11,14,10,0,0,8 +0,0,9,16,16,6,0,0,0,3,16,6,8,16,4,0,0,10,12,0,6,16,6,0,0,10,14,5,13,16,4,0,0,1,11,12,7,14,8,0,0,0,0,0,0,10,10,0,0,0,0,0,0,13,8,0,0,0,11,16,16,16,5,0,9 +0,0,1,10,13,1,0,0,0,0,14,12,8,2,0,0,0,5,14,1,0,0,0,0,0,6,11,0,0,0,0,0,0,8,14,14,12,11,0,0,0,1,16,10,0,2,10,0,0,0,13,11,1,0,13,0,0,0,1,8,15,16,9,0,6 +0,2,15,16,5,0,0,0,0,6,16,10,10,0,0,0,0,3,15,6,12,0,0,0,0,0,1,7,13,0,0,0,0,0,0,13,7,0,0,0,0,0,3,15,3,0,0,0,0,0,14,15,10,8,4,0,0,3,16,16,16,16,16,3,2 +0,0,13,13,0,0,0,0,0,6,16,7,0,0,0,0,0,10,13,0,0,0,0,0,0,8,16,13,16,10,0,0,0,8,16,14,9,15,6,0,0,5,16,6,0,2,15,0,0,7,16,12,0,3,15,0,0,1,6,13,16,16,8,0,6 +0,0,10,13,12,5,0,0,0,3,16,7,10,16,6,0,0,3,16,3,0,14,8,0,0,1,11,15,14,16,8,0,0,0,0,3,6,14,8,0,0,0,0,0,1,13,7,0,0,0,5,4,7,16,2,0,0,0,13,16,16,10,0,0,9 +0,0,0,0,7,13,2,0,0,0,0,0,8,16,4,0,0,3,8,9,15,15,1,0,0,4,12,11,14,12,0,0,0,0,0,0,12,12,0,0,0,0,0,0,11,13,0,0,0,0,0,0,8,16,5,0,0,0,0,0,6,16,5,0,1 +0,0,10,14,8,0,0,0,0,7,15,5,15,9,0,0,0,8,11,0,5,15,2,0,0,4,16,6,5,16,4,0,0,0,8,12,11,14,7,0,0,0,0,0,0,11,9,0,0,0,4,2,0,11,11,0,0,0,11,16,16,15,4,0,9 +0,0,14,5,0,0,0,0,0,0,13,8,0,0,0,0,0,0,16,7,0,0,0,0,0,0,15,8,0,0,0,0,0,2,16,5,15,3,0,0,0,2,16,9,14,11,1,0,0,6,16,16,16,16,15,0,0,1,8,5,7,16,8,0,4 +0,1,10,13,10,1,0,0,0,6,14,4,10,15,2,0,0,8,12,0,2,16,3,0,0,2,14,8,10,16,4,0,0,0,2,4,4,13,6,0,0,0,0,0,0,12,8,0,0,6,12,2,6,16,4,0,0,1,10,16,13,5,0,0,9 +0,0,6,15,11,1,0,0,0,0,15,7,13,15,2,0,0,5,14,0,2,16,4,0,0,3,15,8,10,16,4,0,0,0,6,12,10,14,8,0,0,2,3,0,0,12,7,0,0,9,15,8,8,16,3,0,0,0,7,15,15,4,0,0,9 +0,0,7,15,14,9,0,0,0,4,15,5,12,16,4,0,0,7,13,1,9,14,3,0,0,1,14,16,16,2,0,0,0,0,12,12,14,12,0,0,0,2,16,1,2,15,4,0,0,1,15,7,0,14,8,0,0,0,6,16,16,13,2,0,8 +0,1,10,15,6,0,0,0,0,7,13,6,13,2,0,0,0,7,8,0,13,4,0,0,0,0,1,0,13,4,0,0,0,0,0,3,14,0,0,0,0,0,0,11,10,0,0,0,0,0,7,16,5,4,2,0,0,0,15,16,14,16,15,0,2 +0,0,6,16,16,15,14,0,0,0,2,4,10,16,5,0,0,0,0,1,12,10,0,0,0,1,6,11,16,13,6,0,0,7,14,16,14,9,4,0,0,0,1,16,8,0,0,0,0,0,4,16,8,0,0,0,0,0,5,15,2,0,0,0,7 +0,0,4,13,1,0,0,0,0,0,13,11,0,0,0,0,0,2,16,3,0,0,0,0,0,4,16,11,5,1,0,0,0,4,16,15,15,13,4,0,0,2,16,2,0,7,14,0,0,0,14,10,8,9,16,2,0,0,4,13,16,12,10,0,6 +0,0,10,14,12,1,0,0,0,4,11,4,10,9,0,0,0,1,3,0,7,11,0,0,0,0,0,4,14,6,0,0,0,0,0,11,13,15,1,0,0,0,0,0,0,14,7,0,0,3,6,2,4,15,6,0,0,1,11,15,14,8,0,0,3 +0,0,4,14,15,6,0,0,0,0,13,5,9,11,0,0,0,0,0,0,5,11,0,0,0,0,1,9,16,4,0,0,0,0,3,9,9,15,1,0,0,0,0,0,0,15,4,0,0,8,14,8,5,16,4,0,0,0,6,14,15,8,0,0,3 +0,0,2,14,11,8,12,1,0,0,8,10,9,14,14,0,0,0,10,1,0,12,4,0,0,0,1,2,7,13,1,0,0,0,1,15,16,16,5,0,0,0,0,3,13,2,0,0,0,0,0,9,7,0,0,0,0,0,0,15,4,0,0,0,7 +0,1,13,12,1,0,0,0,0,5,13,12,9,0,0,0,0,1,11,4,16,0,0,0,0,0,3,1,16,3,0,0,0,0,0,2,16,0,0,0,0,0,0,5,15,1,0,0,0,0,7,16,11,4,5,0,0,0,15,16,16,16,16,0,2 +0,0,0,7,9,0,0,0,0,0,4,15,6,0,0,0,0,0,11,11,0,0,0,0,0,1,16,14,12,4,0,0,0,3,16,16,13,14,3,0,0,0,16,6,1,8,11,0,0,0,11,11,1,1,16,0,0,0,2,9,14,16,16,1,6 +0,0,6,15,12,1,0,0,0,0,16,9,15,14,2,0,0,6,14,0,2,16,6,0,0,5,15,5,6,16,4,0,0,0,6,12,12,15,8,0,0,0,0,0,0,13,7,0,0,2,14,4,7,16,2,0,0,0,7,15,15,5,0,0,9 +0,0,0,4,16,2,0,0,0,0,0,8,16,0,0,0,0,0,0,14,11,0,0,0,0,0,7,15,1,0,0,0,0,0,15,11,9,3,0,0,0,9,16,11,16,9,0,0,0,13,16,16,16,16,6,0,0,1,0,8,16,5,0,0,4 +0,0,5,16,14,8,0,0,0,5,15,6,11,16,1,0,0,8,14,0,5,16,1,0,0,1,12,14,16,8,0,0,0,0,6,14,15,10,0,0,0,0,12,4,1,13,6,0,0,0,12,7,1,13,8,0,0,0,4,15,16,10,1,0,8 +0,0,8,14,14,9,0,0,0,1,16,9,14,15,9,0,0,2,16,6,9,15,6,0,0,0,8,16,16,4,0,0,0,0,10,12,13,12,0,0,0,1,15,2,1,16,6,0,0,2,14,4,6,16,4,0,0,0,10,16,16,10,1,0,8 +0,3,14,15,5,0,0,0,0,8,11,7,15,2,0,0,0,9,4,0,16,4,0,0,0,1,3,0,14,8,0,0,0,0,0,4,15,0,0,0,0,0,0,12,10,0,0,0,0,1,14,16,10,10,6,0,0,2,14,15,13,16,12,0,2 +0,0,13,12,12,12,5,0,0,3,16,7,4,4,2,0,0,5,16,8,5,0,0,0,0,5,14,11,16,6,0,0,0,0,0,0,6,14,0,0,0,0,0,0,0,16,0,0,0,2,4,1,9,16,0,0,0,1,13,16,13,2,0,0,5 +0,0,6,15,15,11,5,0,0,0,14,8,12,15,13,0,0,2,16,3,0,11,12,0,0,0,13,16,15,13,1,0,0,0,6,16,16,7,0,0,0,0,13,5,11,12,0,0,0,0,13,11,9,16,2,0,0,0,6,14,15,7,0,0,8 +0,0,0,5,15,0,0,0,0,0,0,12,12,0,0,0,0,0,0,16,8,0,0,0,0,0,9,14,4,1,0,0,0,0,16,7,16,8,0,0,0,11,16,15,16,16,3,0,0,13,16,16,16,16,7,0,0,0,0,5,16,2,0,0,4 +0,0,6,13,6,0,0,0,0,0,14,11,16,10,0,0,0,2,14,0,9,15,0,0,0,5,9,0,0,12,3,0,0,8,8,0,0,8,5,0,0,7,11,0,0,8,8,0,0,1,15,7,5,14,4,0,0,0,6,16,16,9,0,0,0 +0,0,0,11,10,5,8,5,0,0,5,14,13,16,13,3,0,0,12,5,1,15,3,0,0,0,7,1,9,9,0,0,0,0,9,16,16,16,6,0,0,0,0,6,12,4,1,0,0,0,0,13,5,0,0,0,0,0,2,13,1,0,0,0,7 +0,0,0,9,10,0,0,0,0,0,2,15,9,0,0,0,0,0,6,16,6,0,0,0,0,0,13,13,5,1,0,0,0,3,16,7,16,8,0,0,1,13,16,10,16,12,0,0,0,13,16,16,16,16,3,0,0,2,4,10,16,2,0,0,4 +0,0,8,13,10,0,0,0,0,1,16,7,14,10,1,0,0,5,12,0,2,16,4,0,0,3,16,10,9,16,4,0,0,0,3,8,8,14,6,0,0,0,0,0,0,12,8,0,0,3,12,3,3,14,6,0,0,1,9,15,16,9,0,0,9 +0,0,0,4,13,4,0,0,0,0,0,9,16,11,0,0,0,5,16,16,16,8,0,0,0,0,4,11,16,9,0,0,0,0,0,4,16,12,0,0,0,0,0,4,16,14,0,0,0,0,0,12,16,16,5,0,0,0,0,3,16,16,4,0,1 +0,0,5,14,9,3,0,0,0,0,14,11,16,15,2,0,0,5,11,0,11,15,4,0,0,7,8,0,0,9,7,0,0,4,9,0,0,8,8,0,0,4,13,0,0,9,5,0,0,2,16,5,7,15,0,0,0,0,7,15,15,6,0,0,0 +0,0,11,13,2,0,0,0,0,5,13,9,14,0,0,0,0,4,8,0,12,8,0,0,0,1,5,0,11,8,0,0,0,0,0,0,12,7,0,0,0,0,0,6,15,1,0,0,0,0,5,16,11,2,0,0,0,0,11,16,16,16,16,1,2 +0,1,10,13,4,0,0,0,0,9,13,8,14,2,0,0,0,5,10,0,14,4,0,0,0,1,2,0,12,7,0,0,0,0,0,0,13,4,0,0,0,0,0,1,15,3,0,0,0,0,6,15,11,4,2,0,0,0,12,16,16,16,15,0,2 +0,1,11,13,16,1,0,0,0,3,16,5,4,4,1,0,0,5,15,3,4,0,0,0,0,4,15,12,13,15,2,0,0,0,0,0,0,10,8,0,0,0,0,0,0,10,8,0,0,2,8,7,5,15,5,0,0,0,8,12,16,12,0,0,5 +0,1,14,13,4,0,0,0,0,6,15,11,15,0,0,0,0,8,14,9,16,4,0,0,0,2,8,11,15,6,0,0,0,0,0,0,7,13,1,0,0,0,0,0,0,11,10,0,0,0,10,4,0,3,15,0,0,0,10,14,16,16,15,0,9 +0,0,0,1,13,9,0,0,0,0,0,7,16,5,0,0,0,0,1,14,12,0,0,0,0,4,14,13,2,13,6,0,0,6,16,16,16,16,9,0,0,0,6,8,13,16,0,0,0,0,0,0,15,11,0,0,0,0,0,2,16,9,0,0,4 +0,0,3,14,8,8,8,0,0,0,2,11,12,15,10,0,0,0,0,0,0,13,4,0,0,1,16,16,16,15,3,0,0,2,8,8,16,16,6,0,0,0,0,8,13,3,1,0,0,0,1,16,7,0,0,0,0,0,5,11,1,0,0,0,7 +0,0,5,16,5,0,0,0,0,0,2,16,13,0,0,0,0,0,11,16,16,2,0,0,0,0,3,12,16,9,0,0,0,0,0,0,14,14,0,0,0,0,0,0,9,16,3,0,0,0,9,12,14,16,8,5,0,0,4,12,13,16,16,15,1 +0,0,4,6,14,6,0,0,0,8,16,15,13,10,0,0,0,3,12,3,2,13,0,0,0,0,11,11,13,11,0,0,0,0,6,16,16,3,0,0,0,0,16,9,6,13,2,0,0,0,9,8,0,3,14,0,0,0,0,6,15,16,16,4,8 +0,0,8,16,16,11,2,0,0,0,5,8,9,16,9,0,0,0,0,0,2,16,5,0,0,0,4,8,12,12,0,0,0,0,11,16,16,16,4,0,0,0,2,14,11,14,5,0,0,0,6,16,2,0,0,0,0,0,10,14,0,0,0,0,7 +0,0,0,4,16,10,0,0,0,0,0,7,16,6,0,0,0,0,0,15,11,1,0,0,0,0,9,15,2,1,0,0,0,11,16,9,5,15,7,0,0,8,16,16,16,16,1,0,0,0,2,8,16,11,0,0,0,0,0,6,16,6,0,0,4 +0,0,7,16,8,0,0,0,0,0,13,16,16,7,0,0,0,4,16,7,11,15,0,0,0,6,12,0,0,14,6,0,0,8,12,0,0,14,8,0,0,7,13,0,6,15,7,0,0,4,16,16,16,16,1,0,0,0,7,14,7,0,0,0,0 +0,0,16,10,0,0,0,0,0,3,16,16,6,0,0,0,0,0,16,12,12,0,0,0,0,0,13,11,14,0,0,0,0,0,1,6,16,0,0,0,0,0,0,10,12,0,0,0,0,0,13,16,16,14,8,0,0,1,13,16,16,15,16,5,2 +0,0,10,14,14,11,1,0,0,4,16,12,5,14,11,0,0,5,14,2,4,13,8,0,0,1,11,11,14,6,0,0,0,0,9,16,5,0,0,0,0,4,13,11,9,0,0,0,0,5,11,2,16,2,0,0,0,0,13,16,16,6,0,0,8 +0,0,7,13,14,1,0,0,0,0,16,10,5,0,0,0,0,1,16,13,8,3,0,0,0,0,12,10,11,15,3,0,0,0,0,0,0,9,8,0,0,0,0,0,0,3,11,0,0,1,7,4,4,11,11,0,0,1,11,14,15,12,2,0,5 +0,0,0,8,14,3,0,0,0,0,1,16,15,7,0,0,0,0,7,15,5,0,0,0,0,3,14,14,5,0,0,0,0,3,16,10,12,10,0,0,0,0,11,5,0,14,2,0,0,0,3,11,6,14,1,0,0,0,0,7,15,6,0,0,6 +0,0,0,4,13,3,0,0,0,0,6,16,13,2,0,0,0,2,16,9,0,0,0,0,0,3,16,10,2,0,0,0,0,2,16,16,16,14,3,0,0,1,13,9,2,8,11,0,0,0,3,15,8,9,16,1,0,0,0,3,11,14,10,0,6 +0,0,10,16,16,12,6,0,0,0,6,9,16,16,15,1,0,0,0,0,3,13,9,0,0,1,12,8,10,16,2,0,0,1,15,16,16,16,8,0,0,0,1,13,11,10,4,0,0,0,3,16,2,0,0,0,0,0,9,14,0,0,0,0,7 +0,0,8,15,14,1,0,0,0,0,15,15,7,0,0,0,0,0,14,15,9,2,0,0,0,0,10,15,14,14,3,0,0,0,1,1,0,11,12,0,0,0,0,0,0,3,16,1,0,2,14,6,4,7,16,3,0,0,9,16,16,16,13,1,5 +0,0,0,1,14,10,0,0,0,0,0,2,16,9,0,0,0,0,1,11,14,3,0,0,0,1,10,16,6,9,6,0,0,10,16,15,13,16,9,0,0,5,11,12,16,15,1,0,0,0,0,0,16,12,0,0,0,0,0,0,16,11,0,0,4 +0,4,15,16,4,0,0,0,0,8,16,11,15,0,0,0,0,6,14,4,16,4,0,0,0,0,2,4,16,3,0,0,0,0,0,11,15,0,0,0,0,0,9,16,4,0,0,0,0,10,16,16,16,13,3,0,0,5,14,12,13,16,12,0,2 +0,0,0,12,11,0,0,0,0,0,11,16,10,0,0,0,0,0,16,13,0,0,0,0,0,3,16,3,0,0,0,0,0,2,16,16,11,1,0,0,0,0,14,11,3,10,0,0,0,0,5,14,2,9,6,0,0,0,0,7,16,16,6,0,6 +0,0,8,13,8,0,0,0,0,4,13,6,14,3,0,0,0,7,8,0,12,5,0,0,0,3,10,8,16,1,0,0,0,0,0,5,14,12,0,0,0,0,0,0,0,13,8,0,0,0,10,10,0,6,12,0,0,0,4,15,16,16,6,0,3 +0,0,12,16,16,16,3,0,0,0,6,9,12,16,7,0,0,0,0,0,8,15,3,0,0,3,15,16,16,7,0,0,0,1,8,16,16,15,2,0,0,0,3,16,10,14,6,0,0,0,11,16,0,0,0,0,0,0,12,11,0,0,0,0,7 +0,0,5,12,10,0,0,0,0,2,15,14,16,1,0,0,0,0,11,3,16,1,0,0,0,0,0,0,11,5,0,0,0,0,0,0,7,14,0,0,0,0,0,0,0,9,10,0,0,0,10,7,5,12,12,0,0,0,6,14,16,13,4,0,3 +0,0,5,11,12,9,0,0,0,0,12,14,5,16,0,0,0,1,16,5,6,11,0,0,0,0,16,8,15,3,0,0,0,0,9,16,9,0,0,0,0,1,11,4,13,4,0,0,0,3,13,2,4,14,0,0,0,0,5,8,11,11,0,0,8 +0,0,1,7,12,7,0,0,0,0,6,16,10,5,0,0,0,0,14,16,14,4,0,0,0,0,3,4,6,13,0,0,0,0,0,0,0,11,6,0,0,0,0,0,0,7,9,0,0,0,10,5,2,8,11,0,0,0,4,14,16,16,4,0,5 +0,0,9,16,15,1,0,0,0,0,3,8,16,5,0,0,0,0,0,0,14,7,0,0,0,2,11,9,16,3,0,0,0,11,16,16,16,12,1,0,0,0,0,13,15,16,5,0,0,0,5,16,2,0,0,0,0,0,10,10,0,0,0,0,7 +0,0,11,16,7,0,0,0,0,4,15,8,16,9,0,0,0,8,10,0,9,12,0,0,0,4,15,11,13,13,0,0,0,0,0,7,6,16,0,0,0,0,0,0,0,13,2,0,0,0,7,7,2,12,4,0,0,0,6,13,16,16,6,0,9 +0,0,0,12,8,0,0,0,0,0,0,16,8,0,0,0,0,0,10,16,3,0,0,0,0,7,16,12,12,4,0,0,0,8,16,16,16,16,9,0,0,1,9,15,16,10,6,0,0,0,0,12,13,0,0,0,0,0,0,14,12,0,0,0,4 +0,0,11,16,16,4,0,0,0,0,12,16,12,3,0,0,0,2,16,16,15,4,0,0,0,0,5,8,11,15,3,0,0,0,0,0,0,10,12,0,0,0,3,1,0,3,16,0,0,0,16,12,8,11,16,1,0,0,8,14,16,16,11,0,5 +0,0,4,10,15,4,0,0,0,0,8,13,6,1,0,0,0,1,15,14,10,1,0,0,0,0,11,8,10,12,1,0,0,0,0,0,0,10,6,0,0,0,0,0,0,2,14,0,0,0,6,7,3,3,15,2,0,0,4,13,16,16,15,1,5 +0,0,9,16,16,16,7,0,0,0,4,8,8,14,14,0,0,0,0,0,2,15,6,0,0,0,10,12,14,14,0,0,0,0,8,16,16,16,6,0,0,0,0,15,9,13,4,0,0,0,7,16,1,0,0,0,0,0,11,10,0,0,0,0,7 +0,2,12,15,11,3,0,0,0,10,13,5,13,13,0,0,0,2,2,0,12,14,0,0,0,0,0,7,15,3,0,0,0,0,0,5,15,3,0,0,0,0,0,0,8,15,1,0,0,0,8,5,4,12,10,0,0,0,14,16,16,16,5,0,3 +0,0,5,13,6,1,0,0,0,0,8,16,16,15,2,0,0,0,3,13,1,13,8,0,0,0,2,14,15,14,2,0,0,2,14,15,15,3,0,0,0,7,11,0,6,10,0,0,0,5,11,1,1,16,0,0,0,0,8,13,16,13,1,0,8 +0,0,0,1,15,6,0,0,0,0,0,6,15,2,0,0,0,0,1,16,9,0,0,0,0,1,11,14,2,9,7,0,0,12,16,14,15,16,9,0,0,9,12,12,14,14,2,0,0,0,0,0,14,15,0,0,0,0,0,0,16,14,0,0,4 +0,0,2,10,16,12,0,0,0,2,15,13,9,16,2,0,0,1,9,0,8,14,1,0,0,0,0,4,16,10,0,0,0,0,0,1,10,16,3,0,0,0,2,0,0,14,7,0,0,0,9,12,11,16,3,0,0,0,1,11,14,6,0,0,3 +0,0,0,10,5,0,0,0,0,0,0,15,5,0,0,0,0,0,10,12,1,14,1,0,0,5,16,4,8,15,0,0,0,7,16,16,16,16,5,0,0,0,3,5,15,8,1,0,0,0,0,9,12,0,0,0,0,0,0,12,8,0,0,0,4 +0,0,1,11,3,0,0,0,0,0,13,15,13,0,0,0,0,3,16,2,8,5,0,0,0,6,16,14,15,14,2,0,0,2,14,16,16,16,5,0,0,0,1,4,3,9,6,0,0,0,4,6,10,16,5,0,0,0,1,11,10,4,0,0,9 +0,0,0,3,11,0,0,0,0,0,0,11,7,0,0,0,0,0,4,14,2,5,0,0,0,0,12,9,7,11,0,0,0,7,16,12,14,10,1,0,0,3,10,13,16,16,8,0,0,0,0,5,14,0,0,0,0,0,0,7,10,0,0,0,4 +0,0,0,3,9,0,0,0,0,0,0,9,11,0,0,0,0,0,0,15,5,0,2,0,0,0,11,12,0,14,8,0,0,3,16,12,11,16,8,0,0,7,12,12,15,16,4,0,0,0,0,0,12,7,0,0,0,0,0,5,13,2,0,0,4 +0,1,13,16,14,1,0,0,0,14,15,8,14,10,0,0,0,5,5,0,13,10,0,0,0,0,0,12,16,8,0,0,0,0,4,16,16,16,5,0,0,0,0,3,0,16,10,0,0,0,3,4,10,16,8,0,0,0,11,16,16,8,0,0,3 +0,0,3,14,14,2,0,0,0,4,16,16,16,7,0,0,0,4,16,8,3,15,1,0,0,3,16,6,0,14,5,0,0,1,16,5,0,13,7,0,0,0,15,4,1,16,9,0,0,0,11,13,13,16,4,0,0,0,2,15,15,8,0,0,0 +0,0,4,12,14,2,0,0,0,4,15,10,10,10,0,0,0,9,16,0,5,12,0,0,0,5,16,9,14,16,4,0,0,0,7,8,7,15,6,0,0,0,0,0,0,10,12,0,0,0,1,4,4,14,12,0,0,0,3,13,16,11,4,0,9 +0,0,8,16,12,0,0,0,0,4,16,9,8,9,0,0,0,6,16,3,1,16,0,0,0,0,14,16,16,11,0,0,0,0,3,16,16,14,0,0,0,0,9,16,3,14,2,0,0,0,10,15,7,16,2,0,0,0,5,16,15,7,0,0,8 +0,0,2,11,9,0,0,0,0,0,12,7,8,8,0,0,0,6,16,3,9,11,0,0,0,1,13,16,16,14,2,0,0,0,4,16,8,10,5,0,0,0,8,11,0,4,6,0,0,0,10,9,0,8,5,0,0,0,3,11,12,6,0,0,8 +0,0,0,9,10,0,0,0,0,0,2,16,16,3,0,0,0,0,13,16,9,0,0,0,0,1,15,13,0,0,0,0,0,2,16,13,11,12,4,0,0,1,15,16,16,16,15,1,0,0,8,16,16,16,16,4,0,0,0,7,12,13,14,3,6 +0,0,11,12,0,0,0,0,0,4,16,15,3,0,0,0,0,2,11,9,6,0,0,0,0,0,2,10,5,0,0,0,0,0,0,14,3,0,0,0,0,0,3,16,0,0,0,0,0,0,12,16,12,15,14,0,0,0,10,16,14,10,10,1,2 +0,0,4,13,10,3,0,0,0,0,16,15,13,13,0,0,0,1,16,9,8,15,0,0,0,2,16,15,15,16,1,0,0,0,7,12,12,16,6,0,0,0,0,0,0,14,10,0,0,0,3,2,1,14,11,0,0,0,5,13,14,12,2,0,9 +0,0,8,12,16,9,1,0,0,0,14,11,7,16,4,0,0,0,0,0,4,16,2,0,0,0,0,4,14,16,3,0,0,0,0,8,14,15,8,0,0,0,0,0,0,9,11,0,0,3,12,4,6,14,7,0,0,1,9,16,16,11,0,0,3 +0,0,4,13,13,0,0,0,0,0,12,14,12,7,0,0,0,0,12,12,5,14,0,0,0,0,12,10,0,12,3,0,0,4,16,5,0,8,8,0,0,4,16,2,0,9,8,0,0,3,14,9,4,15,5,0,0,0,4,13,14,10,0,0,0 +0,0,4,14,12,1,0,0,0,0,11,11,10,5,0,0,0,0,0,0,12,9,0,0,0,0,0,7,16,12,2,0,0,0,0,0,4,10,10,0,0,0,1,1,0,3,14,0,0,0,11,6,3,12,13,0,0,0,6,14,16,11,1,0,3 +0,0,3,16,14,4,0,0,0,0,10,16,16,15,0,0,0,2,16,12,5,14,2,0,0,5,16,4,0,10,6,0,0,6,16,1,0,7,9,0,0,3,16,3,0,9,12,0,0,0,13,14,13,16,9,0,0,0,3,13,16,12,2,0,0 +0,0,6,13,13,3,0,0,0,2,16,16,16,11,0,0,0,3,16,15,6,16,4,0,0,3,16,9,0,16,8,0,0,8,16,3,1,16,6,0,0,7,16,2,8,16,3,0,0,1,14,16,16,13,0,0,0,0,4,13,12,2,0,0,0 +0,0,5,14,7,0,0,0,0,2,16,16,16,3,0,0,0,1,16,8,13,9,0,0,0,0,6,3,16,7,0,0,0,0,0,0,16,7,0,0,0,0,0,11,15,0,0,0,0,0,9,16,14,8,10,0,0,0,6,15,12,16,16,7,2 +0,0,5,9,13,12,0,0,0,10,16,15,12,16,2,0,0,8,7,0,11,14,0,0,0,0,0,2,16,14,1,0,0,0,0,1,11,16,7,0,0,0,0,0,0,8,12,0,0,0,14,10,10,16,8,0,0,0,6,15,16,10,1,0,3 +0,0,11,12,7,0,0,0,0,3,16,14,16,3,0,0,0,6,16,6,16,6,0,0,0,2,15,16,16,14,0,0,0,0,4,9,8,15,6,0,0,0,0,0,0,11,11,0,0,0,2,4,9,16,7,0,0,0,10,16,13,8,1,0,9 +0,0,0,0,10,7,0,0,0,0,0,2,16,13,0,0,0,0,3,16,16,9,0,0,0,8,16,16,16,10,0,0,0,1,3,3,16,11,0,0,0,0,0,1,16,13,0,0,0,0,0,0,16,16,3,0,0,0,0,0,8,16,10,0,1 +0,0,3,13,14,3,0,0,0,0,15,16,16,12,0,0,0,4,16,8,6,16,0,0,0,5,16,7,1,15,3,0,0,8,16,6,0,16,5,0,0,4,16,7,0,15,6,0,0,3,15,14,12,16,4,0,0,0,3,15,15,8,0,0,0 +0,0,4,13,8,0,0,0,0,0,14,16,16,5,0,0,0,4,16,7,8,15,0,0,0,0,15,12,15,16,4,0,0,0,5,14,14,16,6,0,0,0,0,0,0,13,8,0,0,0,0,0,0,10,13,0,0,0,5,14,16,15,8,0,9 +0,0,0,11,14,2,0,0,0,0,5,16,16,11,0,0,0,2,16,10,6,14,0,0,0,2,16,10,2,14,2,0,0,3,16,10,0,9,8,0,0,3,16,4,0,11,11,0,0,0,12,13,9,16,8,0,0,0,1,11,15,9,2,0,0 +0,0,6,16,6,0,0,0,0,4,16,13,16,2,0,0,0,8,15,1,15,5,0,0,0,5,16,16,16,16,6,0,0,0,14,16,14,13,8,0,0,0,9,16,3,14,8,0,0,0,12,14,11,15,2,0,0,0,4,10,14,7,0,0,8 +0,0,8,16,14,6,0,0,0,5,16,13,12,14,0,0,0,6,16,12,4,0,0,0,0,9,16,16,15,3,0,0,0,0,6,5,14,10,0,0,0,0,0,0,12,14,0,0,0,0,15,9,16,14,0,0,0,0,6,15,15,6,0,0,5 +0,0,8,16,8,0,0,0,0,2,16,16,15,5,0,0,0,2,16,5,12,10,0,0,0,0,8,1,11,10,0,0,0,0,0,4,16,4,0,0,0,0,0,13,15,5,3,0,0,0,10,16,16,16,16,0,0,0,10,14,9,8,11,2,2 +0,0,0,2,11,0,0,0,0,0,0,11,8,0,0,0,0,0,1,16,3,9,0,0,0,0,11,11,2,16,0,0,0,4,16,10,12,15,5,0,0,3,11,12,15,13,3,0,0,0,0,0,14,4,0,0,0,0,0,2,16,2,0,0,4 +0,0,2,15,14,4,0,0,0,0,8,16,16,11,0,0,0,0,7,16,16,10,0,0,0,0,7,16,16,9,0,0,0,0,10,16,16,6,0,0,0,0,10,16,16,8,0,0,0,0,13,16,16,13,0,0,0,0,2,11,15,7,0,0,1 +0,0,6,11,12,3,0,0,0,0,9,16,16,13,0,0,0,0,5,16,16,9,0,0,0,0,5,16,16,7,0,0,0,0,7,16,16,6,0,0,0,0,8,16,16,6,0,0,0,0,9,16,16,6,0,0,0,0,5,12,11,3,0,0,1 +0,0,0,11,12,2,0,0,0,0,6,16,16,10,0,0,0,0,12,14,6,16,1,0,0,2,16,12,0,14,4,0,0,3,16,9,0,14,6,0,0,0,16,8,1,16,5,0,0,0,9,14,12,16,2,0,0,0,1,11,16,6,0,0,0 +0,0,5,15,13,2,0,0,0,0,14,13,9,13,0,0,0,0,10,0,0,10,2,0,0,0,0,0,0,11,3,0,0,0,0,0,2,15,0,0,0,0,0,1,12,9,0,0,0,0,10,12,16,14,2,0,0,0,5,14,12,11,4,0,2 +0,0,13,13,13,8,0,0,0,0,7,8,16,5,0,0,0,0,0,2,16,1,0,0,0,1,3,9,14,5,8,0,0,10,16,16,16,15,6,0,0,3,11,15,3,0,0,0,0,0,10,9,0,0,0,0,0,0,13,4,0,0,0,0,7 +0,0,12,16,16,6,0,0,0,9,16,9,12,15,0,0,0,8,10,0,3,16,4,0,0,0,0,0,6,16,0,0,0,0,0,1,14,11,0,0,0,0,1,10,14,3,0,0,0,2,16,16,9,4,4,0,0,1,10,16,16,16,16,2,2 +0,0,2,13,15,2,0,0,0,0,11,16,13,10,0,0,0,0,14,9,8,12,0,0,0,0,8,14,16,10,0,0,0,0,5,16,13,1,0,0,0,0,9,15,14,2,0,0,0,0,7,14,14,6,0,0,0,0,1,13,16,2,0,0,8 +0,1,13,16,16,6,0,0,0,10,16,11,11,13,0,0,0,5,12,0,4,16,2,0,0,0,0,0,6,16,1,0,0,0,0,0,9,14,0,0,0,0,0,4,16,5,0,0,0,0,6,16,15,9,8,3,0,0,12,16,16,14,13,6,2 +0,0,0,8,11,0,0,0,0,0,7,16,11,9,0,0,0,0,14,15,4,10,2,0,0,1,16,5,0,6,6,0,0,3,12,0,3,8,9,0,0,3,13,0,0,13,8,0,0,0,11,5,6,15,3,0,0,0,1,11,13,9,0,0,0 +0,0,9,14,16,9,1,0,0,4,15,7,2,14,4,0,0,0,1,1,8,14,0,0,0,0,0,2,16,6,0,0,0,0,0,0,2,14,0,0,0,0,0,0,0,6,10,0,0,3,9,4,4,10,10,0,0,1,10,13,12,9,0,0,3 +0,0,10,16,16,12,0,0,0,0,6,16,9,7,0,0,0,0,7,16,4,0,0,0,0,0,11,16,16,10,1,0,0,0,3,8,6,13,11,0,0,0,0,0,0,7,16,0,0,0,0,0,3,15,15,0,0,0,10,16,16,13,2,0,5 +0,0,12,12,14,13,1,0,0,2,16,16,11,10,1,0,0,6,16,5,0,0,0,0,0,8,16,15,8,0,0,0,0,3,14,9,16,3,0,0,0,0,0,0,14,8,0,0,0,1,7,4,14,11,0,0,0,0,13,16,16,8,0,0,5 +0,1,10,16,16,14,0,0,0,10,16,10,6,4,0,0,0,7,14,3,0,0,0,0,0,9,16,16,11,1,0,0,0,5,11,4,13,5,0,0,0,0,0,0,9,8,0,0,0,0,1,3,15,4,0,0,0,0,14,14,10,0,0,0,5 +0,1,13,16,16,4,0,0,0,2,15,9,12,12,0,0,0,0,0,5,15,9,0,0,0,0,0,16,16,14,3,0,0,0,0,4,4,11,12,0,0,3,6,0,0,5,16,0,0,7,14,4,8,15,10,0,0,0,13,16,16,11,1,0,3 +0,0,0,6,13,0,0,0,0,0,1,13,9,0,0,0,0,0,4,16,4,0,0,0,0,0,8,14,0,0,0,0,0,0,13,15,12,8,0,0,0,0,12,14,5,10,12,0,0,0,8,13,4,4,15,2,0,0,0,5,12,14,7,0,6 +0,0,7,13,14,4,0,0,0,8,15,7,8,16,0,0,0,12,12,0,2,16,4,0,0,3,13,16,16,16,2,0,0,0,0,0,0,11,9,0,0,0,0,0,0,6,12,0,0,0,5,0,0,10,12,0,0,0,8,12,14,10,2,0,9 +0,0,3,15,15,3,0,0,0,0,11,14,7,13,0,0,0,0,11,11,12,16,0,0,0,0,3,16,16,16,0,0,0,0,6,16,14,1,0,0,0,0,16,10,15,1,0,0,0,0,14,6,14,3,0,0,0,0,4,15,16,4,0,0,8 +0,0,0,10,15,0,0,0,0,0,8,15,5,7,11,0,0,2,16,6,0,13,11,0,0,10,16,3,6,16,3,0,0,4,16,16,16,16,8,0,0,0,4,12,16,3,0,0,0,0,0,10,15,0,0,0,0,0,0,12,10,0,0,0,4 +0,0,3,16,10,0,0,0,0,1,10,16,16,1,0,0,0,10,16,16,15,0,0,0,0,3,11,16,16,0,0,0,0,0,0,14,16,3,0,0,0,0,0,14,16,3,0,0,0,0,1,16,16,5,0,0,0,0,1,14,13,0,0,0,1 +0,0,5,14,14,6,0,0,0,0,11,5,2,12,0,0,0,0,11,1,8,11,0,0,0,0,7,13,16,6,0,0,0,0,3,16,8,0,0,0,0,0,11,10,12,1,0,0,0,0,14,2,4,12,0,0,0,0,5,15,16,12,0,0,8 +0,0,1,15,13,1,0,0,0,0,8,16,16,8,0,0,3,13,16,16,16,6,0,0,0,9,8,9,16,6,0,0,0,0,0,10,16,1,0,0,0,0,0,12,16,0,0,0,0,0,0,16,14,1,0,0,0,0,0,12,16,1,0,0,1 +0,0,6,16,16,8,0,0,0,1,16,11,8,15,0,0,0,0,15,10,14,14,0,0,0,0,8,16,16,3,0,0,0,0,10,16,10,0,0,0,0,3,16,7,15,2,0,0,0,1,16,6,12,7,0,0,0,0,5,16,15,5,0,0,8 +0,0,4,13,13,2,0,0,0,2,15,7,4,7,0,0,0,6,12,0,5,9,0,0,0,3,11,3,7,14,1,0,0,0,6,15,16,14,4,0,0,0,0,0,0,4,11,0,0,0,0,0,0,4,15,0,0,0,3,13,16,16,8,0,9 +0,1,16,16,15,5,0,0,0,1,16,13,9,15,3,0,0,3,16,0,5,16,5,0,0,0,12,16,16,16,6,0,0,0,1,4,4,12,5,0,0,0,0,0,0,12,9,0,0,0,1,0,2,14,7,0,0,0,12,16,16,15,3,0,9 +0,0,1,15,13,0,0,0,0,0,3,16,16,2,0,0,0,1,12,16,12,0,0,0,0,8,16,16,8,0,0,0,0,2,12,16,6,0,0,0,0,0,7,16,7,0,0,0,0,0,4,16,8,0,0,0,0,0,1,14,9,0,0,0,1 +0,0,7,15,15,9,1,0,0,7,15,5,4,14,4,0,0,2,2,0,7,13,1,0,0,0,2,12,15,5,0,0,0,0,1,5,10,13,0,0,0,0,0,0,0,10,7,0,0,0,11,1,1,12,7,0,0,0,8,14,13,8,0,0,3 +0,2,7,12,16,15,1,0,1,15,16,13,10,6,1,0,0,5,16,6,0,0,0,0,0,6,16,16,8,0,0,0,0,5,15,5,14,7,0,0,0,0,2,0,8,14,0,0,0,0,5,2,12,13,0,0,0,0,8,16,15,4,0,0,5 +0,0,3,16,3,0,0,0,0,0,11,13,1,1,0,0,0,6,14,2,9,12,0,0,0,9,12,0,14,5,1,0,0,7,16,12,16,16,11,0,0,0,6,11,15,5,1,0,0,0,0,11,10,0,0,0,0,0,3,16,4,0,0,0,4 +0,0,0,6,16,10,0,0,0,0,3,15,15,4,0,0,0,0,5,16,8,0,0,0,0,0,10,16,4,0,0,0,0,0,10,16,8,1,0,0,0,1,15,16,16,15,2,0,0,1,13,16,15,16,7,0,0,0,0,5,15,15,5,0,6 +0,0,3,14,15,5,0,0,0,0,11,14,5,11,2,0,0,0,14,7,0,7,4,0,0,1,16,2,0,3,7,0,0,4,13,0,0,5,8,0,0,2,12,0,0,11,7,0,0,0,14,8,13,14,0,0,0,0,3,12,10,3,0,0,0 +0,0,12,16,16,10,0,0,0,0,13,13,8,16,5,0,0,0,0,0,0,16,8,0,0,0,1,8,13,15,2,0,0,0,7,16,16,15,3,0,0,0,0,3,4,11,15,0,0,1,11,0,2,13,15,0,0,1,13,16,16,14,6,0,3 +0,0,9,16,11,2,0,0,0,9,16,8,13,8,0,0,0,8,6,0,0,12,0,0,0,0,0,0,8,10,0,0,0,0,0,0,14,6,0,0,0,0,0,8,11,0,0,0,0,0,8,16,6,4,4,0,0,0,12,15,15,16,13,0,2 +0,2,15,16,16,16,11,0,0,1,8,8,9,16,9,0,0,0,0,0,8,12,0,0,0,1,4,4,15,9,0,0,0,11,16,16,16,15,2,0,0,2,5,16,5,0,0,0,0,0,11,12,0,0,0,0,0,3,16,5,0,0,0,0,7 +0,0,2,16,15,4,0,0,0,0,10,15,11,11,0,0,0,0,10,12,15,10,0,0,0,0,7,16,14,2,0,0,0,0,12,16,7,0,0,0,0,0,14,7,15,1,0,0,0,0,11,7,13,7,0,0,0,0,2,14,15,7,0,0,8 +0,0,7,16,16,12,2,0,0,0,12,16,13,14,7,0,0,2,16,11,0,0,0,0,0,5,16,16,11,0,0,0,0,11,16,11,16,3,0,0,0,1,2,0,13,8,0,0,0,0,2,4,13,8,0,0,0,0,8,16,16,4,0,0,5 +0,0,8,14,12,8,6,0,0,0,12,11,6,8,8,0,0,0,14,5,4,1,0,0,0,4,16,14,12,14,1,0,0,5,7,0,0,9,7,0,0,0,0,0,0,10,4,0,0,0,5,2,4,13,1,0,0,0,8,16,14,1,0,0,5 +0,0,0,0,14,4,0,0,0,0,0,5,15,1,0,0,0,0,0,14,8,0,2,0,0,0,8,14,2,7,13,0,0,3,15,5,0,13,7,0,0,12,15,8,10,16,2,0,1,9,12,12,15,15,1,0,0,0,0,0,15,4,0,0,4 +0,0,1,12,6,0,0,0,0,0,10,13,1,0,0,0,0,0,16,1,0,0,0,0,0,2,12,0,0,0,0,0,0,4,15,14,16,15,4,0,0,3,16,4,0,2,13,0,0,0,11,7,0,4,15,0,0,0,0,10,16,14,5,0,6 +0,3,13,16,14,2,0,0,0,14,11,3,16,8,0,0,0,7,3,7,16,8,0,0,0,0,5,16,16,16,5,0,0,0,2,10,6,11,12,0,0,0,0,0,0,11,13,0,0,1,10,1,4,15,9,0,0,2,15,16,16,11,0,0,3 +0,0,10,13,9,0,0,0,0,5,11,1,11,5,0,0,0,6,7,0,6,14,0,0,0,2,14,8,9,16,1,0,0,0,2,8,5,13,4,0,0,0,0,0,0,7,9,0,0,0,3,2,0,5,11,0,0,0,8,15,13,14,3,0,9 +0,3,16,15,3,0,0,0,0,8,14,12,11,0,0,0,0,3,11,2,16,3,0,0,0,0,0,2,16,2,0,0,0,0,0,5,16,0,0,0,0,0,0,10,12,0,0,0,0,0,11,16,13,13,10,1,0,2,14,16,15,12,12,1,2 +0,0,12,16,16,16,14,0,0,0,5,8,8,15,10,0,0,0,0,0,5,15,1,0,0,0,2,8,14,16,9,0,0,0,13,16,15,12,7,0,0,0,1,14,6,0,0,0,0,0,4,14,0,0,0,0,0,0,12,9,0,0,0,0,7 +0,0,12,12,12,5,0,0,0,0,4,16,16,8,0,0,0,0,2,16,16,9,0,0,0,0,4,16,16,9,0,0,0,0,4,16,16,4,0,0,0,0,5,16,16,3,0,0,0,0,9,16,16,0,0,0,0,0,11,12,12,4,0,0,1 +0,3,15,15,3,0,0,0,0,8,15,14,14,0,0,0,0,4,9,2,16,5,0,0,0,0,0,2,16,5,0,0,0,0,0,7,15,1,0,0,0,0,0,14,9,0,0,0,0,1,11,16,10,8,4,0,0,5,16,16,16,16,11,0,2 +0,0,10,13,16,10,0,0,0,0,15,9,2,2,0,0,0,4,16,16,14,4,0,0,0,0,3,1,4,16,3,0,0,0,0,0,0,12,7,0,0,0,0,0,0,12,7,0,0,5,8,0,4,15,1,0,0,1,11,14,15,2,0,0,5 +0,0,1,14,2,0,0,0,0,0,5,14,0,0,0,0,0,0,8,10,0,0,0,0,0,0,12,6,0,0,0,0,0,0,16,9,12,9,2,0,0,0,16,13,8,10,13,0,0,0,11,12,0,4,15,0,0,0,0,13,16,16,9,0,6 +0,0,10,14,15,12,5,0,0,0,14,4,2,2,0,0,0,3,14,10,10,5,0,0,0,6,15,10,6,15,2,0,0,1,0,0,0,8,8,0,0,0,0,0,0,6,8,0,0,0,11,2,3,12,3,0,0,0,10,16,15,5,0,0,5 +0,0,6,12,16,10,0,0,0,0,0,12,16,16,1,0,0,0,0,9,16,16,4,0,0,0,0,13,16,16,3,0,0,0,0,14,16,14,0,0,0,0,0,14,16,10,0,0,0,0,2,16,16,13,0,0,0,0,7,16,15,2,0,0,1 +0,0,4,15,16,16,16,14,0,0,6,12,12,13,16,8,0,0,0,0,0,8,14,1,0,0,0,6,8,16,4,0,0,0,2,16,16,13,2,0,0,0,0,7,14,1,0,0,0,0,2,14,6,0,0,0,0,0,8,13,1,0,0,0,7 +0,0,13,16,16,6,0,0,0,6,16,6,10,16,0,0,0,1,7,5,14,8,0,0,0,0,1,15,16,13,1,0,0,0,0,3,4,13,10,0,0,0,0,0,0,9,11,0,0,2,14,5,6,16,4,0,0,1,15,16,16,10,0,0,3 +0,0,3,14,4,0,0,0,0,0,12,15,12,4,0,0,0,3,15,5,2,12,0,0,0,5,9,0,0,8,5,0,0,8,8,0,0,4,8,0,0,4,12,0,0,2,12,0,0,0,14,5,0,9,8,0,0,0,3,15,16,13,1,0,0 +0,0,13,16,16,10,0,0,0,1,11,6,9,16,6,0,0,0,0,5,10,16,5,0,0,0,2,15,16,15,0,0,0,0,0,0,5,15,7,0,0,0,0,0,0,13,8,0,0,1,13,10,6,14,7,0,0,1,11,16,16,12,0,0,3 +0,0,0,14,4,0,0,0,0,0,7,15,1,0,0,0,0,0,11,11,0,0,0,0,0,0,13,6,0,0,0,0,0,0,16,8,8,5,0,0,0,0,15,15,9,11,12,0,0,0,9,15,0,0,15,4,0,0,1,11,13,12,12,1,6 +0,0,3,12,7,2,0,0,0,0,9,16,16,8,0,0,0,0,7,16,16,10,0,0,0,0,6,16,16,12,0,0,0,0,6,16,16,11,0,0,0,0,4,16,16,10,0,0,0,0,8,16,16,12,0,0,0,0,3,11,12,6,0,0,1 +0,0,13,16,5,0,0,0,0,0,16,12,15,0,0,0,0,0,3,3,12,4,0,0,0,0,0,0,12,6,0,0,0,0,0,0,14,7,0,0,0,0,0,2,16,5,0,0,0,0,5,14,16,9,4,0,0,0,15,16,13,12,16,2,2 +0,5,16,16,15,2,0,0,0,11,13,8,11,15,0,0,0,1,1,0,7,16,0,0,0,0,0,11,16,14,1,0,0,0,0,12,12,15,11,0,0,0,0,0,0,8,12,0,0,5,11,4,3,12,11,0,0,4,14,16,16,16,4,0,3 +0,0,8,16,13,13,16,8,0,0,7,12,12,10,16,4,0,0,0,0,0,11,8,0,0,0,0,2,6,15,1,0,0,0,9,16,16,16,6,0,0,0,5,14,13,2,0,0,0,0,4,16,5,0,0,0,0,0,12,11,0,0,0,0,7 +0,0,0,1,14,10,0,0,0,0,0,9,15,2,0,0,0,0,1,14,5,0,2,0,0,0,11,9,0,8,14,0,0,7,14,0,0,14,10,0,4,16,15,11,9,16,4,0,1,12,14,16,16,12,0,0,0,0,0,0,14,6,0,0,4 +0,0,7,16,15,2,0,0,0,7,15,7,12,10,0,0,0,8,6,0,10,12,0,0,0,0,0,6,16,12,1,0,0,0,0,5,12,15,10,0,0,0,0,0,0,8,12,0,0,0,6,9,4,13,10,0,0,0,5,16,16,13,1,0,3 +0,0,10,14,15,10,2,0,0,2,15,5,4,16,8,0,0,0,13,0,8,13,1,0,0,3,15,12,14,2,0,0,0,1,12,14,13,1,0,0,0,6,11,0,9,12,0,0,0,7,11,0,1,16,3,0,0,1,10,13,14,12,0,0,8 +0,0,13,16,15,2,0,0,0,5,16,7,11,13,0,0,0,8,16,2,1,14,6,0,0,8,15,0,0,8,11,0,0,10,12,0,0,4,12,0,0,11,12,0,0,7,12,0,0,7,15,4,5,15,8,0,0,0,13,16,16,10,0,0,0 +0,0,4,13,14,3,0,0,0,0,14,7,4,14,0,0,0,3,10,0,0,8,3,0,0,8,4,0,0,4,8,0,0,5,12,0,0,2,8,0,0,0,15,1,0,4,7,0,0,0,10,8,1,12,6,0,0,0,2,12,16,15,0,0,0 +0,0,5,11,16,12,2,0,0,0,15,7,4,13,4,0,0,1,14,1,4,16,2,0,0,1,7,8,14,5,0,0,0,5,16,16,7,0,0,0,0,0,14,6,14,6,0,0,0,0,11,6,5,16,0,0,0,0,5,15,14,8,0,0,8 +0,0,0,0,11,12,0,0,0,0,0,2,15,8,0,0,0,0,0,9,15,3,0,0,0,0,5,15,4,15,5,0,0,0,13,10,0,16,4,0,0,10,15,4,10,16,3,0,0,15,16,16,16,15,3,0,0,0,1,0,11,12,0,0,4 +0,0,1,13,3,0,0,0,0,0,11,13,0,0,0,0,0,0,14,5,0,0,0,0,0,0,16,0,0,0,0,0,0,2,16,16,16,15,4,0,0,1,16,10,4,8,13,0,0,0,10,13,1,5,14,0,0,0,0,13,16,16,8,0,6 +0,0,5,13,12,9,0,0,0,2,14,4,1,16,4,0,0,7,10,0,3,16,1,0,0,5,14,8,15,6,0,0,0,0,6,16,13,0,0,0,0,0,11,8,13,5,0,0,0,0,13,1,1,15,0,0,0,0,6,14,14,10,0,0,8 +0,0,3,11,13,14,16,8,0,0,11,14,12,12,16,3,0,0,0,0,1,15,8,0,0,0,0,2,9,15,1,0,0,0,2,15,16,16,8,0,0,0,1,12,14,0,0,0,0,0,0,12,9,0,0,0,0,0,4,15,2,0,0,0,7 +0,0,7,15,6,0,0,0,0,0,14,14,13,0,0,0,0,0,3,4,13,0,0,0,0,0,0,8,9,0,0,0,0,0,0,12,5,0,0,0,0,0,3,16,1,0,0,0,0,0,11,14,10,12,11,2,0,0,7,15,11,8,9,3,2 +0,0,8,15,16,12,0,0,0,12,16,14,12,6,0,0,0,10,16,5,0,0,0,0,0,6,16,5,0,0,0,0,0,0,8,16,8,0,0,0,0,0,0,6,16,10,0,0,0,0,6,8,15,12,0,0,0,0,7,14,8,0,0,0,5 +0,0,9,12,10,5,0,0,0,5,16,16,16,13,0,0,0,1,16,16,16,16,0,0,0,0,8,16,16,16,3,0,0,0,4,16,16,16,4,0,0,0,2,16,16,16,1,0,0,0,9,16,16,16,1,0,0,0,5,12,12,5,0,0,1 +0,0,6,13,16,15,2,0,0,0,14,14,12,16,9,0,0,0,3,1,7,16,10,0,0,0,1,15,16,16,13,0,0,0,1,7,15,14,1,0,0,0,0,4,16,10,0,0,0,0,0,12,13,1,0,0,0,0,7,16,3,0,0,0,7 +0,0,7,14,3,0,0,0,0,3,16,16,12,0,0,0,0,10,15,10,16,16,2,0,0,8,16,16,16,10,2,0,0,4,16,15,2,0,0,0,0,4,16,16,4,0,0,0,0,3,16,16,15,0,0,0,0,0,9,16,15,0,0,0,8 +0,0,0,0,11,12,0,0,0,0,0,8,16,4,0,0,0,0,2,16,12,1,0,0,0,4,15,16,13,14,11,0,0,13,16,16,16,16,10,0,0,0,0,0,10,16,6,0,0,0,0,0,11,16,2,0,0,0,0,0,14,13,0,0,4 +0,0,2,9,15,11,0,0,0,3,15,16,16,16,0,0,0,3,13,3,9,16,2,0,0,0,0,6,14,16,13,0,0,0,5,16,16,13,2,0,0,0,5,11,16,4,0,0,0,0,0,11,15,0,0,0,0,0,0,16,7,0,0,0,7 +0,0,2,10,13,4,0,0,0,0,11,16,15,5,0,0,0,5,16,11,2,0,0,0,0,10,15,2,0,0,0,0,0,3,13,15,5,0,0,0,0,0,0,8,16,6,0,0,0,0,2,10,16,7,0,0,0,0,3,15,10,0,0,0,5 +0,0,3,15,15,1,0,0,0,0,9,16,12,2,0,0,0,4,16,9,6,9,2,0,0,8,16,16,16,10,3,0,0,2,16,16,14,1,0,0,0,4,16,16,16,4,0,0,0,1,14,16,16,12,0,0,0,0,3,14,12,5,0,0,8 +0,0,7,11,0,0,0,0,0,0,14,13,0,0,0,0,0,3,16,10,3,0,0,0,0,3,16,16,16,11,0,0,0,6,16,16,16,16,1,0,0,4,16,6,1,16,9,0,0,2,15,14,14,16,3,0,0,0,5,16,14,6,0,0,6 +0,0,2,10,16,3,0,0,0,0,15,16,16,4,0,0,0,3,12,1,16,15,9,0,0,0,0,7,16,16,13,0,0,0,0,16,15,2,0,0,0,0,0,8,12,0,0,0,0,0,0,13,9,0,0,0,0,0,0,14,3,0,0,0,7 +0,0,3,12,9,9,0,0,0,0,12,16,16,16,0,0,0,0,12,5,14,16,4,0,0,0,12,0,2,13,2,0,0,2,15,0,0,12,3,0,0,2,16,2,0,12,0,0,0,0,15,14,13,9,0,0,0,0,2,10,8,0,0,0,0 +0,0,5,13,13,2,0,0,0,0,12,14,16,8,0,0,0,0,0,3,16,5,0,0,0,0,0,4,16,5,0,0,0,0,0,0,10,14,2,0,0,0,0,0,3,16,6,0,0,0,1,0,5,16,4,0,0,0,6,13,16,12,3,0,3 +0,0,0,6,12,6,0,0,0,0,5,16,16,13,0,0,0,2,15,16,16,12,0,0,0,4,16,16,16,14,1,0,0,0,8,16,16,13,0,0,0,0,4,16,16,14,0,0,0,0,6,16,16,16,4,0,0,0,1,12,12,6,0,0,1 +0,0,11,13,1,1,0,0,0,3,16,10,16,16,2,0,0,8,14,14,12,2,0,0,0,1,15,16,11,0,0,0,0,0,9,15,16,9,0,0,0,0,12,7,1,14,7,0,0,0,12,11,9,15,3,0,0,0,4,12,10,1,0,0,8 +0,0,1,11,0,0,0,0,0,0,8,16,2,0,0,0,0,0,11,12,0,0,0,0,0,0,12,10,0,0,0,0,0,0,15,14,9,6,0,0,0,0,15,16,16,16,9,0,0,0,11,16,11,16,14,0,0,0,1,12,13,10,2,0,6 +0,0,7,14,16,14,1,0,0,5,16,13,8,6,0,0,0,4,16,4,0,0,0,0,0,7,16,16,15,2,0,0,0,3,8,5,15,9,0,0,0,0,0,0,3,16,3,0,0,2,15,10,11,16,3,0,0,0,8,15,15,4,0,0,5 +0,0,0,4,15,7,0,0,0,0,0,15,15,1,0,0,0,0,8,16,2,0,0,0,0,4,16,7,6,9,0,0,0,10,16,13,16,16,0,0,2,16,16,12,15,11,0,0,4,9,4,3,16,7,0,0,0,0,0,5,16,5,0,0,4 +0,0,2,12,10,2,5,0,0,0,10,16,11,13,15,0,0,2,14,15,14,16,14,0,0,0,13,16,14,16,10,0,0,0,0,0,4,15,4,0,0,0,0,2,15,11,0,0,0,0,0,14,16,3,0,0,0,0,2,13,4,0,0,0,9 +0,0,9,16,9,0,0,0,0,3,16,16,16,1,0,0,0,0,6,12,16,14,4,0,0,0,4,14,16,16,13,0,0,0,8,16,16,7,0,0,0,0,0,13,14,0,0,0,0,0,5,16,8,0,0,0,0,0,9,13,1,0,0,0,7 +0,0,8,12,16,8,0,0,0,5,16,10,8,8,0,0,0,2,16,4,0,0,0,0,0,1,15,12,5,0,0,0,0,0,4,13,16,6,0,0,0,0,0,0,10,16,6,0,0,0,7,9,15,15,4,0,0,0,10,13,8,4,0,0,5 +0,0,0,1,14,8,0,0,0,0,0,12,14,2,0,0,0,0,7,16,4,0,0,0,0,5,16,11,3,10,5,0,0,11,16,16,16,16,6,0,0,7,12,11,16,13,1,0,0,0,0,5,16,10,0,0,0,0,0,2,16,9,0,0,4 +0,0,1,7,10,7,0,0,0,0,8,16,16,16,0,0,0,0,15,16,16,16,0,0,0,1,7,16,16,16,3,0,0,0,1,16,16,16,4,0,0,0,3,16,16,16,1,0,0,0,4,16,16,16,0,0,0,0,1,9,12,6,0,0,1 +0,0,5,12,16,4,0,0,0,2,16,15,10,4,0,0,0,4,16,14,16,5,0,0,0,4,15,16,16,9,0,0,0,0,7,16,16,13,2,0,0,0,10,15,6,16,8,0,0,0,14,14,13,14,1,0,0,0,7,16,11,5,0,0,8 +0,0,0,0,13,5,0,0,0,0,0,9,13,1,0,0,0,0,8,14,1,5,7,0,0,4,16,14,13,16,8,0,0,2,8,8,12,16,2,0,0,0,0,0,10,13,0,0,0,0,0,0,14,9,0,0,0,0,0,0,13,3,0,0,4 +0,0,10,13,2,0,0,0,0,0,14,16,4,0,0,0,0,2,16,14,0,0,0,0,0,6,16,14,2,0,0,0,0,7,16,16,16,13,1,0,0,2,16,16,16,16,7,0,0,2,16,16,16,16,4,0,0,0,8,15,16,13,0,0,6 +0,0,13,15,15,4,0,0,0,3,16,14,16,13,0,0,0,3,16,3,5,16,5,0,0,7,16,4,0,13,8,0,0,5,16,2,0,15,8,0,0,3,16,5,7,16,6,0,0,0,14,16,16,14,1,0,0,0,11,16,12,3,0,0,0 +0,0,10,13,7,1,0,0,0,6,16,15,16,5,0,0,0,9,9,8,16,2,0,0,0,0,2,1,16,8,0,0,0,0,0,0,9,16,1,0,0,0,0,0,2,16,10,0,0,0,7,11,14,15,7,0,0,0,11,13,12,3,0,0,3 +0,0,13,16,5,0,0,0,0,6,16,16,14,0,0,0,0,10,12,4,16,0,0,0,0,8,5,7,14,0,0,0,0,0,1,14,8,0,0,0,0,0,7,15,2,0,0,0,0,0,16,15,12,11,3,0,0,1,16,16,15,16,8,0,2 +0,0,0,13,10,1,0,0,0,0,8,16,11,1,0,0,0,0,12,14,0,0,0,0,0,0,16,10,0,0,0,0,0,3,16,13,8,3,0,0,0,2,16,16,16,16,3,0,0,0,10,16,16,16,15,0,0,0,0,10,16,15,7,0,6 +0,0,11,12,5,0,0,0,0,0,12,16,16,10,0,0,0,0,16,4,8,16,3,0,0,2,16,1,0,9,8,0,0,4,13,0,0,10,8,0,0,1,15,0,3,16,9,0,0,2,16,13,16,13,1,0,0,0,13,13,7,0,0,0,0 +0,0,13,16,6,0,0,0,0,4,16,16,16,1,0,0,0,2,15,4,16,5,0,0,0,0,1,1,16,7,0,0,0,0,0,2,16,7,0,0,0,0,0,11,16,2,0,0,0,0,10,16,16,16,15,4,0,0,13,16,11,8,14,4,2 +0,0,3,12,15,16,8,0,0,0,11,14,13,16,4,0,0,0,0,0,11,11,0,0,0,0,0,3,16,5,0,0,0,2,12,16,16,16,7,0,0,0,10,16,10,5,1,0,0,0,5,16,3,0,0,0,0,0,3,16,1,0,0,0,7 +0,0,2,12,16,7,0,0,0,5,13,13,14,14,0,0,0,6,15,0,0,10,3,0,0,6,12,0,0,5,8,0,0,6,10,0,0,11,5,0,0,3,13,2,13,16,5,0,0,0,11,16,16,15,2,0,0,0,3,16,15,4,0,0,0 +0,0,0,6,15,6,0,0,0,0,0,12,10,13,0,0,0,0,0,11,15,14,0,0,0,0,9,11,14,0,0,0,0,4,11,0,10,6,0,0,0,3,9,0,1,14,1,0,0,0,11,8,2,9,7,0,0,0,0,6,12,13,7,0,8 +0,0,0,0,15,12,0,0,0,0,0,5,16,15,0,0,0,0,3,14,16,10,0,0,0,3,15,16,16,9,0,0,0,11,15,9,16,10,0,0,0,0,0,6,16,7,0,0,0,0,0,7,16,8,0,0,0,0,0,1,13,16,6,0,1 +0,0,0,6,10,14,0,0,0,0,10,16,16,16,6,0,0,1,16,10,4,15,11,0,0,1,12,5,0,9,9,0,0,0,8,9,0,8,8,0,0,0,9,16,16,16,4,0,0,0,2,16,16,14,2,0,0,0,0,6,10,3,0,0,0 +0,0,0,7,12,14,2,0,0,0,3,14,7,13,8,0,0,0,6,14,11,16,5,0,0,2,13,16,15,8,1,0,0,5,12,6,15,0,0,0,0,7,10,0,8,10,0,0,0,2,12,7,5,16,3,0,0,0,0,9,13,14,2,0,8 +0,0,0,7,13,0,0,0,0,0,6,16,5,0,0,0,0,0,15,7,0,0,0,0,0,2,16,1,0,0,0,0,0,4,15,4,4,0,0,0,0,6,16,14,14,14,3,0,0,0,8,15,8,14,12,0,0,0,0,6,12,13,3,0,6 +0,0,6,15,16,16,11,0,0,2,16,11,7,15,16,3,0,2,15,16,16,16,13,0,0,0,5,11,16,12,1,0,0,0,0,8,16,4,0,0,0,0,0,12,11,0,0,0,0,0,5,16,4,0,0,0,0,0,10,14,0,0,0,0,9 +0,0,3,11,16,16,14,1,0,0,12,10,4,7,15,0,0,0,7,14,15,16,4,0,0,0,0,0,7,12,0,0,0,0,0,1,14,4,0,0,0,0,0,5,13,0,0,0,0,0,0,12,7,0,0,0,0,0,2,14,0,0,0,0,9 +0,0,4,14,16,11,0,0,0,0,13,12,9,15,4,0,0,0,11,0,0,8,6,0,0,4,12,0,0,6,8,0,0,6,8,0,0,10,7,0,0,3,13,4,10,16,2,0,0,0,13,16,16,15,0,0,0,0,5,14,12,3,0,0,0 +0,0,1,12,15,1,0,0,0,0,10,16,16,0,0,0,0,3,16,15,10,0,0,0,0,6,16,15,15,2,0,0,0,0,4,2,14,15,1,0,0,0,0,0,3,15,5,0,0,0,2,14,14,16,5,0,0,0,0,15,16,13,1,0,3 +0,0,0,7,12,14,5,0,0,0,11,10,4,10,16,0,0,1,16,12,11,16,14,0,0,1,10,12,12,16,10,0,0,0,0,0,1,15,3,0,0,0,0,0,11,9,0,0,0,0,0,6,13,0,0,0,0,0,0,11,9,0,0,0,9 +0,2,15,12,0,0,0,0,0,5,16,16,4,0,0,0,0,8,14,13,8,0,0,0,0,1,8,6,12,0,0,0,0,0,0,9,12,0,0,0,0,0,4,14,13,4,3,0,0,7,16,16,16,16,15,0,0,3,11,15,11,8,2,0,2 +0,0,0,8,12,11,3,0,0,0,6,15,6,8,12,0,0,2,15,10,0,3,11,0,0,4,16,14,12,14,4,0,0,0,8,16,16,14,0,0,0,0,0,0,5,16,2,0,0,0,0,4,11,15,0,0,0,0,0,13,12,6,0,0,9 +0,0,3,16,15,3,0,0,0,0,6,16,15,3,0,0,0,0,10,16,6,2,0,0,0,0,4,14,16,3,0,0,0,4,13,2,12,6,0,0,0,5,11,0,1,12,3,0,0,0,13,3,0,7,11,0,0,0,3,11,11,14,10,0,8 +0,0,10,10,12,15,12,0,0,0,8,16,13,16,8,0,0,0,0,0,11,14,0,0,0,0,9,12,16,14,7,0,0,1,16,16,15,11,5,0,0,0,7,16,6,0,0,0,0,0,7,16,1,0,0,0,0,0,10,11,0,0,0,0,7 +0,0,5,12,14,0,0,0,0,2,14,10,15,0,0,0,0,3,15,11,11,0,0,0,0,2,12,15,11,2,0,0,0,0,0,0,8,15,1,0,0,0,0,0,1,14,4,0,0,0,2,7,4,14,3,0,0,0,3,13,14,10,0,0,3 +0,0,1,11,14,4,0,0,0,1,7,14,14,15,1,0,0,6,13,0,0,11,5,0,0,8,9,0,0,4,8,0,0,5,12,0,0,4,8,0,0,1,15,2,0,10,8,0,0,0,9,16,16,16,2,0,0,0,1,12,14,9,0,0,0 +0,0,6,13,12,11,2,0,0,0,16,13,5,13,13,0,0,0,15,11,5,15,8,0,0,0,11,16,16,9,0,0,0,1,10,16,15,0,0,0,0,4,16,12,16,6,0,0,0,1,15,16,16,12,0,0,0,0,3,13,16,11,0,0,8 +0,0,0,10,16,5,0,0,0,0,9,16,14,2,0,0,0,1,16,13,0,0,0,0,0,4,16,11,4,0,0,0,0,7,16,16,16,15,2,0,0,1,15,10,4,13,13,0,0,0,7,16,15,16,15,0,0,0,0,7,16,16,7,0,6 +0,0,7,15,10,0,0,0,0,2,16,9,14,3,0,0,0,5,9,0,8,4,0,0,0,2,10,0,13,1,0,0,0,0,0,9,12,0,0,0,0,0,4,15,1,0,0,0,0,0,16,7,4,4,2,0,0,0,9,16,14,12,6,0,2 +0,0,0,6,10,1,15,6,0,0,5,16,4,8,15,1,0,1,15,8,2,15,8,0,0,4,16,14,13,15,0,0,0,1,15,16,16,13,0,0,0,0,3,10,15,0,0,0,0,0,0,11,14,1,0,0,0,0,0,10,15,0,0,0,4 +0,0,0,11,7,5,8,0,0,0,5,16,1,15,8,0,0,1,14,9,5,16,2,0,0,6,14,1,12,11,2,0,0,12,14,8,16,16,10,0,0,9,16,16,15,5,0,0,0,0,1,10,12,0,0,0,0,0,0,11,9,0,0,0,4 +0,0,13,13,1,0,0,0,0,2,16,16,9,0,0,0,0,9,16,12,12,0,0,0,0,12,10,8,13,0,0,0,0,5,7,10,14,0,0,0,0,0,2,15,12,0,0,0,0,1,15,16,16,14,6,0,0,0,11,16,16,14,6,0,2 +0,0,0,7,5,0,1,0,0,0,5,13,1,0,14,4,0,1,16,3,0,9,7,0,0,3,16,7,4,16,3,0,0,2,15,16,16,14,4,0,0,0,0,0,14,6,0,0,0,0,0,3,16,1,0,0,0,0,0,8,7,0,0,0,4 +0,0,0,1,11,10,0,0,0,0,0,8,16,13,0,0,0,0,7,16,16,7,0,0,0,5,16,16,16,0,0,0,0,4,10,16,16,0,0,0,0,0,0,15,16,0,0,0,0,0,0,11,16,4,0,0,0,0,0,0,11,13,1,0,1 +0,0,2,10,13,5,0,0,0,0,13,15,11,1,0,0,0,0,14,4,0,0,0,0,0,6,14,0,0,0,0,0,0,6,16,16,16,16,2,0,0,1,4,4,1,15,2,0,0,0,0,7,10,16,0,0,0,0,1,16,15,7,0,0,5 +0,0,0,0,6,11,0,0,0,0,0,4,16,12,0,0,0,0,1,12,16,11,0,0,0,1,12,13,16,7,0,0,0,9,16,13,16,3,0,0,0,2,3,6,16,3,0,0,0,0,0,6,16,3,0,0,0,0,0,0,9,8,0,0,1 +0,0,7,16,16,10,0,0,0,1,16,16,16,16,4,0,0,4,10,5,3,11,10,0,0,5,12,4,0,6,8,0,0,0,12,2,0,5,8,0,0,0,15,5,5,14,3,0,0,0,11,16,16,12,0,0,0,0,7,16,12,4,0,0,0 +0,0,1,9,16,15,4,0,0,0,4,16,10,14,10,0,0,0,3,16,7,14,11,0,0,0,9,16,16,13,0,0,0,5,16,16,16,2,0,0,0,6,14,9,16,2,0,0,0,0,12,16,16,4,0,0,0,0,0,12,11,3,0,0,8 +0,0,0,6,15,15,4,0,0,0,12,15,8,12,7,0,0,0,14,13,12,16,3,0,0,0,11,16,16,16,4,0,0,0,1,4,4,16,5,0,0,0,0,0,2,16,2,0,0,0,0,3,12,11,0,0,0,0,0,10,13,2,0,0,9 +0,0,8,16,16,14,0,0,0,1,16,12,8,5,0,0,0,3,16,6,0,0,0,0,0,10,16,8,2,0,0,0,0,3,12,14,15,7,0,0,0,0,0,0,8,16,3,0,0,0,11,5,6,16,5,0,0,0,10,16,16,12,0,0,5 +0,0,10,15,14,16,12,0,0,0,5,12,11,13,11,0,0,0,0,0,2,16,3,0,0,0,9,12,14,16,10,0,0,0,12,15,15,9,2,0,0,0,0,14,7,0,0,0,0,0,7,14,1,0,0,0,0,0,13,6,0,0,0,0,7 +0,0,6,11,16,11,1,0,0,4,14,4,9,16,0,0,0,0,15,2,11,15,2,0,0,0,8,15,9,12,5,0,0,0,0,0,0,8,8,0,0,0,0,0,0,8,5,0,0,5,13,2,0,14,2,0,0,0,8,16,16,8,0,0,9 +0,0,0,9,15,4,0,0,0,0,4,15,8,1,0,0,0,0,12,11,0,0,0,0,0,0,15,5,0,0,0,0,0,2,16,15,16,12,3,0,0,0,16,13,5,8,13,0,0,0,7,13,4,6,15,0,0,0,0,8,15,16,11,0,6 +0,4,16,16,9,1,0,0,0,2,11,9,15,7,0,0,0,0,0,0,14,6,0,0,0,0,0,5,16,2,0,0,0,0,1,14,12,0,0,0,0,3,15,13,1,0,0,0,0,11,16,13,8,6,3,0,0,4,14,16,16,16,11,0,2 +0,0,2,13,12,4,0,0,0,1,15,12,3,13,2,0,0,8,13,8,0,6,4,0,0,8,4,3,0,3,8,0,0,5,5,0,0,6,6,0,0,1,14,1,0,8,3,0,0,0,8,8,5,13,1,0,0,0,0,11,15,6,0,0,0 +0,0,3,14,9,2,0,0,0,3,16,12,7,13,0,0,0,3,14,1,8,14,0,0,0,0,6,14,16,2,0,0,0,0,2,16,15,9,0,0,0,0,12,10,1,10,9,0,0,0,11,7,0,7,13,0,0,0,1,11,16,15,4,0,8 +0,0,0,2,15,9,0,0,0,0,0,7,16,15,0,0,0,0,5,15,16,10,0,0,0,3,16,16,16,6,0,0,0,3,12,14,16,5,0,0,0,0,0,8,16,8,0,0,0,0,0,5,16,15,2,0,0,0,0,2,13,15,4,0,1 +0,0,0,9,12,1,0,0,0,0,10,15,12,15,2,0,0,2,16,4,0,11,6,0,0,5,13,0,0,8,8,0,0,8,12,0,0,7,7,0,0,6,14,0,0,13,4,0,0,0,15,10,7,15,1,0,0,0,2,11,13,3,0,0,0 +0,0,0,15,12,5,0,0,0,0,4,16,16,8,0,0,0,0,9,16,15,3,0,0,0,1,16,16,9,0,0,0,0,6,16,16,9,0,0,0,0,0,11,16,11,0,0,0,0,0,4,16,14,1,0,0,0,0,0,13,16,0,0,0,1 +0,1,13,16,15,5,0,0,0,6,15,12,15,11,0,0,0,0,2,0,9,12,0,0,0,0,0,1,16,4,0,0,0,0,1,12,14,0,0,0,0,5,15,14,2,0,0,0,0,9,16,12,7,2,3,0,0,1,15,16,16,16,6,0,2 +0,0,8,14,16,12,1,0,0,2,11,6,5,15,4,0,0,0,0,1,9,14,0,0,0,0,0,11,11,0,0,0,0,0,0,2,15,7,0,0,0,0,0,0,4,16,3,0,0,0,9,5,4,15,3,0,0,0,9,15,13,5,0,0,3 +0,0,3,16,13,2,0,0,0,0,11,7,7,12,0,0,0,2,16,1,0,9,3,0,0,5,13,1,0,6,6,0,0,7,6,0,0,5,8,0,0,4,8,0,0,10,5,0,0,1,13,5,8,15,1,0,0,0,4,15,15,4,0,0,0 +0,0,12,16,16,16,10,0,0,0,4,8,11,16,13,0,0,0,2,0,4,16,5,0,0,6,15,12,15,16,11,0,0,1,11,16,15,11,3,0,0,0,1,13,8,0,0,0,0,0,8,16,1,0,0,0,0,0,10,10,0,0,0,0,7 +0,0,0,12,15,2,0,0,0,0,7,16,7,1,0,0,0,0,15,10,0,0,0,0,0,1,16,9,1,0,0,0,0,3,16,16,15,5,0,0,0,1,15,11,4,12,9,0,0,0,10,12,3,11,14,0,0,0,1,9,16,16,6,0,6 +0,0,6,12,12,12,4,0,0,3,16,8,6,16,7,0,0,4,13,0,5,16,4,0,0,1,13,13,15,16,4,0,0,0,1,8,4,12,4,0,0,0,2,0,0,12,7,0,0,6,15,4,0,13,4,0,0,0,8,15,16,16,2,0,9 +0,0,7,14,14,13,3,0,0,0,14,7,4,9,6,0,0,4,13,0,0,0,0,0,0,6,16,14,8,1,0,0,0,0,0,3,9,13,0,0,0,0,0,0,0,13,0,0,0,0,9,0,3,14,0,0,0,0,11,16,14,3,0,0,5 +0,0,0,0,14,10,0,0,0,0,1,11,16,12,0,0,0,2,12,16,16,12,0,0,0,4,11,5,15,11,0,0,0,0,0,1,16,7,0,0,0,0,0,3,16,7,0,0,0,0,0,1,16,11,0,0,0,0,0,0,11,9,0,0,1 +0,0,0,9,12,0,0,0,0,0,1,15,5,1,5,0,0,1,12,10,0,13,9,0,0,7,16,9,10,16,7,0,0,4,16,16,16,16,10,0,0,0,0,0,14,11,1,0,0,0,0,5,16,1,0,0,0,0,0,12,6,0,0,0,4 +0,0,0,14,15,3,0,0,0,0,7,16,9,2,0,0,0,0,14,12,0,0,0,0,0,2,16,7,0,0,0,0,0,7,16,16,12,2,0,0,0,1,15,8,9,15,1,0,0,0,9,12,7,16,3,0,0,0,0,12,16,14,1,0,6 +0,0,0,4,12,13,2,0,0,0,2,13,16,15,0,0,0,4,16,16,16,9,0,0,0,3,10,15,16,6,0,0,0,0,0,13,16,0,0,0,0,0,0,14,16,1,0,0,0,0,0,9,16,10,0,0,0,0,0,2,15,13,1,0,1 +0,0,0,8,13,6,0,0,0,0,5,14,6,14,2,0,0,0,8,8,1,14,4,0,0,0,2,13,11,16,5,0,0,0,0,3,7,9,4,0,0,1,2,0,0,8,8,0,0,1,13,3,0,14,5,0,0,0,0,11,16,13,1,0,9 +0,0,8,15,12,6,0,0,0,1,16,6,5,13,2,0,0,0,16,4,6,15,3,0,0,0,9,16,16,8,0,0,0,0,7,15,14,10,0,0,0,0,13,12,0,13,7,0,0,3,16,5,4,15,8,0,0,0,9,14,16,12,1,0,8 +0,0,1,9,16,16,12,1,0,0,10,13,8,12,16,3,0,0,2,0,1,13,10,0,0,0,0,9,16,11,0,0,0,0,0,14,16,15,0,0,0,0,0,7,10,16,2,0,0,0,2,16,14,13,0,0,0,0,0,14,14,2,0,0,3 +0,0,8,15,15,12,4,0,0,0,15,4,3,10,16,0,0,0,8,14,12,16,12,0,0,0,0,2,4,9,8,0,0,0,0,0,0,9,7,0,0,1,1,0,0,12,4,0,0,7,11,1,4,12,1,0,0,0,10,16,13,3,0,0,9 +0,0,4,16,13,12,11,0,0,0,7,13,4,7,11,0,0,0,12,1,0,0,0,0,0,5,15,12,8,1,0,0,0,2,8,8,12,12,0,0,0,0,0,0,1,13,0,0,0,0,7,7,7,11,0,0,0,0,4,15,14,2,0,0,5 +0,0,0,6,14,2,0,0,0,0,4,16,9,3,0,0,0,0,13,8,0,0,0,0,0,0,15,10,2,0,0,0,0,4,16,11,13,9,0,0,0,0,10,8,0,9,8,0,0,0,2,13,3,10,8,0,0,0,0,4,14,16,2,0,6 +0,0,1,11,14,10,1,0,0,1,13,10,8,12,8,0,0,6,11,0,1,13,4,0,0,2,15,12,15,6,0,0,0,0,13,16,15,2,0,0,0,1,14,0,7,12,0,0,0,0,9,9,4,16,0,0,0,0,1,9,15,10,0,0,8 +0,1,9,12,14,10,1,0,0,7,15,7,6,15,8,0,0,1,1,0,3,16,5,0,0,0,2,11,16,9,0,0,0,0,2,14,16,8,0,0,0,0,0,1,7,15,7,0,0,0,12,6,6,15,7,0,0,0,8,15,14,9,0,0,3 +0,0,2,14,10,0,0,0,0,0,9,10,10,6,0,0,0,0,16,2,0,12,2,0,0,4,12,0,0,8,5,0,0,5,8,0,0,9,8,0,0,3,12,0,0,14,3,0,0,0,11,6,7,13,0,0,0,0,3,16,15,3,0,0,0 +0,0,0,6,13,6,0,0,0,0,0,12,16,13,0,0,0,0,10,16,16,10,0,0,0,5,16,16,16,7,0,0,0,2,5,13,16,4,0,0,0,0,0,9,16,4,0,0,0,0,0,12,16,1,0,0,0,0,0,8,14,2,0,0,1 +0,0,0,0,5,16,8,0,0,0,0,0,13,16,12,0,0,0,0,9,16,16,9,0,0,0,10,16,14,16,8,0,0,4,15,11,8,16,5,0,0,0,7,1,9,16,3,0,0,0,0,0,12,16,0,0,0,0,0,0,8,15,2,0,1 +0,0,8,12,12,6,0,0,0,0,15,16,16,16,0,0,0,0,8,16,16,16,0,0,0,0,6,16,16,16,4,0,0,0,5,16,16,16,4,0,0,0,4,16,16,16,7,0,0,0,15,16,16,16,8,0,0,0,4,9,11,8,4,0,1 +0,0,3,12,14,2,0,0,0,0,12,11,13,11,0,0,0,7,16,1,4,16,2,0,0,6,16,7,14,16,1,0,0,0,13,16,15,3,0,0,0,0,11,16,16,10,0,0,0,0,14,16,10,16,8,0,0,0,2,11,16,16,12,0,8 +0,0,7,16,11,1,0,0,0,2,15,12,15,12,0,0,0,6,16,6,6,16,3,0,0,8,16,4,1,16,7,0,0,4,16,4,0,13,8,0,0,4,16,4,2,16,5,0,0,2,15,11,14,11,0,0,0,0,6,15,11,1,0,0,0 +0,0,5,16,16,13,5,0,0,0,4,8,8,14,15,0,0,0,0,0,1,16,7,0,0,0,0,0,7,15,0,0,0,2,15,16,16,16,4,0,0,1,4,12,12,0,0,0,0,0,0,15,5,0,0,0,0,0,5,15,0,0,0,0,7 +0,1,15,6,0,0,0,0,0,7,15,16,3,0,0,0,0,11,5,12,4,0,0,0,0,2,3,10,7,0,0,0,0,0,1,16,2,0,0,0,0,0,6,14,0,0,0,0,0,0,14,16,16,15,6,0,0,0,8,8,8,10,13,0,2 +0,0,3,11,12,13,14,3,0,0,8,14,9,12,16,2,0,0,1,0,0,13,11,0,0,0,1,12,13,16,7,0,0,0,1,8,16,11,2,0,0,0,0,6,16,2,0,0,0,0,0,14,11,0,0,0,0,0,2,13,4,0,0,0,7 +0,0,0,5,15,7,0,0,0,0,6,15,10,16,2,0,0,4,16,5,0,10,8,0,0,1,15,4,0,8,8,0,0,0,14,4,0,8,8,0,0,0,12,7,0,14,6,0,0,0,7,16,13,15,1,0,0,0,0,6,13,5,0,0,0 +0,0,1,13,9,0,0,0,0,0,7,16,10,0,0,0,0,0,14,15,1,0,0,0,0,1,16,14,4,0,0,0,0,3,16,16,16,10,1,0,0,0,13,15,8,15,9,0,0,0,8,16,7,11,13,0,0,0,0,8,13,16,13,1,6 +0,0,3,12,10,1,0,0,0,0,12,16,16,10,0,0,0,0,7,16,16,8,0,0,0,0,3,16,16,12,0,0,0,0,2,15,16,14,0,0,0,0,0,16,16,15,0,0,0,0,2,16,16,14,0,0,0,0,4,8,12,3,0,0,1 +0,0,3,13,16,9,1,0,0,3,13,14,7,16,6,0,0,6,16,4,5,16,3,0,0,6,16,8,15,11,0,0,0,1,16,16,13,1,0,0,0,4,16,16,14,2,0,0,0,2,16,15,16,10,0,0,0,0,5,14,16,11,0,0,8 +0,0,0,9,16,1,0,0,0,0,3,16,13,0,0,0,0,0,10,16,7,10,6,0,0,6,16,16,13,16,8,0,0,4,15,16,16,16,6,0,0,0,0,7,16,12,0,0,0,0,0,11,16,2,0,0,0,0,0,14,10,0,0,0,4 +0,1,12,3,0,0,0,0,0,10,16,15,1,0,0,0,0,8,3,13,5,0,0,0,0,1,0,9,9,0,0,0,0,0,0,13,6,0,0,0,0,0,2,15,2,0,0,0,0,0,13,16,14,14,12,0,0,0,8,12,12,10,11,0,2 +0,0,4,15,2,0,0,0,0,0,12,15,1,0,0,0,0,2,16,10,0,0,0,0,0,3,16,8,1,0,0,0,0,6,16,16,15,9,2,0,0,3,16,14,8,15,10,0,0,0,10,15,8,13,15,0,0,0,4,8,13,13,6,0,6 +0,0,0,2,15,3,0,0,0,0,0,9,13,0,2,0,0,0,7,15,1,5,12,0,0,2,16,10,4,12,7,0,0,6,13,14,16,16,5,0,0,0,0,0,8,13,0,0,0,0,0,0,15,5,0,0,0,0,0,3,12,0,0,0,4 +0,0,1,8,12,7,0,0,0,0,7,16,16,16,10,0,0,0,5,16,16,16,5,0,0,0,5,16,16,15,1,0,0,0,4,16,16,16,4,0,0,0,9,16,16,16,0,0,0,0,9,16,16,14,0,0,0,0,2,10,12,9,0,0,1 +0,0,3,13,16,16,9,0,0,0,3,9,11,16,6,0,0,0,0,0,8,14,1,0,0,3,12,13,16,15,4,0,0,4,13,14,16,12,3,0,0,0,0,9,12,0,0,0,0,0,0,15,8,0,0,0,0,0,2,16,3,0,0,0,7 +0,2,12,15,10,0,0,0,0,4,14,8,16,7,0,0,0,0,0,2,16,10,0,0,0,0,2,16,16,9,0,0,0,0,1,8,13,14,2,0,0,0,0,0,2,13,8,0,0,2,9,8,8,12,11,0,0,2,11,14,14,10,3,0,3 +0,0,6,13,3,0,0,0,0,0,15,16,15,5,0,0,0,0,14,4,6,13,0,0,0,0,0,0,4,15,0,0,0,0,0,0,8,12,0,0,0,0,1,6,15,8,0,0,0,0,16,16,16,16,12,0,0,0,5,8,9,10,15,3,2 +0,0,1,14,5,0,0,0,0,0,7,16,6,0,0,0,0,0,14,16,0,0,0,0,0,1,16,9,0,0,0,0,0,0,16,16,14,5,0,0,0,0,13,16,12,16,5,0,0,0,8,16,8,11,16,2,0,0,1,8,13,16,14,2,6 +0,0,1,10,12,11,5,0,0,0,8,16,16,16,3,0,0,0,13,16,16,11,0,0,0,1,16,16,16,12,0,0,0,2,16,16,16,10,0,0,0,2,16,16,16,6,0,0,0,0,7,16,16,8,0,0,0,0,2,11,12,4,0,0,1 +0,0,0,1,13,15,6,0,0,0,0,7,16,16,8,0,0,0,3,14,16,16,6,0,0,5,15,16,16,16,4,0,0,5,12,13,16,16,4,0,0,0,0,4,16,16,8,0,0,0,0,4,16,16,8,0,0,0,0,1,12,16,5,0,1 +0,0,0,8,13,0,0,0,0,0,1,15,12,0,0,0,0,0,9,15,3,0,0,0,0,0,15,11,0,0,0,0,0,3,16,14,12,4,0,0,0,2,16,15,13,16,6,0,0,0,9,15,5,13,13,0,0,0,0,7,14,14,6,0,6 +0,0,0,8,11,3,0,0,0,0,6,16,16,15,1,0,0,0,5,16,16,16,1,0,0,0,5,16,16,16,1,0,0,0,1,16,16,16,3,0,0,0,3,16,16,16,0,0,0,0,0,16,16,12,0,0,0,0,0,11,11,2,0,0,1 +0,0,1,11,12,12,10,0,0,0,3,10,8,16,10,0,0,0,0,0,3,16,3,0,0,0,3,10,15,14,2,0,0,0,14,16,16,16,6,0,0,0,1,14,9,0,0,0,0,0,3,16,3,0,0,0,0,0,6,12,0,0,0,0,7 +0,0,14,16,4,0,0,0,0,2,16,16,16,4,0,0,0,7,16,5,16,16,6,0,0,2,16,14,16,16,8,0,0,0,4,8,6,16,8,0,0,0,0,0,2,16,8,0,0,0,8,8,10,16,7,0,0,0,9,12,15,9,0,0,9 +0,1,12,13,7,0,0,0,0,2,15,13,16,7,0,0,0,0,0,6,16,8,0,0,0,0,7,16,16,3,0,0,0,0,4,12,16,12,0,0,0,0,0,0,4,16,8,0,0,1,8,10,12,16,10,0,0,2,12,14,12,9,1,0,3 +0,0,2,8,12,4,0,0,0,1,14,11,5,16,1,0,0,1,15,3,10,14,0,0,0,0,7,16,13,1,0,0,0,0,9,16,11,0,0,0,0,0,12,4,12,7,0,0,0,0,12,13,8,12,0,0,0,0,2,9,12,6,0,0,8 +0,0,2,15,4,0,0,0,0,0,10,16,2,0,0,0,0,0,12,11,0,0,0,0,0,0,14,12,0,0,0,0,0,2,16,16,16,9,0,0,0,3,16,15,12,16,6,0,0,1,14,16,6,14,10,0,0,0,3,14,16,13,3,0,6 +0,0,10,12,6,0,0,0,0,0,16,14,15,4,0,0,0,1,16,8,16,14,0,0,0,0,7,15,16,16,5,0,0,0,0,3,4,15,9,0,0,0,0,0,0,12,11,0,0,0,5,7,9,16,9,0,0,0,7,16,13,9,2,0,9 +0,0,5,14,10,3,0,0,0,1,16,14,16,11,0,0,0,4,16,6,12,16,7,0,0,1,13,15,15,16,8,0,0,0,0,2,7,16,7,0,0,0,0,0,4,16,4,0,0,0,2,4,7,16,3,0,0,0,6,16,15,8,0,0,9 +0,0,3,12,12,12,2,0,0,0,3,13,9,16,7,0,0,0,0,1,2,16,4,0,0,0,0,1,10,9,0,0,0,0,8,16,16,16,5,0,0,0,3,14,10,0,0,0,0,0,4,16,3,0,0,0,0,0,3,12,1,0,0,0,7 +0,0,0,3,12,2,0,0,0,0,0,12,12,0,0,0,0,0,6,16,4,8,13,0,0,1,15,8,1,15,8,0,0,6,16,14,13,16,1,0,0,2,4,10,16,10,0,0,0,0,0,5,16,2,0,0,0,0,0,3,10,0,0,0,4 +0,0,4,12,15,5,0,0,0,2,15,13,11,11,0,0,0,2,10,2,5,16,0,0,0,0,0,4,14,14,0,0,0,0,0,5,12,14,7,0,0,0,0,0,0,3,14,0,0,0,9,10,3,8,15,2,0,0,2,9,16,16,7,0,3 +0,0,7,16,15,7,0,0,0,0,16,10,8,15,0,0,0,0,5,2,9,15,0,0,0,0,5,16,16,8,0,0,0,0,2,5,11,14,3,0,0,0,0,0,0,8,12,0,0,0,10,10,7,13,12,0,0,0,6,12,16,11,4,0,3 +0,0,3,11,16,5,0,0,0,0,12,15,12,7,0,0,0,0,15,6,0,0,0,0,0,0,16,15,11,1,0,0,0,0,15,15,14,9,0,0,0,0,15,6,2,16,2,0,0,0,12,12,12,16,5,0,0,0,3,13,15,9,0,0,6 +0,0,7,12,5,3,0,0,0,3,15,8,13,16,3,0,0,4,14,1,0,9,0,0,0,0,9,15,13,9,0,0,0,0,3,14,15,6,0,0,0,0,14,5,3,14,2,0,0,0,16,5,5,15,3,0,0,0,6,11,11,6,0,0,8 +0,0,8,15,16,8,0,0,0,2,16,15,13,11,0,0,0,3,16,3,0,0,0,0,0,6,16,15,7,0,0,0,0,2,11,12,16,7,0,0,0,0,0,0,7,14,0,0,0,0,10,15,15,15,1,0,0,0,8,16,15,6,0,0,5 +0,0,8,14,13,13,10,0,0,4,16,13,13,16,9,0,0,4,16,2,6,16,1,0,0,0,0,1,14,5,0,0,0,0,0,6,15,0,0,0,0,0,1,15,6,0,0,0,0,0,11,16,3,0,0,0,0,0,11,11,1,0,0,0,7 +0,0,4,12,12,1,0,0,0,0,14,16,16,11,0,0,0,0,16,3,3,14,4,0,0,1,15,0,0,10,6,0,0,3,13,0,0,11,5,0,0,2,16,0,1,14,3,0,0,1,14,7,12,16,0,0,0,0,6,15,15,5,0,0,0 +0,4,16,14,14,15,5,0,0,8,16,16,16,16,9,0,0,11,14,0,11,15,1,0,0,5,5,3,16,8,0,0,0,0,0,11,14,1,0,0,0,0,4,16,5,0,0,0,0,0,14,14,0,0,0,0,0,3,16,10,0,0,0,0,7 +0,0,9,9,1,0,0,0,0,0,10,16,3,0,0,0,0,0,13,16,7,0,0,0,0,0,6,16,13,0,0,0,0,0,0,14,16,0,0,0,0,0,0,13,16,13,4,0,0,0,10,16,16,16,15,2,0,0,8,16,13,8,5,0,1 +0,0,5,15,10,3,0,0,0,0,14,16,12,14,2,0,0,3,16,5,0,13,4,0,0,5,14,0,0,13,7,0,0,6,12,0,1,16,3,0,0,3,16,2,4,15,2,0,0,0,14,11,15,10,0,0,0,0,6,15,12,3,0,0,0 +0,0,4,15,11,1,0,0,0,2,13,16,16,0,0,0,0,2,12,16,12,0,0,0,0,0,0,14,14,0,0,0,0,0,0,15,14,0,0,0,0,0,0,16,13,0,0,0,0,0,3,16,15,0,0,0,0,0,2,15,16,1,0,0,1 +0,0,5,12,12,1,0,0,0,11,16,12,14,9,0,0,0,14,7,0,10,11,0,0,0,0,3,15,16,8,0,0,0,0,2,12,12,14,3,0,0,0,0,0,0,10,14,0,0,0,9,10,8,13,16,0,0,0,6,12,16,12,4,0,3 +0,0,8,15,13,8,0,0,0,1,16,15,16,15,0,0,0,5,16,1,5,16,2,0,0,2,16,6,15,16,2,0,0,0,9,16,14,16,5,0,0,0,0,0,4,16,3,0,0,0,9,12,15,14,0,0,0,0,7,14,12,3,0,0,9 +0,0,8,12,8,14,3,0,0,2,15,13,11,16,4,0,0,3,16,10,11,9,0,0,0,0,4,16,16,4,0,0,0,0,2,15,16,8,0,0,0,3,15,7,12,10,0,0,0,4,16,4,13,8,0,0,0,1,9,16,15,3,0,0,8 +0,1,15,16,16,16,9,0,0,7,16,13,11,16,9,0,0,9,16,4,10,15,1,0,0,8,9,3,15,8,0,0,0,0,0,10,15,0,0,0,0,0,1,16,9,0,0,0,0,0,10,16,2,0,0,0,0,0,16,13,0,0,0,0,7 +0,0,8,16,7,0,0,0,0,1,15,11,15,0,0,0,0,2,14,0,11,2,0,0,0,0,1,0,13,4,0,0,0,0,0,6,13,0,0,0,0,0,0,13,8,0,0,0,0,0,13,16,10,10,11,0,0,0,10,15,12,12,12,1,2 +0,0,7,16,10,0,0,0,0,0,14,11,13,10,0,0,0,1,16,4,6,13,0,0,0,0,4,0,8,10,0,0,0,0,0,2,15,4,0,0,0,0,0,9,13,0,0,0,0,0,9,16,16,16,9,0,0,0,11,14,12,12,15,2,2 +0,0,12,16,9,5,0,0,0,0,16,9,13,15,2,0,0,4,16,0,4,16,4,0,0,0,14,13,15,16,8,0,0,0,5,11,8,16,8,0,0,0,0,0,0,14,7,0,0,0,12,10,12,16,2,0,0,0,10,13,12,4,0,0,9 +0,0,4,10,12,8,0,0,0,0,9,16,12,11,0,0,0,0,12,9,3,0,0,0,0,0,13,16,16,6,0,0,0,0,9,6,8,13,1,0,0,0,0,0,4,16,3,0,0,0,13,12,14,13,1,0,0,0,7,14,10,0,0,0,5 +0,0,4,16,11,4,0,0,0,1,14,13,13,16,0,0,0,2,15,2,0,12,4,0,0,4,13,0,0,13,1,0,0,3,13,0,0,15,1,0,0,1,16,0,4,15,1,0,0,0,14,10,11,13,0,0,0,0,5,15,15,4,0,0,0 +0,0,6,9,14,10,1,0,0,4,16,15,5,13,4,0,0,7,13,4,6,15,2,0,0,0,9,16,16,9,0,0,0,0,11,13,15,10,0,0,0,0,15,4,5,15,0,0,0,1,16,9,10,14,0,0,0,0,7,13,14,5,0,0,8 +0,0,8,13,7,5,0,0,0,2,16,14,14,15,2,0,0,8,10,4,3,8,4,0,0,8,8,0,0,4,8,0,0,8,8,0,0,8,8,0,0,4,13,0,0,13,7,0,0,0,14,10,10,15,3,0,0,0,6,12,14,5,0,0,0 +0,0,0,10,11,0,0,0,0,0,0,15,11,9,0,0,0,0,5,15,10,16,2,0,0,0,13,9,12,13,0,0,0,3,16,7,13,14,5,0,0,12,16,16,16,16,8,0,0,5,8,13,16,4,0,0,0,0,0,11,15,0,0,0,4 +0,0,4,14,10,2,0,0,0,0,12,13,14,13,0,0,0,0,11,5,3,13,0,0,0,0,7,12,11,16,1,0,0,0,1,10,9,14,3,0,0,0,0,0,0,13,5,0,0,0,4,8,8,16,5,0,0,0,7,16,16,11,0,0,9 +0,0,6,14,9,0,0,0,0,3,16,15,16,6,0,0,0,6,14,0,8,10,0,0,0,0,2,0,11,10,0,0,0,0,0,5,16,4,0,0,0,0,2,14,12,1,0,0,0,0,10,16,16,16,16,0,0,0,10,13,12,8,10,1,2 +0,0,9,12,10,3,0,0,0,0,14,16,13,13,0,0,0,4,16,2,1,16,3,0,0,4,16,0,0,12,8,0,0,6,16,0,0,15,8,0,0,5,15,0,1,16,5,0,0,2,16,11,14,13,0,0,0,0,8,16,13,6,0,0,0 +0,0,9,16,16,9,0,0,0,1,16,12,16,16,0,0,0,0,2,7,16,9,0,0,0,0,7,16,15,8,1,0,0,0,5,9,9,15,8,0,0,0,0,0,0,13,12,0,0,0,6,8,12,16,10,0,0,0,9,15,12,4,0,0,3 +0,0,6,11,8,12,1,0,0,2,16,12,16,14,8,0,0,3,15,4,1,13,4,0,0,0,9,15,14,12,0,0,0,0,6,16,16,9,0,0,0,2,15,8,6,12,0,0,0,4,16,10,12,15,0,0,0,0,5,12,12,7,0,0,8 +0,0,10,8,10,12,6,0,0,2,16,14,12,15,11,0,0,3,9,2,4,14,2,0,0,0,0,1,14,4,0,0,0,0,0,5,14,0,0,0,0,0,1,13,6,0,0,0,0,1,12,16,1,0,0,0,0,0,14,10,0,0,0,0,7 +0,0,0,7,11,0,0,0,0,0,0,11,5,2,0,0,0,0,3,14,3,14,0,0,0,0,8,6,7,7,0,0,0,1,15,4,12,9,4,0,0,5,16,16,16,15,8,0,0,1,1,6,10,0,0,0,0,0,0,10,9,0,0,0,4 +0,0,11,12,7,0,0,0,0,6,14,8,15,16,3,0,0,3,15,9,4,16,3,0,0,0,3,14,16,12,0,0,0,0,3,12,15,15,2,0,0,0,15,10,0,13,8,0,0,1,16,5,5,12,11,0,0,0,14,16,16,9,2,0,8 +0,0,2,11,16,7,0,0,0,0,8,16,16,2,0,0,0,2,16,16,16,0,0,0,0,0,5,16,16,0,0,0,0,0,2,16,16,1,0,0,0,0,8,16,16,3,0,0,0,0,9,16,16,13,0,0,0,0,2,12,13,9,0,0,1 +0,0,5,15,13,2,0,0,0,0,10,13,12,11,0,0,0,0,3,2,4,15,0,0,0,0,0,9,16,10,0,0,0,0,0,3,13,16,6,0,0,0,0,0,0,7,14,0,0,0,5,8,8,10,16,1,0,0,4,9,14,13,7,0,3 +0,0,8,16,16,15,1,0,0,0,16,14,14,11,0,0,0,4,16,16,13,4,0,0,0,3,14,12,14,15,1,0,0,0,0,0,0,15,6,0,0,0,0,0,1,14,7,0,0,0,15,12,14,13,0,0,0,0,11,16,13,2,0,0,5 +0,0,16,14,12,7,0,0,0,0,16,8,12,10,0,0,0,4,16,4,0,0,0,0,0,6,16,16,13,3,0,0,0,0,1,5,10,16,3,0,0,0,0,0,0,14,5,0,0,2,14,8,8,16,3,0,0,1,10,16,15,8,0,0,5 +0,0,6,13,16,3,0,0,0,1,15,16,16,9,0,0,0,0,3,4,16,7,0,0,0,0,0,5,16,6,0,0,0,0,9,16,16,4,1,0,0,0,14,16,16,16,6,0,0,0,9,16,9,5,0,0,0,0,9,13,2,0,0,0,7 +0,0,12,5,0,0,0,0,0,0,16,2,0,0,0,0,0,3,12,0,0,0,0,0,0,4,12,0,0,0,0,0,0,7,14,16,14,3,0,0,0,3,16,8,9,16,4,0,0,2,16,3,4,16,4,0,0,0,11,12,11,4,0,0,6 +0,1,7,13,7,0,0,0,0,8,16,15,16,5,0,0,0,6,16,8,8,13,1,0,0,4,12,0,0,13,7,0,0,8,12,0,0,8,8,0,0,7,13,0,0,14,7,0,0,4,16,8,10,16,5,0,0,1,15,16,14,7,0,0,0 +0,0,9,8,1,0,0,0,0,5,16,16,7,0,0,0,0,8,10,8,8,0,0,0,0,0,2,4,12,0,0,0,0,0,0,6,10,0,0,0,0,0,0,7,9,0,0,0,0,0,6,16,15,11,5,0,0,0,9,14,12,14,10,0,2 +0,0,11,11,2,0,0,0,0,0,12,14,15,2,0,0,0,0,10,8,6,16,0,0,0,0,7,13,7,16,2,0,0,0,0,11,12,14,5,0,0,0,0,0,0,8,10,0,0,0,7,10,10,14,15,0,0,0,7,12,16,16,12,1,9 +0,0,0,14,6,0,0,0,0,0,9,11,2,0,0,0,0,1,16,6,0,0,0,0,0,4,14,0,0,0,0,0,0,6,15,11,6,2,0,0,0,2,16,13,8,15,3,0,0,0,10,13,3,5,14,0,0,0,1,10,13,16,10,0,6 +0,0,8,16,16,8,0,0,0,0,8,11,13,16,0,0,0,0,0,0,13,11,0,0,0,0,0,1,16,7,0,0,0,0,10,16,16,13,6,0,0,0,13,16,13,8,1,0,0,0,6,16,3,0,0,0,0,0,11,15,0,0,0,0,7 +0,0,8,15,16,14,2,0,0,1,16,14,12,16,7,0,0,0,8,1,3,16,5,0,0,0,0,8,16,11,0,0,0,0,0,6,14,13,0,0,0,0,0,0,3,16,6,0,0,0,10,8,12,16,2,0,0,0,9,16,15,5,0,0,3 +0,0,3,13,0,0,0,0,0,0,9,13,0,0,0,0,0,0,12,6,0,0,0,0,0,0,15,7,0,0,0,0,0,3,16,14,8,2,0,0,0,4,16,12,12,16,4,0,0,2,15,12,8,15,13,0,0,0,3,11,15,12,8,0,6 +0,0,4,12,12,11,1,0,0,1,16,10,1,14,0,0,0,1,14,7,0,14,0,0,0,0,4,16,13,10,0,0,0,0,2,16,16,3,0,0,0,1,13,11,15,7,0,0,0,2,16,5,14,8,0,0,0,0,6,9,12,3,0,0,8 +0,0,6,16,7,0,0,0,0,0,16,11,14,6,0,0,0,2,15,0,3,16,1,0,0,7,8,0,0,13,2,0,0,5,8,0,0,11,4,0,0,7,7,0,0,12,4,0,0,5,16,7,7,16,0,0,0,0,7,16,12,4,0,0,0 +0,0,8,13,12,2,0,0,0,3,16,13,12,14,0,0,0,0,6,1,9,13,0,0,0,0,0,5,16,9,0,0,0,0,0,1,12,16,2,0,0,0,0,0,0,12,9,0,0,1,10,6,7,15,9,0,0,0,9,12,15,11,2,0,3 +0,2,11,14,5,0,0,0,0,5,15,12,16,2,0,0,0,4,12,0,12,4,0,0,0,0,1,0,12,4,0,0,0,0,0,1,14,1,0,0,0,0,0,9,7,0,0,0,0,1,9,15,10,8,4,0,0,2,15,15,12,12,7,0,2 +0,0,8,15,12,1,0,0,0,4,16,14,12,11,0,0,0,4,16,5,1,14,4,0,0,4,14,1,0,12,4,0,0,6,12,0,0,10,5,0,0,8,13,0,1,13,4,0,0,4,16,16,16,14,1,0,0,0,8,13,11,3,0,0,0 +0,0,4,13,14,6,0,0,0,0,8,15,6,16,4,0,0,0,8,15,3,16,1,0,0,0,0,13,16,15,1,0,0,0,7,16,16,4,0,0,0,4,16,9,12,10,0,0,0,2,14,9,11,16,0,0,0,0,5,12,15,7,0,0,8 +0,1,6,16,13,6,1,0,0,1,12,16,16,16,1,0,0,0,10,16,16,12,0,0,0,2,14,16,16,12,0,0,0,2,9,16,16,12,0,0,0,0,1,14,16,14,0,0,0,0,6,16,16,14,2,0,0,0,4,12,13,8,2,0,1 +0,0,8,14,13,3,0,0,0,0,16,16,16,15,0,0,0,0,13,12,4,16,2,0,0,0,2,15,14,15,2,0,0,0,4,15,16,7,0,0,0,0,15,14,8,15,4,0,0,4,16,12,6,16,7,0,0,0,6,13,12,8,0,0,8 +0,1,10,16,9,0,0,0,0,9,15,13,16,0,0,0,0,4,2,0,14,2,0,0,0,0,0,0,12,4,0,0,0,0,0,1,14,1,0,0,0,0,0,9,7,0,0,0,0,0,14,16,16,16,11,0,0,0,8,10,12,14,10,0,2 +0,0,1,14,8,0,0,0,0,0,10,15,3,0,0,0,0,0,13,12,0,0,0,0,0,0,16,8,0,0,0,0,0,3,16,14,11,2,0,0,0,4,16,14,12,15,1,0,0,0,12,15,8,16,11,0,0,0,1,10,13,12,5,0,6 +0,0,0,4,16,2,0,0,0,0,0,14,10,2,8,0,0,0,10,15,1,8,12,0,0,4,16,5,1,15,8,0,0,13,16,14,16,16,2,0,1,12,12,12,16,10,0,0,0,0,0,5,16,8,0,0,0,0,0,8,15,4,0,0,4 +0,0,8,10,0,0,0,0,0,0,14,16,5,0,0,0,0,0,15,11,11,0,0,0,0,0,10,6,12,0,0,0,0,0,1,4,14,0,0,0,0,0,0,6,10,0,0,0,0,0,5,15,14,9,9,2,0,0,10,16,15,15,12,1,2 +0,0,6,16,16,13,0,0,0,0,13,12,13,16,3,0,0,0,0,0,8,14,0,0,0,0,1,7,14,10,0,0,0,0,9,16,16,16,7,0,0,0,1,15,12,9,3,0,0,0,6,16,4,0,0,0,0,0,9,13,1,0,0,0,7 +0,0,3,14,5,0,0,0,0,0,12,16,3,0,0,0,0,2,12,8,0,0,0,0,0,3,3,0,0,0,0,0,0,7,4,0,6,0,0,0,0,4,16,15,13,15,3,0,0,2,14,14,4,14,11,0,0,0,3,13,16,16,12,0,6 +0,0,0,2,16,4,0,0,0,0,0,9,13,0,0,0,0,0,2,15,5,9,7,0,0,0,12,10,1,16,5,0,0,7,15,8,10,16,2,0,3,16,16,16,16,13,0,0,1,8,7,5,16,14,0,0,0,0,0,1,15,6,0,0,4 +0,0,9,14,15,4,0,0,0,7,16,16,16,10,0,0,0,5,10,4,16,4,0,0,0,0,0,16,16,7,0,0,0,0,0,3,9,16,3,0,0,0,2,0,1,15,7,0,0,0,15,13,13,16,4,0,0,0,9,15,15,8,0,0,3 +0,0,0,10,15,0,0,0,0,0,5,15,5,0,0,0,0,0,13,8,0,0,0,0,0,0,16,4,0,0,0,0,0,5,16,12,9,3,0,0,0,2,16,15,12,15,8,0,0,0,12,14,5,12,11,0,0,0,1,8,14,15,5,0,6 +0,0,7,11,14,4,0,0,0,12,16,12,15,8,0,0,0,6,9,0,12,7,0,0,0,0,1,9,16,2,0,0,0,0,8,16,16,15,1,0,0,0,0,0,2,15,9,0,0,2,10,4,2,13,10,0,0,0,7,12,15,12,1,0,3 +0,0,0,4,15,7,0,0,0,0,0,6,15,1,0,0,0,0,0,12,8,4,15,0,0,0,4,14,1,12,12,0,0,8,15,13,8,16,6,0,6,16,16,16,16,14,0,0,3,7,6,9,16,9,0,0,0,0,0,5,13,2,0,0,4 +0,0,9,14,4,0,0,0,0,4,16,16,16,7,0,0,0,5,16,10,10,14,1,0,0,8,14,0,1,13,4,0,0,8,12,0,0,10,8,0,0,8,12,0,0,11,7,0,0,5,15,9,5,13,4,0,0,0,8,16,16,13,1,0,0 +0,0,3,14,16,14,3,0,0,3,15,10,14,16,6,0,0,7,13,0,10,16,7,0,0,8,12,0,1,16,4,0,0,7,12,0,0,13,4,0,0,3,14,1,0,13,4,0,0,0,11,11,9,15,2,0,0,0,2,11,12,4,0,0,0 +0,1,10,14,13,0,0,0,0,6,12,8,16,7,0,0,0,0,0,0,14,8,0,0,0,0,8,8,16,6,0,0,0,0,9,16,16,16,6,0,0,0,2,15,5,4,2,0,0,0,11,14,0,0,0,0,0,0,12,5,0,0,0,0,7 +0,0,9,16,16,10,0,0,0,1,16,7,8,16,6,0,0,4,16,7,9,16,6,0,0,0,9,12,12,16,4,0,0,0,0,0,0,16,6,0,0,0,0,0,0,14,7,0,0,0,4,6,4,16,4,0,0,0,9,13,12,10,0,0,9 +0,0,6,8,0,0,0,0,0,0,10,15,0,0,0,0,0,0,10,15,0,0,0,0,0,0,12,14,0,0,0,0,0,2,16,16,16,15,4,0,0,0,13,13,9,16,12,0,0,0,13,12,12,16,7,0,0,0,8,16,15,8,0,0,6 +0,2,11,15,8,6,0,0,0,10,14,8,15,16,0,0,0,9,14,0,10,16,2,0,0,2,16,10,16,16,3,0,0,0,4,8,8,16,4,0,0,0,0,0,0,15,7,0,0,2,11,12,15,16,8,0,0,2,12,9,8,4,0,0,9 +0,0,8,14,7,0,0,0,0,3,16,13,16,10,0,0,0,7,14,1,7,14,1,0,0,7,11,0,0,13,4,0,0,4,8,0,0,8,8,0,0,4,12,0,0,9,8,0,0,3,15,5,6,15,6,0,0,0,8,16,16,9,0,0,0 +0,1,9,16,15,1,0,0,0,8,14,8,14,8,0,0,0,8,5,3,15,5,0,0,0,0,0,9,16,6,0,0,0,0,0,0,9,16,3,0,0,0,0,0,0,10,10,0,0,0,5,7,6,16,7,0,0,0,9,16,14,9,1,0,3 +0,1,8,10,3,0,0,0,0,4,16,16,16,11,1,0,0,3,16,9,8,11,4,0,0,0,10,7,3,10,2,0,0,0,3,16,13,5,0,0,0,0,13,15,8,0,0,0,0,4,15,5,13,0,0,0,0,1,7,12,11,0,0,0,8 +0,0,9,16,12,4,0,0,0,3,16,16,16,16,3,0,0,5,16,1,1,13,8,0,0,1,14,10,10,16,6,0,0,0,6,16,16,8,0,0,0,1,14,16,12,0,0,0,0,4,16,10,16,4,0,0,0,1,11,16,13,1,0,0,8 +0,0,1,14,15,3,0,0,0,2,15,16,16,13,0,0,0,6,15,12,10,16,3,0,0,8,15,0,0,10,8,0,0,6,12,0,0,11,8,0,0,1,15,6,2,14,8,0,0,0,10,16,14,15,3,0,0,0,2,9,16,7,0,0,0 +0,0,2,15,12,0,0,0,0,0,11,8,15,4,0,0,0,0,13,4,12,4,0,0,0,0,1,1,15,0,0,0,0,0,0,10,9,0,0,0,0,0,8,15,1,0,0,0,0,6,16,13,8,8,2,0,0,0,4,10,12,13,6,0,2 +0,0,6,14,14,4,0,0,0,0,16,15,15,16,2,0,0,5,12,1,0,15,5,0,0,8,12,0,0,8,8,0,0,5,12,0,0,8,8,0,0,4,13,0,0,10,7,0,0,1,16,6,7,16,3,0,0,0,5,15,16,11,0,0,0 +0,0,5,14,16,7,0,0,0,3,14,2,5,12,0,0,0,5,9,0,7,11,0,0,0,0,0,5,15,5,0,0,0,0,0,7,12,14,1,0,0,0,0,0,0,11,7,0,0,0,3,8,3,12,6,0,0,0,7,16,13,8,0,0,3 +0,0,0,1,13,12,1,0,0,0,0,6,16,14,1,0,0,8,12,16,16,11,0,0,0,4,8,9,16,15,0,0,0,0,0,0,16,15,0,0,0,0,0,0,13,16,3,0,0,0,0,0,12,16,4,0,0,0,0,0,11,16,5,0,1 +0,0,6,15,5,0,0,0,0,0,15,16,13,3,0,0,0,1,16,11,15,15,3,0,0,8,9,3,5,16,4,0,0,8,9,0,0,10,8,0,0,8,10,0,0,12,5,0,0,3,15,12,13,16,1,0,0,0,8,16,13,6,0,0,0 +0,1,10,16,13,4,0,0,0,7,16,6,12,12,1,0,0,10,13,0,9,16,4,0,0,4,16,9,14,16,5,0,0,0,5,8,11,16,4,0,0,0,0,0,0,16,9,0,0,0,9,8,1,14,9,0,0,0,12,16,16,15,7,0,9 +0,1,7,15,13,2,0,0,0,7,14,7,13,8,0,0,0,0,0,1,13,7,0,0,0,0,0,7,16,5,0,0,0,0,0,1,9,12,0,0,0,0,0,0,2,14,6,0,0,0,5,5,2,14,8,0,0,0,11,16,12,10,1,0,3 +0,0,0,14,8,0,0,0,0,0,0,16,8,0,0,0,0,0,6,16,3,0,0,0,0,0,7,15,0,0,0,0,0,0,15,16,15,8,1,0,0,0,16,12,4,11,12,0,0,0,10,13,0,2,16,4,0,0,1,9,15,16,13,0,6 +0,2,11,14,7,1,0,0,0,10,15,13,16,4,0,0,0,11,12,5,16,3,0,0,0,1,2,15,14,0,0,0,0,0,5,12,15,11,1,0,0,0,0,0,2,14,10,0,0,0,8,12,12,16,11,0,0,0,9,15,11,8,1,0,3 +0,0,15,16,16,7,0,0,0,7,16,10,15,16,3,0,0,10,16,2,14,14,0,0,0,1,13,16,16,16,3,0,0,0,0,0,5,16,4,0,0,0,0,0,1,16,10,0,0,0,7,8,8,16,10,0,0,0,9,12,13,9,1,0,9 +0,2,9,12,16,14,1,0,0,5,14,6,6,7,0,0,0,4,14,6,8,3,0,0,0,4,16,16,15,16,2,0,0,1,6,2,0,13,8,0,0,0,0,0,0,12,7,0,0,1,8,8,9,15,2,0,0,2,12,13,9,3,0,0,5 +0,0,7,12,11,9,0,0,0,7,16,15,11,13,0,0,0,0,12,7,0,0,0,0,0,0,12,10,6,0,0,0,0,0,8,12,13,14,0,0,0,0,0,0,0,8,8,0,0,0,10,6,4,12,9,0,0,0,10,13,12,10,2,0,5 +0,0,7,10,0,0,0,0,0,0,12,13,0,0,0,0,0,2,16,8,0,0,0,0,0,0,16,5,0,0,0,0,0,4,16,10,6,0,0,0,0,4,16,16,16,14,5,0,0,3,16,10,11,16,11,0,0,0,7,14,12,9,2,0,6 +0,2,10,14,13,6,0,0,0,4,16,8,13,15,3,0,0,2,16,4,9,16,4,0,0,0,12,10,12,16,4,0,0,0,2,9,12,16,0,0,0,0,0,0,4,16,1,0,0,0,2,4,11,16,4,0,0,3,16,13,11,4,0,0,9 +0,0,0,1,16,8,0,0,0,0,0,9,16,2,0,0,0,0,2,15,8,0,0,0,0,0,10,14,0,0,0,0,0,6,16,5,2,13,6,0,0,14,16,15,13,16,3,0,0,4,8,8,15,11,0,0,0,0,0,2,16,7,0,0,4 +0,0,0,4,12,0,0,0,0,0,0,10,14,0,0,0,0,0,1,15,5,0,0,0,0,0,10,13,0,12,5,0,0,3,16,2,6,16,3,0,0,11,16,10,13,16,1,0,0,1,7,12,16,11,0,0,0,0,0,5,16,5,0,0,4 +0,0,0,7,13,3,0,0,0,0,1,15,16,15,0,0,0,9,16,16,16,16,2,0,0,4,8,12,16,10,0,0,0,0,0,11,16,13,0,0,0,0,0,7,16,15,0,0,0,0,0,6,16,16,2,0,0,0,0,6,16,16,7,0,1 +0,0,8,15,7,0,0,0,0,0,14,14,16,1,0,0,0,0,8,2,13,5,0,0,0,0,0,0,14,4,0,0,0,0,0,1,15,3,0,0,0,0,0,4,15,0,0,0,0,0,4,15,15,12,10,1,0,0,7,15,14,12,16,3,2 +0,0,4,11,0,0,0,0,0,0,11,9,0,0,0,0,0,0,16,4,0,0,0,0,0,1,15,2,4,2,0,0,0,5,14,13,16,14,1,0,0,4,16,6,0,10,10,0,0,1,15,3,1,13,8,0,0,0,3,12,14,10,1,0,6 +0,0,9,15,15,4,0,0,0,4,15,7,14,12,0,0,0,7,12,2,14,13,0,0,0,2,14,16,14,16,1,0,0,0,1,3,3,16,4,0,0,0,1,0,0,14,4,0,0,0,14,11,6,14,5,0,0,0,8,12,14,11,2,0,9 +0,0,0,10,11,0,0,0,0,0,0,15,8,0,0,0,0,0,3,16,2,0,0,0,0,0,6,14,0,0,0,0,0,0,13,16,16,13,3,0,0,2,14,16,4,11,10,0,0,0,1,14,6,12,12,0,0,0,0,9,16,14,4,0,6 +0,0,4,10,0,0,0,0,0,0,9,15,2,0,0,0,0,0,12,10,0,0,0,0,0,0,15,8,3,0,0,0,0,1,16,16,16,14,2,0,0,0,16,10,5,15,8,0,0,0,11,13,11,16,4,0,0,0,3,11,13,5,0,0,6 +0,0,8,15,16,7,0,0,0,5,16,11,1,12,0,0,0,0,14,4,12,7,0,0,0,0,3,14,12,1,0,0,0,0,2,15,7,0,0,0,0,0,11,10,12,0,0,0,0,0,13,3,16,0,0,0,0,0,7,14,12,0,0,0,8 +0,1,9,13,13,5,0,0,0,10,14,6,9,16,3,0,0,7,10,0,0,16,6,0,0,0,0,0,10,14,1,0,0,0,0,9,15,3,0,0,0,0,7,16,2,0,0,0,0,0,14,9,0,3,1,0,0,0,14,16,15,12,2,0,2 +0,1,10,16,16,15,6,0,0,5,15,8,6,16,11,0,0,0,2,5,15,13,3,0,0,0,0,16,14,0,0,0,0,0,0,6,16,11,1,0,0,0,0,0,6,16,8,0,0,0,3,5,12,16,5,0,0,0,16,16,13,7,0,0,3 +0,0,3,14,16,13,6,0,0,0,13,7,1,13,16,1,0,0,12,5,0,10,12,0,0,0,8,14,12,14,2,0,0,0,0,4,14,6,0,0,0,0,0,5,13,0,0,0,0,0,0,13,6,0,0,0,0,0,2,11,0,0,0,0,9 +0,0,6,13,11,1,0,0,0,0,12,14,11,11,0,0,0,0,15,2,0,14,2,0,0,3,15,0,0,8,6,0,0,5,13,0,0,7,7,0,0,3,14,0,0,12,7,0,0,0,12,7,8,14,2,0,0,0,4,12,12,5,0,0,0 +0,1,8,16,16,12,0,0,0,7,15,9,14,15,0,0,0,3,3,0,12,14,0,0,0,0,6,10,15,14,1,0,0,8,16,16,16,15,8,0,0,5,5,10,14,1,0,0,0,0,1,15,8,0,0,0,0,0,13,11,1,0,0,0,7 +0,0,4,9,16,8,0,0,0,3,16,16,16,13,0,0,0,3,16,8,16,8,0,0,0,0,11,16,12,0,0,0,0,0,10,16,4,0,0,0,0,0,14,16,10,0,0,0,0,0,14,14,13,0,0,0,0,0,6,16,9,0,0,0,8 +0,0,13,16,13,6,0,0,0,0,15,14,15,15,3,0,0,0,10,11,7,13,1,0,0,0,0,13,16,7,0,0,0,0,10,16,13,0,0,0,0,0,15,4,14,3,0,0,0,3,12,4,16,1,0,0,0,1,12,15,6,0,0,0,8 +0,3,15,15,8,9,0,0,0,7,14,11,16,16,6,0,0,2,16,2,7,15,3,0,0,0,8,14,16,5,0,0,0,0,7,16,6,0,0,0,0,1,15,14,8,0,0,0,0,6,14,14,7,0,0,0,0,3,16,15,2,0,0,0,8 +0,0,7,12,0,0,0,0,0,0,15,15,10,13,0,0,0,5,14,3,3,14,2,0,0,4,10,0,0,10,5,0,0,3,9,0,0,15,3,0,0,2,9,0,3,16,1,0,0,0,14,4,13,8,0,0,0,0,5,16,12,1,0,0,0 +0,0,4,13,16,16,9,0,0,2,12,6,1,12,12,0,0,1,3,2,15,10,0,0,0,0,0,11,9,0,0,0,0,0,0,4,14,2,0,0,0,0,0,0,13,10,0,0,0,0,0,0,10,11,0,0,0,6,9,13,12,1,0,0,3 +0,0,9,10,15,14,0,0,0,2,16,16,16,15,0,0,0,2,16,4,15,5,0,0,0,0,12,16,12,0,0,0,0,0,13,16,4,0,0,0,0,2,16,14,12,0,0,0,0,1,16,11,16,4,0,0,0,0,9,16,15,3,0,0,8 +0,0,0,3,15,5,0,0,0,0,3,14,13,1,0,0,0,1,14,13,1,1,2,0,0,8,16,3,0,12,12,0,0,12,14,8,8,16,7,0,0,5,15,16,16,16,1,0,0,0,0,0,15,11,0,0,0,0,0,2,16,7,0,0,4 +0,5,14,15,12,10,1,0,0,13,16,16,16,16,7,0,0,12,16,3,0,1,1,0,0,8,16,4,0,0,0,0,0,0,15,13,0,0,0,0,0,0,5,16,2,0,0,0,0,0,8,16,5,0,0,0,0,6,16,13,0,0,0,0,5 +0,3,13,15,16,9,1,0,0,6,12,2,3,16,6,0,0,0,0,2,13,11,0,0,0,0,0,9,13,0,0,0,0,0,0,8,12,0,0,0,0,0,0,3,15,5,0,0,0,0,4,3,14,11,0,0,0,3,15,15,9,1,0,0,3 +0,0,3,12,14,8,5,0,0,2,14,8,10,16,14,0,0,4,12,0,4,16,11,0,0,2,16,10,13,16,4,0,0,0,5,8,9,16,0,0,0,0,0,0,11,9,0,0,0,0,0,5,15,2,0,0,0,0,2,13,6,0,0,0,9 +0,0,0,4,14,14,0,0,0,0,3,15,13,3,0,0,0,0,15,15,1,0,0,0,0,7,16,5,0,9,6,0,0,11,16,8,12,16,9,0,0,3,11,12,16,14,0,0,0,0,0,6,16,9,0,0,0,0,0,7,16,4,0,0,4 +0,3,14,16,16,13,0,0,0,8,15,8,13,16,0,0,0,0,1,2,16,10,0,0,0,0,0,12,15,1,0,0,0,0,9,15,3,0,0,0,0,2,16,8,0,0,0,0,0,6,16,4,4,7,7,0,0,3,15,16,16,13,7,0,2 +0,0,5,14,16,16,12,0,0,5,16,13,8,13,16,0,0,3,7,0,3,14,10,0,0,0,0,1,13,14,0,0,0,0,0,9,15,3,0,0,0,0,4,16,7,0,0,0,0,0,12,13,0,0,0,0,0,0,6,16,16,10,0,0,2 +0,0,2,14,15,2,0,0,0,0,13,15,14,8,0,0,0,0,16,6,11,10,0,0,0,1,9,8,15,9,0,0,0,10,16,16,16,16,9,0,0,1,4,8,16,5,5,0,0,0,0,14,13,0,0,0,0,0,2,16,6,0,0,0,7 +0,0,9,14,16,12,1,0,0,3,14,5,4,12,8,0,0,1,2,0,0,13,6,0,0,0,4,8,10,16,4,0,0,5,14,11,16,8,1,0,0,3,2,9,11,0,0,0,0,0,3,15,2,0,0,0,0,0,9,8,0,0,0,0,7 +0,0,9,16,14,12,3,0,0,3,13,4,11,8,11,0,0,1,15,5,0,12,4,0,0,0,3,14,13,7,0,0,0,0,0,10,13,0,0,0,0,0,5,12,15,0,0,0,0,0,12,2,12,0,0,0,0,0,10,16,6,0,0,0,8 +0,0,0,7,16,2,0,0,0,0,4,16,9,0,0,0,0,0,13,12,0,0,1,0,0,6,16,2,0,10,11,0,0,10,16,6,13,16,8,0,0,5,16,16,16,14,2,0,0,0,0,5,16,6,0,0,0,0,0,8,16,2,0,0,4 +0,0,1,9,8,1,0,0,0,0,8,16,14,9,0,0,0,0,14,14,1,16,0,0,0,1,16,3,0,11,4,0,0,0,16,0,0,15,4,0,0,0,14,1,5,16,0,0,0,0,11,7,14,7,0,0,0,0,4,15,13,1,0,0,0 +0,6,15,16,16,15,6,0,0,9,16,6,5,15,12,0,0,0,1,1,13,15,3,0,0,0,0,5,16,5,0,0,0,0,0,0,14,10,0,0,0,0,0,0,12,14,0,0,0,3,7,5,14,13,0,0,0,9,16,16,13,2,0,0,3 +0,0,0,11,12,0,0,0,0,0,6,16,6,0,0,0,0,3,16,6,0,5,3,0,0,10,16,0,2,15,10,0,0,6,16,14,14,14,1,0,0,0,2,4,16,10,0,0,0,0,0,8,16,4,0,0,0,0,0,10,14,0,0,0,4 +0,0,10,15,16,16,9,0,0,1,10,2,0,10,14,0,0,0,0,0,7,15,3,0,0,0,0,13,11,1,0,0,0,0,0,13,11,0,0,0,0,0,0,5,15,5,0,0,0,0,5,4,12,11,0,0,0,0,9,16,10,2,0,0,3 +0,0,11,16,12,5,0,0,0,4,16,11,16,16,3,0,0,4,16,11,16,14,0,0,0,0,10,16,16,6,0,0,0,0,0,15,14,0,0,0,0,0,7,16,5,0,0,0,0,0,15,13,0,0,0,0,0,0,12,11,0,0,0,0,9 +0,0,2,16,13,0,0,0,0,0,4,16,15,0,0,0,0,0,2,16,16,2,0,0,0,0,1,16,16,2,0,0,0,0,0,15,16,4,0,0,0,0,0,14,16,3,0,0,0,0,0,16,16,3,0,0,0,0,0,14,16,6,0,0,1 +0,1,9,15,16,13,0,0,0,5,11,4,2,13,3,0,0,1,3,0,1,15,3,0,0,0,0,0,11,11,0,0,0,0,0,11,11,0,0,0,0,0,7,14,1,0,0,0,0,0,15,6,0,0,0,0,0,0,11,16,16,15,6,0,2 +0,0,0,9,16,6,0,0,0,0,8,16,11,1,0,0,0,3,16,8,0,0,0,0,0,11,15,0,0,9,9,0,0,8,16,16,16,16,8,0,0,0,6,8,15,14,1,0,0,0,0,5,16,5,0,0,0,0,0,12,15,1,0,0,4 +0,1,7,11,16,11,0,0,0,8,14,8,10,16,3,0,0,4,4,3,15,12,0,0,0,0,0,8,13,0,0,0,0,0,0,5,16,4,0,0,0,0,0,0,10,11,0,0,0,0,0,5,13,13,0,0,0,0,13,13,8,0,0,0,3 +0,0,4,16,12,1,0,0,0,0,13,16,13,12,0,0,0,5,16,11,0,14,2,0,0,5,16,12,0,9,7,0,0,5,16,6,0,9,6,0,0,1,16,4,0,14,6,0,0,0,10,12,14,16,4,0,0,0,3,14,16,7,0,0,0 +0,3,16,5,0,3,5,0,0,7,16,5,0,12,14,0,0,10,16,2,9,16,5,0,0,7,16,14,16,13,0,0,0,0,7,16,15,5,0,0,0,0,5,16,6,0,0,0,0,0,14,11,0,0,0,0,0,3,16,9,0,0,0,0,4 +0,0,12,8,0,0,0,0,0,1,16,8,0,1,5,0,0,7,16,2,0,12,13,0,0,8,16,8,10,16,6,0,0,3,15,16,16,13,3,0,0,0,0,11,14,1,0,0,0,0,5,16,3,0,0,0,0,0,14,8,0,0,0,0,4 +0,2,14,16,12,1,0,0,0,8,15,12,16,2,0,0,0,0,3,4,16,2,0,0,0,0,0,13,10,0,0,0,0,0,4,16,4,0,0,0,0,0,12,11,0,0,0,0,0,2,16,8,6,8,9,1,0,2,13,16,16,16,16,5,2 +0,0,12,14,12,11,4,0,0,0,4,10,12,15,14,0,0,0,0,0,0,12,10,0,0,0,2,4,5,16,3,0,0,2,14,16,16,16,8,0,0,0,4,11,14,2,0,0,0,0,3,14,3,0,0,0,0,0,14,10,0,0,0,0,7 +0,0,4,16,12,1,0,0,0,0,10,15,13,11,0,0,0,0,5,12,5,16,0,0,0,0,0,0,8,15,0,0,0,0,0,1,15,12,0,0,0,0,0,11,16,10,0,0,0,0,5,16,16,16,16,6,0,0,6,16,5,5,10,13,2 +0,0,0,7,11,0,0,0,0,0,0,16,10,0,0,0,0,0,4,16,0,0,0,0,0,0,4,12,0,0,0,0,0,0,8,12,12,11,2,0,0,0,8,16,12,8,12,0,0,0,3,16,5,8,15,2,0,0,0,9,16,14,5,0,6 +0,2,15,16,16,15,1,0,0,2,13,13,11,16,1,0,0,0,2,16,15,7,0,0,0,0,0,14,14,0,0,0,0,0,9,14,16,0,0,0,0,1,14,8,12,5,0,0,0,3,16,6,15,3,0,0,0,2,16,16,10,0,0,0,8 +0,0,7,13,12,3,0,0,0,0,14,8,11,12,0,0,0,0,0,1,9,12,0,0,0,0,0,13,16,1,0,0,0,0,0,9,12,12,3,0,0,0,0,0,0,6,12,0,0,0,8,1,2,8,13,0,0,0,10,16,16,11,2,0,3 +0,0,2,16,12,0,0,0,0,0,2,16,16,3,0,0,0,0,2,16,16,3,0,0,0,0,1,16,16,2,0,0,0,0,5,16,13,0,0,0,0,0,1,16,14,0,0,0,0,0,4,16,12,0,0,0,0,0,2,14,10,0,0,0,1 +0,0,5,15,9,0,0,0,0,1,13,13,15,6,0,0,0,2,16,4,3,15,2,0,0,5,16,0,0,10,6,0,0,5,14,0,0,9,9,0,0,4,16,0,1,11,6,0,0,1,14,11,12,16,2,0,0,0,5,13,14,4,0,0,0 +0,0,2,11,15,3,0,0,0,0,9,13,5,12,0,0,0,0,14,3,0,5,4,0,0,5,11,0,0,4,6,0,0,7,12,0,0,7,6,0,0,4,14,0,1,13,5,0,0,0,13,10,13,14,0,0,0,0,3,11,13,2,0,0,0 +0,0,1,14,7,0,0,0,0,0,6,14,12,6,0,0,0,0,11,11,0,12,0,0,0,0,13,8,0,7,5,0,0,0,15,6,0,5,8,0,0,0,12,3,0,10,9,0,0,0,10,10,9,15,4,0,0,0,2,11,14,7,0,0,0 +0,0,0,12,11,0,0,0,0,0,3,16,13,1,0,0,0,0,9,16,3,0,0,0,0,0,13,13,0,0,0,0,0,0,12,12,8,6,0,0,0,0,11,16,16,16,9,0,0,0,8,16,11,9,16,2,0,0,0,11,16,16,15,1,6 +0,0,8,13,12,7,0,0,0,0,12,9,9,15,2,0,0,0,0,7,14,11,0,0,0,0,4,16,10,0,0,0,0,0,0,9,16,6,0,0,0,0,0,0,2,15,5,0,0,0,9,1,4,16,4,0,0,0,12,16,16,8,1,0,3 +0,1,7,11,13,16,14,0,0,0,8,7,4,11,10,0,0,0,0,0,1,16,1,0,0,0,0,0,11,6,0,0,0,2,11,12,16,13,4,0,0,5,9,16,6,2,0,0,0,0,4,12,0,0,0,0,0,0,13,3,0,0,0,0,7 +0,0,8,16,15,3,0,0,0,0,10,16,15,10,0,0,0,0,3,11,13,12,0,0,0,0,0,1,16,9,0,0,0,0,0,6,16,4,0,0,0,0,0,12,14,0,0,0,0,0,7,16,15,12,12,3,0,0,8,15,13,16,16,11,2 +0,0,7,15,12,0,0,0,0,6,15,8,14,3,0,0,0,3,6,0,15,3,0,0,0,0,0,4,12,0,0,0,0,0,0,11,6,0,0,0,0,0,3,15,1,0,0,0,0,0,13,13,9,12,10,0,0,0,12,13,12,10,7,2,2 +0,0,12,16,16,10,1,0,0,4,16,9,8,9,1,0,0,9,14,1,0,0,0,0,0,9,16,11,1,0,0,0,0,0,7,12,11,0,0,0,0,0,0,2,16,1,0,0,0,1,12,12,15,0,0,0,0,0,13,16,6,0,0,0,5 +0,0,9,16,15,9,0,0,0,4,16,15,12,14,2,0,0,9,16,12,3,0,0,0,0,5,16,16,13,2,0,0,0,0,3,4,14,5,0,0,0,0,0,0,12,9,0,0,0,0,7,11,16,7,0,0,0,0,13,16,13,1,0,0,5 +0,0,3,14,16,13,0,0,0,1,14,11,5,14,7,0,0,9,16,3,0,13,10,0,0,11,16,8,14,16,7,0,0,1,11,12,15,14,1,0,0,0,0,3,16,7,0,0,0,0,0,12,15,1,0,0,0,0,5,16,4,0,0,0,9 +0,0,2,16,10,0,0,0,0,0,6,16,16,4,0,0,0,0,2,15,16,7,0,0,0,0,0,11,16,4,0,0,0,0,0,15,16,3,0,0,0,0,0,13,15,0,0,0,0,0,1,16,13,0,0,0,0,0,2,15,11,0,0,0,1 +0,0,11,16,16,8,0,0,0,1,13,7,9,16,3,0,0,0,0,1,10,15,1,0,0,0,0,15,16,10,0,0,0,0,0,14,16,16,5,0,0,0,0,0,0,8,14,0,0,1,12,4,5,13,15,0,0,0,11,16,16,15,5,0,3 +0,3,6,15,13,1,0,0,0,10,16,5,11,10,0,0,0,1,11,10,11,11,0,0,0,0,0,10,16,3,0,0,0,0,1,13,13,3,0,0,0,0,7,15,6,11,0,0,0,0,10,12,7,14,0,0,0,0,4,16,15,3,0,0,8 +0,0,11,7,10,3,0,0,0,0,9,12,12,14,0,0,0,0,0,15,15,11,1,0,0,0,1,14,9,0,0,0,0,0,9,13,8,0,0,0,0,0,14,5,10,0,0,0,0,0,14,2,12,0,0,0,0,0,10,16,10,0,0,0,8 +0,4,16,16,16,15,2,0,0,3,11,8,9,16,8,0,0,0,0,1,14,15,1,0,0,0,2,15,16,6,0,0,0,0,2,12,14,14,0,0,0,0,0,0,3,16,1,0,0,5,10,4,7,16,2,0,0,2,16,16,16,8,0,0,3 +0,0,7,16,12,5,0,0,0,0,13,10,7,15,0,0,0,0,10,15,12,13,0,0,0,0,0,13,16,5,0,0,0,0,8,15,10,8,0,0,0,0,13,5,0,13,1,0,0,0,14,2,4,15,1,0,0,0,6,16,16,6,0,0,8 +0,0,5,13,15,6,0,0,0,1,15,15,10,11,0,0,0,7,16,1,0,0,0,0,0,10,16,16,12,2,0,0,0,3,8,4,9,10,0,0,0,0,0,0,5,14,0,0,0,0,4,4,13,13,0,0,0,0,11,16,16,4,0,0,5 +0,0,12,16,16,9,1,0,0,8,16,14,11,14,3,0,0,8,16,4,0,0,0,0,0,3,15,16,6,0,0,0,0,0,0,6,14,1,0,0,0,0,0,1,16,4,0,0,0,0,9,11,16,1,0,0,0,1,13,16,10,0,0,0,5 +0,0,1,15,11,1,0,0,0,0,3,16,16,2,0,0,0,0,3,16,16,1,0,0,0,0,1,16,16,2,0,0,0,0,4,16,14,0,0,0,0,0,2,16,16,0,0,0,0,0,2,16,14,0,0,0,0,0,1,15,7,0,0,0,1 +0,0,6,14,13,5,0,0,0,0,5,6,14,10,0,0,0,0,9,16,14,1,0,0,0,0,7,16,10,0,0,0,0,0,10,7,12,2,0,0,0,0,13,3,6,6,0,0,0,0,12,5,11,9,0,0,0,0,5,16,15,3,0,0,8 +0,0,0,16,6,0,0,0,0,0,0,16,14,0,0,0,0,0,0,14,16,0,0,0,0,0,0,15,15,0,0,0,0,0,0,14,15,0,0,0,0,0,0,15,14,0,0,0,0,0,0,13,14,0,0,0,0,0,0,10,13,0,0,0,1 +0,0,5,14,10,1,0,0,0,2,15,15,15,10,0,0,0,7,16,11,1,16,1,0,0,6,16,2,0,14,6,0,0,3,15,0,0,13,6,0,0,3,16,0,3,16,1,0,0,1,15,12,15,14,0,0,0,0,5,13,13,4,0,0,0 +0,0,3,12,14,3,0,0,0,0,11,11,8,13,0,0,0,5,15,1,0,7,2,0,0,3,15,3,0,3,6,0,0,3,11,2,0,4,7,0,0,1,9,0,0,5,7,0,0,0,11,4,4,14,2,0,0,0,2,13,15,9,0,0,0 +0,0,8,15,16,8,0,0,0,3,13,13,6,2,0,0,0,13,8,0,0,0,0,0,0,5,16,5,0,0,0,0,0,0,8,14,2,0,0,0,0,0,0,13,11,0,0,0,0,0,4,13,16,0,0,0,0,0,7,16,8,0,0,0,5 +0,0,16,16,16,10,0,0,0,0,6,9,15,16,1,0,0,0,0,8,16,7,0,0,0,0,0,4,16,6,0,0,0,0,0,0,8,15,1,0,0,0,0,0,0,15,10,0,0,0,1,4,9,15,12,0,0,0,15,16,16,11,2,0,3 +0,2,12,16,16,6,0,0,0,11,15,8,15,12,0,0,0,10,2,7,16,10,0,0,0,0,0,12,15,2,0,0,0,0,0,3,16,11,0,0,0,0,0,0,6,15,10,0,0,0,7,5,10,16,15,0,0,3,16,16,16,10,2,0,3 +0,0,1,12,8,0,0,0,0,0,3,16,6,0,0,0,0,0,9,12,0,0,0,0,0,0,12,11,4,2,0,0,0,0,16,16,16,16,4,0,0,0,12,10,0,2,14,1,0,0,7,11,2,6,15,5,0,0,1,10,15,16,9,0,6 +0,4,16,16,16,8,0,0,0,0,4,2,11,12,0,0,0,0,0,3,15,6,0,0,0,2,4,11,15,1,0,0,0,10,16,16,16,16,9,0,0,1,12,12,4,4,2,0,0,2,16,2,0,0,0,0,0,7,14,0,0,0,0,0,7 +0,1,12,16,16,12,0,0,0,3,15,9,14,14,0,0,0,0,0,0,14,12,0,0,0,0,6,11,16,11,3,0,0,0,11,16,16,16,12,0,0,0,5,16,6,7,3,0,0,0,11,15,0,0,0,0,0,1,15,8,0,0,0,0,7 +0,0,9,16,16,14,0,0,0,3,15,10,2,5,0,0,0,11,9,0,0,0,0,0,0,8,14,1,0,0,0,0,0,1,15,5,0,0,0,0,0,0,11,9,0,0,0,0,0,0,9,12,0,0,0,0,0,0,14,9,0,0,0,0,5 +0,2,16,16,16,13,1,0,0,0,3,3,3,15,5,0,0,0,0,0,5,15,0,0,0,0,3,5,14,3,0,0,0,0,15,16,16,14,5,0,0,0,6,14,4,9,5,0,0,0,12,7,0,0,0,0,0,4,15,0,0,0,0,0,7 +0,0,8,16,16,11,0,0,0,5,15,7,2,16,3,0,0,12,10,0,4,16,1,0,0,11,14,2,12,12,0,0,0,0,11,14,16,2,0,0,0,0,1,16,13,1,0,0,0,0,7,13,16,8,0,0,0,0,13,9,12,12,0,0,8 +0,2,0,13,12,3,0,0,5,14,1,13,16,5,0,0,0,8,16,14,16,4,0,0,0,0,8,16,16,2,0,0,0,0,0,16,16,9,0,0,0,0,3,16,16,16,4,0,0,0,11,16,16,16,6,0,0,0,1,15,16,10,0,0,8 +0,0,8,16,16,4,0,0,0,5,16,8,13,10,0,0,0,6,11,1,14,7,0,0,0,0,0,6,16,1,0,0,0,0,0,15,9,0,0,0,0,0,8,14,2,0,0,0,0,0,16,9,0,2,3,0,0,0,10,16,16,12,3,0,2 +0,0,3,13,12,0,0,0,0,1,16,13,16,4,0,0,0,8,6,7,16,1,0,0,0,0,2,15,9,0,0,0,0,0,11,14,0,0,0,0,0,0,16,10,0,0,0,0,0,0,15,13,8,9,4,0,0,0,3,13,15,13,2,0,2 +0,3,13,16,8,0,0,0,0,14,11,12,16,0,0,0,0,10,1,7,16,1,0,0,0,0,0,12,12,0,0,0,0,0,7,15,1,0,0,0,0,0,15,8,0,0,0,0,0,4,16,9,7,8,8,0,0,2,15,16,16,16,11,0,2 +0,0,5,13,16,7,0,0,0,0,15,8,11,12,0,0,0,0,12,5,14,5,0,0,0,0,10,16,8,0,0,0,0,0,9,16,2,0,0,0,0,0,15,15,6,0,0,0,0,0,14,13,10,0,0,0,0,0,8,16,4,0,0,0,8 +0,0,4,16,12,1,0,0,0,0,10,16,14,9,0,0,0,0,15,14,2,16,2,0,0,1,16,15,1,12,7,0,0,1,16,8,3,10,10,0,0,0,15,4,0,12,10,0,0,0,11,8,4,16,6,0,0,0,2,13,16,14,1,0,0 +0,0,8,16,13,4,0,0,0,0,5,6,12,14,0,0,0,0,0,3,15,14,0,0,0,0,0,13,15,4,0,0,0,0,0,9,12,1,0,0,0,0,0,1,11,12,0,0,0,0,3,4,9,16,4,0,0,0,8,15,14,12,3,0,3 +0,0,4,16,8,0,0,0,0,0,13,12,1,0,0,0,0,3,16,6,0,0,0,0,0,4,16,3,0,0,0,0,0,6,16,1,9,11,3,0,0,2,16,10,15,12,14,2,0,0,11,16,9,7,16,6,0,0,3,13,16,16,10,0,6 +0,0,5,16,16,8,0,0,0,3,11,11,7,4,0,0,0,11,11,0,0,0,0,0,0,13,15,7,0,0,0,0,0,3,8,13,8,0,0,0,0,0,0,2,15,2,0,0,0,0,8,5,15,6,0,0,0,0,7,16,15,3,0,0,5 +0,0,2,9,15,16,7,0,0,0,13,12,4,10,12,0,0,1,16,6,5,14,6,0,0,0,8,16,16,11,0,0,0,0,0,2,4,16,2,0,0,0,0,0,8,13,1,0,0,0,0,2,16,4,0,0,0,0,0,10,7,0,0,0,9 +0,0,0,6,14,16,10,0,0,0,12,16,9,14,11,0,0,7,15,3,8,13,3,0,0,6,16,14,16,8,0,0,0,0,3,4,11,13,0,0,0,0,0,0,14,10,0,0,0,0,0,4,16,5,0,0,0,0,0,7,16,0,0,0,9 +0,0,0,14,14,0,0,0,0,0,5,16,12,0,0,0,0,0,7,16,6,0,0,0,0,0,12,16,4,0,0,0,0,0,12,16,1,0,0,0,0,0,12,16,0,0,0,0,0,0,9,16,4,0,0,0,0,0,1,13,15,2,0,0,1 +0,0,5,11,14,5,0,0,0,7,13,5,4,14,2,0,0,0,4,8,2,14,2,0,0,0,1,13,14,3,0,0,0,0,1,14,8,0,0,0,0,0,9,9,10,2,0,0,0,0,13,1,8,8,0,0,0,0,6,13,14,7,0,0,8 +0,0,8,11,0,0,13,9,0,3,15,6,0,12,13,1,0,3,16,5,7,16,5,0,0,0,12,16,16,16,8,0,0,0,1,11,13,4,0,0,0,0,1,15,4,0,0,0,0,0,7,13,0,0,0,0,0,0,11,5,0,0,0,0,4 +0,2,7,12,15,13,0,0,2,15,16,15,12,5,0,0,2,16,9,1,0,0,0,0,0,10,13,1,0,0,0,0,0,2,15,7,0,0,0,0,0,0,7,14,1,0,0,0,0,0,9,16,3,0,0,0,0,0,11,13,1,0,0,0,5 +0,0,8,15,16,16,2,0,0,7,16,12,8,5,0,0,0,10,10,0,0,0,0,0,0,11,10,0,0,0,0,0,0,2,16,4,0,0,0,0,0,0,6,14,0,0,0,0,0,0,5,14,0,0,0,0,0,0,14,11,0,0,0,0,5 +0,0,2,14,12,0,0,0,0,0,4,16,12,0,0,0,0,0,5,16,6,0,0,0,0,0,6,16,6,0,0,0,0,0,9,16,5,0,0,0,0,0,10,16,4,0,0,0,0,0,8,16,7,0,0,0,0,0,2,14,16,3,0,0,1 +0,1,8,15,15,6,0,0,0,10,16,15,15,14,0,0,0,4,13,7,16,7,0,0,0,2,14,16,14,1,0,0,0,0,3,16,7,0,0,0,0,0,11,16,15,0,0,0,0,0,13,16,16,3,0,0,0,0,7,16,15,1,0,0,8 +0,0,8,16,8,0,0,0,0,9,16,13,16,5,0,0,0,7,3,6,16,4,0,0,0,0,0,15,13,0,0,0,0,0,9,16,2,0,0,0,0,0,14,9,0,0,0,0,0,0,16,11,4,6,8,0,0,0,7,16,16,11,7,0,2 +0,0,3,10,15,8,0,0,0,3,15,9,8,12,0,0,0,8,8,0,5,11,0,0,0,2,6,0,10,7,0,0,0,0,0,5,14,1,0,0,0,0,0,14,5,0,0,0,0,0,4,16,4,5,11,0,0,0,1,14,15,11,2,0,2 +0,0,6,12,14,5,0,0,0,4,16,11,8,15,6,0,0,4,16,7,2,12,6,0,0,0,13,15,15,13,2,0,0,0,1,16,16,6,0,0,0,0,8,15,14,15,1,0,0,3,16,10,10,16,1,0,0,1,12,16,14,5,0,0,8 +0,0,1,12,16,16,11,0,0,0,5,15,8,8,5,0,0,2,15,15,12,7,0,0,0,11,16,16,16,16,4,0,0,3,5,2,1,13,9,0,0,0,0,0,0,11,9,0,0,0,2,10,9,16,4,0,0,0,2,16,16,8,0,0,5 +0,0,0,0,6,16,4,0,0,0,0,0,9,16,8,0,0,0,1,5,16,16,8,0,0,8,16,16,16,16,6,0,0,5,12,12,12,16,4,0,0,0,0,0,9,16,5,0,0,0,0,0,8,16,4,0,0,0,0,0,5,16,2,0,1 +0,0,5,12,15,15,1,0,0,6,16,13,14,16,0,0,0,1,4,0,8,14,0,0,0,0,0,7,15,11,4,0,0,0,7,16,16,16,11,0,0,0,6,15,13,4,0,0,0,0,0,16,9,0,0,0,0,0,5,16,5,0,0,0,7 +0,1,9,15,16,7,0,0,0,6,14,7,14,8,0,0,0,3,2,0,14,6,0,0,0,0,1,16,16,4,0,0,0,0,0,8,15,16,4,0,0,0,0,0,0,13,8,0,0,0,6,8,11,16,4,0,0,0,15,12,10,3,0,0,3 +0,0,0,0,14,12,1,0,0,0,0,0,16,16,2,0,0,0,0,1,15,16,2,0,0,1,8,11,16,11,0,0,0,10,16,16,16,8,0,0,0,1,8,9,16,8,0,0,0,0,0,1,16,14,0,0,0,0,0,0,12,16,2,0,1 +0,0,0,0,5,13,3,0,0,0,0,1,11,16,4,0,0,2,8,14,16,16,5,0,0,2,7,4,12,16,3,0,0,0,0,0,8,16,4,0,0,0,0,0,8,16,5,0,0,0,0,0,8,16,8,0,0,0,0,0,8,16,6,0,1 +0,0,6,11,4,0,0,0,0,0,14,12,14,1,0,0,0,0,7,0,16,6,0,0,0,0,0,0,11,9,0,0,0,0,0,0,13,3,0,0,0,0,0,3,14,2,0,0,0,0,9,16,12,8,7,0,0,0,11,16,16,15,12,0,2 +0,0,1,15,4,0,0,0,0,4,7,9,16,4,0,0,0,8,7,0,4,15,0,0,0,6,6,0,0,11,6,0,0,5,9,0,0,3,9,0,0,0,15,0,0,5,9,0,0,0,11,11,5,15,5,0,0,0,3,13,16,11,0,0,0 +0,0,0,0,7,15,0,0,0,0,0,0,10,16,1,0,0,0,2,11,16,16,0,0,0,7,16,14,11,16,3,0,0,1,2,0,8,16,3,0,0,0,0,0,4,16,4,0,0,0,0,0,4,16,3,0,0,0,0,0,6,15,2,0,1 +0,0,0,4,16,4,0,0,0,0,0,9,16,3,0,0,0,0,4,16,8,4,11,0,0,1,12,14,2,13,14,0,0,11,16,13,13,16,7,0,0,9,16,16,16,13,3,0,0,0,0,6,16,6,0,0,0,0,0,6,16,4,0,0,4 +0,0,0,0,12,15,2,0,0,0,0,1,15,16,0,0,0,0,3,12,16,16,0,0,0,6,16,16,16,15,0,0,0,6,11,6,16,12,0,0,0,0,0,0,16,12,0,0,0,0,0,0,15,13,0,0,0,0,0,0,9,16,3,0,1 +0,0,8,16,15,5,0,0,0,3,16,13,16,6,0,0,0,1,5,3,16,8,0,0,0,0,1,6,16,5,0,0,0,2,15,16,16,16,6,0,0,5,12,16,15,12,3,0,0,0,1,16,6,0,0,0,0,0,10,15,0,0,0,0,7 +0,0,1,10,16,8,0,0,0,0,13,11,10,15,2,0,0,5,16,0,6,16,4,0,0,3,16,16,14,16,4,0,0,0,0,0,0,13,6,0,0,0,0,0,0,12,8,0,0,0,2,13,5,13,7,0,0,0,1,14,13,9,1,0,9 +0,0,9,16,12,1,0,0,0,7,16,13,16,9,0,0,0,9,6,0,12,12,0,0,0,0,0,0,11,10,0,0,0,0,0,2,15,6,0,0,0,0,0,11,12,0,0,0,0,0,12,16,13,9,2,0,0,0,11,16,16,16,11,0,2 +0,0,6,16,15,10,1,0,0,1,14,8,8,11,2,0,0,5,12,11,12,6,0,0,0,7,16,14,12,15,3,0,0,1,2,0,0,10,8,0,0,0,1,0,0,9,6,0,0,0,14,4,5,14,1,0,0,0,8,16,13,3,0,0,5 +0,0,0,14,3,0,0,0,0,0,5,15,2,0,0,0,0,0,10,11,0,0,0,0,0,0,13,6,0,0,0,0,0,0,14,16,16,11,3,0,0,0,13,7,2,7,15,0,0,0,8,13,1,5,16,1,0,0,1,11,16,15,7,0,6 +0,0,2,15,1,0,0,0,0,0,10,10,0,0,0,0,0,1,14,3,0,0,0,0,0,7,13,0,0,0,0,0,0,7,15,16,12,6,1,0,0,4,16,11,8,13,9,0,0,1,15,10,4,10,14,0,0,0,4,12,16,12,5,0,6 +0,2,14,16,9,0,0,0,0,6,16,14,14,2,0,0,0,5,12,4,16,1,0,0,0,0,1,3,16,1,0,0,0,0,0,8,12,0,0,0,0,0,1,13,9,0,0,0,0,1,15,16,16,13,8,1,0,2,16,16,16,16,16,3,2 +0,0,5,16,16,5,0,0,0,5,16,11,7,1,0,0,0,11,16,14,5,0,0,0,0,4,8,11,16,13,1,0,0,0,0,0,4,16,7,0,0,0,2,0,0,9,11,0,0,0,8,11,8,15,7,0,0,0,3,12,16,9,0,0,5 +0,0,9,14,11,9,0,0,0,1,16,11,9,15,0,0,0,4,16,3,6,14,7,0,0,1,14,16,16,10,1,0,0,0,10,16,12,0,0,0,0,1,14,10,16,7,0,0,0,2,16,4,11,15,0,0,0,0,10,14,15,11,0,0,8 +0,0,5,14,10,0,0,0,0,1,16,11,11,11,0,0,0,4,15,3,1,13,4,0,0,5,9,0,0,6,8,0,0,6,8,0,0,4,8,0,0,4,14,0,0,7,6,0,0,1,16,11,10,15,2,0,0,0,5,12,13,6,0,0,0 +0,0,11,16,15,10,0,0,0,0,10,16,8,7,0,0,0,2,16,3,0,0,0,0,0,6,16,16,15,7,0,0,0,1,8,8,9,15,6,0,0,0,0,0,0,10,8,0,0,0,2,7,12,16,4,0,0,0,11,13,11,2,0,0,5 +0,0,5,16,8,1,0,0,0,4,13,14,15,6,0,0,0,8,13,0,8,16,1,0,0,8,12,0,0,12,8,0,0,8,12,0,0,12,8,0,0,5,15,2,0,13,9,0,0,0,15,11,12,15,4,0,0,0,6,16,14,5,0,0,0 +0,2,14,14,1,0,0,0,0,9,13,13,6,0,0,0,0,9,5,3,10,0,0,0,0,0,1,3,9,0,0,0,0,0,0,8,4,0,0,0,0,0,2,15,4,2,0,0,0,1,13,16,16,16,9,0,0,0,13,8,8,7,2,0,2 +0,1,8,16,16,15,3,0,0,4,15,9,6,4,0,0,0,7,15,12,8,1,0,0,0,8,15,12,14,15,1,0,0,1,2,0,1,14,8,0,0,0,0,0,0,12,8,0,0,0,12,6,7,15,3,0,0,0,10,16,11,3,0,0,5 +0,0,3,14,16,14,0,0,0,0,15,11,14,16,0,0,0,5,10,0,13,10,0,0,0,0,3,8,16,10,1,0,0,0,13,16,16,16,10,0,0,0,4,14,9,5,1,0,0,0,2,16,4,0,0,0,0,0,3,15,0,0,0,0,7 +0,2,10,13,7,0,0,0,0,8,12,9,16,3,0,0,0,1,0,0,16,5,0,0,0,0,0,6,16,2,0,0,0,0,0,4,14,13,0,0,0,0,0,0,3,16,7,0,0,0,3,5,10,16,5,0,0,3,16,14,12,5,0,0,3 +0,0,4,12,9,0,0,0,0,4,13,7,15,4,0,0,0,7,11,0,8,10,0,0,0,2,16,16,16,7,0,0,0,0,6,16,16,15,2,0,0,0,8,11,1,6,8,0,0,0,8,12,8,13,6,0,0,0,4,16,13,8,0,0,8 +0,2,10,16,14,5,0,0,0,6,13,8,15,12,0,0,0,0,0,1,13,10,0,0,0,0,7,15,16,8,0,0,0,0,6,12,13,16,5,0,0,0,0,0,0,14,8,0,0,0,11,14,16,15,3,0,0,1,13,10,8,2,0,0,3 +0,0,11,15,4,0,0,0,0,6,16,16,16,1,0,0,0,1,6,2,15,7,0,0,0,0,0,0,14,8,0,0,0,0,0,4,16,6,0,0,0,0,0,11,16,2,0,0,0,0,12,16,15,12,13,4,0,0,12,16,16,16,15,6,2 +0,0,0,0,12,10,0,0,0,0,0,5,16,2,0,0,0,0,2,15,8,3,10,0,0,1,12,11,1,6,14,0,0,11,16,16,16,15,10,0,0,5,8,8,10,16,3,0,0,0,0,0,11,11,0,0,0,0,0,0,13,6,0,0,4 +0,1,9,13,7,0,0,0,0,7,12,6,15,6,0,0,0,0,1,4,13,10,0,0,0,0,4,16,16,13,0,0,0,0,0,0,2,13,6,0,0,0,0,0,0,10,8,0,0,0,9,7,9,15,1,0,0,0,13,15,9,1,0,0,3 +0,2,14,16,7,0,0,0,0,2,11,11,16,4,0,0,0,0,0,0,13,8,0,0,0,0,4,9,15,7,0,0,0,0,7,16,16,12,1,0,0,0,0,0,3,16,8,0,0,1,6,11,15,16,7,0,0,2,13,14,11,5,0,0,3 +0,0,3,12,15,2,0,0,0,0,14,10,0,0,0,0,0,4,14,0,0,0,0,0,0,6,10,0,0,0,0,0,0,8,9,8,10,7,1,0,0,4,16,14,6,8,10,0,0,2,15,9,5,11,11,0,0,0,3,13,16,11,1,0,6 +0,0,1,10,15,4,0,0,0,4,13,12,8,13,0,0,0,10,14,0,1,14,0,0,0,8,15,8,14,14,0,0,0,1,14,16,16,15,1,0,0,0,11,10,0,10,9,0,0,0,4,14,5,10,12,0,0,0,1,13,16,13,6,0,8 +0,2,8,9,13,16,7,0,0,4,16,10,5,5,2,0,0,4,16,0,0,0,0,0,0,4,16,16,11,3,0,0,0,1,6,8,13,13,0,0,0,0,0,0,7,16,0,0,0,3,11,12,16,6,0,0,0,3,15,14,4,0,0,0,5 +0,0,7,13,16,7,0,0,0,4,16,7,4,14,6,0,0,2,15,7,4,13,12,0,0,0,12,16,14,14,8,0,0,0,0,0,0,8,8,0,0,0,0,0,1,13,4,0,0,0,0,4,14,11,0,0,0,1,12,12,6,0,0,0,9 +0,0,4,13,15,2,0,0,0,1,13,3,7,10,0,0,0,6,8,0,0,12,0,0,0,4,10,3,11,11,0,0,0,0,14,16,15,15,1,0,0,0,8,16,1,5,8,0,0,0,8,10,5,13,7,0,0,0,4,15,11,6,0,0,8 +0,0,10,14,4,0,0,0,0,4,16,12,16,2,0,0,0,0,7,0,8,8,0,0,0,0,0,0,8,8,0,0,0,0,0,0,14,5,0,0,0,0,0,7,13,1,0,0,0,0,6,16,14,12,9,0,0,0,14,14,12,12,15,1,2 +0,1,9,16,13,7,0,0,0,7,13,4,5,16,3,0,0,7,13,4,7,16,7,0,0,1,14,14,12,11,6,0,0,0,0,0,0,12,3,0,0,0,0,0,5,13,0,0,0,0,7,7,15,4,0,0,0,0,11,13,5,0,0,0,9 +0,0,5,12,10,2,0,0,0,0,12,16,16,5,0,0,0,0,16,16,16,3,0,0,0,0,13,16,16,3,0,0,0,3,16,16,16,3,0,0,0,0,16,16,16,5,0,0,0,0,14,16,16,12,1,0,0,0,3,8,10,8,2,0,1 +0,5,16,14,2,0,0,0,0,7,16,16,10,0,0,0,0,0,0,12,10,0,0,0,0,0,0,15,7,0,0,0,0,0,5,16,2,0,0,0,0,1,12,11,0,0,0,0,0,6,16,16,16,12,3,0,0,5,16,16,16,15,5,0,2 +0,0,9,13,16,15,0,0,0,1,12,12,14,13,0,0,0,0,0,1,14,9,0,0,0,0,5,10,16,13,3,0,0,0,16,16,16,12,7,0,0,0,4,16,7,0,0,0,0,0,5,16,0,0,0,0,0,0,11,12,0,0,0,0,7 +0,0,3,12,6,0,0,0,0,0,11,11,1,0,0,0,0,1,13,1,0,0,0,0,0,3,12,0,4,3,0,0,0,6,10,13,16,15,2,0,0,4,16,12,1,2,10,0,0,0,14,4,7,13,7,0,0,0,3,16,14,8,0,0,6 +0,1,14,16,5,0,0,0,0,2,13,13,16,4,0,0,0,0,4,13,16,3,0,0,0,0,9,16,16,13,0,0,0,0,4,8,8,16,8,0,0,0,0,1,5,14,11,0,0,1,13,16,16,15,3,0,0,0,15,11,5,1,0,0,3 +0,0,3,11,11,6,0,0,0,3,14,16,16,12,0,0,0,0,7,16,16,12,0,0,0,0,13,16,16,12,0,0,0,0,13,16,16,14,0,0,0,2,15,16,16,13,0,0,0,0,12,16,16,12,0,0,0,0,3,11,11,7,0,0,1 +0,0,2,13,16,6,0,0,0,0,11,10,1,0,0,0,0,0,14,1,0,0,0,0,0,1,15,0,0,0,0,0,0,1,14,6,8,2,0,0,0,0,15,13,8,11,1,0,0,0,9,12,0,9,7,0,0,0,0,13,16,15,6,0,6 +0,0,0,1,13,10,0,0,0,0,0,10,15,3,0,0,0,0,7,16,6,1,14,1,0,3,16,8,0,9,15,0,0,14,16,16,12,16,10,0,0,6,13,16,16,16,6,0,0,0,0,0,14,12,0,0,0,0,0,2,15,3,0,0,4 +0,0,3,11,14,16,14,0,0,0,13,15,12,15,13,0,0,0,3,0,1,16,5,0,0,0,0,4,16,16,10,0,0,0,0,7,16,16,7,0,0,0,0,6,15,2,0,0,0,0,0,15,9,0,0,0,0,0,3,15,2,0,0,0,7 +0,0,3,8,12,12,16,11,0,0,14,16,16,15,16,9,0,0,6,3,0,9,15,0,0,0,0,9,14,16,12,0,0,0,1,16,16,12,3,0,0,0,0,3,16,4,0,0,0,0,0,13,13,0,0,0,0,0,2,15,5,0,0,0,7 +0,0,5,12,12,14,5,0,0,0,13,13,11,8,4,0,0,5,16,1,0,0,0,0,0,8,16,16,12,2,0,0,0,1,7,8,15,15,0,0,0,0,0,0,1,16,1,0,0,0,5,9,11,15,0,0,0,0,5,14,11,3,0,0,5 +0,0,0,2,16,4,0,0,0,0,1,15,14,1,3,0,0,0,11,16,3,4,16,0,0,9,16,8,4,13,12,0,0,3,15,16,16,16,11,0,0,0,1,8,14,14,4,0,0,0,0,0,16,8,0,0,0,0,0,2,16,6,0,0,4 +0,1,9,15,9,0,0,0,0,4,13,7,15,4,0,0,0,0,0,2,15,3,0,0,0,0,12,16,16,11,1,0,0,0,5,5,5,13,8,0,0,0,0,0,1,12,6,0,0,0,9,8,14,12,1,0,0,0,15,12,6,0,0,0,3 +0,0,4,12,15,8,0,0,0,3,16,6,4,15,2,0,0,2,15,1,2,12,6,0,0,0,13,16,16,15,1,0,0,2,15,12,10,14,7,0,0,6,15,1,0,5,9,0,0,0,15,8,4,10,8,0,0,0,2,15,15,11,1,0,8 +0,0,0,9,12,5,0,0,0,0,15,16,16,9,0,0,0,0,13,16,16,15,0,0,0,0,13,16,16,12,0,0,0,0,11,16,16,13,0,0,0,0,7,16,16,11,1,0,0,0,7,16,16,8,0,0,0,0,0,10,12,5,0,0,1 +0,0,6,14,13,2,0,0,0,10,16,9,16,12,0,0,0,5,16,16,16,16,3,0,0,0,5,9,5,8,8,0,0,0,0,0,0,8,8,0,0,0,0,0,1,12,8,0,0,0,5,16,16,15,3,0,0,0,4,15,9,2,0,0,9 +0,0,13,16,10,1,0,0,0,8,15,8,16,6,0,0,0,11,13,0,11,12,0,0,0,5,7,0,10,12,0,0,0,0,0,0,12,10,0,0,0,0,0,3,16,5,0,0,0,0,7,15,15,12,12,2,0,1,12,16,13,11,11,1,2 +0,0,7,16,14,8,0,0,0,0,5,14,13,12,0,0,0,0,8,16,16,10,0,0,0,0,9,16,16,6,0,0,0,0,11,16,16,5,0,0,0,2,14,16,16,4,0,0,0,1,16,16,16,3,0,0,0,0,7,14,16,10,1,0,1 +0,0,4,15,12,2,0,0,0,0,12,11,10,14,0,0,0,6,11,0,0,15,4,0,0,4,14,0,0,8,8,0,0,6,12,0,0,9,8,0,0,1,15,3,0,10,10,0,0,0,12,13,12,16,3,0,0,0,4,13,15,6,0,0,0 +0,0,5,16,16,16,4,0,0,0,12,15,9,5,1,0,0,12,16,14,7,0,0,0,0,9,16,16,16,15,0,0,0,0,0,0,2,16,4,0,0,0,6,5,0,14,7,0,0,0,12,10,9,16,2,0,0,0,5,16,16,7,0,0,5 +0,1,8,10,3,0,0,0,0,2,15,16,16,12,0,0,0,1,14,16,16,12,0,0,0,4,16,16,16,8,0,0,0,4,16,16,16,6,0,0,0,4,16,16,15,3,0,0,0,4,16,16,13,2,0,0,0,1,7,10,8,2,0,0,1 +0,0,4,13,11,1,0,0,0,9,15,10,12,8,0,0,0,8,10,0,6,9,0,0,0,0,0,1,16,3,0,0,0,0,0,14,16,13,3,0,0,0,6,2,4,8,12,0,0,1,15,4,5,12,12,0,0,0,7,13,16,13,1,0,3 +0,0,8,15,14,1,0,0,0,1,15,15,14,11,0,0,0,7,16,3,1,16,3,0,0,8,16,0,0,13,7,0,0,5,16,0,0,13,8,0,0,2,16,3,1,16,4,0,0,0,16,14,16,12,0,0,0,0,9,16,10,1,0,0,0 +0,0,0,13,6,2,0,0,0,0,2,16,8,16,3,0,0,1,14,13,12,16,0,0,0,9,16,16,16,16,10,0,0,3,12,14,16,13,6,0,0,0,0,8,16,1,0,0,0,0,0,11,10,0,0,0,0,0,0,13,7,0,0,0,4 +0,0,9,12,12,1,0,0,0,6,14,5,9,11,3,0,0,7,12,9,15,9,1,0,0,1,16,14,2,0,0,0,0,0,14,12,14,2,0,0,0,2,12,0,9,13,0,0,0,0,15,1,6,14,0,0,0,0,6,14,15,3,0,0,8 +0,0,4,16,7,2,0,0,0,0,8,16,16,4,0,0,0,0,11,16,16,3,0,0,0,0,9,16,16,0,0,0,0,0,11,16,16,2,0,0,0,0,9,16,15,0,0,0,0,0,9,16,15,0,0,0,0,0,6,12,3,0,0,0,1 +0,0,4,16,10,1,0,0,0,0,11,10,11,12,0,0,0,6,16,0,1,12,3,0,0,6,14,1,0,5,7,0,0,8,8,0,0,5,8,0,0,4,13,0,1,12,9,0,0,0,14,10,14,16,2,0,0,0,4,15,12,3,0,0,0 +0,0,0,5,12,15,12,0,0,0,8,11,3,4,14,1,0,2,15,2,4,16,13,0,0,1,15,12,12,12,10,0,0,0,1,2,0,13,7,0,0,0,0,0,2,14,1,0,0,0,0,0,12,7,0,0,0,0,0,8,11,0,0,0,9 +0,0,1,12,12,1,0,0,0,0,10,13,12,9,0,0,0,2,16,6,1,13,3,0,0,6,16,2,0,12,8,0,0,2,16,1,0,8,8,0,0,0,14,5,0,9,9,0,0,0,5,15,4,14,6,0,0,0,0,14,15,7,0,0,0 +0,3,12,12,15,16,16,2,0,2,14,13,12,16,11,0,0,0,0,0,11,15,2,0,0,0,0,11,16,3,0,0,0,0,1,16,12,0,0,0,0,0,8,16,2,0,0,0,0,0,14,12,0,0,0,0,0,3,16,9,0,0,0,0,7 +0,0,0,3,13,16,5,0,0,0,3,15,7,13,13,0,0,1,13,6,14,16,11,0,0,1,16,14,10,11,10,0,0,0,0,0,0,14,6,0,0,0,0,0,2,15,2,0,0,0,0,0,10,8,0,0,0,0,0,4,13,0,0,0,9 +0,0,0,2,15,10,0,0,0,0,0,10,16,16,0,0,0,0,4,16,16,3,0,0,0,4,15,16,16,1,0,0,0,4,12,16,16,0,0,0,0,0,0,12,16,4,0,0,0,0,0,9,16,4,0,0,0,0,0,4,15,4,0,0,1 +0,0,2,15,15,11,3,0,0,2,15,6,4,4,3,0,0,5,16,15,7,0,0,0,0,1,4,6,13,13,0,0,0,0,0,0,0,14,4,0,0,0,3,3,0,11,3,0,0,0,7,7,10,12,0,0,0,0,3,14,9,1,0,0,5 +0,0,7,13,15,6,0,0,0,1,16,6,10,8,8,0,0,4,16,3,4,11,8,0,0,0,11,11,13,9,0,0,0,0,2,15,16,3,0,0,0,0,9,9,7,15,2,0,0,0,12,7,0,15,4,0,0,0,4,15,16,13,1,0,8 +0,0,1,14,6,9,5,0,0,0,11,12,3,16,7,0,0,5,16,2,12,11,3,0,0,10,16,8,16,16,14,0,0,7,16,16,14,11,3,0,0,0,3,13,8,0,0,0,0,0,0,15,5,0,0,0,0,0,2,16,2,0,0,0,4 +0,0,8,16,16,16,9,0,0,5,15,9,5,14,16,2,0,0,0,0,3,16,9,0,0,0,0,0,14,11,1,0,0,0,0,7,16,5,0,0,0,0,3,16,8,0,0,0,0,0,9,16,3,0,0,0,0,0,12,16,1,0,0,0,7 +0,0,5,14,8,0,0,0,0,0,15,11,15,0,0,0,0,0,5,2,11,3,0,0,0,0,0,9,16,5,0,0,0,0,1,12,12,15,5,0,0,0,0,0,0,5,12,0,0,0,6,4,7,12,10,0,0,0,9,15,12,9,1,0,3 +0,0,6,16,9,1,0,0,0,3,15,8,13,3,0,0,0,8,8,0,11,4,0,0,0,1,0,7,16,6,0,0,0,0,2,16,14,16,5,0,0,0,0,5,0,11,8,0,0,0,4,6,9,15,2,0,0,0,4,15,11,3,0,0,3 +0,0,4,12,14,2,0,0,0,4,15,7,11,14,5,0,0,7,12,0,3,15,4,0,0,2,14,12,14,6,0,0,0,0,3,16,16,2,0,0,0,0,6,12,10,11,0,0,0,0,7,12,9,11,0,0,0,0,2,12,10,1,0,0,8 +0,2,4,7,13,15,2,0,0,14,16,16,15,16,5,0,0,1,4,0,14,14,0,0,0,0,0,0,16,12,0,0,0,0,0,3,16,7,0,0,0,0,0,5,16,7,0,0,0,0,0,5,16,6,0,0,0,0,0,7,16,9,0,0,7 +0,0,2,10,15,5,0,0,0,0,10,15,5,1,0,0,0,1,16,2,0,0,0,0,0,4,14,0,0,0,0,0,0,5,12,2,7,9,2,0,0,1,16,15,13,12,12,0,0,0,15,11,7,15,6,0,0,0,4,13,15,7,0,0,6 +0,0,12,14,1,0,0,0,0,8,16,12,6,0,0,0,0,8,7,7,8,0,0,0,0,0,0,9,7,0,0,0,0,0,0,11,5,0,0,0,0,0,0,14,1,0,0,0,0,0,9,13,4,7,4,0,0,0,15,16,16,16,13,0,2 +0,0,2,12,15,5,0,0,0,0,13,5,5,10,4,0,0,0,14,1,3,15,7,0,0,0,12,10,10,7,0,0,0,0,1,15,11,0,0,0,0,0,4,14,14,0,0,0,0,0,6,10,12,1,0,0,0,0,1,15,11,0,0,0,8 +0,0,3,14,11,0,0,0,0,3,14,9,14,0,0,0,0,8,11,0,4,11,7,0,0,3,15,13,14,11,1,0,0,0,0,14,16,12,1,0,0,0,7,14,2,14,8,0,0,0,8,10,5,14,5,0,0,0,4,15,13,6,0,0,8 +0,0,5,13,8,3,5,0,0,2,16,11,3,2,3,0,0,7,13,4,4,0,0,0,0,5,16,12,14,11,0,0,0,0,0,0,2,14,2,0,0,0,0,0,0,13,3,0,0,0,10,4,6,13,0,0,0,0,6,14,13,3,0,0,5 +0,0,10,16,12,1,0,0,0,9,16,12,16,7,0,0,0,4,7,1,16,4,0,0,0,0,0,14,16,8,0,0,0,0,0,10,14,16,4,0,0,5,7,0,0,14,9,0,0,3,15,5,6,15,7,0,0,0,8,16,16,8,0,0,3 +0,0,0,12,14,1,0,0,0,0,7,16,6,0,0,0,0,1,14,8,0,0,0,0,0,3,16,10,8,2,0,0,0,2,16,16,16,15,4,0,0,1,14,16,6,8,15,0,0,0,5,16,16,13,16,1,0,0,0,9,16,15,7,0,6 +0,1,13,16,15,1,0,0,0,10,15,7,16,4,0,0,0,11,15,7,16,4,0,0,0,1,11,12,15,0,0,0,0,0,1,16,11,0,0,0,0,0,7,16,4,0,0,0,0,0,14,16,5,3,0,0,0,0,13,16,16,16,10,0,2 +0,0,1,13,16,6,0,0,0,6,14,16,16,7,0,0,2,14,16,16,16,2,0,0,0,0,5,16,16,2,0,0,0,0,2,16,16,2,0,0,0,0,0,16,16,1,0,0,0,0,0,14,16,2,0,0,0,0,0,13,16,1,0,0,1 +0,5,14,12,15,15,3,0,0,0,2,4,11,16,5,0,0,0,0,1,15,9,0,0,0,0,0,7,16,1,0,0,0,0,0,3,16,11,0,0,0,0,0,0,15,16,0,0,0,1,7,10,16,7,0,0,0,2,12,13,4,0,0,0,3 +0,0,2,12,13,0,0,0,0,3,15,13,16,0,0,0,0,10,13,1,16,0,0,0,0,0,1,4,16,0,0,0,0,0,0,6,13,0,0,0,0,0,0,9,10,7,8,0,0,0,0,14,15,15,3,0,0,0,0,15,12,3,0,0,2 +0,0,0,9,16,12,2,0,0,0,3,15,10,16,4,0,0,1,14,8,0,13,7,0,0,7,15,1,0,14,6,0,0,4,15,0,0,15,5,0,0,1,12,4,9,15,0,0,0,0,5,16,16,9,0,0,0,0,0,9,14,2,0,0,0 +0,0,0,11,15,4,0,0,0,0,4,16,14,2,0,0,0,0,11,16,3,0,0,0,0,0,14,13,0,0,0,0,0,1,16,12,3,0,0,0,0,2,16,16,16,6,0,0,0,0,11,16,16,15,0,0,0,0,0,8,16,15,1,0,6 +0,0,2,10,13,16,6,0,0,9,16,14,12,9,2,0,0,5,16,3,0,0,0,0,0,0,10,14,6,1,0,0,0,0,1,13,16,11,0,0,0,0,0,0,8,16,0,0,0,0,0,2,15,12,0,0,0,0,0,13,16,2,0,0,5 +0,0,3,11,15,8,0,0,0,0,9,14,8,9,0,0,0,0,6,14,5,0,0,0,0,0,0,12,16,7,0,0,0,0,4,14,11,11,0,0,0,0,12,8,0,16,1,0,0,0,14,11,11,15,1,0,0,0,3,13,13,6,0,0,8 +0,3,13,12,9,12,1,0,0,4,16,16,16,14,1,0,0,4,16,7,3,0,0,0,0,5,16,10,0,0,0,0,0,0,10,16,4,0,0,0,0,0,0,12,12,0,0,0,0,0,6,16,7,0,0,0,0,1,15,12,1,0,0,0,5 +0,0,7,16,16,16,16,3,0,0,2,10,9,9,16,7,0,0,0,0,0,10,15,0,0,0,0,6,12,16,11,0,0,0,0,11,16,16,6,0,0,0,0,9,16,8,0,0,0,0,5,16,10,0,0,0,0,0,11,15,0,0,0,0,7 +0,0,2,12,16,7,0,0,0,0,0,10,16,3,0,0,0,0,0,3,15,5,0,0,0,0,0,0,6,15,3,0,0,0,0,0,0,8,14,0,0,0,0,0,0,11,16,0,0,0,0,3,13,16,9,0,0,0,6,16,12,8,1,0,3 +0,0,0,6,14,15,3,0,0,1,9,16,16,14,2,0,0,6,16,16,16,14,0,0,0,0,6,16,16,8,0,0,0,0,6,16,16,8,0,0,0,0,4,16,16,9,0,0,0,0,1,16,16,15,3,0,0,0,0,10,16,16,4,0,1 +0,0,10,14,14,6,0,0,0,0,8,16,16,11,1,0,0,0,4,16,16,14,2,0,0,0,1,16,16,16,7,0,0,0,0,16,16,16,2,0,0,0,2,14,16,15,3,0,0,0,5,16,16,11,0,0,0,0,8,12,14,6,0,0,1 +0,0,6,16,8,0,0,0,0,2,16,14,13,0,0,0,0,8,14,5,15,0,0,0,0,9,11,8,12,0,0,0,0,3,6,14,8,0,0,0,0,0,4,16,2,0,0,0,0,0,8,16,12,10,5,0,0,0,8,16,16,16,8,0,2 +0,0,0,11,15,10,1,0,0,0,9,10,8,16,5,0,0,0,15,2,0,11,10,0,0,7,10,0,0,9,9,0,0,8,8,0,0,9,8,0,0,8,6,1,5,16,2,0,0,3,14,13,16,14,0,0,0,0,1,9,14,3,0,0,0 +0,0,8,16,9,0,0,0,0,2,15,16,16,4,0,0,0,7,16,4,16,3,0,0,0,9,12,7,14,0,0,0,0,1,4,16,7,0,0,0,0,0,9,16,2,0,0,0,0,0,12,16,15,15,3,0,0,0,7,13,15,14,14,0,2 +0,0,1,7,14,12,0,0,0,0,13,11,12,14,0,0,0,9,16,12,14,14,0,0,0,2,4,2,5,15,0,0,0,0,0,0,3,15,0,0,0,0,0,0,3,16,0,0,0,0,0,0,14,7,0,0,0,0,2,11,11,1,0,0,9 +0,0,2,10,14,3,0,0,0,3,16,16,16,15,1,0,0,12,12,5,16,4,0,0,0,2,15,16,12,0,0,0,0,0,5,16,13,0,0,0,0,0,4,14,14,9,0,0,0,0,7,15,15,12,0,0,0,0,0,12,16,6,0,0,8 +0,0,0,6,11,1,0,0,0,0,5,15,11,1,0,0,0,0,9,12,0,0,0,0,0,0,13,4,0,0,0,0,0,0,14,3,3,0,0,0,0,0,11,16,16,14,4,0,0,0,5,16,11,14,13,0,0,0,0,4,9,14,13,0,6 +0,0,6,11,15,16,13,0,0,0,9,8,4,9,16,0,0,0,0,0,0,10,10,0,0,0,4,8,8,16,4,0,0,0,5,12,16,12,0,0,0,0,0,6,15,1,0,0,0,0,2,16,3,0,0,0,0,0,8,8,0,0,0,0,7 +0,0,11,15,2,0,0,0,0,1,16,16,1,0,0,0,0,6,16,13,6,8,2,0,0,10,16,16,16,16,8,0,0,1,5,10,16,15,0,0,0,0,0,8,15,3,0,0,0,0,2,16,9,0,0,0,0,0,10,16,3,0,0,0,4 +0,0,9,12,14,16,16,2,0,0,4,12,9,12,16,1,0,0,0,0,1,14,8,0,0,0,0,0,8,14,1,0,0,0,0,2,16,12,0,0,0,0,0,0,13,16,5,0,0,0,0,1,13,14,1,0,0,0,10,16,13,1,0,0,3 +0,1,12,14,1,0,0,0,0,10,16,16,4,0,0,0,0,8,11,16,0,0,0,0,0,0,6,13,0,0,0,0,0,0,11,11,0,0,0,0,0,0,16,8,0,0,0,0,0,4,16,15,16,16,9,0,0,1,16,16,16,14,8,0,2 +0,0,4,15,11,1,0,0,0,0,11,14,13,12,0,0,0,3,15,3,1,16,4,0,0,4,14,0,0,12,8,0,0,7,12,0,1,15,7,0,0,5,11,0,5,16,6,0,0,0,12,12,16,10,0,0,0,0,5,12,11,0,0,0,0 +0,0,9,13,10,5,0,0,0,0,4,15,16,13,0,0,0,0,1,14,16,15,0,0,0,0,8,16,16,14,1,0,0,0,6,16,16,14,0,0,0,0,12,16,16,14,0,0,0,0,6,16,16,6,0,0,0,0,4,14,15,3,0,0,1 +0,0,6,15,14,5,0,0,0,0,4,15,16,13,0,0,0,0,3,16,16,16,1,0,0,0,5,16,16,15,2,0,0,0,6,16,16,15,1,0,0,0,6,16,16,16,2,0,0,0,4,16,16,12,0,0,0,0,8,16,16,8,0,0,1 +0,0,5,9,14,16,11,0,0,3,16,16,12,8,3,0,0,7,15,1,0,0,0,0,0,12,13,7,4,0,0,0,0,5,16,16,16,6,0,0,0,0,0,4,16,7,0,0,0,0,0,10,16,2,0,0,0,0,9,15,6,0,0,0,5 +0,0,4,14,11,3,0,0,0,0,14,4,8,3,0,0,0,0,15,1,7,14,10,0,0,0,5,16,12,0,0,0,0,0,3,16,9,0,0,0,0,0,9,7,13,0,0,0,0,0,11,10,13,0,0,0,0,0,5,15,11,0,0,0,8 +0,0,1,16,12,1,0,0,0,0,11,16,16,10,0,0,0,0,13,7,3,12,2,0,0,1,15,1,0,10,6,0,0,2,13,0,0,11,8,0,0,3,15,1,0,13,9,0,0,0,9,10,3,16,11,0,0,0,0,8,12,12,3,0,0 +0,0,5,9,13,8,0,0,0,0,4,16,15,3,0,0,0,0,13,14,2,6,3,0,0,5,16,5,11,16,8,0,0,7,16,16,16,14,1,0,0,3,8,16,16,4,0,0,0,0,5,16,10,0,0,0,0,0,10,12,3,0,0,0,4 +0,0,2,6,12,16,16,2,0,2,15,16,9,5,16,5,0,0,5,1,0,2,16,2,0,0,0,0,0,8,10,0,0,0,0,0,4,16,7,0,0,0,10,13,16,13,1,0,0,0,9,13,16,1,0,0,0,0,0,7,12,0,0,0,7 +0,0,4,11,12,7,0,0,0,1,16,13,14,12,0,0,0,2,16,5,8,14,0,0,0,1,15,16,16,14,0,0,0,0,6,12,12,16,6,0,0,0,1,3,2,15,5,0,0,0,7,16,16,16,2,0,0,0,1,13,12,5,0,0,9 +0,0,2,11,16,16,8,0,0,0,10,16,16,16,8,0,0,0,2,0,8,16,2,0,0,0,0,6,14,16,5,0,0,0,0,14,16,16,10,0,0,0,0,10,16,7,0,0,0,0,0,13,13,0,0,0,0,0,1,16,7,0,0,0,7 +0,0,0,2,14,14,1,0,0,0,0,12,16,16,1,0,0,1,10,16,16,14,0,0,0,6,16,15,16,10,0,0,0,1,3,11,16,7,0,0,0,0,0,7,16,8,0,0,0,0,0,7,16,13,1,0,0,0,0,3,15,15,0,0,1 +0,0,2,14,3,0,0,0,0,0,9,14,0,0,0,0,0,0,11,10,0,0,0,0,0,0,16,7,0,0,0,0,0,2,16,12,8,4,0,0,0,0,16,16,16,16,11,0,0,0,12,10,4,7,14,0,0,0,3,11,16,16,7,0,6 +0,0,3,15,16,14,2,0,0,3,15,13,5,14,4,0,0,4,15,13,10,16,4,0,0,0,7,15,16,16,4,0,0,0,0,0,0,12,4,0,0,0,0,0,0,11,5,0,0,2,13,10,5,12,8,0,0,0,5,13,16,14,5,0,9 +0,0,9,14,8,8,0,0,0,4,16,16,16,16,2,0,0,8,16,4,1,0,0,0,0,1,16,8,0,0,0,0,0,0,7,16,9,0,0,0,0,0,0,3,16,6,0,0,0,0,4,9,15,13,0,0,0,0,14,13,12,5,0,0,5 +0,0,6,14,14,4,0,0,0,0,14,15,6,13,2,0,0,0,7,16,16,15,1,0,0,0,0,10,10,0,0,0,0,0,0,0,14,1,0,0,0,0,0,0,4,11,0,0,0,3,10,4,0,13,0,0,0,0,9,12,16,12,0,0,9 +0,0,7,11,0,0,0,0,0,0,12,10,0,0,0,0,0,1,14,6,0,0,0,0,0,4,16,7,5,2,0,0,0,5,16,16,16,15,2,0,0,2,16,15,13,16,7,0,0,1,16,14,8,16,3,0,0,0,7,15,16,7,0,0,6 +0,0,3,8,10,12,15,0,0,0,8,10,9,15,13,0,0,0,0,0,5,16,5,0,0,0,0,5,13,14,3,0,0,0,4,14,16,16,5,0,0,0,0,11,12,1,0,0,0,0,0,16,7,0,0,0,0,0,2,13,0,0,0,0,7 +0,0,4,12,6,0,0,0,0,0,16,10,16,1,0,0,0,0,16,5,14,13,0,0,0,0,7,16,15,4,0,0,0,0,4,16,9,0,0,0,0,0,13,13,15,4,0,0,0,0,16,8,9,15,2,0,0,0,5,12,12,8,1,0,8 +0,0,10,15,9,2,0,0,0,7,16,9,12,13,0,0,0,8,11,0,0,13,2,0,0,5,12,0,0,8,8,0,0,8,10,0,0,10,6,0,0,5,11,0,0,14,2,0,0,2,16,9,14,12,0,0,0,0,7,14,9,0,0,0,0 +0,0,6,15,14,8,0,0,0,3,13,0,3,14,3,0,0,6,14,0,2,14,4,0,0,0,13,16,16,16,4,0,0,0,0,4,4,13,4,0,0,0,0,0,0,12,5,0,0,0,14,6,0,13,4,0,0,0,6,15,16,9,0,0,9 +0,0,2,16,16,4,0,0,0,0,8,16,16,12,0,0,0,0,14,16,16,0,0,0,0,2,16,16,14,4,0,0,0,8,16,16,10,2,0,0,0,8,16,16,14,2,0,0,0,0,16,16,16,14,2,0,0,0,2,8,16,10,0,0,1 +0,0,7,14,7,0,0,0,0,6,16,16,8,0,0,0,0,3,7,13,10,0,0,0,0,0,0,13,8,0,0,0,0,0,6,15,4,0,0,0,0,0,12,11,0,0,0,0,0,0,14,15,12,16,6,0,0,0,6,14,11,6,1,0,2 +0,0,6,16,14,0,0,0,0,1,11,16,16,4,0,0,0,0,12,16,16,4,0,0,0,0,15,16,16,1,0,0,0,0,16,16,16,0,0,0,0,4,16,16,15,3,0,0,0,0,9,16,16,13,0,0,0,0,4,14,16,6,0,0,1 +0,0,2,13,16,9,0,0,0,0,10,16,16,16,2,0,0,4,16,9,1,15,4,0,0,4,16,3,0,12,5,0,0,5,16,1,0,12,8,0,0,1,16,9,0,12,8,0,0,0,10,16,12,15,3,0,0,0,2,13,16,9,0,0,0 +0,0,14,10,10,4,0,0,0,0,16,8,8,15,4,0,0,0,13,5,0,7,8,0,0,0,6,14,8,15,7,0,0,0,8,16,16,9,0,0,0,2,15,9,15,6,0,0,0,5,15,8,14,10,0,0,0,0,8,10,16,7,0,0,8 +0,0,1,10,2,0,0,0,0,0,3,16,5,0,0,0,0,0,8,15,0,0,0,0,0,0,12,8,0,0,0,0,0,0,13,9,3,0,0,0,0,2,16,16,16,16,7,0,0,0,14,8,2,8,16,2,0,0,2,15,16,16,13,1,6 +0,0,0,5,8,3,0,0,0,0,15,16,16,16,0,0,0,0,16,16,16,13,0,0,0,0,16,16,16,5,0,0,0,0,16,16,16,9,0,0,0,0,16,16,16,4,0,0,0,0,15,16,16,10,0,0,0,0,2,8,8,7,0,0,1 +0,0,0,9,10,0,0,0,0,0,2,16,4,0,0,0,0,0,11,12,0,0,0,0,0,3,16,4,0,6,1,0,0,9,16,3,9,16,4,0,0,9,16,16,16,10,0,0,0,0,4,9,15,2,0,0,0,0,0,8,13,0,0,0,4 +0,0,0,2,15,4,0,0,0,0,0,7,16,5,0,0,0,0,8,16,14,1,0,0,0,7,16,16,12,0,0,0,0,2,8,14,12,0,0,0,0,0,0,9,13,0,0,0,0,0,0,6,16,13,2,0,0,0,0,2,12,13,2,0,1 +0,0,6,16,16,16,8,0,0,6,16,16,15,11,5,0,0,12,13,3,0,0,0,0,0,9,15,3,0,0,0,0,0,3,15,15,2,0,0,0,0,0,5,15,7,0,0,0,0,0,0,14,6,0,0,0,0,0,6,15,1,0,0,0,5 +0,0,5,14,16,14,0,0,0,0,10,13,13,16,1,0,0,0,0,0,12,14,0,0,0,0,2,9,16,16,11,0,0,0,8,16,16,11,3,0,0,0,0,15,13,0,0,0,0,0,3,16,6,0,0,0,0,0,8,16,2,0,0,0,7 +0,0,0,9,14,1,0,0,0,0,7,16,6,0,0,0,0,4,16,10,0,0,0,0,0,8,16,3,0,6,4,0,0,4,16,16,16,16,8,0,0,0,5,8,16,12,0,0,0,0,0,6,16,2,0,0,0,0,0,8,15,2,0,0,4 +0,0,2,15,0,0,0,0,0,0,8,14,0,0,0,0,0,0,12,8,0,0,0,0,0,2,16,5,0,0,0,0,0,6,16,8,8,1,0,0,0,8,16,16,16,14,0,0,0,3,15,10,6,16,6,0,0,0,4,11,16,14,1,0,6 +0,0,6,12,7,0,0,0,0,2,15,13,15,13,0,0,0,8,13,0,9,15,0,0,0,1,15,7,14,11,0,0,0,0,6,16,16,1,0,0,0,0,5,16,16,14,0,0,0,0,11,16,16,16,4,0,0,0,3,7,12,12,3,0,8 +0,3,12,15,9,1,0,0,0,3,16,8,13,10,0,0,0,0,11,12,12,12,0,0,0,0,3,15,16,12,0,0,0,0,0,0,6,14,1,0,0,0,0,0,0,15,6,0,0,7,14,7,4,15,7,0,0,3,9,12,15,9,0,0,9 +0,0,0,4,14,0,0,0,0,0,0,13,9,0,0,0,0,0,2,16,2,0,0,0,0,1,12,11,1,6,2,0,0,9,16,16,16,16,2,0,0,0,7,9,16,8,0,0,0,0,0,6,16,5,0,0,0,0,0,7,13,1,0,0,4 +0,0,8,13,15,10,3,0,0,0,13,15,12,12,9,0,0,3,14,7,0,0,0,0,0,7,8,4,2,0,0,0,0,2,16,16,15,0,0,0,0,0,1,5,13,0,0,0,0,0,0,10,6,0,0,0,0,0,6,13,1,0,0,0,5 +0,0,0,10,9,0,0,0,0,0,1,15,7,0,0,0,0,0,11,13,0,0,0,0,0,1,16,7,0,0,0,0,0,7,16,6,7,15,3,0,0,6,16,16,16,15,2,0,0,0,0,8,16,4,0,0,0,0,0,9,12,0,0,0,4 +0,0,6,12,4,0,0,0,0,0,12,16,15,1,0,0,0,2,16,6,5,14,0,0,0,9,16,4,0,14,3,0,0,7,16,6,0,14,6,0,0,4,16,15,2,16,6,0,0,0,12,16,16,11,0,0,0,0,5,14,14,5,0,0,0 +0,0,7,15,10,0,0,0,0,0,12,10,15,3,0,0,0,0,0,0,12,11,0,0,0,0,0,0,14,9,0,0,0,1,5,8,16,12,2,0,0,10,16,16,15,10,3,0,0,0,2,16,4,0,0,0,0,0,8,12,0,0,0,0,7 +0,0,1,12,6,0,0,0,0,0,5,15,2,3,8,0,0,1,13,11,1,14,9,0,0,6,15,3,6,15,0,0,0,12,15,12,15,15,6,0,0,6,12,14,16,16,5,0,0,0,0,10,14,0,0,0,0,0,0,14,6,0,0,0,4 +0,6,16,6,0,0,0,0,0,9,16,16,2,0,0,0,0,1,2,16,7,0,0,0,0,0,2,16,3,0,0,0,0,0,10,15,0,0,0,0,0,4,16,6,0,0,0,0,0,10,16,12,14,16,9,0,0,7,16,16,12,10,3,0,2 +0,0,0,9,13,5,0,0,0,0,7,16,15,1,0,0,0,0,7,16,8,0,0,0,0,0,11,16,9,0,0,0,0,0,9,16,6,0,0,0,0,0,10,16,5,0,0,0,0,0,11,16,11,0,0,0,0,0,1,12,10,0,0,0,1 +0,0,4,14,9,0,0,0,0,2,15,16,16,0,0,0,0,5,11,7,14,0,0,0,0,0,0,8,11,0,0,0,0,0,0,11,5,0,0,0,0,0,4,15,0,4,0,0,0,0,8,15,12,16,2,0,0,0,7,16,12,7,0,0,2 +0,0,0,3,15,4,0,0,0,0,2,14,13,1,6,1,0,1,13,13,0,5,16,3,0,5,16,3,0,11,13,0,0,12,16,12,13,16,2,0,0,6,12,13,16,14,1,0,0,0,0,1,16,5,0,0,0,0,0,4,15,0,0,0,4 +0,0,7,16,14,2,0,0,0,6,14,8,16,9,0,0,0,1,2,2,16,8,0,0,0,0,0,12,16,5,0,0,0,0,0,5,13,16,3,0,0,0,0,0,1,16,8,0,0,0,1,8,14,14,1,0,0,0,7,15,6,1,0,0,3 +0,0,0,6,16,3,0,0,0,0,2,15,10,11,5,0,0,0,13,9,4,16,3,0,0,4,15,4,9,16,3,0,0,11,15,12,16,16,10,0,0,5,15,14,16,9,0,0,0,0,0,7,13,1,0,0,0,0,0,9,9,0,0,0,4 +0,0,4,14,16,10,0,0,0,0,13,8,11,15,0,0,0,0,1,0,8,11,0,0,0,0,0,0,13,7,0,0,0,0,4,8,16,9,1,0,0,0,7,16,15,10,1,0,0,0,3,15,1,0,0,0,0,0,6,14,0,0,0,0,7 +0,0,0,3,15,4,0,0,0,0,0,12,11,3,5,0,0,0,9,14,2,13,11,0,0,3,16,5,3,16,3,0,0,11,15,6,12,16,3,0,0,11,16,16,16,15,4,0,0,1,5,6,16,2,0,0,0,0,0,5,16,5,0,0,4 +0,0,2,11,14,5,0,0,0,0,0,10,16,16,0,0,0,0,0,10,16,16,4,0,0,0,0,12,16,15,1,0,0,0,1,14,16,13,0,0,0,0,1,16,16,9,0,0,0,0,11,16,16,5,0,0,0,0,6,13,14,0,0,0,1 +0,0,8,14,16,5,0,0,0,2,16,8,10,8,0,0,0,0,4,2,13,6,0,0,0,0,0,7,12,0,0,0,0,0,1,15,7,0,0,0,0,0,9,11,0,0,0,0,0,4,16,9,8,12,2,0,0,0,10,14,9,2,0,0,2 +0,0,0,0,11,15,2,0,0,0,0,9,16,16,3,0,0,1,10,16,16,12,0,0,0,9,16,16,16,13,0,0,0,11,14,8,16,9,0,0,0,0,0,0,16,12,0,0,0,0,0,0,15,13,0,0,0,0,0,0,12,10,0,0,1 +0,0,0,5,12,16,7,0,0,0,9,15,4,13,12,0,0,2,16,16,15,16,13,0,0,3,16,16,14,16,7,0,0,0,6,7,1,15,3,0,0,0,0,0,9,11,0,0,0,0,0,0,13,5,0,0,0,0,0,6,13,0,0,0,9 +0,0,0,12,15,5,0,0,0,0,3,13,9,16,0,0,0,12,14,10,1,14,4,0,0,3,12,13,11,13,0,0,0,0,2,10,16,9,0,0,0,0,0,5,16,8,0,0,0,0,0,13,16,9,0,0,0,0,0,12,12,2,0,0,8 +0,0,0,5,16,3,0,0,0,0,1,16,10,1,14,4,0,0,11,12,1,9,14,2,0,6,15,3,4,16,5,0,0,13,15,14,16,16,8,0,0,12,13,14,16,9,0,0,0,0,0,5,16,2,0,0,0,0,0,7,11,0,0,0,4 +0,0,0,14,10,1,0,0,0,0,8,16,16,11,0,0,0,0,10,10,2,15,1,0,0,4,12,6,0,11,4,0,0,6,16,10,0,9,8,0,0,0,14,16,5,16,9,0,0,0,8,16,16,16,4,0,0,0,0,12,16,9,0,0,0 +0,0,6,14,15,1,0,0,0,0,14,16,16,8,0,0,0,0,4,2,10,10,0,0,0,0,0,0,10,8,0,0,0,0,7,16,16,16,4,0,0,0,9,13,15,8,1,0,0,0,0,12,9,0,0,0,0,0,5,13,0,0,0,0,7 +0,0,2,9,5,0,0,0,0,0,8,16,16,13,0,0,0,2,11,16,16,4,0,0,0,1,11,16,12,0,0,0,0,0,10,12,16,6,0,0,0,0,13,1,7,15,1,0,0,0,14,6,7,13,2,0,0,0,2,11,11,4,0,0,8 +0,0,0,5,14,16,7,0,0,0,6,16,8,12,13,3,0,4,16,14,10,16,16,3,0,1,15,16,16,16,12,0,0,0,3,5,6,16,3,0,0,0,0,0,10,10,0,0,0,0,0,2,15,5,0,0,0,0,0,8,14,0,0,0,9 +0,4,16,13,6,0,0,0,0,1,9,15,16,8,0,0,0,0,0,1,12,16,3,0,0,0,0,1,15,13,1,0,0,0,11,13,16,14,5,0,0,3,16,16,16,14,5,0,0,1,14,13,1,0,0,0,0,3,16,7,0,0,0,0,7 +0,1,12,16,5,0,0,0,0,11,16,16,11,0,0,0,0,6,5,16,11,0,0,0,0,0,2,16,7,0,0,0,0,0,8,16,3,0,0,0,0,1,13,14,0,2,1,0,0,4,16,16,16,16,10,0,0,0,15,16,14,9,3,0,2 +0,0,0,0,13,15,3,0,0,0,0,9,16,16,7,0,0,1,7,16,16,16,1,0,0,6,16,15,14,16,2,0,0,5,11,2,16,15,0,0,0,0,0,0,14,14,0,0,0,0,0,0,16,12,0,0,0,0,0,0,11,13,0,0,1 +0,0,1,8,14,16,12,0,0,0,10,15,13,16,12,0,0,1,12,3,9,15,1,0,0,0,0,8,16,8,0,0,0,0,0,8,16,16,4,0,0,0,0,1,8,16,6,0,0,0,0,5,15,12,1,0,0,0,0,13,10,1,0,0,3 +0,0,3,10,14,14,0,0,0,10,16,14,10,15,0,0,0,0,8,0,0,0,0,0,0,6,16,8,1,0,0,0,0,2,11,15,15,5,0,0,0,0,0,0,15,15,0,0,0,0,0,12,13,1,0,0,0,0,0,11,4,0,0,0,5 +0,0,0,14,2,0,0,0,0,0,2,16,2,0,0,0,0,0,7,13,0,0,0,0,0,0,8,12,0,0,0,0,0,0,9,11,4,2,0,0,0,0,11,16,16,15,8,0,0,0,13,15,2,2,16,3,0,0,0,11,13,12,10,0,6 +0,0,2,11,16,10,0,0,0,0,14,12,8,12,9,0,0,4,16,5,2,14,9,0,0,4,16,16,15,10,2,0,0,0,3,16,15,0,0,0,0,0,8,15,14,0,0,0,0,0,9,16,16,4,0,0,0,0,1,14,11,2,0,0,8 +0,0,4,13,4,0,0,0,0,0,12,15,6,0,0,0,0,0,15,10,0,0,0,0,0,0,16,10,4,1,0,0,0,5,16,16,16,15,1,0,0,5,16,16,3,13,8,0,0,1,16,16,14,16,9,0,0,0,5,14,13,5,1,0,6 +0,0,9,16,16,16,3,0,0,1,14,15,10,7,1,0,0,7,16,2,0,0,0,0,0,2,16,8,0,0,0,0,0,0,2,13,14,0,0,0,0,0,0,4,16,7,0,0,0,0,5,14,13,0,0,0,0,0,11,10,0,0,0,0,5 +0,0,0,3,10,1,0,0,0,0,3,15,9,0,4,0,0,0,12,12,0,4,11,0,0,2,14,3,0,13,6,0,0,7,14,8,9,16,2,0,0,5,12,12,14,15,1,0,0,0,0,0,11,6,0,0,0,0,0,0,13,3,0,0,4 +0,0,3,13,14,2,0,0,0,0,7,9,14,12,0,0,0,0,0,0,7,11,0,0,0,0,0,0,9,7,0,0,0,2,13,16,16,13,3,0,0,2,16,16,14,7,1,0,0,0,0,16,8,0,0,0,0,0,2,13,2,0,0,0,7 +0,0,8,13,11,1,0,0,0,0,16,12,15,8,0,0,0,3,13,0,5,15,2,0,0,6,9,0,1,15,5,0,0,8,8,0,0,12,6,0,0,5,10,0,1,14,3,0,0,2,16,8,12,13,0,0,0,0,8,16,8,1,0,0,0 +0,0,6,14,6,0,0,0,0,0,15,6,12,3,0,0,0,5,12,0,3,14,3,0,0,4,12,0,0,11,8,0,0,4,12,0,0,9,8,0,0,5,15,0,0,8,9,0,0,1,15,2,4,13,2,0,0,0,5,15,15,5,0,0,0 +0,0,15,15,16,14,1,0,0,3,16,15,8,7,0,0,0,8,16,9,1,0,0,0,0,9,16,16,12,0,0,0,0,1,2,0,16,4,0,0,0,0,0,0,13,8,0,0,0,2,7,7,16,4,0,0,0,0,15,16,12,0,0,0,5 +0,0,2,13,16,15,4,0,0,0,9,16,16,16,6,0,0,0,0,0,3,16,7,0,0,0,0,0,4,16,1,0,0,0,2,11,14,16,4,0,0,0,10,16,16,15,2,0,0,0,3,14,11,0,0,0,0,0,3,14,1,0,0,0,7 +0,0,12,16,7,0,0,0,0,2,16,14,16,5,0,0,0,0,14,5,11,13,0,0,0,0,2,1,8,13,0,0,0,0,0,0,8,15,0,0,0,0,0,0,13,10,0,0,0,0,10,10,16,6,0,0,0,0,11,16,16,16,16,8,2 +0,0,0,3,13,0,0,0,0,0,0,12,8,0,0,0,0,0,5,14,2,0,0,0,0,1,15,4,0,3,8,0,0,13,15,12,9,13,10,0,0,5,8,8,15,14,2,0,0,0,0,2,14,5,0,0,0,0,0,6,12,0,0,0,4 +0,0,0,12,8,0,0,0,0,0,6,15,1,0,0,0,0,2,15,6,0,0,6,0,0,8,14,1,0,7,15,1,0,13,10,0,6,16,6,0,0,15,14,14,16,12,0,0,0,7,12,12,16,2,0,0,0,0,0,14,6,0,0,0,4 +0,0,0,9,12,0,0,0,0,0,2,16,6,0,0,0,0,0,13,14,0,0,7,1,0,2,16,9,0,9,16,1,0,6,16,11,11,16,6,0,0,9,16,16,16,12,0,0,0,1,4,9,16,4,0,0,0,0,0,10,11,0,0,0,4 +0,0,5,16,13,1,0,0,0,0,12,14,15,5,0,0,0,0,12,10,16,7,0,0,0,0,3,15,16,12,0,0,0,0,0,0,2,15,2,0,0,0,0,0,0,8,9,0,0,2,12,8,8,13,12,0,0,1,6,10,12,12,2,0,9 +0,0,6,16,8,0,0,0,0,2,16,14,15,0,0,0,0,2,16,13,16,9,0,0,0,0,8,15,16,14,1,0,0,0,0,0,0,13,7,0,0,0,0,0,0,3,13,0,0,0,3,4,4,7,15,0,0,0,7,13,16,13,9,0,9 +0,0,5,16,11,0,0,0,0,1,16,5,12,11,3,0,0,3,13,0,5,16,2,0,0,4,12,0,0,16,4,0,0,5,11,0,1,16,3,0,0,5,13,0,0,12,1,0,0,0,14,7,6,12,0,0,0,0,4,14,12,2,0,0,0 +0,1,12,16,14,4,0,0,0,8,11,0,10,8,0,0,0,1,1,4,14,4,0,0,0,0,2,16,11,0,0,0,0,0,0,3,13,11,0,0,0,0,0,0,1,11,7,0,0,0,10,0,1,10,8,0,0,0,13,15,15,10,2,0,3 +0,0,3,14,13,3,0,0,0,0,12,10,7,12,0,0,0,3,15,1,0,15,7,0,0,5,12,0,0,12,5,0,0,6,12,0,0,9,4,0,0,4,13,0,0,10,4,0,0,1,14,6,5,14,2,0,0,0,3,13,13,6,0,0,0 +0,0,12,16,16,10,1,0,0,0,13,7,4,16,7,0,0,0,0,5,12,15,2,0,0,0,0,7,16,12,0,0,0,0,0,0,7,16,5,0,0,1,3,0,0,9,15,0,0,6,12,4,2,11,16,0,0,1,10,16,16,16,7,0,3 +0,0,0,8,11,0,0,0,0,0,1,15,4,0,0,0,0,0,12,9,0,0,3,0,0,2,16,2,0,9,14,0,0,11,13,0,7,15,3,0,0,13,16,12,16,8,0,0,0,6,12,12,14,1,0,0,0,0,0,7,12,0,0,0,4 +0,1,9,16,14,6,0,0,0,5,14,1,6,15,3,0,0,3,14,2,5,14,4,0,0,0,9,14,15,3,0,0,0,0,4,16,5,0,0,0,0,0,11,13,9,0,0,0,0,0,15,4,13,0,0,0,0,0,11,16,7,0,0,0,8 +0,0,4,14,15,3,0,0,0,0,12,10,5,13,1,0,0,3,13,0,0,16,4,0,0,4,11,0,0,13,6,0,0,5,10,0,0,12,4,0,0,2,13,0,0,9,6,0,0,0,14,3,4,12,0,0,0,0,3,14,14,6,0,0,0 +0,2,15,16,15,1,0,0,0,3,9,5,14,7,0,0,0,0,0,3,15,5,0,0,0,0,6,16,8,0,0,0,0,0,6,13,16,10,0,0,0,0,0,0,6,16,1,0,0,0,0,0,2,14,7,0,0,0,14,13,16,11,1,0,3 +0,0,5,15,16,6,0,0,0,0,15,12,5,16,2,0,0,4,16,3,0,12,8,0,0,8,14,0,0,10,10,0,0,8,13,0,0,10,13,0,0,4,16,0,0,4,15,0,0,1,15,9,3,12,10,0,0,0,5,15,16,15,1,0,0 +0,0,4,16,11,1,0,0,0,0,5,16,16,7,0,0,0,0,0,14,16,10,0,0,0,0,2,16,16,6,0,0,0,0,3,16,16,9,0,0,0,0,1,16,16,11,0,0,0,0,5,16,16,5,0,0,0,0,2,12,16,11,1,0,1 +0,0,0,0,7,11,1,0,0,0,0,0,12,16,3,0,0,0,0,4,16,16,0,0,0,0,0,2,16,16,0,0,0,0,1,13,16,12,0,0,0,0,7,16,15,14,0,0,0,2,15,5,12,11,0,0,0,0,2,0,7,11,0,0,1 +0,0,0,6,13,0,0,0,0,0,3,15,5,0,0,0,0,0,12,9,0,0,4,5,0,5,16,2,0,3,16,2,0,9,14,2,9,15,8,0,0,7,16,15,13,11,1,0,0,0,2,1,14,2,0,0,0,0,0,6,9,0,0,0,4 +0,0,0,6,8,0,0,0,0,0,2,14,3,0,0,0,0,0,9,8,0,0,0,0,0,4,15,1,0,10,5,0,0,7,13,0,2,16,3,0,0,11,14,8,15,9,0,0,0,12,16,14,15,0,0,0,0,1,4,9,8,0,0,0,4 +0,0,9,16,6,0,0,0,0,2,14,16,16,8,3,0,0,0,14,16,16,16,11,0,0,0,14,16,12,5,1,0,0,0,9,16,5,0,0,0,0,0,15,16,5,0,0,0,0,0,16,16,8,0,0,0,0,0,10,13,1,0,0,0,8 +0,0,9,16,12,1,0,0,0,3,16,10,15,9,0,0,0,3,16,4,11,16,1,0,0,0,12,16,16,15,7,0,0,0,0,3,2,6,14,0,0,0,6,0,0,3,16,1,0,1,16,7,4,9,16,0,0,0,8,16,16,16,8,0,9 +0,1,10,16,16,4,0,0,0,8,13,12,16,10,0,0,0,2,0,14,15,3,0,0,0,0,0,11,16,8,0,0,0,0,0,0,7,16,3,0,0,2,1,0,0,13,8,0,0,8,12,4,10,16,5,0,0,2,13,16,12,5,0,0,3 +0,0,6,16,11,0,0,0,0,1,16,2,12,9,0,0,0,4,15,5,13,10,0,0,0,0,9,10,9,15,0,0,0,0,0,0,0,11,5,0,0,0,0,0,0,7,7,0,0,3,9,1,0,8,4,0,0,1,7,14,16,12,1,0,9 +0,0,5,13,11,2,0,0,0,2,15,6,5,12,0,0,0,6,12,0,1,16,2,0,0,1,12,5,5,16,6,0,0,0,1,9,9,12,8,0,0,0,0,0,0,3,13,0,0,0,0,0,1,8,10,0,0,0,8,13,15,10,1,0,9 +0,0,0,1,12,1,0,0,0,0,0,14,10,0,0,0,0,0,10,14,2,0,0,0,0,2,16,7,0,5,6,0,0,12,15,0,9,15,1,0,0,12,16,14,16,8,0,0,0,7,12,10,15,1,0,0,0,0,0,4,9,0,0,0,4 +0,0,3,15,0,0,0,0,0,0,11,14,0,0,0,0,0,0,13,8,0,0,0,0,0,0,16,8,4,0,0,0,0,1,16,16,16,15,2,0,0,6,16,14,11,15,7,0,0,0,15,15,9,15,4,0,0,0,4,14,16,9,0,0,6 +0,0,6,16,2,0,0,0,0,0,15,10,0,0,0,0,0,6,16,3,0,0,0,0,0,9,14,0,0,0,0,0,0,12,13,11,12,12,3,0,0,7,16,15,12,13,13,0,0,2,15,12,2,8,15,0,0,0,5,16,16,16,5,0,6 +0,0,2,15,16,13,1,0,0,0,3,7,10,16,10,0,0,0,0,0,0,11,11,0,0,0,0,2,8,15,5,0,0,0,0,9,16,16,8,0,0,0,0,2,16,5,0,0,0,0,0,12,7,0,0,0,0,0,4,14,1,0,0,0,7 diff --git a/demonstrations_v2/tutorial_quantum_gans/requirements.in b/demonstrations_v2/tutorial_quantum_gans/requirements.in new file mode 100644 index 0000000000..f2ca2d2c1e --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_gans/requirements.in @@ -0,0 +1,6 @@ +matplotlib +numpy +pandas +pennylane +torch +torchvision diff --git a/demonstrations_v2/tutorial_quantum_metrology/demo.py b/demonstrations_v2/tutorial_quantum_metrology/demo.py new file mode 100644 index 0000000000..9dbe56c758 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_metrology/demo.py @@ -0,0 +1,359 @@ +r""" +Variationally optimizing measurement protocols +============================================== + +.. meta:: + :property="og:description": Using a variational quantum algorithm to + optimize a quantum sensing protocol. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/illustration1.png + +.. related:: + + tutorial_noisy_circuit_optimization Optimizing noisy circuits with Cirq + +*Author: Johannes Jakob Meyer — Posted: 18 June 2020. Last updated: 18 November 2021.* + +In this tutorial we use the variational quantum algorithm from +Ref. [#meyer2020]_ to optimize a quantum +sensing protocol. + +Background +---------- + +Quantum technologies are a rapidly expanding field with applications ranging from quantum computers +to quantum communication lines. In this tutorial, we study a particular application of quantum technologies, +namely *Quantum Metrology*. It exploits quantum effects to enhance the precision of measurements. One of the +most impressive examples of a successful application of quantum metrology is gravitational wave interferometers +like `LIGO `_ that harness non-classical light to increase the sensitivity +to passing gravitational waves. + +A quantum metrological experiment, which we call a *protocol*, can be modelled in the following way. +As a first step, a quantum state :math:`\rho_0` is prepared. This state then undergoes a possibly noisy quantum +evolution that depends on a vector of parameters :math:`\boldsymbol{\phi}` we are interested in—we say the quantum +evolution *encodes* the parameters. The values :math:`\boldsymbol{\phi}` can for example be a set of phases that are +picked up in an interferometer. As we use the quantum state to *probe* the encoding evolution, we will call it the *probe state*. + +After the parameters are encoded, we have a new state :math:`\rho(\boldsymbol{\phi})` which we then need to measure. +We can describe any possible measurement in quantum mechanics using a `positive +operator-valued measurement `_ consisting of a set of operators :math:`\{ \Pi_l \}.` +Measuring those operators gives us the output probabilities + +.. math:: p_l(\boldsymbol{\phi}) = \langle \Pi_l \rangle = + \operatorname{Tr}(\rho(\boldsymbol{\phi}) \Pi_l). + +As the last step of our protocol, we have to estimate the parameters :math:`\boldsymbol{\phi}` from these probabilities, +e.g., through `maximum likelihood estimation `_. +Intuitively, we will get the best precision in doing so if the probe state is most "susceptible" to the +encoding evolution and the corresponding measurement can distinguish the states for different values of :math:`\boldsymbol{\phi}` well. + +The variational algorithm +------------------------- + +We now introduce a variational algorithm to optimize such a sensing protocol. As a first step, we parametrize +both the probe state :math:`\rho_0 = \rho_0(\boldsymbol{\theta})` and the POVM :math:`\Pi_l = \Pi_l(\boldsymbol{\mu})` +using suitable quantum circuits with parameters :math:`\boldsymbol{\theta}` and :math:`\boldsymbol{\mu}` respectively. +The parameters should now be adjusted in a way that improves the sensing protocol, and to quantify this, we need a +suitable *cost function*. + +Luckily, there exists a mathematical tool to quantify the best achievable estimation precision, the *Cramér-Rao bound*. +Any estimator :math:`\mathbb{E}(\hat{\boldsymbol{\varphi}}) = \boldsymbol{\phi}` we could construct fulfills the following +condition on its covariance matrix which gives a measure of the precision of the estimation: + +.. math:: \operatorname{Cov}(\hat{\boldsymbol{\varphi}}) \geq \frac{1}{n} I^{-1}_{\boldsymbol{\phi}}, + +where :math:`n` is the number of samples and :math:`I_{\boldsymbol{\phi}}` is the *Classical Fisher Information Matrix* +with respect to the entries of :math:`\boldsymbol{\phi}.` It is defined as + +.. math:: [I_{\boldsymbol{\phi}}]_{jk} := \sum_l \frac{(\partial_j p_l)(\partial_k p_l)}{p_l}, + +where we used :math:`\partial_j` as a shorthand notation for :math:`\frac{\partial}{\partial \phi_j}.` The Cramér-Rao +bound has the very powerful property that it can always be saturated in the limit of many samples! This means we are +guaranteed that we can construct a "best estimator" for the vector of parameters. + +This in turn means that the right hand side of the Cramér-Rao bound would make for a great cost function. There is only +one remaining problem, namely that it is matrix-valued, but we need a scalar cost function. To obtain such a scalar +quantity, we multiply both sides of the inequality with a positive-semidefinite weighting matrix :math:`W` and then perform +a trace, + +.. math:: \operatorname{Tr}(W\operatorname{Cov}(\hat{\boldsymbol{\varphi}})) \geq \frac{1}{n} \operatorname{Tr}(W I^{-1}_{\boldsymbol{\phi}}). + +As its name suggests, :math:`W` can be used to weight the importance of the different entries of :math:`\boldsymbol{\phi}.` +The right-hand side is now a scalar quantifying the best attainable weighted precision and can be readily used as a cost function: + +.. math:: C_W(\boldsymbol{\theta}, \boldsymbol{\mu}) = \operatorname{Tr}(W I^{-1}_{\boldsymbol{\phi}}(\boldsymbol{\theta}, \boldsymbol{\mu})). + +With the cost function in place, we can use Pennylane to optimize the variational parameters :math:`\boldsymbol{\theta}` and +:math:`\boldsymbol{\mu}` to obtain a good sensing protocol. The whole pipeline is depicted below: + +.. figure:: ../_static/demonstration_assets/quantum_metrology/illustration.png + :align: center + :width: 50% + :target: javascript:void(0) + +Here, the encoding process is modeled as a unitary evolution :math:`U(\boldsymbol{\phi})` followed by +a parameter-independent noise channel :math:`\mathcal{N}.` + +Ramsey spectroscopy +------------------- + +In this demonstration, we will study Ramsey spectroscopy, a widely used technique for quantum metrology with atoms and ions. +The encoded parameters are phase shifts :math:`\boldsymbol{\phi}` arising from the interaction of probe ions +modeled as two-level systems with an external driving force. We represent the noise in the parameter encoding using a phase damping +channel (also known as dephasing channel) with damping constant :math:`\gamma.` +We consider a pure probe state on three qubits and a projective measurement, where +the computational basis is parametrized by local unitaries. + +The above method is actually not limited to the estimation of the parameters :math:`\boldsymbol{\phi},` but +can also be used to optimize estimators for functions of those parameters! To add this interesting aspect +to the tutorial, we will seek an optimal protocol for the estimation of the *Fourier amplitudes* of the phases: + +.. math:: f_j(\boldsymbol{\boldsymbol{\phi}}) = \left|\sum_k \phi_k \mathrm{e}^{-i j k \frac{2\pi}{N}}\right|^2. + +For three phases, there are two independent amplitudes :math:`f_0` and :math:`f_1.` To include the effect of the function, +we need to replace the classical Fisher information matrix with respect to :math:`\boldsymbol{\phi}` with the Fisher information +matrix with respect to the entries :math:`f_0` and :math:`f_1.` +To this end we can make use of the following identity which relates the two matrices: + +.. math:: I_{\boldsymbol{f}} = J^T I_{\boldsymbol{\phi}} J, + +where :math:`J_{kl} = \frac{\partial f_k}{\partial \phi_l}` is the Jacobian of :math:`\boldsymbol{f}.` + +We now turn to the actual implementation of the scheme. +""" + +import pennylane as qml +from pennylane import numpy as np + + +############################################################################## +# Modeling the sensing process +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We will first specify the device to carry out the simulations. As we want to +# model a noisy system, it needs to be capable of mixed-state simulations. +# We will choose the ``cirq.mixedsimulator`` device from the +# `Pennylane-Cirq `_ +# plugin for this tutorial. +dev = qml.device("cirq.mixedsimulator", wires=3, shots=1000) + +############################################################################## +# Next, we model the parameter encoding. The phase shifts are recreated using +# the Pauli Z rotation gate. The phase-damping noise channel is available as +# a custom Cirq gate. +from pennylane_cirq import ops as cirq_ops + + +def encoding(phi, gamma): + for i in range(3): + qml.RZ(phi[i], wires=[i]) + cirq_ops.PhaseDamp(gamma, wires=[i]) + + +############################################################################## +# We now choose a parametrization for both the probe state and the POVM. +# To be able to parametrize all possible probe states and all local measurements, +# we make use of the +# `ArbitraryStatePreparation `_ +# template from PennyLane. + + +def ansatz(weights): + qml.ArbitraryStatePreparation(weights, wires=[0, 1, 2]) + + +NUM_ANSATZ_PARAMETERS = 14 + + +def measurement(weights): + for i in range(3): + qml.ArbitraryStatePreparation(weights[2 * i : 2 * (i + 1)], wires=[i]) + + +NUM_MEASUREMENT_PARAMETERS = 6 + + +############################################################################## +# We now have everything at hand to model the quantum part of our experiment +# as a QNode. We will return the output probabilities necessary to compute the +# Classical Fisher Information Matrix. +@qml.qnode(dev) +def experiment(weights, phi, gamma=0.0): + ansatz(weights[:NUM_ANSATZ_PARAMETERS]) + encoding(phi, gamma) + measurement(weights[NUM_ANSATZ_PARAMETERS:]) + + return qml.probs(wires=[0, 1, 2]) + + +# Draw the circuit at the given parameter values +print( + qml.draw(experiment, level="device", max_length=80)( + np.arange(NUM_ANSATZ_PARAMETERS + NUM_MEASUREMENT_PARAMETERS), + np.zeros(3), + gamma=0.2, + ) +) + + +############################################################################## +# Evaluating the cost function +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now, let's turn to the cost function itself. The most important ingredient +# is the Classical Fisher Information Matrix, which we compute using a separate +# function that uses the explicit `parameter-shift rule `_ +# to enable differentiation. +def CFIM(weights, phi, gamma): + p = experiment(weights, phi, gamma=gamma) + dp = [] + + for idx in range(3): + # We use the parameter-shift rule explicitly + # to compute the derivatives + shift = np.zeros_like(phi) + shift[idx] = np.pi / 2 + + plus = experiment(weights, phi + shift, gamma=gamma) + minus = experiment(weights, phi - shift, gamma=gamma) + + dp.append(0.5 * (plus - minus)) + + matrix = [0] * 9 + for i in range(3): + for j in range(3): + matrix[3 * i + j] = np.sum(dp[i] * dp[j] / p) + + return np.array(matrix).reshape((3, 3)) + + +############################################################################## +# As the cost function contains an inversion, we add a small regularization +# to it to avoid inverting a singular matrix. As additional parameters, we add +# the weighting matrix :math:`W` and the Jacobian :math:`J.` +def cost(weights, phi, gamma, J, W, epsilon=1e-10): + return np.trace(W @ np.linalg.inv(J.T @ CFIM(weights, phi, gamma) @ J + np.eye(2) * epsilon)) + + +############################################################################## +# To compute the Jacobian, we make use of `sympy `_. +# The two independent Fourier amplitudes are computed using the `discrete Fourier transform matrix `_ +# :math:`\Omega_{jk} = \frac{\omega^{jk}}{\sqrt{N}}` with :math:`\omega = \exp(-i \frac{2\pi}{N}).` +import sympy +import cmath + +# Prepare symbolic variables +x, y, z = sympy.symbols("x y z", real=True) +phi = sympy.Matrix([x, y, z]) + +# Construct discrete Fourier transform matrix +omega = sympy.exp((-1j * 2.0 * cmath.pi) / 3) +Omega = sympy.Matrix([[1, 1, 1], [1, omega**1, omega**2]]) / sympy.sqrt(3) + +# Compute Jacobian +jacobian = sympy.Matrix(list(map(lambda x: abs(x) ** 2, Omega @ phi))).jacobian(phi).T +# Lambdify converts the symbolic expression to a python function +jacobian = sympy.lambdify((x, y, z), sympy.re(jacobian)) + +############################################################################## +# Optimizing the protocol +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +# We can now turn to the optimization of the protocol. We will fix the dephasing +# constant at :math:`\gamma=0.2` and the ground truth of the sensing parameters at +# :math:`\boldsymbol{\phi} = (1.1, 0.7, -0.6)` and use an equal weighting of the +# two Fourier amplitudes, corresponding to :math:`W = \mathbb{I}_2.` +gamma = 0.2 +phi = np.array([1.1, 0.7, -0.6]) +J = jacobian(*phi) +W = np.eye(2) + + +############################################################################## +# We are now ready to perform the optimization. We will initialize the weights +# at random. Then we make use of the `Adagrad `_ +# optimizer. Adaptive gradient descent methods are advantageous as the optimization +# of quantum sensing protocols is very sensitive to the step size. +def opt_cost(weights, phi=phi, gamma=gamma, J=J, W=W): + return cost(weights, phi, gamma, J, W) + + +# Seed for reproducible results +np.random.seed(395) +weights = np.random.uniform( + 0, + 2 * np.pi, + NUM_ANSATZ_PARAMETERS + NUM_MEASUREMENT_PARAMETERS, + requires_grad=True, +) + +opt = qml.AdagradOptimizer(stepsize=0.1) + +print("Initialization: Cost = {:6.4f}".format(opt_cost(weights))) +for i in range(20): + weights, cost_ = opt.step_and_cost(opt_cost, weights) + + if (i + 1) % 5 == 0: + print("Iteration {:>4}: Cost = {:6.4f}".format(i + 1, cost_)) + +############################################################################## +# Comparison with the standard protocol +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now we want to see how our protocol compares to the standard Ramsey interferometry protocol. +# The probe state in this case is a tensor product of three separate :math:`|+\rangle` states +# while the encoded state is measured in the :math:`|+\rangle / |-\rangle` basis. +# We can recreate the standard schemes with specific weights for our setup. + +Ramsey_weights = np.zeros_like(weights) +Ramsey_weights[1:6:2] = np.pi / 2 +Ramsey_weights[15:20:2] = np.pi / 2 +print("Cost for standard Ramsey sensing = {:6.4f}".format(opt_cost(Ramsey_weights))) + +############################################################################## +# We can now make a plot to compare the noise scaling of the above probes. +gammas = np.linspace(0, 0.75, 21) +comparison_costs = { + "optimized": [], + "standard": [], +} + +for gamma in gammas: + comparison_costs["optimized"].append(cost(weights, phi, gamma, J, W)) + comparison_costs["standard"].append(cost(Ramsey_weights, phi, gamma, J, W)) + +import matplotlib.pyplot as plt + +plt.semilogy(gammas, comparison_costs["optimized"], label="Optimized") +plt.semilogy(gammas, comparison_costs["standard"], label="Standard") +plt.xlabel(r"$\gamma$") +plt.ylabel("Weighted Cramér-Rao bound") +plt.legend() +plt.grid() +plt.show() + +############################################################################## +# We see that after only 20 gradient steps, we already found a sensing protocol +# that has a better noise resilience than standard Ramsey spectroscopy! +# +# This tutorial shows that variational methods are useful for quantum metrology. +# The are numerous avenues open for further research: one could study more intricate +# sensing problems, different noise models, and other platforms like optical systems. +# +# For more intricate noise models that can't be realized on quantum hardware, Ref. [#meyer2020]_ +# offers a way to move certain parts of the algorithm to the classical side. +# It also provides extensions of the method to include prior knowledge +# about the distribution of the underlying parameters or to factor in mutual time-dependence +# of parameters and encoding noise. + + +############################################################################## +# References +# ---------- +# +# .. [#meyer2020] +# +# Johannes Jakob Meyer, Johannes Borregaard, Jens Eisert. +# "A variational toolbox for quantum multi-parameter estimation." `arXiv:2006.06303 +# `__, 2020. +# +# diff --git a/demonstrations_v2/tutorial_quantum_metrology/metadata.json b/demonstrations_v2/tutorial_quantum_metrology/metadata.json new file mode 100644 index 0000000000..106cc1fd24 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_metrology/metadata.json @@ -0,0 +1,44 @@ +{ + "title": "Variationally optimizing measurement protocols", + "authors": [ + { + "username": "jjmeyer" + } + ], + "dateOfPublication": "2020-06-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_optimizing_measurement_protocols.png" + } + ], + "seoDescription": "Using a variational quantum algorithm to optimize a quantum sensing protocol.", + "doi": "", + "references": [ + { + "id": "meyer2020", + "type": "article", + "title": "A variational toolbox for quantum multi-parameter estimation.", + "authors": "Johannes Jakob Meyer, Johannes Borregaard, Jens Eisert", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2006.06303" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2006.06303" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_metrology/requirements.in b/demonstrations_v2/tutorial_quantum_metrology/requirements.in new file mode 100644 index 0000000000..77f4df8aac --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_metrology/requirements.in @@ -0,0 +1,3 @@ +pennylane +pennylane_cirq +sympy diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py new file mode 100644 index 0000000000..026193898c --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py @@ -0,0 +1,496 @@ +r""" + +.. _quantum_natural_gradient: + +Quantum natural gradient +======================== + +.. meta:: + :property="og:description": The quantum natural gradient method can achieve faster convergence for quantum machine + learning problems by taking into account the intrinsic geometry of qubits. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_optimization.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_vqe_qng Accelerating VQE with quantum natural gradient + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 25 January 2021.* + +This example demonstrates the quantum natural gradient optimization technique +for variational quantum circuits, originally proposed in +`Stokes et al. (2019) `__. + +Background +---------- + +The most successful class of quantum algorithms for use on near-term noisy quantum hardware +is the so-called variational quantum algorithm. As laid out in the +`Concepts section `__, in variational quantum algorithms +a low-depth parametrized quantum circuit ansatz is chosen, and a problem-specific +observable measured. A classical optimization loop is then used to find +the set of quantum parameters that *minimize* a particular measurement expectation value +of the quantum device. Examples of such algorithms include the :doc:`variational quantum +eigensolver (VQE) `, the +`quantum approximate optimization algorithm (QAOA) `__, +and :ref:`quantum neural networks (QNN) `. + +Most recent demonstrations +of variational quantum algorithms have used gradient-free classical optimization +methods, such as the Nelder-Mead algorithm. However, the parameter-shift rule +(as implemented in PennyLane) allows the user to automatically compute +analytic gradients of quantum circuits. This opens up the possibility to train +quantum computing hardware using gradient descent—the same method used to train +deep learning models. +Though one caveat has surfaced with gradient descent — how do we choose the optimal +step size for our variational quantum algorithms, to ensure successful and +efficient optimization? + +The natural gradient +^^^^^^^^^^^^^^^^^^^^ + +In standard gradient descent, each optimization step is given by + +.. math:: \theta_{t+1} = \theta_t -\eta \nabla \mathcal{L}(\theta), + +where :math:`\mathcal{L}(\theta)` is the cost as a function of +the parameters :math:`\theta,` and :math:`\eta` is the learning rate +or step size. In essence, each optimization step calculates the +steepest descent direction around the local value of :math:`\theta_t` +in the parameter space, and updates :math:`\theta_t\rightarrow \theta_{t+1}` +by this vector. + +The problem with the above approach is that each optimization step +is strongly connected to a *Euclidean geometry* on the parameter space. +The parametrization is not unique, and different parametrizations can distort +distances within the optimization landscape. + +For example, consider the following cost function :math:`\mathcal{L},` parametrized +using two different coordinate systems, :math:`(\theta_0, \theta_1),` and +:math:`(\phi_0, \phi_1):` + +| + +.. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng7.png + :align: center + :width: 90% + :target: javascript:void(0) + +| + +Performing gradient descent in the :math:`(\theta_0, \theta_1)` parameter +space, we are updating each parameter by the same Euclidean distance, +and not taking into account the fact that the cost function might vary at a different +rate with respect to each parameter. + +Instead, if we perform a change of coordinate system (re-parametrization) +of the cost function, we might find a parameter space where variations in :math:`\mathcal{L}` +are similar across different parameters. This is the case with the new parametrization +:math:`(\phi_0, \phi_1);` the cost function is unchanged, +but we now have a nicer geometry in which to perform gradient descent, and a more +informative stepsize. This leads to faster convergence, and can help avoid optimization +becoming stuck in local minima. For a more in-depth explanation, +including why the parameter space might not be best represented by a Euclidean space, +see `Yamamoto (2019) `__. + +However, what if we avoid gradient descent in the parameter space altogether? +If we instead consider the optimization problem as a +probability distribution of possible output values given an input +(i.e., `maximum likelihood estimation `_), +a better approach is to perform the gradient descent in the *distribution space*, which is +dimensionless and invariant with respect to the parametrization. As a result, +each optimization step will always choose the optimum step-size for every +parameter, regardless of the parametrization. + +In classical neural networks, the above process is known as +*natural gradient descent*, and was first introduced by +`Amari (1998) `__. +The standard gradient descent is modified as follows: + +.. math:: \theta_{t+1} = \theta_t - \eta F^{-1}\nabla \mathcal{L}(\theta), + +where :math:`F` is the `Fisher information matrix `__. +The Fisher information matrix acts as a metric tensor, transforming the +steepest descent in the Euclidean parameter space to the steepest descent in the +distribution space. + +The quantum analog +^^^^^^^^^^^^^^^^^^ + +In a similar vein, it has been shown that the standard Euclidean geometry +is sub-optimal for optimization of quantum variational algorithms +`(Harrow and Napp, 2019) `__. +The space of quantum states instead possesses a unique invariant metric +tensor known as the Fubini-Study metric tensor :math:`g_{ij},` which can be used to +construct a quantum analog to natural gradient descent: + +.. math:: \theta_{t+1} = \theta_t - \eta g^{+}(\theta_t)\nabla \mathcal{L}(\theta), + +where :math:`g^{+}` refers to the pseudo-inverse. + +.. note:: + + It can be shown that the Fubini-Study metric tensor reduces + to the Fisher information matrix in the classical limit. + + Furthermore, in the limit where :math:`\eta\rightarrow 0,` + the dynamics of the system are equivalent to imaginary-time + evolution within the variational subspace, as proposed in + `McArdle et al. (2018) `__. + +""" + +############################################################################## +# Block-diagonal metric tensor +# ---------------------------- +# +# A block-diagonal approximation to the Fubini-Study metric tensor +# of a variational quantum circuit can be evaluated on quantum hardware. +# +# Consider a variational quantum circuit +# +# .. math:: +# +# U(\mathbf{\theta})|\psi_0\rangle = V_L(\theta_L) W_L V_{L-1}(\theta_{L-1}) W_{L-1} +# \cdots V_{\ell}(\theta_{\ell}) W_{\ell} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle +# +# where +# +# * :math:`|\psi_0\rangle` is the initial state, +# * :math:`W_\ell` are layers of non-parametrized quantum gates, +# * :math:`V_\ell(\theta_\ell)` are layers of parametrized quantum gates +# with :math:`n_\ell` parameters :math:`\theta_\ell = \{\theta^{(\ell)}_0, \dots, \theta^{(\ell)}_n\}.` +# +# Further, assume all parametrized gates can be written in the form +# :math:`X(\theta^{(\ell)}_{i}) = e^{i\theta^{(\ell)}_{i} K^{(\ell)}_i},` +# where :math:`K^{(\ell)}_i` is the *generator* of the parametrized operation. +# +# For each parametric layer :math:`\ell` in the variational quantum circuit +# the :math:`n_\ell\times n_\ell` block-diagonal submatrix +# of the Fubini-Study tensor :math:`g_{ij}^{(\ell)}` is calculated by: +# +# .. math:: +# +# g_{ij}^{(\ell)} = \langle \psi_{\ell-1} | K_i K_j | \psi_{\ell-1} \rangle +# - \langle \psi_{\ell-1} | K_i | \psi_{\ell-1}\rangle +# \langle \psi_{\ell-1} |K_j | \psi_{\ell-1}\rangle +# +# where +# +# .. math:: +# +# | \psi_{\ell-1}\rangle = V_{\ell-1}(\theta_{\ell-1}) W_{\ell-1} \cdots V_{0}(\theta_{0}) W_{0} |\psi_0\rangle. +# +# (that is, :math:`|\psi_{\ell-1}\rangle` is the quantum state prior to the application +# of parameterized layer :math:`\ell`), and we have :math:`K_i \equiv K_i^{(\ell)}` for brevity. +# +# Let's consider a small variational quantum circuit example coded in PennyLane: + +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + +dev = qml.device("lightning.qubit", wires=3) + + +@qml.qnode(dev, interface="autograd", diff_method="parameter-shift") +def circuit(params): + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + # V_1(theta2, theta3): Parametrized layer 1 + qml.RY(params[2], wires=1) + qml.RX(params[3], wires=2) + + # W2: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + return qml.expval(qml.PauliY(0)) + +# Use pennylane.numpy for trainable parameters +params = pnp.array([0.432, -0.123, 0.543, 0.233]) + +############################################################################## +# The above circuit consists of 4 parameters, with two distinct parametrized +# layers of 2 parameters each. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng1.png +# :align: center +# :width: 90% +# :target: javascript:void(0) +# +# | +# +# (Note that in this example, the first non-parametrized layer :math:`W_0` +# is simply the identity.) Since there are two layers, each with two parameters, +# the block-diagonal approximation consists of two +# :math:`2\times 2` matrices, :math:`g^{(0)}` and :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng2.png +# :align: center +# :width: 30% +# :target: javascript:void(0) +# +# To compute the first block-diagonal :math:`g^{(0)},` we create subcircuits consisting +# of all gates prior to the layer, and observables corresponding to +# the *generators* of the gates in the layer: +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng3.png +# :align: center +# :width: 30% +# :target: javascript:void(0) + +g0 = np.zeros([2, 2]) + + +def layer0_subcircuit(params): + """This function contains all gates that + precede parametrized layer 0""" + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + +############################################################################## +# We then post-process the measurement results in order to determine :math:`g^{(0)},` +# as follows. +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng4.png +# :align: center +# :width: 50% +# :target: javascript:void(0) +# +# We can see that the diagonal terms are simply given by the variance: + + +@qml.qnode(dev, interface="autograd") +def layer0_diag(params): + layer0_subcircuit(params) + return qml.var(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) + + +# calculate the diagonal terms +varK0, varK1 = layer0_diag(params) +g0[0, 0] = varK0 / 4 +g0[1, 1] = varK1 / 4 + +############################################################################## +# The following two subcircuits are then used to calculate the +# off-diagonal covariance terms of :math:`g^{(0)}:` + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_single(params): + layer0_subcircuit(params) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) + + +@qml.qnode(dev, interface="autograd") +def layer0_off_diag_double(params): + layer0_subcircuit(params) + ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) + return qml.expval(qml.Hermitian(ZZ, wires=[0, 1])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer0_off_diag_single(params) +exK0K1 = layer0_off_diag_double(params) + +g0[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g0[1, 0] = (exK0K1 - exK0 * exK1) / 4 + +############################################################################## +# Note that, by definition, the block-diagonal matrices must be real and +# symmetric. +# +# We can repeat the above process to compute :math:`g^{(1)}.` The subcircuit +# required is given by +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng8.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + +g1 = np.zeros([2, 2]) + + +def layer1_subcircuit(params): + """This function contains all gates that + precede parametrized layer 1""" + # |psi_0>: state preparation + qml.RY(np.pi / 4, wires=0) + qml.RY(np.pi / 3, wires=1) + qml.RY(np.pi / 7, wires=2) + + # V0(theta0, theta1): Parametrized layer 0 + qml.RZ(params[0], wires=0) + qml.RZ(params[1], wires=1) + + # W1: non-parametrized gates + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + + +############################################################################## +# Using this subcircuit, we can now generate the submatrix :math:`g^{(1)}.` +# +# .. figure:: ../_static/demonstration_assets/quantum_natural_gradient/qng5.png +# :align: center +# :width: 50% +# :target: javascript:void(0) + + +@qml.qnode(dev, interface="autograd") +def layer1_diag(params): + layer1_subcircuit(params) + return qml.var(qml.PauliY(1)), qml.var(qml.PauliX(2)) + + +############################################################################## +# As previously, the diagonal terms are simply given by the variance, + +varK0, varK1 = layer1_diag(params) +g1[0, 0] = varK0 / 4 +g1[1, 1] = varK1 / 4 + + +############################################################################## +# while the off-diagonal terms require covariance between the two +# observables to be computed. + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_single(params): + layer1_subcircuit(params) + return qml.expval(qml.PauliY(1)), qml.expval(qml.PauliX(2)) + + +@qml.qnode(dev, interface="autograd") +def layer1_off_diag_double(params): + layer1_subcircuit(params) + X = np.array([[0, 1], [1, 0]]) + Y = np.array([[0, -1j], [1j, 0]]) + YX = np.kron(Y, X) + return qml.expval(qml.Hermitian(YX, wires=[1, 2])) + + +# calculate the off-diagonal terms +exK0, exK1 = layer1_off_diag_single(params) +exK0K1 = layer1_off_diag_double(params) + +g1[0, 1] = (exK0K1 - exK0 * exK1) / 4 +g1[1, 0] = g1[0, 1] + + +############################################################################## +# Putting this altogether, the block-diagonal approximation to the Fubini-Study +# metric tensor for this variational quantum circuit is +from scipy.linalg import block_diag + +g = block_diag(g0, g1) +print(np.round(g, 8)) + + +############################################################################## +# PennyLane contains a built-in function for computing the Fubini-Study metric +# tensor, :func:`~.pennylane.metric_tensor`, which +# we can use to verify this result: +print(np.round(qml.metric_tensor(circuit, approx="block-diag")(params), 8)) + +############################################################################## +# As opposed to our manual computation, which required 6 different quantum +# evaluations, the PennyLane Fubini-Study metric tensor implementation +# requires only 2 quantum evaluations, one per layer. This is done by +# automatically detecting the layer structure, and noting that every +# observable that must be measured commutes, allowing for simultaneous measurement. +# +# Therefore, by combining the quantum natural gradient optimizer with the analytic +# parameter-shift rule to optimize a variational circuit with :math:`d` parameters +# and :math:`L` parametrized layers, a total of :math:`2d+L` quantum evaluations +# are required per optimization step. +# +# Note that the :func:`~.pennylane.metric_tensor` function also supports computing the diagonal +# approximation to the metric tensor: +print(qml.metric_tensor(circuit, approx='diag')(params)) + +############################################################################## +# Furthermore, the returned metric tensor is **full differentiable**; include it +# in your cost function, and train or optimize its value! + +############################################################################## +# Quantum natural gradient optimization +# ------------------------------------- +# +# PennyLane provides an implementation of the quantum natural gradient +# optimizer, :class:`~.pennylane.QNGOptimizer`. Let's compare the optimization convergence +# of the QNG Optimizer and the :class:`~.pennylane.GradientDescentOptimizer` for the simple variational +# circuit above. + +steps = 200 +init_params = pnp.array([0.432, -0.123, 0.543, 0.233], requires_grad=True) + +############################################################################## +# Performing vanilla gradient descent: + +gd_cost = [] +opt = qml.GradientDescentOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + gd_cost.append(circuit(theta)) + +############################################################################## +# Performing quantum natural gradient descent: + +qng_cost = [] +opt = qml.QNGOptimizer(0.01) + +theta = init_params +for _ in range(steps): + theta = opt.step(circuit, theta) + qng_cost.append(circuit(theta)) + + +############################################################################## +# Plotting the cost vs optimization step for both optimization strategies: +from matplotlib import pyplot as plt + +plt.style.use("seaborn") +plt.plot(gd_cost, "b", label="Vanilla gradient descent") +plt.plot(qng_cost, "g", label="Quantum natural gradient descent") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# References +# ---------- +# +# 1. Shun-Ichi Amari. "Natural gradient works efficiently in learning." +# `Neural computation 10.2, 251-276 `__, 1998. +# +# 2. James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo. +# "Quantum Natural Gradient." `arXiv:1909.02108 `__, 2019. +# +# 3. Aram Harrow and John Napp. "Low-depth gradient measurements can improve +# convergence in variational hybrid quantum-classical algorithms." +# `arXiv:1901.05374 `__, 2019. +# +# 4. Naoki Yamamoto. "On the natural gradient for variational quantum eigensolver." +# `arXiv:1909.05074 `__, 2019. +# +# diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json b/demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json new file mode 100644 index 0000000000..b14f164128 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json @@ -0,0 +1,76 @@ +{ + "title": "Quantum natural gradient", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_natural_gradient.png" + } + ], + "seoDescription": "The quantum natural gradient method can achieve faster convergence for quantum machine learning problems by taking into account the intrinsic geometry of qubits.", + "doi": "", + "references": [ + { + "id": "Amari1998", + "type": "article", + "title": "Natural gradient works efficiently in learning.", + "authors": "Shun-Ichi Amari", + "year": "1998", + "journal": "Neural computation", + "url": "https://www.mitpressjournals.org/doi/abs/10.1162/089976698300017746" + }, + { + "id": "Stokes2019", + "type": "article", + "title": "Quantum Natural Gradient.", + "authors": "James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.02108" + }, + { + "id": "Harrow2019", + "type": "article", + "title": "Low-depth gradient measurements can improve convergence in variational hybrid quantum-classical algorithms.", + "authors": "Aram Harrow and John Napp", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1901.05374" + }, + { + "id": "Yamamoto2019", + "type": "article", + "title": "On the natural gradient for variational quantum eigensolver.", + "authors": "Naoki Yamamoto", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.05074" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1909.02108" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in b/demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in new file mode 100644 index 0000000000..8132c3977e --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py new file mode 100644 index 0000000000..e6dbda2076 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py @@ -0,0 +1,609 @@ +r""" +.. _quantum_transfer_learning: + +Quantum transfer learning +========================= + +.. meta:: + :property="og:description": Combine PyTorch and PennyLane to train a hybrid quantum-classical image + classifier using transfer learning. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/transfer_images.png + +*Author: Andrea Mari — Posted: 19 December 2019. Last updated: 28 January 2021.* + +In this tutorial we apply a machine learning method, known as *transfer learning*, to an +image classifier based on a hybrid classical-quantum network. + +This example follows the general structure of the PyTorch +`tutorial on transfer learning `_ +by Sasank Chilamkurthy, with the crucial difference of using a quantum circuit to perform the +final classification task. + +More details on this topic can be found in the research paper [1] (`Mari et al. (2019) `_). + + +Introduction +------------ + +Transfer learning is a well-established technique for training artificial neural networks (see e.g., Ref. [2]), +which is based on the general intuition that if a pre-trained network is good at solving a +given problem, then, with just a bit of additional training, it can be used to also solve a different +but related problem. + +As discussed in Ref. [1], this idea can be formalized in terms of two abstract networks :math:`A` +and :math:`B,` independently from their quantum or classical physical nature. + +| + + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_general.png + :scale: 45% + :alt: transfer_general + :align: center + +| + +As sketched in the above figure, one can give the following **general definition of the +transfer learning method**: + +1. Take a network :math:`A` that has been pre-trained on a dataset :math:`D_A` and for a given + task :math:`T_A.` + +2. Remove some of the final layers. In this way, the resulting truncated network :math:`A'` + can be used as a feature extractor. + +3. Connect a new trainable network :math:`B` at the end of the pre-trained network :math:`A'.` + +4. Keep the weights of :math:`A'` constant, and train the final block :math:`B` with a + new dataset :math:`D_B` and/or for a new task of interest :math:`T_B.` + +When dealing with hybrid systems, depending on the physical nature (classical or quantum) of the +networks :math:`A` and :math:`B,` one can have different implementations of transfer learning as + +summarized in following table: + +| + +.. rst-class:: docstable + ++-----------+-----------+-----------------------------------------------------+ +| Network A | Network B | Transfer learning scheme | ++===========+===========+=====================================================+ +| Classical | Classical | CC - Standard classical method. See e.g., Ref. [2]. | ++-----------+-----------+-----------------------------------------------------+ +| Classical | Quantum | CQ - **Hybrid model presented in this tutorial.** | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Classical | QC - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ +| Quantum | Quantum | QQ - Model studied in Ref. [1]. | ++-----------+-----------+-----------------------------------------------------+ + +Classical-to-quantum transfer learning +-------------------------------------- + +We focus on the CQ transfer learning scheme discussed in the previous section and we give a specific example. + +1. As pre-trained network :math:`A` we use **ResNet18**, a deep residual neural network introduced by + Microsoft in Ref. [3], which is pre-trained on the *ImageNet* dataset. + +2. After removing its final layer we obtain :math:`A',` a pre-processing block which maps any + input high-resolution image into 512 abstract features. + +3. Such features are classified by a 4-qubit "dressed quantum circuit" :math:`B,` i.e., a + variational quantum circuit sandwiched between two classical layers. + +4. The hybrid model is trained, keeping :math:`A'` constant, on the *Hymenoptera* dataset + (a small subclass of ImageNet) containing images of *ants* and *bees*. + +A graphical representation of the full data processing pipeline is given in the figure below. + +.. figure:: ../_static/demonstration_assets/quantum_transfer_learning/transfer_learning_c2q.png + :scale: 55% + :alt: transfer_c2q + :align: center + +""" + +############################################################################## +# General setup +# ------------------------ +# +# .. note:: +# +# To use the PyTorch interface in PennyLane, you must first +# `install PyTorch `_. +# +# In addition to *PennyLane*, we will also need some standard *PyTorch* libraries and the +# plotting library *matplotlib*. + +# Some parts of this code are based on the Python script: +# https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py +# License: BSD + +import time +import os +import copy + +# PyTorch +import torch +import torch.nn as nn +import torch.optim as optim +from torch.optim import lr_scheduler +import torchvision +from torchvision import datasets, transforms + +# Pennylane +import pennylane as qml +from pennylane import numpy as np + +torch.manual_seed(42) +np.random.seed(42) + +# Plotting +import matplotlib.pyplot as plt + +# OpenMP: number of parallel threads. +os.environ["OMP_NUM_THREADS"] = "1" + + +############################################################################## +# Setting of the main hyper-parameters of the model +# ------------------------------------------------------------ +# +# .. note:: +# To reproduce the results of Ref. [1], ``num_epochs`` should be set to ``30`` which may take a long time. +# We suggest to first try with ``num_epochs=1`` and, if everything runs smoothly, increase it to a larger value. + + +n_qubits = 4 # Number of qubits +step = 0.0004 # Learning rate +batch_size = 4 # Number of samples for each training step +num_epochs = 3 # Number of training epochs +q_depth = 6 # Depth of the quantum circuit (number of variational layers) +gamma_lr_scheduler = 0.1 # Learning rate reduction applied every 10 epochs. +q_delta = 0.01 # Initial spread of random quantum weights +start_time = time.time() # Start of the computation timer + +############################################################################## +# We initialize a PennyLane device with a ``default.qubit`` backend. + +dev = qml.device("default.qubit", wires=n_qubits) + +############################################################################## +# We configure PyTorch to use CUDA only if available. Otherwise the CPU is used. + +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + +############################################################################## +# Dataset loading +# ------------------------------------------------------------ +# +# .. note:: +# The dataset containing images of *ants* and *bees* can be downloaded +# `here `_ and +# should be extracted in the subfolder ``../_data/hymenoptera_data``. +# +# This is a very small dataset (roughly 250 images), too small for training from scratch a +# classical or quantum model, however it is enough when using *transfer learning* approach. +# +# The PyTorch packages ``torchvision`` and ``torch.utils.data`` are used for loading the dataset +# and performing standard preliminary image operations: resize, center, crop, normalize, *etc.* + +data_transforms = { + "train": transforms.Compose( + [ + # transforms.RandomResizedCrop(224), # uncomment for data augmentation + # transforms.RandomHorizontalFlip(), # uncomment for data augmentation + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + # Normalize input channels using mean values and standard deviations of ImageNet. + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), + "val": transforms.Compose( + [ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ), +} + +data_dir = "../_data/hymenoptera_data" +image_datasets = { + x if x == "train" else "validation": datasets.ImageFolder( + os.path.join(data_dir, x), data_transforms[x] + ) + for x in ["train", "val"] +} +dataset_sizes = {x: len(image_datasets[x]) for x in ["train", "validation"]} +class_names = image_datasets["train"].classes + +# Initialize dataloader +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + +# function to plot images +def imshow(inp, title=None): + """Display image from tensor.""" + inp = inp.numpy().transpose((1, 2, 0)) + # Inverse of the initial normalization operation. + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + inp = std * inp + mean + inp = np.clip(inp, 0, 1) + plt.imshow(inp) + if title is not None: + plt.title(title) + + +############################################################################## +# Let us show a batch of the test data, just to have an idea of the classification problem. + +# Get a batch of training data +inputs, classes = next(iter(dataloaders["validation"])) + +# Make a grid from batch +out = torchvision.utils.make_grid(inputs) + +imshow(out, title=[class_names[x] for x in classes]) + +dataloaders = { + x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) + for x in ["train", "validation"] +} + + +############################################################################## +# Variational quantum circuit +# ------------------------------------ +# We first define some quantum layers that will compose the quantum circuit. + + +def H_layer(nqubits): + """Layer of single-qubit Hadamard gates. + """ + for idx in range(nqubits): + qml.Hadamard(wires=idx) + + +def RY_layer(w): + """Layer of parametrized qubit rotations around the y axis. + """ + for idx, element in enumerate(w): + qml.RY(element, wires=idx) + + +def entangling_layer(nqubits): + """Layer of CNOTs followed by another shifted layer of CNOT. + """ + # In other words it should apply something like : + # CNOT CNOT CNOT CNOT... CNOT + # CNOT CNOT CNOT... CNOT + for i in range(0, nqubits - 1, 2): # Loop over even indices: i=0,2,...N-2 + qml.CNOT(wires=[i, i + 1]) + for i in range(1, nqubits - 1, 2): # Loop over odd indices: i=1,3,...N-3 + qml.CNOT(wires=[i, i + 1]) + + +############################################################################## +# Now we define the quantum circuit through the PennyLane `qnode` decorator . +# +# The structure is that of a typical variational quantum circuit: +# +# * **Embedding layer:** All qubits are first initialized in a balanced superposition +# of *up* and *down* states, then they are rotated according to the input parameters +# (local embedding). +# +# * **Variational layers:** A sequence of trainable rotation layers and constant +# entangling layers is applied. +# +# * **Measurement layer:** For each qubit, the local expectation value of the :math:`Z` +# operator is measured. This produces a classical output vector, suitable for +# additional post-processing. + + +@qml.qnode(dev) +def quantum_net(q_input_features, q_weights_flat): + """ + The variational quantum circuit. + """ + + # Reshape weights + q_weights = q_weights_flat.reshape(q_depth, n_qubits) + + # Start from state |+> , unbiased w.r.t. |0> and |1> + H_layer(n_qubits) + + # Embed features in the quantum node + RY_layer(q_input_features) + + # Sequence of trainable variational layers + for k in range(q_depth): + entangling_layer(n_qubits) + RY_layer(q_weights[k]) + + # Expectation values in the Z basis + exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)] + return tuple(exp_vals) + + +############################################################################## +# Dressed quantum circuit +# ------------------------ +# +# We can now define a custom ``torch.nn.Module`` representing a *dressed* quantum circuit. +# +# This is a concatenation of: +# +# * A classical pre-processing layer (``nn.Linear``). +# * A classical activation function (``torch.tanh``). +# * A constant ``np.pi/2.0`` scaling. +# * The previously defined quantum circuit (``quantum_net``). +# * A classical post-processing layer (``nn.Linear``). +# +# The input of the module is a batch of vectors with 512 real parameters (features) and +# the output is a batch of vectors with two real outputs (associated with the two classes +# of images: *ants* and *bees*). + + +class DressedQuantumNet(nn.Module): + """ + Torch module implementing the *dressed* quantum net. + """ + + def __init__(self): + """ + Definition of the *dressed* layout. + """ + + super().__init__() + self.pre_net = nn.Linear(512, n_qubits) + self.q_params = nn.Parameter(q_delta * torch.randn(q_depth * n_qubits)) + self.post_net = nn.Linear(n_qubits, 2) + + def forward(self, input_features): + """ + Defining how tensors are supposed to move through the *dressed* quantum + net. + """ + + # obtain the input features for the quantum circuit + # by reducing the feature dimension from 512 to 4 + pre_out = self.pre_net(input_features) + q_in = torch.tanh(pre_out) * np.pi / 2.0 + + # Apply the quantum circuit to each element of the batch and append to q_out + q_out = torch.Tensor(0, n_qubits) + q_out = q_out.to(device) + for elem in q_in: + q_out_elem = torch.hstack(quantum_net(elem, self.q_params)).float().unsqueeze(0) + q_out = torch.cat((q_out, q_out_elem)) + + # return the two-dimensional prediction from the postprocessing layer + return self.post_net(q_out) + + +############################################################################## +# Hybrid classical-quantum model +# ------------------------------------ +# +# We are finally ready to build our full hybrid classical-quantum network. +# We follow the *transfer learning* approach: +# +# 1. First load the classical pre-trained network *ResNet18* from the ``torchvision.models`` zoo. +# 2. Freeze all the weights since they should not be trained. +# 3. Replace the last fully connected layer with our trainable dressed quantum circuit (``DressedQuantumNet``). +# +# .. note:: +# The *ResNet18* model is automatically downloaded by PyTorch and it may take several minutes (only the first time). +# + +weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1 +model_hybrid = torchvision.models.resnet18(weights=weights) + +for param in model_hybrid.parameters(): + param.requires_grad = False + + +# Notice that model_hybrid.fc is the last layer of ResNet18 +model_hybrid.fc = DressedQuantumNet() + +# Use CUDA or CPU according to the "device" object. +model_hybrid = model_hybrid.to(device) + +############################################################################## +# Training and results +# ------------------------ +# +# Before training the network we need to specify the *loss* function. +# +# We use, as usual in classification problem, the *cross-entropy* which is +# directly available within ``torch.nn``. + + +criterion = nn.CrossEntropyLoss() + +############################################################################## +# We also initialize the *Adam optimizer* which is called at each training step +# in order to update the weights of the model. + + +optimizer_hybrid = optim.Adam(model_hybrid.fc.parameters(), lr=step) + +############################################################################## +# We schedule to reduce the learning rate by a factor of ``gamma_lr_scheduler`` +# every 10 epochs. + + +exp_lr_scheduler = lr_scheduler.StepLR( + optimizer_hybrid, step_size=10, gamma=gamma_lr_scheduler +) + +############################################################################## +# What follows is a training function that will be called later. +# This function should return a trained model that can be used to make predictions +# (classifications). + + +def train_model(model, criterion, optimizer, scheduler, num_epochs): + since = time.time() + best_model_wts = copy.deepcopy(model.state_dict()) + best_acc = 0.0 + best_loss = 10000.0 # Large arbitrary number + best_acc_train = 0.0 + best_loss_train = 10000.0 # Large arbitrary number + print("Training started:") + + for epoch in range(num_epochs): + + # Each epoch has a training and validation phase + for phase in ["train", "validation"]: + if phase == "train": + # Set model to training mode + model.train() + else: + # Set model to evaluate mode + model.eval() + running_loss = 0.0 + running_corrects = 0 + + # Iterate over data. + n_batches = dataset_sizes[phase] // batch_size + it = 0 + for inputs, labels in dataloaders[phase]: + since_batch = time.time() + batch_size_ = len(inputs) + inputs = inputs.to(device) + labels = labels.to(device) + optimizer.zero_grad() + + # Track/compute gradient and make an optimization step only when training + with torch.set_grad_enabled(phase == "train"): + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + loss = criterion(outputs, labels) + if phase == "train": + loss.backward() + optimizer.step() + + # Print iteration results + running_loss += loss.item() * batch_size_ + batch_corrects = torch.sum(preds == labels.data).item() + running_corrects += batch_corrects + print( + "Phase: {} Epoch: {}/{} Iter: {}/{} Batch time: {:.4f}".format( + phase, + epoch + 1, + num_epochs, + it + 1, + n_batches + 1, + time.time() - since_batch, + ), + end="\r", + flush=True, + ) + it += 1 + + # Print epoch results + epoch_loss = running_loss / dataset_sizes[phase] + epoch_acc = running_corrects / dataset_sizes[phase] + print( + "Phase: {} Epoch: {}/{} Loss: {:.4f} Acc: {:.4f} ".format( + "train" if phase == "train" else "validation ", + epoch + 1, + num_epochs, + epoch_loss, + epoch_acc, + ) + ) + + # Check if this is the best model wrt previous epochs + if phase == "validation" and epoch_acc > best_acc: + best_acc = epoch_acc + best_model_wts = copy.deepcopy(model.state_dict()) + if phase == "validation" and epoch_loss < best_loss: + best_loss = epoch_loss + if phase == "train" and epoch_acc > best_acc_train: + best_acc_train = epoch_acc + if phase == "train" and epoch_loss < best_loss_train: + best_loss_train = epoch_loss + + # Update learning rate + if phase == "train": + scheduler.step() + + # Print final results + model.load_state_dict(best_model_wts) + time_elapsed = time.time() - since + print( + "Training completed in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60) + ) + print("Best test loss: {:.4f} | Best test accuracy: {:.4f}".format(best_loss, best_acc)) + return model + + +############################################################################## +# We are ready to perform the actual training process. + +model_hybrid = train_model( + model_hybrid, criterion, optimizer_hybrid, exp_lr_scheduler, num_epochs=num_epochs +) + +############################################################################## +# Visualizing the model predictions +# ------------------------------------ + +############################################################################## +# We first define a visualization function for a batch of test data. + + +def visualize_model(model, num_images=6, fig_name="Predictions"): + images_so_far = 0 + _fig = plt.figure(fig_name) + model.eval() + with torch.no_grad(): + for _i, (inputs, labels) in enumerate(dataloaders["validation"]): + inputs = inputs.to(device) + labels = labels.to(device) + outputs = model(inputs) + _, preds = torch.max(outputs, 1) + for j in range(inputs.size()[0]): + images_so_far += 1 + ax = plt.subplot(num_images // 2, 2, images_so_far) + ax.axis("off") + ax.set_title("[{}]".format(class_names[preds[j]])) + imshow(inputs.cpu().data[j]) + if images_so_far == num_images: + return + + +############################################################################## +# Finally, we can run the previous function to see a batch of images +# with the corresponding predictions. +# +visualize_model(model_hybrid, num_images=batch_size) +plt.show() + +############################################################################## +# References +# ------------ +# +# [1] Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran. +# *Transfer learning in hybrid classical-quantum neural networks*. arXiv:1912.08278 (2019). +# +# [2] Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng. +# *Self-taught learning: transfer learning from unlabeled data*. +# Proceedings of the 24th International Conference on Machine Learning*, 759–766 (2007). +# +# [3] Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun. *Deep residual learning for image recognition*. +# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 770-778 (2016). +# +# [4] Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran. +# *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). +# +# diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json b/demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json new file mode 100644 index 0000000000..358928cb86 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json @@ -0,0 +1,64 @@ +{ + "title": "Quantum transfer learning", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2019-12-19T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tutorial_quantum_transfer_learning.png" + } + ], + "seoDescription": "Combine PyTorch and PennyLane to train a hybrid quantum-classical image classifier using transfer learning.", + "doi": "", + "references": [ + { + "id": "Mari2019", + "type": "article", + "title": "Transfer learning in hybrid classical-quantum neural networks", + "authors": "Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran", + "year": "2019", + "journal": "", + "url": "" + }, + { + "id": "Raina2007", + "type": "article", + "title": "Self-taught learning: transfer learning from unlabeled data", + "authors": "Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng", + "year": "2007", + "journal": "Proceedings of the 24th International Conference on Machine Learning", + "url": "" + }, + { + "id": "He2016", + "type": "article", + "title": "Deep residual learning for image recognition", + "authors": "Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun", + "year": "2016", + "journal": "Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition", + "url": "" + }, + { + "id": "Bergholm2018", + "type": "article", + "title": "PennyLane: Automatic differentiation of hybrid quantum-classical computations", + "authors": "Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran", + "year": "2018", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [], + "discussionForumUrl": "https://discuss.pennylane.ai/t/can-quantum-transfer-learning-be-used-for-multi-classification/3963" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in b/demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in new file mode 100644 index 0000000000..88255b3813 --- /dev/null +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in @@ -0,0 +1,4 @@ +matplotlib +pennylane +torch +torchvision diff --git a/demonstrations_v2/tutorial_quanvolution/demo.py b/demonstrations_v2/tutorial_quanvolution/demo.py new file mode 100644 index 0000000000..e4aab89bfa --- /dev/null +++ b/demonstrations_v2/tutorial_quanvolution/demo.py @@ -0,0 +1,371 @@ +r""" +.. _quanvolution: + +Quanvolutional Neural Networks +============================== + +.. meta:: + :property="og:description": Train a quantum convolutional neural network + to classify MNIST images. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/circuit.png + +*Author: Andrea Mari — Posted: 24 March 2020. Last updated: 15 January 2021.* + +In this demo we implement the *Quanvolutional Neural Network*, a quantum +machine learning model originally introduced in +`Henderson et al. (2019) `_. + +.. figure:: ../_static/demonstration_assets/quanvolution/circuit.png + :align: center + :width: 90% + :target: javascript:void(0) + +Introduction +------------ + +Classical convolution +^^^^^^^^^^^^^^^^^^^^^ +The *convolutional neural network* (CNN) is a standard model in classical machine learning which is particularly +suitable for processing images. +The model is based on the idea of a *convolution layer* where, instead of processing the full input data with a global function, +a local convolution is applied. + +If the input is an image, small local regions are sequentially processed with the same kernel. The results obtained for each region are usually associated to different channels +of a single output pixel. The union of all the output pixels produces a new image-like object, which can be further processed by +additional layers. + + +Quantum convolution +^^^^^^^^^^^^^^^^^^^ +One can extend the same idea also to the context of quantum variational circuits. A possible approach is given +by the following procedure which is very similar to the one used in Ref. [1]. The scheme is also represented in the +figure at the top of this tutorial. + + +1. A small region of the input image, in our example a :math:`2 \times 2` square, is embedded into a quantum circuit. + In this demo, this is achieved with parametrized rotations applied to the qubits initialized in the ground state. + +2. A quantum computation, associated to a unitary :math:`U,` is performed on the system. + The unitary could be generated by a variational quantum circuit or, more simply, by a random circuit as + proposed in Ref. [1]. + +3. The quantum system is finally measured, obtaining a list of classical expectation values. + The measurement results could also be classically post-processed as proposed in Ref. [1] but, for simplicity, in this + demo we directly use the raw expectation values. + +4. Analogously to a classical convolution layer, each expectation value is mapped to a different channel of a + single output pixel. + +5. Iterating the same procedure over different regions, one can scan the full input image, + producing an output object which will be structured as a multi-channel image. + +6. The quantum convolution can be followed by further quantum layers or by classical layers. + + +The main difference with respect to a classical convolution is that a quantum circuit can +generate highly complex kernels whose computation could be, at least in principle, classically intractable. + +.. note:: + In this tutorial we follow the approach of Ref. [1] in which a fixed non-trainable quantum + circuit is used as a "quanvolution" kernel, while the subsequent classical layers + are trained for the classification problem of interest. + However, by leveraging the ability of PennyLane to evaluate gradients of + quantum circuits, the quantum kernel could also be trained. + + +General setup +------------- +This Python code requires *PennyLane* with the *TensorFlow* interface and the plotting library *matplotlib*. +""" + +import pennylane as qml +from pennylane import numpy as np +from pennylane.templates import RandomLayers +import tensorflow as tf +from tensorflow import keras +import matplotlib.pyplot as plt + +############################################################################## +# Setting of the main hyper-parameters of the model +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +n_epochs = 30 # Number of optimization epochs +n_layers = 1 # Number of random layers +n_train = 50 # Size of the train dataset +n_test = 30 # Size of the test dataset + +SAVE_PATH = "../_static/demonstration_assets/quanvolution/" # Data saving folder +PREPROCESS = True # If False, skip quantum processing and load data from SAVE_PATH +np.random.seed(0) # Seed for NumPy random number generator +tf.random.set_seed(0) # Seed for TensorFlow random number generator + +############################################################################## +# Loading of the MNIST dataset +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# We import the MNIST dataset from *Keras*. To speedup the evaluation of this demo +# we use only a small number of training and test images. Obviously, better +# results are achievable when using the full dataset. + +mnist_dataset = keras.datasets.mnist +(train_images, train_labels), (test_images, test_labels) = mnist_dataset.load_data() + +# Reduce dataset size +train_images = train_images[:n_train] +train_labels = train_labels[:n_train] +test_images = test_images[:n_test] +test_labels = test_labels[:n_test] + +# Normalize pixel values within 0 and 1 +train_images = train_images / 255 +test_images = test_images / 255 + +# Add extra dimension for convolution channels +train_images = np.array(train_images[..., tf.newaxis], requires_grad=False) +test_images = np.array(test_images[..., tf.newaxis], requires_grad=False) + + +############################################################################## +# Quantum circuit as a convolution kernel +# --------------------------------------- +# +# We follow the scheme described in the introduction and represented in the figure at the top +# of this demo. +# +# We initialize a PennyLane ``default.qubit`` device, simulating a system of :math:`4` qubits. +# The associated ``qnode`` represents the quantum circuit consisting of: +# +# 1. an embedding layer of local :math:`R_y` rotations (with angles scaled by a factor of :math:`\pi`); +# +# 2. a random circuit of ``n_layers``; +# +# 3. a final measurement in the computational basis, estimating :math:`4` expectation values. + + +dev = qml.device("default.qubit", wires=4) +# Random circuit parameters +rand_params = np.random.uniform(high=2 * np.pi, size=(n_layers, 4)) + +@qml.qnode(dev) +def circuit(phi): + # Encoding of 4 classical input values + for j in range(4): + qml.RY(np.pi * phi[j], wires=j) + + # Random quantum circuit + RandomLayers(rand_params, wires=list(range(4))) + + # Measurement producing 4 classical output values + return [qml.expval(qml.PauliZ(j)) for j in range(4)] + + +############################################################################## +# The next function defines the convolution scheme: +# +# 1. the image is divided into squares of :math:`2 \times 2` pixels; +# +# 2. each square is processed by the quantum circuit; +# +# 3. the :math:`4` expectation values are mapped into :math:`4` different +# channels of a single output pixel. +# +# .. note:: +# This process halves the resolution of the input image. In the +# standard language of CNN, this would correspond to a convolution +# with a :math:`2 \times 2` *kernel* and a *stride* equal to :math:`2.` + + +def quanv(image): + """Convolves the input image with many applications of the same quantum circuit.""" + out = np.zeros((14, 14, 4)) + + # Loop over the coordinates of the top-left pixel of 2X2 squares + for j in range(0, 28, 2): + for k in range(0, 28, 2): + # Process a squared 2x2 region of the image with a quantum circuit + q_results = circuit( + [ + image[j, k, 0], + image[j, k + 1, 0], + image[j + 1, k, 0], + image[j + 1, k + 1, 0] + ] + ) + # Assign expectation values to different channels of the output pixel (j/2, k/2) + for c in range(4): + out[j // 2, k // 2, c] = q_results[c] + return out + + +############################################################################## +# Quantum pre-processing of the dataset +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# Since we are not going to train the quantum convolution layer, it is more +# efficient to apply it as a "pre-processing" layer to all the images of our dataset. +# Later an entirely classical model will be directly trained and tested on the +# pre-processed dataset, avoiding unnecessary repetitions of quantum computations. +# +# The pre-processed images will be saved in the folder ``SAVE_PATH``. +# Once saved, they can be directly loaded by setting ``PREPROCESS = False``, +# otherwise the quantum convolution is evaluated at each run of the code. + +if PREPROCESS == True: + q_train_images = [] + print("Quantum pre-processing of train images:") + for idx, img in enumerate(train_images): + print("{}/{} ".format(idx + 1, n_train), end="\r") + q_train_images.append(quanv(img)) + q_train_images = np.asarray(q_train_images) + + q_test_images = [] + print("\nQuantum pre-processing of test images:") + for idx, img in enumerate(test_images): + print("{}/{} ".format(idx + 1, n_test), end="\r") + q_test_images.append(quanv(img)) + q_test_images = np.asarray(q_test_images) + + # Save pre-processed images + np.save(SAVE_PATH + "q_train_images.npy", q_train_images) + np.save(SAVE_PATH + "q_test_images.npy", q_test_images) + + +# Load pre-processed images +q_train_images = np.load(SAVE_PATH + "q_train_images.npy") +q_test_images = np.load(SAVE_PATH + "q_test_images.npy") + +############################################################################## +# Let us visualize the effect of the quantum convolution +# layer on a batch of samples: + +n_samples = 4 +n_channels = 4 +fig, axes = plt.subplots(1 + n_channels, n_samples, figsize=(10, 10)) +for k in range(n_samples): + axes[0, 0].set_ylabel("Input") + if k != 0: + axes[0, k].yaxis.set_visible(False) + axes[0, k].imshow(train_images[k, :, :, 0], cmap="gray") + + # Plot all output channels + for c in range(n_channels): + axes[c + 1, 0].set_ylabel("Output [ch. {}]".format(c)) + if k != 0: + axes[c, k].yaxis.set_visible(False) + axes[c + 1, k].imshow(q_train_images[k, :, :, c], cmap="gray") + +plt.tight_layout() +plt.show() + +############################################################################## +# Below each input image, the :math:`4` output channels generated by the +# quantum convolution are visualized in gray scale. +# +# One can clearly notice the downsampling of the resolution and +# some local distortion introduced by the quantum kernel. +# On the other hand the global shape of the image is preserved, +# as expected for a convolution layer. + +############################################################################## +# Hybrid quantum-classical model +# ------------------------------ +# +# After the application of the quantum convolution layer we feed the resulting +# features into a classical neural network that will be trained to classify +# the :math:`10` different digits of the MNIST dataset. +# +# We use a very simple model: just a fully connected layer with +# 10 output nodes with a final *softmax* activation function. +# +# The model is compiled with a *stochastic-gradient-descent* optimizer, +# and a *cross-entropy* loss function. + + +def MyModel(): + """Initializes and returns a custom Keras model + which is ready to be trained.""" + model = keras.models.Sequential([ + keras.layers.Flatten(), + keras.layers.Dense(10, activation="softmax") + ]) + + model.compile( + optimizer='adam', + loss="sparse_categorical_crossentropy", + metrics=["accuracy"], + ) + return model + + +############################################################################## +# Training +# ^^^^^^^^ +# +# We first initialize an instance of the model, then we train and validate +# it with the dataset that has been already pre-processed by a quantum convolution. + +q_model = MyModel() + +q_history = q_model.fit( + q_train_images, + train_labels, + validation_data=(q_test_images, test_labels), + batch_size=4, + epochs=n_epochs, + verbose=2, +) + +############################################################################## +# In order to compare the results achievable with and without the quantum convolution layer, +# we initialize also a "classical" instance of the model that will be directly trained +# and validated with the raw MNIST images (i.e., without quantum pre-processing). + +c_model = MyModel() + +c_history = c_model.fit( + train_images, + train_labels, + validation_data=(test_images, test_labels), + batch_size=4, + epochs=n_epochs, + verbose=2, +) + + +############################################################################## +# Results +# ^^^^^^^ +# +# We can finally plot the test accuracy and the test loss with respect to the +# number of training epochs. + +import matplotlib.pyplot as plt + +plt.style.use("seaborn") +fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(6, 9)) + +ax1.plot(q_history.history["val_accuracy"], "-ob", label="With quantum layer") +ax1.plot(c_history.history["val_accuracy"], "-og", label="Without quantum layer") +ax1.set_ylabel("Accuracy") +ax1.set_ylim([0, 1]) +ax1.set_xlabel("Epoch") +ax1.legend() + +ax2.plot(q_history.history["val_loss"], "-ob", label="With quantum layer") +ax2.plot(c_history.history["val_loss"], "-og", label="Without quantum layer") +ax2.set_ylabel("Loss") +ax2.set_ylim(top=2.5) +ax2.set_xlabel("Epoch") +ax2.legend() +plt.tight_layout() +plt.show() + + +############################################################################## +# References +# ---------- +# +# 1. Maxwell Henderson, Samriddhi Shakya, Shashindra Pradhan, Tristan Cook. +# "Quanvolutional Neural Networks: Powering Image Recognition with Quantum Circuits." +# `arXiv:1904.04767 `__, 2019. +# +# diff --git a/demonstrations_v2/tutorial_quanvolution/metadata.json b/demonstrations_v2/tutorial_quanvolution/metadata.json new file mode 100644 index 0000000000..9445748cc6 --- /dev/null +++ b/demonstrations_v2/tutorial_quanvolution/metadata.json @@ -0,0 +1,39 @@ +{ + "title": "Quanvolutional Neural Networks", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2020-03-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quanvolutional_neural_networks.png" + } + ], + "seoDescription": "Train a quantum convolutional neural network to classify MNIST images.", + "doi": "", + "references": [ + { + "id": "Henderson2019", + "type": "article", + "title": "Quanvolutional Neural Networks: Powering Image Recognition with Quantum Circuits.", + "authors": "Maxwell Henderson, Samriddhi Shakya, Shashindra Pradhan, Tristan Cook", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1904.04767" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1904.04767" + ], + "referencedByPapers": [], + "relatedContent": [], + "discussionForumUrl": "https://discuss.pennylane.ai/t/quanvolutional-neural-networks/7296" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quanvolution/requirements.in b/demonstrations_v2/tutorial_quanvolution/requirements.in new file mode 100644 index 0000000000..b85bbff928 --- /dev/null +++ b/demonstrations_v2/tutorial_quanvolution/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +tensorflow diff --git a/demonstrations_v2/tutorial_qubit_rotation/demo.py b/demonstrations_v2/tutorial_qubit_rotation/demo.py new file mode 100644 index 0000000000..a34e037a37 --- /dev/null +++ b/demonstrations_v2/tutorial_qubit_rotation/demo.py @@ -0,0 +1,370 @@ +r""" +.. _qubit_rotation: + +Basic tutorial: qubit rotation +============================== + +.. meta:: + :property="og:description": To see how PennyLane allows the easy construction and optimization + of quantum functions, let's consider the 'hello world' of QML: qubit rotation. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/bloch.png + +.. related:: + + plugins_hybrid Plugins and hybrid computation + tutorial_gaussian_transformation Gaussian transformation + tutorial_state_preparation Training a quantum circuit with PyTorch + +*Author: Josh Izaac — Posted: 11 October 2019. Last updated: 19 January 2021.* + +To see how PennyLane allows the easy construction and optimization of quantum functions, let's +consider the simple case of **qubit rotation** the PennyLane version of the 'Hello, world!' +example. + +The task at hand is to optimize two rotation gates in order to flip a single +qubit from state :math:`\left|0\right\rangle` to state :math:`\left|1\right\rangle.` + + +The quantum circuit +------------------- + +In the qubit rotation example, we wish to implement the following quantum circuit: + +.. figure:: ../_static/demonstration_assets/qubit_rotation/rotation_circuit.png + :align: center + :width: 40% + :target: javascript:void(0); + +Breaking this down step-by-step, we first start with a qubit in the ground state +:math:`|0\rangle = \begin{bmatrix}1 & 0 \end{bmatrix}^T,` +and rotate it around the x-axis by applying the gate + +.. math:: + R_x(\phi_1) = e^{-i \phi_1 \sigma_x /2} = + \begin{bmatrix} \cos \frac{\phi_1}{2} & -i \sin \frac{\phi_1}{2} \\ + -i \sin \frac{\phi_1}{2} & \cos \frac{\phi_1}{2} + \end{bmatrix}, + +and then around the y-axis via the gate + +.. math:: + R_y(\phi_2) = e^{-i \phi_2 \sigma_y/2} = + \begin{bmatrix} \cos \frac{\phi_2}{2} & - \sin \frac{\phi_2}{2} \\ + \sin \frac{\phi_2}{2} & \cos \frac{\phi_2}{2} + \end{bmatrix}. + +After these operations the qubit is now in the state + +.. math:: | \psi \rangle = R_y(\phi_2) R_x(\phi_1) | 0 \rangle. + +Finally, we measure the expectation value :math:`\langle \psi \mid \sigma_z \mid \psi \rangle` +of the Pauli-Z operator + +.. math:: + \sigma_z = + \begin{bmatrix} 1 & 0 \\ + 0 & -1 + \end{bmatrix}. + +Using the above to calculate the exact expectation value, we find that + +.. math:: + \langle \psi \mid \sigma_z \mid \psi \rangle + = \langle 0 \mid R_x(\phi_1)^\dagger R_y(\phi_2)^\dagger \sigma_z R_y(\phi_2) R_x(\phi_1) \mid 0 \rangle + = \cos(\phi_1)\cos(\phi_2). + +Depending on the circuit parameters :math:`\phi_1` and :math:`\phi_2,` the +output expectation lies between :math:`1` (if :math:`\left|\psi\right\rangle = \left|0\right\rangle`) +and :math:`-1` (if :math:`\left|\psi\right\rangle = \left|1\right\rangle`). +""" + +############################################################################## +# +# Let's see how we can easily implement and optimize this circuit using PennyLane. +# +# Importing PennyLane and NumPy +# ----------------------------- +# +# The first thing we need to do is import PennyLane, as well as the wrapped version +# of NumPy provided by Jax. + +import pennylane as qml +from jax import numpy as np +import jax + + +############################################################################## +# Creating a device +# ----------------- +# +# Before we can construct our quantum node, we need to initialize a **device**. +# +# .. admonition:: Definition +# :class: defn +# +# Any computational object that can apply quantum operations and return a measurement value +# is called a quantum **device**. +# +# In PennyLane, a device could be a hardware device (take a look at our `plugins `_), or a software simulator (such as our high performance simulator `PennyLane-Lightning `_). +# +# .. tip:: +# +# *Devices are loaded in PennyLane via the function* :func:`~.pennylane.device` +# +# +# PennyLane supports devices using both the qubit model of quantum computation and devices +# using the CV model of quantum computation. In fact, even a hybrid computation containing +# both qubit and CV quantum nodes is possible; see the +# :ref:`hybrid computation example ` for more details. +# +# For this tutorial, we are using the qubit model, so let's initialize the ``'lightning.qubit'`` device +# provided by PennyLane. + +dev1 = qml.device("lightning.qubit", wires=1) + +############################################################################## +# For all devices, :func:`~.pennylane.device` accepts the following arguments: +# +# * ``name``: the name of the device to be loaded +# * ``wires``: the number of subsystems to initialize the device with +# +# Here, as we only require a single qubit for this example, we set ``wires=1``. + +############################################################################## +# Constructing the QNode +# ---------------------- +# +# Now that we have initialized our device, we can begin to construct a +# **quantum node** (or QNode). +# +# +# .. admonition:: Definition +# :class: defn +# +# QNodes are an abstract encapsulation of a quantum function, described by a +# quantum circuit. QNodes are bound to a particular quantum device, which is +# used to evaluate expectation and variance values of this circuit. +# +# .. tip:: +# +# *QNodes can be constructed via the* :class:`~.pennylane.QNode` +# *class, or by using the provided* :func:`~.pennylane.qnode` decorator. +# +# First, we need to define the quantum function that will be evaluated in the QNode: + + +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=0) + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# This is a simple circuit, matching the one described above. +# Notice that the function ``circuit()`` is constructed as if it were any +# other Python function; it accepts a positional argument ``params``, which may +# be a list, tuple, or array, and uses the individual elements for gate parameters. +# +# However, quantum functions are a **restricted subset** of Python functions. +# For a Python function to also be a valid quantum function, there are some +# important restrictions: +# +# * **Quantum functions must contain quantum operations, one operation per line, +# in the order in which they are to be applied.** +# +# In addition, we must always specify the subsystem the operation applies to, +# by passing the ``wires`` argument; this may be a list or an integer, depending +# on how many wires the operation acts on. +# +# For a full list of quantum operations, see :doc:`the documentation `. +# +# * **Quantum functions must return either a single or a tuple of measured observables**. +# +# As a result, the quantum function always returns a classical quantity, allowing +# the QNode to interface with other classical functions (and also other QNodes). +# +# For a full list of observables, see :doc:`the documentation `. +# The documentation also provides details on supported :doc:`measurement return types `. +# +# .. note:: +# +# Certain devices may only support a subset of the available PennyLane +# operations/observables, or may even provide additional operations/observables. +# Please consult the documentation for the plugin/device for more details. +# +# Once we have written the quantum function, we convert it into a :class:`~.pennylane.QNode` running +# on device ``dev1`` by applying the :func:`~.pennylane.qnode` decorator. +# **directly above** the function definition: + + +@qml.qnode(dev1) +def circuit(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=0) + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# Thus, our ``circuit()`` quantum function is now a :class:`~.pennylane.QNode`, which will run on +# device ``dev1`` every time it is evaluated. +# +# To evaluate, we simply call the function with some appropriate numerical inputs: + +params = np.array([0.54, 0.12]) +print(circuit(params)) + +############################################################################## +# Calculating quantum gradients +# ----------------------------- +# +# The gradient of the function ``circuit``, encapsulated within the ``QNode``, +# can be evaluated by utilizing the same quantum +# device (``dev1``) that we used to evaluate the function itself. +# +# PennyLane incorporates both analytic differentiation, as well as numerical +# methods (such as the method of finite differences). Both of these are done +# automatically. +# +# We can differentiate by using the `jax.grad` function. +# This returns another function, representing the gradient (i.e., the vector of +# partial derivatives) of ``circuit``. The gradient can be evaluated in the same +# way as the original function: + +dcircuit = jax.grad(circuit, argnums=0) + +############################################################################## +# The function `jax.grad` itself **returns a function**, representing +# the derivative of the QNode with respect to the argument specified in ``argnums``. +# In this case, the function ``circuit`` takes one argument (``params``), so we +# specify ``argnums=0``. Because the argument has two elements, the returned gradient +# is two-dimensional. We can then evaluate this gradient function at any point in the parameter space. + +print(dcircuit(params)) + +################################################################################ +# **A note on arguments** +# +# Quantum circuit functions, being a restricted subset of Python functions, +# can also make use of multiple positional arguments and keyword arguments. +# For example, we could have defined the above quantum circuit function using +# two positional arguments, instead of one array argument: + + +@qml.qnode(dev1) +def circuit2(phi1, phi2): + qml.RX(phi1, wires=0) + qml.RY(phi2, wires=0) + return qml.expval(qml.PauliZ(0)) + + +################################################################################ +# When we calculate the gradient for such a function, the usage of ``argnums`` +# will be slightly different. In this case, ``argnums=0`` will return the gradient +# with respect to only the first parameter (``phi1``), and ``argnums=1`` will give +# the gradient for ``phi2``. To get the gradient with respect to both parameters, +# we can use ``argnums=[0,1]``: + +phi1 = np.array(0.54) +phi2 = np.array(0.12) + +dcircuit = jax.grad(circuit2, argnums=[0, 1]) +print(dcircuit(phi1, phi2)) + +################################################################################ +# Keyword arguments may also be used in your custom quantum function. PennyLane +# does **not** differentiate QNodes with respect to keyword arguments, +# so they are useful for passing external data to your QNode. + + +################################################################################ +# Optimization +# ------------ +# +# .. admonition:: Definition +# :class: defn +# +# If using the default NumPy/Autograd interface, PennyLane provides a collection +# of optimizers based on gradient descent. These optimizers accept a cost function +# and initial parameters, and utilize PennyLane's automatic differentiation +# to perform gradient descent. +# +# .. tip:: +# +# *See* :doc:`introduction/interfaces` *for details and documentation of available optimizers* +# +# Next, let's make use of PennyLane's built-in optimizers to optimize the two circuit +# parameters :math:`\phi_1` and :math:`\phi_2` such that the qubit, originally in state +# :math:`\left|0\right\rangle,` is rotated to be in state :math:`\left|1\right\rangle.` This is equivalent to measuring a +# Pauli-Z expectation value of :math:`-1,` since the state :math:`\left|1\right\rangle` is an eigenvector +# of the Pauli-Z matrix with eigenvalue :math:`\lambda=-1.` +# +# In other words, the optimization procedure will find the weights +# :math:`\phi_1` and :math:`\phi_2` that result in the following rotation on the Bloch sphere: +# +# .. figure:: ../_static/demonstration_assets/qubit_rotation/bloch.png +# :align: center +# :width: 70% +# :target: javascript:void(0); +# +# To do so, we need to define a **cost** function. By *minimizing* the cost function, the +# optimizer will determine the values of the circuit parameters that produce the desired outcome. +# +# In this case, our desired outcome is a Pauli-Z expectation value of :math:`-1.` Since we +# know that the Pauli-Z expectation is bound between :math:`[-1, 1],` we can define our +# cost directly as the output of the QNode: + + +def cost(x): + return circuit(x) + + +################################################################################ +# To begin our optimization, let's choose small initial values of :math:`\phi_1` and :math:`\phi_2:` + +init_params = np.array([0.011, 0.012]) +print(cost(init_params)) + +################################################################################ +# We can see that, for these initial parameter values, the cost function is close to :math:`1.` +# +# Finally, we use an optimizer to update the circuit parameters for 100 steps. We can use the +# gradient descent optimizer: + +import jaxopt + +# initialise the optimizer +opt = jaxopt.GradientDescent(cost, stepsize=0.4, acceleration = False) + +# set the number of steps +steps = 100 +# set the initial parameter values +params = init_params +opt_state = opt.init_state(params) + +for i in range(steps): + # update the circuit parameters + params, opt_state = opt.update(params, opt_state) + + if (i + 1) % 5 == 0: + print("Cost after step {:5d}: {: .7f}".format(i + 1, cost(params))) + +print("Optimized rotation angles: {}".format(params)) + +################################################################################ +# We can see that the optimization converges after approximately 40 steps. +# +# Substituting this into the theoretical result :math:`\langle \psi \mid \sigma_z \mid \psi \rangle = \cos\phi_1\cos\phi_2,` +# we can verify that this is indeed one possible value of the circuit parameters that +# produces :math:`\langle \psi \mid \sigma_z \mid \psi \rangle=-1,` resulting in the qubit being rotated +# to the state :math:`\left|1\right\rangle.` +# +# .. note:: +# +# Some optimizers, such as :class:`~.pennylane.AdagradOptimizer`, have +# internal hyperparameters that are stored in the optimizer instance. These can +# be reset using the :meth:`reset` method. +# +# Continue on to the next tutorial, :ref:`gaussian_transformation`, to see a similar example using +# continuous-variable (CV) quantum nodes. +# +# diff --git a/demonstrations_v2/tutorial_qubit_rotation/metadata.json b/demonstrations_v2/tutorial_qubit_rotation/metadata.json new file mode 100644 index 0000000000..b9989deda2 --- /dev/null +++ b/demonstrations_v2/tutorial_qubit_rotation/metadata.json @@ -0,0 +1,44 @@ +{ + "title": "Basic tutorial: qubit rotation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_basic_cubic_rotation.png" + } + ], + "seoDescription": "To see how PennyLane allows the easy construction and optimization of quantum functions, let's consider the 'hello world' of QML: qubit rotation.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "plugins_hybrid", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_gaussian_transformation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_state_preparation", + "weight": 1.0 + } + ], + "hardware": [], + "discussionForumUrl": "https://discuss.pennylane.ai/t/basic-tutorial-qubit-rotation/7338" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qubit_rotation/requirements.in b/demonstrations_v2/tutorial_qubit_rotation/requirements.in new file mode 100644 index 0000000000..375a0e28cc --- /dev/null +++ b/demonstrations_v2/tutorial_qubit_rotation/requirements.in @@ -0,0 +1,4 @@ +jax +jaxlib +jaxopt +pennylane diff --git a/demonstrations_v2/tutorial_qubit_tapering/demo.py b/demonstrations_v2/tutorial_qubit_tapering/demo.py new file mode 100644 index 0000000000..4ef36de092 --- /dev/null +++ b/demonstrations_v2/tutorial_qubit_tapering/demo.py @@ -0,0 +1,309 @@ +r""" + +Qubit tapering +============== + +.. meta:: + :property="og:description": Learn how to taper off qubits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qubit_tapering.png + +.. related:: + tutorial_quantum_chemistry Building molecular Hamiltonians + tutorial_vqe A brief overview of VQE + tutorial_givens_rotations Givens rotations for quantum chemistry + tutorial_adaptive_circuits Adaptive circuits for quantum chemistry + tutorial_differentiable_HF Differentiable Hartree-Fock + +*Authors: Utkarsh Azad and Soran Jahangiri. Posted: 16 May 2022. Last updated: 08 Nov 2022* + +The performance of variational quantum algorithms is considerably limited by the number of qubits +required to represent wave functions. In the context of quantum chemistry, this +limitation hinders the treatment of large molecules with algorithms such as the :doc:`variational quantum +eigensolver (VQE) `. Several approaches have been developed to reduce the qubit requirements for +quantum chemistry calculations. In this tutorial, we demonstrate the symmetry-based qubit +tapering approach which allows reducing the number of qubits required to perform molecular quantum +simulations based on the :math:`\mathbb{Z}_2` symmetries present in molecular Hamiltonians +[#bravyi2017]_ [#setia2019]_. + +A molecular Hamiltonian in the qubit basis can be expressed as a linear combination of Pauli words +as + +.. math:: H = \sum_{i=1}^r h_i P_i, + +where :math:`h_i` is a real coefficient and :math:`P_i` is a tensor product of Pauli and +identity operators acting on :math:`M` qubits + +.. math:: P_i \in \pm \left \{ I, X, Y, Z \right \} ^ {\bigotimes M}. + +The main idea in the symmetry-based qubit tapering approach is to find a unitary operator :math:`U` +that transforms :math:`H` to a new Hamiltonian :math:`H'` which has the same eigenvalues as +:math:`H` + +.. math:: H' = U^{\dagger} H U = \sum_{i=1}^r c_i \mu_i, + +such that each :math:`\mu_i` term in the new Hamiltonian always acts trivially, e.g., with an +identity or a Pauli operator, on a set of qubits. This allows tapering-off those qubits from the +Hamiltonian. + +For instance, consider the following Hamiltonian + +.. math:: H' = Z_0 X_1 - X_1 + Y_0 X_1, + +where all terms in the Hamiltonian act on the second qubit with the :math:`X` operator. It is +straightforward to show that each term in the Hamiltonian commutes with :math:`I_0 X_1` and the +ground-state eigenvector of :math:`H'` is also an eigenvector of :math:`I_0 X_1` with eigenvalues +:math:`\pm 1.` We can also rewrite the Hamiltonian as + +.. math:: H' = (Z_0 I_1 - I_0 I_1 + Y_0 I_1) I_0 X_1, + +which gives us + +.. math:: H'|\psi \rangle = \pm1 (Z_0 I_1 - I_0 I_1 + Y_0 I_1)|\psi \rangle, + +where :math:`|\psi \rangle` is an eigenvector of :math:`H'.` This means that the Hamiltonian +:math:`H` can be simplified as + +.. math:: H_{tapered} = \pm1 (Z_0 - I_0 + Y_0). + +The tapered Hamiltonian :math:`H_{tapered}` has the eigenvalues + +.. math:: [-2.41421, 0.41421], + +and + +.. math:: [2.41421, -0.41421], + +depending on the value of the :math:`\pm 1` prefactor. The eigenvalues of the original Hamiltonian +:math:`H` are + +.. math:: [2.41421, -2.41421, 0.41421, -0.41421], + +which are thus reproduced by the tapered Hamiltonian. + +More generally, we can construct the unitary :math:`U` such that each :math:`\mu_i` term acts with a +Pauli-X operator on a set of qubits +:math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` where :math:`j` is the qubit label. +This guarantees that each term of the transformed Hamiltonian commutes with each of the Pauli-X +operators applied to the :math:`j`-th qubit: + +.. math:: [H', X^j] = 0, + +and the eigenvectors of the transformed Hamiltonian :math:`H'` are also eigenvectors of each of the +:math:`X^{j}` operators. Then we can factor out all of the :math:`X^{j}` operators from the +transformed Hamiltonian and replace them with their eigenvalues :math:`\pm 1.` This gives us a +set of tapered Hamiltonians depending on which eigenvalue :math:`\pm 1` we chose for each of the +:math:`X^{j}` operators. For instance, in the case of two tapered qubits, we have four eigenvalue +sectors: :math:`[+1, +1]`, :math:`[-1, +1]`, :math:`[+1, -1],` :math:`[-1, -1].` In these tapered +Hamiltonians, the set of :math:`\left \{ j \right \}, j \in \left \{ l, ..., k \right \}` qubits +are eliminated. For tapered molecular Hamiltonians, it is possible to determine the optimal sector +of the eigenvalues that corresponds to the ground state. This is explained in more detail in the +following sections. + +The unitary operator :math:`U` can be constructed as a +`Clifford `__ operator [#bravyi2017]_ + +.. math:: U = \Pi_j \left [\frac{1}{\sqrt{2}} \left (X^{q(j)} + \tau_j \right) \right], + +where :math:`\tau` denotes the generators of the symmetry group of :math:`H` and +:math:`X^{q}` operators act on those qubits that will be ultimately tapered off from +the Hamiltonian. The symmetry group of the Hamiltonian is defined as an Abelian group of Pauli words that commute +with each term in the Hamiltonian (excluding :math:`−I`). The +`generators `__ of the symmetry group are +those elements of the group that can be combined, along with their inverses, to create any other +member of the group. + +Let's use the qubit tapering method and obtain the ground state energy of the `Helium hydride +cation `__ :math:`\textrm{HeH}^+.` + +Tapering the molecular Hamiltonian +---------------------------------- + +In PennyLane, a :doc:`molecular Hamiltonian ` can be created by specifying the atomic symbols and +coordinates. +""" +import pennylane as qml +from pennylane import numpy as np + +symbols = ["He", "H"] +geometry = np.array([[0.00000000, 0.00000000, -0.87818361], + [0.00000000, 0.00000000, 0.87818362]]) + +molecule = qml.qchem.Molecule(symbols, geometry, charge=1) +H, qubits = qml.qchem.molecular_hamiltonian(molecule) +H + +############################################################################## +# This Hamiltonian contains 27 terms where each term acts on up to four qubits. +# +# We can now obtain the symmetry generators and the :math:`X^{j}` operators that are +# used to construct the unitary :math:`U` operator that transforms the :math:`\textrm{HeH}^+` +# Hamiltonian. In PennyLane, these are constructed by using the +# :func:`~.pennylane.symmetry_generators` and :func:`~.pennylane.paulix_ops` functions. + +generators = qml.symmetry_generators(H) +paulixops = qml.paulix_ops(generators, qubits) + +for idx, generator in enumerate(generators): + print(f"generator {idx+1}: {generator}, paulix_op: {paulixops[idx]}") + +############################################################################## +# Once the operator :math:`U` is applied, each of the Hamiltonian terms will act on the qubits +# :math:`q_2, q_3` either with the identity or with a Pauli-X operator. For each of these qubits, +# we can simply replace the Pauli-X operator with one of its eigenvalues :math:`+1` or :math:`-1.` +# This results in a total number of :math:`2^k` Hamiltonians, where :math:`k` is the number of +# tapered-off qubits and each Hamiltonian corresponds to one eigenvalue sector. The optimal sector +# corresponding to the ground-state energy of the molecule can be obtained by using the +# :func:`~.pennylane.qchem.optimal_sector` function. + + +n_electrons = 2 +paulix_sector = qml.qchem.optimal_sector(H, generators, n_electrons) +print(paulix_sector) + +############################################################################## +# The optimal eigenvalues are :math:`-1, -1` for qubits :math:`q_2, q_3,` respectively. We can now +# build the tapered Hamiltonian with the :func:`~.pennylane.taper` function which +# constructs the operator :math:`U,` applies it to the Hamiltonian and finally tapers off the +# qubits :math:`q_2, q_3` by replacing the Pauli-X operators acting on those qubits with the optimal +# eigenvalues. + +H_tapered = qml.taper(H, generators, paulixops, paulix_sector) +H_tapered_coeffs, H_tapered_ops = H_tapered.terms() +H_tapered = qml.Hamiltonian(np.real(H_tapered_coeffs), H_tapered_ops) +print(H_tapered) + +############################################################################## +# The new Hamiltonian has only 9 non-zero terms acting on only 2 qubits! We can verify that the +# original and the tapered Hamiltonian both give the correct ground state energy of the +# :math:`\textrm{HeH}^+` cation, which is :math:`-2.862595242378` Ha computed with the full +# configuration interaction (FCI) method. In PennyLane, it's possible to build a sparse matrix +# representation of Hamiltonians. This allows us to directly diagonalize them to obtain exact values +# of the ground-state energies. + +H_sparse = qml.SparseHamiltonian(H.sparse_matrix(), wires=H.wires) +H_tapered_sparse = qml.SparseHamiltonian(H_tapered.sparse_matrix(), wires=H_tapered.wires) + +print("Eigenvalues of H:\n", qml.eigvals(H_sparse, k=16)) +print("\nEigenvalues of H_tapered:\n", qml.eigvals(H_tapered_sparse, k=4)) + +############################################################################## +# Note that a second-quantized Hamiltonian is independent of the number of electrons and its +# eigenspectrum contains the energies of the neutral and charged molecules. Therefore, the +# smallest eigenvalue returned by :func:`~.pennylane.eigvals` for a molecular Hamiltonian +# might correspond to the neutral or charged molecule. While in the case of :math:`\textrm{HeH}^+,` +# qubit tapering allows specifying the optimal sector of the eigenvectors corresponding only to the +# correct number of electrons, it is generally guaranteed that the optimal sector covers all +# eigenvectors with the correct number of electrons, but may contain additional eigenvectors of +# different charge. Therefore, the ground-state energy of the :math:`\textrm{HeH}^+` cation is +# the smallest eigenvalue of the tapered Hamiltonian. +# +# Tapering the reference state +# ---------------------------- +# The ground state Hartree-Fock energy of :math:`\textrm{HeH}^+` can be computed by directly +# applying the Hamiltonians to the Hartree-Fock state. For the tapered Hamiltonian, this requires +# transforming the Hartree-Fock state with the same symmetries obtained for the original +# Hamiltonian. This reduces the number of qubits in the Hartree-Fock state to match that of the +# tapered Hamiltonian. It can be done with the :func:`~.pennylane.qchem.taper_hf` function. + +state_tapered = qml.qchem.taper_hf(generators, paulixops, paulix_sector, + num_electrons=n_electrons, num_wires=len(H.wires)) +print(state_tapered) + +############################################################################## +# Recall that the original Hartree-Fock state for the :math:`\textrm{HeH}^+` cation is +# :math:`[1 1 0 0].` We can now generate the qubit representation of these states and compute the +# Hartree-Fock energies for each Hamiltonian. + +dev = qml.device("default.qubit", wires=H.wires) +@qml.qnode(dev, interface="autograd") +def circuit(): + qml.BasisState(np.array([1, 1, 0, 0]), wires=H.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state +print(f"HF energy: {np.real(HF_energy):.8f} Ha") + +dev = qml.device("default.qubit", wires=H_tapered.wires) +@qml.qnode(dev, interface="autograd") +def circuit(): + qml.BasisState(np.array([1, 1]), wires=H_tapered.wires) + return qml.state() + +qubit_state = circuit() +HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state +print(f"HF energy (tapered): {np.real(HF_energy):.8f} Ha") + +############################################################################## +# These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. +# +# VQE simulation +# -------------- +# Finally, we can use the tapered Hamiltonian and the tapered reference state to perform a VQE +# simulation and compute the ground-state energy of the :math:`\textrm{HeH}^+` cation. We build a +# tapered variational ansatz `[3] `__ +# that prepares an entangled state by evolving the tapered Hartree-Fock state using the tapered +# particle-conserving gates, i.e., the :func:`~.pennylane.SingleExcitation` and +# :func:`~.pennylane.DoubleExcitation` operations tapered using +# :func:`~.pennylane.qchem.taper_operation`. + +singles, doubles = qml.qchem.excitations(n_electrons, len(H.wires)) +tapered_doubles = [ + qml.taper_operation(qml.DoubleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=double) for double in doubles +] +tapered_singles = [ + qml.taper_operation(qml.SingleExcitation, generators, paulixops, paulix_sector, + wire_order=H.wires, op_wires=single) for single in singles +] + +dev = qml.device("default.qubit", wires=H_tapered.wires) + +@qml.qnode(dev, interface="autograd") +def tapered_circuit(params): + qml.BasisState(state_tapered, wires=H_tapered.wires) + for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): + tapered_op(params[idx]) + return qml.expval(H_tapered) + +############################################################################## +# We define an optimizer and the initial values of the circuit parameters and optimize the circuit +# parameters with respect to the ground state energy. + +optimizer = qml.GradientDescentOptimizer(stepsize=0.5) +params = np.zeros(len(doubles) + len(singles), requires_grad=True) + +for n in range(1, 41): + params, energy = optimizer.step_and_cost(tapered_circuit, params) + if not n % 5: + print(f"n: {n}, E: {energy:.8f} Ha, Params: {params}") + +############################################################################## +# The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits +# and the number of Hamiltonian terms are significantly reduced with respect to their original +# values. +# +# Conclusions +# ----------- +# Molecular Hamiltonians possess symmetries that can be leveraged to reduce the number of qubits +# required in quantum computing simulations. This tutorial introduces a PennyLane functionality that +# can be used for qubit tapering based on :math:`\mathbb{Z}_2` symmetries. The procedure includes +# obtaining tapered Hamiltonians and tapered reference states that can be used in variational +# quantum algorithms such as VQE. +# +# References +# ---------- +# +# .. [#bravyi2017] +# +# Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme, "Tapering off qubits to +# simulate fermionic Hamiltonians". `arXiv:1701.08213 `__ +# +# .. [#setia2019] +# +# Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield, +# "Reducing qubit requirements for quantum simulation using molecular point group symmetries". +# `arXiv:1910.14644 `__ +# +# +# diff --git a/demonstrations_v2/tutorial_qubit_tapering/metadata.json b/demonstrations_v2/tutorial_qubit_tapering/metadata.json new file mode 100644 index 0000000000..652b6bd730 --- /dev/null +++ b/demonstrations_v2/tutorial_qubit_tapering/metadata.json @@ -0,0 +1,74 @@ +{ + "title": "Qubit tapering", + "authors": [ + { + "username": "whatsis" + }, + { + "username": "soran" + } + ], + "dateOfPublication": "2022-05-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_Qubit_tapering.png" + } + ], + "seoDescription": "Learn how to taper off qubits", + "doi": "", + "references": [ + { + "id": "bravyi2017", + "type": "article", + "title": "Tapering off qubits to simulate fermionic Hamiltonians", + "authors": "Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme", + "year": "2017", + "journal": "", + "url": "https://arxiv.org/abs/1701.08213" + }, + { + "id": "setia2019", + "type": "article", + "title": "Reducing qubit requirements for quantum simulation using molecular point group symmetries", + "authors": "Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.14644" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_adaptive_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_differentiable_HF", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qubit_tapering/requirements.in b/demonstrations_v2/tutorial_qubit_tapering/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_qubit_tapering/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_qubitization/demo.py b/demonstrations_v2/tutorial_qubitization/demo.py new file mode 100644 index 0000000000..f4382e3460 --- /dev/null +++ b/demonstrations_v2/tutorial_qubitization/demo.py @@ -0,0 +1,167 @@ +r"""Intro to qubitization +========================= + +Encoding a Hamiltonian into a quantum computer is a fundamental task for many applications, but the way to do it is not unique. One method that has gained special status is known as **qubitization** — a block-encoding technique that can be used to estimate eigenvalues and for many other applications. + +In this demo, we will introduce the qubitization operator and explain how to view it as a rotation operator. This perspective is useful when combining qubitization with `quantum phase estimation `_ (QPE). We will demonstrate this by implementing qubitization-based quantum phase estimation, with code provided using the :class:`~.pennylane.Qubitization` template in PennyLane. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_large_qubitization.png + :align: center + :width: 70% + :target: javascript:void(0) + +Qubitization operator +---------------------- + +Qubitization is a block-encoding technique that, as we will see, is particularly useful for tasks such as estimating eigenvalues, among other applications. +For a Hamiltonian :math:`\mathcal{H},` given in its representation via a linear combination of unitaries (LCU), the qubitization operator is defined as: + +.. math:: + + Q = \text{PSP}_{\mathcal{H}}\cdot (2|0\rangle\langle 0| - I), + +where :math:`\text{PSP}_{\mathcal{H}}` refers to the block encoding :math:`\text{Prep}_{\mathcal{H}}^{\dagger} \text{Sel}_{\mathcal{H}} \text{Prep}_{\mathcal{H}},` as explained in the `LCU PennyLane Demo `_. + +The operator :math:`Q` is also a block-encoding operator with a key property: its eigenvalues encode the eigenvalues of the Hamiltonian. +As we will soon explain in detail, if :math:`E` is an eigenvalue of :math:`\mathcal{H},` then :math:`e^{i\arccos(E/\lambda)}` is an +eigenvalue of :math:`Q`, where :math:`\lambda` is a known normalization factor. This property is very useful because it means that we can use the `QPE algorithm `_ to estimate the eigenvalues of :math:`Q,` and then use them to retrieve the eigenvalues of the encoded Hamiltonian. + +This is the essence of why qubitization is attractive for applications: it provides a method to exactly encode eigenvalues of a Hamiltonian into a unitary operator. It can then be used inside quantum phase estimation to sample Hamiltonian eigenvalues. + +But where does this decomposition come from? Why are the eigenvalues encoded in this way? 🤔 We explain these concepts below. + +Block encodings +--------------- + +First, we introduce some useful concepts about block encodings. +Given a Hamiltonian :math:`\mathcal{H},` we define as a block encoding any operator that embeds :math:`\mathcal{H}` +inside the matrix associated to the circuit (up to a normalization factor): + +.. math:: + \text{Block Encoding}_\mathcal{H} \rightarrow \begin{bmatrix} \mathcal{H} / \lambda & \cdot \\ \cdot & \cdot \end{bmatrix}. + +Given an eigenvector of :math:`\mathcal{H}` such that :math:`\mathcal{H}| \phi \rangle = E | \phi \rangle,` any :math:`\text{Block Encoding}_\mathcal{H}` +generates a state: + +.. math:: + |\Psi\rangle = \frac{E}{\lambda}|0\rangle |\phi\rangle + \sqrt{1 - \left( \frac{E}{\lambda}\right)^2} |\phi^{\perp}\rangle, + +where :math:`|\phi^{\perp}\rangle` is a state orthogonal to :math:`|0\rangle |\phi\rangle,` +and :math:`E` is the eigenvalue. The advantage of expressing :math:`|\Psi\rangle` as the sum of two orthogonal states is that it can be represented +in a two-dimensional space — an idea that we have explored in our `amplitude amplification `_ demo. The state :math:`|\Psi\rangle` forms an angle :math:`\theta =\arccos {\frac{E}{\lambda}}` with respect to the axis defined by the initial state :math:`|0\rangle |\phi\rangle,` as shown in the image below. + +.. figure:: ../_static/demonstration_assets/qubitization/qubitization0.jpeg + :align: center + :width: 50% + :target: javascript:void(0) + +Qubitization as a rotation +--------------------------- + +Any block-encoding operator manages to transform the state :math:`|0\rangle |\phi\rangle` into the state :math:`|\Psi\rangle.` +The qubitization operator does this by applying a rotation in that two-dimensional subspace by an angle of :math:`\theta=\arccos {\frac{E}{\lambda}}.` +The advantage of a rotation operator is that the angle :math:`\theta` appears directly in the eigenvalues, which are simply :math:`e^{\pm i\theta}.` Therefore, if the rotation angle encodes useful information, it can be retrieved by estimating the phase of the rotation operator, for example using QPE. + +We now show that the qubitization operator is a rotation by using the same idea of amplitude amplification: +two reflections are equivalent to one rotation. These reflections correspond to :math:`(2|0\rangle\langle 0| - I)` and :math:`\text{PSP}_{\mathcal{H}},` which together define our qubitization operator. +Recall that an operator :math:`U` is a reflection if :math:`U^2=I.` + +As an example, let’s take :math:`|\Psi\rangle` as the initial state to visualize the rotation. The first reflection, given by :math:`(2|0\rangle\langle 0| - I)`, mirrors the state around the axis defined by :math:`|0\rangle |\phi\rangle.` This occurs because the operator flips the sign of any component not aligned with :math:`|0\rangle` in the first register. The result is to prepare state :math:`|\Psi_1\rangle,` as illustrated below: + +.. figure:: ../_static/demonstration_assets/qubitization/qubitization2.jpeg + :align: center + :width: 50% + :target: javascript:void(0) + +The block encoding operator :math:`\text{PSP}_{\mathcal{H}}` performs a second reflection along the line that bisects the angle between :math:`|\Psi\rangle` and :math:`|0\rangle |\phi\rangle.` Let's now examine the effect this reflection has on the previous state :math:`|\Psi_1\rangle:` + +.. figure:: ../_static/demonstration_assets/qubitization/qubitization3.jpeg + :align: center + :width: 60% + :target: javascript:void(0) + +After applying the reflection, the new state :math:`|\Psi_2\rangle` is rotated by :math:`\theta` degrees relative to the initial state :math:`|\Psi\rangle.` This shows that the qubitization operator successfully creates a rotation of :math:`\theta` degrees within the subspace. + +The term "qubitization" comes from the fact that this process occurs within a two-dimensional subspace, which can be viewed as a qubit. For each eigenstate of the Hamiltonian, the qubitization operator acts within this two-dimensional space, effectively treating it as a qubit. This is why we say the system has been *qubitized*. + +Qubitization in PennyLane +-------------------------- + +We now describe how to implement qubitization-based `quantum phase estimation `_ to sample eigenvalues of a Hamiltonian. + +First, let's define a simple Hamiltonian to use as an example: +""" + +import pennylane as qml + +H = -0.4 * qml.Z(0) + 0.3 * qml.Z(1) + 0.4 * qml.Z(0) @ qml.Z(1) + +print(qml.matrix(H).real) + +############################################################################## +# We have chosen an operator that is diagonal in the computational basis because it is easy to identify +# eigenvalues and eigenvectors, but the algorithm works with any Hamiltonian. We are going to take +# the eigenstate :math:`|\phi\rangle = |11\rangle` as input and try to estimate its eigenvalue :math:`E = 0.5` using +# this technique. +# +# In PennyLane, the qubitization operator can be easily constructed using the built-in class :class:`~.pennylane.Qubitization`. You simply need to provide the Hamiltonian and the control qubits that define the block encoding. The number of control wires is :math:`⌈\log_2 k⌉,` where :math:`k` is the number of terms in the `LCU representation `_ of the Hamiltonian. +# We will make use of the built-in :class:`~.pennylane.QuantumPhaseEstimation` operator to easily apply the algorithm: + +control_wires = [2, 3] +estimation_wires = [4, 5, 6, 7, 8, 9] + +dev = qml.device("default.qubit") + + +@qml.qnode(dev) +def circuit(): + # Initialize the eigenstate |11⟩ + for wire in [0, 1]: + qml.X(wire) + + # Apply QPE with the qubitization operator + qml.QuantumPhaseEstimation( + qml.Qubitization(H, control_wires), estimation_wires=estimation_wires + ) + + return qml.probs(wires=estimation_wires) + + +############################################################################## +# Let's run the circuit and plot the estimated eigenvalue: + +import matplotlib.pyplot as plt + +plt.style.use("pennylane.drawer.plot") + +results = circuit() + +bit_strings = [f"0.{x:0{len(estimation_wires)}b}" for x in range(len(results))] + +plt.bar(bit_strings, results) +plt.xlabel("theta") +plt.ylabel("probability") +plt.xticks(range(0, len(results), 3), bit_strings[::3], rotation="vertical") + +plt.subplots_adjust(bottom=0.3) +plt.show() + +############################################################################## +# The two peaks obtained correspond to the values :math:`|\theta\rangle` and :math:`|-\theta\rangle.` +# Finally, with some post-processing, we can determine the value of :math:`E:` + +import numpy as np + +lambda_ = sum([abs(coeff) for coeff in H.terms()[0]]) + +# Simplification by estimating theta with the peak value +print("E = ", lambda_ * np.cos(2 * np.pi * np.argmax(results) / 2 ** (len(estimation_wires)))) + +############################################################################## +# Great! We successfully approximated the correct eigenvalue of :math:`E = 0.5.` 🚀 +# +# Conclusion +# ---------------- +# +# In this demo, we explored the concept of qubitization and one of its applications. To achieve this, we combined several key concepts, including `block encoding `_, `quantum phase estimation `_, and `amplitude amplification `_. This algorithm serves as a foundation for more advanced techniques like `quantum singular value transformation `_. We encourage you to continue studying these methods and apply them in your research. +# diff --git a/demonstrations_v2/tutorial_qubitization/metadata.json b/demonstrations_v2/tutorial_qubitization/metadata.json new file mode 100644 index 0000000000..8a08ee9039 --- /dev/null +++ b/demonstrations_v2/tutorial_qubitization/metadata.json @@ -0,0 +1,50 @@ +{ + "title": "Intro to qubitization", + "authors": [ + { + "username": "KetPuntoG" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2024-09-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qubitization.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qubitization.png" + } + ], + "seoDescription": "Learn how to use, visualize and apply qubitization, a block-encoding technique in quantum computing.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_amplitude_amplification", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qpe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_lcu_blockencoding", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qubitization/requirements.in b/demonstrations_v2/tutorial_qubitization/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_qubitization/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py new file mode 100644 index 0000000000..9762791c52 --- /dev/null +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py @@ -0,0 +1,405 @@ +r""" + +Qutrits and quantum algorithms +============================== + +.. meta:: + :property="og:description": Learn how to interpret the Bernstein-Vazirani algorithm with qutrits + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_qutrits_bernstein_vazirani.png + +.. related:: + + +*Author: Guillermo Alonso-Linaje — Posted: 9 May 2023. Last updated: 9 May 2023.* + +A qutrit is a basic quantum unit that can exist in a superposition of three possible quantum states, represented as :math:`|0\rangle`, :math:`|1\rangle`, and :math:`|2\rangle,` which functions as a generalization of the qubit. +There are many problems to which we can apply these units, among which we can highlight an improved decomposition of the Toffoli gate. +Using only qubits, it would take at least 6 CNOTs to decompose the gate, whereas with qutrits it would be enough to use 3 [#toffoli_qutrits]_. +This is one of the reasons why it is important to develop the intuition behind this basic unit of information, to see where qutrits can provide an advantage. The goal of this demo is to start working with qutrits from an algorithmic point of view. To do so, we will start with the Bernstein–Vazirani algorithm, which we will initially explore using qubits, and later using qutrits. + + + +Bernstein–Vazirani algorithm +------------------------------ + +The Bernstein–Vazirani algorithm is a quantum algorithm developed by Ethan Bernstein and Umesh Vazirani [#bv]_. +It was one of the first examples that demonstrated an exponential advantage of a quantum computer over a traditional one. So, in this first section we will understand the problem that they tackled. + + +Suppose there is some hidden bit string "a" that we are trying to learn, and that we have access to a function :math:`f(\vec{x})` that implements the following scalar product: + +.. math:: + f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 2, + +where :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are bit strings of length :math:`n` with :math:`a_i, x_i \in \{0,1\}`. Our challenge will be to discover the hidden value of :math:`\vec{a}` by using the function :math:`f.` We don't know anything about :math:`\vec{a}` so the only thing we can do is to evaluate :math:`f` at different points :math:`\vec{x}` with the idea of gaining hidden information. + + +To give an example, let's imagine that we take :math:`\vec{x}=(1,0,1)` and get the value :math:`f(\vec{x}) = 0`. Although it may not seem obvious, knowing the structure that :math:`f` has, this gives us some information about :math:`\vec{a}.` In this case, :math:`a_0` and :math:`a_2` have the same value. This is because taking that value of :math:`\vec{x}`, the function will be equivalent to :math:`a_0 + a_2 \pmod 2,` which will only take the value 0 if they are equal. +I invite you to take your time to think of a possible strategy (at the classical level) in order to determine :math:`\vec{a}` with the minimum number of evaluations of the function :math:`f.` + +The optimal solution requires only :math:`n` calls to the function! Let's see how we can do this. +Knowing the form of :math:`\vec{a}` and :math:`\vec{x},` we can rewrite :math:`f` as: + +.. math:: + f(\vec{x})=\sum_{i=0}^{n-1}a_ix_i \pmod 2. + +The strategy will be to deduce one element of :math:`\vec{a}` with each call to the function. Imagine that we want to determine the value :math:`a_i.` We can simply choose :math:`\vec{x}` as a vector of all zeros except a one in the i-th position, since in this case: + + + +.. math:: + f(\vec{x})= 0\cdot a_0 + 0\cdot a_1 + ... + 1\cdot a_i + ... + 0\cdot a_{n-1} \pmod 2 \quad= a_i. + +It is trivial to see, therefore, that :math:`n` evaluations of :math:`f` are needed. The question is: can we do it more efficiently with a quantum computer? The answer is yes, and in fact, we only need to make one call to the function! + +The first step is to see how we can represent this statement in a circuit. In this case, we will assume that we have an oracle :math:`U_f` that encodes the function, as we can see in the picture below. + + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/oracle_qutrit.jpg + :scale: 35% + :alt: Oracle definition. + :align: center + + Oracle representation of the function. + + +In general, :math:`U_f` sends the state :math:`|\vec{x} \rangle |y\rangle` to the state :math:`| \vec{x} \rangle |y + \vec{a} \cdot \vec{x} \pmod{2} \rangle.` + +Suppose, for example, that :math:`\vec{a}=[0,1,0]`. Then :math:`U_f|111\rangle |0\rangle = |111\rangle|1\rangle`, since we are evaluating :math:`f` at the point :math:`\vec{x} = [1,1,1]`. The scalar product between the two values is :math:`1,` so the last qubit of the output will take the value :math:`1.` + +The Bernstein–Vazirani algorithm makes use of this oracle according to the following circuit: + +.. figure:: ../_static/demonstration_assets/qutrits_bernstein_vazirani/bernstein_vazirani_algorithm.jpg + :scale: 35% + :alt: Bernstein-Vazirani's algorithm + :align: center + + Bernstein–Vazirani algorithm. + + +What we can see is that, by simply using Hadamard gates before and after the oracle, after a single run, the output of the circuit is exactly the hidden value of :math:`\vec{a}.` Let's do a little math to verify that this is so. + +First, the input to our circuit is :math:`|0001\rangle.` The second step is to apply Hadamard gates to this state, and for this we must use the following property: + +.. math:: + H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{2^n}}\sum_{\vec{z} \in \{0,1\}^n}(-1)^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. + +Taking as input the value :math:`|0001\rangle,` we obtain the state + +.. math:: + |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{z \in \{0,1\}^3}|\vec{z}\rangle\right)\left(\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +As you can see, we have separated the first three qubits from the fourth for clarity. +If we now apply our operator :math:`U_f,` + +.. math:: + |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle\frac{|\vec{a}\cdot\vec{z} \pmod 2\rangle-|1 + \vec{a}\cdot\vec{z} \pmod 2\rangle}{\sqrt{2}}\right). + +Depending on the value of :math:`f(\vec{x}),` the final part of the expression can take two values and it can be checked that + +.. math:: + |\phi_2\rangle = \frac{1}{\sqrt{2^3}}\left(\sum_{\vec{z} \in \{0,1\}^3}|\vec{z}\rangle(-1)^{\vec{a}\cdot\vec{z}}\frac{|0\rangle-|1\rangle}{\sqrt{2}}\right). + +This is because, if :math:`\vec{a}\cdot\vec{z}` takes the value :math:`0`, we will have the :math:`\frac{|0\rangle - |1\rangle}{\sqrt{2}}`, and if it takes the value :math:`1,` the result will be :math:`\frac{|1\rangle - |0\rangle}{\sqrt{2}} = - \frac{|0\rangle - |1\rangle}{\sqrt{2}}.` Therefore, by calculating :math:`(-1)^{\vec{a}\cdot\vec{z}}` we cover both cases. +After this, we can include the :math:`(-1)^{\vec{a}\cdot\vec{z}}` factor in the :math:`|\vec{z}\rangle` term and disregard the last qubit, since we are not going to use it again: + +.. math:: + |\phi_2\rangle =\frac{1}{\sqrt{2^3}}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}|\vec{z}\rangle. + +Note that you cannot always disregard a qubit. In cases where there is entanglement with other qubits that is not possible, but in this case the state is separable. + +Finally, we will reapply the property of the first step to calculate the result after using the Hadamard: + +.. math:: + |\phi_3\rangle = H^{\otimes 3}|\phi_2\rangle = \frac{1}{2^3}\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1\}^3}(-1)^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). + +Rearranging this expression, we obtain: + +.. math:: + |\phi_3\rangle = \frac{1}{2^3}\sum_{\vec{y} \in \{0,1\}^3}\left(\sum_{\vec{z} \in \{0,1\}^3}(-1)^{\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. + +Perfect! The only thing left to check is that, in fact, the previous state is exactly :math:`|\vec{a}\rangle`. It may seem complicated, but I invite you to demonstrate it by showing that :math:`\langle \vec{a}|\phi_3\rangle = 1.` Let's go to the code and check that it works. + +Algorithm coding with qubits +------------------------------ + +We will first code the classical solution. We will do this inside a quantum circuit to understand how to use the oracle, but we are really just programming the qubits as bits. + +""" + + +import pennylane as qml + +dev = qml.device("default.qubit", wires = 4, shots = 1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.CNOT(wires=[1, 3]) + qml.CNOT(wires=[2 ,3]) + + +@qml.qnode(dev) +def circuit0(): + """Circuit used to derive a0""" + + + # Initialize x = [1,0,0] + qml.PauliX(wires = 0) + + # Apply our oracle + + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + # Circuit used to derive a1 + + # Initialize x = [0,1,0] + qml.PauliX(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + # Circuit used to derive a2 + # Initialize x = [0,0,1] + qml.PauliX(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qubit + return qml.sample(wires = 3) + +# We run for x = [1,0,0] +a0 = circuit0() + +# We run for x = [0,1,0] +a1 = circuit1() + +# We run for x = [0,0,1] +a2 = circuit2() + +print(f"The value of 'a' is [{a0},{a1},{a2}]") + +############################################################################## +# +# In this case, with 3 queries (:math:`n=3`), we have discovered the value of :math:`\vec{a}.` Let's run the Bernstein–Vazirani subroutine (using qubits as qubits this time) to check that one call is enough: + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.PauliX(wires = 3) + + # We run the Hadamards + for i in range(4): + qml.Hadamard(wires = i) + + # We apply our function + Uf() + + # We run the Hadamards + for i in range(3): + qml.Hadamard(wires = i) + + # We measure the first 3 qubits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + + +############################################################################## +# Great! Everything works as expected, and we have successfully executed the Bernstein–Vazirani algorithm. +# It is important to note that, because of how we defined our device, we are only using a single shot to find this value! +# +# Generalization to qutrits +# ------------------------------ +# +# To make things more interesting, let's imagine a new scenario. We are given a function of the form :math:`f(\vec{x}) := \vec{a}\cdot\vec{x} \pmod 3` where, :math:`\vec{a}=(a_0,a_1,...,a_{n-1})` and :math:`\vec{x}=(x_0,x_1,...,x_{n-1})` are strings of length :math:`n` with :math:`a_i, x_i \in \{0,1,2\}`. How can we minimize the number of calls to the function to discover :math:`\vec{a}?` In this case, the classical procedure to detect the value of :math:`\vec{a}` is the same as in the case of qubits: we will evaluate the output of the inputs :math:`[1,0,0],` :math:`[0,1,0]` and :math:`[0,0,1].` +# +# But how can we work with these kinds of functions in a simple way? To do this we must use a qutrit and its operators. +# By using this new unit of information and unlocking the third orthogonal state, we will have states represented with a vector of dimension :math:`3^n` and the operators will be :math:`3^n \times 3^n` matrices where :math:`n` is the number of qutrits. +# Specifically, we will use the :class:`~.pennylane.TShift` gate, which is equivalent to the :class:`~.pennylane.PauliX` gate for qutrits. It has the following property: +# +# .. math:: +# \text{TShift}|0\rangle = |1\rangle +# +# .. math:: +# \text{TShift}|1\rangle = |2\rangle +# +# .. math:: +# \text{TShift}|2\rangle = |0\rangle +# +# This means we can use this gate to initialize each of the states. +# Another gate that we will use for the oracle definition is the :class:`~.pennylane.TAdd` gate, which is the generalization of the :class:`~.pennylane.Toffoli` gate for qutrits. +# These generalizations simply adjust the addition operation to be performed in modulo 3 instead of modulo 2. +# So, with these ingredients, we are ready to go to the code. + +dev = qml.device("default.qutrit", wires=4, shots=1) + +def Uf(): + # The oracle in charge of encoding a hidden "a" value. + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [1,3]) + qml.TAdd(wires = [2,3]) + +@qml.qnode(dev) +def circuit0(): + + # Initialize x = [1,0,0] + qml.TShift(wires = 0) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit1(): + + # Initialize x = [0,1,0] + qml.TShift(wires = 1) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +@qml.qnode(dev) +def circuit2(): + + # Initialize x = [0,0,1] + qml.TShift(wires = 2) + + # We apply our oracle + Uf() + + # We measure the last qutrit + return qml.sample(wires = 3) + +# Run to obtain the three trits of a +a0 = circuit0() +a1 = circuit1() +a2 = circuit2() + + +print(f"The value of a is [{a0},{a1},{a2}]") + +############################################################################## +# +# The question is, can we perform the same procedure as we have done before to find :math:`\vec{a}` using a single shot? The Hadamard gate also generalizes to qutrits (also denoted as :class:`~.pennylane.THadamard`), so we could try to simply substitute it and see what happens! +# +# +# The definition of the Hadamard gate in this space is: +# +# .. math:: +# \text{THadamard}=\frac{-i}{\sqrt{3}}\begin{pmatrix} +# 1 & 1 & 1\\ +# 1 & w & w^2\\ +# 1 & w^2 & w +# \end{pmatrix}, +# +# where :math:`w = e^{\frac{2 \pi i}{3}}.` +# Let's go to the code and see how to run this in PennyLane. + + +@qml.qnode(dev) +def circuit(): + + # We initialize to |0001> + qml.TShift(wires = 3) + + # We run the THadamard + for i in range(4): + qml.THadamard(wires = i) + +# We run the oracle + Uf() + +# We run the THadamard again + for i in range(3): + qml.THadamard(wires = i) + + # We measure the first 3 qutrits + return qml.sample(wires = range(3)) + +a = circuit() + +print(f"The value of a is {a}") + +############################################################################## +# +# Awesome! The Bernstein–Vazirani algorithm generalizes perfectly to qutrits! Let's do the mathematical calculations again to check that it does indeed make sense. +# +# As before, the input of our circuit is :math:`|0001\rangle.` +# We will then use the Hadamard definition applied to qutrits: +# +# .. math:: +# H^{\otimes n}|\vec{x}\rangle = \frac{1}{\sqrt{3^n}}\sum_{\vec{z} \in \{0,1,2\}^n}w^{\vec{x}\cdot\vec{z}}|\vec{z}\rangle. +# +# In this case, we are disregarding the global phase of :math:`-i` for simplicity. +# Applying this to the state :math:`|0001\rangle,` we obtain +# +# .. math:: +# |\phi_1\rangle=H^{\otimes 4}|0001\rangle = H^{\otimes 3}|000\rangle\otimes H|1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{z \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0\rangle+w|1\rangle+w^2|2\rangle}{\sqrt{3}}\right). +# +# After that, we apply the operator :math:`U_f` to obtain +# +# .. math:: +# |\phi_2\rangle= U_f |\phi_1\rangle = \frac{1}{\sqrt{3^3}}\left(\sum_{\vec{z} \in \{0,1,2\}^3}|\vec{z}\rangle\frac{|0 + \vec{a}\cdot\vec{z} \pmod 3 \rangle+w|1+ \vec{a}\cdot\vec{z} \pmod 3 \rangle+w^2|2+ \vec{a}\cdot\vec{z} \pmod 3 \rangle}{\sqrt{3}}\right). +# +# Depending on the value of :math:`f(\vec{x}),` as before, we obtain three possible states: +# +# - If :math:`\vec{a}\cdot\vec{z} = 0,` we have :math:`\frac{1}{\sqrt{3}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 1,` we have :math:`\frac{w^2}{\sqrt{3}}\left(|0\rangle+|1\rangle+w|2\rangle\right).` +# - If :math:`\vec{a}\cdot\vec{z} = 2,` we have :math:`\frac{w}{\sqrt{3}}\left(|0\rangle+w^2|1\rangle+|2\rangle\right).` +# +# Based on this, we can group the three states as :math:`\frac{1}{\sqrt{3}}w^{-\vec{a}\cdot\vec{z}}\left(|0\rangle+w|1\rangle+w^2|2\rangle\right).` +# +# After this, we can enter the coefficient in the :math:`|\vec{z}\rangle` term and, as before, disregard the last qutrit, since we are not going to use it again: +# +# .. math:: +# |\phi_2\rangle =\frac{1}{\sqrt{3^3}}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}|\vec{z}\rangle. +# +# Finally, we reapply the THadamard: +# +# .. math:: +# |\phi_3\rangle := H^{\otimes 3}|\phi_2\rangle = \frac{1}{3^3}\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}}\left(\sum_{\vec{y} \in \{0,1,2\}^3}w^{\vec{z}\cdot\vec{y}}|\vec{y}\rangle\right). +# +# Rearranging this expression, we obtain: +# +# .. math:: +# |\phi_3\rangle = \frac{1}{3^3}\sum_{\vec{y} \in \{0,1,2\}^3}\left(\sum_{\vec{z} \in \{0,1,2\}^3}w^{-\vec{a}\cdot\vec{z}+\vec{y}\cdot\vec{z}}\right)|\vec{y}\rangle. +# +# +# In the same way as before, it can be easily checked that :math:`\langle \vec{a}|\phi_3\rangle = 1` and therefore, when measuring, one shot will be enough to obtain the value of :math:`\vec{a}!` +# +# Conclusion +# ---------- +# +# In this demo, we have practised the use of basic qutrit gates such as TShift or THadamard by applying the Bernstein–Vazirani algorithm. In this case, the generalization has been straightforward and we have found that it makes mathematical sense, but we cannot always substitute qubit gates for qutrit gates as we have seen in the demo. To give an easy example of this, we know the property that :math:`X = HZH`, but it turns out that this property does not generalize! The general property is actually :math:`X = H^{\dagger}ZH.` In the case of qubits it holds that :math:`H = H^{\dagger},` but in other dimensions it does not. I invite you to continue practising with other types of algorithms. For instance, will the `Deutsch–Jozsa algorithm `__ generalize well? Take a pen and paper and check it out! +# +# References +# ---------- +# +# .. [#bv] +# +# Ethan Bernstein, Umesh Vazirani, "Quantum Complexity Theory". `SIAM Journal on Computing Vol. 26 (1997). +# `__ +# +# .. [#toffoli_qutrits] +# Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". +# `__ diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json new file mode 100644 index 0000000000..8973cad2de --- /dev/null +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json @@ -0,0 +1,48 @@ +{ + "title": "Qutrits and quantum algorithms", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2023-05-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qutrits_bernstein_vazirani/thumbnail_tutorial_qutrits_bernstein_vazirani.png" + } + ], + "seoDescription": "Learn how to interpret the Bernstein\u2013Vazirani algorithm with qutrits", + "doi": "", + "references": [ + { + "id": "bv", + "type": "article", + "title": "Quantum Complexity Theory", + "authors": "Ethan Bernstein, Umesh Vazirani", + "year": "1997", + "journal": "SIAM Journal on Computing", + "volume": "26", + "doi": "10.1137/S0097539796300921", + "url": "https://epubs.siam.org/doi/10.1137/S0097539796300921" + }, + { + "id": "toffoli_qutrits", + "type": "article", + "title": "Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits", + "authors": "Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/pdf/2109.00558.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_resource_estimation/demo.py b/demonstrations_v2/tutorial_resource_estimation/demo.py new file mode 100644 index 0000000000..10f554ef6d --- /dev/null +++ b/demonstrations_v2/tutorial_resource_estimation/demo.py @@ -0,0 +1,315 @@ +r""" + +Resource estimation for quantum chemistry +========================================= + +.. meta:: + :property="og:description": Learn how to estimate the number of qubits and gates needed to + implement quantum algorithms + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/resource_estimation.jpeg + +.. related:: + tutorial_quantum_chemistry Quantum chemistry with PennyLane + tutorial_vqe A brief overview of VQE + + +*Author: Soran Jahangiri — Posted: 21 November 2022.* + +Quantum algorithms such as +`quantum phase estimation `_ +(QPE) and the `variational quantum eigensolver `_ (VQE) +are widely studied in quantum chemistry as potential avenues to tackle problems that are intractable +for conventional computers. However, we currently do not have quantum computers or simulators +capable of implementing large-scale +versions of these algorithms. This makes it difficult to properly explore their accuracy and +efficiency for problem sizes where the actual advantage of quantum algorithms can potentially occur. +Despite these difficulties, it is still possible to perform **resource estimation** +to assess what we need to implement such quantum algorithms. + +In this demo, we describe how to use PennyLane's resource estimation functionality to estimate the total number of logical qubits and gates required to +implement the QPE algorithm for simulating molecular Hamiltonians represented in first and second +quantization. We focus on `non-Clifford gates `_, which +are the most expensive to implement in a fault-tolerant setting. We also explain how to estimate the +total number of measurements needed to compute expectation values using algorithms such as VQE. + +Quantum Phase Estimation +------------------------ +The QPE algorithm can be used to compute the phase associated with an eigenstate of a unitary +operator. For the purpose of quantum simulation, the unitary operator :math:`U` can be chosen to +share eigenvectors with a molecular Hamiltonian :math:`H,` for example by setting +:math:`U = e^{-iH}.` Estimating the phase of such a unitary then permits recovering the +corresponding eigenvalue of the Hamiltonian. A conceptual QPE circuit diagram is shown below. + +.. figure:: /_static/demonstration_assets/resource_estimation/qpe.png + :width: 60% + :align: center + + Circuit representing the quantum phase estimation algorithm. + +For most cases of interest, this algorithm requires more qubits and longer circuit depths than what +can be implemented on existing hardware. The PennyLane functionality in the +:mod:`qml.resource ` module allows us to estimate the number of logical qubits +and the number of non-Clifford gates that are needed to implement the algorithm. We can estimate +these resources by simply defining system specifications and a target error for estimation. Let's +see how! + +QPE cost for simulating molecules +********************************* +We study the double low-rank Hamiltonian factorization algorithm of [#vonburg2021]_ and use its cost +equations as provided in APPENDIX C of [#lee2021]_. +This algorithm requires the one- and two-electron +`integrals `_ +as input. These integrals can be obtained in different ways and here we use PennyLane to compute +them. We first need to define the atomic symbols and coordinates for the given molecule. Let's use +the water molecule at its equilibrium geometry with the +`6-31g basis set `_ as an example. +""" +import pennylane as qml +from pennylane import numpy as np + +symbols = ['O', 'H', 'H'] +geometry = np.array([[0.00000000, 0.00000000, 0.28377432], + [0.00000000, 1.45278171, -1.00662237], + [0.00000000, -1.45278171, -1.00662237]], requires_grad=False) + +############################################################################## +# Then we construct a molecule object and compute the one- and two-electron +# integrals in the molecular orbital basis. + +mol = qml.qchem.Molecule(symbols, geometry, basis_name='6-31g') +core, one, two = qml.qchem.electron_integrals(mol)() + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.DoubleFactorization` class + +algo = qml.resource.DoubleFactorization(one, two) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. + +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# This estimation is for a target error that is set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` by default. We can change the target error to a larger value which leads to a +# smaller number of non-Clifford gates and logical qubits. + +chemical_accuracy = 0.0016 +error = chemical_accuracy * 10 +algo = qml.resource.DoubleFactorization(one, two, error=error) +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also estimate the number of non-Clifford gates with respect to the threshold error values +# for discarding the negligible factors in the factorized Hamiltonian [#vonburg2021]_ and plot the +# estimated numbers. + +threshold = [10**-n for n in range(10)] +n_gates = [] +n_qubits = [] + +for tol in threshold: + algo_ = qml.resource.DoubleFactorization(one, two, tol_factor=tol, tol_eigval=tol) + n_gates.append(algo_.gates) + n_qubits.append(algo_.qubits) + +import matplotlib.pyplot as plt + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(threshold, n_gates, ':o', markerfacecolor='none', color='teal') + +ax.set_ylabel('n gates') +ax.set_xlabel('threshold') +ax.set_xscale('log') +fig.tight_layout() + +############################################################################## +# QPE cost for simulating periodic materials +# ****************************************** +# For periodic materials, we estimate the cost of implementing the QPE algorithm of [#zini2023]_ +# using Hamiltonians represented in first quantization and in a plane wave basis. We first need to +# define the number of plane waves, the number of electrons, and the lattice vectors that construct +# the unit cell of the periodic material. Let's use dilithium iron silicate +# :math:`\text{Li}_2\text{FeSiO}_4` as an example taken from [#delgado2022]_. For this material, the +# unit cell contains 156 electrons and has dimensions :math:`9.49 \times 10.20 \times 11.83` in +# `atomic units `_. We also use :math:`10^5` plane waves. + +planewaves = 100000 +electrons = 156 +vectors = np.array([[9.49, 0.00, 0.00], + [0.00, 10.20, 0.00], + [0.00, 0.00, 11.83]]) + +############################################################################## +# We now create an instance of the :class:`~.pennylane.resource.FirstQuantization` class +algo = qml.resource.FirstQuantization(planewaves, electrons, vectors=vectors) + +############################################################################## +# and obtain the estimated number of non-Clifford gates and logical qubits. +print(f'Estimated gates : {algo.gates:.2e} \nEstimated qubits: {algo.qubits}') + +############################################################################## +# We can also plot the estimated numbers as a function of the number of plane waves for different +# target errors + +error = [0.1, 0.01, 0.001] # in atomic units +planewaves = [10 ** n for n in range(1, 10)] +n_gates = [] +n_qubits = [] + +for er in error: + n_gates_ = [] + n_qubits_ = [] + + for pw in planewaves: + algo_ = qml.resource.FirstQuantization(pw, electrons, vectors=vectors, error=er) + n_gates_.append(algo_.gates) + n_qubits_.append(algo_.qubits) + n_gates.append(n_gates_) + n_qubits.append(n_qubits_) + +fig, ax = plt.subplots(2, 1) + +for i in range(len(n_gates)): + ax[0].plot(planewaves, n_gates[i], ':o', markerfacecolor='none', label=error[i]) +ax[1].plot(planewaves, n_qubits[i], ':o', markerfacecolor='none', label=error[-1]) + +ax[0].set_ylabel('n gates') +ax[1].set_ylabel('n qubits') + +for i in [0, 1]: + ax[i].set_xlabel('n planewaves') + ax[i].tick_params(axis='x') + ax[0].set_yscale('log') + ax[i].set_xscale('log') + ax[i].legend(title='error [Ha]') + +fig.tight_layout() + +############################################################################## +# The algorithm uses a decomposition of the Hamiltonian as a linear combination of unitaries, +# +# .. math:: H=\sum_{i} c_i U_i. +# +# The parameter :math:`\lambda=\sum_i c_i,` which can be interpreted as the 1-norm of the +# Hamiltonian, plays an important role in determining the cost of implementing the QPE +# algorithm [#delgado2022]_. In PennyLane, the 1-norm of the Hamiltonian can be obtained with + +print(f'1-norm of the Hamiltonian: {algo.lamb}') + +############################################################################## +# PennyLane allows you to get more detailed information about the cost of the algorithms as +# explained in the documentation of :class:`~.pennylane.resource.FirstQuantization` +# and :class:`~.pennylane.resource.DoubleFactorization` classes. +# +# Variational quantum eigensolver +# ------------------------------------------ +# In variational quantum algorithms such as VQE, the expectation value of an observable is +# typically computed by decomposing the observable into a linear combination of Pauli words, +# which are tensor products of Pauli and Identity operators. The expectation values are calculated +# through linearity by measuring the expectation value for each of these terms and combining the +# results. The number of qubits required for the measurement is trivially determined by +# the number of qubits the observable acts on. The number of gates required to implement the +# variational algorithm is determined by a circuit ansatz that is also known a priori. However, +# estimating the number of circuit evaluations, i.e. the number of shots, required to achieve a +# certain error in computing the expectation value is not as straightforward. Let's now use +# PennyLane to estimate the number of shots needed to compute the expectation value of the water +# Hamiltonian. +# +# First, we construct the molecular Hamiltonian. + +molecule = qml.qchem.Molecule(symbols, geometry) +H = qml.qchem.molecular_hamiltonian(molecule)[0] +H_coeffs, H_ops = H.terms() + +############################################################################## +# The number of measurements needed to compute :math:`\left \langle H \right \rangle` can be +# obtained with the :func:`~.pennylane.resource.estimate_shots` function, which requires the +# Hamiltonian coefficients as input. The number of measurements required to compute +# :math:`\left \langle H \right \rangle` with a target error set to the chemical accuracy, 0.0016 +# :math:`\text{Ha},` is obtained as follows. + +m = qml.resource.estimate_shots(H_coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# This number corresponds to the measurement process where each term in the Hamiltonian is measured +# independently. The number can be reduced by using +# :func:`~.pennylane.pauli.group_observables()`, which partitions the Pauli words into +# groups of commuting terms that can be measured simultaneously. + +ops, coeffs = qml.pauli.group_observables(H_ops, H_coeffs) +coeffs = [np.array(c) for c in coeffs] # cast as numpy array + +m = qml.resource.estimate_shots(coeffs) +print(f'Shots : {m:.2e}') + +############################################################################## +# It is also interesting to illustrate how the number of shots depends on the target error. + +error = np.array([0.02, 0.015, 0.01, 0.005, 0.001]) +m = [qml.resource.estimate_shots(H_coeffs, error=er) for er in error] + +e_ = np.linspace(error[0], error[-1], num=50) +m_ = 1.4e4 / np.linspace(error[0], error[-1], num=50)**2 + +fig, ax = plt.subplots(figsize=(5, 3)) +ax.plot(error, m, 'o', markerfacecolor='none', color='teal', label='estimated') +ax.plot(e_, m_, ':', markerfacecolor='none', color='olive', label='$ 1.4e4 * 1/\epsilon^2 $') + +ax.set_ylabel('shots') +ax.set_xlabel('error [Ha]') +ax.set_yscale('log') +ax.tick_params(axis='x', labelrotation = 90) +ax.legend() +fig.tight_layout() + +############################################################################## +# We have added a line showing the dependency of the shots to the error, as +# :math:`\text{shots} = 1.4\text{e}4 \times 1/\epsilon^2,` for comparison. Can you draw any +# interesting information form the plot? +# +# Conclusions +# ----------- +# This tutorial shows how to use the resource estimation functionality in PennyLane to compute the +# total number of non-Clifford gates and logical qubits required to simulate a Hamiltonian with +# quantum phase estimation algorithms. The estimation can be performed for second-quantized +# molecular Hamiltonians obtained with a double low-rank factorization algorithm, +# and first-quantized Hamiltonians of periodic materials in the plane wave basis. We also discussed +# the estimation of the total number of shots required to obtain the expectation value of an +# observable using the variational quantum eigensolver algorithm. The functionality allows one to +# obtain interesting results about the cost of implementing important quantum algorithms. For +# instance, we estimated the costs with respect to factors such as the target error in obtaining +# energies and the number of basis functions used to simulate a system. Can you think of other +# interesting information that can be obtained using this PennyLane functionality? +# +# References +# ---------- +# +# .. [#vonburg2021] +# +# Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, *et al.*, +# "Quantum computing enhanced computational catalysis". +# `Phys. Rev. Research 3, 033055 (2021) +# `__ +# +# .. [#lee2021] +# +# Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, *et al.*, +# "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction". +# `PRX Quantum 2, 030305 (2021) +# `__ +# +# .. [#zini2023] +# +# Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, Pablo A. M. Casares, +# Jonathan E. Mueller, Arne-Christian Voigt, Juan Miguel Arrazola, +# "Quantum simulation of battery materials using ionic pseudopotentials". +# `Quantum 7, 1049 (2023) `__ +# +# .. [#delgado2022] +# +# Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, *et al.*, +# "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". +# `Phys. Rev. A 106, 032428 (2022) +# `__ diff --git a/demonstrations_v2/tutorial_resource_estimation/metadata.json b/demonstrations_v2/tutorial_resource_estimation/metadata.json new file mode 100644 index 0000000000..fe2b5e20e3 --- /dev/null +++ b/demonstrations_v2/tutorial_resource_estimation/metadata.json @@ -0,0 +1,74 @@ +{ + "title": "Resource estimation for quantum chemistry", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2022-11-21T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_resource_estimation_QChem.png" + } + ], + "seoDescription": "Learn how to estimate the number of qubits and gates needed to implement quantum algorithms", + "doi": "", + "references": [ + { + "id": "vonburg2021", + "type": "article", + "title": "Quantum computing enhanced computational catalysis", + "authors": "Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, et al.", + "year": "2021", + "journal": "Phys. Rev. Research", + "url": "https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.3.033055" + }, + { + "id": "lee2021", + "type": "article", + "title": "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction", + "authors": "Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, et al.", + "year": "2021", + "journal": "PRX Quantum", + "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.030305" + }, + { + "id": "zini2023", + "type": "article", + "title": "Quantum simulation of battery materials using ionic pseudopotentials", + "authors": "Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, et al.", + "year": "2023", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2023-07-10-1049" + }, + { + "id": "delgado2022", + "type": "article", + "title": "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer", + "authors": "Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, et al.", + "year": "2022", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.106.032428" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_resource_estimation/requirements.in b/demonstrations_v2/tutorial_resource_estimation/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_resource_estimation/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_rl_pulse/demo.py b/demonstrations_v2/tutorial_rl_pulse/demo.py new file mode 100644 index 0000000000..ab9e9fd98d --- /dev/null +++ b/demonstrations_v2/tutorial_rl_pulse/demo.py @@ -0,0 +1,1049 @@ +r""" +Gate calibration with reinforcement learning +============================================ + +Gate-based quantum circuits are the most common representation of quantum computations. These +provide an abstraction layer that enables the development of quantum algorithms without considering +the hardware in charge of the execution. However, every quantum platform offers a different set of +interactions and controls that define the natural operations that can be performed in the hardware. +These are known as the *native gates* of the device, and they constitute the fundamental +building blocks of any quantum algorithm executed in it. Therefore, it is essential that such +operations are performed as accurately as possible, which requires the careful tuning of the +hardware's controls. In this demo, we will learn how to use reinforcement learning to find the +optimal control parameters to accurately execute quantum gates. We will implement an +experimentally-friendly protocol based on the direct interaction with the hardware, following +the main ideas in [#BaumPRXQ21]_, which we illustrate using superconducting qubits as +an example. + +.. figure:: ../demonstrations/rl_pulse/DemoOG_RLpulse.png + :align: center + :width: 60% + +Gate calibration +---------------- + +Calibrating quantum gates consists in finding the best possible control parameters of the device +that yield the most accurate gate execution. For instance, the gates in superconducting quantum +devices are performed by targeting the qubits with microwave pulses of the form + +.. math:: \Omega(t)\sin(\phi(t) + \omega_p t)\,, + +\ where :math:`\Omega(t)` is a time-dependent amplitude, :math:`\phi(t)` is a time-dependent phase, +and :math:`\omega_p` is the pulse's frequency. Hence, the proper execution of any gate relies on the +careful selection of these parameters in combination with the pulse duration, which we collectively +refer to as a *pulse program*. However, each qubit in the device has distinct properties, such as +the frequency and the connectivity to other qubits. These differences cause the same pulse programs +to produce different operations for every qubit. Consequently, every gate must be carefully +calibrated for each individual qubit in the hardware. For further details about superconducting +quantum computers and their control see `this demo `__. + +A common strategy to calibrate quantum gates involves the detailed modelling of the quantum +computer, enabling the gate optimization through analytical and numerical techniques. Nevertheless, +developing such accurate models requires an exhaustive characterization of the hardware, and it +can be challenging to account for all the relevant interactions in practice. + +An alternative promissing approach is through the direct interaction with the device, refraining +from deriving any explicit model of the system. Here, we frame qubit calibration as a reinforcement +learning problem, drawing inspiration from the experimentally-friendly method proposed in reference +[#BaumPRXQ21]_. In this setting, a reinforcement learning agent learns to calibrate the gates by +tuning the control parameters and directly observing the response of the qubits. Through this +process, the agent implicitly learns an effective model of the device, as it faces all the +experimental nuances associated with the process of executing the gates, such as the effect of the +most relevant noise sources. This makes the resulting agent an excellent calibrator that is robust +to these phenomena. + +The procedure that we present below is entirely agnostic to the quantum hardware. +Among all the possibilities, we will ilustrate it using coupled-transmon superconducting quantum +computers simulated with PennyLane. This will allow us to focus on the method itself, skipping some +of the nuances associated with the execution on real devices, while ensuring that the resulting +code can be easily adapted to run in a quantum computer using the PennyLane plugins, as shown in +`this demo `__. + +Reinforcement learning basics +----------------------------- + +In the typical reinforcement learning setting, we find two main entities: an *agent* and an +*environment*. The environment contains the relevant information about the problem and defines the +“rules of the game”. The main goal of the agent is to find the optimal strategy to perform a given +task through the interaction with the environment. + +In order to complete the desired task, the agent can observe the environment and perform *actions*, +which can affect the environment and change its *state*. This way, the interaction between them is +cyclic, as depicted in the figure below. Let's take chess as an example. In this case, the agent is +a player and the environment comprises the pieces on the board, the rules, and the opponent. At a +given point in time, the agent observes the environment's state, which can be the current location +of all the pieces on the board. With this information, it can choose to perform a certain action, +such as moving a pawn forward among all its possible moves. Doing so affects the environment, which +provides the agent with the new state it is found in and a *reward*. The new state is the resulting +board configuration after the agent's move and the opponent's response. The reward is a measure of +how well the agent is performing the task given the last interaction. We will see more about the +reward in the following section. + +.. figure:: ../demonstrations/rl_pulse/sketch_rl.png + :align: center + :width: 75% + +The agent chooses its actions according to a *policy*, and **the ultimate goal is to learn the +optimal policy that maximizes the obtained rewards**. In general, the policy can take any form, and +its nature is usually related to the reinforcement learning algorithm that we implement to learn it. +We will see how to learn the optimal policy later. For now, let's see how all these concepts apply +to our task. + +Framing qubit calibration as a reinforcement learning problem +------------------------------------------------------------- + +Our main objective is to accurately execute a desired quantum gate in our computer's qubits. To do +so, we need a way to find the correct pulse program for each qubit. In reinforcement learning terms, +our agent needs to learn the optimal policy to obtain the pulse program for every qubit. + +In this case, the environment is the quantum computer itself, and the states encode information +about the qubit's evolution during the pulse execution. The agent's actions correspond to +adjusting the different “control knobs” we can turn to modulate the microwave pulse. This setting +allows the agent to react to the qubit's peculiarities and adapt the pulse parameters accordingly +to execute the target gate. + +The hardest part of this approach is extracting information about the qubit's evolution under +the pulse, since observing it destroys the quantum state. Here, we follow a similar approach to the +one introduced in [#BaumPRXQ21]_. The main idea is to split the pulse program into segments of +constant properties, commonly known as a piece-wise constant (PWC) pulse, and evaluate the +intermediate states between segments: + +1. We fix the total duration and the number of segments of the PWC pulse. +2. We reset the qubit. +3. We perform quantum tomography to determine the qubit's state. +4. With this information, the agent fixes the parameters for the next pulse segment. +5. We reset the qubit and execute the pulse up to the last segment with fixed parameters. +6. We repeat steps 3-5 until we reach the end of the pulse and evaluate the average gate fidelity. + + +The images below show a schematic representation of the main points in the protocol. +In the first image on the left, the first two pulse segments (blue) are executed as a shorter pulse +than the final result. This is repeated several times to perform quantum state tomography of the +state, whose result is used to determine the parameters of the next pulse segment (yellow). +Afterwards, the system is reset, and the first three segments are executed, which leads to the +second figure on the right. There, the steps are repeated in the same order, evolving the qubit +with the first three segments several times to characterize their effect and determine the +parameters of the fourth pulse segment, reaching the end of the pulse. Given that all the pulse +segments are now fixed, we proceed with the evaluation of the pulse with respect to the target gate +that we wish to execute. + +.. figure:: ../demonstrations/rl_pulse/sketch_protocol.png + :align: center + :width: 90% + +With this protocol, the agent iteratively builds a PWC pulse that is tailored to the specifics of +the qubit. Even though this protocol involves multiple (partial) executions of the pulse to perform +the intermediate tomography steps, the overall cost remains low provided that it is only for the +qubit(s) involved in the gate, typically one or two. + +Building a :math:`R_X(\pi/2)` calibrator +----------------------------------------- + +Let's take all these concepts and apply them to train a reinforcement learning agent to calibrate +the single-qubit :math:`R_X(\pi/2)` gate (a.k.a. :math:`\sqrt{X}`), which is a common native gate +in superconducting quantum computers. To do so, we need to define: + +- The environment (hardware, actions, and rewards) +- The agent (the policy and how to act) +- The learning algorithm + +Then, we'll put all the pieces together and train our agent. Let's introduce these concepts one by +one and implement them from scratch with PennyLane and JAX. + +The environment +``````````````` + +As we mentioned earlier, the environment contains all the information about the problem. In an +experimental setting, the actual quantum computer and how we interact with it would constitute +the environment. In this demo, we will simulate it with PennyLane. + +We start by defining the quantum hardware. As we mentioned above, we will simulate a +superconducting quantum computer. The PennyLane :mod:`~pennylane.pulse` module provides the tools +to simulate quantum systems through time, allowing us to control quantum computers at the lowest +pulse level. To perform the simulation, we will define an effective time-dependent Hamiltonian +for the hardware. We often distinguish between two main components: a constant drift term that +describes the interaction between the qubits in our system +(see :func:`~pennylane.pulse.transmon_interaction`), and a time-dependent drive term that +accounts for the pulse (see :func:`~pennylane.pulse.transmon_drive`). The time-dependent +Hamiltonian for a driven qubit is: + +.. math:: H = \underbrace{-\frac{\omega_q}{2}\sigma^z}_{H_{int}} + \underbrace{\Omega(t)\sin(\phi(t) + \omega_p t)\sigma^y\,}_{H_{drive}}, + +where :math:`\omega_q,\omega_p` are the frequencies of the qubit and the pulse, respectively, and +:math:`\sigma^y,\sigma^z` denote the second and third Pauli matrices. + +In order to keep the implementation as simple as possible, we will work with a single-qubit +device. At the end of the demo, we provide insights on how to extend the implementation to +multi-qubit devices and gates. +""" + +import pennylane as qml + +# Quantum computer +qubit_freqs = [4.81] # GHz +connections = [] # No connections +couplings = [] # No couplings +wires = [0] + +H_int = qml.pulse.transmon_interaction(qubit_freqs, connections, couplings, wires) + +# Microwave pulse +pulse_duration = 22.4 # ns +n_segments = 8 +segment_duration = pulse_duration / n_segments + +freq = qubit_freqs[0] # Resonant with the qubit +amplitude = qml.pulse.pwc(pulse_duration) +phase = qml.pulse.pwc(pulse_duration) + +H_drive = qml.pulse.transmon_drive(amplitude, phase, freq, wires) + +# Full time-dependent parametrized Hamiltonian +H = H_int + H_drive + +###################################################################### +# Now that we have the effective model of our system, we need to simulate its time evolution, which +# can be easily done with :func:`~pennylane.evolve`. Since we are simulating the whole process, we +# can speed up the process by simplifying the qubit reset, evolution and tomography steps, which +# are mostly intended for the execution on actual hardware. Here, we can simply pause the +# time-evolution simulation, look at the qubit's state, and then continue after the agent chooses +# the parameters of the subsequent segment. Hence, the environment's state will be exactly the +# qubit's state. +# +# We will do it with a :class:`~pennylane.QNode` that can evolve several states in parallel +# following different pulse programs to speed up the process. +# + +import jax +from functools import partial + +device = qml.device("default.qubit", wires=1) + + +@partial(jax.jit, static_argnames="H") +@partial(jax.vmap, in_axes=(0, None, 0, None)) +@qml.qnode(device=device, interface="jax") +def evolve_states(state, H, params, t): + qml.StatePrep(state, wires=wires) + qml.evolve(H)(params, t, atol=1e-5) + return qml.state() + + +state_size = 2 ** len(wires) + +###################################################################### +# Now that we have a model of the quantum computer and we have defined the environment's states, we +# can proceed to define the actions. As we mentioned before, the actions will adjust the knobs we +# can turn to generate the microwave pulse. We have four parameters to play with in our pulse +# program: amplitude :math:`\Omega(t)`, phase :math:`\phi(t)`, frequency :math:`\omega_p,` and +# duration. Out of those, we fix the duration beforehand (point 1 in the protocol), and we will +# always work with resonant pulses with the qubit, thus fixing the frequency +# :math:`\omega_p=\omega_q.` +# +# Hence, we will let the agent change the amplitude and the phase for every segment in our pulse +# program. To keep the pipeline as simple as possible, we will discretize their values within an +# experimentally-feasible range, and associate every action to a combination of amplitude and phase +# values. +# + +import jax.numpy as jnp + +jax.config.update("jax_enable_x64", True) # Coment this line for a faster execution + +values_phase = jnp.linspace(-jnp.pi, jnp.pi, 9)[1:] # 8 phase values +values_ampl = jnp.linspace(0.0, 0.2, 11) # 11 amplitude values +ctrl_values = jnp.stack( + (jnp.repeat(values_ampl, len(values_phase)), jnp.tile(values_phase, len(values_ampl))), axis=1 +) +n_actions = len(ctrl_values) # 8x11 = 88 possible actions + +###################################################################### +# Finally, we need to define the reward function. In the typical reinforcement learning setting, there +# are rewards after every action is performed, as in the schematic depiction above. However, in some +# cases, those rewards are zero until the task is finished. For example, if we are training an agent +# to play chess, we cannot evaluate every single move on its own, and we need to wait until the game +# is resolved in order to provide the agent with a meaningful reward. +# +# Our case is similar to the chess example. The intermediate states visited along the time evolution +# do not necessarily provide a clear indication of how well the target gate is being executed. It +# is only once we have gone through all the pulse segments that we can see the final outcome and +# evaluate it as a whole, just like a chess strategy is evaluated based on the match's result. The +# reward will be the average gate fidelity of our pulse program with respect to the target gate. +# +# In order to evaluate it, we sample several random initial states and apply the pulse program a few +# consecutive times. Then, we compute the average fidelity between the resulting intermediate and +# final states from our pulse, and the expected states from the target gate. Finally, we take the +# weighted average of all the fidelities, giving more relevance to the initial gate applications. This +# process of repeatedly applying the pulse programs accumulates the errors, making our reward function +# more sensitive to them. This allows the agent to better refine the pulse programs. +# +# The number of initial states and gate repetitions are hyperparameters that will add up really +# quickly with others such as the pulse duration, the segment duration, and so on. In order to keep +# the code as clean as possible, we will put all of them in a ``config`` container (a jit-friendly +# named tuple) and the code below will assume this container is being passed. +# + +target = jnp.array(qml.RX(jnp.pi / 2, 0).matrix()) # RX(pi/2) gate + + +@partial(jax.jit, static_argnames=["H", "config"]) +@partial(jax.vmap, in_axes=(0, None, None, None, None)) +def compute_rewards(pulse_params, H, target, config, subkey): + """Compute the reward for the pulse program based on the average gate fidelity.""" + n_gate_reps = config.n_gate_reps + # Sample the random initial states + states = jnp.zeros((config.n_eval_states, n_gate_reps + 1, state_size), dtype=complex) + states = states.at[:, 0, :].set(sample_random_states(subkey, config.n_eval_states, state_size)) + target_states = states.copy() + + # Repeatedly apply the gates and store the intermediate states + matrix = get_pulse_matrix(H, pulse_params, config.pulse_duration) + for s in range(n_gate_reps): + states = states.at[:, s + 1].set(apply_gate(matrix, states[:, s])) + target_states = target_states.at[:, s + 1].set(apply_gate(target, target_states[:, s])) + + # Compute all the state fidelities (excluding the initial states) + overlaps = jnp.einsum("abc,abc->ab", target_states[:, 1:], jnp.conj(states[:, 1:])) + fidelities = jnp.abs(overlaps) ** 2 + + # Compute the weighted average gate fidelities + weights = 2 * jnp.arange(n_gate_reps, 0, -1) / (n_gate_reps * (n_gate_reps + 1)) + rewards = jnp.einsum("ab,b->a", fidelities, weights) + return rewards.mean() + + +@partial(jax.jit, static_argnames=["n_states", "dim"]) +def sample_random_states(subkey, n_states, dim): + """Sample random states from the Haar measure.""" + subkey0, subkey1 = jax.random.split(subkey, 2) + + s = jax.random.uniform(subkey0, (n_states, dim)) + s = -jnp.log(jnp.where(s == 0, 1.0, s)) + norm = jnp.sum(s, axis=-1, keepdims=True) + phases = jax.random.uniform(subkey1, s.shape) * 2.0 * jnp.pi + random_states = jnp.sqrt(s / norm) * jnp.exp(1j * phases) + return random_states + + +def get_pulse_matrix(H, params, time): + """Compute the unitary matrix associated to the time evolution of H.""" + return qml.evolve(H)(params, time, atol=1e-5).matrix() + + +@jax.jit +def apply_gate(matrix, states): + """Apply the unitary matrix of the gate to a batch of states.""" + return jnp.einsum("ab,cb->ca", matrix, states) + + +###################################################################### +# The agent +# ````````` +# +# The agent is a rather simple entity: it observes a state from the environment and selects an action +# among the ones we have defined above. The action selection is performed according to a policy, which +# is typically denoted by :math:`\pi.` In this case, we will use a stochastic policy +# :math:`\pi_{\mathbf{\theta}}(a_i|s_t)` that provides the probability of choosing the action +# :math:`a_i` given an observed state :math:`s_t` at a given time :math:`t,` according to some +# parameters :math:`\mathbf{\theta}.` Learning the optimal policy :math:`\pi^*` will consist on +# learning the parameters :math:`\mathbf{\theta}^*` that best approximate it +# :math:`\pi_{\mathbf{\theta}^*}\approx\pi^*.` +# +# We parametrize the policy with a small feed-forward neural network that takes the state :math:`s_t` +# as input, and provides the probability to select every action :math:`a_i` at the end. Therefore, the +# input and output layers have ``state_size=2`` and ``n_actions=88`` neurons, respectively. We include +# a hidden layer in between with a hyperbolic tangent activation function. Let's implement this with +# `Flax `__. +# + +from flax import linen as nn + + +# Define the architecture +class MLP(nn.Module): + """Multi layer perceptron (MLP) with a single hidden layer.""" + + hidden_size: int + out_size: int + + @nn.compact + def __call__(self, x): + x = nn.Dense(self.hidden_size)(x) + x = nn.tanh(x) + x = nn.Dense(self.out_size)(x) + return nn.softmax(jnp.sqrt((x * x.conj()).real)) + + +policy_model = MLP(hidden_size=30, out_size=n_actions) + +# Initialize the parameters passing a mock sample +key = jax.random.PRNGKey(3) +key, subkey = jax.random.split(key) + +mock_state = jnp.empty((1, state_size)) +policy_params = policy_model.init(subkey, mock_state) + +###################################################################### +# To act in the environment, we simply need to pass the state through the network and sample an action +# according to the discrete probability distribution provided by the output layer. However, we will +# see how to implement it below to act and compute the policy gradient at once. +# + +###################################################################### +# The learning algorithm +# `````````````````````` +# +# Among the plethora of reinforcement learning algorithms, we use the REINFORCE [#Williams1992]_ +# algorithm. It belongs to the policy gradient algorithms family, which proposes a parametrization of +# the policy (as we have conveniently done in the previous section) and directly optimizes its +# parameters. The main principle of REINFORCE is to directly modify the policy to favour series of +# actions within the agent's experience that have led to high rewards. This way, past beneficial +# actions are more likely to happen again. +# +# We achieve this by maximizing the expected *return* of the policy. To keep it simple, we can +# understand the return (typically :math:`G`) as the weighted sum of rewards along an *episode*, which +# are full executions of our reinforcement learning “game”. In our case, an episode would be the full +# execution of a pulse program, which is comprised by several interactions between the agent and the +# environment. Since the reward will only be given at the end, we will take the return to be the final +# reward. +# +# We perform the maximization of the expected return by gradient ascent over the policy parameters. We +# can compute the gradient of the expected return as follows +# +# .. math:: \nabla_{\mathbf{\theta}} \mathbb{E}\left[G\right] = \mathbb{E}\left[\sum_{t=0}^{T-1}G_t\nabla_{\mathbf{\theta}}\log\pi_{\mathbf{\theta}}(a_t|s_t)\right], +# +# where the expectation values are over episodes sampled following the policy +# :math:`\pi_{\mathbf{\theta}}.` The sum goes over the episode time steps :math:`t,` where the agent +# observes the state :math:`s_t` and chooses to perform the action :math:`a_t.` The term +# :math:`\nabla_{\mathbf{\theta}}\log\pi_{\mathbf{\theta}}(a_t|s_t)` is known as the *score function* +# and it is the gradient of the logarithm of the probability with which the action is taken. Finally, +# :math:`G_t` is the return associated to the episode from time :math:`t` onwards, which is always the +# final reward of the episode, as we mentioned. This expression allows us to compute the gradient +# of our policy parameters without explicitly modeling the environment, hence the name "model-free" +# reinforcement learning. +# +# .. note:: +# At time :math:`t,` an action :math:`a_t` on state :math:`s_t`` leads to the next state +# :math:`s_{t+1}` and yields a reward :math:`r_{t+1}.` The final action is taken at time +# :math:`T-1` which yields the final state :math:`s_T` and reward :math:`r_T.` The return is +# the weighted sum of rewards obtained along an episode: +# +# .. math:: G=\sum_{t=0}^{T-1} \gamma^t r_{t+1}\, +# +# where :math:`\gamma\in[0, 1]` is a *discount factor* that favours early rewards vs latter +# ones. For instance, :math:`\gamma\to0` only values immediate rewards, whereas +# :math:`\gamma\to1` accounts equally for all the rewards regardless of the time. The return +# from time :math:`t` weights the rewards relative to the given time: +# +# .. math:: G_t=\sum_{k=0}^{T-1-t} \gamma^k r_{k+t+1}, +# +# where :math:`k` denotes the number of steps after :math:`t.` Note that +# :math:`G\equiv G_{t=0}` by definition. The return can also be computed recursively following +# the relationship :math:`G_t = r_{t+1} + \gamma G_{t+1},` a property exploited by some +# reinforcement learning algorithms. +# +# In our case, we're in the limit of :math:`\gamma=1,` provided that we only consider the final +# reward :math:`r_T` (:math:`r_{t\neq T}=0`) and we fix the total number of interactions +# :math:`T` beforehand, given by the number of pulse segments. This greatly simplifies the +# expressions and the return is :math:`G=G_t=r_T \,\forall t.` +# +# To learn the optimal policy, we can estimate the gradient +# :math:`\nabla_{\mathbf{\theta}} \mathbb{E}\left[G\right]` by sampling a bunch of episodes following +# the current policy :math:`\pi_{\mathbf{\theta}}.` Then, we can perform a gradient ascent update to +# the policy parameters :math:`\mathbf{\theta},` sample some episodes with the new parameters, and +# repeat until we converge to the optimal policy. +# +# Let's implement these ideas one by one, starting by the episode sampling. The episodes start with +# the qubit in the :math:`|0\rangle` state. Then, we start the observe-act loop, where the agent +# observes the qubit's state and chooses the parameters for the next pulse segment according to its +# policy. We can sample the action and compute the score function at the same time to make the code as +# efficient as possible. Finally, when we reach the end of the pulse program, we compute its reward. +# We like it when things go fast, so we'll sample all the episodes in parallel! +# + + +@partial(jax.jit, static_argnames=["H", "config"]) +def play_episodes(policy_params, H, ctrl_values, target, config, key): + """Play episodes in parallel.""" + n_episodes, n_segments = config.n_episodes, config.n_segments + + # Initialize the qubits on the |0> state + states = jnp.zeros((n_episodes, n_segments + 1, target.shape[0]), dtype=complex) + states = states.at[:, 0, 0].set(1.0) + + # Perform the PWC evolution of the pulse program + pulse_params = jnp.zeros((n_episodes, 2, n_segments)) + actions = jnp.zeros((n_episodes, n_segments), dtype=int) + score_functions = [] + for s in range(config.n_segments): + # Observe the current state and select the parameters for the next pulse segment + sf, (a, key) = act(states[:, s], policy_params, key) + pulse_params = pulse_params.at[..., s].set(ctrl_values[a]) + + # Evolve the states with the next pulse segment + time_window = ( + s * config.segment_duration, # Start time + (s + 1) * config.segment_duration, # End time + ) + states = states.at[:, s + 1].set(evolve_states(states[:, s], H, pulse_params, time_window)) + + # Save the experience for posterior learning + actions = actions.at[:, s].set(a) + score_functions.append(sf) + + # Compute the final reward + key, subkey = jax.random.split(key) + rewards = compute_rewards(pulse_params, H, target, config, subkey) + return states, actions, score_functions, rewards, key + + +@jax.jit +def act(states, params, key): + """Act on states with the current policy params.""" + keys = jax.random.split(key, states.shape[0] + 1) + score_funs, actions = score_function_and_action(params, states, keys[1:]) + return score_funs, (actions, keys[0]) + + +@jax.jit +@partial(jax.vmap, in_axes=(None, 0, 0)) +@partial(jax.grad, argnums=0, has_aux=True) +def score_function_and_action(params, state, subkey): + """Sample an action and compute the associated score function.""" + probs = policy_model.apply(params, state) + action = jax.random.choice(subkey, policy_model.out_size, p=probs) + return jnp.log(probs[action]), action + + +###################################################################### +# Now that we have a way to sample the episodes, we need to process the collected experience to +# compute the gradient of the expected return +# :math:`\nabla_{\mathbf{\theta}} \mathbb{E}\left[G\right].` First, however, we will define two +# utility functions: one to add a list of pytrees together (helps with the temporal sum), and one to +# add extra dimensions to arrays to enable broadcasting. These solve a couple of technicalities that +# will make the following code much more readable. +# + + +@jax.jit +def sum_pytrees(pytrees): + """Sum a list of pytrees.""" + return jax.tree_util.tree_map(lambda *x: sum(x), *pytrees) + + +@jax.jit +def adapt_shape(array, reference): + """Adapts the shape of an array to match the reference (either a batched vector or matrix). + Example: + >>> a = jnp.ones(3) + >>> b = jnp.ones((3, 2)) + >>> adapt_shape(a, b).shape + (3, 1) + >>> adapt_shape(a, b) + b + Array([[2., 2.], + [2., 2.], + [2., 2.]], dtype=float32) + """ + n_dims = len(reference.shape) + if n_dims == 2: + return array.reshape(-1, 1) + return array.reshape(-1, 1, 1) + + +###################################################################### +# In order to compute the gradient, we need to compute the sum within the expectation value for every +# episode. However, we will give this expression a twist and bring our reinforcement learning skills a +# step further. We will subtract a baseline :math:`b` to the return such that +# +# .. math:: \nabla_{\mathbf{\theta}} \mathbb{E}\left[G\right] = \mathbb{E}\left[\sum_{t=0}^{T-1}(G_t - b(s_t))\nabla_{\mathbf{\theta}}\log\pi_{\mathbf{\theta}}(a_t|s_t)\right]. +# +# Intuitively, the magnitude of the reward is arbitrary and depends on our function of choice. Hence, +# it provides the same information if we shift it. For example, our reward based on the average gate +# fidelity is bound between zero and one, but everything would conceptually be the same if we added +# one. In a sense, we could consider the original expression to have a baseline of zero, and this new +# one to be a generalization. Any baseline is valid provided that it does not depend on the action +# :math:`a_t` because this ensures it has a null expectation value, leaving the gradient unaffected. +# +# While these baselines leave the expectation value of the gradient unchanged, they do have an impact +# in its variance. Here, we will implement the optimal state-independent baseline that minimizes the +# variance of the gradient. The optimal baseline for the :math:`k`-th component of the gradient is: +# +# .. math:: b_k = \frac{\mathbb{E}\left[G_t\left(\partial_{\theta_k}\log\pi_{\mathbf{\theta}}(a|s)\right)^2\right]}{\mathbb{E}\left[\left(\partial_{\theta_k}\log\pi_{\mathbf{\theta}}(a|s)\right)^2\right]}\,, +# +# where :math:`\partial_{\theta_k}\log\pi_{\mathbf{\theta}}(a|s)` is the :math:`k`-th component of the +# score function. Thus, this baseline has the same shape as the gradient. You can find a proof in +# Chapter 6.3.1 in [#Dawid22]_. Reducing the variance of our +# estimates significantly speeds up the training process by providing better gradient updates with +# less samples. +# + + +@jax.jit +def reinforce_gradient_with_baseline(episodes): + """Estimates the parameter gradient from the episodes with a state-independent baseline.""" + _, _, score_functions, returns = episodes + ret_episodes = returns.sum() # Sum of episode returns to normalize the final value + # b + baseline = compute_baseline(episodes) + # G - b + ret_minus_baseline = jax.tree_util.tree_map(lambda b: adapt_shape(returns, b) - b, baseline) + # sum((G - b) * sf) + sf_sum = sum_pytrees( + [jax.tree_util.tree_map(lambda r, s: r * s, ret_minus_baseline, sf) for sf in score_functions] + ) + # E[sum((G - b) * sf)] + return jax.tree_util.tree_map(lambda x: x.sum(0) / ret_episodes, sf_sum) + + +@jax.jit +def compute_baseline(episodes): + """Computes the optimal state-independent baseline to minimize the gradient variance.""" + _, _, score_functions, returns = episodes + n_episodes = returns.shape[0] + n_segments = len(score_functions) + total_actions = n_episodes * n_segments + # Square of the score function: sf**2 + sq_sfs = jax.tree_util.tree_map(lambda sf: sf**2, score_functions) + # Expected value: E[sf**2] + exp_sq_sfs = jax.tree_util.tree_map( + lambda sqsf: sqsf.sum(0, keepdims=True) / total_actions, sum_pytrees(sq_sfs) + ) + # Return times score function squared: G*sf**2 + r_sq_sf = sum_pytrees( + [jax.tree_util.tree_map(lambda sqsf: adapt_shape(returns, sqsf) * sqsf, sq_sf) for sq_sf in sq_sfs] + ) + # Expected product: E[G_t*sf**2] + exp_r_sq_sf = jax.tree_util.tree_map(lambda rsqsf: rsqsf.sum(0, keepdims=True) / total_actions, r_sq_sf) + # Ratio of espectation values: E[G_t*sf**2] / E[sf**2] (avoid dividing by zero) + return jax.tree_util.tree_map(lambda ersq, esq: ersq / jnp.where(esq, esq, 1.0), exp_r_sq_sf, exp_sq_sfs) + + +###################################################################### +# Finally, we can choose any optimizer that we like to perform the policy parameter updates with the +# gradient information. We will use the Adam optimizer [#kingma14]_ provided by +# `Optax `__. +# + +import optax + + +def get_optimizer(params, learning_rate): + """Create and initialize an Adam optimizer for the parameters.""" + optimizer = optax.adam(learning_rate) + opt_state = optimizer.init(params) + return optimizer, opt_state + + +###################################################################### +# And we will define our parameter update function to perform the gradient ascent step. These +# optimizers default to gradient descent, which is the most common in machine learning problems. +# Hence, we'll have to subtract the parameter update they provide to go in the opposite direction. +# + + +def update_params(params, gradients, optimizer, opt_state): + """Update model parameters with gradient ascent.""" + updates, opt_state = optimizer.update(gradients, opt_state, params) + new_params = jax.tree_util.tree_map(lambda p, u: p - u, params, updates) # Negative update + return new_params, opt_state + + +###################################################################### +# The training +# ------------ +# +# We have all the building blocks that we need to train an :math:`R_X(\pi/2)` calibrator for our +# superconducting quantum computer. We just need to ensemble the pieces together and choose the right +# parameters for the task. +# +# Let's define the config object that will contain all the hyperparameters of the training process. +# + +from collections import namedtuple + +hyperparams = [ + "pulse_duration", # Total pulse duration + "segment_duration", # Duration of every pulse segment + "n_segments", # Number of pulse segments + "n_episodes", # Episodes to estimate the gradient + "n_epochs", # Training iterations + "n_eval_states", # Random states to evaluate the fidelity + "n_gate_reps", # Gate repetitions for the evaluation + "learning_rate", # Step size of the parameter update +] +Config = namedtuple("Config", hyperparams, defaults=[None] * len(hyperparams)) + +config = Config( + pulse_duration=pulse_duration, + segment_duration=segment_duration, + n_segments=8, + n_episodes=200, + n_epochs=320, + n_eval_states=200, + n_gate_reps=2, + learning_rate=5e-3, +) + +###################################################################### +# Finally, the training loop: +# +# 1. Sample episodes +# 2. Compute the gradient +# 3. Update the policy parameters +# + +optimizer, opt_state = get_optimizer(policy_params, config.learning_rate) + +learning_rewards = [] +for epoch in range(config.n_epochs): + *episodes, key = play_episodes(policy_params, H, ctrl_values, target, config, key) + grads = reinforce_gradient_with_baseline(episodes) + policy_params, opt_state = update_params(policy_params, grads, optimizer, opt_state) + + learning_rewards.append(episodes[3].mean()) + if (epoch % 40 == 0) or (epoch == config.n_epochs - 1): + print(f"Iteration {epoch}: reward {learning_rewards[-1]:.4f}") + +import matplotlib.pyplot as plt + +plt.plot(learning_rewards) +plt.xlabel("Training iteration") +plt.ylabel("Average reward") +plt.grid(alpha=0.3) + +###################################################################### +# The algorithm has converged to a policy with a very high average reward!! Let's see what this agent +# is capable of. +# + +###################################################################### +# Calibrating the qubits +# ---------------------- +# +# After the training, we have a pulse calibrator for the :math:`R_X(\pi/2)` gate with a high average +# gate fidelity. The next step is to actually use it to calibrate the qubits in our device. +# +# Notice that, during the whole training process, the actions of our agent have been stochastic. At +# every step, the policy provides the probability to choose every action and we sample one +# accordingly. When we deploy our calibrator, we want it to consistently provide good pulse programs +# on a single pass through the qubit. Therefore, rather than sampling the actions, we take the one +# with the highest probability. +# +# Let's define a function to extract the pulse program from the policy. +# + + +def get_pulse_program(policy_params, H, ctrl_values, config): + """Extract the pulse program from the trained policy.""" + state = jnp.zeros((1, state_size), dtype=complex).at[:, 0].set(1.0) + pulse_params = jnp.zeros((1, ctrl_values.shape[-1], config.n_segments)) + for s in range(config.n_segments): + probs = policy_model.apply(policy_params, state) + action = probs.argmax() + pulse_params = pulse_params.at[..., s].set(ctrl_values[action]) + time_window = (s * config.segment_duration, (s + 1) * config.segment_duration) + state = evolve_states(state, H, pulse_params, time_window) + return pulse_params[0] + + +###################################################################### +# We can evaluate the resulting average gate fidelity over several random initial states. Remember +# that the reward is a combination of the gate fidelity over several gate repetitions, which is a +# lower bound to the actual gate fidelity. +# + + +def evaluate_program(pulse_program, H, target, config, subkey): + """Compute the average gate fidelity over 1000 random initial states.""" + states = sample_random_states(subkey, 1000, state_size) + target_states = jnp.einsum("ab,cb->ca", target, states) + pulse_matrix = qml.matrix(qml.evolve(H)(pulse_program, config.pulse_duration)) + final_states = jnp.einsum("ab,cb->ca", pulse_matrix, states) + fidelities = qml.math.fidelity_statevector(final_states, target_states) + return fidelities + + +###################################################################### +# Let's extract the pulse program for our qubit, the rotation axis of the resulting gate, and the +# average gate fidelity. +# + +pulse_program = get_pulse_program(policy_params, H, ctrl_values, config) + + +def vector_to_bloch(vector): + """Transform a vector into Bloch sphere coordinates.""" + rho = jnp.outer(vector, vector.conj()) + X, Y, Z = qml.PauliX(0).matrix(), qml.PauliY(0).matrix(), qml.PauliZ(0).matrix() + x, y, z = ( + jnp.trace(rho @ X).real.item(), + jnp.trace(rho @ Y).real.item(), + jnp.trace(rho @ Z).real.item(), + ) + return [x, y, z] + + +matrix = get_pulse_matrix(H, pulse_program, config.pulse_duration) +_, evecs = jnp.linalg.eigh(matrix) +rot_axis = vector_to_bloch(evecs[:, 0]) + +fidelities = evaluate_program(pulse_program, H, target, config, key) +avg_gate_fidelity = fidelities.mean() + +###################################################################### +# We can plot the amplitude and phase over time to get a better idea of what's going on. Furthermore, +# we can visualize the rotation axis in the Bloch sphere to see its alignment with the :math:`X` axis. +# Despite the training reward being 0.982, the actual average gate fidelity is 0.993, showing how +# it accumulates the errors to make the agent more sensitive and reach better results. +# + +import qutip + + +def plot_rotation_axes(rotation_axes, color=["#70CEFF"], fig=None, ax=None): + """Plot the rotation axes in the Bloch sphere.""" + bloch = qutip.Bloch(fig=fig, axes=ax) + bloch.sphere_alpha = 0.05 + bloch.vector_color = color + bloch.add_vectors(rotation_axes) + bloch.render() + + +ts = jnp.linspace(0, pulse_duration - 1e-3, 100) +fig, axs = plt.subplots(ncols=3, figsize=(14, 4), constrained_layout=True) + +axs[0].plot(ts, qml.pulse.pwc(pulse_duration)(pulse_program[0], ts), color="#70CEFF", linewidth=3) +axs[0].set_ylabel("Amplitude (GHz)", fontsize=14) +axs[0].set_yticks(values_ampl) +axs[0].set_ylim([values_ampl[0], values_ampl[-1]]) + +axs[1].plot(ts, qml.pulse.pwc(pulse_duration)(pulse_program[1], ts), color="#FFE096", linewidth=3) +axs[1].set_ylabel("Phase (rad)", fontsize=14) +axs[1].set_yticks( + values_phase, + ["$-3\pi/4$", "$-\pi/2$", "$-\pi/4$", "0", "$\pi/4$", "$\pi/2$", "$3\pi/4$", "$\pi$"], +) +axs[1].set_ylim([values_phase[0] - 0.1, values_phase[-1] + 0.1]) + +for ax in axs[:2]: + ax.grid(alpha=0.3) + ax.tick_params(labelsize=12) + ax.set_xlabel("Time (ns)", fontsize=14) + +axs[2].axis("off") +ax2 = fig.add_subplot(1, 3, 3, projection="3d") +plot_rotation_axes(rot_axis, fig=fig, ax=ax2) +ax2.set_title(f"Average gate fidelity {avg_gate_fidelity:.3f}", fontsize=14) +plt.show() + +###################################################################### +# Beyond single-qubit quantum computers and gates +# ----------------------------------------------- +# +# All the concepts that we have learned throughout the demo are directly applicable to quantum +# computers with more qubits, two-qubit gates, and even other platforms beyond superconducting +# circuits. Perhaps the most straightforward extension of the code presented here would be calibrating +# an entangling gate, such as a CNOT. We will provide an overview of how to adapt this demo to do +# it, although they take significantly longer to train (mainly because the pulses need to be much +# longer). +# +# The first thing we need to build an entangling gate is a second qubit in our device with a different +# frequency coupled to the first one. +# + +# Quantum computer +qubit_freqs = [4.81, 4.88] # GHz +connections = [[0, 1]] +couplings = [0.02] +wires = [0, 1] + +H_int = qml.pulse.transmon_interaction(qubit_freqs, connections, couplings, wires) + +###################################################################### +# The CNOT gate is typically constituted by a series of single-qubit pulses and cross-resonant (CR) +# pulses. Every single-qubit pulse is resonant with the qubit it targets, i.e., it has the same +# frequency, whereas CR pulses are shone on the control qubit at the target's frequency. This way, the +# target is indirectly driven through the coupling with the control qubit, which entangles them. +# +# We will make the CR pulse take the first qubit as control and the second as target. We will +# implement what's known as an echoed CR pulse, which consists of flipping the state of the control +# qubit (applying an :math:`X` gate) in the middle of the CR pulse. The second half of the pulse is +# the negative of the previous one. This “echoes out” the rapid interactions between the qubits, while +# preserving the entangling slower interaction that we are interested in, as introduced in [#SheldonPRA16]_. +# +# Our full pulse ansatz will consist of sandwiching the echoed CR gate between single qubit pulses. +# Therefore, we will have a total of six PWC pulses: two single qubit ones, two CR, and two single +# qubit. +# + +# Microwave pulse +pulse_duration_sq = 22.4 # Single qubit pulse duration +pulse_duration_cr = 100.2 # CR pulse duration + + +def get_drive(timespan, freq, wire): + """Parametrized Hamiltonian driving the qubit in wire with a fixed frequency.""" + amplitude = qml.pulse.pwc(timespan) + phase = qml.pulse.pwc(timespan) + return qml.pulse.transmon_drive(amplitude, phase, freq, wire) + + +pulse_durations = jnp.array( + [ + 0, + pulse_duration_sq, + pulse_duration_sq, + pulse_duration_cr, + pulse_duration_cr, + pulse_duration_sq, + pulse_duration_sq, + ] +) +change_times = jnp.cumsum(pulse_durations) +timespans = [(t0.item(), t1.item()) for t0, t1 in zip(change_times[:-1], change_times[1:])] + +H_sq_0_ini = get_drive(timespans[0], qubit_freqs[0], wires[0]) +H_sq_1_ini = get_drive(timespans[1], qubit_freqs[1], wires[1]) +H_cr_pos = get_drive(timespans[2], qubit_freqs[1], wires[0]) # Target qubit 0 with freq from 1 +H_cr_neg = get_drive(timespans[3], qubit_freqs[1], wires[0]) +H_sq_0_end = get_drive(timespans[4], qubit_freqs[0], wires[0]) +H_sq_1_end = get_drive(timespans[5], qubit_freqs[1], wires[1]) + +###################################################################### +# Notice that the CR pulses are an order of magnitude longer than the single-qubit ones. This is +# because the drive on the target qubit is dampened by the coupling constant, thus requiring much +# longer times to observe a comparable effect compared to driving it directly. +# +# We now have to put everything together in the :class:`~pennylane.QNode` that will be in charge of +# the evolution. We need to account for the control qubit flip between CR pulses and revert it at +# the end, as well as to fix the second CR pulse to be the negative of the previous one. Notice +# that both CR pulses will share their parameters. +# + +H_sq_ini = H_sq_0_ini + H_sq_1_ini +H_sq_end = H_sq_0_end + H_sq_1_end + + +@jax.jit +@partial(jax.vmap, in_axes=(0, None, 0, None)) +@qml.qnode(device=device, interface="jax") +def evolve_states(state, params, t): + params_sq, params_cr = params + qml.StatePrep(state, wires=wires) + # Single qubit pulses + qml.evolve(H_int + H_sq_ini)(params_sq, t, atol=1e-5) + + # Echoed CR + qml.evolve(H_int + H_cr_pos)(params_cr, t, atol=1e-5) + qml.PauliX(0) # Flip control qubit + qml.evolve(H_int - H_cr_neg)(params_cr, t, atol=1e-5) # Negative CR + qml.PauliX(0) # Recover control qubit + + # Single qubit pulses + qml.evolve(H_int + H_sq_end)(params_sq, t, atol=1e-5) + + return qml.state() + + +###################################################################### +# The state of the environment is now a 2-qubit state for which on the quantum computer we need to +# perform :math:`\mathcal{O}(4^2)` measurements for tomography. +# We would need to decide how many segments we wish to split each pulse into, and define the +# appropriate ``time_window`` within ``play_episodes``. This can be achieved by modifying the +# ``config.segment_duration`` to be an array that contains the time spans of every segment, such +# that ``time_window = config.segment_duration[s]``, or similar. Given that the negative CR pulse +# uses the same parameters as the positive CR one, we can skip it as an entire segment merged with +# the last from the positive one that does not involve any intermediate tomography steps. +# +# Finally, when dealing with quantum computers with several qubits, we can opt for two strategies: +# train specialized calibrators for every qubit (or qubit pair), or train a single general +# calibrator for all the qubits. In these cases, we need to define a separate drive Hamiltonian for +# each individual qubit. +# Training individual specialized agents can be done in parallel following the same principles +# introduced in this demo, which will make them robust to the various sources of noise. +# Training a general agent is a bit more involved to adapt. Mainly, every episode +# controls the evolution of a randomly selected qubit with its own ``H_drive``. This involves +# modifying ``play_episodes`` to sample the selected qubits, and carry their evolution under their +# respective Hamiltonians. Notice that ``evolve_states`` should be parallelized over the +# Hamiltonian too. +# + +###################################################################### +# Conclusions +# ----------- +# +# In this demo, we have learned how to design an experimentally-friendly calibrator with +# reinforcement learning. To do so, we have learned the fundamental principles of reinforcement +# learning, the REINFORCE algorithm, and how to frame the calibration of quantum computers within +# this framework. Then, we have put everything together to calibrate a single-qubit gate, and we +# have learned how to apply the principles explored here to gates involving multiple qubits and +# larger devices. +# +# The method presented in this demo has several strengths. First of all, it does not require any +# model or prior information about the quantum computer, making it widely applicable across +# different quantum computing platforms, even though we have only showed an application on a +# superconducting quantum computer. Furthermore, once we have invested resources in training the +# reinforcement learning calibrator, we can use it to recalibrate the qubits multiple times at a +# very low cost. In [#BaumPRXQ21]_, the authors report an average gate fidelity of 0.995 on a +# 2-qubit gate, and 0.993 after 25 days of training! +# +# To continue learning about this topic, try implementing one of the two extensions we explain above. +# To learn more about reinforcement learning, we recommend [#SuttonBarto18]_ for an introduction to +# the topic, and [#Dawid22]_ for an introduction of machine learning for physics (reinforcement +# learning in chapter 6). To learn more about how superconducting +# quantum computers work, see [#KrantzAPR19]_ for an extensive review, and the related `PennyLane +# documentation `__. +# +# Finally, check out the related demos for alternative ways to tune pulse programs. In particular, +# `this demo `__ for an optimal control +# approach to gate calibration, `this demo on optimizing pulses using hardware compatible gradients `__, and +# `this more general intro to differentiable pulse programming `__. +# +# +# References +# ---------- +# +# .. [#BaumPRXQ21] +# +# Y. Baum, et. al. (2019) +# "Experimental Deep Reinforcement Learning for Error-Robust Gate-Set Design on a Superconducting Quantum Computer." +# `PRX Quantum 2(4), 040324 `__. +# +# .. [#Williams1992] +# +# R. J. Williams. (1992) +# "Simple statistical gradient-following algorithms for connectionist reinforcement learning." +# `Machine Learning 8, 229–256 `__. +# +# .. [#Dawid22] +# +# A. Dawid, et. al. (2022) +# "Modern applications of machine learning in quantum sciences." +# `arXiv:2204.04198 `__. +# +# .. [#kingma14] +# +# D. Kingma and J. Ba. (2014) +# "Adam: A method for Stochastic Optimization." +# `arXiv:1412.6980 `__. +# +# .. [#SheldonPRA16] +# +# S. Sheldon, E. Magesan, J. M. Chow and J. M. Gambetta. (2016) +# "Procedure for systematically tuning up cross-talk in the cross-resonance gate." +# `Phys. Rev. A, 93(6), 060302 `__. +# +# .. [#SuttonBarto18] +# +# R. S. Sutton and A. G. Barto. (2018) +# "Reinforcement learning: An introduction." +# `MIT Press `__. +# +# .. [#KrantzAPR19] +# +# P. Krantz, M. Kjaergaard, F. Yan, T. P. Orlando, S. Gustavsson and W. D. Oliver. (2019) +# "A quantum engineer's guide to superconducting qubits." +# `Applied physics reviews 6(2) `__. +# + +###################################################################### diff --git a/demonstrations_v2/tutorial_rl_pulse/metadata.json b/demonstrations_v2/tutorial_rl_pulse/metadata.json new file mode 100644 index 0000000000..a257d0c8bd --- /dev/null +++ b/demonstrations_v2/tutorial_rl_pulse/metadata.json @@ -0,0 +1,128 @@ +{ + "title": "Gate calibration with reinforcement learning", + "authors": [ + { + "username": "brequena" + } + ], + "dateOfPublication": "2024-04-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Optimization", + "Quantum Hardware" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/rl_pulse/thumbnail_RLpulse.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_RLpulse.png" + } + ], + "seoDescription": "Learn how to calibrate quantum computers with reinforcement learning", + "doi": "", + "references": [ + { + "id": "BaumPRXQ21", + "type": "article", + "title": "Experimental Deep Reinforcement Learning for Error-Robust Gate-Set Design on a Superconducting Quantum Computer", + "authors": "Baum, Yuval and Amico, Mirko and Howell, Sean and Hush, Michael and Liuzzi, Maggie and Mundada, Pranav and Merkh, Thomas and Carvalho, Andre R.R. and Biercuk, Michael J.", + "year": "2021", + "publisher": "American Physical Society", + "journal": "PRX Quantum", + "doi": "10.1103/PRXQuantum.2.040324", + "url": "https://link.aps.org/doi/10.1103/PRXQuantum.2.040324" + }, + { + "id": "Williams1992", + "type": "article", + "title": "Simple statistical gradient-following algorithms for connectionist reinforcement learning", + "authors": "Ronald J. Williams", + "year": "1992", + "publisher": "Springer", + "journal": "Machine Learning", + "doi": "10.1007/BF00992696", + "url": "https://link.springer.com/article/10.1007/BF00992696" + }, + { + "id": "Dawid22", + "type": "preprint", + "title": "Modern applications of machine learning in quantum sciences", + "authors": "Anna Dawid and Julian Arnold and Borja Requena and Alexander Gresch and Marcin P\u0142odzie\u0144 and Kaelan Donatella and Kim A. Nicoli and Paolo Stornati and Rouven Koch and Miriam B\u00fcttner and Robert Oku\u0142a and Gorka Mu\u00f1oz-Gil and Rodrigo A. Vargas-Hern\u00e1ndez and Alba Cervera-Lierta and Juan Carrasquilla and Vedran Dunjko and Marylou Gabri\u00e9 and Patrick Huembeli and Evert van Nieuwenburg and Filippo Vicentini and Lei Wang and Sebastian J. Wetzel and Giuseppe Carleo and Eli\u0161ka Greplov\u00e1 and Roman Krems and Florian Marquardt and Micha\u0142 Tomza and Maciej Lewenstein and Alexandre Dauphin", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2204.04198", + "url": "https://arxiv.org/abs/2204.04198" + }, + { + "id": "kingma14", + "type": "preprint", + "title": "Adam: A method for Stochastic Optimization", + "authors": "D. Kingma and J. Ba", + "year": "2014", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2303.11355", + "url": "https://arxiv.org/abs/2303.11355" + }, + { + "id": "SheldonPRA16", + "type": "article", + "title": "Procedure for systematically tuning up cross-talk in the cross-resonance gate", + "authors": "Sheldon, Sarah and Magesan, Easwar and Chow, Jerry M. and Gambetta, Jay M.", + "year": "2016", + "publisher": "American Physical Society", + "journal": "Phys. Rev. A", + "doi": "10.1103/PhysRevA.93.060302", + "url": "https://link.aps.org/doi/10.1103/PhysRevA.93.060302" + }, + { + "id": "SuttonBarto18", + "type": "book", + "title": "Reinforcement learning: An introduction", + "authors": "Sutton, Richard S and Barto, Andrew G", + "year": "2018", + "publisher": "MIT Press", + "journal": "", + "doi": "10.5555/980651.980663", + "url": "https://mitpress.mit.edu/9780262039246/reinforcement-learning/" + }, + { + "id": "KrantzAPR19", + "type": "article", + "title": "A quantum engineer's guide to superconducting qubits", + "authors": "Krantz, Philip and Kjaergaard, Morten and Yan, Fei and Orlando, Terry P and Gustavsson, Simon and Oliver, William D", + "year": "2019", + "publisher": "AIP Publishing", + "journal": "Applied physics reviews", + "doi": "10.1063/1.5089550", + "url": "https://pubs.aip.org/aip/apr/article/6/2/021318/570326/A-quantum-engineer-s-guide-to-superconducting" + } + ], + "basedOnPapers": [ + "10.1103/PRXQuantum.2.040324" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "oqc_pulse", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_optimal_control", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_rl_pulse/requirements.in b/demonstrations_v2/tutorial_rl_pulse/requirements.in new file mode 100644 index 0000000000..67a803e170 --- /dev/null +++ b/demonstrations_v2/tutorial_rl_pulse/requirements.in @@ -0,0 +1,7 @@ +flax +jax +jaxlib +matplotlib +optax +pennylane +qutip diff --git a/demonstrations_v2/tutorial_rl_pulse/rl_pulse/DemoOG_RLpulse.png b/demonstrations_v2/tutorial_rl_pulse/rl_pulse/DemoOG_RLpulse.png new file mode 100644 index 0000000000000000000000000000000000000000..4894ca2e60b7784d7d9a19a1c53f862da5086c8c GIT binary patch literal 208606 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 N)Kj#RHQxmO_zy?Hv|j)K literal 0 HcmV?d00001 diff --git a/demonstrations_v2/tutorial_rl_pulse/rl_pulse/sketch_rl.png b/demonstrations_v2/tutorial_rl_pulse/rl_pulse/sketch_rl.png new file mode 100644 index 0000000000000000000000000000000000000000..036e1dbb4fa753a08d9075d03f327955a45722db GIT binary patch literal 253837 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 z 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�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 rcm97rfUdy*ca{F%x-^`, which involve evaluating +a large number of non-commuting operators in the cost function, decreasing the number of +quantum evaluations required for convergence, while still minimizing statistical noise, can +be a delicate balance. + +Recent work has highlighted that 'quantum-aware' optimization techniques +can lead to marked improvements when training variational quantum algorithms: + +* :doc:`/demos/tutorial_quantum_natural_gradient` descent by Stokes et al. [#stokes2019]_, which + takes into account the quantum geometry during the gradient-descent update step. + +* The work of Sweke et al. [#sweke2019]_, which shows + that quantum gradient descent with a finite number of shots is equivalent to + `stochastic gradient descent `_, + and has guaranteed convergence. Furthermore, combining a finite number of shots with + weighted sampling of the cost function terms leads to :doc:`/demos/tutorial_doubly_stochastic`. + +* The iCANS (individual Coupled Adaptive Number of Shots) optimization technique by + Jonas Kuebler et al. [#kubler2020]_ adapts the number + of shots measurements during training, by maximizing the expected gain per shot. + +In this latest result by Arrasmith et al. [#arrasmith2020]_, the +idea of doubly stochastic gradient descent has been used to extend the iCANS optimizer, +resulting in faster convergence. + +Over the course of this tutorial, we will explore their results; beginning first with a +demonstration of *weighted random sampling* of the cost Hamiltonian operators, before +combining this with the shot-frugal iCANS optimizer to perform doubly stochastic +Rosalin optimization. + +Weighted random sampling +------------------------ + +Consider a Hamiltonian :math:`H` expanded as a weighted sum of operators :math:`h_i` that can +be directly measured: + +.. math:: H = \sum_{i=1}^N c_i h_i. + +Due to the linearity of expectation values, the expectation value of this Hamiltonian +can be expressed as the weighted sum of each individual term: + +.. math:: \langle H\rangle = \sum_{i=1}^N c_i \langle h_i\rangle. + +In the :doc:`doubly stochastic gradient descent demonstration `, +we estimated this expectation value by **uniformly sampling** a subset of the terms +at each optimization step, and evaluating each term by using the same finite number of shots +:math:`N.` + +However, what happens if we use a weighted approach to determine how to distribute +our samples across the terms of the Hamiltonian? In **weighted random sampling** (WRS), +the number of shots used to determine the expectation value :math:`\langle h_i\rangle` +is a discrete random variable distributed according to a +`multinomial distribution `__, + +.. math:: S \sim \text{Multinomial}(p_i), + +with event probabilities + +.. math:: p_i = \frac{|c_i|}{\sum_i |c_i|}. + +That is, the number of shots assigned to the measurement of the expectation value of the +:math:`i\text{th}` term of the Hamiltonian is drawn from a probability distribution +*proportional to the magnitude of its coefficient* :math:`c_i.` + +To see this strategy in action, consider the Hamiltonian + +.. math:: H = 2I\otimes X + 4 I\otimes Z - X\otimes X + 5Y\otimes Y + 2 Z\otimes X. + +We can solve for the ground state energy using the variational quantum eigensolver (VQE) algorithm. + +First, let's import NumPy and PennyLane, and define our Hamiltonian. +""" +import pennylane as qml +from pennylane import numpy as np + +# set the random seed +np.random.seed(4) + +coeffs = [2, 4, -1, 5, 2] + +obs = [ + qml.PauliX(1), + qml.PauliZ(1), + qml.PauliX(0) @ qml.PauliX(1), + qml.PauliY(0) @ qml.PauliY(1), + qml.PauliZ(0) @ qml.PauliZ(1) +] + + +############################################################################## +# We can now create our quantum device (let's use the ``default.qubit`` simulator). + +num_layers = 2 +num_wires = 2 + +# create a device that estimates expectation values using a finite number of shots +non_analytic_dev = qml.device("default.qubit", wires=num_wires, shots=100, seed=432423) + +# create a device that calculates exact expectation values +analytic_dev = qml.device("default.qubit", wires=num_wires, shots=None) + +############################################################################## +# Now, let's set the total number of shots, and determine the probability +# for sampling each Hamiltonian term. + +total_shots = 8000 +prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) +print(prob_shots) + +############################################################################## +# We can now use SciPy to create our multinomial distributed random variable +# :math:`S,` using the number of trials (total shot number) and probability values: + +from scipy.stats import multinomial + +si = multinomial(n=total_shots, p=prob_shots) + +############################################################################## +# Sampling from this distribution will provide the number of shots used to +# sample each term in the Hamiltonian: + +samples = si.rvs()[0] +print(samples) +print(sum(samples)) + +############################################################################## +# As expected, if we sum the sampled shots per term, we recover the total number of shots. +# +# Let's now create our cost function. Recall that the cost function must do the +# following: +# +# 1. It must sample from the multinomial distribution we created above, +# to determine the number of shots :math:`s_i` to use to estimate the expectation +# value of the ith Hamiltonian term. +# +# 2. It then must estimate the expectation value :math:`\langle h_i\rangle` +# by creating the required QNode. For our ansatz, we'll use the +# :class:`~.pennylane.templates.layers.StronglyEntanglingLayers`. +# +# 3. And, last but not least, estimate the expectation value +# :math:`\langle H\rangle = \sum_i c_i\langle h_i\rangle.` +# + +from pennylane.templates.layers import StronglyEntanglingLayers + + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(observable) + +def cost(params): + # sample from the multinomial distribution + shots_per_term = si.rvs()[0] + + result = 0 + + for o, c, s in zip(obs, coeffs, shots_per_term): + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=int(s)) + + return result + + +############################################################################## +# Evaluating our cost function with some initial parameters, we can test out +# that our cost function evaluates correctly. + +param_shape = StronglyEntanglingLayers.shape(n_layers=num_layers, n_wires=num_wires) +init_params = np.random.uniform(low=0.0, high=2*np.pi, size=param_shape, requires_grad=True) +print(cost(init_params)) + + +############################################################################## +# Performing the optimization, with the number of shots randomly +# determined at each optimization step: + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_wrs = [] +shots_wrs = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_wrs.append(_cost) + shots_wrs.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) + +############################################################################## +# Let's compare this against an optimization not using weighted random sampling. +# Here, we will split the 8000 total shots evenly across all Hamiltonian terms, +# also known as *uniform deterministic sampling*. + +@qml.qnode(non_analytic_dev, diff_method="parameter-shift", interface="autograd") +def qnode(weights, obs): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(obs) + +def cost(params): + shots_per_term = int(total_shots / len(coeffs)) + + result = 0 + + for o, c in zip(obs, coeffs): + + # evaluate the QNode corresponding to + # the Hamiltonian term, and add it on to our running sum + result += c * qnode(params, o, shots=shots_per_term) + + return result + +opt = qml.AdamOptimizer(0.05) +params = init_params + +cost_adam = [] +shots_adam = [] + +for i in range(100): + params, _cost = opt.step_and_cost(cost, params) + cost_adam.append(_cost) + shots_adam.append(total_shots*i) + print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Comparing these two techniques: + +from matplotlib import pyplot as plt + +plt.style.use("seaborn-v0_8") +plt.plot(shots_wrs, cost_wrs, "b", label="Adam WRS") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.show() + +############################################################################## +# We can see that weighted random sampling performs just as well as the uniform +# deterministic sampling. However, weighted random sampling begins to show a +# non-negligible improvement over deterministic sampling for large Hamiltonians +# with highly non-uniform coefficients. For example, see Fig (3) and (4) of +# Arrasmith et al. [#arrasmith2020]_, comparing weighted random sampling VQE optimization +# for both :math:`\text{H}_2` and :math:`\text{LiH}` molecules. +# +# .. note:: +# +# While not covered here, another approach that could be taken is +# *weighted deterministic sampling*. Here, the number of shots is distributed +# across terms as per +# +# .. math:: s_i = \left\lfloor N \frac{|c_i|}{\sum_i |c_i|}\right\rfloor, +# +# where :math:`N` is the total number of shots. +# + +############################################################################## +# Rosalin: Frugal shot optimization +# --------------------------------- +# +# We can see above that both methods optimize fairly well; weighted random +# sampling converges just as well as evenly distributing the shots across +# all Hamiltonian terms. However, deterministic shot distribution approaches +# will always have a minimum shot value required per expectation value, as below +# this threshold they become biased estimators. This is not the case with random +# sampling; as we saw in the +# :doc:`doubly stochastic gradient descent demonstration `, +# the introduction of randomness allows for as little +# as a single shot per expectation term, while still remaining an unbiased estimator. +# +# Using this insight, Arrasmith et al. [#arrasmith2020]_ modified the iCANS frugal +# shot-optimization technique [#kubler2020]_ to include weighted random sampling, making it +# 'doubly stochastic'. +# +# iCANS optimizer +# ~~~~~~~~~~~~~~~ +# +# Two variants of the iCANS optimizer were introduced in Kübler et al., iCANS1 and iCANS2. +# The iCANS1 optimizer, on which Rosalin is based, frugally distributes a shot budget +# across the partial derivatives of each parameter, which are computed using the +# :doc:`parameter-shift rule `. It works roughly as follows: +# +# 1. The initial step of the optimizer is performed with some specified minimum +# number of shots, :math:`s_{min},` for all partial derivatives. +# +# 2. The parameter-shift rule is then used to estimate the gradient :math:`g_i` +# for each parameter :math:`\theta_i,` parameters, as well as the *variances* +# :math:`v_i` of the estimated gradients. +# +# 3. Gradient descent is performed for each parameter :math:`\theta_i,` using +# the pre-defined learning rate :math:`\alpha` and the gradient information :math:`g_i:` +# +# .. math:: \theta_i = \theta_i - \alpha g_i. +# +# 4. The improvement in the cost function per shot, for a specific parameter value, +# is then calculated via +# +# .. math:: +# +# \gamma_i = \frac{1}{s_i} \left[ \left(\alpha - \frac{1}{2} L\alpha^2\right) +# g_i^2 - \frac{L\alpha^2}{2s_i}v_i \right], +# +# where: +# +# * :math:`L \leq \sum_i|c_i|` is the bound on the `Lipschitz constant +# `__ of the variational quantum algorithm objective function, +# +# * :math:`c_i` are the coefficients of the Hamiltonian, and +# +# * :math:`\alpha` is the learning rate, and *must* be bound such that :math:`\alpha < 2/L` +# for the above expression to hold. +# +# 5. Finally, the new values of :math:`s_i` (shots for partial derivative of parameter +# :math:`\theta_i`) is given by: +# +# .. math:: +# +# s_i = \frac{2L\alpha}{2-L\alpha}\left(\frac{v_i}{g_i^2}\right)\propto +# \frac{v_i}{g_i^2}. +# +# In addition to the above, to counteract the presence of noise in the system, a +# running average of :math:`g_i` and :math:`s_i` (:math:`\chi_i` and :math:`\xi_i` respectively) +# are used when computing :math:`\gamma_i` and :math:`s_i.` +# +# .. note:: +# +# In classical machine learning, the Lipschitz constant of the cost function is generally +# unknown. However, for a variational quantum algorithm with cost of the form +# :math:`f(x) = \langle \psi(x) | \hat{H} |\psi(x)\rangle,` +# an upper bound on the Lipschitz constant is given by :math:`L < \sum_i|c_i|,` +# where :math:`c_i` are the coefficients of :math:`\hat{H}` when decomposed +# into a linear combination of Pauli-operator tensor products. +# +# Rosalin implementation +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# Let's now modify iCANS above to incorporate weighted random sampling of Hamiltonian +# terms — the Rosalin frugal shot optimizer. +# +# Rosalin takes several hyper-parameters: +# +# * ``min_shots``: the minimum number of shots used to estimate the expectations +# of each term in the Hamiltonian. Note that this must be larger than 2 for the variance +# of the gradients to be computed. +# +# * ``mu``: The running average constant :math:`\mu\in[0, 1].` Used to control how quickly the +# number of shots recommended for each gradient component changes. +# +# * ``b``: Regularization bias. The bias should be kept small, but non-zero. +# +# * ``lr``: The learning rate. Recall from above that the learning rate *must* be such +# that :math:`\alpha < 2/L = 2/\sum_i|c_i|.` +# +# Since the Rosalin optimizer has a state that must be preserved between optimization steps, +# let's use a class to create our optimizer. +# + +class Rosalin: + + def __init__(self, obs, coeffs, min_shots, mu=0.99, b=1e-6, lr=0.07): + self.obs = obs + self.coeffs = coeffs + + self.lipschitz = np.sum(np.abs(coeffs)) + + if lr > 2 / self.lipschitz: + raise ValueError("The learning rate must be less than ", 2 / self.lipschitz) + + # hyperparameters + self.min_shots = min_shots + self.mu = mu # running average constant + self.b = b # regularization bias + self.lr = lr # learning rate + + # keep track of the total number of shots used + self.shots_used = 0 + # total number of iterations + self.k = 0 + # Number of shots per parameter + self.s = np.zeros_like(params, dtype=np.float64) + min_shots + + # Running average of the parameter gradients + self.chi = None + # Running average of the variance of the parameter gradients + self.xi = None + + def estimate_hamiltonian(self, params, shots): + """Returns an array containing length ``shots`` single-shot estimates + of the Hamiltonian. The shots are distributed randomly over + the terms in the Hamiltonian, as per a Multinomial distribution. + + Since we are performing single-shot estimates, the QNodes must be + set to 'sample' mode. + """ + # note that convergence depends on seed for random number generation + rosalin_device = qml.device("default.qubit", wires=num_wires, shots=100, seed=93754352) + + # determine the shot probability per term + prob_shots = np.abs(coeffs) / np.sum(np.abs(coeffs)) + + # construct the multinomial distribution, and sample + # from it to determine how many shots to apply per term + si = multinomial(n=shots, p=prob_shots) + shots_per_term = si.rvs()[0] + + results = [] + + @qml.qnode(rosalin_device, diff_method="parameter-shift", interface="autograd") + def qnode(weights, observable): + StronglyEntanglingLayers(weights, wires=rosalin_device.wires) + return qml.sample(observable) + + for o, c, p, s in zip(self.obs, self.coeffs, prob_shots, shots_per_term): + + # if the number of shots is 0, do nothing + if s == 0: + continue + + # evaluate the QNode corresponding to + # the Hamiltonian term + res = qnode(params, o, shots=int(s)) + + if s == 1: + res = np.array([res]) + + # Note that, unlike above, we divide each term by the + # probability per shot. This is because we are sampling one at a time. + results.append(c * res / p) + + return np.concatenate(results) + + def evaluate_grad_var(self, i, params, shots): + """Evaluate the gradient, as well as the variance in the gradient, + for the ith parameter in params, using the parameter-shift rule. + """ + shift = np.zeros_like(params) + shift[i] = np.pi / 2 + + shift_forward = self.estimate_hamiltonian(params + shift, shots) + shift_backward = self.estimate_hamiltonian(params - shift, shots) + + g = np.mean(shift_forward - shift_backward) / 2 + s = np.var((shift_forward - shift_backward) / 2, ddof=1) + + return g, s + + def step(self, params): + """Perform a single step of the Rosalin optimizer.""" + # keep track of the number of shots run + self.shots_used += int(2 * np.sum(self.s)) + + # compute the gradient, as well as the variance in the gradient, + # using the number of shots determined by the array s. + grad = [] + S = [] + + p_ind = list(np.ndindex(*params.shape)) + + for l in p_ind: + # loop through each parameter, performing + # the parameter-shift rule + g_, s_ = self.evaluate_grad_var(l, params, self.s[l]) + grad.append(g_) + S.append(s_) + + grad = np.reshape(np.stack(grad), params.shape) + S = np.reshape(np.stack(S), params.shape) + + # gradient descent update + params = params - self.lr * grad + + if self.xi is None: + self.chi = np.zeros_like(params, dtype=np.float64) + self.xi = np.zeros_like(params, dtype=np.float64) + + # running average of the gradient variance + self.xi = self.mu * self.xi + (1 - self.mu) * S + xi = self.xi / (1 - self.mu ** (self.k + 1)) + + # running average of the gradient + self.chi = self.mu * self.chi + (1 - self.mu) * grad + chi = self.chi / (1 - self.mu ** (self.k + 1)) + + # determine the new optimum shots distribution for the next + # iteration of the optimizer + s = np.ceil( + (2 * self.lipschitz * self.lr * xi) + / ((2 - self.lipschitz * self.lr) * (chi ** 2 + self.b * (self.mu ** self.k))) + ) + + # apply an upper and lower bound on the new shot distributions, + # to avoid the number of shots reducing below min(2, min_shots), + # or growing too significantly. + gamma = ( + (self.lr - self.lipschitz * self.lr ** 2 / 2) * chi ** 2 + - xi * self.lipschitz * self.lr ** 2 / (2 * s) + ) / s + + argmax_gamma = np.unravel_index(np.argmax(gamma), gamma.shape) + smax = s[argmax_gamma] + self.s = np.clip(s, min(2, self.min_shots), max(2, smax)) + + self.k += 1 + return params + + +############################################################################## +# Rosalin optimization +# ~~~~~~~~~~~~~~~~~~~~ +# +# We are now ready to use our Rosalin optimizer to optimize the initial VQE problem. But first let's +# also create a separate cost function using an 'exact' quantum device, so that we can keep track of the +# *exact* cost function value at each iteration. + +@qml.qnode(analytic_dev, interface="autograd") +def cost_analytic(weights): + StronglyEntanglingLayers(weights, wires=analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +############################################################################## +# Creating the optimizer and beginning the optimization: + + +opt = Rosalin(obs, coeffs, min_shots=10) +params = init_params + +cost_rosalin = [cost_analytic(params)] +shots_rosalin = [0] + +for i in range(60): + params = opt.step(params) + cost_rosalin.append(cost_analytic(params)) + shots_rosalin.append(opt.shots_used) + print(f"Step {i}: cost = {cost_rosalin[-1]}, shots_used = {shots_rosalin[-1]}") + + +############################################################################## +# Let's compare this to a standard Adam optimization. Using 100 shots per quantum +# evaluation, for each update step there are 2 quantum evaluations per parameter. + +adam_shots_per_eval = 100 +adam_shots_per_step = 2 * adam_shots_per_eval * len(params.flatten()) +print(adam_shots_per_step) + +############################################################################## +# Thus, Adam is using 2400 shots per update step. + +params = init_params +opt = qml.AdamOptimizer(0.07) + +adam_dev = qml.device('default.qubit', shots=adam_shots_per_eval, seed=595905) + +@qml.qnode(adam_dev, diff_method="parameter-shift", interface="autograd") +def cost(weights): + StronglyEntanglingLayers(weights, wires=non_analytic_dev.wires) + return qml.expval(qml.Hamiltonian(coeffs, obs)) + +cost_adam = [cost_analytic(params)] +shots_adam = [0] + +for i in range(100): + params = opt.step(cost, params) + cost_adam.append(cost_analytic(params)) + shots_adam.append(adam_shots_per_step * (i + 1)) + print("Step {}: cost = {} shots_used = {}".format(i, cost_adam[-1], shots_adam[-1])) + +############################################################################## +# Plotting both experiments: + +plt.style.use("seaborn-v0_8") +plt.plot(shots_rosalin, cost_rosalin, "b", label="Rosalin") +plt.plot(shots_adam, cost_adam, "g", label="Adam") + +plt.ylabel("Cost function value") +plt.xlabel("Number of shots") +plt.legend() +plt.xlim(0, 300000) +plt.show() + +############################################################################## +# The Rosalin optimizer performs significantly better than the Adam optimizer, +# approaching the ground state energy of the Hamiltonian with strikingly +# fewer shots. +# +# While beyond the scope of this demonstration, the Rosalin optimizer can be +# modified in various other ways; for instance, by incorporating *weighted hybrid +# sampling* (which distributes some shots deterministically, with the remainder +# done randomly), or by adapting the variant iCANS2 optimizer. Download +# this demonstration from the sidebar 👉 and give it a go! ⚛️ + + +############################################################################## +# References +# ---------- +# +# .. [#arrasmith2020] +# +# Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles. "Operator Sampling +# for Shot-frugal Optimization in Variational Algorithms." `arXiv:2004.06252 +# `__ (2020). +# +# .. [#stokes2019] +# +# James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo. "Quantum Natural Gradient." +# `arXiv:1909.02108 `__ (2019). +# +# .. [#sweke2019] +# +# Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy +# Meynard-Piganeau, and Jens Eisert. "Stochastic gradient descent for hybrid quantum-classical +# optimization." `arXiv:1910.01155 `__ (2019). +# +# .. [#kubler2020] +# +# Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles. "An Adaptive Optimizer +# for Measurement-Frugal Variational Algorithms." `Quantum 4, 263 +# `__ (2020). +# +# diff --git a/demonstrations_v2/tutorial_rosalin/metadata.json b/demonstrations_v2/tutorial_rosalin/metadata.json new file mode 100644 index 0000000000..3eeee3973e --- /dev/null +++ b/demonstrations_v2/tutorial_rosalin/metadata.json @@ -0,0 +1,86 @@ +{ + "title": "Frugal shot optimization with Rosalin", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2020-05-19T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_frugal_shot_optimization_Rosalin.png" + } + ], + "seoDescription": "The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the number of times a quantum computer is accessed.", + "doi": "", + "references": [ + { + "id": "arrasmith2020", + "type": "article", + "title": "Operator Sampling for Shot-frugal Optimization in Variational Algorithms.", + "authors": "Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2004.06252" + }, + { + "id": "stokes2019", + "type": "article", + "title": "Quantum Natural Gradient.", + "authors": "James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.02108" + }, + { + "id": "sweke2019", + "type": "article", + "title": "Stochastic gradient descent for hybrid quantum-classical optimization.", + "authors": "Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. F\u00e4hrmann, Barth\u00e9l\u00e9my Meynard-Piganeau, and Jens Eisert", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.01155" + }, + { + "id": "kubler2020", + "type": "article", + "title": "An Adaptive Optimizer for Measurement-Frugal Variational Algorithms.", + "authors": "Jonas M. K\u00fcbler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles", + "year": "2020", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2020-05-11-263/" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2004.06252" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_doubly_stochastic", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rotoselect", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_rosalin/requirements.in b/demonstrations_v2/tutorial_rosalin/requirements.in new file mode 100644 index 0000000000..a9c09d8048 --- /dev/null +++ b/demonstrations_v2/tutorial_rosalin/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +scipy diff --git a/demonstrations_v2/tutorial_rotoselect/demo.py b/demonstrations_v2/tutorial_rotoselect/demo.py new file mode 100644 index 0000000000..aeccba97f2 --- /dev/null +++ b/demonstrations_v2/tutorial_rotoselect/demo.py @@ -0,0 +1,467 @@ +r""" + +.. _rotoselect: + +Quantum circuit structure learning +================================== + +.. meta:: + :property="og:description": Applying the Rotoselect optimization algorithm to find the minimum in + a variational quantum algorithm. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/rotoselect_structure.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_vqe_qng Accelerating VQEs with quantum natural gradient + tutorial_rosalin Frugal shot optimization with Rosalin + +*Author: Angus Lowe — Posted: 16 October 2019. Last updated: 20 January 2021.* + +""" +############################################################################## +# This example shows how to learn a good selection of rotation +# gates so as to minimize a cost +# function using the Rotoselect algorithm of `Ostaszewski et al. +# (2019) `__. We apply this algorithm to minimize a Hamiltonian for a +# variational quantum eigensolver (VQE) problem, +# and improve upon an initial circuit structure ansatz. +# +# .. note:: +# +# The Rotoselect and Rotosolve algorithms are directly implemented and +# available in PennyLane via the optimizers :class:`~.pennylane.RotoselectOptimizer` +# and :class:`~.pennylane.RotosolveOptimizer`, respectively. +# +# Background +# ---------- +# +# In quantum machine learning and optimization problems, +# one wishes to minimize a cost function with respect to some parameters in the circuit. It is desirable +# to keep the circuit as shallow as possible to reduce the effects of noise, but an arbitrary +# choice of gates is generally suboptimal for performing the optimization. +# Therefore, it would be useful to employ an +# algorithm which learns a good circuit structure at fixed depth to minimize the cost function. +# +# Furthermore, PennyLane's optimizers perform automatic differentiation of quantum nodes by evaluating phase-shifted +# expectation values using the quantum circuit itself. +# The output of these calculations, the gradient, is used in optimization methods to minimize +# the cost function. However, +# there exists a technique to discover the optimal parameters of a quantum circuit through phase-shifted evaluations, +# without the need for calculating the gradient as an intermediate step (i.e., a gradient-free optimization). +# It could be desirable, in some cases, to +# take advantage of this. +# +# +# The Rotoselect algorithm addresses the above two points: it allows one to jump directly to the +# optimal value for a single parameter +# with respect to fixed values for the other parameters, skipping gradient descent, and tries various +# rotation gates along the way. +# The algorithm works by updating the parameters :math:`\boldsymbol{\theta}=\theta_1,\dots,\theta_D` and gate choices +# :math:`\boldsymbol{R}=R_1,\dots,R_D` +# one at a time according to a *closed-form expression* for the optimal value of the :math:`d^{\text{th}}` parameter +# :math:`\theta^{*}_d` when the other parameters and gate choices are fixed: +# +# .. math:: +# +# \theta^{*}_d &= \underset{\theta_d}{\text{argmin}} \langle H \rangle_{\theta_d} \\ +# &= -\frac{\pi}{2} - \text{arctan}\left(\frac{2\langle H \rangle_{\theta_d = 0} - +# \langle H \rangle_{\theta_d=\pi/2} - \langle H \rangle_{\theta_d=-\pi/2}}{\langle +# H \rangle_{\theta_d=\pi/2} - +# \langle H \rangle_{\theta_d=-\pi/2}}\right) +# +# The calculation makes use of 3 separate evaluations +# of the expectation value :math:`\langle H \rangle_{\theta_d}` using the quantum circuit. Although +# :math:`\langle H \rangle` is really a function of all parameters and gate choices +# (:math:`\boldsymbol{\theta},` :math:`\boldsymbol{R}`), we +# are fixing every parameter and gate choice apart from :math:`\theta_d` in this expression so we write it as +# :math:`\langle H \rangle = \langle H \rangle_{\theta_d}.` +# For each parameter in the quantum circuit, the algorithm proceeds by evaluating :math:`\theta^{*}_d` +# for each choice of +# gate :math:`R_d \in \{R_x,R_y,R_z\}` and selecting the gate which yields the minimum value of +# :math:`\langle H \rangle.` +# +# Thus, one might expect the number of circuit evaluations required to be 9 for each parameter (3 for each gate +# choice). However, since all 3 rotation gates yield identity when :math:`\theta_d=0,` +# +# .. math:: R_x(0) = R_y(0) = R_z(0) = 1, +# +# the value of :math:`\langle H \rangle_{\theta_d=0}` in the expression for :math:`\theta_d^{*}` above +# is the same for each of the gate choices, and this 3-fold +# degeneracy reduces the number of evaluations required to 7. +# +# One cycle of the Rotoselect algorithm involves +# iterating through every parameter and performing the calculation above. +# This cycle is repeated for a fixed number of steps or until convergence. In this way, one could learn both +# the optimal parameters and gate choices for the task of minimizing +# a given cost function. Next, we present an example of this algorithm +# applied to a VQE Hamiltonian. +# +# Example VQE Problem +# ------------------- +# +# We focus on a 2-qubit VQE circuit for simplicity. Here, the Hamiltonian +# is +# +# .. math:: +# H = 0.5Y_2 + 0.8Z_1 - 0.2X_1 +# +# where the subscript denotes the qubit upon which the Pauli operator acts. The +# expectation value of this quantity acts as the cost function for our +# optimization. +# +# Rotosolve +# --------- +# As a precursor to implementing Rotoselect we can analyze a version of the algorithm +# which does not optimize the choice of gates and only optimizes the parameters for a given circuit ansatz, +# called Rotosolve. Later, we will build on this example +# to implement Rotoselect and vary the circuit structure. +# +# Imports +# ~~~~~~~ +# To get started, we import PennyLane and the PennyLane-wrapped version of NumPy. We also +# create a 2-qubit device using the ``lightning.qubit`` plugin and set the ``analytic`` keyword to ``True`` +# in order to obtain exact values for any expectation values calculated. In contrast to real +# devices, simulators have the capability of doing these calculations without sampling. + +import pennylane as qml +from pennylane import numpy as np + +np.random.seed(9432092) + +n_wires = 2 + +dev = qml.device("lightning.qubit", shots=1000, wires=2) + +############################################################################## +# Creating a fixed quantum circuit +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# .. figure:: ../_static/demonstration_assets/rotoselect/original_ansatz.png +# :scale: 65% +# :align: center +# :alt: original_ansatz +# +# | +# +# Next, we set up a circuit with a fixed ansatz structure—which will later be subject to change—and encode +# the Hamiltonian into a cost function. The structure is shown in the figure above. + + +def ansatz(params): + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.CNOT(wires=[0, 1]) + + +@qml.qnode(dev) +def circuit(params): + ansatz(params) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1)) + + +@qml.qnode(dev) +def circuit2(params): + ansatz(params) + return qml.expval(qml.PauliX(0)) + + +def cost(params): + Z_1, Y_2 = circuit(params) + X_1 = circuit2(params) + return 0.5 * Y_2 + 0.8 * Z_1 - 0.2 * X_1 + + +############################################################################## +# Helper methods for the algorithm +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# We define methods to evaluate +# the expression in the previous section. These will serve as the basis for +# our optimization algorithm. + +# calculation as described above +def opt_theta(d, params, cost): + params[d] = 0.0 + M_0 = cost(params) + params[d] = np.pi / 2.0 + M_0_plus = cost(params) + params[d] = -np.pi / 2.0 + M_0_minus = cost(params) + a = np.arctan2( + 2.0 * M_0 - M_0_plus - M_0_minus, M_0_plus - M_0_minus + ) # returns value in (-pi,pi] + params[d] = -np.pi / 2.0 - a + # restrict output to lie in (-pi,pi], a convention + # consistent with the Rotosolve paper + if params[d] <= -np.pi: + params[d] += 2 * np.pi + + +# one cycle of rotosolve +def rotosolve_cycle(cost, params): + for d in range(len(params)): + opt_theta(d, params, cost) + return params + + +############################################################################## +# Optimization and comparison with gradient descent +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# We set up some initial parameters for the :math:`R_x` and :math:`R_y` +# gates in the ansatz circuit structure and perform an optimization using the +# Rotosolve algorithm. + +init_params = np.array([0.3, 0.25], requires_grad=True) +params_rsol = init_params.copy() +n_steps = 30 + +costs_rotosolve = [] + +for i in range(n_steps): + costs_rotosolve.append(cost(params_rsol)) + params_rsol = rotosolve_cycle(cost, params_rsol) + +############################################################################## +# We then compare the results of Rotosolve to an optimization +# performed with gradient descent and plot +# the cost functions at each step (or cycle in the case of Rotosolve). +# This comparison is fair since the number of circuit +# evaluations involved in a cycle of Rotosolve is similar to those required to calculate +# the gradient of the circuit and step in this direction. Evidently, the Rotosolve algorithm +# converges on the minimum after the first cycle for this simple circuit. + +params_gd = init_params.copy() +opt = qml.GradientDescentOptimizer(stepsize=0.5) +costs_gd = [] +for i in range(n_steps): + costs_gd.append(cost(params_gd)) + params_gd = opt.step(cost, params_gd) + + +# plot cost function optimization using the 2 techniques +import matplotlib.pyplot as plt + +steps = np.arange(0, n_steps) +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(7, 3)) +plt.subplot(1, 2, 1) +plt.plot(steps, costs_gd, "o-") +plt.title("grad. desc.") +plt.xlabel("steps") +plt.ylabel("cost") +plt.subplot(1, 2, 2) +plt.plot(steps, costs_rotosolve, "o-") +plt.title("rotosolve") +plt.xlabel("cycles") +plt.ylabel("cost") +plt.tight_layout() +plt.show() + + +############################################################################## +# Cost function surface for circuit ansatz +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Now, we plot the cost function surface for later comparison with the surface generated +# by learning the circuit structure. + +from matplotlib import cm +from matplotlib.ticker import MaxNLocator +from mpl_toolkits.mplot3d import Axes3D + +fig = plt.figure(figsize=(6, 4)) +ax = fig.add_subplot(projection="3d") + +X = np.linspace(-4.0, 4.0, 40) +Y = np.linspace(-4.0, 4.0, 40) +xx, yy = np.meshgrid(X, Y) +Z = np.array([[cost([x, y]) for x in X] for y in Y]).reshape(len(Y), len(X)) +surf = ax.plot_surface(xx, yy, Z, cmap=cm.coolwarm, antialiased=False) + +ax.set_xlabel(r"$\theta_1$") +ax.set_ylabel(r"$\theta_2$") +ax.zaxis.set_major_locator(MaxNLocator(nbins=5, prune="lower")) + +plt.show() + +############################################################################## +# It is apparent that, based on the circuit structure +# chosen above, the cost function does not depend on the angle parameter :math:`\theta_2` +# for the rotation gate :math:`R_y.` As we will show in the following sections, this independence is not true +# for alternative gate choices. +# +# Rotoselect +# ---------- +# +# .. figure:: ../_static/demonstration_assets/rotoselect/rotoselect_structure.png +# :scale: 65% +# :align: center +# :alt: rotoselect_structure +# +# | +# +# We now implement the Rotoselect algorithm to learn a good selection of gates to minimize +# our cost function. The structure is similar to the original ansatz, but the generators of rotation are +# selected from the set of Pauli gates :math:`P_d \in \{X,Y,Z\}` as shown in the figure above. For example, +# :math:`U(\theta,Z) = R_z(\theta).` +# +# Creating a quantum circuit with variable gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# First, we set up a quantum circuit with a similar structure to the one above, but +# instead of fixed rotation gates :math:`R_x` and :math:`R_y,` we allow the gates to be specified with the +# ``generators`` keyword, which is a list of the generators of rotation that will be used for the gates in the circuit. +# For example, ``generators=['X', 'Y']`` reproduces the original circuit ansatz used in the Rotosolve example +# above. +# A helper method ``RGen`` returns the correct unitary gate according to the +# rotation specified by an element of ``generators``. + + +def RGen(param, generator, wires): + if generator == "X": + qml.RX(param, wires=wires) + elif generator == "Y": + qml.RY(param, wires=wires) + elif generator == "Z": + qml.RZ(param, wires=wires) + + +def ansatz_rsel(params, generators): + RGen(params[0], generators[0], wires=0) + RGen(params[1], generators[1], wires=1) + qml.CNOT(wires=[0, 1]) + + +@qml.qnode(dev) +def circuit_rsel(params, generators): + ansatz_rsel(params, generators) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1)) + + +@qml.qnode(dev) +def circuit_rsel2(params, generators): + ansatz_rsel(params, generators) + return qml.expval(qml.PauliX(0)) + + +def cost_rsel(params, generators): + Z_1, Y_2 = circuit_rsel(params, generators) + X_1 = circuit_rsel2(params, generators) + return 0.5 * Y_2 + 0.8 * Z_1 - 0.2 * X_1 + + +############################################################################## +# Helper methods +# ~~~~~~~~~~~~~~ +# We define helper methods in a similar fashion to Rotosolve. In this case, +# we must iterate through the possible gate choices in addition to optimizing each parameter. + + +def rotosolve(d, params, generators, cost, M_0): # M_0 only calculated once + params[d] = np.pi / 2.0 + M_0_plus = cost(params, generators) + params[d] = -np.pi / 2.0 + M_0_minus = cost(params, generators) + a = np.arctan2( + 2.0 * M_0 - M_0_plus - M_0_minus, M_0_plus - M_0_minus + ) # returns value in (-pi,pi] + params[d] = -np.pi / 2.0 - a + if params[d] <= -np.pi: + params[d] += 2 * np.pi + return cost(params, generators) + + +def optimal_theta_and_gen_helper(d, params, generators, cost): + params[d] = 0.0 + M_0 = cost(params, generators) # M_0 independent of generator selection + for generator in ["X", "Y", "Z"]: + generators[d] = generator + params_cost = rotosolve(d, params, generators, cost, M_0) + # initialize optimal generator with first item in list, "X", and update if necessary + if generator == "X" or params_cost <= params_opt_cost: + params_opt_d = params[d] + params_opt_cost = params_cost + generators_opt_d = generator + return params_opt_d, generators_opt_d + + +def rotoselect_cycle(cost, params, generators): + for d in range(len(params)): + params[d], generators[d] = optimal_theta_and_gen_helper(d, params, generators, cost) + return params, generators + + +############################################################################## +# Optimizing the circuit structure +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# We perform the optimization and print the optimal generators for the rotation gates. The minimum value of the +# cost function obtained by optimizing using Rotoselect is less than the minimum value of the cost function obtained by +# gradient descent or Rotosolve, which were performed on the original circuit structure ansatz. +# In other words, Rotoselect performs better without +# increasing the depth of the circuit by selecting better gates for the task of minimizing the cost function. + +costs_rsel = [] +params_rsel = init_params.copy() +init_generators = np.array(["X", "Y"], requires_grad=False) +generators = init_generators +for _ in range(n_steps): + costs_rsel.append(cost_rsel(params_rsel, generators)) + params_rsel, generators = rotoselect_cycle(cost_rsel, params_rsel, generators) + +print("Optimal generators are: {}".format(generators.tolist())) + +# plot cost function vs. steps comparison +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(7, 3)) +plt.subplot(1, 2, 1) +plt.plot(steps, costs_gd, "o-") +plt.title("grad. desc. on original ansatz") +plt.xlabel("steps") +plt.ylabel("cost") +plt.subplot(1, 2, 2) +plt.plot(steps, costs_rsel, "o-") +plt.title("rotoselect") +plt.xlabel("cycles") +plt.ylabel("cost") +plt.yticks(np.arange(-1.25, 0.80, 0.25)) +plt.tight_layout() +plt.show() + + +############################################################################## +# Cost function surface for learned circuit structure +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# .. figure:: ../_static/demonstration_assets/rotoselect/learned_structure.png +# :scale: 65% +# :align: center +# :alt: learned_structure +# +# | +# +# Finally, we plot the cost function surface for the newly discovered optimized +# circuit structure shown in the figure above. It is apparent from the minima in the plot that +# the new circuit structure is better suited for the problem. + +fig = plt.figure(figsize=(6, 4)) +ax = fig.add_subplot(projection="3d") + +X = np.linspace(-4.0, 4.0, 40) +Y = np.linspace(-4.0, 4.0, 40) +xx, yy = np.meshgrid(X, Y) +# plot cost for fixed optimal generators +Z = np.array([[cost_rsel([x, y], generators) for x in X] for y in Y]).reshape( + len(Y), len(X) +) +surf = ax.plot_surface(xx, yy, Z, cmap=cm.coolwarm, antialiased=False) + +ax.set_xlabel(r"$\theta_1$") +ax.set_ylabel(r"$\theta_2$") +ax.zaxis.set_major_locator(MaxNLocator(nbins=5, prune="lower")) + +plt.show() + +############################################################################## +# References +# ---------- +# +# 1. Mateusz Ostaszewski, Edward Grant, Marcello Bendetti. "Quantum circuit structure learning." +# `arxiv:1905.09692 `__, 2019. + +############################################################################## diff --git a/demonstrations_v2/tutorial_rotoselect/metadata.json b/demonstrations_v2/tutorial_rotoselect/metadata.json new file mode 100644 index 0000000000..79244eef57 --- /dev/null +++ b/demonstrations_v2/tutorial_rotoselect/metadata.json @@ -0,0 +1,54 @@ +{ + "title": "Quantum circuit structure learning", + "authors": [ + { + "username": "alowe" + } + ], + "dateOfPublication": "2019-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_circuit_structure_learning.png" + } + ], + "seoDescription": "Applying the Rotoselect optimization algorithm to find the minimum in a variational quantum algorithm.", + "doi": "", + "references": [ + { + "id": "Ostaszewski2019", + "type": "article", + "title": "Quantum circuit structure learning.", + "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Bendetti", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1905.09692" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1905.09692" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rosalin", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_rotoselect/requirements.in b/demonstrations_v2/tutorial_rotoselect/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_rotoselect/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_sc_qubits/demo.py b/demonstrations_v2/tutorial_sc_qubits/demo.py new file mode 100644 index 0000000000..eb02c2d1a2 --- /dev/null +++ b/demonstrations_v2/tutorial_sc_qubits/demo.py @@ -0,0 +1,876 @@ +r""".. _superconducting_qubits: + +Quantum computing with superconducting qubits +============================================= + +.. meta:: + :property="og:description": Learn about quantum computers based on superconducting qubits, developed by companies such as IBM and Google. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/sc_qubits_tn.png + +.. related:: + tutorial_pasqal Quantum computation with neutral atoms + tutorial_trapped_ions Trapped ion quantum computers + tutorial_photonics Photonic quantum computers + +*Author: Alvaro Ballon — Posted: 22 March 2022. Last updated: 26 August 2022.* + +**Superconducting qubits** are among the most promising approaches to building quantum computers. +It is no surprise that this technology is being used by well-known tech companies in their quest +to pioneer the quantum era. Google's Sycamore claimed quantum advantage back in 2019 [#Google2019]_ and, in 2021, +IBM built its Eagle quantum computer with 127 qubits [#IBM2021]_! The central insight that allows for these +quantum computers is that superconductivity is a quantum phenomenon, so we can use superconducting +circuits as quantum systems that we can control at will. We can actually bring the quantum world +to a larger scale and manipulate it more freely! + +By the end of this demo, you will learn how superconductors are used to create, prepare, +control, and measure the state of a qubit. Moreover, you will identify the strengths and +weaknesses of this technology in terms of Di Vincenzo's criteria, as introduced in the +box below. You will be armed with the basic concepts to understand the main scientific +papers on the topic and keep up-to-date with the newest developments. + +.. container:: alert alert-block alert-info + + **Di Vincenzo's criteria**: In the year 2000, David DiVincenzo proposed a + wishlist for the experimental characteristics of a quantum computer [#DiVincenzo2000]_. + DiVincenzo's criteria have since become the main guideline for + physicists and engineers building quantum computers: + + 1. **Well-characterized and scalable qubits**. Many of the quantum systems that + we find in nature are not qubits, so we must find a way to make them behave as such. + Moreover, we need to put many of these systems together. + + 2. **Qubit initialization**. We must be able to prepare the same state repeatedly within + an acceptable margin of error. + + 3. **Long coherence times**. Qubits will lose their quantum properties after + interacting with their environment for a while. We would like them to last long + enough so that we can perform quantum operations. + + 4. **Universal set of gates**. We need to perform arbitrary operations on the + qubits. To do this, we require both single-qubit gates and two-qubit gates. + + 5. **Measurement of individual qubits**. To read the result of a quantum algorithm, + we must accurately measure the final state of a pre-chosen set of qubits. + +""" + + +############################################################################## +# +# Superconductivity +# ~~~~~~~~~~~~~~~~~ +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/sc_device.png +# :align: center +# :width: 60% +# +# .. +# +# Superconducting chip with 4 qubits +# +# To understand how superconducting qubits work, we first need to explain why some materials are +# superconductors. Let's begin by addressing a simpler question: why do conductors allow for the +# easy passage of electrons and insulating materials don't? Solid-state physics tells us that +# when an electric current travels through a material, the electrons therein come in two types. +# *Conduction electrons* flow freely through the material, while *valence electrons* are attached to +# the atoms that form the material itself. A material is a good conductor of electricity +# if the valence electrons require no energy to be stripped from the atoms to become +# conduction electrons. Similarly, the material is a semi-conductor if the energy needed is small, +# and it's an insulator if the energy is large. +# +# But, if conduction electrons can be obtained for free in conducting materials, then why don't all conductors +# have infinite conductivity? Even the tiniest of stimuli should create a very large current! +# To address this valid concern, let us recall the *exclusion principle* in atomic physics: +# the atom's discrete energy levels have a population limit, so only a limited number of +# electrons can have the same energy. However, the exclusion principle is not limited +# to electrons in atomic orbitals. In fact, it applies to all electrons that are +# organized in discrete energy levels. Since conduction electrons also occupy discrete +# *conduction energy levels*, they must also abide by this law! The conductivity is then limited because, +# when the lower conduction energy levels are occupied, the energy required to promote valence to +# conduction electrons is no longer zero. This energy will keep increasing as the population +# of conduction electrons grows. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/conduction_band.png +# :align: center +# :width: 60% +# +# .. +# +# Valence and conduction energy levels +# +# However, superconductors *do* have infinite conductivity. How is this even possible? +# It's not a phenomenon that we see in our daily lives. For some materials, +# at extremely low temperatures, the conduction electrons attract the positive nuclei +# to form regions of high positive charge density, alternating with regions of +# low charge density. This charge distribution oscillates in an organized manner, +# creating waves in the material known as *phonons*. The conduction electrons are +# pushed together by these phonons, forming *Cooper pairs*. Most importantly, +# these coupled electrons need not obey the exclusion principle. +# We no longer have an electron population limit in +# the lower conduction energy levels, allowing for infinite conductivity! [#Bergou2021]_ +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/cooper_pairs.png +# :align: center +# :width: 60% +# +# .. +# +# Cooper pairs are formed by alternating regions of high and low density of positive +# charge (phonons) represented by the density of red dots. +# +# .. container:: alert alert-block alert-info +# +# **PennyLane plugins:** You can run your quantum algorithms on actual superconducting +# qubit quantum computers on the Cloud. The Qiskit, Amazon Braket, Cirq, and Rigetti PennyLane plugins +# give you access to some of the most powerful superconducting quantum hardware. +# +# +# Building an artificial atom +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# When we code in PennyLane, we deal with the abstraction of a qubit. But how is a qubit +# actually implemented physically? Some of the most widely used real-life qubits are +# built from individual atoms. But atoms are given to us by nature, and we cannot easily alter their properties. +# So although they are reliable qubits, they are not very versatile. We may adapt our +# technology to the atoms, but they seldom adapt to our technology. Could there be a way to +# build a device with the same properties that make atoms suitable qubits? +# Let's see if we can build an artificial atom! +# +# Our first task is to isolate the features that an ideal qubit should have. First and foremost, +# we must not forget that a qubit is a physical system with **two distinguishable configurations** +# that correspond to the computational basis states. In the case of an atom, these are usually the +# ground and excited states, :math:`\left\lvert g \right\rangle` and :math:`\left\lvert e \right\rangle,` of a +# valence electron. In atoms, we can distinguish these states reliably because the ground and excited +# states have two distinct values of energy that can be resolved by our measuring devices. If we +# measure the energy of the valence electron that is in either :math:`\left\lvert g \right\rangle` +# or :math:`\left\lvert e \right\rangle,` we will measure two — and only two — possible values +# :math:`E_0` and :math:`E_1,` associated to :math:`\left\lvert g \right\rangle` +# and :math:`\left\lvert e \right\rangle` respectively. +# +# Most importantly, the physical system under consideration must **exhibit quantum properties**. +# The presence of **discrete energy levels** is indeed one such property, so if we do build a device that stores +# energy in discrete values, we can suspect that it obeys the laws of quantum mechanics. Usually, +# one thinks of a quantum system as being at least as small as a molecule, but building something +# so small is technologically impossible. It turns out that we don't need to go to such small +# scales. If we build a somewhat **small electric circuit using superconducting wires** and bring +# it to temperatures of about 10 mK, it becomes a quantum system with discrete energy levels. +# +# Finally, we must account for the fact that electrons in atoms have more states available than just +# :math:`\left\lvert g \right\rangle` and :math:`\left\lvert e \right\rangle.` In fact, the +# energy levels in an atom are infinitely many. How do we guarantee that an electron does not +# escape to another state that is neither of our hand-picked states? The transition between the ground +# and the excited state only happens when the electron absorbs a photon (a particle of light) with energy +# :math:`\Delta E = E_1 - E_0.` To get to another state with energy :math:`E_2,` +# the electron would need to absorb a photon with energy :math:`E_2 - E_1` or :math:`E_2-E_0.` In an atom, +# these energy differences are always different: there is a **non-uniform spacing between the energy levels**. +# Therefore, if we limit ourselves to interacting with the atom using photons with energy :math:`\Delta E,` +# we will not go beyond the states that define our qubit. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/photon_absorb.png +# :align: center +# :width: 60% +# +# .. +# +# Photons with a particular energy excite electrons +# +# Let's then build the simplest superconducting circuit. We do not want the circuit to warm up, or +# it will lose its quantum properties. Of all the elements that an ordinary circuit may have, +# only two do not produce heat when they're superconducting: *capacitors* and *inductors*. +# Capacitors are two parallel metallic plates that store electric charge. +# They are characterized by their *capacitance* :math:`C,` which measures how much charge +# they can store when connected to a given power source. Inductors are wires shaped +# as a coil and store magnetic fields when a current passes through. +# These magnetic fields, in turn, slow down changing currents that pass through the inductor. +# They are described by an *inductance* :math:`L,` which measures the strength of the magnetic field +# stored in the inductor at a fixed current. The simplest superconducting circuit is, therefore, +# a capacitor connected to an inductor, also known as an LC circuit, as shown below: +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/LC_circuit.png +# :align: center +# :width: 60% +# +# .. +# +# Superconducting LC circuit +# +# Sadly, this simple circuit has a problem: the spacing between energy levels is constant, +# which means identical photons will cause energy transitions between many neighbouring pairs of states. +# This makes it impossible to isolate just two specific states for our qubit. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/anharmonic.png +# :align: center +# :width: 60% +# +# .. +# +# Non-uniform energy levels in an atom vs. uniform energy levels in a superconducting LC circuit +# +# But there turns out to be a fix for the even spacing. Enter the *Josephson junction*. It consists of a very thin piece of an +# insulating material placed between two superconducting metals. Why do we need this? If it's insulating, +# no current should go through it and our circuit should stop working! Here's where another +# famous quantum effect comes into play: the *tunnel effect*. Due to the quantum-probabilistic +# behaviour of their location, Cooper pairs can sometimes go through the Josephson junction, so +# that the current is reduced but not completely stopped. +# If we replace the inductor with one of these junctions, the energy levels of the superconducting +# circuit become unevenly spaced, exactly as we wanted. We have built an artificial atom! +# +# The transmon +# ~~~~~~~~~~~~ +# +# Mission accomplished? Not yet. We want our qubit to be useful for building quantum computers. +# In short, this means that we need to interact with the environment in a controlled way. +# A way to do this is to add a *gate capacitor* with capacitance :math:`C_g` to the artificial atom, so that it +# can receive external signals (photons, in our case). The amount of charge :math:`Q_g` in this +# capacitor can be chosen by the operator, and it determines how strongly the circuit +# interacts with the environment. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/JC_circuit.png +# :align: center +# :width: 60% +# +# .. +# +# Circuit with a Josephson junction and a gate capacitor +# +# But we run into a problem again. Adding a gate capacitor messes +# with our uneven energy levels, which we worked so hard to obtain. The separation in energy +# levels depends on :math:`Q_g,` as shown below [#Blais2021]_. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/energy_levels.png +# :align: center +# :width: 60% +# +# .. +# +# First three energy levels (green, orange, purple) as a function of the charge in the gate capacitor. +# +# The problem with this dependence is that a small change in the gate charge :math:`Q_g` can change the +# difference in energy levels significantly. A solution to this issue is to work around the +# value :math:`Q_g/2e = 1/2,` where the levels are not too sensitive to changes in the gate charge. +# But there is a more straightforward solution: the difference in energy levels also depends +# on the in-circuit capacitance :math:`C` and the physical characteristics of the junction. If we can make +# the capacitance larger, the energy level differences become less sensitive to :math:`Q_g.` So all we need +# to do is choose an appropriate capacitor. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/levels_capacitance.png +# :align: center +# :width: 100% +# +# .. +# +# Energy levels vs gate capacitor charge for different values of in-circuit capacitance. +# +# But there is a price to be paid: making the in-circuit capacitance :math:`C` larger does reduce the +# sensitivity to the gate charge, but it also makes the differences in energy levels more uniform. +# Does that make the Josephson junction pointless? Thankfully, the latter effect turns out to be +# smaller than the former, so we can adjust the capacitance value and preserve some non-uniformity. +# The regime that has been proven ideal is known as the **transmon regime**, and artificial atoms +# in this regime are called **transmons**. They have proven to be highly effective as qubits, +# and they are used in many applications nowadays. We can thus work with the first two +# energy levels of the transmon, which we will also denote :math:`\left\lvert g \right\rangle` and +# :math:`\left\lvert e \right\rangle,` the ground and excited states respectively. The energy difference +# between these states is known as the *energy gap* :math:`E_a.` We can stimulate transitions using +# photons of frequency :math:`\omega_a,` where +# +# .. math:: E_a = \hbar\omega_a. +# +# We have now partially satisfied Di Vincenzo's first criterion of a well-defined qubit, +# and we will discuss scalability later. A great feature of superconducting qubits is that +# the second criterion is satisfied effortlessly. Since the excited states in an artificial +# atom are short-lived and prefer to be on the ground state, all we have to do is wait for a +# short period. If the circuit is well isolated, it is guaranteed that all the qubits will +# be in the ground state with a high probability after this short interval. +# +# Measuring the circuit's state +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now that we have finally fabricated our qubit, we need to understand how to manipulate it. +# The way to do this is to put the qubit inside an *optical cavity*, a metal box where we can +# contain electromagnetic waves. Our focus will be on the so-called *Fabry-Perot* cavities. +# They consist of two mirrors facing each other and whose rears are coated with an anti-reflecting +# material. Something surprising happens when we shine a beam of light on a Fabry-Perot cavity of +# length :math:`L:` electromagnetic waves will only be transmitted when they have a wavelength :math:`\lambda` +# such that +# +# .. math:: L = n\lambda/2, +# +# where :math:`n` is an arbitrary positive integer. If this condition is not met, most of the photons in the wave will be reflected away. +# Therefore, we will have an electromagnetic field inside if we carefully tune our light source to one of these +# wavelengths. For superconducting qubits, it is most +# common to use wavelengths in the microwave range. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/fabry_perot.png +# :align: center +# :width: 60% +# +# .. +# +# The Fabry-Perot Cavity rejects most of the red wave but allows all of the blue wave, since +# an integer number of blue wavelengths fit exactly in the cavity. +# +# Following Di Vincenzo's fifth criterion, let's see how we can measure the state of the qubit placed inside the cavity. +# To obtain information, we need to shine light at a frequency :math:`\omega_r` that the cavity lets through +# (recall that the frequency and the wavelength are related via :math:`\omega = 2\pi c/\lambda,` where :math:`c` is the speed of light). +# We may also choose the frequency value :math:`\omega_r` to be far from the transmon's frequency gap :math:`\omega_a,` so the qubit +# does not absorb the photons. Namely, the *detuning* :math:`\Delta` needs to be large: +# +# .. math:: \Delta \equiv \left\lvert \omega_r - \omega_a \right\rvert \gg 1. +# +# What happens to the photons of this frequency that meet paths with the qubit? They are scattered by the circuit, +# as opposed to photons of frequency :math:`\omega_a,` which get absorbed. Scattering counts as an interaction, +# albeit a weak one, so the scattered photons contain some information about the qubit's state. +# Indeed, the collision causes an exchange in momentum and energy. For light, this means that its +# amplitude and phase will change slightly. If we carefully measure the properties of the scattered light, +# we can distill the information about the state of the qubit and measure its state. +# +# To understand how this works in more detail, we need to do some hands-on calculations. We will rely on the concept +# of a Hamiltonian; do read the blue box below if you need a refresher on the topic! +# +# .. container:: alert alert-block alert-info +# +# **Primer on Hamiltonians:** When a physical system is exposed to outside influences, it will change configurations. +# One way to describe these external interactions is through a mathematical object called a **Hamiltonian**, which +# represents the total energy on the system. In quantum mechanics, the Hamiltonian :math:`\hat{H}` is a Hermitian matrix whose eigenvalues +# represent the possible energies the system may have. The Hamiltonian also tells us how an initial state changes +# in time. In quantum mechanics, this change is described by a differential equation known as Schrodinger's equation: +# +# .. math:: i\hbar \frac{\partial}{\partial t}\left\lvert \psi(t)\right\rangle = \hat{H}\left\lvert \psi(t)\right\rangle. +# +# When the Hamiltonian does not depend on time, this equation can be solved exactly: +# +# .. math:: \left\lvert \psi(t)\right\rangle= \exp(-i\hat{H}t/\hbar)\left\lvert \psi(0)\right\rangle +# +# where :math:`\exp` represents the matrix exponential. Don't worry, there's no need to know how to solve this equation +# or calculate matrix exponentials. Pennylane can do this for us using ``ApproxTimeEvolution``. +# +# +# We are given a Hamiltonian :math:`\hat{H}` that describes the transmon and the photons inside the cavity. +# The transmon is initially in its ground state :math:`\left\lvert g \right\rangle,` and the cavity starts +# without any photons in it, i.e., in the *vacuum state* denoted by :math:`\left\lvert 0 \right\rangle.` +# According to Schrodinger's equation, the state of the cavity (transmon and photons system) evolves +# into :math:`\left\lvert \psi(t)\right\rangle= \exp(-i\hat{H}t/\hbar)\left\lvert g \right\rangle\left\lvert 0 \right\rangle` +# after a time :math:`t.` What is the Hamiltonian that describes light of amplitude :math:`\epsilon` +# and frequency :math:`\omega_r` incident on the cavity, when the detuning :math:`\Delta` is large? +# Deriving the Hamiltonian is not an easy job, so we should trust physicists on this one! The Hamiltonian turns out +# to be +# +# .. math:: \hat{H}=\hbar(\omega_r I+\chi\hat{\sigma}_z)\otimes\hat{N} + \hbar\epsilon I\otimes \hat{P}, +# +# where :math:`\hat{N}` counts the number of photons in the cavity, :math:`\hat{P}` is the photon momentum operator, and +# :math:`\epsilon` is the amplitude of the electromagnetic wave incident on the cavity. The shift :math:`\chi` is +# a quantity that depends on the circuit and gate capacitances and the detuning :math:`\Delta.` +# +# The effect of this evolution can be calculated explicitly. Shining microwaves on the cavity gives +# us a *coherent state* of light contained in it, which is the state of light that lasers give out. +# Coherent states are completely determined by two quantities called :math:`\bar{x}` and :math:`\bar{p}` +# (these quantities are mathematically similar to the notions of average position and average momentum, +# but are in fact physically connected to the phase of the light), +# so we will denote them via :math:`\left\lvert \bar{x}, \bar{p}\right\rangle.` For the state of the qubit and cavity +# system, we write the ket in the form :math:`\left\lvert g \right\rangle \left\lvert \bar{x}, \bar{p}\right\rangle.` +# The Hamiltonian above has (approximately) the following effect: +# +# .. math:: \left\lvert g \right\rangle \left\lvert 0 \right\rangle \rightarrow \left\lvert g \right\rangle \left\lvert \epsilon t, (\omega_r+\chi)t \right\rangle, +# +# .. math:: \left\lvert e \right\rangle \left\lvert 0 \right\rangle \rightarrow \left\lvert e \right\rangle \left\lvert \epsilon t, (\omega_r-\chi)t \right\rangle. +# +# Consequently, if the state of the qubit was initially the superposition +# :math:`\alpha \left\lvert g \right\rangle +\beta \left\lvert e \right\rangle,` then the qubit-cavity system would evolve to the state +# +# .. math:: \left\lvert\psi(t)\right\rangle=\alpha \left\lvert g \right\rangle \left\lvert \epsilon t, (\omega_r+\chi)t \right\rangle +\beta \left\lvert e \right\rangle \left\lvert \epsilon t, (\omega_r-\chi)t \right\rangle. +# +# In general, this state represents an entangled state between the qubit and the cavity. So if we measure the state of the light +# trapped by the cavity, we can determine the qubit's state as well. +# +# Let us see how this works in practice using PennyLane. The ``default.gaussian`` device allows us to simulate +# coherent states of light. These states start implicitly in the vacuum (no photons) state. +# The PennyLane function ``qml.Displacement(x,0)`` applies a *displacement operator*, which creates +# a coherent state :math:`\left\lvert \bar{x}, 0\right\rangle.` The rotation operator ``qml.Rotation(phi)`` rotates the state +# :math:`\left\lvert \bar{x}, 0\right\rangle` in :math:`(x, p)` space. When applied after a large displacement, +# it changes the value of :math:`\bar{x}` only slightly, but noticeably changes the value of :math:`\bar{p}` by shifting it +# off from zero, as shown in the figure [#Blais2021]_: +# +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/phase_space.png +# :align: center +# :width: 60% +# +# .. +# +# Translation and rotation in the position-momentum picture +# +# It turns out that this sequence of operations implements the evolution of the cavity state exactly. Note that here we are +# taking :math:`\omega_r=0,` which simply corresponds to taking :math:`\omega_r` as a reference frequency, so a rotation by +# angle :math:`\phi` actually means a rotation by :math:`\omega_r+\phi.` In PennyLane, the operations read: + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +# Call the default.gaussian device with 50 shots +dev = qml.device("default.gaussian", wires=1, shots=50) + +# Fix parameters +epsilon, chi = 1.0, 0.1 + +# Implement displacement and rotation and measure both X and P observables + + +@qml.qnode(dev) +def measure_P_shots(time, state): + qml.Displacement(epsilon * time, 0, wires=0) + qml.Rotation((-1) ** state * chi * time, wires=0) + return qml.sample(qml.QuadP(0)) + + +@qml.qnode(dev) +def measure_X_shots(time, state): + qml.Displacement(epsilon * time, 0, wires=0) + qml.Rotation((-1) ** state * chi * time, wires=0) + return qml.sample(qml.QuadX(0)) + + +############################################################################## +# +# .. note:: +# +# It may be surprising to see the ``default.gaussian`` device being used here, since it is most often used +# when we work with photonic systems. But it is also valid to use it here, since we are modelling a measurement +# process that uses photons. +# +# We measure the photon's momentum at the end, since it allows us to distinguish qubit states +# as long as we can resolve them. Let us plot for three different durations of the microwave-cavity interaction. We will simulate +# the measurement of 50 photons, which can inform us whether the qubit is in the ground or excited state: + +fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5)) +fig.suptitle("Position/momentum measurement", fontsize=18) +ax1.scatter(measure_X_shots(1, 0), measure_P_shots(1, 0)) +ax1.scatter(measure_X_shots(1, 1), measure_P_shots(1, 1)) +ax2.scatter(measure_X_shots(3, 0), measure_P_shots(3, 0)) +ax2.scatter(measure_X_shots(3, 1), measure_P_shots(3, 1)) +ax3.scatter(measure_X_shots(5, 0), measure_P_shots(5, 0)) +ax3.scatter(measure_X_shots(5, 1), measure_P_shots(5, 1)) +ax1.set_title(r"$t=1/\epsilon$", fontsize=16) +ax2.set_title(r"$t=3/\epsilon$", fontsize=16) +ax3.set_title(r"$t=5/\epsilon$", fontsize=16) +ax1.set_ylabel("Momentum", fontsize=16) +ax2.set_xlabel("Position", fontsize=16) +plt.show() + +############################################################################## +# +# In the above, the blue and orange dots represent qubits which we can infer to be in the state :math:`\left\lvert g \right\rangle` +# and :math:`\left\lvert e \right\rangle` respectively. +# +# We see that the longer we shine microwaves on the cavity, the greater our ability +# to resolve the momentum change, making for an equally good measurement of the qubit's +# state. However, this poses a problem: being a relatively large object, the state of the +# qubit is rather short-lived due to decoherence. Therefore, taking a long time to make +# the measurement introduces additional inaccuracies: the qubits may lose quantum +# properties due to decoherence before we even finish measuring! An outstanding +# challenge for superconducting qubit technologies is to perform measurements as fast +# as possible, in times well below the decoherence time. +# +# Superconducting single-qubit gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We have seen that shining light with detuning :math:`\Delta \gg 1` is used to perform indirect measurements. +# A rather different choice, :math:`\omega_r =\omega_a` (:math:`\Delta=0`), allows us to manipulate the state +# of the qubit. But does the Fabry-Perot cavity transmit these photons if their wavelength does not +# allow it? We must emphasize that the cavity reflects only the vast majority of photons, +# but not all of them. If we compensate by increasing the radiation intensity, some photons +# will still be transmitted into the cavity and be absorbed by the superconducting qubit. +# +# If we shine a coherent state light with frequency :math:`\omega_a` on the cavity and phase +# :math:`\phi` at the position of the qubit, then the Hamiltonian for the artificial atom is +# +# .. math:: \hat{H}=\hbar\Omega_R(\hat{\sigma}_{x}\cos\phi + \hat{\sigma}_{y}\sin\phi). +# +# Here, :math:`\Omega_R` is a special frequency called the *Rabi frequency*, which depends on the average electric field in the +# cavity and the size of the superconducting qubit. With this Hamiltonian, we can implement +# a universal set of single-qubit gates since :math:`\phi=0` implements an :math:`X`-rotation and :math:`\phi=\pi/2` +# applies a :math:`Y`-rotation. +# +# Let us check this using PennyLane. For qubits, we can define +# Hamiltonians using ``qml.Hamiltonian`` and evolve an initial state using ``ApproxTimeEvolution``: + +from pennylane.templates import ApproxTimeEvolution + +dev2 = qml.device("lightning.qubit", wires=1) + +# Implement Hamiltonian evolution given phase phi and time t, from a given initial state +@qml.qnode(dev2) +def H_evolve(state, phi, time): + + if state == 1: + qml.PauliX(wires=0) + + coeffs = [np.cos(phi), np.sin(phi)] + ops = [qml.PauliX(0), qml.PauliY(0)] + Ham = qml.Hamiltonian(coeffs, ops) + ApproxTimeEvolution(Ham, time, 1) + return qml.state() + + +# Implement X rotation exactly +@qml.qnode(dev2) +def Sc_X_rot(state, phi): + + if state == 1: + qml.PauliX(wires=0) + + qml.RX(phi, wires=0) + return qml.state() + + +# Implement Y rotation exactly +@qml.qnode(dev2) +def Sc_Y_rot(state, phi): + + if state == 1: + qml.PauliX(wires=0) + + qml.RY(phi, wires=0) + return qml.state() + + +# Print to compare results + +print("State |0>:") + +print( + "X-rotated by pi/3: {}; Evolved for phi=0, t=pi/6: {}".format( + Sc_X_rot(0, np.pi / 3).round(2), H_evolve(0, 0, np.pi / 6).round(2) + ) +) +print( + "Y-rotated by pi/3: {}; Evolved for phi=pi/2, t=pi/6: {}\n".format( + Sc_Y_rot(0, np.pi / 3).round(2), H_evolve(0, np.pi / 2, np.pi / 6).round(2) + ) +) + +print("State |1>:") +print( + "X-rotated by pi/3: {}; Evolved for phi=0, t=pi/6: {}".format( + Sc_X_rot(1, np.pi / 3).round(2), H_evolve(1, 0, np.pi / 6).round(2) + ) +) +print( + "Y-rotated by pi/3: {}; Evolved for phi=pi/2, t=pi/6: {}\n".format( + Sc_Y_rot(1, np.pi / 3).round(2), H_evolve(1, np.pi / 2, np.pi / 6).round(2) + ) +) + +############################################################################## +# +# Thus, for a particular choice of angle, we have verified that this Hamiltonian implements rotations around the X and Y axes. +# We can do this for any choice of angle, where we see that the time interval needed for a rotation by an angle +# :math:`\theta` is :math:`t=2\theta/\Omega_R.` This time can be controlled simply by turning the source of microwaves on and off. +# +# The typical times in which a single-qubit gate is executed are in the order of the nanoseconds, making superconducting +# quantum computers the fastest ones out there. Why do they have this intrinsic advantage? The reason is that the +# Rabi frequency :math:`\Omega_R,` which grows with the size of the qubit and the magnitude of the electric field, +# is extremely high. We have the technology to make very small cavities, which means we can pack strong +# electric fields in a small region. Moreover, superconducting qubits are billions of times larger than +# other qubits, such as atoms. The drawback is that superconducting qubit technology would be +# impossible without these extremely high speeds: large qubits are extremely short-lived, so we are +# constrained to find the quickest ways to perform all quantum operations. +# +# Two-qubit gates +# ~~~~~~~~~~~~~~~ +# +# One of the main challenges in all realizations of quantum computers is building two-qubit gates, +# needed to satisfy Di Vincenzo's fourth criterion in full. An advantage of superconducting technology +# is the variety of options to connect qubits with each other. There are many options for this connection: +# do we just connect the two qubits with a superconducting wire, do we just put them in the same cavity? +# One of the best ways is via *capacitative coupling*, where we connect two transmons through +# a wire and *coupling capacitor*. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/capacitative.png +# :align: center +# :width: 60% +# +# .. +# +# Two transmons connected through a coupling capacitor +# +# As we will see, this capacitor helps us have +# a controlled interaction between qubits. When two **identical** transmons are connected through a coupling +# capacitor, the Hamiltonian for the system of two qubits reads [#Schuch2003]_ +# +# .. math:: \hat{H}=\frac{\hbar J}{2} (\sigma^{x}_1\sigma^{x}_2+\sigma^{y}_1\sigma^{y}_2), +# +# where :math:`J` depends on the coupling capacitance and the characteristics of both circuits. Note that since the transmons are +# identical, they have the same energy gap. +# The Hamiltonian :math:`\hat{H}` allows us to implement the two-qubit :math:`iSWAP` gate +# +# .. math:: iSWAP = \left( \begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 0 & i & 0 \\ 0 & i & 0 & 0 \\ 0 & 0 & 0 & 1 \end{array} \right) +# +# when applied for a time :math:`t=3\pi/2J,` as shown with the following PennyLane code: +# +dev3 = qml.device("lightning.qubit", wires=2) + +# Define Hamiltonian +coeffs = [0.5, 0.5] +ops = [qml.PauliX(0) @ qml.PauliX(1), qml.PauliY(0) @ qml.PauliY(1)] + +Two_qubit_H = qml.Hamiltonian(coeffs, ops) + +# Implement Hamiltonian evolution for time t and some initial computational basis state +@qml.qnode(dev3) +def Sc_ISWAP(basis_state, time): + qml.BasisState(basis_state, wires=range(2)) + ApproxTimeEvolution(Two_qubit_H, time, 1) + return qml.state() + + +# Implement ISWAP exactly +@qml.qnode(dev3) +def iswap(basis_state): + qml.BasisState(basis_state, wires=range(2)) + qml.ISWAP(wires=[0, 1]) + return qml.state() + + +# Print to compare results + +print("State |0>|0>:") +print( + "Evolved for t=3*pi/2: {}; Output of ISWAP gate: {}\n ".format( + Sc_ISWAP([0, 0], 3 * np.pi / 2).round(2), iswap([0, 0]).round(2) + ) +) + +print("State |0>|1>:") +print( + "Evolved for t=3*pi/2: {}; Output of ISWAP gate: {}\n ".format( + Sc_ISWAP([0, 1], 3 * np.pi / 2).round(2), iswap([0, 1]).round(2) + ) +) + +print("State |1>|0>:") +print( + "Evolved for t=3*pi/2: {}; Output of ISWAP gate: {}\n ".format( + Sc_ISWAP([1, 0], 3 * np.pi / 2).round(2), iswap([1, 0]).round(2) + ) +) + +print("State |1>|1>:") +print( + "Evolved for t=3*pi/2: {}; Output of ISWAP gate: {}\n ".format( + Sc_ISWAP([1, 1], 3 * np.pi / 2).round(2), iswap([1, 1]).round(2) + ) +) + + +############################################################################## +# +# To allow for universal computation, we must be able to build the ``CNOT`` gate by using only the ``ISWAP`` +# gate and any number of single-qubit gates. The following quantum circuit diagram depicts how we can achieve +# this [#Schuch2003]_. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/circuit.png +# :align: center +# :width: 85% +# +# .. +# +# Circuit to obtain the CNOT gate from the ISWAP gate +# +# We can verify that the circuit above gives us the ``CNOT`` gate up to a global phase using PennyLane: +# +def cnot_with_iswap(): + qml.RZ(-np.pi / 2, wires=0) + qml.RX(np.pi / 2, wires=1) + qml.RZ(np.pi / 2, wires=1) + qml.ISWAP(wires=[0, 1]) + qml.RX(np.pi / 2, wires=0) + qml.ISWAP(wires=[0, 1]) + qml.RZ(np.pi / 2, wires=1) + + +# Get matrix of circuit above +matrix = qml.matrix(cnot_with_iswap, wire_order=[0, 1])() + +# Multiply by a global phase to obtain CNOT +(np.exp(1j * np.pi / 4) * matrix).round(2) + +############################################################################## +# +# +# In the code above, we assumed that we can switch the interaction on and off to control its duration. How do we do this +# without physically tampering with the qubits? The truth is that we can never switch the interaction off completely, but we can weaken it greatly +# by changing the characteristics of one of the qubits. For example, if we change the inductance +# in one of the circuits to be much different from the other, the interaction strength :math:`J` in +# the Hamiltonian will be almost zero. +# +# This strategy, however, begs another question: how can we change the characteristics of the circuit elements on-demand? One possibility is to use +# **flux-tunable qubits**, also known as superconducting quantum interference devices (SQUIDs). +# They use two parallel Josephson junctions placed around each circuit, a setup that allows us to change the +# inductance of one qubit using an external magnetic field. This architecture, +# although fast, requires further interaction with the qubit, so there's some probability of unintentionally +# changing its state. +# +# .. figure:: ../_static/demonstration_assets/sc_qubits/squid.png +# :align: center +# :width: 60% +# +# .. +# +# SQUID architecture surrounding a transmon +# +# Another option is to use **all-microwave gates**. In this scenario, we place two **different** transmons in a single +# cavity, and shine microwaves that can be absorbed by the second qubit. The first qubit will scatter the +# photons, and the other will absorb them, causing a similar effect to that of the qubit-cavity +# system in the case of measurement. This means that we can entangle the two qubits. When the first qubit +# receives a microwave at the frequency that stimulates the second qubit, one can show that the (simplified) +# Hamiltonian is given by [#Rigetti2003]_ +# +# .. math:: \hat{H}=\hbar \tilde{\Omega} (\sigma^{z}_1\sigma^{x}_2\cos\phi+\sigma^{z}_1\sigma^{y}_2\sin\phi), +# +# where :math:`\phi` is the phase of the wave. As promised, we can obtain an entangled state by concatenating +# the evolution under this Hamiltonian for a time :math:`t=\tfrac{\pi}{4\Omega}` with :math:`R_x` and :math:`R_y` rotations +# and a ``qml.Hadamard`` gate: +# +@qml.qnode(dev3) +def H_evolve(state, phi, time): + # Prepare initial state + qml.BasisState(state, wires=range(2)) + # Define Hamiltonian + coeffs = [np.cos(phi), np.sin(phi)] + ops = [qml.PauliZ(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliY(1)] + Ham = qml.Hamiltonian(coeffs, ops) + # Combine Hamiltonian evolution with single-qubit gates + qml.Hadamard(wires=0) + qml.RX(-np.pi / 2, wires=1) + ApproxTimeEvolution(Ham, time, 1) + qml.RZ(-np.pi / 2, wires=0) + return qml.state() + + +# Verify that we return maximally entangled state up to a global phase +(np.exp(-1j * np.pi / 4) * H_evolve([0, 0], 0, np.pi / 4)).round(3) + +############################################################################## +# +# Although this method does not affect the qubit's state much, these gates are +# slower than those built using flux-tuning, since the Rabi frequency for this interaction +# turns out to be smaller. +# +# .. container:: alert alert-block alert-info +# +# **Historical note:** In the literature, you may find some proper names for particular +# types of coupled transmons. One of the most common is the **xmon**, which are transmons coupled +# with a "+" shaped capacitor. A further improvement over the xmon is the **gmon**, which adds an additional +# inductor to the coupling to better switch interactions on and off. Many more architectures have been +# proposed since the introduction of the xmon and the gmon, so we have reached a point where it is +# better to avoid getting lost in names. Understanding the basic principles introduced +# above will make us go a long way! +# +# The state of the art +# ~~~~~~~~~~~~~~~~~~~~ +# +# Superconducting qubits are a force to be reckoned with as practical implementations of quantum computers. +# Nonetheless, there are still some technological challenges preventing them from scaling further. +# A glaring issue is that we need to keep the transmons at very low temperatures, which requires the use of +# large cryogenic devices known as *dilution refrigerators*. However, many quantum computing +# technologies need to use cryogenic devices for different reasons. In the future, other quantum +# technologies may bypass the use of low temperatures, but superconducting qubits may not be so lucky, since +# they are constrained by the laws of physics that allow for superconductivity. +# +# In terms of Di Vincenzo's criteria, the long coherence times required by the +# third criterion have been rather hard to achieve. Superconducting circuits are +# large compared to other quantum systems, so their interaction with the environment is difficult to control. +# As a consequence, the excited state lasts for about 1 micro-second before decaying back to the ground state. +# Current single-qubit gates are acceptable since they can be applied in a matter of nanoseconds, +# thanks to our ability to manufacture very small cavities. Problems arise, however, when we +# want to perform precise measurements of the qubit. We've already seen that taking a longer +# time to perform a measurement gives us better precision. But there is an additional issue: +# the Hamiltonian we wrote in the measurement section is only +# valid when the number of photons in the cavity does not exceed some critical number. +# This upper bound on the photons sets additional constraints: +# we will need longer times to resolve the state, since we have few photons available to us. +# +# Speeding up measurements without losing precision is a hot research topic in superconductor +# quantum computing. The primary approach has been to make the most out of the limited number of +# photons we have by reducing all possible environmental noise. Currently, we can perform +# measurements in about 50 nanoseconds with 1% error. However, since frequent measurements +# are needed for error correction, better precision and shorter times are required +# to scale our architectures further. +# +# An additional challenge is the execution of the multi-qubit gates, which are currently ten times +# slower than single-qubit gates. One contender to the fastest two-qubit gate is used by Google, +# where the qubits are coupled with a capacitor and a SQUID. The net effect is a quick flux-tunable +# qubit, where the coupling :math:`J` is changed by using a magnetic flux that goes through the coupling SQUID. +# While slower, IBM prefers to use all-microwave gates to increase coherence times. Both approaches have +# the problem of *frequency crowding*: if we interconnect too many qubits together inside a cavity, they +# may start to have similar energy gaps. In that case, we may manipulate qubits that we did +# not intend to. The problem can be somewhat addressed by changing the geometry in which the qubits +# are connected [#IBMHex2021]_. However, much more work needs to be done to address this scalability issue. +# +# Conclusion +# ~~~~~~~~~~ +# +# +# Superconducting quantum computing has gained momentum in the last decade as a leading competitor +# in the race for building a functional quantum computer. It is based on artificial versions of atomic systems +# done using superconducting circuits, which allows for versatility and control. +# They have been easy to scale so far, but increasing the qubit coherence time and the speed of quantum +# operations and measurements is essential to scaling this technology further. This has motivated so +# many approaches for gates and measurement that we could not possibly cover them +# all in one demo. Do check the literature below if you'd like to learn more! +# +# References +# ~~~~~~~~~~ +# +# +# .. [#Google2019] +# +# Arute, F., Arya, K., Babbush, R. et al. (2019) "Quantum supremacy using a programmable superconducting processor", +# `Nature 574, 505-510 +# `__. +# +# .. [#IBM2021] +# +# `"IBM Unveils Breakthrough 127-Qubit Quantum Processor" +# `__. +# IBM Newsroom. Retrieved 2022-03-15. +# +# .. [#DiVincenzo2000] +# +# D. DiVincenzo. (2000) "The Physical Implementation of Quantum Computation", +# `Fortschritte der Physik 48 (9–11): 771–783 +# `__. +# (`arXiv `__) +# +# .. [#Bergou2021] +# +# Bergou, J., Hillery, M., and Saffman, M. (2021) "Quantum Information Processing", +# Springer. +# +# .. [#Blais2021] +# +# Blais, A., Grimsmo, A., Girvin, S., and Walraff, A. (2021) "Circuit quantum electrodynamics", +# `Rev. Mod. Phys. 93, 025005 +# `__. +# (`arXiv `__) +# +# .. [#Schuch2003] +# +# Schuch, N., Siewert, J. (2003) "Natural two-qubit gate for quantum computation using the XY interaction", +# `Phys. Rev. A 67, 032301 +# `__. +# (`arXiv `__) +# +# .. [#Rigetti2003] +# +# Rigetti, C., Devoret, M. (2009) "Fully microwave-tunable universal gates in superconducting qubits with linear couplings and fixed transition frequencies", +# `Phys. Rev. B 81, 134057 +# `__. +# +# .. [#IBMHex2021] +# +# `"The IBM Quantum heavy hex lattice" +# `__. +# IBM Research Blog. Retrieved 2022-03-15 +# diff --git a/demonstrations_v2/tutorial_sc_qubits/metadata.json b/demonstrations_v2/tutorial_sc_qubits/metadata.json new file mode 100644 index 0000000000..cfdf626252 --- /dev/null +++ b/demonstrations_v2/tutorial_sc_qubits/metadata.json @@ -0,0 +1,117 @@ +{ + "title": "Quantum computing with superconducting qubits", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2022-03-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QC_superconducting_qubits.png" + } + ], + "seoDescription": "Learn about quantum computers based on superconducting qubits, developed by companies such as IBM and Google.", + "doi": "", + "references": [ + { + "id": "Google2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Arute, F., Arya, K., Babbush, R. et al.", + "year": "2019", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-019-1666-5" + }, + { + "id": "IBM2021", + "type": "article", + "title": "IBM Unveils Breakthrough 127-Qubit Quantum Processor", + "authors": "", + "year": "2021", + "journal": "", + "url": "https://newsroom.ibm.com/2021-11-16-IBM-Unveils-Breakthrough-127-Qubit-Quantum-Processor" + }, + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "D. DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" + }, + { + "id": "Bergou2021", + "type": "book", + "title": "Quantum Information Processing", + "authors": "Bergou, J., Hillery, M., and Saffman, M.", + "year": "2021", + "publisher": "Springer", + "url": "" + }, + { + "id": "Blais2021", + "type": "article", + "title": "Circuit quantum electrodynamics", + "authors": "Blais, A., Grimsmo, A., Girvin, S., and Walraff, A.", + "year": "2021", + "journal": "Rev. Mod. Phys.", + "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.93.025005" + }, + { + "id": "Schuch2003", + "type": "article", + "title": "Natural two-qubit gate for quantum computation using the XY interaction", + "authors": "Schuch, N., Siewert, J.", + "year": "2003", + "journal": "Phys. Rev. A", + "url": "https://doi.org/10.1103/PhysRevA.67.032301" + }, + { + "id": "Rigetti2003", + "type": "article", + "title": "Fully microwave-tunable universal gates in superconducting qubits with linear couplings and fixed transition frequencies", + "authors": "Rigetti, C., Devoret, M.", + "year": "2009", + "journal": "Phys. Rev. B", + "url": "https://journals.aps.org/prb/abstract/10.1103/PhysRevB.81.134507" + }, + { + "id": "IBMHex2021", + "type": "article", + "title": "The IBM Quantum heavy hex lattice", + "authors": "", + "year": "2021", + "journal": "", + "url": "https://research.ibm.com/blog/heavy-hex-lattice" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_trapped_ions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/quantum-computing-with-superconducting-qubits-demo/7336" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_sc_qubits/requirements.in b/demonstrations_v2/tutorial_sc_qubits/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_sc_qubits/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py new file mode 100644 index 0000000000..84ac1fda71 --- /dev/null +++ b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py @@ -0,0 +1,363 @@ +r"""Shadow Hamiltonian simulation +================================= + +Shadow Hamiltonian simulation is a new approach (published last week) to quantum simulation on quantum computers [#SommaShadow]_. +Despite its name, it has little to do with :doc:`classical shadows `. +In quantum simulation, the goal is typically to simulate the time evolution of expectation values +of :math:`M` observables :math:`O_m,` for :math:`m=1,\ldots ,M.` +The common approach is to evolve the wave function :math:`|\psi\rangle` and then measure the desired observables after the evolution. + +In shadow Hamiltonian simulation, we instead directly encode the expectation values in a proxy state — the **shadow state** — +and evolve that state accordingly. Specifically for time evolution, we can write a shadow Schrödinger equation that governs the +dynamics of the shadow state. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_shadow_hamiltonian_simulation.png + :align: center + :width: 70% + +This is fundamentally different to the common approach. Foremost, the dimensionality of the +shadow system no longer depends on the number of constituents, :math:`n,` of the system. +In fact, the underlying state can be mixed or even infinite-dimensional. +Instead, the shadow system's size is dependent on the number of observables :math:`M` that we want to measure. +Note that there are conditions of completeness on the observables for the shadow encoding to succeed, called `invariance property` in [#SommaShadow]_. +Further, since the +expectation values are encoded in the amplitudes of states, we cannot directly measure them anymore, but need to resort to some +form of state tomography. +On the other hand, this gives us entirely new possibilities by letting us sample from the probability distribution +:math:`p_m = |\langle O_m \rangle|^2` and measure the absolute value of all observables simultaneously in the standard Z basis. + +In this demo, we are going to introduce the basic concepts of shadow Hamiltonian simulation alongside some easy-to-follow code snippets. +We will also see later how shadow Hamiltonian simulation comes down to :doc:`g-sim `, +a Lie-algebraic classical simulation tool, but run on a quantum computer with some simplifications specifically due to considering Hamiltonian simulation. + +Shadow Hamiltonian simulation — Definition +------------------------------------------ + +In common quantum Hamiltonian simulation, we evolve a state vector :math:`|\psi(t)\rangle` according to the Schrödinger equation, + +.. math:: \frac{\text{d}}{\text{dt}} |\psi(t)\rangle = -i H |\psi(t)\rangle, + +by some Hamiltonian :math:`H,` and then compute expectation values of the evolved state through measurement. +In shadow Hamiltonian simulation, we encode a set of expectation values in the amplitudes of a quantum state, +and evolve those according to some shadow Schrödinger equation. + +For that, we first need to define the shadow state, + +.. math:: |\rho\rangle = \frac{1}{\sqrt{A}} \begin{pmatrix} \langle O_1 \rangle \\ \vdots \\ \langle O_M \rangle \end{pmatrix}, + +for a set of operators :math:`S = \{O_m\}` and normalization constant :math:`A = \sum_m |\langle O_m \rangle|^2.` +This means that we can encode these :math:`M` operator expectation values into :math:`n_S` qubits, as long as :math:`2^{n_S} \geq M.` +Note that :math:`\langle O_m \rangle = \text{tr}[O_m \rho],` so we can have mixed or even infinite-dimensional states :math:`\rho.` + +The shadow state evolves according to its shadow Schrödinger equation, + +.. math:: \frac{\text{d}}{\text{dt}} |\rho\rangle = - i H_S |\rho\rangle. + +The Hamiltonian matrix :math:`H_S` is given by the commutation relations between the system Hamiltonian :math:`H` and +the operators in :math:`S = \{O_m\},` + +.. math:: [H, O_m] = - \sum_{m'=1}^M \left( H_S \right)_{m m'} O_{m'}. + +Let us solve for the matrix elements :math:`(H_S)_{m m'}.` To do this, recall that a vector :math:`\boldsymbol{v}` can always be decomposed in an orthogonal basis :math:`\boldsymbol{e}_j` via +:math:`\boldsymbol{v} = \sum_j \frac{\langle \boldsymbol{e}_j, \boldsymbol{v}\rangle}{||\boldsymbol{e}_j||^2} \boldsymbol{e}_j.` +Since the operators under consideration are elements of the vector space of Hermitian operators, we can use this to compute :math:`H_S.` + +In particular, with the trace inner product, this amounts to + +.. math:: [H, O_m] = \sum_{m'=1}^M \frac{\text{tr}\left( O_{m'} [H, O_m] \right)}{|| O_{m'} ||^2} O_{m'}, + +from which we can read off the matrix elements of :math:`H_S,` i.e., + +.. math:: (H_S)_{m m'} = -\frac{\text{tr}\left( O_{m'} [H, O_m] \right)}{|| O_{m'} ||^2}. + +Now, we can see that the operators :math:`O_m` need to be chosen such that all potentially +new operators :math:`\mathcal{O} = [H, O_m]`, resulting from taking the commutator between :math:`H` and :math:`O_m,` are decomposable +in terms of :math:`O_m` again. In particular, the operators :math:`O_m` need to form a basis for :math:`\{\mathcal{O} | \mathcal{O} = [H, O_m] \}.` +Another way to say this is that :math:`\{O_m\}` need to contain all nested commutators :math:`[[[H, O_m], O_m'], .. ],` which is similar to :func:`~pennylane.lie_closure` but weaker because it revolves around just :math:`H.` +In the paper this is called the **invariance property**. + +.. note:: + + Take for example :math:`H = X` and :math:`S = \{Y\}`. Then :math:`[H, Y] = iZ,` so there is no linear combination of elements in :math:`S` that can decompose :math:`[H, Y].` + We need to extend the list such that we have :math:`S = \{Y, Z\}`. Now all results from commutation, :math:`[H, Y] = iZ` and :math:`[H, Z] = -iY,` are supported by :math:`S.` This is similar + to the Lie closure that we discuss in our :doc:`intro to Lie algebras for quantum practitioners `, but the requirements are not as strict because + we only need support with respect to commentators with :math:`H,` and not among all elements in :math:`S.` + +How this relates to g-sim +------------------------- + +In :doc:`g-sim ` +[#Somma]_ [#Somma2]_ [#Galitski]_ [#Goh]_, we have operators :math:`\{ g_i \}` that are generators or observables for a parametrized quantum circuit, +e.g. :math:`U(\theta) = \prod_\ell \exp(-i \theta_\ell g_\ell)` and :math:`\langle g_i \rangle.` +For that, we are looking at the so-called dynamical Lie algebra (DLA) of the circuit, +:math:`\mathfrak{g} = \langle \{ g_i \} \rangle_\text{Lie} = \{ g_1, .., g_{|\mathfrak{g}|} \},` as well as +the adjoint representation +:math:`(-i \text{ad}_{g_\gamma})_{\alpha \beta} = f^\gamma_{\alpha \beta},` where :math:`f^\gamma_{\alpha \beta}` are the +:func:`~pennylane.structure_constants` of the DLA. +They are computed via + +.. math:: f^\gamma_{\alpha \beta} = \frac{\text{tr}\left(g_\gamma [g_\alpha, g_\beta] \right)}{||g_\gamma||^2}. + +The operators in :math:`\frak{g}` can always be orthonormalized via the `Gram–Schmidt process `__, +in which case we can drop the denominator. Further, by means of the cyclic property of the trace, we can rewrite this expression to obtain + +.. math:: f^\gamma_{\alpha \beta} = \text{tr}\left(g_\beta [g_\gamma, g_\alpha] \right). + +From this, we see how :math:`H_S` corresponds to the adjoint representation :math:`i \text{ad}_H` (but we don't require the full Lie algebra here, see below). +For further details on the concept of the adjoint representation, see our +:doc:`demo on g-sim ` that makes extensive use of it. + +In g-sim, we also evolve expectation vectors :math:`(\vec{g})_i = \langle g_i \rangle.` +In particular, the circuit of evolving a state according to :math:`U(\theta)` and computing expectation values +:math:`\langle g_i \rangle` then corresponds to evolving :math:`\vec{g}` by :math:`\prod_\ell \exp(-i \theta_\ell \text{ad}_{g_\ell}).` + +Shadow Hamiltonian simulation can thus be viewed as g-sim +with a single, specific gate :math:`U(\theta) = e^{-i \theta H}` and parameter :math:`\theta = t,` and run on a quantum computer. + +One striking difference is that, because +we only have one specific "gate", we do not need the full Lie closure of the operators whose expectation values we want to compute. +Instead, here it is sufficient to choose :math:`O_m` such that they build up the full support for all :math:`[H, O_m].` +This could potentially be a significant difference, as the Lie closure in most cases leads to an exponentially large DLA [#Wiersema]_ [#Aguilar]_, +though the scaling of the span of all :math:`[H, O_m]` is unclear at this point. + +A simple example +---------------- + +The abstract concepts of shadow Hamiltonian simulation are best illustrated with a simple and concrete example. +We are interested in simulating the Hamiltonian evolution of + +.. math:: H = X + Y + +after a time :math:`t = 1` and computing the expectation values of :math:`S = \{X, Y, Z, I \}.` +In the standard formulation, we simply evolve the initial quantum state :math:`|\psi(0)\rangle = |0\rangle` by :math:`H` in the +following way. + +""" + +import pennylane as qml +import numpy as np +from pennylane import X, Y, Z, I + +dev = qml.device("default.qubit") + +S = [X(0), Y(0), Z(0), I(0)] +H = X(0) + Y(0) + +@qml.qnode(dev) +def evolve(H, t): + qml.evolve(H, t) + return [qml.expval(Om) for Om in S] + +t = 1. +O_t_standard = np.array(evolve(H, t)) +O_t_standard + +############################################################################## +# We evolved a :math:`2^n = 2` dimensional quantum state and performed :math:`3` independent (non-commuting) measurements. +# +# In shadow Hamiltonian simulation, we encode :math:`4` expectation values in a :math:`2^2 = 4`-dimensional +# quantum state, i.e., :math:`n_S = 2.` +# +# For this specific example, the number of operators is larger than the number of qubits, leading to a shadow system that +# is larger than the original system. This may or may not be a clever choice, but the point here is just to illustrate +# the conceptual difference between both approaches. The authors in [#SommaShadow]_ show various examples where +# the resulting shadow system is significantly smaller than the original system. It should also be noted that having a smaller shadow system may not +# always be its sole purpose, as there are conceptually new avenues one can explore with shadow Hamiltonian simulation, such +# as sampling from the distribution :math:`p_m = |\langle O_m \rangle |^2.` +# +# Let us first construct the initial shadow state :math:`\boldsymbol{O}(t=0)` by computing +# :math:`\langle O_m \rangle_{t=0} = \text{tr}\left(O_m |\psi(0)\rangle \langle \psi(0)| \right)` +# with :math:`|\psi(0)\rangle = |0\rangle.` +# The ``pauli_rep`` attribute of PennyLane operators returns a :class:`~.pennylane.pauli.PauliSentence` instance and lets us efficiently +# compute the trace, where we use the trick that :math:`|0 \rangle \langle 0| = (I + Z)/2.` +# + +S_pauli = [op.pauli_rep for op in S] + +O_0 = np.zeros(len(S)) + +for m, Om in enumerate(S_pauli): + psi0 = (I(0) + Z(0)).pauli_rep + + O_0[m] = (psi0 @ Om).trace() + + +O_0 + +############################################################################## +# There are a variety of methods to encode this vector in a qubit basis, but we will just be using +# :class:`~.pennylane.StatePrep` later. +# +# We now go on to construct the shadow Hamiltonian :math:`H_S` by computing the elements +# :math:`(H_S)_{m m'} = \frac{\text{tr}\left( O_{m'} [H, O_m] \right)}{|| O_{m'} ||^2},` and +# we again make use of the :meth:`~.pennylane.pauli.PauliSentence.trace` method. +# + +H_pauli = H.pauli_rep + +H_S = np.zeros((len(S), len(S)), dtype=complex) + +for m, Om in enumerate(S_pauli): + com = H_pauli.commutator(Om) + for mt, Omt in enumerate(S_pauli): + # v = ∑ (v · e_j / ||e_j||^2) * e_j + + value = (Omt @ com).trace() + value = value / (Omt @ Omt).trace() + H_S[m,mt] = value + +H_S = -H_S # definition eq. (2) in [1] + +############################################################################## +# In order for the shadow evolution to be unitary and implementable on a quantum computer, +# we need :math:`H_S` to be Hermitian. +# + +np.all(H_S == H_S.conj().T) + +############################################################################## +# Knowing that, we can write the formal solution to the shadow Schrödinger equation as +# +# .. math:: \boldsymbol{O}(t) = \exp\left(-i t H_S \right) \boldsymbol{O}(0). +# + +from scipy.linalg import expm + +O_t = expm(-1j * t * H_S) @ O_0 +O_t + +############################################################################## +# Up to this point, this is equivalent to :doc:`g-sim ` if we were doing classical simulation. +# Now, the main novelty for shadow Hamiltonian simulation is to perform this on a quantum computer by encoding the +# expectation values of :math:`\langle O_m \rangle` in the amplitude of a quantum state, and to translate :math:`H_S` +# accordingly. +# +# This can be done by decomposing the numerical matrix :math:`H_S` into Pauli operators, which can, in turn, +# be implemented on a quantum computer. +# + +H_S_qubit = qml.pauli_decompose(H_S) +H_S_qubit + +############################################################################## +# Using all these ingredients, we now are able to formulate the shadow Hamiltonian simulation as a quantum algorithm. +# For the amplitude encoding, we need to make sure that the state is normalized. We use that normalization factor to then +# later retrieve the correct result. +# + +A = np.linalg.norm(O_0) + +@qml.qnode(dev) +def shadow_evolve(H_S_qubit, O_0, t): + qml.StatePrep(O_0 / A, wires=range(2)) + qml.evolve(H_S_qubit, t) + return qml.state() + +O_t_shadow = shadow_evolve(H_S_qubit, O_0, t) * A + +print(O_t_standard) +print(O_t_shadow) + +############################################################################## +# We see that the results of both approaches match. +# +# The first result is coming from three independent measurements on a quantum computer after evolution with system Hamiltonian :math:`H.` +# This is conceptually very different from the second result where +# :math:`\boldsymbol{O}` is encoded in the state of the shadow system (note the ``qml.state()`` return), which we evolved according to :math:`H_S.` +# +# In the first case, the measurement is directly obtained, however, +# in the shadow Hamiltonian simulation, we need to access the amplitudes of the underlying state. +# This can be done naively with state tomography, but in instances where we know +# that :math:`\langle O_m \rangle \geq 0,` we can just sample bitstrings according to +# :math:`p_m = |\langle O_m\rangle|^2.` The ability to sample from such a distribution +# :math:`p_m = |\langle O_m\rangle|^2` is a unique and new feature to shadow Hamiltonian simulation. +# +# We should also note that we made use of the abstract quantum sub-routines :func:`~.pennylane.evolve` and :class:`~.pennylane.StatePrep`, which each warrant their +# specific implementation. For example, :class:`~.pennylane.StatePrep` can be realized by :class:`~MottonenStatePreparation` and :func:`~.pennylane.evolve` can be realized +# by :class:`TrotterProduct`, though that is not be the focus of this demo. + +############################################################################## +# +# Conclusion +# ---------- +# +# We introduced the basic concepts of shadow Hamiltonian simulation and learned how it fundamentally differs from the common approach to Hamiltonian simulation. +# +# We have seen how classical Hamiltonian simulation is tightly connected to g-sim, but run on a quantum computer. +# A significant difference comes from the fact that the authors in [#SommaShadow]_ specifically look at Hamiltonian simulation, :math:`\exp(-i t H),` +# which allows us to just look at operators :math:`O_m` that support all commutators :math:`[H, O_m],` instead of the full Lie closure. +# There may be some advantage to this feat, because Lie algebras in quantum computing typically scale exponentially [#Wiersema]_ [#Aguilar]_. +# However, the scaling of such sets of operators is unclear at this point and needs further investigation. +# +# Note that even in the case of an exponentially sized set of operators, we have — at least in principle — an exponentially large state vector to store the +# :math:`M \leq 2^{n_S}` values. In the absolute worst case we have :math:`\mathfrak{su}(2^n)` with a dimension of +# :math:`2^{2n}-1,` so :math:`n_S = 2n` and thus it is just doubling the number of qubits. +# +# The biggest potential to this new persepctive on Hamiltonian simulation most likely lies in finding interesting applications like [#Babbush]_ or [#Barthe]_ +# that naturally encode the problem and allow for efficient retrieval of all the relevant information. +# + + + +############################################################################## +# +# References +# ---------- +# +# .. [#SommaShadow] +# +# Rolando D. Somma, Robbie King, Robin Kothari, Thomas O'Brien, Ryan Babbush +# "Shadow Hamiltonian Simulation" +# `arXiv:2407.21775 `__, 2024. +# +# .. [#Somma] +# +# Rolando D. Somma +# "Quantum Computation, Complexity, and Many-Body Physics" +# `arXiv:quant-ph/0512209 `__, 2005. +# +# .. [#Somma2] +# +# Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill +# "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models" +# `arXiv:quant-ph/0601030 `__, 2006. +# +# .. [#Galitski] +# +# Victor Galitski +# "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach" +# `arXiv:1012.2873 `__, 2010. +# +# .. [#Goh] +# +# Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage +# "Lie-algebraic classical simulations for variational quantum computing" +# `arXiv:2308.01432 `__, 2023. +# +# .. [#Wiersema] +# +# Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov +# "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension" +# `arXiv:2309.05690 `__, 2023. +# +# .. [#Aguilar] +# +# Gerard Aguilar, Simon Cichy, Jens Eisert, Lennart Bittel +# "Full classification of Pauli Lie algebras" +# `arXiv:2408.00081 `__, 2024. +# +# .. [#Babbush] +# +# Ryan Babbush, Dominic W. Berry, Robin Kothari, Rolando D. Somma, Nathan Wiebe +# "Exponential quantum speedup in simulating coupled classical oscillators" +# `arXiv:2303.13012 `__, 2023. +# +# .. [#Barthe] +# +# Alice Barthe, M. Cerezo, Andrew T. Sornborger, Martin Larocca, Diego García-Martín +# "Gate-based quantum simulation of Gaussian bosonic circuits on exponentially many modes" +# `arXiv:2407.06290 `__, 2024. +# + + +############################################################################## diff --git a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json new file mode 100644 index 0000000000..8575f38484 --- /dev/null +++ b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json @@ -0,0 +1,122 @@ +{ + "title": "Shadow Hamiltonian simulation", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-08-06T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_shadow_hamiltonian_simulation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_shadow_hamiltonian_simulation.png" + } + ], + "seoDescription": "A user-friendly intro to the contents of shadow Hamiltonian simulation, a technique introduced by by Somma et al (arXiv:2407.21775), with a code implementation.", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "Somma2", + "type": "preprint", + "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", + "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", + "year": "2006", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0601030", + "url": "https://arxiv.org/abs/quant-ph/0601030" + }, + { + "id": "Galitski", + "type": "preprint", + "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", + "authors": "Victor Galitski", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1012.2873", + "url": "https://arxiv.org/abs/1012.2873" + }, + { + "id": "Aguilar", + "type": "preprint", + "title": "Full classification of Pauli Lie algebras", + "authors": "Gerard Aguilar, Simon Cichy, Jens Eisert, Lennart Bittel", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2408.00081", + "url": "https://arxiv.org/abs/2408.00081" + }, + { + "id": "SommaShadow", + "type": "preprint", + "title": "Shadow Hamiltonian Simulation", + "authors": "olando D. Somma, Robbie King, Robin Kothari, Thomas O'Brien, Ryan Babbush", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2407.21775", + "url": "https://arxiv.org/abs/2407.21775" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2407.21775" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in new file mode 100644 index 0000000000..17c51f9a9f --- /dev/null +++ b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in @@ -0,0 +1,3 @@ +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_spsa/demo.py b/demonstrations_v2/tutorial_spsa/demo.py new file mode 100644 index 0000000000..9f7bd07bf3 --- /dev/null +++ b/demonstrations_v2/tutorial_spsa/demo.py @@ -0,0 +1,554 @@ +r""".. _spsa: + +Optimization using SPSA +======================= + +.. meta:: + :property="og:description": Use the simultaneous perturbation stochastic + approximation algorithm to optimize variational circuits in PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/spsa_mntn.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_vqe_qng Accelerating VQEs with quantum natural gradient + qnspsa Quantum natural SPSA optimizer + +*Authors: Antal Szava & David Wierichs — Posted: 19 March 2021. Last updated: 23 February 2023.* + +In this tutorial, we investigate using a stochastic optimizer called +the Simultaneous Perturbation Stochastic Approximation (SPSA) algorithm to optimize quantum +circuits. This optimizer is built into PennyLane as :class:`~pennylane.SPSAOptimizer`. +SPSA is a technique that involves approximating the gradient of a +quantum circuit without having to compute it exactly. + +This demonstration shows how the SPSA optimizer performs on the following tasks, +compared to a gradient descent optimization: + +1. A simple task on a sampling device, +2. The variational quantum eigensolver on a simulated hardware device. + +Throughout the demo, we show results obtained with SPSA and with gradient +descent and also compare the number of executed circuits required to complete +each optimization. + +Background +---------- + +In PennyLane, quantum gradients on hardware are commonly computed using +`parameter-shift rules `_. +Computing quantum gradients involves evaluating the partial derivative of the quantum +function with respect to every free parameter. These partial derivatives are then used +to apply the chain rule to compute the gradient of the quantum circuit. For qubit +operations that are generated by one of the Pauli matrices, each partial +derivative computation will involve two quantum circuit evaluations with a +positive and a negative shift in the parameter values. + +As there are two circuit evaluations for each free parameter, the number of +overall quantum circuit executions for computing a quantum gradient can be expected +to scale as :math:`O(p)` with the number of free parameters :math:`p.` +This scaling can be very costly for optimization tasks with many +free parameters. For the overall optimization this scaling means we need +:math:`O(pn)` quantum circuit evaluations, where :math:`n` is the number of +optimization steps taken. + +Fortunately, there are certain optimization techniques that offer an +alternative to computing the gradients of quantum circuits. One such technique +is called the Simultaneous Perturbation Stochastic Approximation (SPSA) +algorithm [#spall_overview]_. SPSA is an optimization method that involves +*approximating* the gradient of the cost function at each iteration step. This +technique requires only two quantum circuit executions per iteration step, +regardless of the number of free parameters. Therefore the overall number of +circuit executions would be :math:`O(n')` where :math:`n'` is the number of +optimization steps taken when using SPSA. This technique is also considered +robust against noise, making it a great optimization method in the NISQ era. + +In this demo, you'll learn how the SPSA algorithm works, and how to apply it in +PennyLane to compute gradients of quantum circuits. You'll also see it in action +using noisy quantum data! + +Simultaneous perturbation stochastic approximation (SPSA) +--------------------------------------------------------- + +SPSA is a general method for minimizing differentiable multivariate functions. +It is particularly useful for functions for which evaluating the gradient is not +possible, or too resource intensive. SPSA provides a stochastic method for +approximating the gradient of the cost function. To +accomplish this, the cost function is evaluated twice using perturbed parameter +vectors: every component of the original parameter vector is simultaneously +shifted with a randomly generated value. This is in contrast to +finite-differences methods where for each evaluation only one component of the +parameter vector is shifted at a time. + +Similar to gradient-based approaches such as gradient descent, SPSA is an +iterative optimization algorithm. Let's consider a differentiable cost function +:math:`L(\theta)` where :math:`\theta` is a :math:`p`-dimensional vector and +where the optimization problem can be translated into finding a optimal +parameter setting :math:`\theta^*` +at which :math:`\frac{\partial L}{\partial \theta} = 0.` It is assumed that +measurements of :math:`L(\theta)` are available at various values of +:math:`\theta`---this is exactly the problem that we'd consider when optimizing +quantum functions! + +SPSA starts with an initial parameter vector :math:`\hat{\theta}_{0}.` +Its update rule is very similar to the one of standard gradient descent: + +.. math:: \hat{\theta}_{k+1} = \hat{\theta}_{k} - a_{k}\hat{g}_{k}(\hat{\theta}_{k}), + +where :math:`\hat{g}_{k}` is the stochastic estimate of the gradient +:math:`g(\theta) = \frac{ \partial L}{\partial \theta}` +at the iterate :math:`\hat{\theta}_{k}` +based on prior measurements of the cost function, and :math:`a_{k}` is a +positive number [#spall_overview]_. + +One of the advantages of SPSA is that it is robust to noise that may occur +when measuring the function :math:`L.` Therefore, let's consider the function +:math:`y(\theta)=L(\theta) + \varepsilon,` where :math:`\varepsilon` is some +perturbation of the output. In SPSA, the estimated gradient at each iteration +step is expressed as + +.. math:: \hat{g}_{ki} (\hat{\theta}_{k}) = \frac{y(\hat{\theta}_{k} +c_{k}\Delta_{k}) + - y(\hat{\theta}_{k} -c_{k}\Delta_{k})}{2c_{k}\Delta_{ki}}, + +where :math:`c_{k}` is a positive number and :math:`\Delta_{k} = (\Delta_{k_1}, +\Delta_{k_2}, ..., \Delta_{k_p})^{T}` is a perturbation vector. The +stochasticity of the technique comes from the fact that for each iteration step +:math:`k` the components of the :math:`\Delta_{k}` perturbation vector are +randomly generated using a zero-mean distribution. In most cases, the Rademacher +distribution is used, meaning each parameter is simultaneously perturbed by +either :math:`\pm c_k.` + +It is this perturbation that makes SPSA robust to noise — since every +parameter is already being shifted, additional shifts due to noise are less +likely to hinder the optimization process. In a sense, noise gets "absorbed" +into the already-stochastic process. This is highlighted in the figure below, +which portrays an example of the type of path SPSA takes through the space of +the function, compared to a standard gradient-based optimizer. + +.. figure:: ../_static/demonstration_assets/spsa/spsa_mntn.png + :align: center + :width: 60% + + .. + + A schematic of the search paths used by gradient descent with + parameter-shift and SPSA. + +Now that we have explored how SPSA works, let's see how it performs in practice! + +Optimization on a sampling device +--------------------------------- + +First, let's consider a simple quantum circuit on a sampling device. For this, +we'll be using a device from the `PennyLane-Qiskit plugin +`_ that samples quantum +circuits to get measurement outcomes and later post-processes these outcomes to +compute statistics like expectation values. + +.. note:: + + Just as with other PennyLane device, the number of samples taken for a circuit + execution can be specified using the ``shots`` keyword argument of the + device. + +Once we have a device selected, we just need a couple of other ingredients for +the pieces of an example optimization to come together: + +* a circuit ansatz: :func:`~pennylane.templates.layers.StronglyEntanglingLayers`, +* initial parameters: the correct shape can be computed by the ``shape`` method of the ansatz. + We also use a seed so that we can simulate the same optimization every time + (except for the device noise and shot noise). +* an observable: :math:`\bigotimes_{i=0}^{N-1}\sigma_z^i,` where :math:`N` stands + for the number of qubits. +* the number of layers in the ansatz and the number of wires. + We choose five layers and four wires. + +""" + +import pennylane as qml +from pennylane import numpy as np + +num_wires = 4 +num_layers = 5 + +device = qml.device("qiskit.aer", wires=num_wires, shots=1000) + +ansatz = qml.StronglyEntanglingLayers + +all_pauliz_tensor_prod = qml.prod(*[qml.PauliZ(i) for i in range(num_wires)]) + + +def circuit(param): + ansatz(param, wires=list(range(num_wires))) + return qml.expval(all_pauliz_tensor_prod) + + +cost_function = qml.QNode(circuit, device) + +np.random.seed(50) + +param_shape = ansatz.shape(num_layers, num_wires) +init_param = np.random.normal(scale=0.1, size=param_shape, requires_grad=True) + +############################################################################## +# We will execute a few optimizations in this demo, so let's prepare a convenience +# function that runs an optimizer instance and records the cost values +# along the way. Together with the number of executed circuits, this will be an +# interesting quantity to evaluate the optimization cost on hardware! + + +def run_optimizer(opt, cost_function, init_param, num_steps, interval, execs_per_step): + # Copy the initial parameters to make sure they are never overwritten + param = init_param.copy() + + # Obtain the device used in the cost function + dev = cost_function.device + + # Initialize the memory for cost values during the optimization + cost_history = [] + # Monitor the initial cost value + cost_history.append(cost_function(param)) + exec_history = [0] + + print( + f"\nRunning the {opt.__class__.__name__} optimizer for {num_steps} iterations." + ) + for step in range(num_steps): + # Print out the status of the optimization + if step % interval == 0: + print( + f"Step {step:3d}: Circuit executions: {exec_history[step]:4d}, " + f"Cost = {cost_history[step]}" + ) + + # Perform an update step + param = opt.step(cost_function, param) + + # Monitor the cost value + cost_history.append(cost_function(param)) + exec_history.append((step + 1) * execs_per_step) + + print( + f"Step {num_steps:3d}: Circuit executions: {exec_history[-1]:4d}, " + f"Cost = {cost_history[-1]}" + ) + return cost_history, exec_history + + +############################################################################## +# Once we have defined each piece of the optimization, there's only one +# remaining component required: the *SPSA optimizer*. +# We'll use the :class:`~pennylane.SPSAOptimizer` built into PennyLane, +# for 200 iterations in total. +# +# Choosing the hyperparameters +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``SPSAOptimizer`` allows us to choose the initial value of two +# hyperparameters for SPSA: the :math:`c` and :math:`a` coefficients. Recall +# from above that the :math:`c` values control the scale of the random shifts when +# evaluating the cost function, while the :math:`a` coefficient is analogous to a +# learning rate and affects the rate at which the parameters change at each update +# step. +# +# With stochastic approximation, specifying such hyperparameters significantly +# influences the convergence of the optimization for a given problem. Although +# there is no universal recipe for selecting these values (as they depend +# strongly on the specific problem), [#spall_implementation]_ includes +# guidelines for the selection. In our case, the initial values for :math:`c` +# and :math:`a` were selected as a result of a grid search to ensure a fast +# convergence. We further note that apart from :math:`c` and :math:`a,` there +# are further coefficients that are initialized in the ``SPSAOptimizer`` +# using the previously mentioned guidelines. For more details, also consider the +# `PennyLane documentation of the optimizer +# `__ + +num_steps_spsa = 200 +opt = qml.SPSAOptimizer(maxiter=num_steps_spsa, c=0.15, a=0.2) +# We spend 2 circuit evaluations per step: +execs_per_step = 2 +cost_history_spsa, exec_history_spsa = run_optimizer( + opt, cost_function, init_param, num_steps_spsa, 20, execs_per_step +) + +############################################################################## +# Now let's perform the same optimization using gradient descent. We set the +# step size according to a favourable value found after grid search for fast +# convergence. + +num_steps_grad = 15 +opt = qml.GradientDescentOptimizer(stepsize=0.3) +# We spend 2 circuit evaluations per parameter per step: +execs_per_step = 2 * np.prod(param_shape) +cost_history_grad, exec_history_grad = run_optimizer( + opt, cost_function, init_param, num_steps_grad, 3, execs_per_step +) + +############################################################################## +# SPSA and gradient descent comparison +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# At this point, nothing else remains but to check which of these approaches did better! +import matplotlib.pyplot as plt + +plt.figure(figsize=(10, 6)) + +plt.plot(exec_history_grad, cost_history_grad, label="Gradient descent") +plt.plot(exec_history_spsa, cost_history_spsa, label="SPSA") + +plt.xlabel("Circuit executions", fontsize=14) +plt.ylabel("Cost function value", fontsize=14) +plt.grid() + +plt.title("Gradient descent vs. SPSA for simple optimization", fontsize=16) +plt.legend(fontsize=14) +plt.show() + + +############################################################################## +# It seems that SPSA performs great and it does so with significant savings when +# compared to gradient descent! +# +# Let's take a deeper dive to see how much better it actually is by computing +# the ratio of required circuit executions to reach an absolute accuracy of 0.01. +# +grad_execs_to_prec = exec_history_grad[ + np.where(np.array(cost_history_grad) < -0.99)[0][0] +] +spsa_execs_to_prec = exec_history_spsa[ + np.where(np.array(cost_history_spsa) < -0.99)[0][0] +] +print(f"Circuit execution ratio: {np.round(grad_execs_to_prec/spsa_execs_to_prec, 3)}.") + +############################################################################## +# This means that SPSA found the minimum up to an absolute accuracy of 0.01 while +# using multiple times fewer circuit executions than gradient descent! That's an important +# saving, especially when running the algorithm on actual quantum hardware. +# +# SPSA and the variational quantum eigensolver +# -------------------------------------------- +# +# Now that we've explored the theoretical underpinnings of SPSA and its use for a +# toy problem optimization, let's use it +# to optimize a real chemical system, namely that of the hydrogen molecule :math:`H_2.` +# This molecule was studied previously in the :doc:`introductory variational quantum +# eigensolver (VQE) demo `, and so we will reuse some of +# that machinery below to set up the problem. +# +# The :math:`H_2` Hamiltonian uses 4 qubits, contains 15 terms, and has a ground +# state energy of :math:`-1.136189454088` Hartree. +# + +from pennylane import qchem + +symbols = ["H", "H"] +coordinates = np.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) +molecule = qchem.Molecule(symbols, coordinates) +h2_ham, num_qubits = qchem.molecular_hamiltonian(molecule) +h2_ham_coeffs, h2_ham_ops = h2_ham.terms() +h2_ham = qml.Hamiltonian(qml.math.real(h2_ham_coeffs), h2_ham_ops) + +true_energy = -1.136189454088 + + +# Variational ansatz for H_2 - see Intro VQE demo for more details +def ansatz(param, wires): + qml.BasisState(np.array([1, 1, 0, 0]), wires=wires) + for i in wires: + qml.Rot(*param[0, i], wires=i) + qml.CNOT(wires=[2, 3]) + qml.CNOT(wires=[2, 0]) + qml.CNOT(wires=[3, 1]) + for i in wires: + qml.Rot(*param[1, i], wires=i) + + +############################################################################## +# +# Since SPSA is robust to noise, let's see how it fares compared to gradient +# descent when run on noisy hardware. For this, we will set up and use a simulated +# version of IBM Q's hardware. +# + +# Note: you will need to be authenticated to IBMQ to run the following (commented) code. +# Do not run the simulation on this device, as it will send it to real hardware +# For access to IBMQ, the following statements will be useful: +# from qiskit_ibm_provider import IBMProvider +# IBMProvider.save_account("MY_API_TOKEN") # Save your IBMQ account to disk +# The above line only needs to be run once. +# List the providers to pick an available backend: +# IBMProvider().backends() # List all available backends +# dev = qml.device("qiskit.ibmq", wires=num_qubits, backend="ibmq_lima") + +from qiskit_ibm_runtime.fake_provider import FakeLima +from qiskit_aer import noise + +# Load a fake backed to create a noise model, and create a device using that model +noise_model = noise.NoiseModel.from_backend(FakeLima()) +noisy_device = qml.device( + "qiskit.aer", wires=num_qubits, shots=1000, noise_model=noise_model +) + + +def circuit(param): + ansatz(param, range(num_qubits)) + return qml.expval(h2_ham) + + +cost_function = qml.QNode(circuit, noisy_device) + +# This random seed was used in the original VQE demo and is known to allow the +# gradient descent algorithm to converge to the global minimum. +np.random.seed(0) +param_shape = (2, num_qubits, 3) +init_param = np.random.normal(0, np.pi, param_shape, requires_grad=True) + +# Initialize the optimizer - optimal step size was found through a grid search +opt = qml.GradientDescentOptimizer(stepsize=2.2) + +# We spend 2 * 15 circuit evaluations per parameter per step, as there are +# 15 Hamiltonian terms +execs_per_step = 2 * 15 * np.prod(param_shape) +# Run the optimization +cost_history_grad, exec_history_grad = run_optimizer( + opt, cost_function, init_param, num_steps_grad, 3, execs_per_step +) + +final_energy = cost_history_grad[-1] +print(f"\nFinal estimated value of the ground state energy = {final_energy:.8f} Ha") +print( + f"Distance to the true ground state energy: {np.abs(final_energy - true_energy):.8f} Ha" +) + + +############################################################################## +# What does the optimization with gradient descent look like? Let's plot +# the energy during optimization and compare it to the exact ground state +# energy of the molecule: + +plt.figure(figsize=(10, 6)) + +plt.plot(exec_history_grad, cost_history_grad, label="Gradient descent") + +plt.xticks(fontsize=13) +plt.yticks(fontsize=13) +plt.xlabel("Circuit executions", fontsize=14) +plt.ylabel("Energy (Ha)", fontsize=14) +plt.grid() + +plt.axhline(y=true_energy, color="black", linestyle="--", label="True energy") + +plt.legend(fontsize=14) + +plt.title("H2 energy from VQE with gradient descent", fontsize=16) + +plt.show() + +############################################################################## +# On noisy hardware, the energy never quite reaches its true value, no matter +# how many iterations are used. This is due to the noise as well as the stochastic +# nature of quantum measurements and the way they are realized on hardware. +# The simulator of the noisy quantum device allows us to observe these features. +# +# VQE with SPSA +# ^^^^^^^^^^^^^ +# +# Now let's perform the same experiment using SPSA for the VQE optimization. +# SPSA should use only 2 circuit executions per term in the expectation value. +# Since there are 15 terms and we choose 160 iterations with two evaluations for +# each gradient estimate, we expect 4800 total device +# executions. + +num_steps_spsa = 160 +opt = qml.SPSAOptimizer(maxiter=num_steps_spsa, c=0.3, a=1.5) + +# We spend 2 * 15 circuit evaluations per step, as there are 15 Hamiltonian terms +execs_per_step = 2 * 15 +# Run the optimization +cost_history_spsa, exec_history_spsa = run_optimizer( + opt, cost_function, init_param, num_steps_spsa, 20, execs_per_step +) +final_energy = cost_history_spsa[-1] + +print(f"\nFinal estimated value of the ground state energy = {final_energy:.8f} Ha") +print( + f"Distance to the true ground state energy: {np.abs(final_energy - true_energy):.8f} Ha" +) + +############################################################################## +# The SPSA optimization seems to have found a similar energy value. +# We again take a look at how the optimization curves compare, in particular +# with respect to the circuit executions spent on the task. + +plt.figure(figsize=(10, 6)) + +plt.plot(exec_history_grad, cost_history_grad, label="Gradient descent") +plt.plot(exec_history_spsa, cost_history_spsa, label="SPSA") +plt.axhline(y=true_energy, color="black", linestyle="--", label="True energy") + +plt.title("$H_2$ energy from VQE using gradient descent vs. SPSA", fontsize=16) +plt.xlabel("Circuit executions", fontsize=14) +plt.ylabel("Energy (Ha)", fontsize=14) +plt.grid() + +plt.legend(fontsize=14) +plt.show() + +############################################################################## +# We observe here that the SPSA optimizer again converges in fewer device +# executions than the gradient descent optimizer. 🎉 +# +# Due to the (simulated) hardware noise, however, the obtained energies are +# higher than the true ground state energy. +# In addition, the output still bounces around, which is due to shot noise +# and the inherently stochastic nature of SPSA. +# +# Conclusion +# ---------- +# +# SPSA is a useful optimization technique that may be particularly beneficial on +# near-term quantum hardware. It uses significantly fewer circuit executions to achieve +# comparable results as gradient-based methods, giving it the potential +# to save time and resources. It can be a good alternative to +# gradient-based methods when the optimization problem involves executing +# quantum circuits with many free parameters. +# +# There are also extensions to SPSA that could be interesting to explore in +# this context. One, in particular, uses an adaptive technique to approximate +# the *Hessian* matrix during optimization to effectively increase the +# convergence rate of SPSA [#spall_hessian]_. +# +# In addition, there is a proposal to use an SPSA variant of the quantum natural +# gradient [#qnspsa]_. +# This is implemented in PennyLane as well and we discuss it in the +# :doc:`demo on QNSPSA `. +# +# References +# ---------- +# +# .. [#spall_overview] +# +# James C. Spall, "`An Overview of the Simultaneous Perturbation Method for Efficient +# Optimization `__", 1998 +# +# .. [#spall_implementation] +# +# J. C. Spall, "Implementation of the simultaneous perturbation algorithm +# for stochastic optimization," in IEEE Transactions on Aerospace and +# Electronic Systems, vol. 34, no. 3, pp. 817-823, July 1998, doi: +# 10.1109/7.705889. +# +# .. [#spall_hessian] +# +# J. C. Spall, "Adaptive stochastic approximation by the simultaneous +# perturbation method," in IEEE Transactions on Automatic Control, +# vol. 45, no. 10, pp. 1839-1853, Oct 2020, doi: +# 10.1109/TAC.2000.880982. +# +# .. [#qnspsa] +# +# J. Gacon, C. Zoufal, G. Carleo, and S. Woerner "Simultaneous Perturbation +# Stochastic Approximation of the Quantum Fisher Information", +# `Quantum, 5, 567 `__, Oct 2021 + +############################################################################## diff --git a/demonstrations_v2/tutorial_spsa/metadata.json b/demonstrations_v2/tutorial_spsa/metadata.json new file mode 100644 index 0000000000..252077a34a --- /dev/null +++ b/demonstrations_v2/tutorial_spsa/metadata.json @@ -0,0 +1,90 @@ +{ + "title": "Optimization using SPSA", + "authors": [ + { + "username": "aszava" + }, + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2023-03-19T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimization_SPSA.png" + } + ], + "seoDescription": "Use the simultaneous perturbation stochastic approximation algorithm to optimize variational circuits in PennyLane.", + "doi": "", + "references": [ + { + "id": "spall_overview", + "type": "article", + "title": "An Overview of the Simultaneous Perturbation Method for Efficient Optimization", + "authors": "James C. Spall", + "year": "1998", + "journal": "", + "url": "https://www.jhuapl.edu/SPSA/PDF-SPSA/Spall_An_Overview.PDF" + }, + { + "id": "spall_implementation", + "type": "article", + "title": "Implementation of the simultaneous perturbation algorithm for stochastic optimization", + "authors": "J. C. Spall", + "year": "1998", + "journal": "IEEE Transactions on Aerospace and Electronic Systems", + "volume": "34", + "number": "3", + "pages": "817-823", + "doi": "10.1109/7.705889", + "url": "" + }, + { + "id": "spall_hessian", + "type": "article", + "title": "Adaptive stochastic approximation by the simultaneous perturbation method", + "authors": "J. C. Spall", + "year": "2020", + "journal": "IEEE Transactions on Automatic Control", + "volume": "45", + "number": "10", + "pages": "1839-1853", + "doi": "10.1109/TAC.2000.880982", + "url": "" + }, + { + "id": "qnspsa", + "type": "article", + "title": "Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information", + "authors": "J. Gacon, C. Zoufal, G. Carleo, and S. Woerner", + "year": "2021", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-10-20-567/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qnspsa", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_spsa/requirements.in b/demonstrations_v2/tutorial_spsa/requirements.in new file mode 100644 index 0000000000..f3b5d9cab2 --- /dev/null +++ b/demonstrations_v2/tutorial_spsa/requirements.in @@ -0,0 +1,4 @@ +matplotlib +pennylane +qiskit_aer +qiskit_ibm_runtime diff --git a/demonstrations_v2/tutorial_state_preparation/demo.py b/demonstrations_v2/tutorial_state_preparation/demo.py new file mode 100644 index 0000000000..3e07e43190 --- /dev/null +++ b/demonstrations_v2/tutorial_state_preparation/demo.py @@ -0,0 +1,198 @@ +r""" +.. _state_preparation: + +Training a quantum circuit with PyTorch +======================================= + +.. meta:: + :property="og:description": Build and optimize a circuit to prepare + arbitrary single-qubit states, including mixed states, with PyTorch + and PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/NOON.png + +.. related:: + + tutorial_qubit_rotation Basic tutorial: qubit rotation + pytorch_noise PyTorch and noisy devices + tutorial_isingmodel_PyTorch 3-qubit Ising model in PyTorch + +*Author: Juan Miguel Arrazola — Posted: 11 October 2019. Last updated: 25 January 2021.* + +In this notebook, we build and optimize a circuit to prepare arbitrary +single-qubit states, including mixed states. Along the way, we also show +how to: + +1. Construct compact expressions for circuits composed of many layers. +2. Succinctly evaluate expectation values of many observables. +3. Estimate expectation values from repeated measurements, as in real + hardware. + +""" + +############################################################################## +# The most general state of a qubit is represented in terms of a positive +# semi-definite density matrix :math:`\rho` with unit trace. The density +# matrix can be uniquely described in terms of its three-dimensional +# *Bloch vector* :math:`\vec{a}=(a_x, a_y, a_z)` as: +# +# .. math:: \rho=\frac{1}{2}(\mathbb{1}+a_x\sigma_x+a_y\sigma_y+a_z\sigma_z), +# +# where :math:`\sigma_x, \sigma_y, \sigma_z` are the Pauli matrices. Any +# Bloch vector corresponds to a valid density matrix as long as +# :math:`\|\vec{a}\|\leq 1.` +# +# The *purity* of a state is defined as :math:`p=\text{Tr}(\rho^2),` which +# for a qubit is bounded as :math:`1/2\leq p\leq 1.` The state is pure if +# :math:`p=1` and maximally mixed if :math:`p=1/2.` In this example, we +# select the target state by choosing a random Bloch vector and +# renormalizing it to have a specified purity. +# +# To start, we import PennyLane, NumPy, and PyTorch for the optimization: + +import pennylane as qml +import numpy as np +import torch +from torch.autograd import Variable +np.random.seed(42) + +# we generate a three-dimensional random vector by sampling +# each entry from a standard normal distribution +v = np.random.normal(0, 1, 3) + +# purity of the target state +purity = 0.66 + +# create a random Bloch vector with the specified purity +bloch_v = Variable( + torch.tensor(np.sqrt(2 * purity - 1) * v / np.sqrt(np.sum(v ** 2))), + requires_grad=False +) + +# array of Pauli matrices (will be useful later) +Paulis = Variable(torch.zeros([3, 2, 2], dtype=torch.complex128), requires_grad=False) +Paulis[0] = torch.tensor([[0, 1], [1, 0]]) +Paulis[1] = torch.tensor([[0, -1j], [1j, 0]]) +Paulis[2] = torch.tensor([[1, 0], [0, -1]]) + +############################################################################## +# Unitary operations map pure states to pure states. So how can we prepare +# mixed states using unitary circuits? The trick is to introduce +# additional qubits and perform a unitary transformation on this larger +# system. By "tracing out" the ancilla qubits, we can prepare mixed states +# in the target register. In this example, we introduce two additional +# qubits, which suffices to prepare arbitrary states. +# +# The ansatz circuit is composed of repeated layers, each of which +# consists of single-qubit rotations along the :math:`x, y,` and :math:`z` +# axes, followed by three CNOT gates entangling all qubits. Initial gate +# parameters are chosen at random from a normal distribution. Importantly, +# when declaring the layer function, we introduce an input parameter +# :math:`j,` which allows us to later call each layer individually. + +# number of qubits in the circuit +nr_qubits = 3 +# number of layers in the circuit +nr_layers = 2 + +# randomly initialize parameters from a normal distribution +params = np.random.normal(0, np.pi, (nr_qubits, nr_layers, 3)) +params = Variable(torch.tensor(params), requires_grad=True) + +# a layer of the circuit ansatz +def layer(params, j): + for i in range(nr_qubits): + qml.RX(params[i, j, 0], wires=i) + qml.RY(params[i, j, 1], wires=i) + qml.RZ(params[i, j, 2], wires=i) + + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[0, 2]) + qml.CNOT(wires=[1, 2]) + + +############################################################################## +# Here, we use the ``default.qubit`` device to perform the optimization, but this can be changed to +# any other supported device. + +dev = qml.device("default.qubit", wires=3) + +############################################################################## +# When defining the QNode, we introduce as input a Hermitian operator +# :math:`A` that specifies the expectation value being evaluated. This +# choice later allows us to easily evaluate several expectation values +# without having to define a new QNode each time. +# +# Since we will be optimizing using PyTorch, we configure the QNode +# to use the PyTorch interface: + + +@qml.qnode(dev, interface="torch") +def circuit(params, A): + + # repeatedly apply each layer in the circuit + for j in range(nr_layers): + layer(params, j) + + # returns the expectation of the input matrix A on the first qubit + return qml.expval(qml.Hermitian(A, wires=0)) + + +############################################################################## +# Our goal is to prepare a state with the same Bloch vector as the target +# state. Therefore, we define a simple cost function +# +# .. math:: C = \sum_{i=1}^3 \left|a_i-a'_i\right|, +# +# where :math:`\vec{a}=(a_1, a_2, a_3)` is the target vector and +# :math:`\vec{a}'=(a'_1, a'_2, a'_3)` is the vector of the state prepared +# by the circuit. Optimization is carried out using the Adam optimizer. +# Finally, we compare the Bloch vectors of the target and output state. + +# cost function +def cost_fn(params): + cost = 0 + for k in range(3): + cost += torch.abs(circuit(params, Paulis[k]) - bloch_v[k]) + + return cost + + +# set up the optimizer +opt = torch.optim.Adam([params], lr=0.1) + +# number of steps in the optimization routine +steps = 200 + +# the final stage of optimization isn't always the best, so we keep track of +# the best parameters along the way +best_cost = cost_fn(params) +best_params = np.zeros((nr_qubits, nr_layers, 3)) + +print("Cost after 0 steps is {:.4f}".format(cost_fn(params))) + +# optimization begins +for n in range(steps): + opt.zero_grad() + loss = cost_fn(params) + loss.backward() + opt.step() + + # keeps track of best parameters + if loss < best_cost: + best_cost = loss + best_params = params + + # Keep track of progress every 10 steps + if n % 10 == 9 or n == steps - 1: + print("Cost after {} steps is {:.4f}".format(n + 1, loss)) + +# calculate the Bloch vector of the output state +output_bloch_v = np.zeros(3) +for l in range(3): + output_bloch_v[l] = circuit(best_params, Paulis[l]) + +# print results +print("Target Bloch vector = ", bloch_v.numpy()) +print("Output Bloch vector = ", output_bloch_v) + +############################################################################## diff --git a/demonstrations_v2/tutorial_state_preparation/metadata.json b/demonstrations_v2/tutorial_state_preparation/metadata.json new file mode 100644 index 0000000000..30f43a8192 --- /dev/null +++ b/demonstrations_v2/tutorial_state_preparation/metadata.json @@ -0,0 +1,42 @@ +{ + "title": "Training a quantum circuit with PyTorch", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tarining_QC_with_PyTorch.png" + } + ], + "seoDescription": "Build and optimize a circuit to prepare arbitrary single-qubit states, including mixed states, with PyTorch and PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_isingmodel_PyTorch", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_state_preparation/requirements.in b/demonstrations_v2/tutorial_state_preparation/requirements.in new file mode 100644 index 0000000000..d3389b90bd --- /dev/null +++ b/demonstrations_v2/tutorial_state_preparation/requirements.in @@ -0,0 +1,3 @@ +numpy +pennylane +torch diff --git a/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py b/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py new file mode 100644 index 0000000000..23007029a1 --- /dev/null +++ b/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py @@ -0,0 +1,416 @@ +r""" +The stochastic parameter-shift rule +=================================== + +.. meta:: + :property="og:description": Differentiate any qubit gate with the stochastic parameter-shift rule. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/stochastic_parameter_shift_thumbnail.png + +.. related:: + + glossary/parameter_shift Parameter-shift rules + tutorial_backprop Quantum gradients with backpropagation + tutorial_general_parshift Generalized parameter-shift rules + +*Author: Nathan Killoran — Posted: 25 May 2020. Last updated: 15 January 2021.* + +We demonstrate how the stochastic parameter-shift rule, discovered by Banchi and Crooks [#banchi2020]_, +can be used to differentiate arbitrary qubit gates, generalizing the original +:doc:`parameter-shift rule `, which applies only for gates of a particular +(but widely encountered) form. + +Background +---------- + +One of the main ideas encountered in near-term quantum machine learning is the +:doc:`variational circuit `. +Evolving from earlier concepts pioneered by domain-specific algorithms like the +:doc:`variational quantum eigensolver ` and the +:doc:`quantum approximate optimization algorithm `, +this class of quantum algorithms makes heavy use of two distinguishing ingredients: + +i) The circuit's gates have free parameters +ii) Expectation values of measurements are built up from samples + +These two ingredients allow one circuit to actually represent an entire *family of circuits*. +An objective function—encapsulating some problem-specific goal—is built from the expectation values, +and the circuit's free parameters are progressively tuned to optimize this function. +At each step, the circuit has the same gate layout, but slightly different parameters, making +this approach promising to run on constrained near-term devices. + +But how should we actually update the circuit's parameters to move us closer to a good output? +Borrowing a page from classical optimization and deep learning, we can use +`gradient descent `_. +In this general-purpose method, we compute the derivative of a (smooth) function :math:`f` with +respect to its parameters :math:`\theta,` i.e., its gradient :math:`\nabla_\theta f.` +Since the gradient always points in the direction of steepest ascent/descent, if we make small updates +to the parameters according to + +.. math:: + + \theta \rightarrow \theta - \eta \nabla_\theta f, + +we can iteratively progress to lower and lower values of the function. + +The Parameter-Shift Rule +------------------------ + +In the quantum case, the expectation value of a circuit with respect to an measurement operator +:math:`\hat{C}` depends smoothly on the the circuit's gate parameters :math:`\theta.` We can write this +expectation value as :math:`\langle \hat{C}(\theta)\rangle.` This means that the derivatives +:math:`\nabla_\theta \langle \hat{C} \rangle` exist and gradient descent can be used. + +Before digging deeper, we will first set establish some basic notation. For simplicity, though a circuit +may contain many gates, we can concentrate on just a single gate :math:`\hat{U}` that we want to differentiate +(other gates will follow the same pattern). + +.. figure:: ../_static/demonstration_assets/stochastic_parameter_shift/quantum_circuit.png + :align: center + :width: 90% + +All gates appearing before :math:`\hat{U}` can be absorbed into an initial state preparation +:math:`\vert \psi_0 \rangle,` and all gates appearing after :math:`\hat{U}` can be absorbed with the measurement +operator :math:`\hat{C}` to make a new effective measurement operator :math:`\hat{A}.` +The expectation value :math:`\hat{A}` in the simpler one-gate circuit is identical to +the expectation value :math:`\hat{C}` in the larger circuit. + +We can also write any unitary gate in the form + +.. math:: + + \hat{U}(\theta) = e^{i\theta\hat{V}}, + +where :math:`\hat{V}` is the Hermitian *generator* of the gate :math:`\hat{U}.` + +Now, how do we actually obtain the numerical values of the gradient necessary for gradient descent? + +This is where the parameter-shift rule [#li2016]_ [#mitarai2018]_ [#schuld2018]_ enters the story. +In short, the parameter-shift rule says that for +many gates of interest—including all single-qubit gates—we can obtain the value of the derivative +:math:`\nabla_\theta \langle \hat{A}(\theta) \rangle` by subtracting two related +circuit evaluations: + +.. math:: + + \nabla_\theta \langle \hat{A} \rangle = + u\left[ + \langle \hat{A}(\theta + \tfrac{\pi}{4u}) \rangle - + \langle \hat{A}(\theta - \tfrac{\pi}{4u}) \rangle + \right] + +.. figure:: ../_static/demonstration_assets/stochastic_parameter_shift/parameter_shift_rule.png + :align: center + :width: 80% + +.. note:: + + The multiplier :math:`u` in this formula is arbitrary and can differ between implementations. + For example, PennyLane internally uses the convention where :math:`u=\tfrac{1}{2}.` + +The parameter-shift rule is *exact*, i.e., the formula for the gradient doesn't involve any approximations. +For quantum hardware, we can only take a finite number of samples, so we can never determine a circuit's +expectation values *exactly*. However, the parameter-shift rule provides the guarantee that it is an +`unbiased estimator `_, meaning that if we could take a infinite number of samples, it converges to the +correct gradient value. + +Let's jump into some code and take a look at the parameter-shift rule in action. +""" + +import pennylane as qml +import matplotlib.pyplot as plt +from pennylane import numpy as np +from scipy.linalg import expm + +np.random.seed(143) +angles = np.linspace(0, 2 * np.pi, 50) +dev = qml.device('default.qubit', wires=2) + + +############################################################################## +# We will consider a very simple circuit, containing just a single-qubit +# rotation about the x-axis, followed by a measurement along the z-axis. + +@qml.qnode(dev) +def rotation_circuit(theta): + qml.RX(theta, wires=0) + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# We will examine the gradient with respect to the parameter :math:`\theta.` +# The parameter-shift recipe requires taking the difference of two circuit +# evaluations, with forward and backward shifts in angles. +# PennyLane also provides a convenience function :func:`~pennylane.grad` +# to automatically compute the gradient. We can use it here for comparison. +# +# .. note:: +# +# Check out the :mod:`qml.gradients ` module +# to explore all quantum gradient transforms provided by PennyLane. + +def param_shift(theta): + # using the convention u=1/2 + r_plus = rotation_circuit(theta + np.pi / 2) + r_minus = rotation_circuit(theta - np.pi / 2) + return 0.5 * (r_plus - r_minus) + +gradient = qml.grad(rotation_circuit, argnum=0) + +expvals = [rotation_circuit(theta) for theta in angles] +grad_vals = [gradient(theta) for theta in angles] +param_shift_vals = [param_shift(theta) for theta in angles] +plt.plot(angles, expvals, 'b', label="Expecation value") +plt.plot(angles, grad_vals, 'r', label="qml.grad function") +plt.plot(angles, param_shift_vals, 'mx', label="Parameter-shift rule") +plt.xlabel("theta") +plt.legend() +plt.show() + + +############################################################################## +# We have evaluated the expectation value at all possible values for the angle +# :math:`\theta.` By inspection, we can see that the functional dependence is +# :math:`\cos(\theta).` +# +# The parameter-shift evaluations are plotted with 'x' markers. +# Again, by simple inspection, we can see that these have the functional form +# :math:`-\sin(\theta)`, the expected derivative of :math:`\cos(\theta),` +# and that they match the values provided by the :func:`~pennylane.grad` +# function. +# +# The parameter-shift works really nicely for many gates—like the rotation +# gate we used in our example above. But it does have constraints. There are +# some technical conditions that, if a gate satisfies them, we can guarantee +# it has a parameter-shift rule [#schuld2018]_. Concretely, the +# parameter-shift rule holds for any gate of the form +# :math:`e^{i\theta\hat{G}}` where :math:`\hat{G}^2=\mathbb{1}.` +# Furthermore, we can derive +# similar parameter-shift recipes for some other gates that *don't* meet +# this technical conditions, on a one-by-one basis. +# +# But, in general, the parameter-shift rule is not universally applicable. +# In cases where it does not hold (or is not yet known to hold), we would +# either have to decompose the gate into compatible gates, or use an +# alternate estimator for the gradient, e.g., the finite-difference +# approximation. But both of these alternatives can have drawbacks due +# to increased circuit complexity or potential errors in the gradient +# value. +# +# If only there was a parameter-shift method that could be used +# for *any* qubit gate. 🤔 + +############################################################################## +# The Stochastic Parameter-Shift Rule +# ----------------------------------- +# +# Here's where the stochastic parameter-shift rule makes its appearance +# on the stage. +# +# The stochastic parameter-shift rule introduces two new ingredients to +# the parameter-shift recipe: +# +# i) A random parameter :math:`s,` sampled uniformly from :math:`[0,1]` +# (this is the origin of the "stochastic" in the name); +# ii) Sandwiching the "shifted" gate application with one additional +# gate on each side. +# +# These additions allow the stochastic parameter-shift rule to work +# for arbitrary qubit gates. +# +# Every gate is unitary, which means they +# have the form :math:`\hat{U}(\theta) = e^{i\theta \hat{G}}` +# for some generator :math:`G.` +# Additionally, every multi-qubit operator can be expressed as a +# sum of tensor products of Pauli operators, so let's assume, +# without loss of generality, the following form for :math:`\hat{G}:` +# +# .. math:: +# +# \hat{G} = \hat{H} + \theta \hat{V}, +# +# where :math:`\hat{V}` is a "Pauli word", i.e., a tensor +# product of Pauli operators (e.g., +# :math:`\hat{Z}_0\otimes\hat{Y}_1)` and :math:`\hat{H}` can +# be an arbitrary linear combination of Pauli-operator +# tensor products. For simplicity, we assume that the parameter +# :math:`\theta` appears only in front of :math:`\hat{V}` (other +# cases can be handled using the chain rule). + +############################################################################## +# The stochastic parameter-shift rule gives the following recipe for +# computing the gradient of the expectation value +# :math:`\langle \hat{A} (\theta) \rangle:` +# +# i) Sample a value for the variable :math:`s` uniformly form +# :math:`[0,1].` +# ii) In place of gate :math:`\hat{U}(\theta),` apply the following +# three gates: +# +# a) :math:`e^{i(1-s)(\hat{H} + \theta\hat{V})}` +# b) :math:`e^{+i\tfrac{\pi}{4}\hat{V}}` +# c) :math:`e^{is(\hat{H} + \theta\hat{V})}` +# +# Measure the observable :math:`\hat{A}` and call the resulting +# expectation value of :math:`\langle r_+\rangle.` +# +# iii) Repeat step ii, but flip the sign of the angle :math:`\tfrac{\pi}{4}` +# in part b. Call the resulting expectation value +# :math:`\langle r_-\rangle.` +# +# The gradient can be obtained from the average value of +# :math:`\langle r_+ \rangle - \langle r_-\rangle,` i.e., +# +# .. math:: +# +# \mathbb{E}_{s\in\mathcal{U}[0,1]}[\langle r_+ \rangle - \langle r_-\rangle] +# +# .. figure:: ../_static/demonstration_assets/stochastic_parameter_shift/stochastic_parameter_shift.png +# :align: center +# :width: 90% +# +# Let's see this method in action. +# +# Following [#banchi2020]_, we will use the cross-resonance gate as a +# working example. This gate is defined as +# +# .. math:: +# +# \hat{U}_{CR}(\theta_1, \theta_2, \theta_3) +# = \exp\left[ i(\theta_1\hat{X}\otimes\hat{\mathbf{1}} - +# \theta_2\hat{Z}\otimes\hat{X} + +# \theta_3\hat{\mathbf{1}}\otimes\hat{X} +# ) \right]. + +# First we define some basic Pauli matrices +I = np.eye(2) +X = np.array([[0, 1], [1, 0]]) +Z = np.array([[1, 0], [0, -1]]) + +def Generator(theta1, theta2, theta3): + G = theta1.item() * np.kron(X, I) - \ + theta2 * np.kron(Z, X) + \ + theta3 * np.kron(I, X) + return G + +# A simple example circuit that contains the cross-resonance gate +@qml.qnode(dev) +def crossres_circuit(gate_pars): + G = Generator(*gate_pars) + qml.QubitUnitary(expm(-1j * G), wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + +# Subcircuit implementing the gates necessary for the +# stochastic parameter-shift rule. +# In this example, we will differentiate the first term of +# the circuit (i.e., our variable is theta1). +def SPSRgates(gate_pars, s, sign): + G = Generator(*gate_pars) + # step a) + qml.QubitUnitary(expm(1j * (1 - s) * G), wires=[0, 1]) + # step b) + qml.QubitUnitary(expm(1j * sign * np.pi / 4 * X), wires=0) + # step c) + qml.QubitUnitary(expm(1j * s * G), wires=[0,1]) + +# Function which can obtain all expectation vals needed +# for the stochastic parameter-shift rule +@qml.qnode(dev) +def spsr_circuit(gate_pars, s=None, sign=+1): + SPSRgates(gate_pars, s, sign) + return qml.expval(qml.PauliZ(0)) + +# Fix the other parameters of the gate +theta2, theta3 = -0.15, 1.6 + +# Obtain r+ and r- +# Even 10 samples gives a good result for this example +pos_vals = np.array([[spsr_circuit([theta1, theta2, theta3], s=s, sign=+1) + for s in np.random.uniform(size=10)] + for theta1 in angles]) +neg_vals = np.array([[spsr_circuit([theta1, theta2, theta3], s=s, sign=-1) + for s in np.random.uniform(size=10)] + for theta1 in angles]) + +# Plot the results +evals = [crossres_circuit([theta1, theta2, theta3]) for theta1 in angles] +spsr_vals = (pos_vals - neg_vals).mean(axis=1) +plt.plot(angles, evals, 'b', label="Expectation Value") +plt.plot(angles, spsr_vals, 'r', label="Stochastic parameter-shift rule") +plt.xlabel("theta1") +plt.legend() +plt.show() + +############################################################################## +# By inspection, we can see that the expectation values of the cross-resonance +# gate lead to a functional form :math:`\cos(2\theta_1).` +# Also by inspection, the results from the stochastic parameter-shift rule +# have the functional form :math:`-2\sin(2\theta_1),` which is the derivative +# of :math:`\cos(2\theta_1)!` + +############################################################################## +# Finally, it is interesting to notice when the stochastic parameter-shift rule +# reduces to the regular parameter-shift rule. Consider again the case +# where the gate has just a single term: +# +# .. math:: +# +# \hat{U}(\theta) = e^{i\theta\hat{V}}. +# +# In this case, the terms encapsulated in the operator :math:`\hat{H}` are all +# zero, and the gates :math:`e^{i(1-s)\hat{G}},` +# :math:`e^{\pm i\tfrac{\pi}{4}\hat{V}},` and :math:`e^{is\hat{G}}` which +# appear in the stochastic parameter-shift rule all commute. Therefore, +# we can combine them together into a single gate: +# +# .. math:: +# +# \begin{align} +# e^{i(1-s)\hat{G}}e^{\pm i\tfrac{\pi}{4}\hat{V}}e^{is\hat{G}} +# & = e^{i(1-s)\hat{G}}e^{is\hat{G}}e^{\pm i\tfrac{\pi}{4}\hat{V}} \\ +# & = e^{i\hat{G}}e^{\pm i\tfrac{\pi}{4}\hat{V}} \\ +# & = e^{i\theta\hat{V}}e^{\pm i\tfrac{\pi}{4}\hat{V}} \\ +# & = e^{i\left(\theta\pm\tfrac{\pi}{4}\right)\hat{V}} +# \end{align} +# +# Since the random variable :math:`s` no longer appears in this equation, +# averaging over it has no effect, and the stochastic parameter-shift rule +# nicely reduces back to the ordinary parameter-shift rule! +# +# .. math:: +# +# \begin{align} +# \mathbb{E}_{s\in\mathcal{U}[0,1]}\left[ +# e^{i(1-s)\hat{G}}e^{\pm i\tfrac{\pi}{4}\hat{V}}e^{is\hat{G}} +# \right] +# = & e^{i\left(\theta\pm\tfrac{\pi}{4}\right)\hat{V}} +# \end{align} + +############################################################################## +# References +# ---------- +# +# .. [#banchi2020] +# +# Leonardo Banchi and Gavin E. Crooks. "Measuring Analytic Gradients of +# General Quantum Evolution with the Stochastic Parameter Shift Rule." +# `Quantum **5** 386 `__ (2021). +# +# .. [#li2016] +# +# Jun Li, Xiaodong Yang, Xinhua Peng, and Chang-Pu Sun. +# "Hybrid Quantum-Classical Approach to Quantum Optimal Control." +# `arXiv:1608.00677 `__ (2016). +# +# .. [#mitarai2018] +# +# Kosuke Mitarai, Makoto Negoro, Masahiro Kitagawa, and Keisuke Fujii. +# "Quantum Circuit Learning." +# `arXiv:1803.00745 `__ (2020). +# +# .. [#schuld2018] +# +# Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, and +# Nathan Killoran. "Evaluating analytic gradients on quantum hardware." +# `arXiv:1811.11184 `__ (2019). +# +# diff --git a/demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json b/demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json new file mode 100644 index 0000000000..20ee733061 --- /dev/null +++ b/demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json @@ -0,0 +1,74 @@ +{ + "title": "The stochastic parameter-shift rule", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-05-25T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_stochastic_parametershift_rule.png" + } + ], + "seoDescription": "Differentiate any qubit gate with the stochastic parameter-shift rule.", + "doi": "", + "references": [ + { + "id": "banchi2020", + "type": "article", + "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule.", + "authors": "Leonardo Banchi and Gavin E. Crooks", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2005.10299" + }, + { + "id": "li2016", + "type": "article", + "title": "Hybrid Quantum-Classical Approach to Quantum Optimal Control.", + "authors": "Jun Li, Xiaodong Yang, Xinhua Peng, and Chang-Pu Sun", + "year": "2016", + "journal": "", + "url": "https://arxiv.org/abs/1608.00677" + }, + { + "id": "mitarai2018", + "type": "article", + "title": "Quantum Circuit Learning", + "authors": "Kosuke Mitarai, Makoto Negoro, Masahiro Kitagawa, and Keisuke Fujii", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/1803.00745" + }, + { + "id": "schuld2018", + "type": "article", + "title": "Evaluating analytic gradients on quantum hardware.", + "authors": "Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, and Nathan Killoran", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1811.11184" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in b/demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in new file mode 100644 index 0000000000..a9c09d8048 --- /dev/null +++ b/demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +scipy diff --git a/demonstrations_v2/tutorial_teleportation/demo.py b/demonstrations_v2/tutorial_teleportation/demo.py new file mode 100644 index 0000000000..ac3e5f7953 --- /dev/null +++ b/demonstrations_v2/tutorial_teleportation/demo.py @@ -0,0 +1,436 @@ +r""" +Quantum Teleportation +===================== + +This tutorial walks you through a popular quantum information technique known as +*quantum teleportation*. While teleportation has been thought of as the stuff of +sci-fi legend, we are going to prove that it is actually already possible today! The +technique leverages many foundational principles of quantum computing, and it has +lots of useful applications across the entire field. These principles include (but +are not limited to): the no-cloning theorem, quantum entanglement, and the +principle of deferred measurement. Let's dive in! + +| + +.. figure:: ../_static/demonstration_assets/teleportation/socialthumbnail_large_Quantum_Teleportation.png + :align: center + :width: 50% + :target: javascript:void(0) + +| + +Goal: Transferring Quantum Information +-------------------------------------- + +Suppose there are two researchers named Alice and Bob, and Alice wants to send +her quantum state to Bob. The quantum teleportation protocol enables Alice to +do exactly this in a very elegant manner, and it can be described in four steps: + +1. State preparation: Alice initializes her qubit to the state she wishes to +teleport. + +2. Shared entanglement: A Bell state is created and distributed to Alice and +Bob (one qubit each). + +3. Change of basis: Alice converts her two qubits from the Bell basis to the +computational basis. + +4. Measurement: Alice measures her two qubits, then tells Bob how to convert +his qubit to obtain the desired state. Note that it is only quantum *information* +being teleported, and not a physical particle. + +An overview of the protocol can be seen here: + +.. figure:: ../_static/demonstration_assets/teleportation/teleport_circuit_full_text.svg + :align: center + :width: 75% + +Problem: The No-Cloning Theorem +------------------------------- + +You might be wondering why we need to teleport a state at all. Can't Alice +just make a copy of it and send the copy to Bob? It turns out that copying +arbitrary states is *prohibited*, which you can understand using something called the +**no-cloning theorem**. The proof is surprisingly straightforward. Suppose we +would like to design a circuit (unitary transformation) :math:`U` that can +perform the following action: + +.. math:: + + \begin{align*} + U(\vert \psi\rangle \otimes \vert s\rangle ) &= \vert \psi\rangle \otimes \vert \psi\rangle, \\ + U(\vert \varphi\rangle \otimes \vert s\rangle ) &= \vert \varphi \rangle \otimes \vert \varphi \rangle, + \end{align*} + +where :math:`\vert \psi\rangle` and :math:`\vert \varphi\rangle` are arbitrary, +normalized single-qubit states, and :math:`\vert s \rangle` is some arbitrary, +normalized starting state. We will now prove that no such :math:`U` exists! + +First, let's take the inner product of the left-hand sides of the two equations: + +.. math:: + + (\langle \psi \vert \otimes \langle s \vert) U^\dagger U(\vert \varphi\rangle \otimes \vert s\rangle ) = \langle \psi \vert \varphi\rangle \ \langle s \vert s\rangle + +Since :math:`\langle s \vert s\rangle` equals 1, this evaluates to +:math:`\langle \psi \vert \varphi \rangle.` Next, we compare the inner product of the +right-hand sides of the two equations: :math:`(\langle \psi \vert \varphi \rangle)^2.` +These inner products must be equal, and they are only equal if they are a value that +squares to itself. The only valid values for the inner product then are 1 and 0. But +if the inner product is 1, the states are the same; on the other hand, if the inner +product is 0, the states are orthogonal. Therefore, we can't clone arbitrary states! + +Solution: Quantum Teleportation +---------------------------------- + +We will now walk through how to share quantum information without cloning it, one +step at a time. + +""" + +############################################################################## +# +# 1. State preparation +# ```````````````````` +# +# .. figure:: ../_static/demonstration_assets/teleportation/teleport_circuit_state_preparation.svg +# :align: center +# :width: 75% +# +# Teleportation involves three qubits. Two of them are held by Alice, and the +# third by Bob. We'll denote their states using subscripts: +# +# 1. :math:`\vert\cdot\rangle_S,` Alice's first qubit that she will prepare in +# some arbitrary state +# 2. :math:`\vert\cdot\rangle_A,` Alice's auxiliary (or "ancilla") qubit that +# she will entangle with Bob's qubit for communication purposes +# 3. :math:`\vert \cdot\rangle_B,` Bob's qubit that will receive the teleported +# state +# +# Together, their starting state is: +# +# .. math:: +# +# \vert 0\rangle_S \vert 0\rangle_A \vert 0\rangle_B. +# +# The first thing Alice does is prepare her first qubit in whichever state :math:`\vert +# \psi\rangle` that she'd like to send to Bob so that their combined state +# becomes: +# +# .. math:: +# +# \vert \psi\rangle_S \vert 0\rangle_A \vert 0\rangle_B. +# +# We can use the following `quantum function `__ +# to do the state preparation step: + +import pennylane as qml +import numpy as np + + +def state_preparation(state): + qml.StatePrep(state, wires=["S"]) + + +############################################################################## +# +# 2. Shared entanglement +# `````````````````````` +# +# .. figure:: ../_static/demonstration_assets/teleportation/teleport_circuit_entanglement.svg +# :align: center +# :width: 75% +# +# The reason why teleportation works is the use of an *entangled state* as a +# shared resource between Alice and Bob. You can imagine some process that +# generates a pair of entangled qubits, and sends one qubit to each party. For +# simplicity (and simulation!), we will represent the entanglement process as +# part of our circuit. +# +# Entangling the qubits :math:`A` and :math:`B` leads to the combined state: +# +# .. math:: +# +# \frac{1}{\sqrt{2}}\left( \vert \psi\rangle_S \vert 0\rangle_A \vert 0\rangle_B + \vert \psi\rangle_S \vert 1\rangle_A \vert 1\rangle_B \right)\tag{1} +# +# The :math:`AB` subsystem is now in what is known as a *Bell state*. There are +# four maximally entangled two-qubit Bell states, and they form the Bell basis: +# +# .. math:: +# +# \begin{align*} +# \vert \psi_+\rangle &= \frac{1}{\sqrt{2}} \left( \vert 00\rangle + \vert 11\rangle \right), \\ +# \vert \psi_-\rangle &= \frac{1}{\sqrt{2}} \left( \vert 00\rangle - \vert 11\rangle \right), \\ +# \vert \phi_+\rangle &= \frac{1}{\sqrt{2}} \left( \vert 01\rangle + \vert 10\rangle \right), \\ +# \vert \phi_-\rangle &= \frac{1}{\sqrt{2}} \left( \vert 01\rangle - \vert 10\rangle \right). +# \end{align*} +# +# In our experiment, because :math:`AB` started in the :math:`\vert 00\rangle` +# state, we create the :math:`\vert \psi_+\rangle` Bell state as is shown in +# equation (1). + + +def entangle_qubits(): + qml.Hadamard(wires="A") + qml.CNOT(wires=["A", "B"]) + + +############################################################################## +# +# From now on, the qubit subscripts will be removed from states for brevity. +# +# 3. Change of basis +# `````````````````` +# +# .. figure:: ../_static/demonstration_assets/teleportation/teleport_circuit_change_of_basis.svg +# :align: center +# :width: 75% +# +# This is where things get tricky, but also very interesting. The third step of +# the protocol is to apply a CNOT and a Hadamard to the first two qubits. This is +# done prior to the measurements, and labelled "change of basis". But what basis +# is this? Notice how these two gates are the *opposite* of what we do to create a +# Bell state. If we run them in the opposite direction, we transform the basis +# back to the computational one, and simulate a measurement in the Bell basis. +# +# After the basis transform, if we observe the first two qubits to be in the state +# :math:`\vert 00\rangle,` this would correspond to the outcome :math:`\vert \psi_+\rangle` in +# the Bell basis, :math:`\vert 11\rangle` would correspond to :math:`\vert \phi_-\rangle,` +# etc. Let's perform this change of basis, one step at a time. +# +# Suppose we write our initial state :math:`\vert \psi\rangle` as +# :math:`\alpha\vert 0\rangle + \beta\vert 1\rangle,` with :math:`\alpha` and +# :math:`\beta` being complex coefficients. Expanding out the terms from (1), we obtain: +# +# .. math:: +# +# \frac{1}{\sqrt{2}} ( \alpha\vert 000\rangle + +# \beta\vert 100\rangle + \alpha \vert 011\rangle + +# \beta\vert 111\rangle ) +# +# Now let's apply a CNOT between Alice's two qubits: +# +# .. math:: +# +# \frac{1}{\sqrt{2}} ( \alpha\vert 000\rangle + +# \beta\vert 110\rangle + \alpha \vert 011\rangle + +# \beta\vert 101\rangle ) +# +# And then a Hadamard on her first qubit: +# +# .. math:: +# +# \frac{1}{2} ( \alpha \vert 000\rangle + \alpha\vert 100\rangle + \beta\vert 010\rangle - \beta\vert 110\rangle + \alpha \vert 011\rangle + \alpha \vert 111 \rangle + \beta\vert 001\rangle - \beta\vert 101 \rangle ). +# +# Now we need to do some rearranging. We group the terms based on the first two qubits: +# +# .. math:: +# +# \frac{1}{2} \vert 00\rangle(\alpha\vert 0\rangle + \beta\vert 1\rangle) + \frac{1}{2}\vert 01\rangle (\beta\vert 0\rangle + \alpha\vert 1\rangle) + \frac{1}{2}\vert 10\rangle (\alpha\vert 0\rangle - \beta\vert 1\rangle) + \frac{1}{2}\vert 11\rangle (-\beta\vert 0\rangle + \alpha\vert 1\rangle).\tag{2} + + +def basis_rotation(): + qml.CNOT(wires=["S", "A"]) + qml.Hadamard(wires="S") + + +############################################################################## +# +# 4. Measurement +# `````````````` +# +# .. figure:: ../_static/demonstration_assets/teleportation/teleport_circuit_measurement.svg +# :align: center +# :width: 75% +# +# The last step of the protocol involves Alice performing a measurement on her +# qubits, and telling Bob to perform some operations depending on what she +# measured. But why exactly do we need to do this? In the previous step, we +# already performed a basis rotation back to the computational basis, so +# shouldn't we be good to go? Not quite, but almost! +# +# Let's take another look at equation (2). If Alice measures her two qubits in the +# computational basis, she is equally likely to obtain any of the four possible +# outcomes. If she observes the first two qubits in the state :math:`\vert 00 \rangle,` +# she would immediately know that Bob's qubit was in the state +# :math:`\alpha \vert 0 \rangle + \beta \vert 1 \rangle,` which is precisely the +# state we are trying to teleport! +# +# If instead she observed the qubits in state :math:`\vert 01\rangle,` she'd still +# know what state Bob has, but it's a little off from the original state. In particular, +# we have: +# +# .. math:: +# +# \beta \vert 0 \rangle + \alpha \vert 1 \rangle = X \vert \psi \rangle. +# +# After obtaining these results, Alice could tell Bob to simply apply an X +# gate to his qubit to recover the original state. Similarly, if she obtained +# :math:`\vert 10\rangle,` she would tell him to apply a Z gate. +# +# In the `"traditional" version of quantum +# teleportation `__ [#Teleportation1993]_, +# this is, in fact, exactly what happens. Alice would call up Bob on the phone, +# tell him which state she observed, and then he would be able to apply an appropriate +# correction. In this situation, measurements are happening partway through the protocol, +# and the results would be used to control the application of future quantum gates. This +# is known as mid-circuit measurement, and such mid-circuit measurements are expressed +# in PennyLane using :func:`qml.measure `. Mid-circuit measurement +# results can be used to control operations, and this is expressed in PennyLane using +# :func:`qml.cond `. + + +def measure_and_update(): + m0 = qml.measure("S") + m1 = qml.measure("A") + qml.cond(m1, qml.PauliX)("B") + qml.cond(m0, qml.PauliZ)("B") + + +############################################################################## +# +# We've now defined all the building blocks for the quantum teleportation +# protocol. Let's put it all together! + + +def teleport(state): + state_preparation(state) + entangle_qubits() + basis_rotation() + measure_and_update() + + +state = np.array([1 / np.sqrt(2) + 0.3j, 0.4 - 0.5j]) +_ = qml.draw_mpl(teleport, style="pennylane")(state) + +############################################################################## +# +# There is a neat concept known as the `principle of deferred measurement +# `__ [#NandC2000]_, +# and it basically states that we can push all our measurements to the *end* +# of our circuit. This can be useful for a variety of reasons, such as when +# working in a system that does not support mid-circuit measurements. In +# PennyLane, when you bind a circuit to a device that does not support them, +# it will automatically apply the principle of deferred measurement and update +# your circuit to use controlled operations instead. Note that you need to +# specify ``level="device"`` when calling ``draw_mpl`` so it +# runs the device pre-processing before drawing the circuit. + +dev = qml.device("default.qubit", wires=["S", "A", "B"]) + + +@qml.qnode(dev) +def teleport(state): + state_preparation(state) + entangle_qubits() + basis_rotation() + measure_and_update() + return qml.density_matrix(wires=["B"]) + + +_ = qml.draw_mpl(teleport, style="pennylane", level="device")(state) + +############################################################################## +# +# Poof! Our classical signals have been turned into CNOT and CZ gates. This is +# exactly what the principle of deferred measurement gives us — we can apply a +# CNOT instead of Alice calling Bob and telling him to apply an X gate, and +# likewise for the CZ gate. This is incredibly useful, as it allows us to +# perform our correction *before* any measurements are made. Let's evaluate the +# action of the CNOT and CZ on Bob's qubit, and ensure that Alice's state has been +# successfully teleported. Applying the CNOT yields: +# +# .. math:: +# +# \frac{1}{2} \vert 00\rangle(\alpha\vert 0\rangle + \beta\vert 1\rangle) + \frac{1}{2}\vert 01\rangle (\alpha\vert 0\rangle + \beta\vert 1\rangle) + \frac{1}{2}\vert 10\rangle (\alpha\vert 0\rangle - \beta\vert 1\rangle) + \frac{1}{2}\vert 11\rangle (\alpha\vert 0\rangle - \beta\vert 1\rangle) +# +# Then, applying the CZ yields: +# +# .. math:: +# +# \frac{1}{2} \vert 00\rangle(\alpha\vert 0\rangle + \beta\vert 1\rangle) + \frac{1}{2}\vert 01\rangle (\alpha\vert 0\rangle + \beta\vert 1\rangle) + \frac{1}{2}\vert 10\rangle (\alpha\vert 0\rangle + \beta\vert 1\rangle) + \frac{1}{2}\vert 11\rangle (\alpha\vert 0\rangle + \beta\vert 1\rangle)\tag{3} +# +# When Alice measures her two qubits at the end, no matter which outcome she +# gets, Bob's qubit will be in the state :math:`\alpha\vert 0\rangle + \beta \vert +# 1\rangle`. This means that our protocol has changed the state of Bob's qubit +# into the one Alice wished to send him, which is truly incredible! +# +# We can use :func:`qml.density_matrix ` to trace out +# and return Bob's subsystem as a density matrix, which is a more general +# description of the state of his qubit. We will use this to verify that Alice's +# state was successfully teleported to Bob's qubit. +# Re-arranging equation (3), we can see that the final state of the system is: +# +# .. math:: +# +# \frac{1}{2} (\vert 00\rangle + \vert 01\rangle + \vert 10\rangle + \vert 11\rangle) \vert \psi\rangle\tag{4} +# +# Now, we can confirm that our implementation of the quantum teleportation protocol +# is working as expected by comparing Bob's final density matrix to Alice's original +# density matrix: + + +def teleport_state(state): + teleported_density_matrix = teleport(state) + original_density_matrix = qml.math.dm_from_state_vector(state) + + if not np.allclose(teleported_density_matrix, original_density_matrix): + raise ValueError( + f"Alice's state ({state}) not teleported properly. " + f"Final density matrix of Bob's subsystem: {teleported_density_matrix}" + ) + print("State successfully teleported!") + + +teleport_state(state) + +############################################################################## +# +# Conclusion +# ---------- +# +# Let's recap the concepts we've learned throughout this tutorial. First, we +# established that quantum states cannot be arbitrarily copied due to the +# *no-cloning theorem*. Then, we learned about *quantum entanglement*. Once +# Alice and Bob shared an entangled pair of qubits, Alice performed a *change +# of basis* from the Bell basis to the computational basis. Finally, using the +# *principle of deferred measurement* when needed, Alice measured her two +# qubits and informed Bob on how to rotate his qubit into the desired state. +# +# .. figure:: ../_static/demonstration_assets/teleportation/teleport_circuit_full_gates.svg +# :align: center +# :width: 75% +# +# Just like that, Alice and Bob performed quantum teleportation, and with a +# fairly small circuit! Some of you may now be thinking that teleportation is +# impossible, and you would be in good company if so. Einstein himself referred +# to the non-local nature of entanglement as "spooky action at a distance", but +# we can all rest easy: due to the need for classical communication from Alice +# to Bob, quantum states (in other words, information) still cannot be +# teleported faster than the speed of light. Don't let that take away from +# the importance of quantum teleportation! This protocol is a critical tool in +# quantum information processing, and now it is a tool that you wield. +# +# References +# ------------ +# +# .. [#Teleportation1993] +# +# C. H. Bennett, G. Brassard, C. Crépeau, R. Jozsa, A. Peres, W. K. Wootters (1993) +# `"Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels" +# `__, +# Phys. Rev. Lett. 70, 1895. +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# +# .. [#Codebook] +# +# C. Albornoz, G. Alonso, M. Andrenkov, P. Angara, A. Asadi, A. Ballon, S. Bapat, I. De Vlugt, +# O. Di Matteo, P. Finlay, A. Fumagalli, A. Gardhouse, N. Girard, A. Hayes, J. Izaac, R. Janik, +# T. Kalajdzievski, N. Killoran, J. Soni, D. Wakeham. (2021) Xanadu Quantum Codebook. + +############################################################################## +# diff --git a/demonstrations_v2/tutorial_teleportation/metadata.json b/demonstrations_v2/tutorial_teleportation/metadata.json new file mode 100644 index 0000000000..e8d825c761 --- /dev/null +++ b/demonstrations_v2/tutorial_teleportation/metadata.json @@ -0,0 +1,74 @@ +{ + "title": "Quantum Teleportation", + "authors": [ + { + "username": "timmysilv" + } + ], + "dateOfPublication": "2023-10-20T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/teleportation/thumbnail_teleportation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_teleportation.png" + } + ], + "seoDescription": "Transmit arbitrary quantum states using quantum teleportation", + "doi": "", + "references": [ + { + "id": "Teleportation1993", + "type": "article", + "title": "Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels", + "authors": "C. H. Bennett, G. Brassard, C. Cr\u00e9peau, R. Jozsa, A. Peres, W. K. Wootters", + "year": "1993", + "publisher": "", + "journal": "Phys. Rev. Lett.", + "doi": "10.1103/PhysRevLett.70.1895", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.70.1895" + }, + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "journal": "", + "url": "" + }, + { + "id": "Codebook", + "type": "webpage", + "title": "Xanadu Quantum Codebook", + "authors": "C. Albornoz, G. Alonso, M. Andrenkov, P. Angara, A. Asadi, A. Ballon, S. Bapat, I. De Vlugt, O. Di Matteo, P. Finlay, A. Fumagalli, A. Gardhouse, N. Girard, A. Hayes, J. Izaac, R. Janik, T. Kalajdzievski, N. Killoran, J. Soni, D. Wakeham", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://codebook.xanadu.ai/I.15" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_zx_calculus", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_teleportation/requirements.in b/demonstrations_v2/tutorial_teleportation/requirements.in new file mode 100644 index 0000000000..002324f217 --- /dev/null +++ b/demonstrations_v2/tutorial_teleportation/requirements.in @@ -0,0 +1,2 @@ +numpy +pennylane diff --git a/demonstrations_v2/tutorial_testing_symmetry/demo.py b/demonstrations_v2/tutorial_testing_symmetry/demo.py new file mode 100644 index 0000000000..1f9849788c --- /dev/null +++ b/demonstrations_v2/tutorial_testing_symmetry/demo.py @@ -0,0 +1,465 @@ +r""" +Testing for symmetry with quantum computers +=========================================== + +.. meta:: + :property="og:description": Test if a system possesses discrete symmetries + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_testing_symmetry.png + + +.. related:: + + tutorial_geometric_qml Intro to geometric quantum machine learning + +*Author: David Wakeham. Posted: 24 January 2023.* + +Symmetries are transformations that leave something looking the same. +They are not only pretty — think of geometric patterns and shapes — but +a great labour-saving device for the lazy physicist! +You can do something once, and by applying transformations, find your +work has been multiplied ten-fold. +Symmetries need not be exact, since a system can look +approximately, rather than exactly, the same after a transformation. It +therefore makes sense to have an algorithm to determine if a Hamiltonian, +encoding the physics of a quantum system, has an approximate symmetry. + +.. figure:: ../_static/demonstration_assets/testing_symmetry/symm2.png + :alt: symm + :align: center + :width: 40% + +In this demo, we’ll implement the elegant algorithm of `LaBorde and +Wilde (2022) `__ for testing the +symmetries of a Hamiltonian. We’ll be able to determine whether a system +has a finite group of symmetries :math:`G,` and if not, by how much the +symmetry is violated. + +Background +-------------- + +We will encode symmetries into a `finite group +`__. This is an +algebraic structure consisting of transformations :math:`g,` which act +on the Hilbert space :math:`\mathcal{H}` of our system in the form of +unitary operators :math:`U(g)` for :math:`g \in G.` More formally, +given any two elements :math:`g_1, g_2\in G`, there is a *product* :math:`g_1 \circ g_2 \in G,` and such that: + +* **multiplication is associative**, :math:`g_1 \circ (g_2 \circ g_3) = (g_1 \circ g_2) \circ g_3` for all :math:`g_1, g_2, g_3 \in G;` +* **there is a boring transformation** :math:`e` that does nothing, :math:`g \circ e = e \circ g = e` for all :math:`g \in G;` +* **transformations can be undone**, with some :math:`g^{-1} \in G` such that :math:`g \circ g^{-1} = g^{-1} \circ g = e` for all :math:`g \in G.` + +It is sensible to ask that the unitary operators preserve the structure of the group: + +.. math:: + + + U(g_1)U(g_2) = U(g_1 \circ g_2). + +For more on groups and how to represent them with matrices, see our `demo on geometric learning `__. +For the Hamiltonian to respect the symmetries encoded in the group :math:`G` it means that it commutes with the matrices, + +.. math:: + + + [U(g),\hat{H}] = 0 + +for all :math:`g \in G.` +Since the Hamiltonian generates time evolution, this means that if we +apply a group transformation now or we apply it later, the effect is the +same. Thus, we seek an algorithm which checks if these commutators are +zero. + +Averaging over symmetries +----------------------------- + +To verify that the Hamiltonian is symmetric with respect to :math:`G,` it +seems like we will need to check each element +:math:`g \in G` separately. There is a clever way to avoid this and boil +it all down to a single number. For now, we’ll content ourselves with +looking at the *average* over the group: + +.. math:: \frac{1}{|G|}\sum_{g\in G}[U(g),\hat{H}] = 0. + +To make things concrete, let’s consider the `cyclic group `__ +:math:`G = \mathbb{Z}_4,` which we can think of as rotations of the square. If we place a qubit +on each corner, this group will naturally act on four qubits. + +.. figure:: ../_static/demonstration_assets/testing_symmetry/square.png + :alt: square + :align: center + :width: 30% + +It is generated by a single rotation, which we’ll call :math:`c.` We’ll +consider three Hamiltonians: one which is exactly symmetric with respect +to :math:`G` (called :math:`\hat{H}_\text{symm}`), one which is near symmetric +(called :math:`\hat{H}_\text{nsym}`), and one which is asymmetric +(called :math:`\hat{H}_\text{asym}`). We'll define them by + +.. math:: \begin{align*} + \hat{H}_\text{symm} & = X_0 + X_1 + X_2 + X_3\\ + \hat{H}_\text{nsym} & = X_0 + 1.1 \cdot X_1 + 0.9 \cdot X_2 + X_3 \\ + \hat{H}_\text{asym} & = X_0 + 2\cdot X_1 + 3\cdot X_2. + \end{align*} + +Let’s see how this looks in PennyLane. We’ll create a register +``system`` with four wires, one for each qubit. The generator :math:`c` +acts as a permutation: + +.. math:: \vert x_0 x_1 x_2 x_3\rangle \overset{c}{\mapsto} \vert x_3 x_0 x_1 x_2\rangle + +for basis states :math:`\vert x_0 x_1 x_2 x_3\rangle` and extends by linearity. +The simplest way to do this is by using +:class:`qml.Permute `. +We can convert this into a matrix by using +:class:`qml.matrix() `. +We can obtain any other element :math:`g\in G` by simply iterating +:math:`c` the appropriate number of times. + +""" + +import pennylane as qml +from pennylane import numpy as np + +# Create wires for the system +system = range(4) + +# The generator of the group +c = qml.Permute([3, 0, 1, 2], wires=system) +c_mat = qml.matrix(c) + + +###################################################################### +# To create the Hamiltonians, we use +# :class:`qml.Hamiltonian `: +# + +# Create Hamiltonians +obs = [qml.PauliX(system[0]), qml.PauliX(system[1]), qml.PauliX(system[2]), qml.PauliX(system[3])] +coeffs1, coeffs2, coeffs3 = [1, 1, 1, 1], [1, 1.1, 0.9, 1], [1, 2, 3, 0] +Hsymm, Hnsym, Hasym = ( + qml.Hamiltonian(coeffs1, obs), + qml.Hamiltonian(coeffs2, obs), + qml.Hamiltonian(coeffs3, obs), +) + + +###################################################################### +# To arrive at the algorithm for testing this average symmetry property, +# we start with a trick called the `Choi-Jamiołkowski +# isomorphism `__ +# for thinking of time evolution as a state instead of an operator. +# This state is called the *dual* of the operator. It’s +# easy to describe how to construct this state in words: make a copy :math:`\mathcal{H}_\text{copy}` +# of the system :math:`\mathcal{H},` create a maximally entangled state, +# and time evolve the state on :math:`\mathcal{H}.` In fact, this trick works to +# give a dual state :math:`\vert\Phi^U\rangle` for any operator :math:`U,` as below: +# +# .. figure:: ../_static/demonstration_assets/testing_symmetry/choi.png +# :alt: choi +# :align: center +# :width: 40% +# +# We've pictured entanglement by joining the wires corresponding to the system and the copy. +# For time evolution, we’ll call the dual state +# :math:`\vert\Phi_t\rangle,` and formally define it: +# +# .. math:: +# +# +# \vert\Phi_t\rangle = \frac{1}{\sqrt{d}}\sum_{i=1}^d e^{-it\hat{H}}\vert i\rangle \otimes \vert i_\text{copy}\rangle. +# +# You can `show mathematically `__ that the average symmetry condition is equivalent to +# +# .. math:: +# +# +# \Pi_G\vert\Phi_t\rangle = \vert\Phi_t\rangle, +# +# where :math:`\Pi_G` is an operator defined by +# +# .. math:: +# +# +# \Pi_G = \frac{1}{|G|}\sum_{g\in G} U(g) \otimes \overline{U(g)}. +# +# In fact, it turns out that :math:`\Pi_G^2 = \Pi_G,` and hence it is a +# *projector*, with an associated measurement, asking: is the state +# symmetric on average? The statement :math:`\Pi_G \vert \Psi_t\rangle = \vert \Psi_t \rangle` is a mathematical way of +# saying “yes”. So, our goal now is to write a circuit +# which (a) prepares the state :math:`\vert\Phi_t\rangle,` and (b) performs the +# measurement :math:`\Pi_G.` Part (a) is simpler — in general, we can just +# use a “cascade” of Hadamards and CNOTs, similar to the usual circuit for +# generating a Bell state on two qubits, as pictured below: +# +# .. figure:: ../_static/demonstration_assets/testing_symmetry/bells.png +# :alt: bells +# :align: center +# :width: 50% +# +# Let’s implement this for our four-qubit system in PennyLane: +# + +# Create copy of the system +copy = range(4, 8) + +# Prepare entangled state on system and copy +def prep_entangle(): + for wire in system: + qml.Hadamard(wire) + qml.CNOT(wires=[wire, wire + 4]) + + +###################################################################### +# We then need to implement time evolution on the system. In applications, +# the system’s evolution could be a “black box” we can query, or something +# given to us analytically. In general, we can approximate time evolution +# with +# :class:`qml.ApproxTimeEvolution `. +# However, since our Hamiltonians consist of terms that *commute*, we will +# be able to evolve exactly using +# :class:`qml.CommutingEvolution `. +# We will reiterate this below. +# That's it for part (a)! + +# Use Choi-Jamiołkowski isomorphism +def choi_state(hamiltonian, time): + prep_entangle() + qml.CommutingEvolution(hamiltonian, time) + +###################################################################### +# Controlled symmetries +# ------------------------- +# +# Part (b) is more interesting. The simplest approach is to use an auxiliary register :math:`\mathcal{H}_G` which +# encodes :math:`G,` with basis elements :math:`\vert g\rangle` labelled +# by group elements :math:`g \in G.` This needs :math:`\log \vert G\vert` +# qubits, which (along with any Hamiltonian simulation) will form the main +# resource cost of the algorithm. These will then can group +# transformations to a state +# :math:`\vert\psi\rangle \in \mathcal{H}\otimes \mathcal{H}_\text{copy}` +# in a controlled way via +# +# .. math:: +# +# +# \vert{\psi}\rangle \otimes \vert g\rangle \mapsto (U(g)\otimes \overline{U(g)})\vert{\psi} \rangle \otimes \vert g\rangle_G, +# +# which we’ll call :math:`CU.` We take this controlled gate as a +# primitive. To test average symmetry, we simply place +# :math:`\mathcal{H}_G` in a uniform superposition +# +# .. math:: +# +# +# \vert +\rangle_G = \frac{1}{\sqrt{|G|}}\sum_{g\in G} \vert g\rangle_G +# +# and apply the controlled operator to the state generated in part (a). +# This gives +# +# .. math:: +# +# +# \vert\Phi_t\rangle \otimes \vert +\rangle_G \mapsto \frac{1}{\sqrt{|G|}}\sum_{g\in G}(U(g)\otimes \overline{U(g)})\vert\Phi_t\rangle \otimes \vert g\rangle_G. +# +# This isn’t quite what we want yet, in particular because the system +# :math:`\mathcal{H}` is entangled not only with the copy, but also with +# the register :math:`\mathcal{H}_G.` To fix this, we observe the +# *register*, and see if it’s in the superposition +# :math:`\vert+\rangle_G.` The state, conditioned on this observation, is +# +# .. math:: +# +# +# \begin{align*} +# {}_G\langle +\vert \frac{1}{\sqrt{|G|}}\sum_{g\in G}(U(g)\otimes \overline{U(g)})\vert\Phi_t\rangle \otimes \vert g\rangle_G & = \frac{1}{|G|}\sum_{g, g'\in G}(U(g)\otimes \overline{U(g)})\vert\Phi_t\rangle \langle g'\vert g\rangle_G \\ & = \Pi_G \vert\Phi_t\rangle, +# \end{align*} +# +# and hence the probability of observing it is +# +# .. math:: +# +# +# P_+ = \vert \Pi_G \vert\Phi_t\rangle \vert^2 = \langle \Phi_t \vert \Pi_G^\dagger \Pi_G\vert\Phi_t\rangle = \langle \Phi_t \vert \Pi_G\vert\Phi_t\rangle +# +# This is exactly what we want! So, let’s code all this up for our +# example. We’ll need two qubits for our auxiliary register +# :math:`\mathcal{H}_G.` To place it in a uniform superposition, just +# apply a Hadamard gate to each qubit. To measure the +# :math:`\vert+\rangle_G` state at the end, we undo these Hadamards and +# try to measure “:math:`00`”. Finally, it’s straightforward to implement +# the controlled gate :math:`CU` using controlled +# operations (namely :class:`qml.ControlledQubitUnitary`) +# on each qubit: +# +# .. figure:: ../_static/demonstration_assets/testing_symmetry/cu.png +# :alt: cu +# :align: center +# :width: 50% +# + +# Create group register and device +aux = range(8, 10) +dev = qml.device("lightning.qubit", wires=10) + +# Create plus state +def prep_plus(): + qml.Hadamard(wires=aux[0]) + qml.Hadamard(wires=aux[1]) + +# Implement controlled symmetry operations on system +def CU_sys(): + qml.ControlledQubitUnitary(c_mat @ c_mat, control_wires=[aux[0]], wires=system) + qml.ControlledQubitUnitary(c_mat, control_wires=[aux[1]], wires=system) + + +# Implement controlled symmetry operations on copy +def CU_cpy(): + qml.ControlledQubitUnitary(c_mat @ c_mat, control_wires=[aux[0]], wires=copy) + qml.ControlledQubitUnitary(c_mat, control_wires=[aux[1]], wires=copy) + +###################################################################### +# Let’s combine everything and actually run our circuit! +# + +# Circuit for average symmetry +@qml.qnode(dev, interface="autograd") +def avg_symm(hamiltonian, time): + + # Use Choi-Jamiołkowski isomorphism + choi_state(hamiltonian, time) + + # Apply controlled symmetry operations + prep_plus() + CU_sys() + CU_cpy() + + # Ready register for measurement + prep_plus() + + return qml.probs(wires=aux) + + +print("For Hamiltonian Hsymm, the |+> state is observed with probability", avg_symm(Hsymm, 1)[0], ".") +print("For Hamiltonian Hnsym, the |+> state is observed with probability", avg_symm(Hnsym, 1)[0], ".") +print("For Hamiltonian Hasym, the |+> state is observed with probability", avg_symm(Hasym, 1)[0], ".") + + +###################################################################### +# We see that for the symmetric Hamiltonian, we’re *certain* to observe +# :math:`\vert +\rangle_G.` We’re very likely to observe +# :math:`\vert +\rangle_G` for the near-symmetric Hamiltonian, and our +# chances suck for the asymmetric Hamiltonian. +# + + +###################################################################### +# A short time limit +# ---------------------- +# +# This circuit leaves a few things to be desired. First, it only measures +# whether our Hamiltonian is symmetric *on average*. What if we want to +# know if it’s symmetric with respect to each element individually? We could run the circuit for each +# element :math:`g\in G,` but perhaps there is a better way. Second, even +# if we could do that, we don’t know what the numbers coming out of the +# circuit mean. +# We can address both questions by considering very short times, +# :math:`t \to 0.` In this case, we can Taylor expand the unitary +# time-evolution operator, +# +# .. math:: +# +# +# e^{-it \hat{H}} = \mathbb{I} - it \hat{H} - \frac{t^2 \hat{H}^2}{2} + O(t^3). +# +# We’ll assume that, if we’re simulating the evolution, the expansion is +# accurate to this order. Also, let :math:`d` be the dimension of our system. +# In this case, it’s `possible to prove `__ that the +# probability :math:`P_+` of observing the :math:`\vert +\rangle_G` state +# is +# +# .. math:: +# +# +# P_+ = \langle \Phi_t \vert \Pi_G \vert \Phi_t\rangle = 1 - \frac{t^2}{2d \vert G\vert}\sum_{g\in G}\vert\vert [U(g), \hat{H}] \vert\vert_2^2 + O(t^3), +# +# where :math:`\vert\vert\cdot\vert\vert_2` represents the usual +# (Pythagorean) :math:`2`-norm. This is a sharp expression relating the +# output of the circuit :math:`P_+` to a quantity measuring the degree +# of symmetry or lack thereof, the sum of squared commutator norms. +# We’ll call this sum the *asymmetry* :math:`\xi.` Rearranging, we have +# +# .. math:: +# +# +# \xi = \sum_{g\in G}\vert\vert [U(g), \hat{H}] \vert\vert_2^2 = \frac{2d\vert G\vert(1 - P_+)}{t^2} + O(t). +# +# Let’s see how this works in our example. Since :math:`d` is the +# dimension of the system, in our four-qubit case, :math:`d=2^4 = 16,` +# while :math:`\vert G\vert = 4.` +# + +# Define asymmetry circuit +def asymm(hamiltonian, time): + d, G = 16, 4 + P_plus = avg_symm(hamiltonian, time)[0] + xi = 2 * d * (1 - P_plus) / (time ** 2) + return xi + + +print("The asymmetry for Hsymm is", asymm(Hsymm, 1e-4), ".") +print("The asymmetry for Hnsym is", asymm(Hnsym, 1e-4), ".") +print("The asymmetry for Hasym is", asymm(Hasym, 1e-4), ".") + + +###################################################################### +# Our symmetric Hamiltonian :math:`\hat{H}_\text{symm}` has a near-zero asymmetry as +# expected. The near-symmetric +# Hamiltonian :math:`\hat{H}_\text{nsym}` has an asymmetry much larger than +# :math:`O(t);` evidently, :math:`\xi` is a more intelligible +# measure of symmetry than :math:`P_+.` Finally, the Hamiltonian +# :math:`\hat{H}_\text{asym}` has a huge error; it is not even approximately +# symmetric. +# + + +###################################################################### +# Concluding remarks +# ---------------------- +# +# Symmetries are physically important in quantum mechanics. Most systems +# of interest are large and complex, and even with an explicit description +# of a Hamiltonian, symmetries can be hard to determine by hand. Testing +# for symmetry when we have access to Hamiltonian evolution (either +# physically or by simulation) is thus a natural target for quantum +# computing. +# +# Here, we’ve described a simple algorithm to check if a system with +# Hamiltonian :math:`\hat{H}` is approximately symmetric with respect to a +# finite group :math:`G.` More precisely, for short times, applying +# controlled symmetry operations to the state dual to Hamiltonian +# evolution gives the asymmetry +# +# .. math:: +# +# +# \xi = \sum_{g\in G} \vert\vert [U(g), H]\vert\vert^2_2. +# +# This vanishes just in case the system possesses the symmetry, and +# otherwise tells us by how much it is violated. The main overhead is the +# size of the register encoding the group, which scales logarithmically +# with :math:`\vert G\vert.` +# So, it's expensive in memory for big groups, but quick to run! +# Regardless of the details, this algorithm suggests routes for further exploring +# the rich landscape of symmetry with quantum computers. + + +###################################################################### +# References +# -------------- +# +# 1. LaBorde, M. L and Wilde, M.M. `Quantum Algorithms for Testing +# Hamiltonian Symmetry `__ +# (2022). +# + +############################################################################## diff --git a/demonstrations_v2/tutorial_testing_symmetry/metadata.json b/demonstrations_v2/tutorial_testing_symmetry/metadata.json new file mode 100644 index 0000000000..d4657b48b2 --- /dev/null +++ b/demonstrations_v2/tutorial_testing_symmetry/metadata.json @@ -0,0 +1,49 @@ +{ + "title": "Testing for symmetry with quantum computers", + "authors": [ + { + "username": "hapax" + } + ], + "dateOfPublication": "2023-01-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/testing_symmetry/thumbnail_tutorial_testing_symmetry.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_testing_symmetry.png" + } + ], + "seoDescription": "Test if a system possesses discrete symmetries", + "doi": "", + "references": [ + { + "id": "LaBorde2022", + "type": "article", + "title": "Quantum Algorithms for Testing Hamiltonian Symmetry", + "authors": "LaBorde, M. L and Wilde, M.M.", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/pdf/2203.10017.pdf" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2203.10017" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_testing_symmetry/requirements.in b/demonstrations_v2/tutorial_testing_symmetry/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_testing_symmetry/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_tn_circuits/demo.py b/demonstrations_v2/tutorial_tn_circuits/demo.py new file mode 100644 index 0000000000..f1d412c8a5 --- /dev/null +++ b/demonstrations_v2/tutorial_tn_circuits/demo.py @@ -0,0 +1,418 @@ +r""" +.. _tn_circuits: + +Tensor-network quantum circuits +=============================== + +.. meta:: + :property="og:description": This demonstration explains how to simulate tensor-network quantum circuits. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tn_circuits.png + +.. related:: + + tutorial_variational_classifier Variational classifier + +*Authors: Diego Guala*:superscript:`1` *, Esther Cruz-Rico*:superscript:`2` *, +Shaoming Zhang*:superscript:`2` *, Juan Miguel Arrazola*:superscript:`1` *— Posted: 29 March 2022. Last updated: 27 June 2022.* + +This demonstration, written in collaboration between Xanadu and the BMW group, explains how to use PennyLane templates to design and implement tensor-network quantum circuits +as in Ref. [#Huggins]_. Tensor-network quantum circuits emulate the shape and connectivity of tensor networks such as matrix product states +and tree tensor networks. + +We begin with a short introduction to tensor networks and explain their relationship to quantum circuits. Next, we +illustrate how PennyLane's templates make it easy to design, customize, and simulate these circuits. Finally, we +show how to use the circuits to learn to classify the bars and stripes data set. This is a toy problem where the template +learns to recognize whether an image exhibits horizontal stripes or vertical bars. + +Tensors and Tensor Networks +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Tensors are multi-dimensional arrays of numbers. +Intuitively, they can be interpreted as a +generalization of scalars, vectors, and matrices. +Tensors can be described by their rank, indices, and the dimension of the indices. +The rank is the number of indices in a tensor — a scalar has +rank zero, a vector has rank one, and a matrix has rank two. +The dimension of an index is the number of values that index can take. +For example, a vector with three elements has one index that can take three +values. This is vector is therefore a rank one tensor and its index has dimension +three. + +To define tensor networks, it is important to first understand tensor contraction. +Two or more tensors can be contracted by summing over repeated indices. +In diagrammatic notation, the repeated indices appear as lines connecting tensors, as in the figure below. +We see two tensors of rank two connected by one repeated index, :math:`k.` The dimension of the +repeated index is called the bond dimension. + +.. image:: ../_static/demonstration_assets/tn_circuits/simple_tn_color.PNG + :align: center + :width: 50 % + +The contraction of the tensors above is equivalent to the standard +matrix multiplication formula and can be expressed as + +.. math:: + C_{ij} = \sum_{k}A_{ik}B_{kj}, + +where :math:`C_{ij}` denotes the entry for the :math:`i`-th row and :math:`j`-th column of the product :math:`C=AB.` + +A tensor network is a collection of tensors where a subset of +all indices are contracted. As mentioned above, we can use diagrammatic notation +to specify which indices and tensors will be contracted together by connecting +individual tensors with lines. +Tensor networks can represent complicated operations involving +several tensors with many indices contracted in sophisticated patterns. + +Two well-known tensor network architectures are matrix product states (MPS) and tree tensor networks (TTN). +These follow specific patterns of connections between tensors and can be extended to have +many or few indices. Examples of these architectures with only a few tensors +can be seen in the figure below. An MPS is shown on the left and a TTN on the right. + +.. image:: ../_static/demonstration_assets/tn_circuits/MPS_TTN_Color.PNG + :align: center + :width: 50 % + +These tensor networks are commonly used to efficiently represent certain many-body quantum +states [#orus]_. Every quantum circuit can be represented as a tensor network, with the bond +dimension dependent on the width and connectivity of the circuit. Moreover, one can design +quantum circuits that have the same connectivity as well-known tensor networks like MPS and TTN. +We call these **tensor-network quantum circuits**. +Note that the connectivity of a tensor network is related to how entanglement is distributed and +how correlations spread in the resulting tensor-network quantum circuit. We therefore design +circuits based on the tensor networks that best capture the information we want to extract. + +In tensor-network quantum circuits, the tensor network architecture acts as a +guideline for the shape of the quantum circuit. +More specifically, the tensors in the tensor networks above are replaced with +unitary operations to obtain quantum circuits, as illustrated in the figure below. + +.. image:: ../_static/demonstration_assets/tn_circuits/MPS_TTN_Circuit_Color.PNG + :align: center + :width: 70 % + +.. |vspace| raw:: latex + + \vspace{15mm} + +|vspace| + +Since the unitary operations :math:`U_1` to :math:`U_3` are in principle completely general, +it is not always clear how to implement them with a specific gate set. +Instead, we can replace the unitary operations with variational quantum circuits +determined by a specific template of choice. +The PennyLane tensor network templates allow us to do precisely this: implement tensor-network quantum +circuits with user-defined circuit ansatze as the unitary operations. In this sense, just as a template +is a strategy for arranging parametrized gates, tensor-network quantum circuits +are strategies for structuring circuit templates. They can therefore be interpreted as templates of templates, +i.e., as meta-templates. + + + +PennyLane Implementation +^^^^^^^^^^^^^^^^^^^^^^^^ +We now demonstrate how to use PennyLane to build and simulate tensor-network quantum circuits. + +The first step is to define the circuit that will be broadcast into the tensor network shape. +We call this a block. The block defines a variational quantum circuit that takes the position +of tensors in the network. +""" + +import numpy as onp +import pennylane as qml +from pennylane import numpy as np + + +def block(weights, wires): + qml.RX(weights[0], wires=wires[0]) + qml.RY(weights[1], wires=wires[1]) + qml.CNOT(wires=wires) + + +############################################################################## +# With the block defined, we can build the full tensor-network quantum circuit. +# The following code broadcasts the above block into the +# shape of an MPS tensor network and computes the expectation value of a Pauli Z +# operator on the bottom qubit. + +dev = qml.device("default.qubit", wires=4) + + +@qml.qnode(dev) +def circuit(template_weights): + qml.MPS( + wires=range(4), + n_block_wires=2, + block=block, + n_params_block=2, + template_weights=template_weights, + ) + return qml.expval(qml.PauliZ(wires=3)) + + +np.random.seed(1) +weights = np.random.random(size=[3, 2]) +qml.drawer.use_style("black_white") +fig, ax = qml.draw_mpl(circuit, level="device")(weights) +fig.set_size_inches((6, 3)) + +############################################################################## +# Using the :class:`~pennylane.MPS` template we can easily change the block type, +# depth, and size. For example, the block can contain a template like :class:`~pennylane.StronglyEntanglingLayers`, +# yielding a deeper block. + + +def deep_block(weights, wires): + qml.StronglyEntanglingLayers(weights, wires) + + +############################################################################## +# We can use the :class:`~pennylane.MPS` template again and simply set +# ``n_params_block = 3`` to suit the new block. + +dev = qml.device("default.qubit", wires=4) + + +@qml.qnode(dev) +def circuit(template_weights): + qml.MPS( + wires=range(4), + n_block_wires=2, + block=deep_block, + n_params_block=3, + template_weights=template_weights, + ) + return qml.expval(qml.PauliZ(wires=3)) + + +############################################################################## +# To ensure that the weights of the block and ``template_weights`` +# sent to the :class:`~pennylane.MPS` template are compatible, we use +# the :class:`~pennylane.StronglyEntanglingLayers.shape` function and +# replicate the elemnts for the number of expected blocks. Since this example +# will have three blocks, we replicate the elements three times using ``[list]*3``. +# The resulting circuit is illustrated in the figure below the code. +# Note that this circuit retains the layout of an MPS, +# but each block is now a deeper circuit with more gates. +# Both this circuit and the previous circuit +# can be represented by an MPS with a bond dimension of two. + +shape = qml.StronglyEntanglingLayers.shape(n_layers=2, n_wires=2) +template_weights = [np.random.random(size=shape)] * 3 +fig, ax = qml.draw_mpl(circuit, level="device")(template_weights) + +############################################################################## +# In addition to deep blocks, we can easily expand to wider blocks with more +# input wires. In the next example, we use the :class:`~pennylane.SimplifiedTwoDesign` +# template as the block. + + +def wide_block(weights, wires): + qml.SimplifiedTwoDesign(initial_layer_weights=weights[0], weights=weights[1], wires=wires) + + +############################################################################### +# To implement this wider block, we can use the :class:`~pennylane.MPS` template +# as before. To account for the extra wires per block, we simply set the ``n_block_wires`` +# argument to a higher number. The figure below shows the resulting circuit. Notice +# that, in the circuit diagram, gates are left-justified. Therefore parts of later blocks +# appear near the beginning of the circuit. Furthermore, this circuit has a higher bond +# dimension than the previous ones and would correspond to an MPS with a bond dimension of four. + +dev = qml.device("default.qubit", wires=8) + + +@qml.qnode(dev) +def circuit(template_weights): + qml.MPS( + wires=range(8), + n_block_wires=4, + block=wide_block, + n_params_block=2, + template_weights=template_weights, + ) + return qml.expval(qml.PauliZ(wires=7)) + + +shapes = qml.SimplifiedTwoDesign.shape(n_layers=1, n_wires=4) +weights = [onp.random.random(size=shape) for shape in shapes] +template_weights = onp.array([weights] * 3, dtype="object") +fig, ax = qml.draw_mpl(circuit, level="device")(template_weights) + +############################################################################## +# We can also broadcast a block to the tree tensor network architecture by using the +# :class:`~pennylane.TTN` template. + + +def block(weights, wires): + qml.RX(weights[0], wires=wires[0]) + qml.RX(weights[1], wires=wires[1]) + qml.CNOT(wires=wires) + + +dev = qml.device("default.qubit", wires=8) + + +@qml.qnode(dev) +def circuit(template_weights): + qml.TTN( + wires=range(8), + n_block_wires=2, + block=block, + n_params_block=2, + template_weights=template_weights, + ) + return qml.expval(qml.PauliZ(wires=7)) + + +weights = np.random.random(size=[7, 2]) +fig, ax = qml.draw_mpl(circuit, level="device")(weights) +fig.set_size_inches((4, 4)) +############################################################################## +# Classifying the bars and stripes data set +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Next, we use a tensor-network quantum circuit to tackle a toy machine learning problem. +# For this, we use the bars and stripes data set and optimize a parametrized circuit to label the images as +# either bars or stripes. +# The data set is composed of binary black and white images of size :math:`n \times n` pixels. +# In images that should receive the bars label, all pixels in any given column have the same color. +# In images with the stripes label, all pixels in any given row have the same color. +# The full data set for :math:`4\times 4` images is shown in the image below: +# +# .. figure:: ../_static/demonstration_assets/tn_circuits/BAS.png +# :align: center +# :height: 300 +# +# A quantum circuit that successfully performs this task +# accepts any image from the data set as input and outputs the correct label. +# We will therefore choose a data encoding strategy that can record the input image in +# a qubit register, a processing circuit that can analyze the data, and a final measurement +# that can serve as a label of either stripes or bars. +# +# The first step is to generate the bars and stripes data set. +# For :math:`2\times 2` images, we can manually define the full data set, giving white pixels a value of 1 +# and black pixels a value of 0: + +import matplotlib.pyplot as plt + +BAS = [[1, 1, 0, 0], [0, 0, 1, 1], [1, 0, 1, 0], [0, 1, 0, 1]] +j = 1 +plt.figure(figsize=[3, 3]) +for i in BAS: + plt.subplot(2, 2, j) + j += 1 + plt.imshow(np.reshape(i, [2, 2]), cmap="gray") + plt.xticks([]) + plt.yticks([]) + +############################################################################## +# The next step is to define the parameterized quantum circuit that will be trained +# to label the images. This involves determining the block and the tensor-network architecture. +# For the block, a circuit consisting of :class:`~pennylane.RY` rotations and +# :class:`~pennylane.CNOT` gates suffices for this simple data set. + + +def block(weights, wires): + qml.RY(weights[0], wires=wires[0]) + qml.RY(weights[1], wires=wires[1]) + qml.CNOT(wires=wires) + + +############################################################################## +# As for the tensor-network architecture, we use the tree tensor-network quantum circuit. +# We use :class:`~pennylane.BasisState` to encode the input images. +# The following code implements the :class:`~pennylane.BasisState` encoding, +# followed by a :class:`~pennylane.TTN` circuit using the above ``block``. Finally, we compute the expectation +# value of a :class:`~pennylane.PauliZ` measurement as the output. +# The circuit diagram below shows the full circuit. The :class:`~pennylane.BasisState` +# encoding appears in the initial :class:`~pennylane.PauliX` gates. + +dev = qml.device("default.qubit", wires=4) + + +@qml.qnode(dev) +def circuit(image, template_weights): + qml.BasisState(image, wires=range(4)) + qml.TTN( + wires=range(4), + n_block_wires=2, + block=block, + n_params_block=2, + template_weights=template_weights, + ) + return qml.expval(qml.PauliZ(wires=3)) + + +weights = np.random.random(size=[3, 2]) +fig, ax = qml.draw_mpl(circuit, level="device")(BAS[0], weights) +fig.set_size_inches((6, 3.5)) + +############################################################################## +# When the output of the above circuit is less than zero, we label the image "stripes", +# otherwise we label it "bars". Based on these labels, we define a cost function to train the circuit. +# The cost function in the following code adds the expectation value result +# if the label should be negative and subtracts the result if the label should +# be positive. In other words, the cost will be minimized when the stripes images +# output negative one and the bars images output positive one. + + +def costfunc(params): + cost = 0 + for i in range(len(BAS)): + if i < len(BAS) / 2: + cost += circuit(BAS[i], params) + else: + cost -= circuit(BAS[i], params) + return cost + + +############################################################################## +# Finally, we initialize the parameters and use PennyLane’s built-in optimizer +# train the circuit over 100 iterations. This optimizer will attempt to minimize +# the cost function. + +params = np.random.random(size=[3, 2], requires_grad=True) +optimizer = qml.GradientDescentOptimizer(stepsize=0.1) + +for k in range(100): + if k % 20 == 0: + print(f"Step {k}, cost: {costfunc(params)}") + params = optimizer.step(costfunc, params) + +############################################################################## +# With the circuit trained and the parameters stored in ``params``, +# we can now show the full circuits and the resulting output for each image. + +for image in BAS: + fig, ax = qml.draw_mpl(circuit, level="device")(image, params) + plt.figure(figsize=[1.8, 1.8]) + plt.imshow(np.reshape(image, [2, 2]), cmap="gray") + plt.title( + f"Exp. Val. = {circuit(image,params):.0f};" + + f" Label = {'Bars' if circuit(image,params)>0 else 'Stripes'}", + fontsize=8, + ) + plt.xticks([]) + plt.yticks([]) + +############################################################################## +# The resulting labels are all correct. For images with stripes, the circuit outputs +# an expectation value of minus one, corresponding to stripes and for images with +# bars the circuit outputs an expectation value of positive one, corresponding to bars. +# +# .. _tn_circuits_references: +# +# References +# ^^^^^^^^^^ +# +# +# .. [#huggins] +# +# W. Huggins, P. Patil, B. Mitchell, K. B. Whaley, and +# E. M. Stoudenmire, Quantum Science and Technology 4, +# 024001 (2019), ISSN 2058-9565, URL ``__ +# +# .. [#orus] +# +# R. Orús, Annals of Physics 349, 117 (2014), ISSN 0003- +# 4916, URL https://www.sciencedirect.com/science/article/pii/S0003491614001596. +# +# diff --git a/demonstrations_v2/tutorial_tn_circuits/metadata.json b/demonstrations_v2/tutorial_tn_circuits/metadata.json new file mode 100644 index 0000000000..7ddf0ce33d --- /dev/null +++ b/demonstrations_v2/tutorial_tn_circuits/metadata.json @@ -0,0 +1,63 @@ +{ + "title": "Tensor-network quantum circuits", + "authors": [ + { + "username": "Diego" + }, + { + "username": "ecrico" + }, + { + "username": "szhang" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2022-03-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tensor_network_quantum_circuits.png" + } + ], + "seoDescription": "This demonstration explains how to simulate tensor-network quantum circuits.", + "doi": "", + "references": [ + { + "id": "huggins", + "type": "article", + "title": "Towards quantum machine learning with tensor networks", + "authors": "W. Huggins, P. Patil, B. Mitchell, K. B. Whaley, and E. M. Stoudenmire", + "year": "2019", + "journal": "Quantum Science and Technology", + "url": "http://dx.doi.org/10.1088/2058-9565/aaea94" + }, + { + "id": "orus", + "type": "article", + "title": "A practical introduction to tensor networks: Matrix product states and projected entangled pair states", + "authors": "R. Or\u00fas", + "year": "2014", + "journal": "Annals of Physics", + "url": "https://www.sciencedirect.com/science/article/pii/S0003491614001596" + } + ], + "basedOnPapers": [ + "10.1088/2058-9565/aaea94" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/tensor-network-quantum-circuits-demo/7340" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_tn_circuits/requirements.in b/demonstrations_v2/tutorial_tn_circuits/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_tn_circuits/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_toric_code/demo.py b/demonstrations_v2/tutorial_toric_code/demo.py new file mode 100644 index 0000000000..f36824b2c1 --- /dev/null +++ b/demonstrations_v2/tutorial_toric_code/demo.py @@ -0,0 +1,1014 @@ +r""".. _toric_code: +Modeling the toric code on a quantum computer +============================================= + + +.. meta:: + :property="og:description": Investigation of the toric code degenerate ground state and anyon excitations + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/types_of_loops.png + +*Author: Christina Lee. Posted: 08 August 2022.* + +Introduction +------------ + +The *toric code model* [#Kitaev2003]_ is a treasure trove of interesting physics and +mathematics. The model sparked the development of the error-correcting surface codes [#surface_codes]_, +an essential category of error correction models. But why is the +model so useful for error correction? + +We delve into mathematics and condensed matter physics to answer that very question. +Viewing the model as a description of spins in an exotic magnet allows us to start +analyzing the model as a material. What kind of material is it? The toric code is an +example of a topological state of matter. + +A state of matter, or phase, cannot become a different phase without some discontinuity +in the physical properties as coefficients in the Hamiltonian change. This discontinuity +may exist in an arbitrary order derivative or non-local observable. For example, ice cannot +become water without a discontinuity in density as the temperature changes. The ground state +of a **topological** state of matter cannot smoothly deform to a non-entangled state +without a phase transition. Entanglement, and more critically *long-range* entanglement, +is a key hallmark of a topological state of matter. + +Local measurements cannot detect topological states of matter. We have to consider the +entire system to determine a topological phase. +To better consider this type of property, consider the parity of the number of +dancers on a dance floor. Does everyone have a partner, or is there an odd person out? To +measure that, we have to look at the entire system. + +Topology is the study of global properties that are preserved under continuous +deformations. For example, a coffee cup is equivalent to a donut because they +both have a single hole. More technically, they both have an +`Euler characteristic `__ of zero. +When we zoom to a local patch, both a sphere and a torus look the same. Only by considering +the object as a whole can you detect the single hole. + +.. figure:: ../_static/demonstration_assets/toric_code/torus_to_cup.png + :align: center + :width: 70% + + A donut can be smoothly deformed into a mug. + +In this demo, we will look at the degenerate ground state and the +excitations of the toric code model. The toric code was initially +proposed in “Fault-tolerant quantum computation by anyons” by Kitaev [#Kitaev2003]_. +This demo was inspired by “Realizing topologically ordered states on +a quantum processor” by K. J. Satzinger et al [#google_paper]_. For further reading, I +recommend “Quantum Spin Liquids” by Lucile Savary and Leon Balents [#savary_balents]_ and +"A Pedagogical Overview on 2D and 3D Toric Codes and the Origin of their Topological Orders" [#Resende]_. + +The Model +--------- + +What is the source of all this fascinating physics? The Hamiltonian is: + +.. math:: + \mathcal{H} = -\sum_s S_s - \sum_p P_p, + +where + +.. math:: + S_s = \prod_{i \in s} Z_i \quad P_p = \prod_{j \in p} X_j. + +In the literature, the :math:`S_s` terms are called the “star” +operators, and the :math:`P_p` terms are called the “plaquette” +operators. Each star :math:`s` and plaquette :math:`p` is a group of 4 sites on a square lattice. +You can compare this model to something like the `Heisenberg model `_ +used to describe spins interacting magnetically in a material. + +In the most common formulation of the model, sites live on +the edges of a square lattice. In this formulation, the “plaquette” operators are products of Pauli X operators on all the sites +in a square, and the "star" operators are products of Pauli Z operators on all the sites bordering a vertex. + +Instead, we can view the model as a checkerboard of alternating square +types. In this formulation, all sites :math:`i` and :math:`j` are the vertices +of a square lattice. +Each square is a group of four sites, and adjacent squares +alternate between the two types of groups. Since the groups on +this checkerboard no longer look like stars and plaquettes, we will call +them the “Z Group” and “X Group” operators in this tutorial. + +.. figure:: ../_static/demonstration_assets/toric_code/stars_plaquettes2.png + :align: center + :width: 70% + + On the left, sites are grouped into stars around vertices and plaquettes + on the faces. On the right, we view the lattice as a checkerboard of + alternating types of groups. + +We will be embedding the lattice on a torus via periodic boundary +conditions. Periodic boundary conditions basically “glue” the bottom of +the lattice to the top of the lattice and the left to the right. + +Modular arithmetic accomplishes this matching. Any site at ``(x,y)`` is +the same as a site at ``(x+width, y+height)``. + +.. figure:: ../_static/demonstration_assets/toric_code/converting_to_torus.png + :align: center + :width: 70% + + By matching up the edges with periodic boundary conditions, we turn a square grid into + a torus. + +On to some practical coding! First, let's define the sites on a :math:`4\times 6` lattice. +""" + +import pennylane as qml +import matplotlib.pyplot as plt +from matplotlib.patches import Polygon, Patch +from itertools import product +from dataclasses import dataclass + +import numpy as np + +np.set_printoptions(suppress=True) + +height = 4 +width = 6 + +all_sites = [(i, j) for i, j in product(range(width), range(height))] + +###################################################################### +# We would like for our wire labels to match the sites. To do this, we will be using an `Immutable Data Class `__ . +# PennyLane allows wire labels to be any **hashable** object, but iterable wire labels are currently not supported. +# Therefore we use a frozen dataclass to represent individual wires by a row and column position. +# + + +@dataclass(frozen=True) +class Wire: + i: int + j: int + +example_wire = Wire(0, 0) +print("Example wire: ", example_wire) +print("At coordinates: ", example_wire.i, example_wire.j) + + +###################################################################### +# Setting up Operators +# -------------------- +# +# For each type of group operator (X and Z), we will have two different +# lists: the “sites” and the “ops”. The “sites” are tuples and will include virtual +# sites off the edge of the lattice that match up with locations on the +# other side. For example, the site ``(6, 1)`` denotes the real location +# ``(0,1)``. We will use the ``zgroup_sites`` and ``xgroup_sites`` lists +# to help us view the measurements of the corresponding operators. +# +# The "ops" list will contain the tensor observables. We will later +# take the expectation value of each tensor. +# + +mod = lambda s: Wire(s[0] % width, s[1] % height) + +zgroup_sites = [] # list of sites in each group +zgroup_ops = [] # list of operators for each group + +for x, y in product(range(width // 2), range(height)): + + x0 = 2 * x + (y + 1) % 2 # x starting coordinate + + sites = [(x0, y), (x0 + 1, y), (x0 + 1, y + 1), (x0, y + 1)] + + op = qml.prod(*(qml.PauliZ(mod(s)) for s in sites)) + + zgroup_sites.append(sites) + zgroup_ops.append(op) + +print("First set of sites: ", zgroup_sites[0]) +print("First operator: ", zgroup_ops[0]) + + +###################################################################### +# We will later use the X Group operator sites to prepare the ground +# state, so the order here is important. One group needs a slightly +# different order due to interference with the periodic boundary +# condition. +# + +xgroup_sites = [] +xgroup_ops = [] +for x, y in product(range(width // 2), range(height)): + x0 = 2 * x + y % 2 # lower x coordinate + + sites = [(x0 + 1, y + 1), (x0, y + 1), (x0, y), (x0 + 1, y)] + + if x == 2 and y == 1: # change order for state prep later + sites = sites[1:] + sites[0:1] + + op = qml.prod(*(qml.PauliX(mod(s)) for s in sites)) + + xgroup_sites.append(sites) + xgroup_ops.append(op) + +###################################################################### +# How can we best visualize these groups of four sites? +# +# We use ``matplotlib`` to show each group of four sites as a Polygon patch, +# coloured according to the type of group. The ``misc_plot_formatting`` function +# performs some minor styling improvements +# repeated throughout this demo. The dotted horizontal lines and +# dashed vertical lines denote where we glue our boundaries together. +# + + +def misc_plot_formatting(fig, ax): + plt.hlines([-0.5, height - 0.5], -0.5, width - 0.5, linestyle="dotted", color="black") + plt.vlines([-0.5, width - 0.5], -0.5, height - 0.5, linestyle="dashed", color="black") + plt.xticks(range(width + 1), [str(i % width) for i in range(width + 1)]) + plt.yticks(range(height + 1), [str(i % height) for i in range(height + 1)]) + + for direction in ["top", "right", "bottom", "left"]: + ax.spines[direction].set_visible(False) + + return fig, ax + + +fig, ax = plt.subplots() +fig, ax = misc_plot_formatting(fig, ax) + +for group in xgroup_sites: + x_patch = ax.add_patch(Polygon(group, color="lavender", zorder=0)) + +for group in zgroup_sites: + z_patch = ax.add_patch(Polygon(group, color="mistyrose", zorder=0)) + +plt_sites = ax.scatter(*zip(*all_sites)) + +plt.legend([x_patch, z_patch, plt_sites], ["XGroup", "ZGroup", "Site"], loc="upper left") + +plt.show() + + +###################################################################### +# The Ground State +# ---------------- +# +# While individual X and Z operators do not commute with each other, the X Group and Z Group operators +# do: +# +# .. math:: +# +# [S_s, P_p] = 0. +# +# Since they commute, the wavefunction can be an eigenstate of each group operator independently. To minimize +# the energy of the Hamiltonian on the system as a whole, we can minimize the contribution of each group operator. +# Due to the negative coefficients in the Hamiltonian, we need to maximize the +# expectation value of each operator. +# The maximum possible expectation value for each operator is :math:`+1.` We can turn this +# into a constraint on our ground state: +# +# .. math:: +# +# S_s |G \rangle = +1 |G \rangle \qquad \qquad P_p | G \rangle = +1 |G\rangle. +# +# The wavefunction +# +# .. math:: +# +# | G \rangle = \prod_{p} \frac{\mathbb{I} + P_p}{\sqrt{2}} |00\dots 0\rangle = \prod_{p} U_p |00\dots 0 \rangle, +# +# where :math:`P_p` (plaquette) denotes an X Group operator, is such a state. +# +# .. note:: +# +# To check your understanding, confirm that this ground state obeys the constraints using pen and paper. +# +# :math:`|G \rangle` contains a product of unitaries :math:`U_p.` If we can figure out how to apply a single +# :math:`U_p` using a quantum computer's operations, we can apply that decomposition +# for every :math:`p` in the product. +# +# To better understand how to decompose :math:`U_p,` let’s write +# it concretely for a single group of four qubits: +# +# .. math:: +# U |0000 \rangle = +# \frac{\left(\mathbb{I} + X_1 X_2 X_3 X_4 \right)}{\sqrt{2}} |0000 \rangle +# = \frac{1}{\sqrt{2}} \left( |0000\rangle + |1111\rangle \right). +# +# This `generalized GHZ state `__ +# can be prepared with a Hadamard and 3 CNOT +# gates: +# +# .. figure:: ../_static/demonstration_assets/toric_code/generalized_ghz_draw.png +# :align: center +# :width: 50% +# +# This decomposition for :math:`U_p` holds only when the initial Hadamard +# qubit begins in the :math:`|0\rangle` state, so we need to be careful in +# the choice of the Hadamard's qubit. This restriction is +# why we rotated the order for a single X Group on the right border +# earlier. +# +# We will also not need to prepare the final X Group that contains the +# four edges of the lattice. +# +# Now let’s actually put these together into a circuit! +# + +dev = qml.device("lightning.qubit", wires=[Wire(*s) for s in all_sites]) + +def state_prep(): + for op in xgroup_ops[0:-1]: + qml.Hadamard(op.wires[0]) + for w in op.wires[1:]: + qml.CNOT(wires=[op.wires[0], w]) + +@qml.qnode(dev, diff_method=None) +def circuit(): + state_prep() + return [qml.expval(op) for op in xgroup_ops + zgroup_ops] + + +###################################################################### +# From this QNode, we can calculate the group operators' expectation +# values and the system's total energy. +# + +n_xgroups = len(xgroup_ops) +separate_expvals = lambda expvals: (expvals[:n_xgroups], expvals[n_xgroups:]) + +xgroup_expvals, zgroup_expvals = separate_expvals(circuit()) + +E0 = -sum(xgroup_expvals) - sum(zgroup_expvals) + +print("X Group expectation values", [np.round(val) for val in xgroup_expvals]) +print("Z Group expectation values", [np.round(val) for val in zgroup_expvals]) +print("Total energy: ", E0) + + +###################################################################### +# Excitations +# ----------- +# +# Quasiparticles allow physicists to describe complex +# systems as interacting particles in a vacuum. Examples of +# quasiparticles include electrons and holes in semiconductors, phonons, +# and magnons. +# +# Imagine trying to describe the traffic on the road. We could either explicitly +# enumerate the location of each vehicle or list the positions and severities of traffic jams. +# +# The first option provides complete information about the system but +# is much more challenging to analyze. For most purposes, we can work +# with information about how the traffic deviates from a baseline. In +# semiconductors, we don’t write out the wave function for every single +# electron. We instead use electrons and holes. Neither quasiparticle +# electrons nor holes are fundamental particles like an electron or +# positron in a vacuum. Instead, they are useful descriptions of how the +# wave function differs from its ground state. +# +# While the electrons and holes of a metal behave just like electrons and +# positrons in a vacuum, some condensed matter systems contain +# quasiparticles that cannot or do not exist as fundamental particles. +# The excitations of the toric code are one such example. To find these quasiparticles, +# we look at states that are *almost* the ground state, such as the ground state +# with a single operator applied to it. +# +# Suppose we apply a perturbation to the ground state in the form of a single +# X gate at location :math:`i` : +# +# .. math:: +# +# | \phi \rangle = X_i | G \rangle. +# +# Two Z group operators :math:`S_s` contain individual Z operators at that +# same site :math:`i.` The noise term :math:`X_i` will anti-commute with both +# of these group operators: +# +# .. math:: +# +# S_s X_i = \left( Z_i Z_a Z_b Z_c \right) X_i = - X_i S_s. +# +# Using this relation, we can determine the expectation value of the Z group +# operators with the altered state: +# +# .. math:: +# +# S_s |\phi\rangle = S_s X_i |G\rangle = - X_i S_s |G\rangle = - X_i |G\rangle = - |\phi\rangle. +# +# Thus, +# +# .. math:: +# +# \langle \phi | S_s | \phi \rangle = -1. +# +# :math:`S_s` now has an expectation value of :math:`-1.` +# +# Applying a single X operator noise term changes the expectation value of *two* Z group operators. +# +# This analysis repeats for the effect of a Z operator on the X Group +# measurement. A single Z operator noise term changes the expectation values of *two* +# X group operators. +# +# Each group with a negative expectation value is considered an excitation. In the +# literature, you will often see a Z Group excitation +# :math:`\langle S_s \rangle = -1` called an “electric” :math:`e` excitation and +# an X Group excitation :math:`\langle P_p \rangle = -1` called a +# “magnetic” :math:`m` excitation. You may also see the inclusion of an identity +# :math:`\mathbb{I}` particle for the ground state and the combination +# particle :math:`\Psi` consisting of a single :math:`e` and a single :math:`m` +# excitation. +# +# Let’s create a QNode where we can apply these perturbations: + + +@qml.qnode(dev, diff_method=None) +def excitations(x_sites, z_sites): + state_prep() + + for s in x_sites: + qml.PauliX(Wire(*s)) + + for s in z_sites: + qml.PauliZ(Wire(*s)) + + return [qml.expval(op) for op in xgroup_ops + zgroup_ops] + + +###################################################################### +# What are the expectation values when we apply a single X operation? +# Two Z group measurements have indeed changed signs. +# + +single_x = [(1, 2)] + +x_expvals, z_expvals = separate_expvals(excitations(single_x, [])) + +print("XGroup: ", [np.round(val) for val in x_expvals]) +print("ZGroup: ", [np.round(val) for val in z_expvals]) + + +###################################################################### +# Instead of interpreting the state via the expectation values of the operators, +# we can view the state as occupation numbers of the corresponding +# quasiparticles. A group with an expectation value of :math:`+1` is in the +# ground state and thus has an occupation number of :math:`0.` If the +# expectation value is :math:`-1,` then a quasiparticle exists in that location. +# + +occupation_numbers = lambda expvals: [0.5 * (1 - np.round(val)) for val in expvals] + +def print_info(x_expvals, z_expvals): + E = -sum(x_expvals) - sum(z_expvals) + + print("Total energy: ", E) + print("Energy above the ground state: ", E - E0) + print("X Group occupation numbers: ", occupation_numbers(x_expvals)) + print("Z Group occupation numbers: ", occupation_numbers(z_expvals)) + +print_info(x_expvals, z_expvals) + + +###################################################################### +# Since we will plot the same thing many times, we group the following +# code into a function to easily call later. +# + + +def excitation_plot(x_excite, z_excite): + x_color = lambda expval: "navy" if expval < 0 else "lavender" + z_color = lambda expval: "maroon" if expval < 0 else "mistyrose" + + fig, ax = plt.subplots() + fig, ax = misc_plot_formatting(fig, ax) + + for expval, sites in zip(x_excite, xgroup_sites): + ax.add_patch(Polygon(sites, color=x_color(expval), zorder=0)) + + for expval, sites in zip(z_excite, zgroup_sites): + ax.add_patch(Polygon(sites, color=z_color(expval), zorder=0)) + + handles = [ + Patch(color="navy", label="X Group -1"), + Patch(color="lavender", label="X Group +1"), + Patch(color="maroon", label="Z Group -1"), + Patch(color="mistyrose", label="Z Group +1"), + Patch(color="navy", label="Z op"), + Patch(color="maroon", label="X op"), + ] + + plt.legend(handles=handles, ncol=3, loc="lower left") + + return fig, ax + +###################################################################### +# With this function, we can quickly view the expectation values and the location of +# the additional X operation. The operation changes the expectation values for the +# adjacent Z groups. +# + + +fig, ax = excitation_plot(x_expvals, z_expvals) +ax.scatter(*zip(*single_x), color="maroon", s=100) +plt.show() + + +###################################################################### +# What if we apply a Z operation instead at the same site? We instead +# get two X Group excitations. +# + +single_z = [(1, 2)] + +expvals = excitations([], single_z) +x_expvals, z_expvals = separate_expvals(expvals) +print_info(x_expvals, z_expvals) + +###################################################################### +# + +fig, ax = excitation_plot(x_expvals, z_expvals) +ax.scatter(*zip(*single_z), color="navy", s=100) +plt.show() + +###################################################################### +# What happens if we apply the same perturbation twice at one +# location? We regain the ground state. +# +# The excitations of the toric code are +# Majorana particles: particles that are their own antiparticles. While +# postulated to exist in standard particle physics, Majorana particles +# have only been experimentally seen as quasiparticle excitations in +# materials. +# +# We can think of the second operation as creating another set of +# excitations at the same location. This second pair then +# annihilates the existing particles. +# + +single_z = [(1, 2)] + +expvals = excitations([], single_z + single_z) +x_expvals, z_expvals = separate_expvals(expvals) + +print_info(x_expvals, z_expvals) + + +###################################################################### +# Moving Excitations and String Operators +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# What if we create a second set of particles such that one of the new particles +# overlaps with an existing particle? One old and one new particle annihilate +# each other. One old and one new particle remain. We still have two particles +# in total. +# +# Alternatively, we can view the second operation as moving a single excitation. +# +# Let’s see what that looks like in code: +# + +two_z = [(1, 2), (2, 2)] + +expvals = excitations([], two_z) +x_expvals, z_expvals = separate_expvals(expvals) + +print_info(x_expvals, z_expvals) + +###################################################################### +# + +fig, ax = excitation_plot(x_expvals, z_expvals) +ax.plot(*zip(*two_z), color="navy", linewidth=10) +plt.show() + + +###################################################################### +# In that example, we just moved an excitation a little. How about we try +# moving it even further? +# + +long_string = [(1, 2), (2, 2), (3, 2), (4, 1)] + +expvals = excitations([], long_string) +x_expvals, z_expvals = separate_expvals(expvals) + +print_info(x_expvals, z_expvals) + +###################################################################### +# + +fig, ax = excitation_plot(x_expvals, z_expvals) +ax.plot(*zip(*long_string), color="navy", linewidth=10) +plt.show() + +###################################################################### +# We end up with strings of operations that connect pairs of quasiparticles. +# +# We can use a branch of topology called `Homotopy `__ +# to describe the relationship between these strings and the wave function. +# Two paths :math:`s_1` and :math:`s_2` are +# **homotopy equivalent** or **homotopic** if they can be continuously deformed into each +# other: +# +# .. math:: +# +# s_1 \sim s_2 +# +# In the following picture, assume the red “X” is a defect +# in space, like a tear in a sheet. The two blue +# paths are equivalent because you can smoothly move one +# into the other. You cannot move the blue path into the green path +# without going through the defect, so they are not equivalent to each +# other. +# +# .. figure:: ../_static/demonstration_assets/toric_code/homotopy.png +# :align: center +# :width: 40% +# +# Paths are homotopy equivalent if they can be smoothly deformed into each other. +# +# We can divide the set of all possible paths into **homotopy classes**. A homotopy class +# is an `equivalence class `__ under +# homotopy. Every member of the same homotopy class can be deformed into every other member of the +# same class, and +# members of different homotopy classes cannot be deformed into each other. All the +# homotopy classes for a given space :math:`S` form its +# `first homotopy group `__, denoted by :math:`\pi_1(S).` +# The first homotopy group of a space is also called its *fundamental group*. +# +# How do these mathematical concepts apply to the toric code model? +# To find out, let's look at a string of Z operations homotopic to the one above. +# +# This next string creates the same final state. +# Only the homotopy class of the path used to create the excitations influences +# the occupation numbers and total energy. +# If the endpoints are the same and the path doesn’t +# wrap around the torus or other particles, the details do not +# impact any observables. +# + +equivalent_string = [(1, 2), (2, 1), (3, 1), (4, 1)] + +expvals = excitations([], equivalent_string) +x_expvals, z_expvals = separate_expvals(expvals) + +print_info(x_expvals, z_expvals) + +###################################################################### +# + +fig, ax = excitation_plot(x_expvals, z_expvals) +ax.plot(*zip(*equivalent_string), color="navy", linewidth=10) +plt.show() + +###################################################################### +# Contractible loops +# ^^^^^^^^^^^^^^^^^^ +# +# We can also have a loop of operations that doesn’t create any new +# excitations. The loop forms a pair, moves one around in a circle, and +# then annihilates the two particles again. +# + +contractible_loop = [(1, 1), (2, 1), (3, 1), (4, 1), (4, 2), (3, 3), (2, 3), (1, 2)] + +expvals = excitations(contractible_loop, []) +x_expvals, z_expvals = separate_expvals(expvals) +print_info(x_expvals, z_expvals) + +###################################################################### +# + +fig, ax = excitation_plot(x_expvals, z_expvals) + +ax.plot(*zip(*contractible_loop), color="maroon", linewidth=10) + +plt.show() + +###################################################################### +# The loop doesn’t affect the positions of any excitations, but does it +# affect the state at all? +# +# We will look at the probabilities instead of the expectation values +# to answer that question. +# + + +@qml.qnode(dev, diff_method=None) +def probs(x_sites, z_sites): + state_prep() + + for s in x_sites: + qml.PauliX(Wire(*s)) + + for s in z_sites: + qml.PauliZ(Wire(*s)) + + return qml.probs(wires=[Wire(*s) for s in all_sites]) + + +null_probs = probs([], []) +contractible_probs = probs(contractible_loop, []) + +print("Are the probabilities equal? ", np.allclose(null_probs, contractible_probs)) + + +###################################################################### +# The toric code's dependence on the homotopy of the path explains +# this result. All paths we can smoothly deform into each other will give +# the same result. The contractible loop can be smoothly deformed to nothing, +# so the state with the contractible loop is the same as the state with no loop, our initial :math:`|G\rangle.` +# + + +###################################################################### +# Looping the torus +# ^^^^^^^^^^^^^^^^^ +# On the torus, we have four types of unique paths: +# +# - The trivial path that contracts to nothing +# - A horizontal loop around the boundaries +# - A vertical loop around the boundaries +# - A loop around both the horizontal and vertical boundaries +# +# .. figure:: ../_static/demonstration_assets/toric_code/types_of_loops.png +# :align: center +# :width: 50% +# +# The homotopy group of a torus can be generated by a horizontal loop +# and a vertical loop around the boundaries. +# +# Each of these paths represents a member of the first homotopy group +# of the torus: :math:`\pi_1(T) = \mathbb{Z}^2` modulo 2. +# +# None of these loops of X operations create net excitations, so the wave function +# remains in the ground state. +# + +horizontal_loop = [(i, 1) for i in range(width)] +vertical_loop = [(1, i) for i in range(height)] + +expvals = excitations(horizontal_loop + vertical_loop, []) +fig, ax = excitation_plot(*separate_expvals(expvals)) + +ax.plot(*zip(*horizontal_loop), color="maroon", linewidth=10) +ax.plot(*zip(*vertical_loop), color="maroon", linewidth=10) + +plt.show() + + +###################################################################### +# We can compute the probabilities for each of these four types of loops: +# + +null_probs = probs([], []) +horizontal_probs = probs(horizontal_loop, []) +vertical_probs = probs(vertical_loop, []) +combo_probs = probs(horizontal_loop + vertical_loop, []) + + +###################################################################### +# While X and Z operations can change the group operator expectation values +# and create quasiparticles, only X operators can change the probability +# distribution. Applying a Z operator would only rotate the phase of the +# state and not change any amplitudes. Hence we only use loops of X +# operators in this section. I encourage you to try this analysis with +# loops of Z operators to confirm that they do not change the probability +# distribution. +# +# We can compare the original state and one with a horizontal loop to see +# if the probability distributions are different: +# + +print("Are the probabilities equal? ", qml.math.allclose(null_probs, horizontal_probs)) +print("Is this significant?") +print("Maximum difference in probabilities: ", max(abs(null_probs - horizontal_probs))) +print("Maximum probability: ", max(null_probs)) + +###################################################################### +# These numbers seem small, but remember we have 24 qubits, and thus +# :math:`2^{24}=16777216` probability components. Since the maximum difference +# in probabilities is the same size as the maximum probability, we know +# this isn’t just random fluctuations and errors. The states are different, but the energies are the same, +# so they are *degenerate*. +# +# That compared a horizontal “x” loop with the initial ground +# state. How about the other two types of loops? Let’s iterate over all +# combinations of two probability distributions to see if any match. +# + +probs_type_labels = ["null", "x", "y", "combo"] +all_probs = [null_probs, horizontal_probs, vertical_probs, combo_probs] + +print("\t" + "\t".join(probs_type_labels)) + +for name, probs1 in zip(probs_type_labels, all_probs): + comparisons = (str(np.allclose(probs1, probs2)) for probs2 in all_probs) + print(name, "\t", "\t".join(comparisons)) + + +###################################################################### +# This table shows the model has four distinct ground states. More importantly, +# these ground states are separated from each other by long-range +# operations. We must perform a loop of operations across the entire +# lattice to switch between degenerate ground states. +# +# This four-way degeneracy is the source of the error correction in the +# toric code. Instead of 24 qubits, we work with two logical qubits (4 states) +# that are well separated from each other by topological operations. +# +# .. note:: +# +# I encourage dedicated readers to explore what happens when a path +# loops the same boundaries twice. +# +# In this section, we've seen that the space of ground states is directly +# related to the first homotopy group of the lattice. The first homotopy group +# of a torus is :math:`\pi_1(T) = \mathbb{Z}^2,` and the space of ground states is +# that group modulo two, :math:`\mathbb{Z}_2^2.` +# +# What if we defined the model on a differently shaped lattice? Then the space of the +# ground state would change to reflect the first homotopy group of that space. +# For example, if the model was defined on a sphere, then only a single unique ground state would exist. +# Adding defects like missing sites to the lattice also changes the topology. Error correction +# with the toric code often uses missing sites to add additional logical qubits. +# +# +# + + +###################################################################### +# Mutual and Exchange Statistics +# ------------------------------ +# +# The hole in the center of the donut isn’t the only thing that prevents +# paths from smoothly deforming into each other. We don’t yet know if we +# can distort paths past other particles. +# +# When one indistinguishable fermion of spin 1/2 orbits another fermion of +# the same type, the combined wave function picks up a relative phase of negative +# one. When fermions of different types orbit each other, the state is +# unchanged. For example, if an electron goes around a proton and returns +# to the same spot, the wave function is unchanged. If a boson orbits +# around a different type of boson, again, the wave function is unchanged. +# +# What if a particle went around a different type of particle and +# everything picked up a phase? Would it be a boson or a fermion? +# +# It would be something else entirely: an anyon. An anyon is anything that +# doesn’t cleanly fall into the boson/fermion categorization of particles. +# +# While the toric code is just an extremely useful mathematical +# model, anyons exist in physical materials. For example, +# fractional quantum Hall systems have anyonic particles with spin +# :math:`1/q` for different integers :math:`q.` +# +# The **statistics** are described by the phase accumulated by moving one particle +# around another. For example, if the particle picks up phases like a fermion, then it +# obeys `Fermi-Dirac statistics `_. +# **Exchange statistics** are described by the phases that accumulate from exchanging +# the *same* type of particles. **Mutual** statistics are characterized by the phase acquired +# by moving one particle around a particle of a *different* type. +# +# To measure the mutual statistics of a Z Group excitation and an X group +# excitation, we need to prepare at least one of each type of particle and then +# orbit one around the other. +# +# The following code rotates an X Group excitation around a Z Group excitation. +# + +prep1 = [(1, 1), (2, 1)] +prep2 = [(1, 3)] +loop1 = [(2, 3), (2, 2), (2, 1), (3, 1), (3, 2), (2, 3)] + +expvals = excitations(prep1, prep2 + loop1) +x_expvals, z_expvals = separate_expvals(expvals) + +fig, ax = excitation_plot(x_expvals, z_expvals) + +ax.plot(*zip(*prep1), color="maroon", linewidth=10) +ax.plot(*zip(*(prep2 + loop1)), color="navy", linewidth=10) + +plt.show() + +###################################################################### +# While we managed to loop one particle around the other, we did not +# extract the relative phase applied to the wave function. To procure +# this information, we will need the *Hadamard test*. + + +###################################################################### +# Hadamard test +# ^^^^^^^^^^^^^ +# +# The `Hadamard test `__ +# extracts the real component of a unitary operation +# :math:`\text{Re}\left(\langle \psi | U | \psi \rangle \right).` If the unitary operation just applies a phase +# :math:`U |\psi\rangle = e^{i \phi} |\psi \rangle,` the measured quantity reduces to :math:`\cos (\phi).` +# +# The steps in the Hadamard test are: +# +# 1. Prepare the auxiliary qubit into a superposition with a Hadamard +# gate +# 2. Apply a controlled version of the operation with the auxiliary +# qubit as the control +# 3. Apply another Hadamard gate to the auxiliary qubit +# 4. Measure the auxiliary qubit in the Z-basis +# +# .. figure:: ../_static/demonstration_assets/toric_code/Hadamard_test.png +# :align: center +# :width: 50% +# +# .. note:: +# +# For extra understanding, validate the Hadamard test algorithm using pen and paper. +# +# Below we implement this algorithm in PennyLane and measure the mutual exchange statistics +# of an X Group excitation and a Z Group excitation. + +dev_aux = qml.device("lightning.qubit", wires=[Wire(*s) for s in all_sites] + ["aux"]) + +def loop(x_loop, z_loop): + for s in x_loop: + qml.PauliX(Wire(*s)) + for s in z_loop: + qml.PauliZ(Wire(*s)) + +@qml.qnode(dev_aux, diff_method=None) +def hadamard_test(x_prep, z_prep, x_loop, z_loop): + state_prep() + + for s in x_prep: + qml.PauliX(Wire(*s)) + + for s in z_prep: + qml.PauliZ(Wire(*s)) + + qml.Hadamard("aux") + qml.ctrl(loop, control="aux")(x_loop, z_loop) + qml.Hadamard("aux") + return qml.expval(qml.PauliZ("aux")) + +x_around_z = hadamard_test(prep1, prep2, [], loop1) +print("Move x excitation around z excitation: ", x_around_z) + + +###################################################################### +# We just moved two different types of particles around each other and +# picked up a phase. As neither bosons nor fermions behave like this, this +# result demonstrates that the excitations of a toric code are anyons. +# +# +# .. note:: +# +# I encourage dedicated readers to calculate the phase accumulated by exchanging: +# +# * A Z Group excitation and a Z Group Excitation +# * An X Group excitation and an X Group Excitation +# * A combination :math:`\Psi` particle and an X Group excitation +# * A combination :math:`\Psi` particle and a Z Group excitation +# * A combination :math:`\Psi` particle with another :math:`\Psi` particle +# +# The combination particle should behave like a standard fermion. You can create and move combination +# particles by applying ``PauliY`` operations. +# +# In this demo, we have demonstrated: +# +# 1. How to prepare the ground state of the toric code model on a lattice +# of qubits +# 2. How to create and move excitations +# 3. The ground state degeneracy of the model on a toric lattice, arising +# from homotopically distinct loops of operations +# 4. The excitations are anyons due to non-trivial mutual statistics +# Make sure to go and modify the code as suggested if you wish to gain more intuition. +# +# +# Do check the references below if you want to learn more! +# +# +# References +# ---------- +# +# .. [#Kitaev2003] +# +# Kitaev, A. Yu. "Fault-tolerant quantum computation by anyons." +# `Annals of Physics 303.1 (2003): 2-30. `__. +# (`arXiv `__) +# +# .. [#surface_codes] +# +# Fowler, Austin G., et al. "Surface codes: Towards practical large-scale quantum computation." +# `Physical Review A 86.3 (2012): 032324. `__. +# (`arXiv `__) +# +# .. [#google_paper] +# +# Satzinger, K. J., et al. "Realizing topologically ordered states on a quantum processor." +# `Science 374.6572 (2021): 1237-1241. `__. +# (`arXiv `__) +# +# .. [#savary_balents] +# +# Savary, Lucile, and Leon Balents. "Quantum spin liquids: a review." +# `Reports on Progress in Physics 80.1 (2016): 016502. `__. +# (`arXiv `__) +# +# .. [#Resende] +# +# Araujo de Resende, M. F. "A pedagogical overview on 2D and 3D Toric Codes and the origin of their topological orders." +# `Reviews in Mathematical Physics 32.02 (2020): 2030002. `__ +# (`arXiv `__) +# diff --git a/demonstrations_v2/tutorial_toric_code/metadata.json b/demonstrations_v2/tutorial_toric_code/metadata.json new file mode 100644 index 0000000000..439fc723c9 --- /dev/null +++ b/demonstrations_v2/tutorial_toric_code/metadata.json @@ -0,0 +1,76 @@ +{ + "title": "Modeling the toric code on a quantum computer", + "authors": [ + { + "username": "christina" + } + ], + "dateOfPublication": "2022-08-08T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_modeling_toric_code_on_QC.png" + } + ], + "seoDescription": "Investigation of the toric code degenerate ground state and anyon excitations", + "doi": "", + "references": [ + { + "id": "Kitaev2003", + "type": "article", + "title": "Fault-tolerant quantum computation by anyons.", + "authors": "Kitaev, A. Yu", + "year": "2003", + "journal": "Annals of Physics", + "doi": "10.1016/S0003-4916(02)00018-0", + "url": "https://arxiv.org/pdf/quant-ph/9707021" + }, + { + "id": "surface_codes", + "type": "article", + "title": "Surface codes: Towards practical large-scale quantum computation.", + "authors": "Fowler, Austin G., et al.", + "year": "2012", + "journal": "Physical Review A", + "doi": "10.1103/PhysRevA.86.032324", + "url": "https://arxiv.org/pdf/1208.0928" + }, + { + "id": "google_paper", + "type": "article", + "title": "Realizing topologically ordered states on a quantum processor.", + "authors": "Satzinger, K. J., et al.", + "year": "2021", + "journal": "Science", + "url": "https://www.science.org/doi/abs/10.1126/science.abi8378" + }, + { + "id": "savary_balents", + "type": "article", + "title": "Quantum spin liquids: a review.", + "authors": "Savary, Lucile, and Leon Balents", + "year": "2016", + "journal": "Reports on Progress in Physics", + "url": "https://iopscience.iop.org/article/10.1088/0034-4885/80/1/016502/meta" + }, + { + "id": "Resende", + "type": "article", + "title": "A pedagogical overview on 2D and 3D Toric Codes and the origin of their topological orders.", + "authors": "Araujo de Resende, M. F.", + "year": "2020", + "journal": "Reviews in Mathematical Physics", + "doi": "10.1142/S0129055X20300022", + "url": "https://arxiv.org/pdf/1712.01258.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_toric_code/requirements.in b/demonstrations_v2/tutorial_toric_code/requirements.in new file mode 100644 index 0000000000..3090ac2cf2 --- /dev/null +++ b/demonstrations_v2/tutorial_toric_code/requirements.in @@ -0,0 +1,3 @@ +matplotlib +numpy +pennylane diff --git a/demonstrations_v2/tutorial_trapped_ions/demo.py b/demonstrations_v2/tutorial_trapped_ions/demo.py new file mode 100644 index 0000000000..309d305447 --- /dev/null +++ b/demonstrations_v2/tutorial_trapped_ions/demo.py @@ -0,0 +1,1102 @@ +r""".. _trapped_ions: + +Trapped ion quantum computers +============================= + +.. meta:: + :property="og:description": Learn all about trapped ion quantum computers, developed by companies such as IonQ and Honeywell. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/trapped_ions_tn.png + +.. related:: + tutorial_pasqal Quantum computation with neutral atoms + tutorial_sc_qubits Quantum computing with superconducting qubits + tutorial_photonics Photonic quantum computers + +*Author: Alvaro Ballon — Posted: 10 November 2021. Last updated: 26 August 2022.* + +The race for quantum advantage is on! +A host of competitors are using different technologies to build a useful quantum +computer. Some common approaches are **trapped ions, +:doc:`superconducting qubits `, and +:doc:`photonics `, among others. Discussing whether there +is a superior framework leads to a neverending debate. All of them pose +complex technological challenges, which we can only solve through +innovation, inventiveness, hard work, and a bit of luck. It is difficult +to predict whether these problems are solvable in a given timeframe. +More often than not, our predictions have been wrong. Forecasting the winner +of this race is not easy at all! + +Here, we introduce **trapped ion quantum +computers**. It is the preferred technology that research groups use at +several universities around the world, and at research companies like +`Honeywell `_ and `IonQ `_. +In particular, Honeywell has achieved a +:doc:`quantum volume ` +of 128, the largest in the market! As the name suggests, the +qubits are ions trapped by electric fields and manipulated with lasers. +Trapped ions have relatively long coherence times, which means that the qubits are +long-lived. Moreover, they can easily interact with their neighbours. +Scalability is a challenge, but, as we will see, there are +innovative ways to get around them. + +After reading this demo, you will learn how trapped ion quantum computers +prepare, evolve, and measure quantum states. In particular, you will gain +knowledge on how single and multi-qubit gates are implemented and how we can +simulate them using PennyLane. You will also identify the features that +make trapped ion quantum computers an appropriate physical implementation, and where the +technical challenges lie, in terms of **DiVincenzo's criteria** (see box below). +Finally, you will become familiar with the concepts required to understand recent articles on the topic +and read future papers to keep up-to-date with the most recent developments. + +.. container:: alert alert-block alert-info + + **Di Vincenzo's criteria**: In the year 2000, David DiVincenzo proposed a + wishlist for the experimental characteristics of a quantum computer [#DiVincenzo2000]_. + DiVincenzo's criteria have since become the main guideline for + physicists and engineers building quantum computers: + + 1. **Well-characterized and scalable qubits**. Many of the quantum systems that + we find in nature are not qubits, so we must find a way to make them behave as such. + Moreover, we need to put many of these systems together. + + 2. **Qubit initialization**. We must be able to prepare the same state repeatedly within + an acceptable margin of error. + + 3. **Long coherence times**. Qubits will lose their quantum properties after + interacting with their environment for a while. We would like them to last long + enough so that we can perform quantum operations. + + 4. **Universal set of gates**. We need to perform arbitrary operations on the + qubits. To do this, we require both single-qubit gates and two-qubit gates. + + 5. **Measurement of individual qubits**. To read the result of a quantum algorithm, + we must accurately measure the final state of a pre-chosen set of qubits. + +""" + +############################################################################## +# +# How to trap an ion +# ~~~~~~~~~~~~~~~~~~ +# +# Why do we use ions, i.e., charged atoms, as qubits? The main reason +# is that they can be contained (that is, trapped) in one precise location using electric fields. It is +# possible to contain neutral atoms using optical tweezers, but our focus +# is on ions, which can be contained using an electromagnetic trap. Ion traps +# are rather old technology: their history goes back to 1953 when Wolfgang +# Paul proposed his now-called Paul trap [#Paul1953]_. For this invention, Paul and +# Dehmelt were awarded the 1989 Physics Nobel Prize, since it is used to make highly +# precise atomic clocks. Current trapped ion quantum computers extensively +# use the Paul trap, but Paul won the prize six years before such an +# application was proposed [#CiracZoller]_! +# +# It is not easy to create electric fields that contain the ion in a tiny +# region of space. The ideal configuration of an electric field +# —also known as a *potential*— would look like this: +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/confining.png +# :align: center +# :width: 70% +# +# .. +# +# Confining potential +# +# The potential should be interpreted as a wall that the ion must climb +# over to escape from a physical region. Positively charged ions will always roll +# down from regions of high potential to low potential. So if we can +# achieve an electric potential like the above, the ion should remain +# trapped in the pit. However, using the laws of electrostatics, we can +# show that it is impossible to create a confining potential with only +# static electric fields. Instead, they produce saddle-shaped potentials: +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/saddle_potential.png +# :align: center +# :width: 70% +# +# .. +# +# Saddle-shaped potential allowed by electrostatics +# +# This potential is problematic since the ion is contained in one +# direction but could escape in the perpendicular direction. Therefore, +# the solution is to use time-dependent electric fields to allow the +# potential wall to move. What would happen, for example, if we rotated +# the potential plotted above? We can imagine that if the saddle potential +# rotates at a specific frequency, the wall will catch the ion as it tries +# to escape in the downhill direction. Explicitly, the electric potential +# that we generate is given by [#Malinowski]_ +# +# .. math:: \Phi = \frac{1}{2}\left(u_x x^2 + u_y y^2 + u_z z^2\right) + \frac{1}{2}\left(v_x x^2 + v_y y^2 + v_z z^2\right)\cos(\omega t+\phi). +# +# The parameters :math:`u_i`, :math:`v_i,` and :math:`\phi` need to be +# adjusted to the charge and mass of the ion and to the potential's +# angular frequency :math:`\omega.` We have to tune these +# parameters very carefully, since the ion could escape if we do not apply +# the right forces at the right time. It takes a lot of care, but this +# technique is so old that it is almost perfect by now. Here is what the +# rotating potential would look like: +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/Rotating.gif +# :align: center +# :width: 70% +# +# .. +# +# A rotating potential with the correct frequency and magnitude +# can contain an ion +# +# We want to make a quantum computer, so having one qubit cannot be +# enough. We would like as many as we can possibly afford! The good news +# is that we have the technology to trap many ions and put them close +# together in a one-dimensional array, called an ion chain. Why do we need +# this particular configuration? To manipulate the qubits, we need the +# system of ions to absorb photons. However, shooting a photon at an ion +# can cause relative motion between ions. The +# proximity between qubits will cause unwanted interactions, which could +# modify their state. Happily, there is a solution to this issue: we place +# the ions in a sufficiently spaced one-dimensional array and **cool them +# all down to the point where their motion in space is quantized**. In +# this scenario, photons that would bring the ion to their excited states +# will not cause any relative motion. Instead, all ions will recoil +# together [#NandC2000]_. This phenomenon is called the **Mossbauer effect**. We will +# see later that by carefully tuning the laser frequency, we can control +# both the excitations of the ions and the motion of the ion chain. This +# user-controlled motion is precisely what we need to perform quantum +# operations with two qubits. +# +# Trapped ions as robust qubits +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Now that we know how to trap ions, we would like to use them as qubits. +# Would any ion out there work well as a qubit? In fact, only a select few +# isotopes will do the trick. The reason is that our qubit basis states +# are the ground and excited states of an electron in the atom, and we +# need to be able to transition between them using laser light. Therefore, +# we would like the atom to have an excited state that is long-lived, and +# also one that we may manipulate using frequencies that lasers can +# produce. Thanks to semiconductor laser technology, we have a wide range +# of frequencies that we can use in the visible and infrared ranges, so +# getting the desired frequency is not too much of a problem. The best +# ions for our purposes are single-charged ions in Group II of the +# periodic table, such as Calcium-40, Beryllium-9, and Barium-138, +# commonly used in university laboratories [#Bergou2021]_. The rare earth Ytterbium-171 is +# used by IonQ and Honeywell. These elements have two *valence electrons*, +# but their ionized version only has one. The valence electron is not so +# tightly bound to the atom, so it is the one whose state we use to represent a +# qubit. +# +# .. container:: alert alert-block alert-info +# +# **Atomic Physics Primer:** Atoms consist of a positively charged nucleus +# and negative electrons around them. The electrons inhabit energy +# levels, which have a population limit. As the levels fill up, the +# electrons occupy higher and higher energy levels. But as long as +# there is space, electrons can change energy levels, with a preference +# for the lower ones. This can happen spontaneously or due to external +# influences. +# +# When the lower energy levels are not occupied, the higher energy levels +# are unstable: electrons will prefer to minimize their energy and jump to +# a lower level on their own. What happens when an electron jumps from +# a high energy level to a lower one? Conservation of energy tells us +# that the energy must go somewhere. Indeed, a photon with an energy +# equal to the energy lost by the electron is emitted. This energy is +# proportional to the frequency (colour) of the photon. +# +# Conversely, we can use laser light to induce the opposite process. +# When an electron is in a stable or ground state, we can use lasers +# with their frequency set roughly to the difference +# in energy levels, or energy gap, between the ground state and an +# excited state . If a photon hits an electron, it will go to that +# higher energy state. When the light stimulus is removed, the excited +# electrons will return to stable states. The time it takes them to do +# so depends on the particular excited state they are in since, +# sometimes, the laws of physics will make it harder for electrons to +# jump back on their own. +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/atomic.png +# :align: center +# :width: 60% +# +# .. +# +# Photons with an energy equal to the atomic gap drive excitations +# +# +# Having chosen the ions that will act as our qubits, we need to prepare +# them in a stable fiducial state, known as the **ground state** and +# denoted by :math:`\left\lvert g \right\rangle.` The preparation is done +# by a procedure called **optical pumping**. To understand how it works, let us +# take Calcium-40 as an example. In this case, the electron has two stable +# states with the same energy, but different direction of rotation. +# We denote these by :math:`\left\lvert g_1 \right\rangle` and +# :math:`\left\lvert g_2\right\rangle.` We do not know which stable state +# the electron is in, and we would like to ensure that the electron is in +# the :math:`\left\lvert g_1\right\rangle` state. This will be our chosen +# fiducial state, so +# :math:`\left\lvert g\right\rangle = \left\lvert g_1\right\rangle.` +# However, quantum mechanics forbids a direct transition between these two +# stable states. To get from one state to the other, the electron +# would have to change its rotation without giving out any energy, which +# is impossible! But we can take a detour: we use circularly polarized +# laser light of a particular wavelength (397nm for Calcium-40) to excite +# :math:`\left\lvert g_2\right\rangle` into a short-lived excited state +# :math:`\left\lvert \textrm{aux}\right\rangle.` This light does not +# stimulate any other transitions in the ion so that an electron in the +# ground state :math:`\left\lvert g_1\right\rangle` will remain there. +# Quantum mechanics tells us that, in a matter of nanoseconds, the excited electron +# decays to our desired ground state :math:`\left\lvert g \right\rangle` +# with probability 1/3, but returns to +# :math:`\left\lvert g_2 \right\rangle` otherwise. For this reason, we +# need to repeat the procedure many times, gradually "pumping" the +# electrons in all (or the vast majority of) our ions to the ground state. +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/pumping.png +# :align: center +# :width: 60% +# +# .. +# +# Optical pumping to prepare the ground state +# +# What about the other basis qubit state? It will be a long-lived excited +# state, denoted by :math:`\left\lvert e \right\rangle.` For the +# Calcium-40 ion, this state is a *metastable state:* a state that +# has a sufficiently long lifetime since quantum mechanics restricts, but +# does not entirely forbid, transitions to a lower energy level. For +# example, the metastable state of Calcium-40 has a half-life of about 1 +# second. While apparently short, most quantum operations can be performed +# on a timescale of micro to milliseconds. The energy difference between the ground and +# excited state corresponds to a laser frequency of 729nm, achievable with +# an infrared laser. Therefore, we call this an **optical qubit**. An +# alternative is to use an ion, such as Calcium-43, that has a *hyperfine +# structure*, which means that the ground and excited states are separated by a very small +# energy gap. In this case, the higher energy state has a virtually +# infinite lifespan, since it is only slightly different +# from the stable ground state. We can use a procedure similar to optical pumping to +# transition between these two states, so while coherence times are longer +# for these **hyperfine qubits**, gate implementation is more complicated +# and needs a lot of precision. +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/hyperfine.png +# :align: center +# :width: 60% +# +# .. +# +# Optical vs. hyperfine qubits +# +# We have now learned how trapped ions make for very stable qubits that +# allow us to implement many quantum operations without decohering too +# soon. We have also learned how to prepare these qubits in a stable +# ground state. Does this mean that we have already satisfied DiVincezo's +# first, second, and third criteria? We have definitely fulfilled the +# second one since optical pumping is a very robust method. However, we +# have mainly been focusing on a single qubit and, since we have not +# discussed scalability yet, we have not fully satisfied the first +# criterion. Introducing more ions will pose additional challenges to +# meeting the third criterion. For now, let us focus on how to satisfy +# criteria 4 and 5, and we will come back to these issues once we discuss +# what happens when we deal with multiple ions. +# +# Non-demolition measurements +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Let us now discuss the last step in a quantum computation: measuring the +# qubits. Since it takes quite a bit of work to trap an ion, it would be +# ideal if we could measure the state of our qubits without it escaping +# from the trap. We definitely do not want to trap ions again after performing +# one measurement. Moreover, we want measurements that can be repeated +# on the same ions and yield consistent results. These are called **non-demolition +# measurements**, and they are easy enough to carry out for trapped ions. +# +# The measurement method uses a similar principle to that of optical +# pumping. Once again, and continuing with the Calcium-40 example, we make +# use of the auxiliary state. This time, we shine a laser light wavelength +# of 397 nm that drives the transition from +# :math:`\left\lvert g \right\rangle` to the auxiliary state +# :math:`\left\lvert \textrm{aux} \right\rangle.` The transition is +# short-lived; it will quickly go back to :math:`\left\lvert g \right\rangle,` +# emitting a photon of the same wavelength. The state +# :math:`\left\lvert e \right\rangle` is not affected. Therefore, +# we will measure :math:`\left\lvert g \right\rangle` if +# we see the ion glowing: it continuously emits light at a wavelength of +# 397 nm. Conversely, if the ion is dark, we will have measured the result +# corresponding to state :math:`\left\lvert e\right\rangle.` To see the +# photons emitted by the ions, we need to collect the photons using a lens +# and a photomultiplier, a device that transforms weak light signals into +# electric currents. +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/measurement.png +# :align: center +# :width: 60% +# +# .. +# +# Non-demolition measurement of ion states +# +# Have we fully satisfied the fifth criterion? Via a careful experimental +# arrangement, we can detect the emission of photons of each atom +# individually, so we are on the right track. But in reality, there is +# also some uncertainty in the measurement. In many quantum computing +# algorithms, we only measure the state of a pre-chosen set of ions called +# the **ancilla**. If these ions emit light, they can accidentally excite +# other ions on the chain, causing decoherence. A way to avoid this source +# of uncertainty is to use two species of ions: one for the ancilla and +# one for the qubits that are not measured, or **logical qubits**. +# In this case, the ions emitted by the ancilla ions would not excite the +# logical qubits. However, using two different species of ions causes +# extra trouble when we want to implement arbitrary qubit operations [#Hughes2020]_. +# +# Rabi oscillations to manipulate single qubits +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# How do we make single-qubit quantum gates? Namely, is there a way to put the electron in a +# superposition of the ground and excited states? Since we aim to change +# the energy state of an electron, we have no choice but to continue using +# lasers to shoot photons at it, tuning the frequency to the +# energy gap. To understand how we would achieve a superposition by +# interacting with the ion using light, let us look at a mathematical +# operator called the *Hamiltonian*. In physics, the Hamiltonian describes +# the motion and external forces around an object we want to study. One of +# the main difficulties encountered in quantum mechanics is determining +# the correct Hamiltonian for a system. In our case, this work has already +# been done by quantum optics experts. After many simplifications +# involving some approximations, we find that the Hamiltonian that +# describes an electron in an ion resonant to the laser light is given by +# the operator +# +# .. math:: \hat{H}=\frac{\hbar\Omega}{2}\left(S_+ e^{i\varphi}+S_{-}e^{-i\varphi}\right). +# +# Here, :math:`\Omega` is the **Rabi frequency**. It is defined by +# :math:`\Omega=\mu_m B/2\hbar,` where :math:`B` is the applied magnetic +# field due to the laser, and :math:`\mu_m` is the magnetic moment of the +# ion. The phase :math:`\varphi` measures the initial displacement of the +# light wave at the atom's position. The matrices :math:`S_+` and +# :math:`S_-` are +# +# .. math:: S_+=\left( \begin{array}{cc} 0 & 0 \\ 1 & 0\end{array}\right), \qquad S_-=\left( \begin{array}{cc} 0 & 1 \\ 0 & 0\end{array}\right). +# +# Hamiltonians in physics are helpful because they tell us how systems +# change with time in the presence of external interactions. In quantum +# mechanics, Hamiltonians are represented by matrices, and the evolution of a system is +# calculated using Schrödinger's equation. When the Hamiltonian does not +# depend on time, a qubit starting in state +# :math:`\left\lvert g \right\rangle` will evolve into the following +# time-dependent state: +# +# .. math:: \left\lvert \psi(t)\right\rangle = \exp(-i \hat{H} t/\hbar)\left\lvert g \right\rangle, +# +# where :math:`\exp` denotes the matrix exponential and :math:`t` is +# the duration of the interaction, which is controlled using *pulses*, i.e., short +# bursts of light. We do not need to +# elaborate on how matrix exponentials are calculated, since we can +# implement them using the scipy library in Python. Let us see how our +# basis states :math:`\left\lvert g \right\rangle` and +# :math:`\left\lvert e \right\rangle` (:math:`\left\lvert 0 \right\rangle` and +# :math:`\left\lvert 1 \right\rangle` in PennyLane) evolve under the action of this +# Hamiltonian. First, we write a function that returns the matrix exponential +# :math:`\exp(-i \hat{H} t/\hbar)` as a function of :math:`\varphi` and the +# duration :math:`t` of the pulse, with :math:`\Omega` set to 100 kHz. + +import pennylane as qml +import numpy as np +from scipy.linalg import expm + +Omega = 100 +S_plus = np.array([[0, 0], [1, 0]]) +S_minus = np.array([[0, 1], [0, 0]]) + + +def evolution(phi, t): + Ham = Omega / 2 * (S_plus * np.exp(1j * phi) + S_minus * np.exp(-1j * phi)) + return expm(-1j * Ham * t) + + +############################################################################## +# With this operator implemented, we can determine the sequences of pulses that +# produce common gates. For example, there is a combination of pulses +# with different phases and durations that yield the Hadamard gate: + +dev = qml.device("default.qubit", wires=1) + + +@qml.qnode(dev) +def ion_hadamard(state): + + if state == 1: + qml.PauliX(wires=0) + + """We use a series of seemingly arbitrary pulses that will give the Hadamard gate. + Why this is the case will become clear later""" + + qml.QubitUnitary(evolution(0, -np.pi / 2 / Omega), wires=0) + qml.QubitUnitary(evolution(np.pi / 2, np.pi / 2 / Omega), wires=0) + qml.QubitUnitary(evolution(0, np.pi / 2 / Omega), wires=0) + qml.QubitUnitary(evolution(np.pi / 2, np.pi / 2 / Omega), wires=0) + qml.QubitUnitary(evolution(0, np.pi / 2 / Omega), wires=0) + + return qml.state() + +#For comparison, we use the Hadamard built into PennyLane +@qml.qnode(dev) +def hadamard(state): + + if state == 1: + qml.PauliX(wires=0) + + qml.Hadamard(wires=0) + + return qml.state() + +#We confirm that the values given by both functions are the same up to numerical error +print(np.isclose(1j * ion_hadamard(0), hadamard(0))) +print(np.isclose(1j * ion_hadamard(1), hadamard(1))) + +############################################################################## +# Note that the desired gate was obtained up to a global phase factor. +# A similar exercise can be done for the :math:`T` gate: + + +@qml.qnode(dev) +def ion_Tgate(state): + + if state == 1: + qml.PauliX(wires=0) + + qml.QubitUnitary(evolution(0, -np.pi / 2 / Omega), wires=0) + qml.QubitUnitary(evolution(np.pi / 2, np.pi / 4 / Omega), wires=0) + qml.QubitUnitary(evolution(0, np.pi / 2 / Omega), wires=0) + + return qml.state() + + +@qml.qnode(dev) +def tgate(state): + + if state == 1: + qml.PauliX(wires=0) + + qml.T(wires=0) + + return qml.state() + + +print(np.isclose(np.exp(1j * np.pi / 8) * ion_Tgate(0), tgate(0))) +print(np.isclose(np.exp(1j * np.pi / 8) * ion_Tgate(1), tgate(1))) + +############################################################################## +# This PennyLane code shows that we can obtain a Hadamard gate and a +# :math:`T` gate using consecutive pulses with different times and phases. Namely, +# to get a Hadamard gate, we need five pulses, all of them with duration +# :math:`t=\frac{\pi}{2\Omega},` where the second and the fourth pulse +# have a phase of :math:`\pi/2.` The Hadamard and :math:`T` gates together can be used to +# implement any operation on a single qubit, to an arbitrary degree of approximation. We +# see that timing and dephasing our laser pulses provides a versatile way +# to manipulate single qubits. +# +# To get a better idea about how the duration +# of the pulses affects the state that we generate, let us plot the probability +# of obtaining the state :math:`\left\lvert e \right\rangle` against the +# duration of the pulse for a fixed phase of :math:`\varphi = 0.` + +import matplotlib.pyplot as plt + + +@qml.qnode(dev) +def evolution_prob(t): + + qml.QubitUnitary(evolution(0, t / Omega), wires=0) + + return qml.probs(wires=0) + + +t = np.linspace(0, 4 * np.pi, 101) +s = [evolution_prob(i)[1] for i in t] + +fig1, ax1 = plt.subplots(figsize=(9, 6)) + +ax1.plot(t, s, color="#9D2EC5") + +ax1.set( + xlabel="time (in units of 1/Ω)", + ylabel="Probability", + title="Probability of measuring the excited state" +) +ax1.grid() + +plt.show() + +############################################################################## +# We see that the probability of obtaining the excited state changes with +# the duration of the pulse, reaching a maximum at a time +# :math:`t=\pi/\Omega,` and then vanishing at :math:`t=2\pi/\Omega.` This +# pattern keeps repeating itself and is known as a **Rabi oscillation**. +# +# In fact, we can solve the Schrödinger equation +# explicitly (feel free to do this if you want to practice solving +# differential equations!). If we do this, we can deduce that the +# ground state :math:`\left\lvert g \right\rangle` evolves to [#Bergou2021]_ +# +# .. math:: \left\lvert \psi_0(t) \right\rangle = \cos\left(\frac{\Omega t}{2}\right)\left\lvert g \right\rangle -i\sin\left(\frac{\Omega t}{2}\right) e^{i\varphi}\left\lvert e \right\rangle . +# +# We observe that we can obtain an arbitrary superposition of qubits by +# adjusting the duration of the interaction and the phase. This means +# that we can produce any single-qubit gate! To be more precise, let us +# see what would happen if the initial state was +# :math:`\left\lvert e \right\rangle.` As before, we can show that the +# evolution is given by +# +# .. math:: \left\lvert \psi_1(t) \right\rangle = -i\sin\left(\frac{\Omega t}{2}\right)e^{-i\varphi}\left\lvert g \right\rangle +\cos\left(\frac{\Omega t}{2}\right)\left\lvert e \right\rangle . +# +# Therefore, the unitary induced by a laser pulse of amplitude +# :math:`B`, duration :math:`t,` and phase :math:`\varphi` on an ion +# with magnetic moment :math:`\mu_m` is +# +# .. math:: U(\Omega,\varphi,t)=\left( \begin{array}{cc} \cos\left(\frac{\Omega t}{2}\right) & -i\sin\left(\frac{\Omega t}{2}\right)e^{-i\varphi} \\ -i\sin\left(\frac{\Omega t}{2}\right)e^{i\varphi} & \cos\left(\frac{\Omega t}{2}\right)\end{array}\right), +# +# which has the form of a general rotation. Since we can generate +# arbitrary X and Y rotations using :math:`\varphi=0` and +# :math:`\varphi=\pi/2,` Rabi oscillations allow us to build a +# universal set of single-qubit gates. +# +# Achieving the required superpositions of quantum states requires precise +# control of the timing and phase of the pulse. This feat is not easy, but +# it is not the most challenging step towards creating a trapped-ion +# quantum computer. For typical Rabi frequencies of :math:`\Omega=100` +# kHz, the single-qubit gates can be implemented in a few milliseconds +# with high accuracy. Thus, we can implement quantum algorithms involving +# many gates even for the seemingly short lifespans of optical qubits. As +# a consequence, we have now satisfied the single-qubit gate requirement of criterion 4. +# The rest of this criterion is not theoretically difficult to implement. +# However, it can be experimentally challenging. +# +# The ion chain as a harmonic oscillator +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To fully address the fourth criterion, we need to create +# gates on two qubits. How can we +# achieve this? It turns out that placing ions in a +# chain is ideal for multiple-qubit gate implementations. When cooled +# down, the entire ion chain acts as a **quantum harmonic oscillator**, +# meaning that it can vibrate with energies that are multiples of Planck's +# constant :math:`\hbar` times a fundamental frequency +# :math:`\omega:` +# +# .. math:: E=n\hbar\omega. +# +# When the chain is oscillating with energy :math:`E=n\hbar\omega,` we +# denote the harmonic oscillator state, also known as **phonon state** or +# **motional state**, by :math:`\left\lvert n\right\rangle.` The harmonic +# oscillator can absorb and emit energy in multiples of +# :math:`\hbar\omega,` in packets of energy known as **phonons**. +# When we shine laser light on a particular atom of +# the ion chain, the entire chain could absorb the energy of the photons +# and start oscillating. However, we have seen that this does not happen +# when the atoms are cooled down and the light frequency matches the +# energy gap. Instead, the atom changes energy level, and we can +# manipulate a single qubit. But what happens when the frequency is away +# from this value? In most cases, it does nothing, but it will excite both +# the atom and the harmonic oscillator in some special circumstances. We +# can use the harmonic oscillator states as auxiliary states that will +# allow us to build two-qubit gates. +# +# Let us introduce some notation that will help us understand exactly how +# the two-qubit gates are implemented. When an ion is in the ground state +# :math:`\left\lvert g \right\rangle` and the chain is in the state +# :math:`\left\lvert n \right\rangle,` we will write the state as +# :math:`\left\lvert g \right\rangle \left\lvert n \right\rangle,` and +# similarly when the ion is in the excited state +# :math:`\left\lvert e \right\rangle.` If we are studying two ions at the +# same time, then we will write the states in the form +# :math:`\left\lvert g \right\rangle\left\lvert g \right\rangle\left\lvert n \right\rangle,` +# where the last :math:`\left\lvert n \right\rangle` always represents the +# state of the oscillating ion chain. Suppose that the ion's energy gap +# value is :math:`\Delta,` and we shine light of frequency +# :math:`\omega_b=\omega+\Delta` on a particular ion. If it is in the +# ground state, it will absorb an energy :math:`\Delta,` and the ion chain +# will absorb the rest. Therefore, this light frequency induces the +# following **blue sideband** transition: +# +# .. math:: \left\lvert g \right\rangle \left\lvert n \right\rangle \rightarrow \left\lvert e \right\rangle \left\lvert n+1\right\rangle. +# +# By using the frequency :math:`\omega_r=\Delta-\omega,` we can instead +# excite the ion and de-excite the ion chain, also known as a +# **red sideband** transition: +# +# .. math:: \left\lvert g \right\rangle \left\lvert n \right\rangle \rightarrow \left\lvert e \right\rangle \left\lvert n-1\right\rangle. +# +# Crucially, this frequency will do nothing if the ion chain is in the +# state of zero energy. +# If the light frequency is exactly :math:`\Delta,` +# the ion chain does not absorb any phonons, but the ion does become +# excited. We will call this a **carrier** transition. +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/sidebands.png +# :align: center +# :width: 60% +# +# .. +# +# Effects of the sideband and carrier frequencies on an ion chain +# +# Since the oscillations of the ion chain are quantum states, we may +# wonder whether we can also create superpositions of motional states. For +# both the red and blue sideband frequencies, the Hamiltonian turns out to +# be similar to the one we saw above, but with a different Rabi frequency +# :math:`\tilde{\Omega}.` Following the same prescription as with single +# ions, we can tune the duration and phase of the pulses to form +# superpositions of phonon states. For example, for a blue sideband pulse +# of duration :math:`t=\pi/2\tilde{\Omega}` and phase +# :math:`\varphi=\pi/2,` a system of two ions in both the motional and +# electronic ground state evolves as +# +# .. math:: \left\lvert g\right\rangle \left\lvert g\right\rangle \left\lvert n\right\rangle \rightarrow \frac{1}{\sqrt{2}}\left(\left\lvert g\right\rangle \left\lvert g\right\rangle \left\lvert n\right\rangle + \left\lvert e\right\rangle \left\lvert g\right\rangle \left\lvert n+1\right\rangle\right) +# +# when the pulse is incident on the first ion. Similarly, other choices of +# duration and phase allow for arbitrary superpositions between phonon +# states. This freedom to act on the motional states gives us the +# necessary tools to implement two-qubit gates. We will see two examples +# and use one of them to build a CNOT gate which, as is well-known, allows +# for universal computations when combined with single-qubit gates. +# +# .. note:: +# +# The results above are relatively simple to understand, but they +# are not exact. In particular, to guarantee that the sideband frequencies +# will have the effects we described on the ion chain, the separation between +# ions must be large enough, in a range known as the Lamb-Dicke regime. If we do +# not operate under this condition, stronger interactions will come into +# play, and we will not be able to act on individual ions with the laser +# beams. +# +# Entangling ions with multi-qubit gates +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# .. container:: alert alert-block alert-info +# +# **Maximally entangled states:** When all outcomes have the same +# probability in an entangled state, such as in the two-ion state +# +# .. math:: +# +# \left\lvert \psi \right\rangle = \frac{1}{\sqrt{2}}\left(\left\lvert e \right\rangle \left\lvert g \right\rangle + +# \left\lvert g \right\rangle \left\lvert e \right\rangle\right), +# +# we say that it is **maximally entangled**. To be able to do arbitrary +# computations and for quantum advantage to be possible, we need two-qubit gates +# that, combined with single qubit gates, produce maximally entangled states. +# The CNOT gate is an example of this, and we will learn about others below. +# +# +# The **Cirac-Zoller** gate [#CiracZoller]_ can completely entangle ions. It is also the +# simplest way to illustrate how we can use the states of the harmonic +# oscillator as an aid to create two-qubit gates. For a chain +# with zero motional energy, we saw above that +# applying a blue sideband pulse of duration :math:`t=\pi/2\tilde{\Omega}` and phase +# :math:`\varphi=\pi/2` to the first ion gives us the state +# +# .. math:: \left\lvert \psi \right\rangle = \frac{1}{\sqrt{2}}\left(\left\lvert g\right\rangle \left\lvert g\right\rangle \left\lvert 0\right\rangle + \left\lvert e\right\rangle \left\lvert g\right\rangle \left\lvert 1\right\rangle\right). +# +# We can then use a similar idea to keep creating superpositions until we +# end up in a maximally entangled state. The steps to implement the +# Cirac-Zoller gate are shown on the diagram: +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/CZgate.png +# :align: center +# :width: 85% +# +# .. +# +# Implementation of the Cirac-Zoller gate using phonon states +# +# We see that the consecutive application of a blue sideband, a carrier +# frequency, and a red sideband, with different durations, gives us a +# maximally entangled state. It is important to note that, in the last +# step, the part of the superposition that has no chain motion is +# unaffected by the red sideband. This property allows the creation of +# entanglement in electronic states by using the phonon states. +# +# However, the implementation of the Cirac-Zoller gate in real life is +# plagued by problems. First, the ion chain needs to be completely cooled +# down to the ground motional state, which can never be achieved. Second, +# the gate is too slow. Surely, if we use hyperfine qubits, we can take as +# long as we want to implement the gates. The problem comes from the +# harmonic oscillator states. Since ion chains are large and less isolated +# from the environment, phonon states are rather short-lived due to +# decoherence. +# +# For actual applications, we use a more ingenious gate, known as the +# **Mølmer-Sørensen** gate [#Molmer1999]_. It has the advantage that the ions do not +# need to be perfectly cooled to the motional ground state for it to work. +# It relies on simultaneously shining two lasers at different frequencies +# :math:`\omega_{\pm}` on the two target ions, which are slightly detuned +# from the atomic energy gap :math:`\Delta:` +# +# .. math:: \omega_{\pm}=\Delta \pm \delta +# +# The net effect of this interaction with laser light is to excite +# :math:`\left\lvert g \right\rangle \left\lvert g \right\rangle \left\lvert n \right\rangle \rightarrow \left\lvert e \right\rangle \left\lvert e \right\rangle\left\lvert n \right\rangle,` +# and it can do so through any of the four paths shown below: +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/molmer_sorensen.png +# :align: center +# :width: 60% +# +# .. +# +# Mølmer-Sørensen gate implemented with two simultaneous laser pulses +# +# Using a quantum mechanical technique known as perturbation theory, we +# can deduce that there is also a Rabi frequency :math:`\Omega_{MS}` +# associated with this evolution. Therefore, adjusting the time and the +# phase of the lasers can lead to a superposition of +# :math:`\left\lvert g \right\rangle \left\lvert g \right\rangle \left\lvert n \right\rangle` +# and +# :math:`\left\lvert e \right\rangle \left\lvert e \right\rangle\left\lvert n \right\rangle.` +# For example, we can obtain the state +# :math:`\frac{1}{\sqrt{2}}\left(\left\lvert g \right\rangle \left\lvert g \right\rangle\left\lvert n \right\rangle +\left\lvert e \right\rangle \left\lvert e \right\rangle\left\lvert n \right\rangle\right)` +# which, in the two-ion subsystem, corresponds to the maximally entangled state +# :math:`\frac{1}{\sqrt{2}}\left(\left\lvert g \right\rangle \left\lvert g \right\rangle +\left\lvert e \right\rangle \left\lvert e \right\rangle\right).` +# Using Schrödinger's equation allows us to derive how the qubits evolve +# when we apply the Mølmer-Sørensen protocol for a time :math:`t.` The +# Hamiltonian is more involved, so we will not do this. We simply state +# the result (for zero phase) and implement it via a Python function +# +# .. math:: U_{MS}(t) =\left( \begin{array}{cccc} +# \cos(\frac{\Omega_{MS}t}{2}) & 0 & 0 & -i\sin(\frac{\Omega_{MS} t}{2})\\ +# 0 & \cos(\frac{\Omega_{MS} t}{2}) & -i\sin(\frac{\Omega_{MS} t}{2}) & 0 \\ +# 0 & -i\sin(\frac{\Omega_{MS} t}{2}) & \cos(\frac{\Omega_{MS} t}{2}) & 0 \\ +# -i\sin(\frac{\Omega_{MS} t}{2}) & 0 & 0 & \cos(\frac{\Omega_{MS} t}{2}) +# \end{array} +# \right) +# + +Omega = 100 + + +def Molmer_Sorensen(t): + ms = np.array( + [ + [np.cos(Omega * t / 2), 0, 0, -1j * np.sin(Omega * t / 2)], + [0, np.cos(Omega * t / 2), -1j * np.sin(Omega * t / 2), 0], + [0, -1j * np.sin(Omega * t / 2), np.cos(Omega * t / 2), 0], + [-1j * np.sin(Omega * t / 2), 0, 0, np.cos(Omega * t / 2)], + ] + ) + return ms + + +############################################################################## +# Since the CNOT gate is commonly used in quantum algorithms, let us +# determine how to obtain it from the Mølmer-Sørensen gate. +# It is possible to do so by using a combination of +# single-qubit rotations and the Mølmer-Sørensen gate applied for a period of +# :math:`t=\pi/2\Omega_{MS}.` Explicitly, we do this using the +# following circuit [#Brown2019]_: +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/CNOTgate.png +# :align: center +# :width: 100% +# +# .. +# +# Circuit for the CNOT gate using rotations and an MS gate +# +# where :math:`RX` and :math:`RY` are the usual rotations around the X and Y +# axes, and :math:`MS(t)` denotes the Mølmer-Sørensen gate applied for a +# time :math:`t/\Omega_{MS}.` Let us verify that this is indeed the case +# by building the circuit in PennyLane: + +dev2 = qml.device("default.qubit",wires=2) + +@qml.qnode(dev2) +def ion_cnot(basis_state): + + #Prepare the two-qubit basis states from the input + qml.BasisState(basis_state, wires=range(2)) + + #Implements the circuit shown above + qml.RY(np.pi/2, wires=0) + qml.QubitUnitary(Molmer_Sorensen(np.pi/2/Omega),wires=[0,1]) + qml.RX(-np.pi/2, wires=0) + qml.RX(-np.pi/2, wires=1) + qml.RY(-np.pi/2, wires=0) + + return qml.state() + +#Compare with built-in CNOT +@qml.qnode(dev2) +def cnot_gate(basis_state): + + qml.BasisState(basis_state, wires=range(2)) + + qml.CNOT(wires=[0,1]) + + return qml.state() + +#Check that they are the same up to numerical error and global phase +print(np.isclose(np.exp(-1j*np.pi/4)*ion_cnot([0,0]),cnot_gate([0,0]))) +print(np.isclose(np.exp(-1j*np.pi/4)*ion_cnot([0,1]),cnot_gate([0,1]))) +print(np.isclose(np.exp(-1j*np.pi/4)*ion_cnot([1,0]),cnot_gate([1,0]))) +print(np.isclose(np.exp(-1j*np.pi/4)*ion_cnot([1,1]),cnot_gate([1,1]))) + +############################################################################## +# This is indeed the CNOT gate, up to a global phase. +# At sufficiently low temperatures, the Rabi frequency :math:`\Omega_{MS}` +# does not depend on the initial harmonic oscillator state, so this method +# can be used reliably even when we fail to cool down the ion chain +# completely. This property also makes this gate more robust to the +# decoherence of the chain. +# +# The problem with too many ions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We have learned that the trapped ion paradigm allows us to prepare and +# measure individual qubits, and that we can implement single and +# multi-qubit gates with high accuracy. What's not to like? As in every +# physical realization of quantum computers, trapped ions come with +# advantages and disadvantages. The main problem shared by all physical +# implementations of quantum computers is scalability. The root of the +# problem and the technological challenges involved depend on our +# particular framework. +# +# To understand why scalability is a problem for trapped ions, let us +# consider a long ion chain. As discussed in the previous section, to +# implement multi-qubit gates, we need to lean on the harmonic oscillator +# states of the ion chains. These turn out to be a blessing and a curse +# simultaneously. Quantum computing with trapped ions would not be +# possible without motional states. However, if we put more ions in the +# chain, the values of the frequencies needed to excite it become too +# close together. As a consequence, unless we are extremely careful with +# our laser frequencies, we may end up in the wrong quantum state. We do +# not have infinite precision, so when the number of ions becomes close to +# 100, our current gate technology becomes practically unusable. +# +# Is there a way to make the frequency values more spread out? One way is +# to reduce the Rabi frequency of the Mølmer-Sørensen gate, which we +# control by changing the strength of the laser light. Disappointingly, +# not only does this strategy make it harder to control the ion, but it +# also increases the time needed to apply the Mølmer-Sørensen gate. As +# already mentioned in the previous section, time is of the essence when +# applying multi-qubit gates since the motional states of the chain are +# extremely sensitive to decoherence. We cannot afford to have even slower +# gates. +# +# Which of the DiVincenzo criteria do trapped ions quantum computers still +# fail to meet? Criterion 1 is only met partially: we do have robust +# qubits, but there seems to be a hard technological limit for +# scalability. Criterion 3 also becomes an issue when the ion chain is too +# long since coherence times for motional states become shorter. The +# two-qubit requirement of criterion 4 is related to this decoherence problem since +# multi-qubit gates can take too long to implement accurately in a long +# ion chain. Criterion 2, as already discussed, does not present too much +# of a problem thanks to optical pumping technology. However, problems remain for criterion 5. +# As we already saw, we can use two different +# species of ions to obtain good measurements. But, in general, it is +# challenging to implement consecutive good-quality two-qubit gates +# between different ion species; strategies like the Mølmer-Sørensen +# gate will not work and need modification. +# +# The state of the art +# ~~~~~~~~~~~~~~~~~~~~ +# +# Of course, no matter how insurmountable these challenges seem to be, +# physicists will not give up. Many ingenious ways to address these +# technical complications have already been proposed. Not surprisingly, it +# is one of the hottest research topics in quantum computing, and papers +# with newer technologies have probably been published since this tutorial +# was written. +# +# The main issue discussed above is that a long ion chain is noisy and +# makes qubits challenging to manipulate. In 2002, Kielpinski and +# collaborators [#QCCD2002]_ came up with an intelligent solution: if size is a +# problem, let us make the chain shorter! Of course, we would still like +# to be able to manipulate thousands of qubits. To achieve this, we could +# build a segmented trap, also known as a **QCCD** (Quantum Charge-Coupled +# Device) **architecture**. The idea is to make our traps mobile. We could +# move ions from one place to another whenever we need to apply a +# multi-qubit gate and move them far away when we need to manipulate them +# individually. Thus, the chain that we interact with when we need to +# entangle qubits is not long. This method makes the motional states less +# prone to decoherence. The phonon frequencies are also sufficiently +# spread apart so that the gates can be implemented. +# +# .. figure:: ../_static/demonstration_assets/trapped_ions/qccd.png +# :align: center +# :width: 60% +# +# .. +# +# Example of a proposed QCCD architecture, as in [#Amini2010]_ +# +# QCCD architectures sound like a straightforward solution, but seeing as +# we do not have large quantum computers yet, there must be some nuances. +# In practice, moving ions around a trap is not easy at all. The +# containing potential must be changed in a highly accurate manner to +# transport the ions without losing them. Such technology has not been +# perfected yet. While it has been possible to manipulate ions and make +# them interact, the traps we need for a good quantum computer are +# somewhat involved. We want multiple segments in the trap that allow for +# arbitrary ions to be brought together to run quantum algorithms without +# any limitations. In April 2021, Honeywell reported building a +# multi-segment QCCD architecture with six qubits and two interaction +# zones [#Pino2021]_. However, it is unclear how this proposed technology would scale +# to higher orders of magnitude. +# +# Another path towards a solution would be to simply accept the short +# coherence times of the ion chains, and try to make the two-qubit gates +# faster. Such an approach is being followed by the startup IonQ. In +# January 2021, they showed that it is possible to speed up the +# Mølmer-Sørensen gate by one order of magnitude by changing the shape +# of the laser pulse [#Blumel2021]_. Such a speedup might not be enough as the ion chain +# grows. However, a combination of approaches involving QCCDs and +# faster gates may yield the solution to the scalability problem in the +# future. +# +# .. note:: +# +# There is another proposed solution to apply two-qubit gates efficiently, which +# involves connecting the ions with photons. Using polarization state +# measurements, we can also entangle electronic states [#Monroe2014]_. This technology +# is still in the early stages of development. +# +# Implementing multi-qubit gates is not the only problem for +# trapped-ion quantum computers. There is still much to do to improve the +# precision of measurements, for example. Most of the photons emitted by +# ions during a measurement are lost, so it would be good to find ways to +# direct more of them to the detector. One can do this using a waveguide architecture inside +# the trap. Similarly, as the number of ions grows, the number of laser +# beams we need does as well [#Niffenegger2020]_. Again, waveguides can also be used to +# direct the photons to target ions. Combined with a better QCCD +# architecture, this optical integration would well-equip us to run +# quantum computing algorithms with trapped ions. +# +# Concluding Remarks +# ~~~~~~~~~~~~~~~~~~ +# +# Ion trapping is currently one of the most widespread physical implementations +# of quantum computers, both in academia and in industry. Their popularity comes +# as no surprise, since the physical principles that make the paradigm work are +# simple enough, and the necessary technology is already well-developed. +# Granted, there are challenging technical difficulties to scale these quantum +# computers further. However, viable solutions have been proposed, and many +# institutions around the world are working non-stop to make them a reality. +# Moreover, what could be considered simple prototypes of such technologies have +# already proven extremely powerful. The big unknown is whether such devices can scale as much as we +# would like them to. It would be unwise to give up only because the challenge is +# imposing. After all, personal computers were the fruit of hard work and +# inventiveness, and very few people were able to predict that they would scale +# as much as they have. Now you possess a high-level knowledge of how trapped +# ion computers work! Make sure to read any new papers that come out +# to keep updated on new developments. Will the trapped ion framework +# emerge victorious in this race to obtain a useful quantum computer? Only time will tell! +# +# References +# ~~~~~~~~~~ +# +# .. [#DiVincenzo2000] +# +# D. DiVincenzo. (2000) "The Physical Implementation of Quantum Computation", +# `Fortschritte der Physik 48 (9–11): 771–783 +# `__. +# (`arXiv `__) +# +# .. [#Paul1953] +# +# W. Paul, H. Steinwedel. (1953) "Ein neues Massenspektrometer ohne Magnetfeld", +# RZeitschrift für Naturforschung A 8 (7): 448-450. +# +# .. [#CiracZoller] +# +# J. Cirac, P. Zoller. (1995) "Quantum Computations with Cold Trapped Ions". +# Physical Review Letters 74 (20): 4091–4094. +# +# .. [#Malinowski] +# +# M. Malinowski. (2021) "Unitary and Dissipative Trapped-​Ion Entanglement Using +# Integrated Optics". PhD Thesis retrieved from `ETH thesis repository +# `__. +# +# .. [#NandC2000] +# +# M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", +# Cambridge University Press. +# +# .. [#Hughes2020] +# +# A. Hughes, V. Schafer, K. Thirumalai, et al. (2020) +# "Benchmarking a High-Fidelity Mixed-Species Entangling Gate" +# `Phys. Rev. Lett. 125, 080504 +# `__. +# (`arXiv `__) +# +# .. [#Bergou2021] +# +# J. Bergou, M. Hillery, and M. Saffman. (2021) "Quantum Information Processing", +# Springer. +# +# .. [#Molmer1999] +# +# A. Sørensen, K. Mølmer. (1999) "Multi-particle entanglement of hot trapped ions", +# `Physical Review Letters. 82 (9): 1835–1838 +# `__. +# (`arXiv `__) +# +# .. [#Brown2019] +# +# M. Brown, M. Newman, and K. Brown. (2019) +# "Handling leakage with subsystem codes", +# `New J. Phys. 21 073055 +# `__. +# (`arXiv `__) +# +# .. [#Monroe2014] +# +# C. Monroe, R. Ruassendorf, A Ruthven, et al. (2019) +# "Large scale modular quantum computer architecture with atomic memory and photonic interconnects", +# `Phys. Rev. A 89 022317 +# `__. +# (`arXiv `__) +# +# .. [#QCCD2002] +# +# D. Kielpinski, C. Monroe, and D. Wineland. (2002) +# "Architecture for a large-scale ion-trap quantum computer", +# `Nature 417, 709–711 (2002). +# `__. +# +# .. [#Amini2010] +# +# J. Amini, H. Uys, J. Wesenberg, et al. (2010) +# "Toward scalable ion traps for quantum information processing", +# `New J. Phys 12 033031 +# `__. +# (`arXiv `__) +# +# +# .. [#Pino2021] +# +# J. Pino, J. Dreiling, J, C, Figgatt, et al. (2021) +# "Demonstration of the trapped-ion quantum CCD computer architecture". +# `Nature 592, 209–213 +# `__. +# (`arXiv `__) +# +# .. [#Blumel2021] +# +# R. Blumel, N. Grzesiak, N. Nguyen, et al. (2021) +# "Efficient Stabilized Two-Qubit Gates on a Trapped-Ion Quantum Computer" +# `Phys. Rev. Lett. 126, 220503 +# `__. +# (`arXiv `__) +# +# .. [#Niffenegger2020] +# +# R. Niffenegger, J. Stuart, C.Sorace-Agaskar, et al. (2020) +# "Integrated multi-wavelength control of an ion qubit" +# `Nature volume 586, pages538–542 +# `__. +# (`arXiv `__) +# diff --git a/demonstrations_v2/tutorial_trapped_ions/metadata.json b/demonstrations_v2/tutorial_trapped_ions/metadata.json new file mode 100644 index 0000000000..3fced30416 --- /dev/null +++ b/demonstrations_v2/tutorial_trapped_ions/metadata.json @@ -0,0 +1,182 @@ +{ + "title": "Trapped ion quantum computers", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2021-11-10T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tapped_ion_QC.png" + } + ], + "seoDescription": "Learn all about trapped ion quantum computers, developed by companies such as IonQ and Honeywell.", + "doi": "", + "references": [ + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "D. DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" + }, + { + "id": "Paul1953", + "type": "article", + "title": "Ein neues Massenspektrometer ohne Magnetfeld", + "authors": "W. Paul, H. Steinwedel", + "year": "1953", + "journal": "RZeitschrift f\u00fcr Naturforschung", + "url": "" + }, + { + "id": "CiracZoller", + "type": "article", + "title": "Quantum Computations with Cold Trapped Ions", + "authors": "J. Cirac, P. Zoller", + "year": "1995", + "journal": "Physical Review Letters", + "url": "" + }, + { + "id": "Malinowski", + "type": "article", + "title": "Unitary and Dissipative Trapped-\u200bIon Entanglement Using Integrated Optics", + "authors": "M. Malinowski", + "year": "2021", + "journal": "", + "url": "https://ethz.ch/content/dam/ethz/special-interest/phys/quantum-electronics/tiqi-dam/documents/phd_theses/Thesis-Maciej-Malinowski" + }, + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "url": "" + }, + { + "id": "Hughes2020", + "type": "article", + "title": "Benchmarking a High-Fidelity Mixed-Species Entangling Gate", + "authors": "A. Hughes, V. Schafer, K. Thirumalai, et al.", + "year": "2020", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.125.080504" + }, + { + "id": "Bergou2021", + "type": "book", + "title": "Quantum Information Processing", + "authors": "J. Bergou, M. Hillery, and M. Saffman", + "year": "2021", + "publisher": "Springer", + "url": "" + }, + { + "id": "Molmer1999", + "type": "article", + "title": "Multi-particle entanglement of hot trapped ions", + "authors": "A. S\u00f8rensen, K. M\u00f8lmer", + "year": "1999", + "journal": "Physical Review Letters", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.82.1835" + }, + { + "id": "Brown2019", + "type": "article", + "title": "Handling leakage with subsystem codes", + "authors": "M. Brown, M. Newman, and K. Brown", + "year": "2019", + "journal": "New J. Phys.", + "url": "https://iopscience.iop.org/article/10.1088/1367-2630/ab3372" + }, + { + "id": "Monroe2014", + "type": "article", + "title": "Large scale modular quantum computer architecture with atomic memory and photonic interconnects", + "authors": "C. Monroe, R. Ruassendorf, A Ruthven, et al.", + "year": "2019", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.89.022317" + }, + { + "id": "QCCD2002", + "type": "article", + "title": "Architecture for a large-scale ion-trap quantum computer", + "authors": "D. Kielpinski, C. Monroe, and D. Wineland", + "year": "2002", + "journal": "Nature", + "url": "https://www.nature.com/articles/nature00784" + }, + { + "id": "Amini2010", + "type": "article", + "title": "Toward scalable ion traps for quantum information processing", + "authors": "J. Amini, H. Uys, J. Wesenberg, et al.", + "year": "2010", + "journal": "New J. Phys", + "url": "https://iopscience.iop.org/article/10.1088/1367-2630/12/3/033031/meta" + }, + { + "id": "Pino2021", + "type": "article", + "title": "Demonstration of the trapped-ion quantum CCD computer architecture", + "authors": "J. Pino, J. Dreiling, J, C, Figgatt, et al.", + "year": "2021", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-021-03318-4" + }, + { + "id": "Blumel2021", + "type": "article", + "title": "Efficient Stabilized Two-Qubit Gates on a Trapped-Ion Quantum Computer", + "authors": "R. Blumel, N. Grzesiak, N. Nguyen, et al.", + "year": "2021", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.126.220503" + }, + { + "id": "Niffenegger2020", + "type": "article", + "title": "Integrated multi-wavelength control of an ion qubit", + "authors": "R. Niffenegger, J. Stuart, C.Sorace-Agaskar, et al.", + "year": "2020", + "journal": "Nature", + "volume": "586", + "pages": "538\u2013542", + "url": "https://www.nature.com/articles/s41586-020-2811-x" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_sc_qubits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/trapped-ion-quantum-computers-demo/7341" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_trapped_ions/requirements.in b/demonstrations_v2/tutorial_trapped_ions/requirements.in new file mode 100644 index 0000000000..8132c3977e --- /dev/null +++ b/demonstrations_v2/tutorial_trapped_ions/requirements.in @@ -0,0 +1,4 @@ +matplotlib +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_unitary_designs/demo.py b/demonstrations_v2/tutorial_unitary_designs/demo.py new file mode 100644 index 0000000000..828a33156e --- /dev/null +++ b/demonstrations_v2/tutorial_unitary_designs/demo.py @@ -0,0 +1,740 @@ +r""".. role:: html(raw) + :format: html + +Unitary designs +=============== + +.. meta:: + :property="og:description": Learn about designs and their uses in quantum computing. + + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/fano.png + +.. related:: + + tutorial_haar_measure Understanding the Haar measure + +*Author: Olivia Di Matteo — Posted: 07 September 2021. Last updated: 07 September 2021.* + + +.. note:: + + This demo is intended to be a sequel to the + :doc:`demo about the Haar measure `. + If you are not familiar with the Haar measure, we recommend going through + that demo first before exploring this one. + +Take a close look at the following mathematical object: + +.. figure:: /_static/demonstration_assets/unitary_designs/fano_no_labels.svg + :align: center + :width: 30% + +| + +There are many things we can say about it: it consists of seven points and seven +lines (the circle counts as a line); each line contains three points, and each +point is contained in three lines. Furthermore, any pair of points occur +together in exactly one line. This object, called the `Fano plane +`__, is an instance of a mathematical +structure called a `projective plane +`__, which is just one example +of a `combinatorial design +`__. Designs are sets of +objects, and groups of those objects, that satisfy certain balance properties +and symmetries. They have been studied for hundreds of years in a huge variety +of contexts [#Handbook]_, from `error correcting codes +`__, to `card +games `__, and +even `agriculture +`__. So, what about +quantum computing? + +Designs are actually quite prevalent in quantum computing. You've almost +certainly come across one before, though you may not have realized it. At the +end of the Haar measure demo, we asked a very important question: "do we always +*need* to sample from the full Haar measure?". The answer to this is "no", and +the reasoning lies in the study of *unitary designs*. + +In this demo, you'll learn the definition of :math:`t`-designs, what it means to +generalize them to unitary :math:`t`-designs, and you'll see some canonical +examples of designs in quantum computing. You'll also learn about their +connection with the Haar measure, what it means to *twirl* a quantum channel, +and explore how to leverage 2-designs in PennyLane to compute the average +fidelity of a noisy channel. You will experience directly a situation where we can +use a :math:`t`-design as a shortcut over the full Haar measure to greatly improve +the efficiency of a task 🎉. + + +From spheres to unitary :math:`t`-designs +----------------------------------------- + +Spherical designs +^^^^^^^^^^^^^^^^^ + +Before diving into unitary designs, let's look at the sphere for some +intuition. Suppose we have a polynomial in :math:`d` variables, and we would +like to compute its average over the surface of a real, :math:`d`-dimensional +unit sphere, :math:`S(R^d).` We can do so by integrating that function over the +sphere (using the proper measure), but that would be a lot of parameters to +keep track of. + +One could alternatively approximate the average by sampling thousands of points +uniformly at random on the sphere, evaluating the function at those points, and +computing their average value. That will always work, and it will get us close, +but it will not be exact. + +In fact, both of those approaches may be overkill in some special cases—if the +terms in the polynomial have the same degree of at most :math:`t,` you can +compute the average **exactly** over the sphere using only a small set of points +rather than integrating over the entire sphere. That set of points is called a +spherical :math:`t`-design. More formally [#Handbook]_, [#Delsarte]_: + +.. admonition:: Definition + :class: defn + + Let :math:`p_t: \mathcal{S}(R^d)\rightarrow R` be a polynomial in :math:`d` + variables, with all terms homogeneous in degree at most :math:`t.` A + set :math:`X = \{x: x \in \mathcal{S}(R^d)\}` is a spherical :math:`t`-design if + + .. math:: + + \frac{1}{|X|} \sum_{x \in X} p_t(x) = \int_{\mathcal{S}(R^d)} p_t (u) d\mu(u) + + holds for all possible :math:`p_t,` where :math:`d\mu` is the uniform, + normalized spherical measure. A spherical :math:`t`-design is also a + :math:`k`-design for all :math:`k < t.` + + + +Now this is a pretty abstract picture, so let's consider the 3-dimensional +sphere. This definition tells us that if we want to take the average of a +polynomial over a sphere where all terms have the same degree of at most 2, we +can do so using a small, representative set of points called a 2-design, +rather than the whole sphere. Similarly, if all terms of the polynomial have the +same degree of at most 3, we could use a 3-design. + +But what are these representative sets of points? Since we are using these +points as a stand-in for averaging over the whole sphere, we'd want the points +in the set to be distributed in a way that provides sufficient "coverage". In +the 3-dimensional case, the vertices of some familiar solids form :math:`t`-designs +[#Handbook]_, [#sph4design]_: + +.. figure:: /_static/demonstration_assets/unitary_designs/shapes.svg + :align: center + :width: 80% + +| + +We see from these illustrations that spherical designs are sets of +*evenly-spaced points*. As :math:`t` increases, the configurations become +increasingly sphere-like. Looking at this in a different way, the more complex a +function becomes as its degree increases, the closer the :math:`t`-design must +be to a sphere; we need to evaluate the function at more points in order to gain +sufficient information when a function is varying more quickly due to a higher +degree. In 3 dimensions, we can compute the average of a polynomial with degree +2 by evaluating it only at the points of a tetrahedron, despite the fact that it +doesn't look spherical at all. More complex functions require more points +and thus more intricate configurations for the design. Spherical designs exist +for all :math:`t` and dimension :math:`d` [#Handbook]_. They are not always +unique, and may have varying numbers of points. + +To show that this really works, let's look at an explicit example. Consider the +following polynomial in 3 variables: + +.. math:: + + f(x, y, z) = x^4 - 4 x^3 y + y^2 z^2 + +We can compute the average value of :math:`f` by integrating over a unit sphere: +the result is :math:`4/15 \approx 0.26667.` However, this integral is +non-trivial to evaluate by hand; the most straightforward way is to convert to +polar coordinates, and even then, it involves integrating functions with 4th and +5th powers of trigonometric functions. + +Instead, this is a case where we can leverage the fact that all terms in the +polynomial have degree 4, and compute the average exactly using only a subset of +points that form a 4-design. We choose a dodecahedron for convenience; while +this is actually a 5 design, it also forms a 4-design, and is a more familiar +shape than the 4-design depicted above. + +First, we define the set of points that comprise a dodecahedron: + +""" + +import numpy as np + +# The golden ratio +g = (1 + np.sqrt(5)) / 2 + +# A dodecahedron has 20 points +dodecahedron = np.array([ + # 8 of them form a cube within the sphere + [1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, -1], + [-1, 1, 1], [-1, 1, -1], [-1, -1, 1], [-1, -1, -1], + + # 4 of them form a rectangle within the y-z plane + [0, g, 1/g], [0, g, -1/g], [0, -g, 1/g], [0, -g, -1/g], + + # 4 of them form a rectangle within the x-z plane + [1/g, 0, g], [1/g, 0, -g], [-1/g, 0, g], [-1/g, 0, -g], + + # 4 of them form a rectangle within the x-y plane + [g, 1/g, 0],[g, -1/g, 0], [-g, 1/g, 0], [-g, -1/g, 0], +]) + +# Normalize the points so they all fit in the unit sphere +dodecahedron = np.array( + [point / np.linalg.norm(point) for point in dodecahedron] +) + +###################################################################### +# Now we define our function and compute the average over the dodecahedron: + +def f(x, y, z): + return (x ** 4) - 4 * (x ** 3) * y + y ** 2 * z ** 2 + +dodeca_average = np.mean([f(*point) for point in dodecahedron]) +print(dodeca_average) + +###################################################################### +# This is exactly the value we expect. What happens if we try to do this using +# only a 3-design, the cube? + +# The first 8 points of the dodecahedron are a cube +cube = dodecahedron[:8] + +cube_average = np.mean([f(*point) for point in cube]) +print(cube_average) + + +###################################################################### +# This clearly differs from the true value. We need a design with :math:`t=4` +# or better in order to compute this average, and when such a design is +# available, we may save significant computational time. +# +# Unitary designs +# ^^^^^^^^^^^^^^^ +# +# We've learned now that spherical designs are sets of evenly-spaced points, and +# saw how they can be used as a shortcut to evaluate the average of a +# polynomial up to a given degree :math:`t.` However, there was nothing quantum +# about this; there weren't even any complex numbers involved. A *unitary +# design* extends this concept from evenly-distributed points to +# evenly-distributed unitaries. More formally, instead of averaging polynomials +# over spheres, we consider polynomials that are functions of the entries of +# unitary matrices [#Dankert]_, [#Gross]_. +# +# .. admonition:: Definition +# :class: defn +# +# Let :math:`P_{t,t}(U)` be a polynomial with homogeneous degree at most :math:`t` in +# :math:`d` variables in the entries of a unitary matrix :math:`U,` and degree +# :math:`t` in the complex conjugates of those entries. A unitary +# :math:`t`-design is a set of :math:`K` unitaries :math:`\{U_k\}` such that +# +# .. math:: +# +# \frac{1}{K} \sum_{k=1}^{K} P_{t,t}(U_k) = \int_{\mathcal{U}(d)} +# P_{t,t} (U) d\mu(U) +# +# holds for all possible :math:`P_{t,t},` and where :math:`d\mu` is the +# uniform *Haar measure*. +# +# We stress again that this expression is **exact**. The unitaries in a unitary +# design are a representative set of points that are "evenly spaced" across the +# unitary group. With just a subset of the full group, we can evaluate complex +# expressions that would be otherwise intractable. +# +# A surprising result about unitary designs is that they exist for all possible +# combinations of :math:`t` and :math:`d` [#Roy]_. There are some known lower +# bounds for the number of unitaries required; for example, a 2-design in +# dimension :math:`d` has at least :math:`d^4 - 2d^2 + 2` elements [#Gross]_, +# [#Roy]_. However, actually finding the sets (and in particular, finding ones +# with minimal size), is a challenging problem [#Bannai]_, though very recently +# some constructions have been put forward [#Nakata]_. +# +# +# .. admonition:: Fun fact +# +# Applying the elements of a unitary design to a fixed pure state produces a +# set of vectors that form a *complex projective design* [#DankertThesis]_. +# These are much like spherical designs, but they live in a complex vector +# space. If you've ever studied the characterization of quantum systems, you +# may have come across some special sets of measurements called mutually +# unbiased bases (MUBs), or symmetric, informationally complete positive +# operator valued measurements (SIC-POVMs). Both of these sets of vectors +# are complex projective 2-designs [#Klappenecker]_. +# +# .. figure:: /_static/demonstration_assets/unitary_designs/sic-povm.svg +# :align: center +# :width: 80% +# +# The vectors of the simplest SIC-POVM in dimension 2, plotted on a Bloch sphere. +# +# Unitary :math:`t`-designs in action +# ----------------------------------- +# +# Unitary designs come into play in applications that require randomization, or +# sampling of random unitaries—essentially, they can be used as a stand-in for +# the Haar measure. The way in which the unitaries are used in the application may +# place restrictions on the value of :math:`t` that is required; arguably the most +# common is the unitary 2-design. +# +# While in general unitary designs are hard to construct, there are well known +# results for unitary 1-, 2-, and 3-designs based on familiar objects in quantum +# computing. Before we see what those are, let's explore an important use case. +# +# Average fidelity +# ^^^^^^^^^^^^^^^^ +# A key application of unitary 2-designs is benchmarking quantum +# operations. Suppose we have a noisy quantum channel :math:`\Lambda` that should +# perform something close to the unitary operation :math:`V.` What can we say +# about the performance of this channel? +# +# One metric of interest is the *fidelity*. Consider the state :math:`|0\rangle.` +# In an ideal case, we apply :math:`V` and obtain :math:`V|0\rangle.` But applying the +# channel :math:`\Lambda` gives us something a little different. Since it's noisy, +# we must consider the state as a density matrix. The action of :math:`\Lambda` on +# our starting state is :math:`\Lambda(|0\rangle \langle 0|).` If :math:`\Lambda` +# was perfect, then :math:`\Lambda(|0\rangle \langle 0|) = V|0\rangle \langle +# 0|V^\dagger`, and the fidelity is +# +# .. math:: +# +# F(\Lambda, V) = \langle 0 | V^\dagger \cdot \Lambda(|0\rangle \langle 0|) \cdot V|0\rangle = 1. +# +# In reality, :math:`\Lambda` is not going to implement :math:`V` perfectly, and +# :math:`F < 1.` More importantly though, all we've computed so far is the fidelity when +# the initial state is :math:`|0\rangle.` What if the initial state is something +# different? What is the fidelity *on average*? +# +# To compute an average fidelity, we must do so with respect to the full set +# of Haar-random states. We usually generate random states by applying a +# Haar-random unitary :math:`U` to :math:`|0\rangle.` Thus to compute the average +# over all such :math:`U` we must evaluate +# +# .. math:: +# +# \bar{F}(\Lambda, V) = \int_{\mathcal{U}} d\mu(U) \langle 0 | U^\dagger V^\dagger \Lambda(U |0\rangle \langle 0| U^\dagger) V U |0\rangle. +# +# This is known as *twirling* the channel :math:`\Lambda.` Computing the average +# fidelity in this way would be a nightmare—we'd have to compute the fidelity +# with respect to an infinite number of states! +# +# However, consider the expression in the integral above. We have an inner product +# involving two instances of :math:`U,` and two instances of +# :math:`U^\dagger.` This means that the expression is a polynomial of degree 2 in +# both the elements of :math:`U` and its complex conjugates—this matches exactly +# the definition of a unitary 2-design. This means that if we can find a set of +# :math:`K` unitaries that form a 2-design, we can compute the average fidelity +# using only a finite set of initial states: +# +# .. math:: +# +# \frac{1}{K} \sum_{j=1}^K \langle 0 | U_j^\dagger V^\dagger \Lambda(U_j |0\rangle \langle 0| +# U_j^\dagger) V^\dagger U_j |0\rangle = \int_{\mathcal{U}} d\mu(U) \langle 0 +# | U^\dagger V^\dagger \Lambda(U |0\rangle \langle 0| U^\dagger) V U |0\rangle +# +# This is great, but a question remains: what is the representative set of unitaries? +# +# The Clifford group +# ^^^^^^^^^^^^^^^^^^ +# +# A beautiful result in quantum computing is that some special groups you may +# already be familiar with are unitary designs: +# +# - the Pauli group forms a unitary 1-design, and +# - the Clifford group forms a unitary 3-design. +# +# By the definition of designs, this means the Clifford group is also a 1- +# and 2-design. +# +# The :math:`n`-qubit Pauli group, :math:`\mathcal{P}(n),` is the set of all tensor +# products of Pauli operations :math:`X`, :math:`Y`, :math:`Z,` and :math:`I.` The +# :math:`n`-qubit Clifford group, :math:`\mathcal{C}(n),` is the *normalizer* of the +# Pauli group. In simpler terms, the Clifford group is the set of operations that +# send Paulis to Paulis (up to a phase) under conjugation i.e., +# +# .. math:: +# +# C P C^\dagger = \pm P^\prime, \quad \forall P, P^\prime \in \mathcal{P}(n), \quad C \in \mathcal{C}(n). +# +# The Clifford group has some profoundly interesting properties and countless uses +# across quantum computing, from circuit compilation to error correcting +# codes. For a single qubit, the group is built from just two operations. One is +# the Hadamard: +# +# .. math:: +# +# H X H^\dagger = Z, \quad H Y H^\dagger = -Y, \quad H Z H^\dagger = X. +# +# This clearly maps Paulis to Paulis (up to a phase). The other is the phase gate :math:`S:` +# +# .. math:: +# +# S X S^\dagger = Y, \quad S Y S^\dagger = -X, \quad S Z S^\dagger = Z. +# +# If both :math:`H` and :math:`S` map Paulis to Paulis, then products of them do +# as well. In group theory terms, the single-qubit Clifford group is +# generated by :math:`H` and :math:`S.` For example, consider the action of +# :math:`HS:` +# +# .. math:: +# +# (HS) X (HS)^\dagger = -Y, \quad (HS) Y (HS)^\dagger = -Z, \quad (HS) Z (HS)^\dagger = X. +# +# Since :math:`Y = iXZ,` it is enough to specify Clifford operations by how they +# act on :math:`X` and :math:`Z.` For a particular Clifford, there are 6 possible +# ways it can transform :math:`X`, namely :math:`\pm X, \pm Y,` or :math:`\pm Z.` Once +# that is determined, there are four remaining options for the transformation of +# :math:`Z,` leading to 24 elements total. +# +# It takes some work, but you can take combinations of :math:`H` and :math:`S` +# and evaluate their action on :math:`X` and :math:`Z` (or look at their matrix +# representations) until you find all 24 unique elements. The results of +# this endeavour are expressed below as strings: + +single_qubit_cliffords = [ + '', + 'H', 'S', + 'HS', 'SH', 'SS', + 'HSH', 'HSS', 'SHS', 'SSH', 'SSS', + 'HSHS', 'HSSH', 'HSSS', 'SHSS', 'SSHS', + 'HSHSS', 'HSSHS', 'SHSSH', 'SHSSS', 'SSHSS', + 'HSHSSH', 'HSHSSS', 'HSSHSS' +] + +###################################################################### +# To see for yourself how this set of unitaries is evenly distributed, try +# applying each of the Cliffords to the initial state :math:`|0\rangle,` and +# plot the resulting states on the Bloch sphere. You'll find they are +# symmetric and evenly spaced; in fact, they are all eigenstates of :math:`X,` +# :math:`Y,` and :math:`Z.` Furthermore, under the full group action, the result +# is balanced in the sense that each eigenstate is obtained the same number of +# times. +# +# The multi-qubit Clifford group can also be +# specified by only a small set of generators (in fact, only one more +# than is needed for the single-qubit case). Together, :math:`H`, :math:`S,` and +# CNOT (on every possible qubit or pair of qubits) generate the :math:`n`-qubit +# group. Be careful though—the size of the group increases exponentially. The +# 2-qubit group alone has 11520 elements! The size can be worked out in a manner +# analogous to that we used above in the single qubit case: by looking at the +# combinatorics of the possible ways the gates can map Paulis with only +# :math:`X` and :math:`Z` to other Paulis. +# +# An experiment +# ^^^^^^^^^^^^^ +# The whole idea of unitary designs may sound too good to be true. Can we +# *really* compute the exact average fidelity using just 24 operations? In this +# section, we put them to the test: we'll compute the average fidelity of an +# operation first with experiments using a large but finite amount of Haar-random +# unitaries, and then again with only the Clifford group. + +import pennylane as qml + +# Scipy allows us to sample Haar-random unitaries directly +from scipy.stats import unitary_group + +# set the random seed +np.random.seed(42) + +# Use the mixed state simulator +dev = qml.device("default.mixed", wires=1) + +###################################################################### +# Let's set up a noisy quantum channel. To keep things simple, assume it +# consists of applying :class:`~.pennylane.SX`, the square-root of +# :math:`X` gate, followed by a few different types of noise. First, write a +# quantum function for our ideal experiment: + +def ideal_experiment(): + qml.SX(wires=0) + return qml.state() + +###################################################################### +# Next, we apply some noise. We do so by making use of a relatively new feature +# in PennyLane called `quantum function transforms `__. Such transforms work by +# modifying the underlying, low-level quantum tapes which queue the quantum +# operations. Suppose the noisy channel is composed of the following: + +def noisy_operations(damp_factor, depo_factor, flip_prob): + qml.AmplitudeDamping(damp_factor, wires=0) + qml.DepolarizingChannel(depo_factor, wires=0) + qml.BitFlip(flip_prob, wires=0) + + +###################################################################### +# Let's create a transform that applies this noise to any quantum function +# *after* the original operations, but before the measurements. We use the +# convenient :func:`~.pennylane.transform` decorator: + +@qml.transform +def apply_noise(tape, damp_factor, depo_factor, flip_prob): + # Capture the operations from the noisy sequence + noisy_tape = qml.tape.make_qscript(noisy_operations)(damp_factor, depo_factor, flip_prob) + + # Apply the original operations, then the noisy ones + noisy_ops = tape.operations + noisy_tape.operations + + def null_postprocessing_fn(results): + return results[0] + + # Apply the original measurements + return (type(tape)(noisy_ops, tape.measurements, shots=tape.shots),), null_postprocessing_fn + +###################################################################### +# We can now apply this transform to create a noisy version of our ideal +# quantum function: + +# The strengths of various types of noise +damp_factor = 0.02 +depo_factor = 0.02 +flip_prob = 0.01 + +noisy_experiment = apply_noise(ideal_experiment, damp_factor, depo_factor, flip_prob) + +###################################################################### +# The last part of the experiment involves applying a random unitary matrix +# before all the operations, and its inverse right before the measurements. We +# can write another transform here to streamline this process: + +@qml.transform +def conjugate_with_unitary(tape, matrix): + new_ops = [ + qml.QubitUnitary(matrix, wires=0), + *tape.operations, + qml.QubitUnitary(matrix.conj().T, wires=0), + ] + + def null_postprocessing_fn(results): + return results[0] + + return (type(tape)(new_ops, tape.measurements, shots=tape.shots),), null_postprocessing_fn + +###################################################################### +# Finally, in order to perform a comparison, we need a function to compute the +# `fidelity `__ +# compared to the ideal operation. + +from scipy.linalg import sqrtm + +def fidelity(rho, sigma): + # Inputs rho and sigma are density matrices + sqrt_sigma = sqrtm(sigma) + fid = np.trace(sqrtm(sqrt_sigma @ rho @ sqrt_sigma)) + return fid.real + +###################################################################### +# Let's now compute the average fidelity, averaging over 50000 Haar-random unitaries: + +n_samples = 50000 + +fidelities = [] + +for _ in range(n_samples): + # Select a Haar-random unitary + U = unitary_group.rvs(2) + + # Apply transform to construct the ideal and noisy quantum functions + conjugated_ideal_experiment = conjugate_with_unitary(ideal_experiment, U) + conjugated_noisy_experiment = conjugate_with_unitary(noisy_experiment, U) + + # Use the functions to create QNodes + ideal_qnode = qml.QNode(conjugated_ideal_experiment, dev) + noisy_qnode = qml.QNode(conjugated_noisy_experiment, dev) + + # Execute the QNodes + ideal_state = ideal_qnode() + noisy_state = noisy_qnode() + + # Compute the fidelity + fidelities.append(fidelity(ideal_state, noisy_state)) + +fid_mean = np.mean(fidelities) +print(f"Mean fidelity = {fid_mean}") + +###################################################################### +# Now let's repeat the procedure using only Clifford group elements. First, we +# write a quantum function that performs a Clifford operation (or its inverse) +# based on its string representation. + +def apply_single_clifford(clifford_string, inverse=False): + for gate in clifford_string: + if gate == 'H': + qml.Hadamard(wires=0) + else: + sign = -1 if inverse else 1 + qml.PhaseShift(sign * np.pi/2, wires=0) + +###################################################################### +# Next, we write a transform that applies a Clifford in the context of the full +# experiment, i.e., apply the Clifford, then the operations, followed by the +# inverse of the Clifford. + +@qml.transform +def conjugate_with_clifford(tape, clifford_string): + make_single_clifford = qml.tape.make_qscript(apply_single_clifford) + non_inv_tape = make_single_clifford(clifford_string, inverse=False) + inv_tape = make_single_clifford(clifford_string, inverse=True) + + new_ops = non_inv_tape.operations + tape.operations + inv_tape.operations + + def null_postprocessing_fn(results): + return results[0] + + return (type(tape)(new_ops, tape.measurements, shots=tape.shots),), null_postprocessing_fn + +###################################################################### +# You may have noticed this transform has exactly the same form as +# ``conjugate_with_unitary`` from above. Only the input type has changed, since +# the application of Cliffords here is specified by their string representation. +# +# It's now time to run the experiments: + +fidelities = [] + +for C in single_qubit_cliffords: + conjugated_ideal_experiment = conjugate_with_clifford(ideal_experiment, C) + conjugated_noisy_experiment = conjugate_with_clifford(noisy_experiment, C) + + ideal_qnode = qml.QNode(conjugated_ideal_experiment, dev) + noisy_qnode = qml.QNode(conjugated_noisy_experiment, dev) + + ideal_state = ideal_qnode() + noisy_state = noisy_qnode() + + fidelities.append(fidelity(ideal_state, noisy_state)) + +###################################################################### +# Let's see how our results compare to the earlier simulation: + +clifford_fid_mean = np.mean(fidelities) + +print(f"Haar-random mean fidelity = {fid_mean}") +print(f"Clifford mean fidelity = {clifford_fid_mean}") + +###################################################################### +# Incredible 🤯 🤯 🤯 We were able to compute the average fidelity using only +# 24 experiments. Furthermore, the mean fidelity obtained from the Clifford +# experiments is **exact**; even with 50000 Haar-random experiments, we see +# deviations starting a few decimal places in. Consider the resources that would +# be saved if you were actually implementing this in a lab! It's not hard to see +# why the Clifford group plays such an important role in characterization +# procedures. +# +# Conclusion +# ---------- +# +# In this demo, we've barely scratched the surface of designs and their +# applications in quantum computing. While benchmarking is a key application +# area, there are many others. The Pauli group as a unitary 1-design has +# applications in the construction of private quantum channels [#PQC]_. +# *Approximate* unitary :math:`t`-designs (where the equality in the definition +# is replaced by approximately equal up to some finite precision) are also of +# interest, as there ways to construct them that are more efficient than those +# of exact designs [#Dankert]_. In particular, it has been shown that +# approximate complex projective 4-designs have applications to the state +# discrimination problem [#Ambainis]_. +# +# Furthermore, unitary designs are not the only designs that you'll encounter +# in quantum computing. The familiar Hadamard gate is just a 2-dimensional +# example of a broader family of *Hadamard designs*, on which there has been +# extensive research [#Seberry]_. Some sets of `mutually orthogonal Latin +# squares `__ +# have a direct correspondence with mutually unbiased bases, which are optimal +# quantum measurements [#Gaeta]_, as well as complex projective designs; and Latin +# squares themselves have direct correspondence with affine and projective +# planes, bringing us full circle back to the Fano plane from which we began. +# +# +# .. figure:: /_static/demonstration_assets/unitary_designs/affine-latin.svg +# :align: center +# :width: 80% +# +# An affine plane, Hadamard matrix, and a depiction of mutually orthogonal Latin squares. +# +# References +# ---------- +# +# .. [#Handbook] +# +# C. J. Colbourn and J. H. Dinitz (2006) *Handbook of Combinatorial Designs, +# Second Edition*. Chapman & Hall/CRC. +# +# .. [#Delsarte] +# +# P. Delsarte, J.M. Goethals, J.J. Seidel (1977) *Spherical Codes and Designs*. Geometriae +# Dedicata 6 363-388. +# +# .. [#sph4design] +# +# R. H. Hardin and N. J. A. Sloane (1992) *New spherical 4-designs*. Discrete +# Mathematics, 106-107 (255-264). `(PDF) +# `__. +# +# .. [#Ambainis] +# +# A. Ambainis and J. Emerson (2007) *Quantum t-designs: t-wise independence +# in the quantum world.* Twenty-Second Annual IEEE Conference on +# Computational Complexity 129-140. +# `(arXiv) `__. +# +# .. [#Klappenecker] +# +# A. Klappenecker and M. Roetteler (2005) *Mutually unbiased bases, spherical +# designs, and frames.* Proceedings of SPIE Vol. 5914. +# +# .. [#Dankert] +# +# C. Dankert, R. Cleve, J. Emerson, and E. Levine (2009) *Exact and +# Approximate Unitary 2-Designs: Constructions and Applications.* Phys. Rev. A 80, 012304. +# `(arXiv) `__. +# +# .. [#DankertThesis] +# +# C. Dankert (2005) *Efficient Simulation of Random Quantum States and +# Operators.* MSc Thesis, University of Waterloo. `(arXiv) +# `__. +# +# .. [#Gross] +# +# D. Gross, K. Audenaert, and J. Eisert (2007) *Evenly distributed unitaries: +# on the structure of unitary designs*. J. Math. Phys. 48, 052104. +# `(arXiv) `__. +# +# .. [#Roy] +# +# A. Roy and A. J. Scott (2009) *Unitary designs and codes*. Des. Codes Cryptogr. 53 13-31. +# `(arXiv) `__. +# +# .. [#Bannai] +# +# E. Bannai, M. Nakahara, D. Zhao, and Y. Zhu (2019) *On the explicit constructions of +# certain unitary t-designs.* J. Phys. A: Math. Theor. 52 495301. +# `(arXiv) `__. +# +# .. [#Nakata] +# +# Y. Nakata et al. (2021) *Quantum circuits for exact unitary t-designs and +# applications to higher-order randomized benchmarking.* `(arXiv) +# `__. +# +# .. [#PQC] +# +# A. Ambainis, M. Mosca, A. Tapp, and R. de Wolf (2000) *Private Quantum Channels*. Proc. +# 41st FOCS, 547-553. `(PDF) `__. +# +# .. [#Seberry] +# +# J. Seberry and M. Yamada (1992) *Hadamard matrices, sequences, and block +# designs.* Contemporary Design Theory – A Collection of Surveys +# (D. J. Stinson and J. Dinitz, Eds.), John Wiley and Sons, 431-560. +# `(PDF) `__. +# +# .. [#Gaeta] +# +# M. Gaeta, O. Di Matteo, A. B. Klimov, and H. de Guise (2014) *Discrete phase-space +# approach to mutually orthogonal Latin squares*. J. Phys. A: Math. Theor. 47 (43) 435303. +# `(arXiv) `__. +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_unitary_designs/metadata.json b/demonstrations_v2/tutorial_unitary_designs/metadata.json new file mode 100644 index 0000000000..557e4a1091 --- /dev/null +++ b/demonstrations_v2/tutorial_unitary_designs/metadata.json @@ -0,0 +1,161 @@ +{ + "title": "Unitary designs", + "authors": [ + { + "username": "glassnotes" + } + ], + "dateOfPublication": "2021-09-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_unitary_designs.png" + } + ], + "seoDescription": "Learn about designs and their uses in quantum computing.", + "doi": "", + "references": [ + { + "id": "Handbook", + "type": "book", + "title": "Handbook of Combinatorial Designs, Second Edition", + "authors": "C. J. Colbourn and J. H. Dinitz", + "year": "2006", + "publisher": "Chapman & Hall/CRC", + "url": "" + }, + { + "id": "Delsarte", + "type": "article", + "title": "Spherical Codes and Designs", + "authors": "P. Delsarte, J.M. Goethals, J.J. Seidel", + "year": "1977", + "journal": "Geometriae Dedicata", + "url": "" + }, + { + "id": "sph4design", + "type": "article", + "title": "New spherical 4-designs", + "authors": "R. H. Hardin and N. J. A. Sloane", + "year": "1992", + "journal": "Discrete Mathematics", + "url": "https://www.sciencedirect.com/science/article/pii/0012365X9290552Q" + }, + { + "id": "Ambainis", + "type": "article", + "title": "Quantum t-designs: t-wise independence in the quantum world.", + "authors": "A. Ambainis and J. Emerson", + "year": "2007", + "journal": "Twenty-Second Annual IEEE Conference on Computational Complexity", + "url": "https://arxiv.org/abs/quant-ph/0701126" + }, + { + "id": "Klappenecker", + "type": "article", + "title": "Mutually unbiased bases, spherical designs, and frames.", + "authors": "A. Klappenecker and M. Roetteler", + "year": "2005", + "journal": "Proceedings of SPIE", + "volume": "5914", + "url": "" + }, + { + "id": "Dankert", + "type": "article", + "title": "Exact and Approximate Unitary 2-Designs: Constructions and Applications.", + "authors": "C. Dankert, R. Cleve, J. Emerson, and E. Levine", + "year": "2009", + "journal": "Phys. Rev. A", + "url": "https://arxiv.org/abs/quant-ph/0606161" + }, + { + "id": "DankertThesis", + "type": "article", + "title": "Efficient Simulation of Random Quantum States and Operators.", + "authors": "C. Dankert", + "year": "2005", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0512217" + }, + { + "id": "Gross", + "type": "article", + "title": "Evenly distributed unitaries: on the structure of unitary designs", + "authors": "D. Gross, K. Audenaert, and J. Eisert", + "year": "2007", + "journal": "J. Math. Phys.", + "url": "https://arxiv.org/abs/quant-ph/0611002" + }, + { + "id": "Roy", + "type": "article", + "title": "Unitary designs and codes", + "authors": "A. Roy and A. J. Scott", + "year": "2009", + "journal": "Des. Codes Cryptogr.", + "url": "https://arxiv.org/abs/0809.3813" + }, + { + "id": "Bannai", + "type": "article", + "title": "On the explicit constructions of certain unitary t-designs.", + "authors": "E. Bannai, M. Nakahara, D. Zhao, and Y. Zhu", + "year": "2019", + "journal": "J. Phys. A: Math. Theor.", + "url": "https://arxiv.org/abs/1906.04583" + }, + { + "id": "Nakata", + "type": "article", + "title": "Quantum circuits for exact unitary t-designs and applications to higher-order randomized benchmarking.", + "authors": "Y. Nakata et al.", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2102.12617" + }, + { + "id": "PQC", + "type": "article", + "title": "Private Quantum Channels", + "authors": "A. Ambainis, M. Mosca, A. Tapp, and R. de Wolf", + "year": "2000", + "journal": "Proc. 41st FOCS", + "url": "https://homepages.cwi.nl/~rdewolf/publ/qc/AMTW00.pdf" + }, + { + "id": "Seberry", + "type": "article", + "title": "Hadamard matrices, sequences, and block designs.", + "authors": "J. Seberry and M. Yamada", + "year": "1992", + "publisher": "John Wiley and Sons", + "url": "http://mathscinet.ru/files/YamadaSeberry1992.pdf" + }, + { + "id": "Gaeta", + "type": "article", + "title": "Discrete phase-space approach to mutually orthogonal Latin squares", + "authors": "M. Gaeta, O. Di Matteo, A. B. Klimov, and H. de Guise", + "year": "2014", + "journal": "J. Phys. A: Math. Theor.", + "url": "https://arxiv.org/abs/1408.6742" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_haar_measure", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_unitary_designs/requirements.in b/demonstrations_v2/tutorial_unitary_designs/requirements.in new file mode 100644 index 0000000000..17c51f9a9f --- /dev/null +++ b/demonstrations_v2/tutorial_unitary_designs/requirements.in @@ -0,0 +1,3 @@ +numpy +pennylane +scipy diff --git a/demonstrations_v2/tutorial_univariate_qvr/demo.py b/demonstrations_v2/tutorial_univariate_qvr/demo.py new file mode 100644 index 0000000000..117a1a2d56 --- /dev/null +++ b/demonstrations_v2/tutorial_univariate_qvr/demo.py @@ -0,0 +1,1266 @@ +""" +Quantum detection of time series anomalies +========================================== + +.. meta:: + :property="og:description": Learn how to quantumly detect anomalous behaviour in time series data with the help of Covalent. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_univariate_qvr.jpg + +.. related:: + tutorial_qaoa_intro Intro to QAOA + +*Authors: Jack Stephen Baker, Santosh Kumar Radha — Posted: 7 February 2023.* + +Systems producing observable characteristics which evolve with time are +almost everywhere we look. The temperature changes as day turns to night, +stock markets fluctuate and the bacteria colony living in the coffee cup to your +right, which you *promised* you would clean yesterday, is slowly growing +(seriously, clean it). In many situations, it is important to know when these +systems start behaving abnormally. For example, if the pressure +inside a nuclear fission reactor starts violently fluctuating, you may wish +to be alerted of that. The task of identifying such temporally abnormal +behaviour is known as time series anomaly detection and is well known in machine +learning circles. + +In this tutorial, we take a stab at time series anomaly detection using the *Quantum Variational +Rewinding* algorithm, or QVR, proposed by `Baker, Horowitz, Radha et. +al (2022) `__ [#Baker2022]_ — a quantum machine learning algorithm for gate model quantum computers. QVR leverages the power of unitary time evolution/devolution operators to +learn a model of *normal* behaviour for time series data. Given a new (i.e., unseen in training) time +series, the normal model produces a value that, beyond a threshold, +defines anomalous behaviour. In this tutorial, we’ll be showing you how +all of this works, combining elements from +`Covalent `__, +`Pennylane `__ and +`PyTorch `__. + +Before getting into the technical details of the algorithm, let's get a high-level overview with the help of the cartoon below. +""" +###################################################################### +# .. figure:: ../_static/demonstration_assets/univariate_qvr/cartoon_pennylane.png +# :width: 70% +# :align: center +# :alt: QVR cartoon +# +# +# +# Going left-to-right, a time series is sampled at three points in time, corresponding to +# different stages in the life cycle of a butterfly: a catepillar, a chrysalis and a butterfly. +# This information is then encoded into quantum states and passed to a +# time machine which time devolves the states as generated by a learnt +# Hamiltonian operator (in practice, there is a distribution of such operators). +# After the devolved state is measured, the time series is recognized as +# normal if the average measurement is smaller than a given threshold and anomalous if the +# threshold is exceeded. In the first case, the time series is considered +# rewindable, correctly recovering the initial condition for the life cycle +# of a butterfly: eggs on a leaf. In the second case, the output is unrecognizable. +# +# This will all make more sense once we delve into the math a little. Let's do it! + +###################################################################### +# Background +# ---------- +# +# To begin, let’s quickly recount the data that QVR handles: time series. +# A general time series :math:`\boldsymbol{y}` can be described as a sequence of +# :math:`p`-many observations of a process/system arranged in +# chronological order, where :math:`p` is a positive integer: +# +# .. math:: +# +# +# \boldsymbol{y} := (\boldsymbol{y}_t: t \in T), \quad T := (t_l: l \in \mathbb{Z}^{+}_{\leq p}). +# +# In the simple and didactic case treated in this tutorial, :math:`\boldsymbol{y}` is univariate (i.e, is a +# one-dimensional time series), so bold-face for :math:`\boldsymbol{y}` is +# dropped from this point onwards. Also, we take :math:`y_t \in \mathbb{R}` and +# :math:`t_l \in \mathbb{R}_{>0}.` +# +# The goal of QVR and many other (classical) machine learning algorithms +# for time series anomaly detection is to determine a suitable *anomaly +# score* function :math:`a_{X},` where :math:`X` is a training dataset of +# *normal* time series instances :math:`x \in X` (:math:`x` is defined +# analogously to :math:`y` in the above), from which the anomaly score function +# was learnt. When passed a general time series +# :math:`y,` this function produces a real number: +# :math:`a_X(y) \in \mathbb{R}.` The goal is to have +# :math:`a_X(x) \approx 0,` for all :math:`x \in X.` Then, for an unseen time +# series :math:`y` and a threshold :math:`\zeta \in \mathbb{R},` the series is said to be anomalous should +# :math:`a_X(y) > \zeta,` and normal otherwise. We show a strategy for setting +# :math:`\zeta` later in this tutorial. +# +# The first step for doing all of this *quantumly* is to generate a +# sequence :math:`\mathcal{S} := (|x_{t} \rangle: t \in T)` of +# :math:`n`-qubit quantum states corresponding to a classical time series +# instance in the training set. Now, we suppose that each +# :math:`|x_t \rangle` is a quantum state evolved to a time :math:`t,` as +# generated by an *unknown embedding Hamiltonian* :math:`H_E.` That is, +# each element of :math:`\mathcal{S}` is defined by +# :math:`|x_t \rangle = e^{-iH_E(x_t)}|0\rangle^{\otimes n} = U(x_t)|0\rangle^{\otimes n}` +# for an embedding unitary operator :math:`U(x_t)` implementing a quantum +# feature map (see the `Pennylane embedding +# templates `__ +# for efficient quantum circuits for doing so). Next, we operate on each +# :math:`|x_t\rangle` with a parameterized +# :math:`e^{-iH(\boldsymbol{\alpha}, \boldsymbol{\gamma})t}` operator to +# prepare the states +# +# .. math:: +# +# +# |x_t, \boldsymbol{\alpha}, \boldsymbol{\gamma}\rangle := e^{-iH(\boldsymbol{\alpha}, \boldsymbol{\gamma})t}|x_t\rangle, +# +# where we write +# :math:`e^{-iH(\boldsymbol{\alpha}, \boldsymbol{\gamma})t}` as an +# eigendecomposition +# +# .. math:: +# +# +# V_t(\boldsymbol{\alpha}, \boldsymbol{\gamma}) := W^{\dagger}(\boldsymbol{\alpha})D(\boldsymbol{\gamma}, t)W(\boldsymbol{\alpha}) = e^{-iH(\boldsymbol{\alpha}, \boldsymbol{\gamma})t}. +# +# Here, the unitary matrix of eigenvectors :math:`W(\boldsymbol{\alpha})` is parametrized by :math:`\boldsymbol{\alpha}` and the unitary diagonalization :math:`D(\boldsymbol{\gamma}, t)` +# is parametrized by :math:`\boldsymbol{\gamma}.` +# Both can be implemented +# efficiently using parameterized quantum circuits. The above equality +# with :math:`e^{-iH(\boldsymbol{\alpha}, \boldsymbol{\gamma})t}` is a +# consequence of Stone’s theorem for strongly continuous one-parameter +# unitary groups [#Stone1932]_. +# +# We now ask the question: *What condition is required for* +# :math:`|x_t, \boldsymbol{\alpha}, \boldsymbol{\gamma} \rangle = |0 \rangle^{\otimes n}` *for all time?* +# To answer this, we impose +# :math:`P(|0\rangle^{\otimes n}) = |\langle 0|^{\otimes n}|x_t, \boldsymbol{\alpha}, \boldsymbol{\gamma} \rangle|^2 = 1.` +# Playing with the algebra a little, we find that the following condition must be satisfied for all :math:`t:` +# +# .. math:: +# +# +# \langle 0|^{\otimes n}e^{-iH(\boldsymbol{\alpha}, \boldsymbol{\gamma})t}e^{-iH_E(x_t)}|0\rangle^{\otimes n} = 1 \iff H(\boldsymbol{\alpha}, \boldsymbol{\gamma})t = -H_E(x_t). +# +# In other words, for the above to be true, the parameterized unitary +# operator :math:`V_t(\boldsymbol{\alpha}, \boldsymbol{\gamma})` should be +# able to reverse or *rewind* :math:`|x_t\rangle` to its initial +# state :math:`|0\rangle^{\otimes n}` before the embedding unitary operator :math:`U(x_t)` was +# applied. +# +# We are nearly there! Because it is reasonable to expect that a single +# Hamiltonian will not be able to successfully rewind every +# :math:`x \in X` (in fact, this is impossible to do if each +# :math:`x` is unique, which is usually true), we consider the average effect of many +# Hamiltonians generated by drawing :math:`\boldsymbol{\gamma}` from a +# normal distribution :math:`\mathcal{N}(\mu, \sigma)` with mean :math:`\mu` and +# standard deviation :math:`\sigma:` +# +# .. math:: +# +# +# F(\boldsymbol{\phi}, x_t) := \mathop{\mathbb{E}_{\boldsymbol{\gamma} \sim \mathcal{N}(\mu, \sigma)}}\left[\langle 0|^{\otimes n} |x_t, \boldsymbol{\alpha}, \boldsymbol{\gamma}\rangle \right], \quad \boldsymbol{\phi} = [\boldsymbol{\alpha}, \mu, \sigma]. +# +# The goal is for the function :math:`F` defined above to be as close to :math:`1` as possible, +# for all :math:`x \in X` and :math:`t \in T.` With this in mind, we can +# define the loss function to minimize as the mean square error +# regularized by a penalty function :math:`P_{\tau}(\sigma)` with a single +# hyperparameter :math:`\tau:` +# +# .. math:: +# +# +# \mathcal{L(\boldsymbol{\phi})} = \frac{1}{2|X||T|}\sum_{x \in X} \sum_{t \in T}[1 - F(\boldsymbol{\phi}, x_t)]^2 + P_{\tau}(\sigma). +# +# We will show the exact form of :math:`P_{\tau}(\sigma)` later. +# The general purpose of the penalty function is to penalize +# large values of :math:`\sigma` (justification for this is given in the +# Supplement of [#Baker2022]_). After approximately finding the argument :math:`\boldsymbol{\phi}^{\star}` +# that minimizes the loss function +# (found using a classical optimization routine), we finally arrive at a definition +# for our anomaly score function :math:`a_X(y)` +# +# .. math:: +# +# +# a_X(y) = \frac{1}{|T|}\sum_{t \in T}[1 - F(\boldsymbol{\phi}^{\star}, y_t)]^2. +# +# It may now be apparent that we have implemented a clustering algorithm! +# That is, our model :math:`F` was trained such that normal time series +# :math:`x \in X` produce :math:`F(\boldsymbol{\phi}^{\star}, x_t)` +# clustered about a center at :math:`1.` Given a new time series +# :math:`y,` should :math:`F(\boldsymbol{\phi}^{\star}, y_t)` venture far +# from the normal center at :math:`1,` we are observing anomalous behaviour! +# +# Take the time now to have another look at the cartoon at the start of this tutorial. +# Hopefully things should start making sense now. +# +# Now with our algorithm defined, let’s stitch this all together: enter +# `Covalent `__. +# + + +###################################################################### +# Covalent: heterogeneous workflow orchestration +# ---------------------------------------------- +# +# Presently, many QML algorithms are *heterogeneous* in nature. This means that +# they require computational resources from both classical and quantum computing. +# Covalent is a tool that can be used +# to manage their interaction by sending different tasks to different +# computational resources and stitching them together as a workflow. While you +# will be introduced to other concepts in Covalent throughout this +# tutorial, we define two key components to begin with. +# +# 1. **Electrons**. Decorate regular Python functions with +# ``@ct.electron`` to desginate a *task*. These are the atoms of +# a computation. +# +# 2. **Lattices**. Decorate a regular Python function with ``@ct.lattice`` +# to designate a *workflow*. These contain electrons stitched together to do something useful. +# +# Different electrons can be run remotely on +# different hardware and multiple computational paridigms (classical, quantum, etc.: see the `Covalent executors `__). +# In this tutorial, however, to keep things simple, tasks are run on a local +# Dask cluster, which provides (among other things) auto-parallelization. +# +# .. figure:: ../_static/demonstration_assets/univariate_qvr/covalent_platform.png +# :align: center +# :width: 70% +# :alt: The Covalent platform +# +# | +# A schematic demonstrating the different platforms Covalent can interact with. +# +# Now is a good time to import Covalent and launch the Covalent server! +# + +import covalent as ct +import os +import time + +# Set up Covalent server +os.environ["COVALENT_SERVER_IFACE_ANY"] = "1" +os.system("covalent start") +# If you run into any out-of-memory issues with Dask when running this notebook, +# Try reducing the number of workers and making a specific memory request. I.e.: +# os.system("covalent start -m "2GiB" -n 2") +# try covalent –help for more info +time.sleep(2) # give the Dask cluster some time to launch + + +###################################################################### +# Generating univariate synthetic time series +# ------------------------------------------- +# +# In this tutorial, we shall deal with a simple and didactic example. +# Normal time series instances are chosen to be noisy low-amplitude +# signals normally distributed about the origin. In our case, :math:`x_t \sim \mathcal{N}(0, 0.1).` +# Series we deem to be anomalous are the same but with randomly inserted +# spikes with random durations and amplitudes. +# +# Let’s make a ``@ct.electron`` to generate each of these synthetic time +# series sets. For this, we'll need to import Torch. We'll also +# set the default tensor type and pick a random seed for the whole tutorial +# for reproducibility. +# + +import torch + +# Seed Torch for reproducibility and set default tensor type +GLOBAL_SEED = 1989 +torch.manual_seed(GLOBAL_SEED) +torch.set_default_tensor_type(torch.DoubleTensor) + + +@ct.electron +def generate_normal_time_series_set( + p: int, num_series: int, noise_amp: float, t_init: float, t_end: float, seed: int = GLOBAL_SEED +) -> tuple: + """Generate a normal time series data set where each of the p elements + is drawn from a normal distribution x_t ~ N(0, noise_amp). + """ + torch.manual_seed(seed) + X = torch.normal(0, noise_amp, (num_series, p)) + T = torch.linspace(t_init, t_end, p) + return X, T + + +@ct.electron +def generate_anomalous_time_series_set( + p: int, + num_series: int, + noise_amp: float, + spike_amp: float, + max_duration: int, + t_init: float, + t_end: float, + seed: int = GLOBAL_SEED, +) -> tuple: + """Generate an anomalous time series data set where the p elements of each sequence are + from a normal distribution x_t ~ N(0, noise_amp). Then, + anomalous spikes of random amplitudes and durations are inserted. + """ + torch.manual_seed(seed) + Y = torch.normal(0, noise_amp, (num_series, p)) + for y in Y: + # 5–10 spikes allowed + spike_num = torch.randint(low=5, high=10, size=()) + durations = torch.randint(low=1, high=max_duration, size=(spike_num,)) + spike_start_idxs = torch.randperm(p - max_duration)[:spike_num] + for start_idx, duration in zip(spike_start_idxs, durations): + y[start_idx : start_idx + duration] += torch.normal(0.0, spike_amp, (duration,)) + T = torch.linspace(t_init, t_end, p) + return Y, T + + +###################################################################### +# Let's do a quick sanity check and plot a couple of these series. Despite the +# above function's ``@ct.electron`` decorators, these can still be used as normal +# Python functions without using the Covalent server. This is useful +# for quick checks like this: +# + +import matplotlib.pyplot as plt + +X_norm, T_norm = generate_normal_time_series_set(25, 25, 0.1, 0.1, 2 * torch.pi) +Y_anom, T_anom = generate_anomalous_time_series_set(25, 25, 0.1, 0.4, 5, 0, 2 * torch.pi) + +plt.figure() +plt.plot(T_norm, X_norm[0], label="Normal") +plt.plot(T_anom, Y_anom[1], label="Anomalous") +plt.ylabel("$y(t)$") +plt.xlabel("t") +plt.grid() +leg = plt.legend() + +###################################################################### +# Taking a look at the above, the generated series are what we wanted. We have +# a simple human-parsable notion of what it is for a time series to be anomalous +# (big spikes). Of course, we don't need a complicated algorithm to be able to detect +# such anomalies but this is just a didactic example remember! +# +# Like many machine learning algorithms, training is done in mini-batches. +# Examining the form of the loss function +# :math:`\mathcal{L}(\boldsymbol{\phi}),` we can see that time series are +# atomized. In other words, each term in the mean square error is for a given +# :math:`x_t` and not measured against the entire series :math:`x.` This +# allows us to break down the training set :math:`X` into +# time-series-independent chunks. Here’s an electron to do that: +# + + +@ct.electron +def make_atomized_training_set(X: torch.Tensor, T: torch.Tensor) -> list: + """Convert input time series data provided in a two-dimensional tensor format + to atomized tuple chunks: (xt, t). + """ + X_flat = torch.flatten(X) + T_flat = T.repeat(X.size()[0]) + atomized = [(xt, t) for xt, t in zip(X_flat, T_flat)] + return atomized + + +###################################################################### +# We now wish to pass this to a cycled ``torch.utils.data.DataLoader``. +# However, this object is not +# `pickleable `__, +# which is a requirement of electrons in Covalent. We therefore use the +# below helper class to create a pickleable version. +# + +from collections.abc import Iterator + + +class DataGetter: + """A pickleable mock-up of a Python iterator on a torch.utils.Dataloader. + Provide a dataset X and the resulting object O will allow you to use next(O). + """ + + def __init__(self, X: torch.Tensor, batch_size: int, seed: int = GLOBAL_SEED) -> None: + """Calls the _init_data method on intialization of a DataGetter object.""" + torch.manual_seed(seed) + self.X = X + self.batch_size = batch_size + self.data = [] + self._init_data( + iter(torch.utils.data.DataLoader(self.X, batch_size=self.batch_size, shuffle=True)) + ) + + def _init_data(self, iterator: Iterator) -> None: + """Load all of the iterator into a list.""" + x = next(iterator, None) + while x is not None: + self.data.append(x) + x = next(iterator, None) + + def __next__(self) -> tuple: + """Analogous behaviour to the native Python next() but calling the + .pop() of the data attribute. + """ + try: + return self.data.pop() + except IndexError: # Caught when the data set runs out of elements + self._init_data( + iter(torch.utils.data.DataLoader(self.X, batch_size=self.batch_size, shuffle=True)) + ) + return self.data.pop() + + +###################################################################### +# We call an instance of the above in an electron +# + + +@ct.electron +def get_training_cycler(Xtr: torch.Tensor, batch_size: int, seed: int = GLOBAL_SEED) -> DataGetter: + """Get an instance of the DataGetter class defined above, which behaves analogously to + next(iterator) but is pickleable. + """ + return DataGetter(Xtr, batch_size, seed) + + +###################################################################### +# We now have the means to create synthetic data and cycle through a +# training set. Next, we need to build our loss function +# :math:`\mathcal{L}(\boldsymbol{\phi})` from electrons with the help of +# ``PennyLane``. +# + + +###################################################################### +# Building the loss function +# -------------------------- +# +# Core to building the loss function is the quantum circuit implementing +# :math:`V_t(\boldsymbol{\alpha}, \boldsymbol{\gamma}) := W^{\dagger}(\boldsymbol{\alpha})D(\boldsymbol{\gamma}, t)W(\boldsymbol{\alpha}).` +# While there are existing templates in ``PennyLane`` for implementing +# :math:`W(\boldsymbol{\alpha}),` we use a custom circuit to implement +# :math:`D(\boldsymbol{\gamma}, t).` Following the approach taken in +# [#Welch2014]_ (also explained in [#Baker2022]_ and the +# appendix of [#Cîrstoiu2020]_), we create the electron: +# + +import pennylane as qml +from itertools import combinations + + +@ct.electron +def D(gamma: torch.Tensor, n_qubits: int, k: int = None, get_probs: bool = False) -> None: + """Generates an n_qubit quantum circuit according to a k-local Walsh operator + expansion. Here, k-local means that 1 <= k <= n of the n qubits can interact. + See for more + details. Optionally return probabilities of bit strings. + """ + if k is None: + k = n_qubits + cnt = 0 + for i in range(1, k + 1): + for comb in combinations(range(n_qubits), i): + if len(comb) == 1: + qml.RZ(gamma[cnt], wires=[comb[0]]) + cnt += 1 + elif len(comb) > 1: + cnots = [comb[i : i + 2] for i in range(len(comb) - 1)] + for j in cnots: + qml.CNOT(wires=j) + qml.RZ(gamma[cnt], wires=[comb[-1]]) + cnt += 1 + for j in cnots[::-1]: + qml.CNOT(wires=j) + if get_probs: + return qml.probs(wires=range(n_qubits)) + + +###################################################################### +# While the above may seem a little complicated, since we only use a single +# qubit in this tutorial, the resulting circuit is merely a single :math:`R_z(\theta)` gate. + +n_qubits = 1 +dev = qml.device("default.qubit", wires=n_qubits, shots=None) +D_one_qubit = qml.qnode(dev)(D) +_ = qml.draw_mpl(D_one_qubit, decimals=2)(torch.tensor([1, 0]), 1, 1, True) + +###################################################################### +# You may find the general function for :math:`D`` useful in case you want to experiment +# with more qubits and your own (possibly multi-dimensional) data after +# this tutorial. +# +# Next, we define a circuit to calculate the probability of certain bit strings being measured in the +# computational basis. In our simple example, we work only with one qubit +# and use the ``default.qubit`` local quantum circuit simulator. +# + + +@ct.electron +@qml.qnode(dev, interface="torch", diff_method="backprop") +def get_probs( + xt: torch.Tensor, + t: float, + alpha: torch.Tensor, + gamma: torch.Tensor, + k: int, + U: callable, + W: callable, + D: callable, + n_qubits: int, +) -> torch.Tensor: + """Measure the probabilities for measuring each bitstring after applying a + circuit of the form W†DWU to the |0⟩^(⊗n) state. This + function is defined for individual sequence elements xt. + """ + U(xt, wires=range(n_qubits)) + W(alpha, wires=range(n_qubits)) + D(gamma * t, n_qubits, k) + qml.adjoint(W)(alpha, wires=range(n_qubits)) + return qml.probs(range(n_qubits)) + + +###################################################################### +# To take the projector +# :math:`|0\rangle^{\otimes n} \langle 0 |^{\otimes n},` we consider only +# the probability of measuring the bit string of all zeroes, which is the +# 0th element of the probabilities (bit strings are returned in +# lexicographic order). +# + + +@ct.electron +def get_callable_projector_func( + k: int, U: callable, W: callable, D: callable, n_qubits: int, probs_func: callable +) -> callable: + """Using get_probs() above, take only the probability of measuring the + bitstring of all zeroes (i.e, take the projector + |0⟩^(⊗n)⟨0|^(⊗n)) on the time devolved state. + """ + callable_proj = lambda xt, t, alpha, gamma: probs_func( + xt, t, alpha, gamma, k, U, W, D, n_qubits + )[0] + return callable_proj + + +###################################################################### +# We now have the necessary ingredients to build +# :math:`F(\boldsymbol{\phi}, x_t).` +# + + +@ct.electron +def F( + callable_proj: callable, + xt: torch.Tensor, + t: float, + alpha: torch.Tensor, + mu: torch.Tensor, + sigma: torch.Tensor, + gamma_length: int, + n_samples: int, +) -> torch.Tensor: + """Take the classical expecation value of of the projector on zero sampling + the parameters of D from normal distributions. The expecation value is estimated + with an average over n_samples. + """ + # length of gamma should not exceed 2^n - 1 + gammas = sigma.abs() * torch.randn((n_samples, gamma_length)) + mu + expectation = torch.empty(n_samples) + for i, gamma in enumerate(gammas): + expectation[i] = callable_proj(xt, t, alpha, gamma) + return expectation.mean() + + +###################################################################### +# We now return to the matter of the penalty function :math:`P_{\tau}.` +# We choose +# +# .. math:: +# +# +# P_{\tau}(\sigma) := \frac{1}{\pi} \arctan(2 \pi \tau |\sigma|). +# +# As an electron, we have + + +@ct.electron +def callable_arctan_penalty(tau: float) -> callable: + """Create a callable arctan function with a single hyperparameter + tau to penalize large entries of sigma. + """ + prefac = 1 / (torch.pi) + callable_pen = lambda sigma: prefac * torch.arctan(2 * torch.pi * tau * sigma.abs()).mean() + return callable_pen + + +###################################################################### +# The above is a sigmoidal function chosen because it comes with the useful property of being bounded. +# The prefactor of :math:`1/\pi` is chosen such that the final loss +# :math:`\mathcal{L}(\boldsymbol{\phi})` is defined in the range (0, 1), +# as defined in the below electron. +# + + +@ct.electron +def get_loss( + callable_proj: callable, + batch: torch.Tensor, + alpha: torch.Tensor, + mu: torch.Tensor, + sigma: torch.Tensor, + gamma_length: int, + n_samples: int, + callable_penalty: callable, +) -> torch.Tensor: + """Evaluate the loss function ℒ, defined in the background section + for a certain set of parameters. + """ + X_batch, T_batch = batch + loss = torch.empty(X_batch.size()[0]) + for i in range(X_batch.size()[0]): + # unsqueeze required for tensor to have the correct dimension for PennyLane templates + loss[i] = ( + 1 + - F( + callable_proj, + X_batch[i].unsqueeze(0), + T_batch[i].unsqueeze(0), + alpha, + mu, + sigma, + gamma_length, + n_samples, + ) + ).square() + return 0.5 * loss.mean() + callable_penalty(sigma) + + +###################################################################### +# Training the normal model +# ------------------------- +# +# Now equipped with a loss function, we need to minimize it with a +# classical optimization routine. To start this optimization, however, we +# need some initial parameters. We can generate them with the below +# electron. +# + + +@ct.electron +def get_initial_parameters( + W: callable, W_layers: int, n_qubits: int, seed: int = GLOBAL_SEED +) -> dict: + """Randomly generate initial parameters. We need initial parameters for the + variational circuit ansatz implementing W(alpha) and the standard deviation + and mean (sigma and mu) for the normal distribution we sample gamma from. + """ + torch.manual_seed(seed) + init_alpha = torch.rand(W.shape(W_layers, n_qubits)) + init_mu = torch.rand(1) + # Best to start sigma small and expand if needed + init_sigma = torch.rand(1) + init_params = { + "alpha": (2 * torch.pi * init_alpha).clone().detach().requires_grad_(True), + "mu": (2 * torch.pi * init_mu).clone().detach().requires_grad_(True), + "sigma": (0.1 * init_sigma + 0.05).clone().detach().requires_grad_(True), + } + return init_params + + +###################################################################### +# Using the ``PyTorch`` interface to ``PennyLane``, we define our final +# electron before running the training workflow. +# + + +@ct.electron +def train_model_gradients( + lr: float, + init_params: dict, + pytorch_optimizer: callable, + cycler: DataGetter, + n_samples: int, + callable_penalty: callable, + batch_iterations: int, + callable_proj: callable, + gamma_length: int, + seed=GLOBAL_SEED, + print_intermediate=False, +) -> dict: + """Train the QVR model (minimize the loss function) with respect to the + variational parameters using gradient-based training. You need to pass a + PyTorch optimizer and a learning rate (lr). + """ + torch.manual_seed(seed) + opt = pytorch_optimizer(init_params.values(), lr=lr) + alpha = init_params["alpha"] + mu = init_params["mu"] + sigma = init_params["sigma"] + + def closure(): + opt.zero_grad() + loss = get_loss( + callable_proj, next(cycler), alpha, mu, sigma, gamma_length, n_samples, callable_penalty + ) + loss.backward() + return loss + + loss_history = [] + for i in range(batch_iterations): + loss = opt.step(closure) + loss_history.append(loss.item()) + if batch_iterations % 10 == 0 and print_intermediate: + print(f"Iteration number {i}\n Current loss {loss.item()}\n") + + results_dict = { + "opt_params": { + "alpha": opt.param_groups[0]["params"][0], + "mu": opt.param_groups[0]["params"][1], + "sigma": opt.param_groups[0]["params"][2], + }, + "loss_history": loss_history, + } + return results_dict + + +###################################################################### +# Now, enter our first ``@ct.lattice``. This combines the above electrons, +# eventually returning the optimal parameters +# :math:`\boldsymbol{\phi}^{\star}` and the loss with batch iterations. +# + + +@ct.lattice +def training_workflow( + U: callable, + W: callable, + D: callable, + n_qubits: int, + k: int, + probs_func: callable, + W_layers: int, + gamma_length: int, + n_samples: int, + p: int, + num_series: int, + noise_amp: float, + t_init: float, + t_end: float, + batch_size: int, + tau: float, + pytorch_optimizer: callable, + lr: float, + batch_iterations: int, +): + """ + Combine all of the previously defined electrons to do an entire training workflow, + including (1) generating synthetic data, (2) packaging it into training cyclers + (3) preparing the quantum functions and (4) optimizing the loss function with + gradient based optimization. You can find definitions for all of the arguments + by looking at the electrons and text cells above. + """ + + X, T = generate_normal_time_series_set(p, num_series, noise_amp, t_init, t_end) + Xtr = make_atomized_training_set(X, T) + cycler = get_training_cycler(Xtr, batch_size) + init_params = get_initial_parameters(W, W_layers, n_qubits) + callable_penalty = callable_arctan_penalty(tau) + callable_proj = get_callable_projector_func(k, U, W, D, n_qubits, probs_func) + results_dict = train_model_gradients( + lr, + init_params, + pytorch_optimizer, + cycler, + n_samples, + callable_penalty, + batch_iterations, + callable_proj, + gamma_length, + print_intermediate=False, + ) + return results_dict + + +###################################################################### +# Before running this workflow, we define all of the input parameters. +# + +general_options = { + "U": qml.AngleEmbedding, + "W": qml.StronglyEntanglingLayers, + "D": D, + "n_qubits": 1, + "probs_func": get_probs, + "gamma_length": 1, + "n_samples": 10, + "p": 25, + "num_series": 25, + "noise_amp": 0.1, + "t_init": 0.1, + "t_end": 2 * torch.pi, + "k": 1, +} + +training_options = { + "batch_size": 10, + "tau": 5, + "pytorch_optimizer": torch.optim.Adam, + "lr": 0.01, + "batch_iterations": 100, + "W_layers": 2, +} + +training_options.update(general_options) + + +###################################################################### +# We can now dispatch the lattice to the Covalent server. +# + +tr_dispatch_id = ct.dispatch(training_workflow)(**training_options) + + +###################################################################### +# If you are running the notebook version of this tutorial, if you +# navigate to http://localhost:48008/ you can view the workflow on the +# Covalent GUI. It will look like the screenshot below, showing nicely how all of the +# electrons defined above interact with each other in the workflow. You can +# also track the progress of the calculation here. +# +# .. figure:: ../_static/demonstration_assets/univariate_qvr/covalent_tutorial_screenshot.png +# :width: 85% +# :align: center +# :alt: Training workflow screenshot in Covalent +# +# +# A screenshot of the Covalent GUI for the training workflow. +# + + +###################################################################### +# We now pull the results back from the Covalent server: +# + +ct_tr_results = ct.get_result(dispatch_id=tr_dispatch_id, wait=True) +results_dict = ct_tr_results.result + + +###################################################################### +# and take a look at the training loss history: +# + +plt.figure() +plt.plot(results_dict["loss_history"], ".-") +plt.ylabel("Loss [$\mathcal{L}$]") +plt.xlabel("Batch iterations") +plt.title("Loss function versus batch iterations in training") +plt.grid() + + +###################################################################### +# Tuning the threshold :math:`\zeta` +# ---------------------------------- +# +# When we have access to labelled anomalous series (as we do in our toy +# problem here, often not the case in reality), we can tune the threshold +# :math:`\zeta` to maximize some success metric. We choose to maximize the +# accuracy score as defined using the three electrons below. +# + + +@ct.electron +def get_preds_given_threshold(zeta: float, scores: torch.Tensor) -> torch.Tensor: + """For a given threshold, get the predicted labels (1 or -1), given the anomaly scores.""" + return torch.tensor([-1 if score > zeta else 1 for score in scores]) + + +@ct.electron +def get_truth_labels( + normal_series_set: torch.Tensor, anomalous_series_set: torch.Tensor +) -> torch.Tensor: + """Get a 1D tensor containing the truth values (1 or -1) for a given set of + time series. + """ + norm = torch.ones(normal_series_set.size()[0]) + anom = -torch.ones(anomalous_series_set.size()[0]) + return torch.cat([norm, anom]) + + +@ct.electron +def get_accuracy_score(pred: torch.Tensor, truth: torch.Tensor) -> torch.Tensor: + """Given the predictions and truth values, return a number between 0 and 1 + indicating the accuracy of predictions. + """ + return torch.sum(pred == truth) / truth.size()[0] + + +###################################################################### +# Then, knowing the anomaly scores :math:`a_X(y)` for a validation data +# set, we can scan through various values of :math:`\zeta` on a fine 1D grid and calcuate +# the accuracy score. Our goal is to pick the :math:`\zeta` with the +# largest accuracy score. +# + + +@ct.electron +def threshold_scan_acc_score( + scores: torch.Tensor, truth_labels: torch.Tensor, zeta_min: float, zeta_max: float, steps: int +) -> torch.Tensor: + """Given the anomaly scores and truth values, + scan over a range of thresholds = [zeta_min, zeta_max] with a + fixed number of steps, calculating the accuracy score at each point. + """ + accs = torch.empty(steps) + for i, zeta in enumerate(torch.linspace(zeta_min, zeta_max, steps)): + preds = get_preds_given_threshold(zeta, scores) + accs[i] = get_accuracy_score(preds, truth_labels) + return accs + + +@ct.electron +def get_anomaly_score( + callable_proj: callable, + y: torch.Tensor, + T: torch.Tensor, + alpha_star: torch.Tensor, + mu_star: torch.Tensor, + sigma_star: torch.Tensor, + gamma_length: int, + n_samples: int, + get_time_resolved: bool = False, +): + """Get the anomaly score for an input time series y. We need to pass the + optimal parameters (arguments with suffix _star). Optionally return the + time-resolved score (the anomaly score contribution at a given t). + """ + scores = torch.empty(T.size()[0]) + for i in range(T.size()[0]): + scores[i] = ( + 1 + - F( + callable_proj, + y[i].unsqueeze(0), + T[i].unsqueeze(0), + alpha_star, + mu_star, + sigma_star, + gamma_length, + n_samples, + ) + ).square() + if get_time_resolved: + return scores, scores.mean() + else: + return scores.mean() + + +@ct.electron +def get_norm_and_anom_scores( + X_norm: torch.Tensor, + X_anom: torch.Tensor, + T: torch.Tensor, + callable_proj: callable, + model_params: dict, + gamma_length: int, + n_samples: int, +) -> torch.Tensor: + """Get the anomaly scores assigned to input normal and anomalous time series instances. + model_params is a dictionary containing the optimal model parameters. + """ + alpha = model_params["alpha"] + mu = model_params["mu"] + sigma = model_params["sigma"] + norm_scores = torch.tensor( + [ + get_anomaly_score(callable_proj, xt, T, alpha, mu, sigma, gamma_length, n_samples) + for xt in X_norm + ] + ) + anom_scores = torch.tensor( + [ + get_anomaly_score(callable_proj, xt, T, alpha, mu, sigma, gamma_length, n_samples) + for xt in X_anom + ] + ) + return torch.cat([norm_scores, anom_scores]) + + +###################################################################### +# We now create our second ``@ct.lattice``. We are to test the optimal +# model against two random models. If our model is trainable, we should +# see that the trained model is better. +# + + +@ct.lattice +def threshold_tuning_workflow( + opt_params: dict, + gamma_length: int, + n_samples: int, + probs_func: callable, + zeta_min: float, + zeta_max: float, + steps: int, + p: int, + num_series: int, + noise_amp: float, + spike_amp: float, + max_duration: int, + t_init: float, + t_end: float, + k: int, + U: callable, + W: callable, + D: callable, + n_qubits: int, + random_model_seeds: torch.Tensor, + W_layers: int, +) -> tuple: + """A workflow for tuning the threshold value zeta, in order to maximize the accuracy score + for a validation data set. Results are tested against random models at their optimal zetas. + """ + # Generate datasets + X_val_norm, T = generate_normal_time_series_set(p, num_series, noise_amp, t_init, t_end) + X_val_anom, T = generate_anomalous_time_series_set( + p, num_series, noise_amp, spike_amp, max_duration, t_init, t_end + ) + truth_labels = get_truth_labels(X_val_norm, X_val_anom) + + # Initialize quantum functions + callable_proj = get_callable_projector_func(k, U, W, D, n_qubits, probs_func) + + accs_list = [] + scores_list = [] + # Evaluate optimal model + scores = get_norm_and_anom_scores( + X_val_norm, X_val_anom, T, callable_proj, opt_params, gamma_length, n_samples + ) + accs_opt = threshold_scan_acc_score(scores, truth_labels, zeta_min, zeta_max, steps) + accs_list.append(accs_opt) + scores_list.append(scores) + + # Evaluate random models + for seed in random_model_seeds: + rand_params = get_initial_parameters(W, W_layers, n_qubits, seed) + scores = get_norm_and_anom_scores( + X_val_norm, X_val_anom, T, callable_proj, rand_params, gamma_length, n_samples + ) + accs_list.append(threshold_scan_acc_score(scores, truth_labels, zeta_min, zeta_max, steps)) + scores_list.append(scores) + return accs_list, scores_list + + +###################################################################### +# We now set the input parameters. +# + +threshold_tuning_options = { + "spike_amp": 0.4, + "max_duration": 5, + "zeta_min": 0, + "zeta_max": 1, + "steps": 100000, + "random_model_seeds": [0, 1], + "W_layers": 2, + "opt_params": results_dict["opt_params"], +} + +threshold_tuning_options.update(general_options) + + +###################################################################### +# As before, we dispatch the lattice to the ``Covalent`` server. +# + +val_dispatch_id = ct.dispatch(threshold_tuning_workflow)(**threshold_tuning_options) +ct_val_results = ct.get_result(dispatch_id=val_dispatch_id, wait=True) +accs_list, scores_list = ct_val_results.result + + +###################################################################### +# Now, we can plot the results: +# + +zeta_xlims = [(0, 0.001), (0.25, 0.38), (0.25, 0.38)] +titles = ["Trained model", "Random model 1", "Random model 2"] +zetas = torch.linspace( + threshold_tuning_options["zeta_min"], + threshold_tuning_options["zeta_max"], + threshold_tuning_options["steps"], +) +fig, axs = plt.subplots(ncols=3, nrows=2, sharey="row") +for i in range(3): + axs[0, i].plot(zetas, accs_list[i]) + axs[0, i].set_xlim(zeta_xlims[i]) + axs[0, i].set_xlabel("Threshold [$\zeta$]") + axs[0, i].set_title(titles[i]) + axs[1, i].boxplot( + [ + scores_list[i][0 : general_options["num_series"]], + scores_list[i][general_options["num_series"] : -1], + ], + labels=["Normal", "Anomalous"], + ) + axs[1, i].set_yscale("log") + axs[1, i].axhline( + zetas[torch.argmax(accs_list[i])], color="k", linestyle=":", label="Optimal $\zeta$" + ) + axs[1, i].legend() +axs[0, 0].set_ylabel("Accuracy score") +axs[1, 0].set_ylabel("Anomaly score [$a_X(y)$]") +fig.tight_layout() + +###################################################################### +# Parsing the above, we can see that the optimal model achieves high +# accuracy when the threshold is tuned using the validation data. +# On the other hand, the random models return mostly random results +# (sometimes even worse than random guesses), regardless of how we set the +# threshold. +# + + +###################################################################### +# Testing the model +# ----------------- +# +# Now with optimal thresholds for our optimized and random models, we can perform testing. +# We already have all of the electrons to do this, so we create +# the ``@ct.lattice`` +# + + +@ct.lattice +def testing_workflow( + opt_params: dict, + gamma_length: int, + n_samples: int, + probs_func: callable, + best_zetas: list, + p: int, + num_series: int, + noise_amp: float, + spike_amp: float, + max_duration: int, + t_init: float, + t_end: float, + k: int, + U: callable, + W: callable, + D: callable, + n_qubits: int, + random_model_seeds: torch.Tensor, + W_layers: int, +) -> list: + """A workflow for calculating anomaly scores for a set of testing time series + given an optimal model and set of random models. We use the optimal zetas found in threshold tuning. + """ + # Generate time series + X_val_norm, T = generate_normal_time_series_set(p, num_series, noise_amp, t_init, t_end) + X_val_anom, T = generate_anomalous_time_series_set( + p, num_series, noise_amp, spike_amp, max_duration, t_init, t_end + ) + truth_labels = get_truth_labels(X_val_norm, X_val_anom) + + # Prepare quantum functions + callable_proj = get_callable_projector_func(k, U, W, D, n_qubits, probs_func) + + accs_list = [] + # Evaluate optimal model + scores = get_norm_and_anom_scores( + X_val_norm, X_val_anom, T, callable_proj, opt_params, gamma_length, n_samples + ) + preds = get_preds_given_threshold(best_zetas[0], scores) + accs_list.append(get_accuracy_score(preds, truth_labels)) + # Evaluate random models + for zeta, seed in zip(best_zetas[1:], random_model_seeds): + rand_params = get_initial_parameters(W, W_layers, n_qubits, seed) + scores = get_norm_and_anom_scores( + X_val_norm, X_val_anom, T, callable_proj, rand_params, gamma_length, n_samples + ) + preds = get_preds_given_threshold(zeta, scores) + accs_list.append(get_accuracy_score(preds, truth_labels)) + return accs_list + + +###################################################################### +# We dispatch it to the Covalent server with the appropriate parameters. +# + +testing_options = { + "spike_amp": 0.4, + "max_duration": 5, + "best_zetas": [zetas[torch.argmax(accs)] for accs in accs_list], + "random_model_seeds": [0, 1], + "W_layers": 2, + "opt_params": results_dict["opt_params"], +} + +testing_options.update(general_options) + +test_dispatch_id = ct.dispatch(testing_workflow)(**testing_options) +ct_test_results = ct.get_result(dispatch_id=test_dispatch_id, wait=True) +accs_list = ct_test_results.result + +###################################################################### +# Finally, we plot the results below. +# + +plt.figure() +plt.bar([1, 2, 3], accs_list) +plt.axhline(0.5, color="k", linestyle=":", label="Random accuracy") +plt.xticks([1, 2, 3], ["Trained model", "Random model 1", "Random model 2"]) +plt.ylabel("Accuracy score") +plt.title("Accuracy scores for trained and random models") +leg = plt.legend() + +###################################################################### +# As can be seen, once more, the trained model is far more accurate than +# the random models. Awesome! Now that we're done with the calculations in +# this tutorial, we just need to remember to shut down the Covalent server +# + +# Shut down the covalent server +stop = os.system("covalent stop") + +###################################################################### +# Conclusions +# ----------- +# +# We've now reached the end of this tutorial! Quickly recounting what we have learnt, we: +# +# 1. Learnt the background of how to detect anomalous time series instances, *quantumly*, +# 2. Learnt how to build the code to achieve this using PennyLane and PyTorch, and, +# 3. Learnt the basics of Covalent: a workflow orchestration tool for heterogeneous computation +# +# If you want to learn more about QVR, you should consult the paper [#Baker2022]_ where we +# generalize the math a little and test the algorithm on less trivial time series data than +# was dealt with in this tutorial. We also ran some experiments on real quantum computers, +# enhancing our results using error mitigation techniques. If you want to play some more with +# Covalent, check us out on `GitHub `__ and/or engage +# with other tutorials in our `documentation `__. + + +###################################################################### +# References +# ---------- +# +# .. [#Baker2022] +# +# Baker, Jack S. et al. “Quantum Variational Rewinding for Time Series +# Anomaly Detection.” arXiv preprint +# `arXiv:2210.164388 `__ (2022). +# +# .. [#Stone1932] +# +# Stone, Marshall H. “On one-parameter unitary groups in Hilbert +# space.” Annals of Mathematics, 643-648, +# `doi:10.2307/1968538 `__ (1932). +# +# .. [#Welch2014] +# +# Welch, Jonathan et al. “Efficient quantum circuits for diagonal +# unitaries without ancillas”, New Journal of Physics, **16**, 033040 +# `doi:10.1088/1367-2630/16/3/033040 `__ +# (2014). +# +# .. [#Cîrstoiu2020] +# +# Cîrstoiu, Cristina et al. “Variational fast forwarding for quantum +# simulation beyond the coherence time”, npj Quantum Information, +# **6**, +# `doi:10.1038/s41534-020-00302-0 `__, +# (2020) +# +# diff --git a/demonstrations_v2/tutorial_univariate_qvr/metadata.json b/demonstrations_v2/tutorial_univariate_qvr/metadata.json new file mode 100644 index 0000000000..de42b0694c --- /dev/null +++ b/demonstrations_v2/tutorial_univariate_qvr/metadata.json @@ -0,0 +1,88 @@ +{ + "title": "Quantum detection of time series anomalies", + "authors": [ + { + "username": "jsbaker" + }, + { + "username": "skradha" + } + ], + "dateOfPublication": "2023-02-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/univariate_qvr/thumbnail_tutorial_univariate_qvr.jpg" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_univariate_qvr.png" + } + ], + "seoDescription": "Learn how to quantumly detect anomalous behaviour in time series data with the help of Covalent.", + "doi": "", + "references": [ + { + "id": "Baker2022", + "type": "article", + "title": "Quantum Variational Rewinding for Time Series Anomaly Detection.", + "authors": "Baker, Jack S. et al.", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.16438" + }, + { + "id": "Stone1932", + "type": "article", + "title": "On one-parameter unitary groups in Hilbert space.", + "authors": "Stone, Marshall H.", + "year": "1932", + "journal": "Annals of Mathematics", + "doi": "10.2307/1968538", + "url": "https://doi.org/10.2307/1968538" + }, + { + "id": "Welch2014", + "type": "article", + "title": "Efficient quantum circuits for diagonal unitaries without ancillas", + "authors": "Welch, Jonathan et al.", + "year": "2014", + "journal": "New Journal of Physics", + "doi": "10.1088/1367-2630/16/3/033040", + "url": "https://doi.org/10.1088/1367-2630/16/3/033040" + }, + { + "id": "C\u00eerstoiu2020", + "type": "article", + "title": "Variational fast forwarding for quantum simulation beyond the coherence time", + "authors": "C\u00eerstoiu, Cristina et al.", + "year": "2020", + "journal": "npj Quantum Information", + "doi": "10.1038/s41534-020-00302-0", + "url": "https://doi.org/10.1038/s41534-020-00302-0" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2210.16438" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "covalent", + "link": "https://github.com/AgnostiqHQ/QuantumVariationalRewinding", + "logo": "/_static/hardware_logos/covalent.png" + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_univariate_qvr/requirements.in b/demonstrations_v2/tutorial_univariate_qvr/requirements.in new file mode 100644 index 0000000000..6e3976cc87 --- /dev/null +++ b/demonstrations_v2/tutorial_univariate_qvr/requirements.in @@ -0,0 +1,4 @@ +covalent==0.227.0rc0 +matplotlib +pennylane +torch diff --git a/demonstrations_v2/tutorial_variational_classifier/demo.py b/demonstrations_v2/tutorial_variational_classifier/demo.py new file mode 100644 index 0000000000..28396efb1a --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/demo.py @@ -0,0 +1,554 @@ +r""" +.. role:: html(raw) + :format: html + +.. _variational_classifier: + +Variational classifier +====================== + +.. meta:: + :property="og:description": Using PennyLane to implement quantum circuits that can be trained from labelled data to classify new data samples. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/classifier_output_59_0.png + +.. related:: + + tutorial_data_reuploading_classifier Data-reuploading classifier + tutorial_multiclass_classification Multiclass margin classifier + ensemble_multi_qpu Ensemble classification with Rigetti and Qiskit devices + +*Author: Maria Schuld — Posted: 11 October 2019. Last updated: 11 December 2023.* + +In this tutorial, we show how to use PennyLane to implement variational +quantum classifiers - quantum circuits that can be trained from labelled +data to classify new data samples. The two examples used are inspired by two of the first papers that +proposed variational circuits as supervised machine learning models: +`Farhi and Neven (2018) `__ as well as +`Schuld et al. (2018) `__. +""" + +############################################################################## +# +# More precisely, the first example shows that a variational circuit can be optimized to +# emulate the parity function +# +# .. math:: +# +# f: x \in \{0,1\}^{\otimes n} \rightarrow y = +# \begin{cases} 1 \text{ if uneven number of 1's in } x \\ 0 +# \text{ else}. \end{cases} +# +# It demonstrates how to encode binary inputs into +# the initial state of the variational circuit, which is simply a +# computational basis state (*basis encoding*). +# +# The second example shows how to encode real vectors as amplitude vectors into quantum states (*amplitude +# encoding*) and how to train a variational circuit to recognize the first two classes of +# flowers in the Iris dataset. +# +# 1. Fitting the parity function +# ------------------------------ +# +# Imports +# ~~~~~~~ +# +# We start by importing PennyLane, the PennyLane-provided version of NumPy, +# and an optimizer. + +import pennylane as qml +from pennylane import numpy as np +from pennylane.optimize import NesterovMomentumOptimizer + +############################################################################## +# Quantum and classical nodes +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We then create a quantum device that will run our circuits. + +dev = qml.device("default.qubit") + +############################################################################## +# Variational classifiers usually define a “layer” or “block”, which is an +# elementary circuit architecture that gets repeated to build the +# full variational circuit. +# +# Our circuit layer will use four qubits, or wires, and consists of an arbitrary +# rotation on every qubit, as well as a ring of CNOTs that entangles each qubit +# with its neighbour. Borrowing from machine learning, we call the parameters +# of the layer ``weights``. + + +def layer(layer_weights): + for wire in range(4): + qml.Rot(*layer_weights[wire], wires=wire) + + for wires in ([0, 1], [1, 2], [2, 3], [3, 0]): + qml.CNOT(wires) + + +############################################################################## +# We also need a way to encode data inputs :math:`x` into the circuit, so +# that the measured output depends on the inputs. In this first example, +# the inputs are bitstrings, which we encode into the state of the qubits. +# The quantum state :math:`\psi` after +# state preparation is a computational basis state that has 1s where +# :math:`x` has 1s, for example +# +# .. math:: x = 0101 \rightarrow |\psi \rangle = |0101 \rangle . +# +# The :class:`~pennylane.BasisState` function provided by PennyLane is made to do just +# this. It expects ``x`` to be a list of zeros and ones, i.e. ``[0,1,0,1]``. + + +def state_preparation(x): + qml.BasisState(x, wires=[0, 1, 2, 3]) + + +############################################################################## +# Now we define the variational quantum circuit as this state preparation routine, followed +# by a repetition of the layer structure. + + +@qml.qnode(dev) +def circuit(weights, x): + state_preparation(x) + + for layer_weights in weights: + layer(layer_weights) + + return qml.expval(qml.PauliZ(0)) + + +############################################################################## +# If we want to add a “classical” bias parameter, the variational quantum +# classifier also needs some post-processing. We define the full model +# as a sum of the output of the quantum circuit, plus the trainable bias. + + +def variational_classifier(weights, bias, x): + return circuit(weights, x) + bias + + +############################################################################## +# Cost +# ~~~~ +# +# In supervised learning, the cost function is usually the sum of a loss +# function and a regularizer. We restrict ourselves to the standard square loss that +# measures the distance between target labels and model predictions. + + +def square_loss(labels, predictions): + # We use a call to qml.math.stack to allow subtracting the arrays directly + return np.mean((labels - qml.math.stack(predictions)) ** 2) + + +############################################################################## +# To monitor how many inputs the current classifier predicted correctly, +# we also define the accuracy, or the proportion of predictions that agree with +# a set of target labels. + + +def accuracy(labels, predictions): + acc = sum(abs(l - p) < 1e-5 for l, p in zip(labels, predictions)) + acc = acc / len(labels) + return acc + + +############################################################################## +# For learning tasks, the cost depends on the data - here the features and +# labels considered in the iteration of the optimization routine. + + +def cost(weights, bias, X, Y): + predictions = [variational_classifier(weights, bias, x) for x in X] + return square_loss(Y, predictions) + + +############################################################################## +# Optimization +# ~~~~~~~~~~~~ +# +# Let’s now load and preprocess some data. +# +# .. note:: +# +# The parity dataset's :html:`train` and +# :html:`test` sets can be downloaded and +# should be placed in the subfolder ``variational_classifier/data``. + +data = np.loadtxt("variational_classifier/data/parity_train.txt", dtype=int) +X = np.array(data[:, :-1]) +Y = np.array(data[:, -1]) +Y = Y * 2 - 1 # shift label from {0, 1} to {-1, 1} + +for x,y in zip(X, Y): + print(f"x = {x}, y = {y}") + + +############################################################################## +# We initialize the variables randomly (but fix a seed for +# reproducibility). Remember that one of the variables is used as a bias, +# while the rest is fed into the gates of the variational circuit. + +np.random.seed(0) +num_qubits = 4 +num_layers = 2 +weights_init = 0.01 * np.random.randn(num_layers, num_qubits, 3, requires_grad=True) +bias_init = np.array(0.0, requires_grad=True) + +print("Weights:", weights_init) +print("Bias: ", bias_init) + +############################################################################## +# Next we create an optimizer instance and choose a batch size… + +opt = NesterovMomentumOptimizer(0.5) +batch_size = 5 + +############################################################################## +# …and run the optimizer to train our model. We track the accuracy - the share of +# correctly classified data samples. For this we compute the outputs of the +# variational classifier and turn them into predictions in +# :math:`\{-1,1\}` by taking the sign of the output. + +weights = weights_init +bias = bias_init +for it in range(100): + + # Update the weights by one optimizer step, using only a limited batch of data + batch_index = np.random.randint(0, len(X), (batch_size,)) + X_batch = X[batch_index] + Y_batch = Y[batch_index] + weights, bias = opt.step(cost, weights, bias, X=X_batch, Y=Y_batch) + + # Compute accuracy + predictions = [np.sign(variational_classifier(weights, bias, x)) for x in X] + + current_cost = cost(weights, bias, X, Y) + acc = accuracy(Y, predictions) + + print(f"Iter: {it+1:4d} | Cost: {current_cost:0.7f} | Accuracy: {acc:0.7f}") + +############################################################################## +# As we can see, the variational classifier learned to classify all bit strings from the training set +# correctly. +# +# But unlike optimization, in machine learning the goal is to generalize from limited +# data to *unseen* examples. Even if the variational quantum circuit +# was perfectly optimized with respect to the cost, it might not generalize, a phenomenon +# known as *overfitting*. +# The art of (quantum) machine learning is to create models and learning procedures +# that tend to find "good" minima, or those that lead to models which generalize well. +# +# With this in mind, let's look at a test set of examples we have not used during training: + +data = np.loadtxt("variational_classifier/data/parity_test.txt", dtype=int) +X_test = np.array(data[:, :-1]) +Y_test = np.array(data[:, -1]) +Y_test = Y_test * 2 - 1 # shift label from {0, 1} to {-1, 1} + +predictions_test = [np.sign(variational_classifier(weights, bias, x)) for x in X_test] + +for x,y,p in zip(X_test, Y_test, predictions_test): + print(f"x = {x}, y = {y}, pred={p}") + +acc_test = accuracy(Y_test, predictions_test) +print("Accuracy on unseen data:", acc_test) + +############################################################################## +# The quantum circuit has also learnt to predict all unseen examples perfectly well! +# This is actually remarkable, since the encoding strategy creates quantum states +# from the data that have zero overlap -- and hence the states created from the test +# set have no overlap with the states created from the training set. There are +# many functional relations the variational circuit could learn from this kind +# of representation, but the classifier chooses to label bit strings according +# to our ground truth, the parity function. +# +# Let's look at the second example, in which we use another encoding strategy. +# +# 2. Iris classification +# ---------------------- +# +# We now move on to classifying data points from the Iris dataset, which are no longer +# simple bitstrings but represented as real-valued vectors. The vectors are 2-dimensional, +# but we will add some "latent dimensions" and therefore encode inputs into 2 qubits. +# +# Quantum and classical nodes +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# State preparation is not as simple as when we represent a bitstring with +# a basis state. Every input x has to be translated into a set of angles +# which can get fed into a small routine for state preparation. To +# simplify things a bit, we will work with data from the positive +# subspace, so that we can ignore signs (which would require another +# cascade of rotations around the Z-axis). +# +# The circuit is coded according to the scheme in `Möttönen, et al. +# (2004) `__, or—as presented +# for positive vectors only—in `Schuld and Petruccione +# (2018) `__. We +# also decomposed controlled Y-axis rotations into more basic +# gates, following `Nielsen and Chuang +# (2010) `__. + + +def get_angles(x): + beta0 = 2 * np.arcsin(np.sqrt(x[1] ** 2) / np.sqrt(x[0] ** 2 + x[1] ** 2 + 1e-12)) + beta1 = 2 * np.arcsin(np.sqrt(x[3] ** 2) / np.sqrt(x[2] ** 2 + x[3] ** 2 + 1e-12)) + beta2 = 2 * np.arcsin(np.linalg.norm(x[2:]) / np.linalg.norm(x)) + + return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2]) + + +def state_preparation(a): + qml.RY(a[0], wires=0) + + qml.CNOT(wires=[0, 1]) + qml.RY(a[1], wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(a[2], wires=1) + + qml.PauliX(wires=0) + qml.CNOT(wires=[0, 1]) + qml.RY(a[3], wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(a[4], wires=1) + qml.PauliX(wires=0) + + +############################################################################## +# Let’s test if this routine actually works. + +x = np.array([0.53896774, 0.79503606, 0.27826503, 0.0], requires_grad=False) +ang = get_angles(x) + + +@qml.qnode(dev) +def test(angles): + state_preparation(angles) + + return qml.state() + + +state = test(ang) + +print("x : ", np.round(x, 6)) +print("angles : ", np.round(ang, 6)) +print("amplitude vector: ", np.round(np.real(state), 6)) + + +############################################################################## +# The method computed the correct angles to prepare the desired state! +# +# .. note:: +# +# The ``default.qubit`` simulator provides a shortcut to +# ``state_preparation`` with the command +# ``qml.StatePrep(x, wires=[0, 1])``. On state simulators, this just +# replaces the quantum state with our (normalized) input. On hardware, the operation implements +# more sophisticated versions of the routine used above. + +############################################################################## +# Since we are working with only 2 qubits now, we need to update the ``layer`` +# function. +# In addition, we redefine the ``cost`` function to pass the full batch of data +# to the state preparation of the circuit simultaneously, a technique similar +# to NumPy broadcasting. + + +def layer(layer_weights): + for wire in range(2): + qml.Rot(*layer_weights[wire], wires=wire) + qml.CNOT(wires=[0, 1]) + + +def cost(weights, bias, X, Y): + # Transpose the batch of input data in order to make the indexing + # in state_preparation work + predictions = variational_classifier(weights, bias, X.T) + return square_loss(Y, predictions) + + +############################################################################## +# Data +# ~~~~ +# +# We load the Iris data set. There is a bit of preprocessing to do in +# order to encode the inputs into the amplitudes of a quantum state. We will augment the +# data points by two so-called "latent dimensions", making the size of the padded data point +# match the size of the state vector in the quantum device. We then need +# to normalize the data points, and finally, we translate the inputs x to rotation +# angles using the ``get_angles`` function we defined above. +# +# Data preprocessing should always be done with the problem in mind; for example, if we do not +# add any latent dimensions, normalization erases any information on the length of the vectors and +# classes separated by this feature will not be distinguishable. +# +# .. note:: +# +# The Iris dataset can be downloaded +# :html:`here` and should be placed +# in the subfolder ``variational_classifer/data``. + +data = np.loadtxt("variational_classifier/data/iris_classes1and2_scaled.txt") +X = data[:, 0:2] +print(f"First X sample (original) : {X[0]}") + +# pad the vectors to size 2^2=4 with constant values +padding = np.ones((len(X), 2)) * 0.1 +X_pad = np.c_[X, padding] +print(f"First X sample (padded) : {X_pad[0]}") + +# normalize each input +normalization = np.sqrt(np.sum(X_pad**2, -1)) +X_norm = (X_pad.T / normalization).T +print(f"First X sample (normalized): {X_norm[0]}") + +# the angles for state preparation are the features +features = np.array([get_angles(x) for x in X_norm], requires_grad=False) +print(f"First features sample : {features[0]}") + +Y = data[:, -1] + +############################################################################## +# These angles are our new features, which is why we have renamed X to +# “features” above. Let’s plot the stages of preprocessing and play around +# with the dimensions (dim1, dim2). Some of them still separate the +# classes well, while others are less informative. + +import matplotlib.pyplot as plt + +plt.figure() +plt.scatter(X[:, 0][Y == 1], X[:, 1][Y == 1], c="b", marker="o", ec="k") +plt.scatter(X[:, 0][Y == -1], X[:, 1][Y == -1], c="r", marker="o", ec="k") +plt.title("Original data") +plt.show() + +plt.figure() +dim1 = 0 +dim2 = 1 +plt.scatter(X_norm[:, dim1][Y == 1], X_norm[:, dim2][Y == 1], c="b", marker="o", ec="k") +plt.scatter(X_norm[:, dim1][Y == -1], X_norm[:, dim2][Y == -1], c="r", marker="o", ec="k") +plt.title(f"Padded and normalised data (dims {dim1} and {dim2})") +plt.show() + +plt.figure() +dim1 = 0 +dim2 = 3 +plt.scatter(features[:, dim1][Y == 1], features[:, dim2][Y == 1], c="b", marker="o", ec="k") +plt.scatter(features[:, dim1][Y == -1], features[:, dim2][Y == -1], c="r", marker="o", ec="k") +plt.title(f"Feature vectors (dims {dim1} and {dim2})") +plt.show() + + +############################################################################## +# This time we want to generalize from the data samples. This means that we want +# to train our model on one set of data and test its performance on a second set +# of data that has not been used in training. To monitor the +# generalization performance, the data is split into training and +# validation set. + +np.random.seed(0) +num_data = len(Y) +num_train = int(0.75 * num_data) +index = np.random.permutation(range(num_data)) +feats_train = features[index[:num_train]] +Y_train = Y[index[:num_train]] +feats_val = features[index[num_train:]] +Y_val = Y[index[num_train:]] + +# We need these later for plotting +X_train = X[index[:num_train]] +X_val = X[index[num_train:]] + +############################################################################## +# Optimization +# ~~~~~~~~~~~~ +# +# First we initialize the variables. + +num_qubits = 2 +num_layers = 6 + +weights_init = 0.01 * np.random.randn(num_layers, num_qubits, 3, requires_grad=True) +bias_init = np.array(0.0, requires_grad=True) + +############################################################################## +# Again we minimize the cost, using the imported optimizer. + +opt = NesterovMomentumOptimizer(0.01) +batch_size = 5 + +# train the variational classifier +weights = weights_init +bias = bias_init +for it in range(60): + # Update the weights by one optimizer step + batch_index = np.random.randint(0, num_train, (batch_size,)) + feats_train_batch = feats_train[batch_index] + Y_train_batch = Y_train[batch_index] + weights, bias, _, _ = opt.step(cost, weights, bias, feats_train_batch, Y_train_batch) + + # Compute predictions on train and validation set + predictions_train = np.sign(variational_classifier(weights, bias, feats_train.T)) + predictions_val = np.sign(variational_classifier(weights, bias, feats_val.T)) + + # Compute accuracy on train and validation set + acc_train = accuracy(Y_train, predictions_train) + acc_val = accuracy(Y_val, predictions_val) + + if (it + 1) % 2 == 0: + _cost = cost(weights, bias, features, Y) + print( + f"Iter: {it + 1:5d} | Cost: {_cost:0.7f} | " + f"Acc train: {acc_train:0.7f} | Acc validation: {acc_val:0.7f}" + ) + + +############################################################################## +# We can plot the continuous output of the variational classifier for the +# first two dimensions of the Iris data set. + +plt.figure() +cm = plt.cm.RdBu + +# make data for decision regions +xx, yy = np.meshgrid(np.linspace(0.0, 1.5, 30), np.linspace(0.0, 1.5, 30)) +X_grid = [np.array([x, y]) for x, y in zip(xx.flatten(), yy.flatten())] + +# preprocess grid points like data inputs above +padding = 0.1 * np.ones((len(X_grid), 2)) +X_grid = np.c_[X_grid, padding] # pad each input +normalization = np.sqrt(np.sum(X_grid**2, -1)) +X_grid = (X_grid.T / normalization).T # normalize each input +features_grid = np.array([get_angles(x) for x in X_grid]) # angles are new features +predictions_grid = variational_classifier(weights, bias, features_grid.T) +Z = np.reshape(predictions_grid, xx.shape) + +# plot decision regions +levels = np.arange(-1, 1.1, 0.1) +cnt = plt.contourf(xx, yy, Z, levels=levels, cmap=cm, alpha=0.8, extend="both") +plt.contour(xx, yy, Z, levels=[0.0], colors=("black",), linestyles=("--",), linewidths=(0.8,)) +plt.colorbar(cnt, ticks=[-1, 0, 1]) + +# plot data +for color, label in zip(["b", "r"], [1, -1]): + plot_x = X_train[:, 0][Y_train == label] + plot_y = X_train[:, 1][Y_train == label] + plt.scatter(plot_x, plot_y, c=color, marker="o", ec="k", label=f"class {label} train") + plot_x = (X_val[:, 0][Y_val == label],) + plot_y = (X_val[:, 1][Y_val == label],) + plt.scatter(plot_x, plot_y, c=color, marker="^", ec="k", label=f"class {label} validation") + +plt.legend() +plt.show() + +############################################################################## +# We find that the variational classifier learnt a separating line between the datapoints of +# the two different classes, which allows it to classify even the unseen validation data with +# perfect accuracy. +# diff --git a/demonstrations_v2/tutorial_variational_classifier/metadata.json b/demonstrations_v2/tutorial_variational_classifier/metadata.json new file mode 100644 index 0000000000..1afa350ef0 --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/metadata.json @@ -0,0 +1,44 @@ +{ + "title": "Variational classifier", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variational_classifier.png" + } + ], + "seoDescription": "Use PennyLane to implement quantum circuits that can be trained from labelled data to classify new data samples.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_multiclass_classification", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ensemble_multi_qpu", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/variational-classifier-demo-question-on-padding/3367" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_variational_classifier/requirements.in b/demonstrations_v2/tutorial_variational_classifier/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes1and2_scaled.txt b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes1and2_scaled.txt new file mode 100644 index 0000000000..fa8b755d6d --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes1and2_scaled.txt @@ -0,0 +1,100 @@ +3.999999999999999112e-01 7.500000000000000000e-01 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.000000000000002665e-01 5.000000000000000000e-01 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +2.000000000000001776e-01 6.000000000000000888e-01 1.500000000000000222e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +1.499999999999999112e-01 5.500000000000000444e-01 2.500000000000000000e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.500000000000000888e-01 8.000000000000000444e-01 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +5.500000000000002665e-01 9.499999999999999556e-01 3.499999999999999778e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +1.499999999999999112e-01 6.999999999999999556e-01 1.999999999999999556e-01 9.999999999999999167e-02 -1.000000000000000000e+00 +3.500000000000000888e-01 6.999999999999999556e-01 2.500000000000000000e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +5.000000000000026645e-02 4.499999999999999556e-01 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.000000000000002665e-01 5.500000000000000444e-01 2.500000000000000000e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +5.500000000000002665e-01 8.500000000000000888e-01 2.500000000000000000e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +2.500000000000000000e-01 6.999999999999999556e-01 3.000000000000000444e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +2.500000000000000000e-01 5.000000000000000000e-01 1.999999999999999556e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +0.000000000000000000e+00 5.000000000000000000e-01 5.000000000000004441e-02 0.000000000000000000e+00 -1.000000000000000000e+00 +7.500000000000000000e-01 1.000000000000000000e+00 9.999999999999997780e-02 5.000000000000000278e-02 -1.000000000000000000e+00 +7.000000000000001776e-01 1.200000000000000178e+00 2.500000000000000000e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +5.500000000000002665e-01 9.499999999999999556e-01 1.500000000000000222e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +3.999999999999999112e-01 7.500000000000000000e-01 1.999999999999999556e-01 9.999999999999999167e-02 -1.000000000000000000e+00 +7.000000000000001776e-01 8.999999999999999112e-01 3.499999999999999778e-01 9.999999999999999167e-02 -1.000000000000000000e+00 +3.999999999999999112e-01 8.999999999999999112e-01 2.500000000000000000e-01 9.999999999999999167e-02 -1.000000000000000000e+00 +5.500000000000002665e-01 6.999999999999999556e-01 3.499999999999999778e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.999999999999999112e-01 8.500000000000000888e-01 2.500000000000000000e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +1.499999999999999112e-01 8.000000000000000444e-01 0.000000000000000000e+00 5.000000000000000278e-02 -1.000000000000000000e+00 +3.999999999999999112e-01 6.499999999999999112e-01 3.499999999999999778e-01 2.000000000000000111e-01 -1.000000000000000000e+00 +2.500000000000000000e-01 6.999999999999999556e-01 4.499999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.500000000000000888e-01 5.000000000000000000e-01 3.000000000000000444e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.500000000000000888e-01 6.999999999999999556e-01 3.000000000000000444e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +4.500000000000001776e-01 7.500000000000000000e-01 2.500000000000000000e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +4.500000000000001776e-01 6.999999999999999556e-01 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +2.000000000000001776e-01 6.000000000000000888e-01 3.000000000000000444e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +2.500000000000000000e-01 5.500000000000000444e-01 3.000000000000000444e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +5.500000000000002665e-01 6.999999999999999556e-01 2.500000000000000000e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +4.500000000000001776e-01 1.049999999999999822e+00 2.500000000000000000e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +6.000000000000000888e-01 1.100000000000000089e+00 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.000000000000002665e-01 5.500000000000000444e-01 2.500000000000000000e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +3.500000000000000888e-01 6.000000000000000888e-01 9.999999999999997780e-02 5.000000000000000278e-02 -1.000000000000000000e+00 +6.000000000000000888e-01 7.500000000000000000e-01 1.500000000000000222e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.000000000000002665e-01 5.500000000000000444e-01 2.500000000000000000e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +5.000000000000026645e-02 5.000000000000000000e-01 1.500000000000000222e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.999999999999999112e-01 6.999999999999999556e-01 2.500000000000000000e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.500000000000000888e-01 7.500000000000000000e-01 1.500000000000000222e-01 9.999999999999999167e-02 -1.000000000000000000e+00 +1.000000000000000888e-01 1.499999999999999112e-01 1.500000000000000222e-01 9.999999999999999167e-02 -1.000000000000000000e+00 +5.000000000000026645e-02 6.000000000000000888e-01 1.500000000000000222e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.500000000000000888e-01 7.500000000000000000e-01 3.000000000000000444e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +3.999999999999999112e-01 8.999999999999999112e-01 4.499999999999999556e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +2.500000000000000000e-01 5.000000000000000000e-01 1.999999999999999556e-01 9.999999999999999167e-02 -1.000000000000000000e+00 +3.999999999999999112e-01 8.999999999999999112e-01 3.000000000000000444e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +1.499999999999999112e-01 6.000000000000000888e-01 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +5.000000000000000000e-01 8.500000000000000888e-01 2.500000000000000000e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +3.500000000000000888e-01 6.499999999999999112e-01 1.999999999999999556e-01 5.000000000000000278e-02 -1.000000000000000000e+00 +1.350000000000000089e+00 6.000000000000000888e-01 1.850000000000000089e+00 6.499999999999999112e-01 1.000000000000000000e+00 +1.050000000000000266e+00 6.000000000000000888e-01 1.750000000000000000e+00 6.999999999999999556e-01 1.000000000000000000e+00 +1.300000000000000266e+00 5.500000000000000444e-01 1.950000000000000178e+00 6.999999999999999556e-01 1.000000000000000000e+00 +6.000000000000000888e-01 1.499999999999999112e-01 1.500000000000000000e+00 5.999999999999999778e-01 1.000000000000000000e+00 +1.100000000000000089e+00 3.999999999999999112e-01 1.799999999999999822e+00 6.999999999999999556e-01 1.000000000000000000e+00 +7.000000000000001776e-01 3.999999999999999112e-01 1.750000000000000000e+00 5.999999999999999778e-01 1.000000000000000000e+00 +1.000000000000000000e+00 6.499999999999999112e-01 1.850000000000000089e+00 7.500000000000000000e-01 1.000000000000000000e+00 +3.000000000000002665e-01 1.999999999999999556e-01 1.149999999999999911e+00 4.500000000000000111e-01 1.000000000000000000e+00 +1.149999999999999911e+00 4.499999999999999556e-01 1.799999999999999822e+00 5.999999999999999778e-01 1.000000000000000000e+00 +4.500000000000001776e-01 3.500000000000000888e-01 1.449999999999999956e+00 6.499999999999999112e-01 1.000000000000000000e+00 +3.500000000000000888e-01 0.000000000000000000e+00 1.250000000000000000e+00 4.500000000000000111e-01 1.000000000000000000e+00 +8.000000000000002665e-01 5.000000000000000000e-01 1.600000000000000089e+00 6.999999999999999556e-01 1.000000000000000000e+00 +8.500000000000000888e-01 1.000000000000000888e-01 1.500000000000000000e+00 4.500000000000000111e-01 1.000000000000000000e+00 +8.999999999999999112e-01 4.499999999999999556e-01 1.850000000000000089e+00 6.499999999999999112e-01 1.000000000000000000e+00 +6.499999999999999112e-01 4.499999999999999556e-01 1.300000000000000044e+00 5.999999999999999778e-01 1.000000000000000000e+00 +1.200000000000000178e+00 5.500000000000000444e-01 1.700000000000000178e+00 6.499999999999999112e-01 1.000000000000000000e+00 +6.499999999999999112e-01 5.000000000000000000e-01 1.750000000000000000e+00 6.999999999999999556e-01 1.000000000000000000e+00 +7.500000000000000000e-01 3.500000000000000888e-01 1.549999999999999822e+00 4.500000000000000111e-01 1.000000000000000000e+00 +9.500000000000001776e-01 1.000000000000000888e-01 1.750000000000000000e+00 6.999999999999999556e-01 1.000000000000000000e+00 +6.499999999999999112e-01 2.500000000000000000e-01 1.449999999999999956e+00 5.000000000000000000e-01 1.000000000000000000e+00 +8.000000000000002665e-01 6.000000000000000888e-01 1.899999999999999911e+00 8.499999999999999778e-01 1.000000000000000000e+00 +8.999999999999999112e-01 3.999999999999999112e-01 1.500000000000000000e+00 5.999999999999999778e-01 1.000000000000000000e+00 +1.000000000000000000e+00 2.500000000000000000e-01 1.950000000000000178e+00 6.999999999999999556e-01 1.000000000000000000e+00 +8.999999999999999112e-01 3.999999999999999112e-01 1.850000000000000089e+00 5.499999999999999334e-01 1.000000000000000000e+00 +1.050000000000000266e+00 4.499999999999999556e-01 1.649999999999999911e+00 5.999999999999999778e-01 1.000000000000000000e+00 +1.149999999999999911e+00 5.000000000000000000e-01 1.700000000000000178e+00 6.499999999999999112e-01 1.000000000000000000e+00 +1.250000000000000000e+00 3.999999999999999112e-01 1.899999999999999911e+00 6.499999999999999112e-01 1.000000000000000000e+00 +1.200000000000000178e+00 5.000000000000000000e-01 2.000000000000000000e+00 7.999999999999999334e-01 1.000000000000000000e+00 +8.500000000000000888e-01 4.499999999999999556e-01 1.750000000000000000e+00 6.999999999999999556e-01 1.000000000000000000e+00 +7.000000000000001776e-01 3.000000000000000444e-01 1.250000000000000000e+00 4.500000000000000111e-01 1.000000000000000000e+00 +6.000000000000000888e-01 1.999999999999999556e-01 1.399999999999999911e+00 5.000000000000000000e-01 1.000000000000000000e+00 +6.000000000000000888e-01 1.999999999999999556e-01 1.350000000000000089e+00 4.500000000000000111e-01 1.000000000000000000e+00 +7.500000000000000000e-01 3.500000000000000888e-01 1.449999999999999956e+00 5.499999999999999334e-01 1.000000000000000000e+00 +8.500000000000000888e-01 3.500000000000000888e-01 2.049999999999999822e+00 7.500000000000000000e-01 1.000000000000000000e+00 +5.500000000000002665e-01 5.000000000000000000e-01 1.750000000000000000e+00 6.999999999999999556e-01 1.000000000000000000e+00 +8.500000000000000888e-01 6.999999999999999556e-01 1.750000000000000000e+00 7.500000000000000000e-01 1.000000000000000000e+00 +1.200000000000000178e+00 5.500000000000000444e-01 1.850000000000000089e+00 6.999999999999999556e-01 1.000000000000000000e+00 +1.000000000000000000e+00 1.499999999999999112e-01 1.700000000000000178e+00 5.999999999999999778e-01 1.000000000000000000e+00 +6.499999999999999112e-01 5.000000000000000000e-01 1.549999999999999822e+00 5.999999999999999778e-01 1.000000000000000000e+00 +6.000000000000000888e-01 2.500000000000000000e-01 1.500000000000000000e+00 5.999999999999999778e-01 1.000000000000000000e+00 +6.000000000000000888e-01 3.000000000000000444e-01 1.700000000000000178e+00 5.499999999999999334e-01 1.000000000000000000e+00 +8.999999999999999112e-01 5.000000000000000000e-01 1.799999999999999822e+00 6.499999999999999112e-01 1.000000000000000000e+00 +7.500000000000000000e-01 3.000000000000000444e-01 1.500000000000000000e+00 5.499999999999999334e-01 1.000000000000000000e+00 +3.500000000000000888e-01 1.499999999999999112e-01 1.149999999999999911e+00 4.500000000000000111e-01 1.000000000000000000e+00 +6.499999999999999112e-01 3.500000000000000888e-01 1.600000000000000089e+00 5.999999999999999778e-01 1.000000000000000000e+00 +7.000000000000001776e-01 5.000000000000000000e-01 1.600000000000000089e+00 5.499999999999999334e-01 1.000000000000000000e+00 +7.000000000000001776e-01 4.499999999999999556e-01 1.600000000000000089e+00 5.999999999999999778e-01 1.000000000000000000e+00 +9.500000000000001776e-01 4.499999999999999556e-01 1.649999999999999911e+00 5.999999999999999778e-01 1.000000000000000000e+00 +3.999999999999999112e-01 2.500000000000000000e-01 1.000000000000000000e+00 5.000000000000000000e-01 1.000000000000000000e+00 +7.000000000000001776e-01 3.999999999999999112e-01 1.549999999999999822e+00 5.999999999999999778e-01 1.000000000000000000e+00 diff --git a/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes2and3_scaled.txt b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes2and3_scaled.txt new file mode 100644 index 0000000000..a486df846c --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_classes2and3_scaled.txt @@ -0,0 +1,100 @@ +1.049999999999999822e+00 6.000000000000000888e-01 8.500000000000000888e-01 1.999999999999999556e-01 -1.000000000000000000e+00 +7.500000000000000000e-01 6.000000000000000888e-01 7.500000000000000000e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +1.000000000000000000e+00 5.500000000000000444e-01 9.500000000000001776e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +2.999999999999998224e-01 1.499999999999999112e-01 5.000000000000000000e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +7.999999999999998224e-01 3.999999999999999112e-01 7.999999999999998224e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +3.999999999999999112e-01 3.999999999999999112e-01 7.500000000000000000e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +6.999999999999997335e-01 6.499999999999999112e-01 8.500000000000000888e-01 3.000000000000000444e-01 -1.000000000000000000e+00 +0.000000000000000000e+00 1.999999999999999556e-01 1.499999999999999112e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +8.499999999999996447e-01 4.499999999999999556e-01 7.999999999999998224e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +1.499999999999999112e-01 3.500000000000000888e-01 4.499999999999999556e-01 1.999999999999999556e-01 -1.000000000000000000e+00 +4.999999999999982236e-02 0.000000000000000000e+00 2.500000000000000000e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +5.000000000000000000e-01 5.000000000000000000e-01 6.000000000000000888e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +5.499999999999998224e-01 1.000000000000000888e-01 5.000000000000000000e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +5.999999999999996447e-01 4.499999999999999556e-01 8.500000000000000888e-01 1.999999999999999556e-01 -1.000000000000000000e+00 +3.499999999999996447e-01 4.499999999999999556e-01 3.000000000000000444e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +8.999999999999999112e-01 5.500000000000000444e-01 7.000000000000001776e-01 1.999999999999999556e-01 -1.000000000000000000e+00 +3.499999999999996447e-01 5.000000000000000000e-01 7.500000000000000000e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +4.499999999999997335e-01 3.500000000000000888e-01 5.499999999999998224e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +6.499999999999999112e-01 1.000000000000000888e-01 7.500000000000000000e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +3.499999999999996447e-01 2.500000000000000000e-01 4.499999999999999556e-01 5.000000000000004441e-02 -1.000000000000000000e+00 +5.000000000000000000e-01 6.000000000000000888e-01 8.999999999999999112e-01 4.000000000000000222e-01 -1.000000000000000000e+00 +5.999999999999996447e-01 3.999999999999999112e-01 5.000000000000000000e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +6.999999999999997335e-01 2.500000000000000000e-01 9.500000000000001776e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +5.999999999999996447e-01 3.999999999999999112e-01 8.500000000000000888e-01 9.999999999999997780e-02 -1.000000000000000000e+00 +7.500000000000000000e-01 4.499999999999999556e-01 6.499999999999999112e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +8.499999999999996447e-01 5.000000000000000000e-01 7.000000000000001776e-01 1.999999999999999556e-01 -1.000000000000000000e+00 +9.499999999999997335e-01 3.999999999999999112e-01 8.999999999999999112e-01 1.999999999999999556e-01 -1.000000000000000000e+00 +8.999999999999999112e-01 5.000000000000000000e-01 1.000000000000000000e+00 3.499999999999999778e-01 -1.000000000000000000e+00 +5.499999999999998224e-01 4.499999999999999556e-01 7.500000000000000000e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +3.999999999999999112e-01 3.000000000000000444e-01 2.500000000000000000e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +2.999999999999998224e-01 1.999999999999999556e-01 3.999999999999999112e-01 5.000000000000004441e-02 -1.000000000000000000e+00 +2.999999999999998224e-01 1.999999999999999556e-01 3.500000000000000888e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +4.499999999999997335e-01 3.500000000000000888e-01 4.499999999999999556e-01 9.999999999999997780e-02 -1.000000000000000000e+00 +5.499999999999998224e-01 3.500000000000000888e-01 1.049999999999999822e+00 3.000000000000000444e-01 -1.000000000000000000e+00 +2.500000000000000000e-01 5.000000000000000000e-01 7.500000000000000000e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +5.499999999999998224e-01 6.999999999999999556e-01 7.500000000000000000e-01 3.000000000000000444e-01 -1.000000000000000000e+00 +8.999999999999999112e-01 5.500000000000000444e-01 8.500000000000000888e-01 2.500000000000000000e-01 -1.000000000000000000e+00 +6.999999999999997335e-01 1.499999999999999112e-01 7.000000000000001776e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +3.499999999999996447e-01 5.000000000000000000e-01 5.499999999999998224e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +2.999999999999998224e-01 2.500000000000000000e-01 5.000000000000000000e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +2.999999999999998224e-01 3.000000000000000444e-01 7.000000000000001776e-01 9.999999999999997780e-02 -1.000000000000000000e+00 +5.999999999999996447e-01 5.000000000000000000e-01 7.999999999999998224e-01 1.999999999999999556e-01 -1.000000000000000000e+00 +4.499999999999997335e-01 3.000000000000000444e-01 5.000000000000000000e-01 9.999999999999997780e-02 -1.000000000000000000e+00 +4.999999999999982236e-02 1.499999999999999112e-01 1.499999999999999112e-01 0.000000000000000000e+00 -1.000000000000000000e+00 +3.499999999999996447e-01 3.500000000000000888e-01 6.000000000000000888e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +3.999999999999999112e-01 5.000000000000000000e-01 6.000000000000000888e-01 9.999999999999997780e-02 -1.000000000000000000e+00 +3.999999999999999112e-01 4.499999999999999556e-01 6.000000000000000888e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +6.499999999999999112e-01 4.499999999999999556e-01 6.499999999999999112e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +9.999999999999964473e-02 2.500000000000000000e-01 0.000000000000000000e+00 5.000000000000004441e-02 -1.000000000000000000e+00 +3.999999999999999112e-01 3.999999999999999112e-01 5.499999999999998224e-01 1.500000000000000222e-01 -1.000000000000000000e+00 +6.999999999999997335e-01 6.499999999999999112e-01 1.500000000000000000e+00 7.500000000000000000e-01 1.000000000000000000e+00 +4.499999999999997335e-01 3.500000000000000888e-01 1.049999999999999822e+00 4.499999999999999556e-01 1.000000000000000000e+00 +1.099999999999999645e+00 5.000000000000000000e-01 1.450000000000000178e+00 5.500000000000000444e-01 1.000000000000000000e+00 +6.999999999999997335e-01 4.499999999999999556e-01 1.299999999999999822e+00 4.000000000000000222e-01 1.000000000000000000e+00 +7.999999999999998224e-01 5.000000000000000000e-01 1.399999999999999911e+00 6.000000000000000888e-01 1.000000000000000000e+00 +1.349999999999999645e+00 5.000000000000000000e-01 1.799999999999999822e+00 5.500000000000000444e-01 1.000000000000000000e+00 +0.000000000000000000e+00 2.500000000000000000e-01 7.500000000000000000e-01 3.499999999999999778e-01 1.000000000000000000e+00 +1.199999999999999734e+00 4.499999999999999556e-01 1.649999999999999911e+00 4.000000000000000222e-01 1.000000000000000000e+00 +8.999999999999999112e-01 2.500000000000000000e-01 1.399999999999999911e+00 4.000000000000000222e-01 1.000000000000000000e+00 +1.149999999999999911e+00 8.000000000000000444e-01 1.549999999999999822e+00 7.500000000000000000e-01 1.000000000000000000e+00 +7.999999999999998224e-01 6.000000000000000888e-01 1.049999999999999822e+00 5.000000000000000000e-01 1.000000000000000000e+00 +7.500000000000000000e-01 3.500000000000000888e-01 1.149999999999999911e+00 4.499999999999999556e-01 1.000000000000000000e+00 +9.499999999999997335e-01 5.000000000000000000e-01 1.250000000000000000e+00 5.500000000000000444e-01 1.000000000000000000e+00 +3.999999999999999112e-01 2.500000000000000000e-01 1.000000000000000000e+00 5.000000000000000000e-01 1.000000000000000000e+00 +4.499999999999997335e-01 3.999999999999999112e-01 1.049999999999999822e+00 6.999999999999999556e-01 1.000000000000000000e+00 +7.500000000000000000e-01 6.000000000000000888e-01 1.149999999999999911e+00 6.499999999999999112e-01 1.000000000000000000e+00 +7.999999999999998224e-01 5.000000000000000000e-01 1.250000000000000000e+00 4.000000000000000222e-01 1.000000000000000000e+00 +1.399999999999999911e+00 8.999999999999999112e-01 1.850000000000000089e+00 6.000000000000000888e-01 1.000000000000000000e+00 +1.399999999999999911e+00 3.000000000000000444e-01 1.950000000000000178e+00 6.499999999999999112e-01 1.000000000000000000e+00 +5.499999999999998224e-01 1.000000000000000888e-01 1.000000000000000000e+00 2.500000000000000000e-01 1.000000000000000000e+00 +1.000000000000000000e+00 6.000000000000000888e-01 1.350000000000000089e+00 6.499999999999999112e-01 1.000000000000000000e+00 +3.499999999999996447e-01 3.999999999999999112e-01 9.500000000000001776e-01 5.000000000000000000e-01 1.000000000000000000e+00 +1.399999999999999911e+00 3.999999999999999112e-01 1.850000000000000089e+00 5.000000000000000000e-01 1.000000000000000000e+00 +6.999999999999997335e-01 3.500000000000000888e-01 9.500000000000001776e-01 4.000000000000000222e-01 1.000000000000000000e+00 +8.999999999999999112e-01 6.499999999999999112e-01 1.350000000000000089e+00 5.500000000000000444e-01 1.000000000000000000e+00 +1.149999999999999911e+00 6.000000000000000888e-01 1.500000000000000000e+00 4.000000000000000222e-01 1.000000000000000000e+00 +6.499999999999999112e-01 3.999999999999999112e-01 8.999999999999999112e-01 4.000000000000000222e-01 1.000000000000000000e+00 +5.999999999999996447e-01 5.000000000000000000e-01 9.500000000000001776e-01 4.000000000000000222e-01 1.000000000000000000e+00 +7.500000000000000000e-01 3.999999999999999112e-01 1.299999999999999822e+00 5.500000000000000444e-01 1.000000000000000000e+00 +1.149999999999999911e+00 5.000000000000000000e-01 1.399999999999999911e+00 3.000000000000000444e-01 1.000000000000000000e+00 +1.250000000000000000e+00 3.999999999999999112e-01 1.549999999999999822e+00 4.499999999999999556e-01 1.000000000000000000e+00 +1.500000000000000000e+00 8.999999999999999112e-01 1.700000000000000178e+00 5.000000000000000000e-01 1.000000000000000000e+00 +7.500000000000000000e-01 3.999999999999999112e-01 1.299999999999999822e+00 6.000000000000000888e-01 1.000000000000000000e+00 +6.999999999999997335e-01 3.999999999999999112e-01 1.049999999999999822e+00 2.500000000000000000e-01 1.000000000000000000e+00 +5.999999999999996447e-01 3.000000000000000444e-01 1.299999999999999822e+00 1.999999999999999556e-01 1.000000000000000000e+00 +1.399999999999999911e+00 5.000000000000000000e-01 1.549999999999999822e+00 6.499999999999999112e-01 1.000000000000000000e+00 +6.999999999999997335e-01 6.999999999999999556e-01 1.299999999999999822e+00 6.999999999999999556e-01 1.000000000000000000e+00 +7.500000000000000000e-01 5.500000000000000444e-01 1.250000000000000000e+00 4.000000000000000222e-01 1.000000000000000000e+00 +5.499999999999998224e-01 5.000000000000000000e-01 8.999999999999999112e-01 4.000000000000000222e-01 1.000000000000000000e+00 +1.000000000000000000e+00 5.500000000000000444e-01 1.200000000000000178e+00 5.500000000000000444e-01 1.000000000000000000e+00 +8.999999999999999112e-01 5.500000000000000444e-01 1.299999999999999822e+00 6.999999999999999556e-01 1.000000000000000000e+00 +1.000000000000000000e+00 5.500000000000000444e-01 1.049999999999999822e+00 6.499999999999999112e-01 1.000000000000000000e+00 +4.499999999999997335e-01 3.500000000000000888e-01 1.049999999999999822e+00 4.499999999999999556e-01 1.000000000000000000e+00 +9.499999999999997335e-01 6.000000000000000888e-01 1.450000000000000178e+00 6.499999999999999112e-01 1.000000000000000000e+00 +8.999999999999999112e-01 6.499999999999999112e-01 1.350000000000000089e+00 7.500000000000000000e-01 1.000000000000000000e+00 +8.999999999999999112e-01 5.000000000000000000e-01 1.100000000000000089e+00 6.499999999999999112e-01 1.000000000000000000e+00 +6.999999999999997335e-01 2.500000000000000000e-01 1.000000000000000000e+00 4.499999999999999556e-01 1.000000000000000000e+00 +7.999999999999998224e-01 5.000000000000000000e-01 1.100000000000000089e+00 5.000000000000000000e-01 1.000000000000000000e+00 +6.499999999999999112e-01 6.999999999999999556e-01 1.200000000000000178e+00 6.499999999999999112e-01 1.000000000000000000e+00 +5.000000000000000000e-01 5.000000000000000000e-01 1.049999999999999822e+00 4.000000000000000222e-01 1.000000000000000000e+00 diff --git a/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_scaled.txt b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_scaled.txt new file mode 100644 index 0000000000..8adc84bd28 --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/iris_scaled.txt @@ -0,0 +1,100 @@ +-5.810659036233283281e-01 8.571354287056350829e-01 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-8.943089783528815895e-01 -1.984500746264286075e-01 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.207552053082436183e+00 2.237841267063972184e-01 -1.083741153029416315e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.364173590447214313e+00 1.266702603998430543e-02 -9.449778811946638601e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-7.376874409881049033e-01 1.068252529372047954e+00 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.112012915289955634e-01 1.701603831371285791e+00 -8.062146093599115160e-01 -6.832914140167521966e-01 0.000000000000000000e+00 +-1.364173590447214313e+00 6.460183280392221006e-01 -1.014359517112040088e+00 -8.607697033717529589e-01 0.000000000000000000e+00 +-7.376874409881049033e-01 6.460183280392221006e-01 -9.449778811946638601e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.677416665176767463e+00 -4.095671752928415343e-01 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-8.943089783528815895e-01 1.266702603998430543e-02 -9.449778811946638601e-01 -1.215726282081754261e+00 0.000000000000000000e+00 +-1.112012915289955634e-01 1.279369630038460937e+00 -9.449778811946638601e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.050930515717659608e+00 6.460183280392221006e-01 -8.755962452772876325e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.050930515717659608e+00 -1.984500746264286075e-01 -1.014359517112040088e+00 -1.215726282081754261e+00 0.000000000000000000e+00 +-1.834038202541545592e+00 -1.984500746264286075e-01 -1.222504424864168548e+00 -1.215726282081754261e+00 0.000000000000000000e+00 +5.152848579301123610e-01 1.912720932037698773e+00 -1.153122788946792543e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +3.586633205653357859e-01 2.757189334703350259e+00 -9.449778811946638601e-01 -6.832914140167521966e-01 0.000000000000000000e+00 +-1.112012915289955634e-01 1.701603831371285791e+00 -1.083741153029416315e+00 -6.832914140167521966e-01 0.000000000000000000e+00 +-5.810659036233283281e-01 8.571354287056350829e-01 -1.014359517112040088e+00 -8.607697033717529589e-01 0.000000000000000000e+00 +3.586633205653357859e-01 1.490486730704872809e+00 -8.062146093599115160e-01 -8.607697033717529589e-01 0.000000000000000000e+00 +-5.810659036233283281e-01 1.490486730704872809e+00 -9.449778811946638601e-01 -8.607697033717529589e-01 0.000000000000000000e+00 +-1.112012915289955634e-01 6.460183280392221006e-01 -8.062146093599115160e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-5.810659036233283281e-01 1.279369630038460937e+00 -9.449778811946638601e-01 -6.832914140167521966e-01 0.000000000000000000e+00 +-1.364173590447214313e+00 1.068252529372047954e+00 -1.291886060781544776e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-5.810659036233283281e-01 4.349012273728092293e-01 -8.062146093599115160e-01 -5.058131246617515453e-01 0.000000000000000000e+00 +-1.050930515717659608e+00 6.460183280392221006e-01 -6.674513375251592828e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-7.376874409881049033e-01 -1.984500746264286075e-01 -8.755962452772876325e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-7.376874409881049033e-01 6.460183280392221006e-01 -8.755962452772876325e-01 -6.832914140167521966e-01 0.000000000000000000e+00 +-4.244443662585502541e-01 8.571354287056350829e-01 -9.449778811946638601e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-4.244443662585502541e-01 6.460183280392221006e-01 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.207552053082436183e+00 2.237841267063972184e-01 -8.755962452772876325e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.050930515717659608e+00 1.266702603998430543e-02 -8.755962452772876325e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.112012915289955634e-01 6.460183280392221006e-01 -9.449778811946638601e-01 -6.832914140167521966e-01 0.000000000000000000e+00 +-4.244443662585502541e-01 2.123838032704110645e+00 -9.449778811946638601e-01 -1.215726282081754261e+00 0.000000000000000000e+00 +4.542024583578108121e-02 2.334955133370524738e+00 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-8.943089783528815895e-01 1.266702603998430543e-02 -9.449778811946638601e-01 -1.215726282081754261e+00 0.000000000000000000e+00 +-7.376874409881049033e-01 2.237841267063972184e-01 -1.153122788946792543e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +4.542024583578108121e-02 8.571354287056350829e-01 -1.083741153029416315e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-8.943089783528815895e-01 1.266702603998430543e-02 -9.449778811946638601e-01 -1.215726282081754261e+00 0.000000000000000000e+00 +-1.677416665176767463e+00 -1.984500746264286075e-01 -1.083741153029416315e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-5.810659036233283281e-01 6.460183280392221006e-01 -9.449778811946638601e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-7.376874409881049033e-01 8.571354287056350829e-01 -1.083741153029416315e+00 -8.607697033717529589e-01 0.000000000000000000e+00 +-1.520795127811990888e+00 -1.676269779291318152e+00 -1.083741153029416315e+00 -8.607697033717529589e-01 0.000000000000000000e+00 +-1.677416665176767463e+00 2.237841267063972184e-01 -1.083741153029416315e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-7.376874409881049033e-01 8.571354287056350829e-01 -8.755962452772876325e-01 -3.283348353067509495e-01 0.000000000000000000e+00 +-5.810659036233283281e-01 1.490486730704872809e+00 -6.674513375251592828e-01 -6.832914140167521966e-01 0.000000000000000000e+00 +-1.050930515717659608e+00 -1.984500746264286075e-01 -1.014359517112040088e+00 -8.607697033717529589e-01 0.000000000000000000e+00 +-5.810659036233283281e-01 1.490486730704872809e+00 -8.755962452772876325e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-1.364173590447214313e+00 2.237841267063972184e-01 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +-2.678228288937736234e-01 1.279369630038460937e+00 -9.449778811946638601e-01 -1.038247992726753388e+00 0.000000000000000000e+00 +-7.376874409881049033e-01 4.349012273728092293e-01 -1.014359517112040088e+00 -1.038247992726753388e+00 0.000000000000000000e+00 +2.394743306307439035e+00 2.237841267063972184e-01 1.275234468161373869e+00 1.091491479533254205e+00 1.000000000000000000e+00 +1.455014082118776475e+00 2.237841267063972184e-01 1.136471196326621413e+00 1.268969768888255079e+00 1.000000000000000000e+00 +2.238121768942662460e+00 1.266702603998430543e-02 1.413997739996126324e+00 1.268969768888255079e+00 1.000000000000000000e+00 +4.542024583578108121e-02 -1.676269779291318152e+00 7.895630167397404975e-01 9.140131901782538870e-01 1.000000000000000000e+00 +1.611635619483553050e+00 -6.206842759592544612e-01 1.205852832243997419e+00 1.268969768888255079e+00 1.000000000000000000e+00 +3.586633205653357859e-01 -6.206842759592544612e-01 1.136471196326621413e+00 9.140131901782538870e-01 1.000000000000000000e+00 +1.298392544753998346e+00 4.349012273728092293e-01 1.275234468161373869e+00 1.446448058243255952e+00 1.000000000000000000e+00 +-8.943089783528815895e-01 -1.465152678624905169e+00 3.038915653181071264e-01 3.815783221132517666e-01 1.000000000000000000e+00 +1.768257156848329625e+00 -4.095671752928415343e-01 1.205852832243997419e+00 9.140131901782538870e-01 1.000000000000000000e+00 +-4.244443662585502541e-01 -8.318013766256664443e-01 7.201813808223642699e-01 1.091491479533254205e+00 1.000000000000000000e+00 +-7.376874409881049033e-01 -2.309621081290555988e+00 4.426548371528596371e-01 3.815783221132517666e-01 1.000000000000000000e+00 +6.719063952948904905e-01 -1.984500746264286075e-01 9.283262885744929527e-01 1.268969768888255079e+00 1.000000000000000000e+00 +8.285279326596670657e-01 -1.887386879957730024e+00 7.895630167397404975e-01 3.815783221132517666e-01 1.000000000000000000e+00 +9.851494700244437519e-01 -4.095671752928415343e-01 1.275234468161373869e+00 1.091491479533254205e+00 1.000000000000000000e+00 +2.020417832005577119e-01 -4.095671752928415343e-01 5.120364730702358091e-01 9.140131901782538870e-01 1.000000000000000000e+00 +1.924878694213107755e+00 1.266702603998430543e-02 1.067089560409245408e+00 1.091491479533254205e+00 1.000000000000000000e+00 +2.020417832005577119e-01 -1.984500746264286075e-01 1.136471196326621413e+00 1.268969768888255079e+00 1.000000000000000000e+00 +5.152848579301123610e-01 -8.318013766256664443e-01 8.589446526571163920e-01 3.815783221132517666e-01 1.000000000000000000e+00 +1.141771007389221770e+00 -1.887386879957730024e+00 1.136471196326621413e+00 1.268969768888255079e+00 1.000000000000000000e+00 +2.020417832005577119e-01 -1.254035577958492187e+00 7.201813808223642699e-01 5.590566114682525845e-01 1.000000000000000000e+00 +6.719063952948904905e-01 2.237841267063972184e-01 1.344616104078749874e+00 1.801404636953257032e+00 1.000000000000000000e+00 +9.851494700244437519e-01 -6.206842759592544612e-01 7.895630167397404975e-01 9.140131901782538870e-01 1.000000000000000000e+00 +1.298392544753998346e+00 -1.254035577958492187e+00 1.413997739996126324e+00 1.268969768888255079e+00 1.000000000000000000e+00 +9.851494700244437519e-01 -6.206842759592544612e-01 1.275234468161373869e+00 7.365349008232530137e-01 1.000000000000000000e+00 +1.455014082118776475e+00 -4.095671752928415343e-01 9.977079244918689582e-01 9.140131901782538870e-01 1.000000000000000000e+00 +1.768257156848329625e+00 -1.984500746264286075e-01 1.067089560409245408e+00 1.091491479533254205e+00 1.000000000000000000e+00 +2.081500231577884552e+00 -6.206842759592544612e-01 1.344616104078749874e+00 1.091491479533254205e+00 1.000000000000000000e+00 +1.924878694213107755e+00 -1.984500746264286075e-01 1.483379375913502329e+00 1.623926347598256381e+00 1.000000000000000000e+00 +8.285279326596670657e-01 -4.095671752928415343e-01 1.136471196326621413e+00 1.268969768888255079e+00 1.000000000000000000e+00 +3.586633205653357859e-01 -1.042918477292079427e+00 4.426548371528596371e-01 3.815783221132517666e-01 1.000000000000000000e+00 +4.542024583578108121e-02 -1.465152678624905169e+00 6.507997449049880423e-01 5.590566114682525845e-01 1.000000000000000000e+00 +4.542024583578108121e-02 -1.465152678624905169e+00 5.814181089876121478e-01 3.815783221132517666e-01 1.000000000000000000e+00 +5.152848579301123610e-01 -8.318013766256664443e-01 7.201813808223642699e-01 7.365349008232530137e-01 1.000000000000000000e+00 +8.285279326596670657e-01 -8.318013766256664443e-01 1.552761011830878113e+00 1.446448058243255952e+00 1.000000000000000000e+00 +-1.112012915289955634e-01 -1.984500746264286075e-01 1.136471196326621413e+00 1.268969768888255079e+00 1.000000000000000000e+00 +8.285279326596670657e-01 6.460183280392221006e-01 1.136471196326621413e+00 1.446448058243255952e+00 1.000000000000000000e+00 +1.924878694213107755e+00 1.266702603998430543e-02 1.275234468161373869e+00 1.268969768888255079e+00 1.000000000000000000e+00 +1.298392544753998346e+00 -1.676269779291318152e+00 1.067089560409245408e+00 9.140131901782538870e-01 1.000000000000000000e+00 +2.020417832005577119e-01 -1.984500746264286075e-01 8.589446526571163920e-01 9.140131901782538870e-01 1.000000000000000000e+00 +4.542024583578108121e-02 -1.254035577958492187e+00 7.895630167397404975e-01 9.140131901782538870e-01 1.000000000000000000e+00 +4.542024583578108121e-02 -1.042918477292079427e+00 1.067089560409245408e+00 7.365349008232530137e-01 1.000000000000000000e+00 +9.851494700244437519e-01 -1.984500746264286075e-01 1.205852832243997419e+00 1.091491479533254205e+00 1.000000000000000000e+00 +5.152848579301123610e-01 -1.042918477292079427e+00 7.895630167397404975e-01 7.365349008232530137e-01 1.000000000000000000e+00 +-7.376874409881049033e-01 -1.676269779291318152e+00 3.038915653181071264e-01 3.815783221132517666e-01 1.000000000000000000e+00 +2.020417832005577119e-01 -8.318013766256664443e-01 9.283262885744929527e-01 9.140131901782538870e-01 1.000000000000000000e+00 +3.586633205653357859e-01 -1.984500746264286075e-01 9.283262885744929527e-01 7.365349008232530137e-01 1.000000000000000000e+00 +3.586633205653357859e-01 -4.095671752928415343e-01 9.283262885744929527e-01 9.140131901782538870e-01 1.000000000000000000e+00 +1.141771007389221770e+00 -4.095671752928415343e-01 9.977079244918689582e-01 9.140131901782538870e-01 1.000000000000000000e+00 +-5.810659036233283281e-01 -1.254035577958492187e+00 9.574665756597874888e-02 5.590566114682525845e-01 1.000000000000000000e+00 +3.586633205653357859e-01 -6.206842759592544612e-01 8.589446526571163920e-01 9.140131901782538870e-01 1.000000000000000000e+00 diff --git a/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/moons.txt b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/moons.txt new file mode 100644 index 0000000000..47a32da3d1 --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/moons.txt @@ -0,0 +1,100 @@ +4.011115133762360174e-01 1.590278148651119994e-01 1.000000000000000000e+00 +4.575819075107570022e-01 8.041737079620360218e-01 0.000000000000000000e+00 +1.164462491869930061e-01 6.675915718078609951e-01 0.000000000000000000e+00 +8.919547200202939674e-01 3.821512162685389846e-01 1.000000000000000000e+00 +1.179233044385910034e-01 7.903273701667790085e-01 0.000000000000000000e+00 +5.290988087654110439e-01 7.314714789390559524e-01 0.000000000000000000e+00 +4.132365882396700218e-01 2.924386262893680088e-01 1.000000000000000000e+00 +6.546121835708620385e-01 4.103422462940219950e-01 0.000000000000000000e+00 +2.642016112804410066e-01 8.212707042694089576e-01 0.000000000000000000e+00 +7.385842204093929775e-01 6.370430439710600412e-02 1.000000000000000000e+00 +2.935258150100710228e-01 6.180745959281920276e-01 1.000000000000000000e+00 +6.140079498291020066e-01 7.589566055684999861e-03 1.000000000000000000e+00 +9.176263213157650478e-01 3.831354379653930109e-01 1.000000000000000000e+00 +4.300386309623719927e-01 8.505838513374329724e-01 0.000000000000000000e+00 +7.500934004783630371e-01 7.029006630182299542e-02 1.000000000000000000e+00 +2.342466115951540029e-01 1.000000953674319959e+00 0.000000000000000000e+00 +5.834711194038390003e-01 1.550683826208109906e-01 1.000000000000000000e+00 +3.159057796001429885e-01 8.957415819168089710e-01 0.000000000000000000e+00 +7.488205432891850144e-01 1.052983924746510036e-01 1.000000000000000000e+00 +5.084586143493650123e-01 1.546515673398969892e-01 1.000000000000000000e+00 +4.218871891498570115e-01 7.531512379646300159e-01 0.000000000000000000e+00 +6.343901157379150391e-01 5.697919800877600249e-02 1.000000000000000000e+00 +9.245810508728029564e-01 4.046880304813390006e-01 1.000000000000000000e+00 +5.998508334159849964e-01 4.454343914985660069e-01 0.000000000000000000e+00 +9.468706846237180397e-01 5.198460817337039463e-01 1.000000000000000000e+00 +3.411930203437810172e-01 9.422042965888980381e-01 0.000000000000000000e+00 +4.133777022361759812e-01 1.669690310955050105e-01 1.000000000000000000e+00 +4.740540385246280186e-01 7.283512353897100278e-01 0.000000000000000000e+00 +5.971856713294979579e-01 4.181247353553769752e-01 0.000000000000000000e+00 +4.162031114101409912e-01 3.513433933258059971e-01 1.000000000000000000e+00 +9.296716749668100521e-02 6.062612533569340378e-01 0.000000000000000000e+00 +2.670714855194090132e-01 9.254829883575439453e-01 0.000000000000000000e+00 +6.650617718696589797e-01 5.113456398248700241e-02 1.000000000000000000e+00 +5.889605730771999698e-02 5.399901866912839576e-01 0.000000000000000000e+00 +5.496156811714170143e-01 2.087311893701550014e-01 1.000000000000000000e+00 +2.709029912948610064e-01 9.019712805747990281e-01 0.000000000000000000e+00 +1.118648424744610043e-01 8.006678223609919876e-01 0.000000000000000000e+00 +9.965954348444999375e-03 4.859770834445950038e-01 0.000000000000000000e+00 +8.310848474502560146e-01 1.610267907381059960e-01 1.000000000000000000e+00 +9.547966718673710496e-01 3.654518425464630127e-01 1.000000000000000000e+00 +6.056703329086300380e-01 2.740092948079100171e-02 1.000000000000000000e+00 +9.627804160118099697e-01 4.941197931766510010e-01 1.000000000000000000e+00 +2.251293882727600087e-02 2.750225961208340175e-01 0.000000000000000000e+00 +6.564611792564389869e-01 4.596029222011570048e-01 0.000000000000000000e+00 +6.718327403068540260e-01 4.299556314945219837e-01 0.000000000000000000e+00 +1.213351488113399990e-01 7.348242998123170056e-01 0.000000000000000000e+00 +5.552020072937009498e-01 6.180184334516500289e-02 1.000000000000000000e+00 +6.702484488487240322e-01 4.355140626430509809e-01 0.000000000000000000e+00 +9.458698034286500134e-01 5.087803602218630150e-01 1.000000000000000000e+00 +6.582403779029849522e-01 6.083387732505799450e-01 0.000000000000000000e+00 +1.000000953674319959e+00 4.457997977733609840e-01 1.000000000000000000e+00 +5.611504912376400478e-01 6.627915501594540126e-01 0.000000000000000000e+00 +9.249354004859919876e-01 3.435919582843779962e-01 1.000000000000000000e+00 +2.930009067058559902e-01 7.761875391006469727e-01 0.000000000000000000e+00 +7.908365130424499512e-01 1.886903494596479935e-01 1.000000000000000000e+00 +1.888298392295839900e-01 8.303870558738709606e-01 0.000000000000000000e+00 +5.357410311698910244e-01 1.417464464902880028e-01 1.000000000000000000e+00 +8.134583830833439499e-01 1.563831418752670011e-01 1.000000000000000000e+00 +1.783042699098590089e-01 6.707832217216489479e-01 0.000000000000000000e+00 +8.601800203323359817e-01 2.860515117645259786e-01 1.000000000000000000e+00 +4.044354557991030052e-01 3.550909459590910200e-01 1.000000000000000000e+00 +5.725147202611000075e-02 6.542432904243470349e-01 0.000000000000000000e+00 +5.846989750862120472e-01 6.306179165840150036e-01 0.000000000000000000e+00 +4.974109306931499830e-02 5.056774020195009545e-01 0.000000000000000000e+00 +5.431554913520809658e-01 6.709472537040710449e-01 0.000000000000000000e+00 +1.721331924200059926e-01 7.652425169944759853e-01 0.000000000000000000e+00 +5.856622457504270240e-01 5.804976224899289772e-01 0.000000000000000000e+00 +8.619057536125179775e-01 3.006010651588439941e-01 1.000000000000000000e+00 +6.691037416458129883e-01 4.928275644779209763e-01 0.000000000000000000e+00 +8.698500394821170323e-01 2.214711010456089924e-01 1.000000000000000000e+00 +4.164281785488129772e-01 9.512299895286560059e-01 0.000000000000000000e+00 +8.898538537322999570e-03 3.849079310894010231e-01 0.000000000000000000e+00 +3.915156424045559969e-01 2.490620762109760078e-01 1.000000000000000000e+00 +1.840998530387880083e-01 7.641628384590150036e-01 0.000000000000000000e+00 +6.721290946006780453e-01 5.878306627273559570e-01 0.000000000000000000e+00 +4.433862566947939787e-01 1.520695239305500102e-01 1.000000000000000000e+00 +8.091065287590030186e-01 1.180164963006969936e-01 1.000000000000000000e+00 +5.444716811180120297e-01 7.264912724494929641e-01 0.000000000000000000e+00 +3.956750929355620228e-01 3.140463232994080145e-01 1.000000000000000000e+00 +7.981764078140259899e-01 2.127292901277539894e-01 1.000000000000000000e+00 +9.770510792732239880e-01 6.679542660713200242e-01 1.000000000000000000e+00 +5.908265113830569737e-01 6.602926254272459827e-01 0.000000000000000000e+00 +3.582223355770109974e-01 5.890402197837829590e-01 1.000000000000000000e+00 +1.824602484703060079e-01 7.419059276580810547e-01 0.000000000000000000e+00 +5.220882296562200375e-01 7.541199773550000263e-02 1.000000000000000000e+00 +3.349952399730680153e-01 3.150256574153900146e-01 1.000000000000000000e+00 +8.858425021171569824e-01 4.118901491165160023e-01 1.000000000000000000e+00 +5.048267841339110218e-01 7.912409901618959740e-01 0.000000000000000000e+00 +5.409889221191409581e-01 1.751023530960079955e-01 1.000000000000000000e+00 +9.876127839088439941e-01 5.637462735176089756e-01 1.000000000000000000e+00 +5.521961450576780006e-01 1.622613519430159967e-01 1.000000000000000000e+00 +6.216816306114200108e-01 1.556816045194900008e-02 1.000000000000000000e+00 +9.465795755386350319e-01 3.844772577285769932e-01 1.000000000000000000e+00 +6.305994391441349656e-01 4.664319753646850031e-01 0.000000000000000000e+00 +4.693745076656339887e-01 1.769391894340520133e-01 1.000000000000000000e+00 +3.336153626441960007e-01 8.805221319198609509e-01 0.000000000000000000e+00 +3.593181967735289972e-01 3.811520040035250023e-01 1.000000000000000000e+00 +1.652869582176209884e-01 7.615084052085879796e-01 0.000000000000000000e+00 +3.366923332214359910e-01 4.541445374488830011e-01 1.000000000000000000e+00 +3.631200194358830124e-01 5.126598477363589756e-01 1.000000000000000000e+00 diff --git a/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_test.txt b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_test.txt new file mode 100644 index 0000000000..06e2a6fea4 --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_test.txt @@ -0,0 +1,6 @@ +0 0 0 0 0 +0 0 1 1 0 +1 0 1 0 0 +1 1 1 0 1 +1 1 0 0 0 +1 1 0 1 1 diff --git a/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_train.txt b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_train.txt new file mode 100644 index 0000000000..c7c292fe9b --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/parity_train.txt @@ -0,0 +1,10 @@ +0 0 0 1 1 +0 0 1 0 1 +0 1 0 0 1 +0 1 0 1 0 +0 1 1 0 0 +0 1 1 1 1 +1 0 0 0 1 +1 0 0 1 0 +1 0 1 1 1 +1 1 1 1 0 diff --git a/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/sine.txt b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/sine.txt new file mode 100644 index 0000000000..5fd4324592 --- /dev/null +++ b/demonstrations_v2/tutorial_variational_classifier/variational_classifier/data/sine.txt @@ -0,0 +1,50 @@ +-1.126864863386807247e-01 -3.708159070558681991e-01 +-3.301285119623154074e-01 -7.792427358570894746e-01 +5.437373320144804900e-01 1.054098341344321677e+00 +8.070486641099010594e-01 5.322417543552232511e-01 +5.553805663845667873e-01 9.748881583874732248e-01 +-5.410705356289753354e-01 -1.088048456912506401e+00 +1.893558075959462794e-01 4.734526482250831503e-01 +9.678158481574299365e-01 -1.302588669256566356e-01 +-4.132705878660354326e-01 -8.624334642190834010e-01 +7.441549921477224050e-01 6.913377941518736858e-01 +4.469660457997881586e-01 9.705948747955092104e-01 +-6.136369141113018344e-01 -9.692711387142799451e-01 +4.670053467858530372e-01 9.794716239939255109e-01 +-4.636229179649002941e-01 -1.067035807027205019e+00 +-4.144634067458823345e-01 -1.073031690217886913e+00 +8.926497505367123608e-01 4.513380225653021993e-01 +-4.707999105020499453e-01 -9.070550006430189161e-01 +-2.955472983520031072e-01 -8.105567691326949209e-01 +4.489277896741954432e-01 9.912920882721452109e-01 +5.395111789663880053e-01 7.497494800446058427e-01 +-2.930728511891524946e-01 -8.775278941715457526e-01 +-4.414068665763015709e-01 -1.035242467712504055e+00 +-8.658362970419448690e-01 -5.025929184370131475e-01 +-4.623850544961565223e-01 -1.018235000340985685e+00 +-7.903110212330584083e-01 -6.166949200402385145e-01 +4.522388321405566014e-01 9.907633078698372397e-01 +5.591535563019804922e-01 1.293713718446708461e+00 +-2.729254939268668245e-01 -7.150978992492591502e-01 +5.059821246925562122e-01 1.255142372251238703e+00 +-5.446543389406808267e-01 -8.935765856770312432e-01 +2.033681443502215469e-01 7.077886573326038766e-01 +-6.167002732651516439e-02 3.920109089734935548e-02 +5.796850819998449200e-01 8.524126682567517710e-01 +-7.616987714988283642e-01 -6.177300174846972469e-01 +8.948506937348801316e-01 2.999365564998681677e-01 +5.513585080104186353e-01 9.581782265612324156e-01 +6.491408843476647039e-01 7.160916397847562065e-01 +-7.900171418051258332e-01 -5.593765518859867836e-01 +2.394985801571656303e-01 6.554242891274077998e-01 +8.679560164674415823e-02 1.610122233486118160e-01 +-7.433439703240112273e-01 -7.275447775287638974e-01 +-1.382534942429427538e-01 -4.198394461597458882e-01 +3.912144661285357206e-01 8.593492644115249268e-01 +4.966753976963111938e-03 -3.796030501145489927e-02 +-6.740040411272392173e-01 -8.949238679856507117e-01 +7.765303413106914299e-01 6.702556171354185910e-01 +8.948328359406956256e-01 2.300049796612450848e-01 +-5.147167398986964848e-01 -1.167565123752561052e+00 +9.288003148748003035e-02 2.212325623087216531e-01 +9.092683767247877746e-02 3.603936964894627892e-01 diff --git a/demonstrations_v2/tutorial_vqe/demo.py b/demonstrations_v2/tutorial_vqe/demo.py new file mode 100644 index 0000000000..5a26167517 --- /dev/null +++ b/demonstrations_v2/tutorial_vqe/demo.py @@ -0,0 +1,292 @@ +r""" +A brief overview of VQE +======================= + +.. meta:: + :property="og:description": Find the ground state of a Hamiltonian using the + variational quantum eigensolver algorithm. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/pes_h2.png + +.. related:: + + tutorial_quantum_chemistry Building molecular Hamiltonians + vqe_parallel VQE with parallel QPUs with Rigetti + tutorial_vqe_qng Accelerating VQE with quantum natural gradient + tutorial_vqe_spin_sectors VQE in different spin sectors + tutorial_vqt Variational quantum thermalizer + +*Author: Alain Delgado — Posted: 08 February 2020. Last updated: 29 August 2023.* + +The Variational Quantum Eigensolver (VQE) is a flagship algorithm for quantum chemistry +using near-term quantum computers [#peruzzo2014]_. It is an application of the +`Ritz variational principle `_, where a quantum +computer is trained to prepare the ground state of a given molecule. + +The inputs to the VQE algorithm are a molecular Hamiltonian and a +parametrized circuit preparing the quantum state of the molecule. Within VQE, the +cost function is defined as the expectation value of the Hamiltonian computed in the +trial state. The ground state of the target Hamiltonian is obtained by performing an +iterative minimization of the cost function. The optimization is carried out +by a classical optimizer which leverages a quantum computer to evaluate the cost function +and calculate its gradient at each optimization step. + +In this tutorial you will learn how to implement the VQE algorithm in a few lines of code. +As an illustrative example, we use it to find the ground state of the hydrogen +molecule, :math:`\mathrm{H}_2.` First, we build the molecular Hamiltonian using a minimal +basis set approximation. Next, we design the quantum circuit preparing the trial +state of the molecule, and the cost function to evaluate the expectation value +of the Hamiltonian. Finally, we select a classical optimizer, initialize the +circuit parameters, and run the VQE algorithm using a PennyLane simulator. + +Let's get started! + +Building the electronic Hamiltonian +----------------------------------- +The first step is to specify the molecule we want to simulate. We will use +the electronic Hamiltonian of the hydrogen molecule. A wide variety of molecular +data, including Hamiltonians, is available on the +`PennyLane Datasets service `__. This data can +be downloaded using the :func:`~.pennylane.data.load` function: +""" + +from jax import numpy as np +import jax +jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_enable_x64', True) + +import pennylane as qml + +dataset = qml.data.load('qchem', molname="H2")[0] +H, qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) +print("Number of qubits = ", qubits) +print("The Hamiltonian is ", H) + +############################################################################## +# For more details on quantum datasets, check out the +# `Quantum Datasets `__ +# documentation. +# +# The outputs of the function are the Hamiltonian, represented as +# a linear combination of Pauli operators, and the number of qubits +# required for the quantum simulations. For this example, we use a +# `minimal basis set `_ +# to represent the `molecular orbitals `_. +# In this approximation, we have four spin orbitals, which defines the +# number of qubits. Furthermore, we use the Jordan-Wigner +# transformation [#seeley2012]_ to perform the fermionic-to-qubit mapping of +# the Hamiltonian. +# +# For a more comprehensive discussion on how to build the Hamiltonian of more +# complicated molecules, see the tutorial :doc:`tutorial_quantum_chemistry`. +# +# .. note:: +# +# You can also manually construct the Hamiltonian using the following code: +# +# .. code-block:: python +# +# symbols = ["H", "H"] +# coordinates = np.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) +# molecule = qml.qchem.Molecule(symbols, coordinates) +# H, qubits = qml.qchem.molecular_hamiltonian(molecule) +# +# Implementing the VQE algorithm +# ------------------------------ +# From here on, we can use PennyLane as usual, employing its entire stack of +# algorithms and optimizers. We begin by defining the device, in this case PennyLane’s +# standard qubit simulator: + +dev = qml.device("lightning.qubit", wires=qubits) + +############################################################################## +# Next, we need to define the quantum circuit that prepares the trial state of the +# molecule. We want to prepare states of the form, +# +# .. math:: +# \vert \Psi(\theta) \rangle = \cos(\theta/2)~|1100\rangle -\sin(\theta/2)~|0011\rangle, +# +# where :math:`\theta` is the variational parameter to be optimized in order to find +# the best approximation to the true ground state. In the Jordan-Wigner [#seeley2012]_ encoding, +# the first term :math:`|1100\rangle` represents the `Hartree-Fock (HF) state +# `_ where the two electrons in +# the molecule occupy the lowest-energy orbitals. The second term :math:`|0011\rangle` +# encodes a double excitation of the HF state where the two particles are excited from +# qubits 0, 1 to 2, 3. +# +# The quantum circuit to prepare the trial state :math:`\vert \Psi(\theta) \rangle` is +# schematically illustrated in the figure below. +# +# | +# +# .. figure:: /_static/demonstration_assets/variational_quantum_eigensolver/sketch_circuit.png +# :width: 50% +# :align: center +# +# | +# +# In this figure, the gate :math:`G^{(2)}` corresponds to the +# :class:`~.pennylane.DoubleExcitation` operation, implemented in PennyLane +# as a `Givens rotation `_, which couples +# the four-qubit states :math:`\vert 1100 \rangle` and :math:`\vert 0011 \rangle.` +# For more details on how to use the excitation operations to build +# quantum circuits for quantum chemistry applications see the +# tutorial :doc:`tutorial_givens_rotations`. +# +# Implementing the circuit above using PennyLane is straightforward. First, we use the +# :func:`hf_state` function to generate the vector representing the Hartree-Fock state. + +electrons = 2 +hf = qml.qchem.hf_state(electrons, qubits) +print(hf) + +############################################################################## +# The ``hf`` array is used by the :class:`~.pennylane.BasisState` operation to initialize +# the qubit register. Then, we just act with the :class:`~.pennylane.DoubleExcitation` operation +# on the four qubits. The next step is to compute the expectation value +# of the molecular Hamiltonian in the trial state prepared by the circuit. +# We do this using the :func:`~.expval` function. The decorator syntax allows us to +# run the cost function as an executable QNode with the gate parameter :math:`\theta:` + +@qml.qnode(dev, interface="jax") +def circuit(param, wires): + qml.BasisState(hf, wires=wires) + qml.DoubleExcitation(param, wires=[0, 1, 2, 3]) + return qml.expval(H) + + +############################################################################## +# We can now define our error function simply as the expected value calculated above: + + +def cost_fn(param): + return circuit(param, wires=range(qubits)) + +############################################################################## +# Now we proceed to minimize the cost function to find the ground state of +# the :math:`\mathrm{H}_2` molecule. To start, we need to define the classical optimizer. +# The library ``optax`` offers different `optimizers `__. +# Here we use a basic gradient-descent optimizer. +# We carry out the optimization over a maximum of 100 steps aiming to reach a +# convergence tolerance of :math:`10^{-6}` for the value of the cost function. + + +import optax + +max_iterations = 100 +conv_tol = 1e-06 + +opt = optax.sgd(learning_rate=0.4) + +############################################################################## +# We initialize the circuit parameter :math:`\theta` to zero, meaning that we start +# from the Hartree-Fock state. + +theta = np.array(0.) + +# store the values of the cost function +energy = [cost_fn(theta)] + +# store the values of the circuit parameter +angle = [theta] + +opt_state = opt.init(theta) + +for n in range(max_iterations): + + gradient = jax.grad(cost_fn)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + + angle.append(theta) + energy.append(cost_fn(theta)) + + conv = np.abs(energy[-1] - energy[-2]) + + if n % 2 == 0: + print(f"Step = {n}, Energy = {energy[-1]:.8f} Ha") + + if conv <= conv_tol: + break + +print("\n" f"Final value of the ground-state energy = {energy[-1]:.8f} Ha") +print("\n" f"Optimal value of the circuit parameter = {angle[-1]:.4f}") + +############################################################################## +# Let's plot the values of the ground state energy of the molecule +# and the gate parameter :math:`\theta` as a function of the optimization step. + +import matplotlib.pyplot as plt + +fig = plt.figure() +fig.set_figheight(5) +fig.set_figwidth(12) + +# Full configuration interaction (FCI) energy computed classically +E_fci = -1.136189454088 + +# Add energy plot on column 1 +ax1 = fig.add_subplot(121) +ax1.plot(range(n + 2), energy, "go", ls="dashed") +ax1.plot(range(n + 2), np.full(n + 2, E_fci), color="red") +ax1.set_xlabel("Optimization step", fontsize=13) +ax1.set_ylabel("Energy (Hartree)", fontsize=13) +ax1.text(0.5, -1.1176, r"$E_\mathrm{HF}$", fontsize=15) +ax1.text(0, -1.1357, r"$E_\mathrm{FCI}$", fontsize=15) +plt.xticks(fontsize=12) +plt.yticks(fontsize=12) + +# Add angle plot on column 2 +ax2 = fig.add_subplot(122) +ax2.plot(range(n + 2), angle, "go", ls="dashed") +ax2.set_xlabel("Optimization step", fontsize=13) +ax2.set_ylabel("Gate parameter $\\theta$ (rad)", fontsize=13) +plt.xticks(fontsize=12) +plt.yticks(fontsize=12) + +plt.subplots_adjust(wspace=0.3, bottom=0.2) +plt.show() + +############################################################################## +# In this case, the VQE algorithm converges after thirteen iterations. The optimal +# value of the circuit parameter :math:`\theta^* = 0.208` defines the state +# +# .. math:: +# \vert \Psi(\theta^*) \rangle = 0.994~\vert 1100 \rangle - 0.104~\vert 0011 \rangle, +# +# which is precisely the ground state of the :math:`\mathrm{H}_2` molecule in a +# minimal basis set approximation. +# +# Conclusion +# ---------- +# In this tutorial, we have implemented the VQE algorithm to find the ground state +# of the hydrogen molecule. We used a simple circuit to prepare quantum states of +# the molecule beyond the Hartree-Fock approximation. The ground-state energy +# was obtained by minimizing a cost function defined as the expectation value of the +# molecular Hamiltonian in the trial state. +# +# The VQE algorithm can be used to simulate other chemical phenomena. +# In the tutorial :doc:`tutorial_vqe_bond_dissociation`, we use VQE to explore the +# potential energy surface of molecules to simulate chemical reactions. +# Another interesting application is to probe the lowest-lying states of molecules +# in specific sectors of the Hilbert space. For example, see the tutorial +# :doc:`tutorial_vqe_spin_sectors`. Furthermore, the algorithm presented here can be +# generalized to find the equilibrium geometry of a molecule as it is demonstrated in the +# tutorial :doc:`tutorial_mol_geo_opt`. +# +# .. _vqe_references: +# +# References +# ---------- +# +# .. [#peruzzo2014] +# +# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# .. [#seeley2012] +# +# Jacob T. Seeley, Martin J. Richard, Peter J. Love. "The Bravyi-Kitaev transformation for +# quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). +# `__ +# diff --git a/demonstrations_v2/tutorial_vqe/metadata.json b/demonstrations_v2/tutorial_vqe/metadata.json new file mode 100644 index 0000000000..f0d3ef7279 --- /dev/null +++ b/demonstrations_v2/tutorial_vqe/metadata.json @@ -0,0 +1,80 @@ +{ + "title": "A brief overview of VQE", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2020-02-08T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_brief_overview_of_VQE.png" + } + ], + "seoDescription": "Find the ground state of a Hamiltonian using the variational quantum eigensolver algorithm.", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "seeley2012", + "type": "article", + "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", + "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", + "year": "2012", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "vqe_parallel", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_spin_sectors", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqt", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/3_Hydrogen_Molecule_geometry_with_VQE/3_Hydrogen_Molecule_geometry_with_VQE.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/a-brief-overview-of-vqe-demo/7333" +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqe/requirements.in b/demonstrations_v2/tutorial_vqe/requirements.in new file mode 100644 index 0000000000..0aae374bda --- /dev/null +++ b/demonstrations_v2/tutorial_vqe/requirements.in @@ -0,0 +1,5 @@ +jax +jaxlib +matplotlib +optax +pennylane diff --git a/demonstrations_v2/tutorial_vqe_qng/demo.py b/demonstrations_v2/tutorial_vqe_qng/demo.py new file mode 100644 index 0000000000..81c1f919cb --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_qng/demo.py @@ -0,0 +1,475 @@ +r""" +Accelerating VQEs with quantum natural gradient +=============================================== + +.. meta:: + :property="og:description": Accelerating variational quantum eigensolvers + using quantum natural gradients in PennyLane. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/qng_example.png + +.. related:: + + tutorial_vqe A brief overview of VQE + tutorial_quantum_natural_gradient Quantum natural gradient + +*Authors: Maggie Li, Lana Bozanic, Sukin Sim — Posted: 06 November 2020. Last updated: 29 August 2023.* + +This tutorial showcases how one can apply quantum natural gradients (QNG) [#stokes2019]_ [#yamamoto2019]_ +to accelerate the optimization step of the Variational Quantum Eigensolver (VQE) algorithm [#peruzzo2014]_. +We will implement two small examples: estimating the ground state energy of a single-qubit VQE +problem, which we can visualize using the Bloch sphere, and the hydrogen molecule. + +Before going through this tutorial, we recommend that readers refer to the +:doc:`QNG tutorial ` and +:doc:`VQE tutorial ` for overviews +of quantum natural gradient and the variational quantum eigensolver algorithm, respectively. +Let's get started! + + +Single-qubit VQE example +------------------------ + +The first step is to import the required libraries and packages: +""" + +import matplotlib.pyplot as plt +from pennylane import numpy as np +import pennylane as qml + +############################################################################## +# For this simple example, we consider the following single-qubit Hamiltonian: :math:`\sigma_x + \sigma_z.` +# +# We define the device: + +dev = qml.device("default.qubit", wires=1) + + +############################################################################## +# For the variational ansatz, we use two single-qubit rotations, which the user may recognize +# from a previous :doc:`tutorial ` on qubit rotations. + + +def circuit(params, wires=0): + qml.RX(params[0], wires=wires) + qml.RY(params[1], wires=wires) + + +############################################################################## +# We then define our cost function which supports the computation of +# block-diagonal or diagonal approximations to the Fubini-Study metric tensor [#stokes2019]_. This tensor is a +# crucial component for optimizing with quantum natural gradients. + +coeffs = [1, 1] +obs = [qml.PauliX(0), qml.PauliZ(0)] + +H = qml.Hamiltonian(coeffs, obs) + +@qml.qnode(dev, interface="autograd") +def cost_fn(params): + circuit(params) + return qml.expval(H) + +############################################################################## +# To analyze the performance of quantum natural gradient on VQE calculations, +# we set up and execute optimizations using the ``GradientDescentOptimizer`` (which does not +# utilize quantum gradients) and the ``QNGOptimizer`` that uses the block-diagonal approximation +# to the metric tensor. +# +# To perform a fair comparison, we fix the initial parameters for the two optimizers. + +init_params = np.array([3.97507603, 3.00854038], requires_grad=True) + + +############################################################################## +# We will carry out each optimization over a maximum of 500 steps. As was done in the VQE +# tutorial, we aim to reach a convergence tolerance of around :math:`10^{-6}.` +# We use a step size of 0.01. + +max_iterations = 500 +conv_tol = 1e-06 +step_size = 0.01 + +############################################################################## +# First, we carry out the VQE optimization using the standard gradient descent method. + +opt = qml.GradientDescentOptimizer(stepsize=step_size) + +params = init_params + +gd_param_history = [params] +gd_cost_history = [] + +for n in range(max_iterations): + + # Take step + params, prev_energy = opt.step_and_cost(cost_fn, params) + gd_param_history.append(params) + gd_cost_history.append(prev_energy) + + energy = cost_fn(params) + + # Calculate difference between new and old energies + conv = np.abs(energy - prev_energy) + + if n % 20 == 0: + print( + "Iteration = {:}, Energy = {:.8f} Ha, Convergence parameter = {" + ":.8f} Ha".format(n, energy, conv) + ) + + if conv <= conv_tol: + break + +print() +print("Final value of the energy = {:.8f} Ha".format(energy)) +print("Number of iterations = ", n) + +############################################################################## +# We then repeat the process for the optimizer employing quantum natural gradients: + +opt = qml.QNGOptimizer(stepsize=step_size, approx="block-diag") + +params = init_params + +qngd_param_history = [params] +qngd_cost_history = [] + +for n in range(max_iterations): + + # Take step + params, prev_energy = opt.step_and_cost(cost_fn, params) + qngd_param_history.append(params) + qngd_cost_history.append(prev_energy) + + # Compute energy + energy = cost_fn(params) + + # Calculate difference between new and old energies + conv = np.abs(energy - prev_energy) + + if n % 20 == 0: + print( + "Iteration = {:}, Energy = {:.8f} Ha, Convergence parameter = {" + ":.8f} Ha".format(n, energy, conv) + ) + + if conv <= conv_tol: + break + +print() +print("Final value of the energy = {:.8f} Ha".format(energy)) +print("Number of iterations = ", n) + +############################################################################## +# Visualizing the results +# ^^^^^^^^^^^^^^^^^^^^^^^ +# +# For single-qubit examples, we can visualize the optimization process in several ways. +# +# For example, we can track the energy history: + +plt.style.use("seaborn-v0_8") +plt.plot(gd_cost_history, "b", label="Gradient descent") +plt.plot(qngd_cost_history, "g", label="Quantum natural gradient descent") + +plt.ylabel("Cost function value") +plt.xlabel("Optimization steps") +plt.legend() +plt.show() + +############################################################################## +# Or we can visualize the optimization path in the parameter space using a contour plot. +# Energies at different grid points have been pre-computed, and they can be downloaded by +# clicking :download:`here<../_static/demonstration_assets/vqe_qng/param_landscape.npy>`. + +# Discretize the parameter space +theta0 = np.linspace(0.0, 2.0 * np.pi, 100) +theta1 = np.linspace(0.0, 2.0 * np.pi, 100) + +# Load energy value at each point in parameter space +parameter_landscape = np.load("vqe_qng/param_landscape.npy") + +# Plot energy landscape +fig, axes = plt.subplots(figsize=(6, 6)) +import matplotlib as mpl +cmap = mpl.colormaps["coolwarm"] +contour_plot = plt.contourf(theta0, theta1, parameter_landscape, cmap=cmap) +plt.xlabel(r"$\theta_0$") +plt.ylabel(r"$\theta_1$") + +# Plot optimization path for gradient descent. Plot every 10th point. +gd_color = "g" +plt.plot( + np.array(gd_param_history)[::10, 0], + np.array(gd_param_history)[::10, 1], + ".", + color=gd_color, + linewidth=1, + label="Gradient descent", +) +plt.plot( + np.array(gd_param_history)[:, 0], + np.array(gd_param_history)[:, 1], + "-", + color=gd_color, + linewidth=1, +) + +# Plot optimization path for quantum natural gradient descent. Plot every 10th point. +qngd_color = "k" +plt.plot( + np.array(qngd_param_history)[::10, 0], + np.array(qngd_param_history)[::10, 1], + ".", + color=qngd_color, + linewidth=1, + label="Quantum natural gradient descent", +) +plt.plot( + np.array(qngd_param_history)[:, 0], + np.array(qngd_param_history)[:, 1], + "-", + color=qngd_color, + linewidth=1, +) + +plt.legend() +plt.show() + +############################################################################## +# Here, the blue regions indicate states with lower energies, and the red regions indicate +# states with higher energies. We can see that the ``QNGOptimizer`` takes a more direct +# route to the minimum in larger strides compared to the path taken by the ``GradientDescentOptimizer``. +# +# Lastly, we can visualize the same optimization paths on the Bloch sphere using routines +# from `QuTiP `__. The result should look like the following: +# +# .. figure:: /_static/demonstration_assets/vqe_qng/opt_paths_bloch.png +# :width: 50% +# :align: center +# +# where again the black markers and line indicate the path taken by the ``QNGOptimizer``, +# and the green markers and line indicate the path taken by the ``GradientDescentOptimizer``. +# Using this visualization method, we can clearly see how the path using the ``QNGOptimizer`` tightly +# "hugs" the curvature of the Bloch sphere and takes the shorter path. +# +# Now, we will move onto a more interesting example: estimating the ground state energy +# of molecular hydrogen. +# +# Hydrogen VQE Example +# -------------------- +# +# To construct our system Hamiltonian, we can use `PennyLane Datasets `__ to obtain the dataset for a :math:`\text{H}_2` molecule. + +dataset = qml.data.load('qchem',molname="H2", bondlength=0.7)[0] +hamiltonian, qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) +hamiltonian_coeffs, hamiltonian_ops = hamiltonian.terms() + +print("Number of qubits = ", qubits) + + +############################################################################## +# For our ansatz, we use the circuit from the +# `VQE tutorial `__ +# but expand out the arbitrary single-qubit rotations to elementary +# gates (RZ-RY-RZ). + +dev = qml.device("default.qubit", wires=qubits) +hf_state = np.array([1, 1, 0, 0], requires_grad=False) + +def ansatz(params, wires=[0, 1, 2, 3]): + qml.BasisState(hf_state, wires=wires) + for i in wires: + qml.RZ(params[3 * i], wires=i) + qml.RY(params[3 * i + 1], wires=i) + qml.RZ(params[3 * i + 2], wires=i) + qml.CNOT(wires=[2, 3]) + qml.CNOT(wires=[2, 0]) + qml.CNOT(wires=[3, 1]) + + +############################################################################## +# Note that the qubit register has been initialized to :math:`|1100\rangle,` which encodes for +# the Hartree-Fock state of the hydrogen molecule described in the minimal basis. +# Again, we define the cost function to be the following QNode that measures ``expval(H)``: + +@qml.qnode(dev, interface="autograd") +def cost(params): + ansatz(params) + return qml.expval(hamiltonian) + +############################################################################## +# For this problem, we can compute the exact value of the +# ground state energy via exact diagonalization. We provide the value below using the dataset. + +exact_value = dataset.fci_energy # -1.1361895496530567 + + +############################################################################## +# We now set up our optimizations runs. + +np.random.seed(0) +init_params = np.random.uniform(low=0, high=2 * np.pi, size=12, requires_grad=True) +max_iterations = 500 +step_size = 0.5 +conv_tol = 1e-06 + +############################################################################## +# As was done with our previous VQE example, we run the standard gradient descent +# optimizer. + +opt = qml.GradientDescentOptimizer(step_size) + +params = init_params + +gd_cost = [] + +for n in range(max_iterations): + params, prev_energy = opt.step_and_cost(cost, params) + gd_cost.append(prev_energy) + + energy = cost(params) + conv = np.abs(energy - prev_energy) + + if n % 20 == 0: + print( + "Iteration = {:}, Energy = {:.8f} Ha".format(n, energy) + ) + + if conv <= conv_tol: + break + + +print() +print("Final convergence parameter = {:.8f} Ha".format(conv)) +print("Number of iterations = ", n) +print("Final value of the ground-state energy = {:.8f} Ha".format(energy)) +print( + "Accuracy with respect to the FCI energy: {:.8f} Ha ({:.8f} kcal/mol)".format( + np.abs(energy - exact_value), np.abs(energy - exact_value) * 627.503 + ) +) +print() +print("Final circuit parameters = \n", params) + + +############################################################################## +# Next, we run the optimizer employing quantum natural gradients. We also need to make the +# Hamiltonian coefficients non-differentiable by setting ``requires_grad=False``. + +hamiltonian = qml.Hamiltonian(np.array(hamiltonian_coeffs, requires_grad=False), hamiltonian_ops) + +opt = qml.QNGOptimizer(step_size, lam=0.001, approx="block-diag") + +params = init_params +prev_energy = cost(params) +qngd_cost = [] + +for n in range(max_iterations): + params, prev_energy = opt.step_and_cost(cost, params) + qngd_cost.append(prev_energy) + + energy = cost(params) + conv = np.abs(energy - prev_energy) + + if n % 4 == 0: + print( + "Iteration = {:}, Energy = {:.8f} Ha".format(n, energy) + ) + + if conv <= conv_tol: + break + + +print("\nFinal convergence parameter = {:.8f} Ha".format(conv)) +print("Number of iterations = ", n) +print("Final value of the ground-state energy = {:.8f} Ha".format(energy)) +print( + "Accuracy with respect to the FCI energy: {:.8f} Ha ({:.8f} kcal/mol)".format( + np.abs(energy - exact_value), np.abs(energy - exact_value) * 627.503 + ) +) +print() +print("Final circuit parameters = \n", params) + +############################################################################## +# Visualizing the results +# ^^^^^^^^^^^^^^^^^^^^^^^ +# +# To evaluate the performance of our two optimizers, we can compare: (a) the +# number of steps it takes to reach our ground state estimate and (b) the quality of our ground +# state estimate by comparing the final optimization energy to the exact value. + +plt.style.use("seaborn-v0_8") +plt.plot(np.array(gd_cost) - exact_value, "g", label="Gradient descent") +plt.plot(np.array(qngd_cost) - exact_value, "k", label="Quantum natural gradient descent") +plt.yscale("log") +plt.ylabel("Energy difference") +plt.xlabel("Step") +plt.legend() +plt.show() + +############################################################################## +# We see that by employing quantum natural gradients, it takes fewer steps +# to reach a ground state estimate and the optimized energy achieved by +# the optimizer is lower than that obtained using vanilla gradient descent. +# + +############################################################################## +# Robustness in parameter initialization +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# While results above show a more rapid convergence for quantum natural gradients, +# what if we were just lucky, i.e., we started at a "good" point in parameter space? +# How do we know this will be the case with high probability regardless of the +# parameter initialization? +# +# Using the same system Hamiltonian, ansatz, and device, we tested the robustness +# of the ``QNGOptimizer`` by running 10 independent trials with random parameter initializations. +# For this numerical test, our optimizer does not terminate based on energy improvement; we fix the number of +# iterations to 200. +# We show the result of this test below (after pre-computing), where we plot the mean and standard +# deviation of the energies over optimization steps for quantum natural gradient and standard gradient descent. +# +# .. figure:: ../_static/demonstration_assets/vqe_qng/k_runs_.png +# :align: center +# :width: 60% +# :target: javascript:void(0) +# +# We observe that quantum natural gradient on average converges faster for this system. +# +# .. note:: +# +# While using QNG may help accelerate the VQE algorithm in terms of optimization steps, +# each QNG step is more costly than its vanilla gradient descent counterpart due to +# a greater number of calls to the quantum computer that are needed to compute the Fubini-Study metric tensor. +# +# While further benchmark studies are needed to better understand the advantages +# of quantum natural gradient, preliminary studies such as this tutorial show the potentials +# of the method. 🎉 +# + +############################################################################## +# +# References +# -------------- +# +# .. [#stokes2019] +# +# Stokes, James, *et al.*, "Quantum Natural Gradient". +# `arXiv preprint arXiv:1909.02108 (2019). +# `__ +# +# .. [#yamamoto2019] +# +# Yamamoto, Naoki, "On the natural gradient for variational quantum eigensolver". +# `arXiv preprint arXiv:1909.05074 (2019). +# `__ +# +# .. [#peruzzo2014] +# +# Alberto Peruzzo, Jarrod McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# diff --git a/demonstrations_v2/tutorial_vqe_qng/metadata.json b/demonstrations_v2/tutorial_vqe_qng/metadata.json new file mode 100644 index 0000000000..37d6c550eb --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_qng/metadata.json @@ -0,0 +1,71 @@ +{ + "title": "Accelerating VQEs with quantum natural gradient", + "authors": [ + { + "username": "mli" + }, + { + "username": "lbozanic" + }, + { + "username": "ssim" + } + ], + "dateOfPublication": "2020-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_accelerating_VQEs.png" + } + ], + "seoDescription": "Accelerating variational quantum eigensolvers using quantum natural gradients in PennyLane.", + "doi": "", + "references": [ + { + "id": "stokes2019", + "type": "article", + "title": "Quantum Natural Gradient", + "authors": "Stokes, James, et al.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.02108" + }, + { + "id": "yamamoto2019", + "type": "article", + "title": "On the natural gradient for variational quantum eigensolver", + "authors": "Yamamoto, Naoki", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.05074" + }, + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqe_qng/requirements.in b/demonstrations_v2/tutorial_vqe_qng/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_qng/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_vqe_qng/vqe_qng/param_landscape.npy b/demonstrations_v2/tutorial_vqe_qng/vqe_qng/param_landscape.npy new file mode 100644 index 0000000000000000000000000000000000000000..99e6c8cd27286c22ae0b63d63d4d6ea5ce57feda GIT binary patch literal 80128 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 SGRKO`_, +the eigenstates of the molecular Hamiltonian can be calculated for specific values of the +spin quantum numbers. This allows us to compute quantities such as the energy of the electronic +states in different sectors of the total-spin projection :math:`S_z.` This is illustrated in the +figure below for the energy spectrum of the hydrogen molecule. In this case, the ground state has +total spin :math:`S=0` while the lowest-lying excited states, with total spin :math:`S=1,` form +a triplet related to the spin components :math:`S_z=-1, 0, 1.` + +| + +.. figure:: /_static/demonstration_assets/vqe_spin_sectors/energy_spectra_h2_sto3g.png + :width: 70% + :align: center + +| + +In this tutorial we demonstrate how to run VQE simulations to find the lowest-energy states +of the hydrogen molecule in different spin sectors. First, we show how to +build the electronic Hamiltonian and the total spin operator :math:`\hat{S}^2.` Next, we use +excitation operations, implemented in PennyLane as Givens rotations [#qchemcircuits]_, to prepare +the trial states of the molecule. In order to probe the molecular states with different total spin, +:math:`S=0` and :math:`S=1,` we apply excitation operations which preserve and modify, respectively, +the total-spin projection of the initial state encoded in the qubit register. Finally, we run +the VQE algorithm to compute the energy of the states. + +Let's get started! + +Building the Hamiltonian and the total spin operator :math:`\hat{S}^2` +---------------------------------------------------------------------- +First, we need to specify the structure of the molecule. This is done by providing a list +with the symbols of the constituent atoms and a one-dimensional array with the corresponding +nuclear coordinates in `atomic units `_. +""" + +from pennylane import numpy as np + +symbols = ["H", "H"] +coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) + +############################################################################## +# The geometry of the molecule can also be imported from an external file using +# the :func:`~.pennylane.qchem.read_structure` function. +# +# Now, we can build the electronic Hamiltonian. We use a minimal `basis set +# `_ approximation to represent +# the `molecular orbitals `_. Then, +# the qubit Hamiltonian of the molecule is built using the +# :func:`~.pennylane.qchem.molecular_hamiltonian` function. + +import pennylane as qml + +H, qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates) + +print("Number of qubits = ", qubits) +print("The Hamiltonian is ", H) + +############################################################################## +# The outputs of the function are the Hamiltonian and the number of qubits +# required for the quantum simulations. For the :math:`\mathrm{H}_2` molecule in a minimal +# basis, we have four molecular **spin**-orbitals, which defines the number of qubits. +# +# The :func:`~.pennylane.qchem.molecular_hamiltonian` function allows us to define +# additional keyword arguments to simulate more complicated molecules. For more details +# take a look at the tutorial :doc:`tutorial_quantum_chemistry`. +# +# We also want to build the total spin operator :math:`\hat{S}^2,` +# +# .. math:: +# +# \hat{S}^2 = \frac{3}{4} N + \sum_{\alpha, \beta, \gamma, \delta} +# \langle \alpha, \beta \vert \hat{s}_1 \cdot \hat{s}_2 +# \vert \gamma, \delta \rangle ~ \hat{c}_\alpha^\dagger \hat{c}_\beta^\dagger +# \hat{c}_\gamma \hat{c}_\delta. +# +# In the equation above, :math:`N` is the number of electrons, +# :math:`\hat{c}` and :math:`\hat{c}^\dagger` are respectively the electron annihilation and +# creation operators, and +# :math:`\langle \alpha, \beta \vert \hat{s}_1 \cdot \hat{s}_2 \vert \gamma, \delta \rangle` +# is the `matrix element of the two-body spin operator +# `_ +# :math:`\hat{s}_1 \cdot \hat{s}_2` in the basis of spin orbitals. +# +# We use the :func:`~.pennylane.qchem.obs.spin2` function to build the +# :math:`\hat{S}^2` observable. + +electrons = 2 +S2 = qml.qchem.spin2(electrons, qubits) +print(S2) + +############################################################################## +# Building the quantum circuit to find the ground state +# ----------------------------------------------------- +# We build the variational circuit for the ground state of the hydrogen molecule using the +# :class:`~.pennylane.SingleExcitation` and :class:`~.pennylane.DoubleExcitation` operations +# which act as Givens rotations on subspaces of two and four qubits, respectively +# [#qchemcircuits]_. The total number of excitation operations is given by +# all possible single and double excitations of the Hartree-Fock reference state. The indices +# of the qubits they act on correspond to the spin-orbitals involved in each excitation. +# For more details on how to use the excitation operations see the +# tutorial :doc:`tutorial_givens_rotations`. +# +# First, we use the :func:`~.pennylane.qchem.hf_state` +# function to generate the vector representing the Hartree-Fock state +# :math:`\vert 1100 \rangle` of the :math:`\mathrm{H}_2` molecule. + +hf = qml.qchem.hf_state(electrons, qubits) +print(hf) + +############################################################################## +# Next, we use the :func:`~.pennylane.qchem.excitations` +# function to generate all single and double excitations of the Hartree-Fock state. +# This function allows us to define the keyword argument ``delta_sz`` +# to specify the total-spin projection of the excitations with respect to the reference +# state. This is illustrated in the figure below. +# +# | +# +# .. figure:: /_static/demonstration_assets/vqe_spin_sectors/fig_excitations.png +# :width: 100% +# :align: center +# +# | +# +# Therefore, for the ground state of the :math:`\mathrm{H}_2` molecule we choose +# ``delta_sz = 0``. + +singles, doubles = qml.qchem.excitations(electrons, qubits, delta_sz=0) +print(singles) +print(doubles) + +############################################################################## +# The output lists ``singles`` and ``doubles`` contain the qubit indices involved in the +# single and double excitations. Even and odd indices correspond, respectively, to spin-up +# and spin-down orbitals. For ``delta_sz = 0`` we have two single excitations, one from qubit +# 0 to 2 and the other from qubit 1 to 3, and one double excitation from qubits 0, 1 to 2, 3. +# In order to build the variational circuit, we apply the corresponding +# excitation operations. This can be done straightforwardly using the +# :class:`~.pennylane.templates.subroutines.AllSinglesDoubles` template. + + +def circuit(params, wires): + qml.AllSinglesDoubles(params, wires, hf, singles, doubles) + + +############################################################################## +# The circuit above prepares trial states for the :math:`\mathrm{H}_2` molecule of the form: +# +# .. math:: +# +# \vert \Psi(\theta) \rangle = +# c_\mathrm{HF}(\theta) \vert 1100 \rangle +# + c_{0123}(\theta) \vert 0011 \rangle +# + c_{02}(\theta) \vert 0110 \rangle +# + c_{13}(\theta) \vert 1001 \rangle, +# +# where the coefficients :math:`c` are functions of the variational parameters +# :math:`\theta = (\theta_1, \theta_2, \theta_3)` to be optimized by the VQE algorithm. +# Note that the prepared state :math:`\vert \Psi(\theta) \rangle` is a superposition of the +# Hartree-Fock (HF) state with all possible single and double excitations that preserve +# the spin projection of the HF reference state. + +############################################################################## +# Running the VQE simulation +# -------------------------- +# Now we proceed to optimize the variational parameters. First, we define the device, +# in this case a qubit simulator: + +dev = qml.device("lightning.qubit", wires=qubits) + +############################################################################## +# Next, we define the cost function as the following QNode, where we make use of +# the :func:`~.pennylane.expval` function to compute the expectation value of the hamiltonian. +# This requires specifying the circuit, the target Hamiltonian, and the device. It returns +# a cost function that can be evaluated with the circuit parameters: + + +@qml.qnode(dev, interface="autograd") +def cost_fn(params): + circuit(params, wires=range(qubits)) + return qml.expval(H) + + +############################################################################## +# As a reminder, we also built the total spin operator :math:`\hat{S}^2` for which +# we can now define a function to compute its expectation value: + + +@qml.qnode(dev, interface="autograd") +def S2_exp_value(params): + circuit(params, wires=range(qubits)) + return qml.expval(S2) + + +############################################################################## +# The total spin :math:`S` of the trial state can be obtained from the +# expectation value :math:`\langle \hat{S}^2 \rangle` as +# +# .. math:: +# +# S = -\frac{1}{2} + \sqrt{\frac{1}{4} + \langle \hat{S}^2 \rangle}. +# +# We define a function to compute the total spin. + + +def total_spin(params): + return -0.5 + np.sqrt(1 / 4 + S2_exp_value(params)) + + +############################################################################## +# Now, we proceed to minimize the cost function to find the ground state. We define +# the classical optimizer and initialize the circuit parameters. + +opt = qml.GradientDescentOptimizer(stepsize=0.8) +np.random.seed(0) # for reproducibility +theta = np.random.normal(0, np.pi, len(singles) + len(doubles), requires_grad=True) +print(theta) + +############################################################################## +# We carry out the optimization over a maximum of 100 steps, aiming to reach a +# convergence tolerance of :math:`10^{-6}` for the value of the cost function. + +max_iterations = 100 +conv_tol = 1e-06 + +for n in range(max_iterations): + + theta, prev_energy = opt.step_and_cost(cost_fn, theta) + + energy = cost_fn(theta) + spin = total_spin(theta) + + conv = np.abs(energy - prev_energy) + + if n % 4 == 0: + print(f"Step = {n}, Energy = {energy:.8f} Ha, S = {spin:.4f}") + + if conv <= conv_tol: + break + +print("\n" f"Final value of the ground-state energy = {energy:.8f} Ha") +print("\n" f"Optimal value of the circuit parameters = {theta}") + +############################################################################## +# As a result, we have estimated the lowest-energy state of the hydrogen molecule +# with total spin :math:`S = 0` which corresponds to the ground state. +# +# Finding the lowest-lying excited state with :math:`S=1` +# ------------------------------------------------------- +# In the last part of the tutorial, we will use VQE to find the lowest-lying +# excited state of the hydrogen molecule with total spin :math:`S=1.` +# In this case, we use the :func:`~.pennylane.qchem.excitations` function to generate +# excitations whose total-spin projection differs by the quantity ``delta_sz=1`` +# with respect to the Hartree-Fock state. + +singles, doubles = qml.qchem.excitations(electrons, qubits, delta_sz=1) +print(singles) +print(doubles) + +############################################################################## +# For the :math:`\mathrm{H}_2` molecule in a minimal basis set there are no +# double excitations, but only a spin-flip single excitation from qubit 1 to 2. +# In this case, the circuit will contain only one :class:`~.pennylane.SingleExcitation` +# operation. Additionally, as we want to probe the excited state of the hydrogen molecule, +# we initialize the qubit register to the state :math:`\vert 0011 \rangle.` + + +def circuit(params, wires): + qml.AllSinglesDoubles(params, wires, np.flip(hf), singles, doubles) + + +############################################################################## +# This allows us to prepare trial states of the form +# +# .. math:: +# +# \vert \Psi(\theta) \rangle = c_{03}(\theta) \vert 0101 \rangle +# + c_{0123}(\theta) \vert 0011 \rangle, +# +# where the first term :math:`\vert 0101 \rangle` encodes a spin-flip excitation with +# :math:`S_z=-1` and the second term is a double excitation with :math:`S_z=0.` +# Since an eigenstate of the electronic Hamiltonian cannot contain a superposition of +# states with different total-spin projections, the double excitation coefficient +# should vanish as the VQE algorithm minimizes the cost function. The optimized state will +# correspond to the lowest-energy state with spin quantum numbers :math:`S=1, S_z=-1.` +# +# Now, we define the new functions to compute the expectation values of the Hamiltonian +# and the total spin operator for the new circuit. + + +@qml.qnode(dev, interface="autograd") +def cost_fn(params): + circuit(params, wires=range(qubits)) + return qml.expval(H) + + +@qml.qnode(dev, interface="autograd") +def S2_exp_value(params): + circuit(params, wires=range(qubits)) + return qml.expval(S2) + + +############################################################################## +# Finally, we generate the new set of initial parameters, and proceed with the VQE algorithm to +# optimize the variational circuit. + +np.random.seed(0) +theta = np.random.normal(0, np.pi, len(singles) + len(doubles), requires_grad=True) + +max_iterations = 100 +conv_tol = 1e-06 + +for n in range(max_iterations): + + theta, prev_energy = opt.step_and_cost(cost_fn, theta) + + energy = cost_fn(theta) + spin = total_spin(theta) + + conv = np.abs(energy - prev_energy) + + if n % 4 == 0: + print(f"Step = {n}, Energy = {energy:.8f} Ha, S = {spin:.4f}") + + if conv <= conv_tol: + break + +print("\n" f"Final value of the energy = {energy:.8f} Ha") +print("\n" f"Optimal value of the circuit parameters = {theta}") + +############################################################################## +# As expected, the VQE algorithms has found the lowest-energy state with total spin +# :math:`S=1` which is an excited state of the hydrogen molecule. +# +# Conclusion +# ---------- +# In this tutorial we have used the standard VQE algorithm to find the ground and the +# lowest-lying excited states of the hydrogen molecule. We used the single and +# double excitation operations, implemented as Givens rotations, +# to prepare the trial states of a molecule. We also showed how to build +# the total-spin operator :math:`\hat{S}^2` and used it to compute the total spin +# of the optimized states. By choosing the total-spin projection of the +# generated excitations, we were able to probe the lowest-energy eigenstates of the +# molecular Hamiltonian in different sectors of the spin quantum numbers. +# +# References +# ---------- +# +# .. [#peruzzo2014] +# +# A. Peruzzo, J. McClean *et al.*, "A variational eigenvalue solver on a photonic +# quantum processor". `Nature Communications 5, 4213 (2014). +# `__ +# +# .. [#qchemcircuits] +# +# J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran. +# "Universal quantum circuits for quantum chemistry". `arXiv:2106.13839, (2021) +# `__ +# diff --git a/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json b/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json new file mode 100644 index 0000000000..72502a258c --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "VQE in different spin sectors", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2020-10-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_VQE_different_spin_sectors.png" + } + ], + "seoDescription": "Find the lowest-energy states of a Hamiltonian in different spin sectors", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "A. Peruzzo, J. McClean et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "qchemcircuits", + "type": "article", + "title": "Universal quantum circuits for quantum chemistry", + "authors": "J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran.", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2106.13839" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "vqe_parallel", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in b/demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in new file mode 100644 index 0000000000..18b28256af --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in @@ -0,0 +1 @@ +pennylane diff --git a/demonstrations_v2/tutorial_vqe_vqd/demo.py b/demonstrations_v2/tutorial_vqe_vqd/demo.py new file mode 100644 index 0000000000..f560c260fe --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_vqd/demo.py @@ -0,0 +1,225 @@ +r"""How to implement VQD with PennyLane +=============================================================== + +Finding the eigenvalues of a Hamiltonian is a key task in quantum computing. Algorithms such as the variational quantum eigensolver (VQE) are used to find the smallest +eigenvalue, but sometimes we are interested in other eigenvalues. Here we will show you how to implement the variational +quantum deflation (VQD) algorithm in PennyLane +and find the first excited state energy of the `hydrogen molecule `__. To benefit the most from this tutorial, we recommend +a familiarization with the `variational quantum eigensolver (VQE) algorithm `__ first. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_how_to_vqd_pennylane.png + :align: center + :width: 70% + :target: javascript:void(0) + +""" + +###################################################################### +# +# Variational quantum deflation +# ------------------------------ +# +# The VQD algorithm [#Vqd]_ is a method used to find the excited states of a quantum system. +# It is related to the `VQE algorithm `__, which is often used to find the ground state energy of a quantum system. +# The main idea of the VQE algorithm is to define a quantum state ansatz that depends on adjustable parameters :math:`\theta` and minimize the energy of the system, computed as: +# +# .. math:: C_0(\theta) = \left\langle\Psi_0 (\theta)|\hat H |\Psi_0 (\theta) \right\rangle, +# +# where :math:`\Psi_0(\theta)` is the ansatz. However, this is not enough if we are interested in states other than the ground state. +# We must find a function whose minimum is no longer the ground state energy but gives the next excited state. +# This is possible by just adding a penalty term to the above function, which accounts for the orthogonality of the states: +# +# .. math:: C_1(\theta) = \left\langle\Psi(\theta)|\hat H |\Psi (\theta) \right\rangle + \beta | \left\langle \Psi (\theta)| \Psi_0 \right\rangle|^2, +# +# where :math:`\beta` is a hyperparameter that controls the penalty term and :math:`| \Psi \rangle` is the excited state. +# Note that :math:`\beta` should be larger than the energy gap between the ground and excited states. +# This function can now be minimized to give the first excited state energy. +# Similarly, we could iteratively calculate the :math:`k`-th excited states by adding the corresponding penalty term to the previous :math:`k - 1` excited state. +# +# As easy as that! Let's see how we can run this using PennyLane. +# +# +# Finding the ground state +# ------------------------------------------- +# +# To implement the VQD algorithm, we first need to know the ground state of our system, and it is a breeze to use the data from `PennyLane Datasets `__ to obtain the Hamiltonian and the ground state +# of the hydrogen molecule: +# +# .. note:: +# +# To improve viewability of this tutorial, we will suppress any ``ComplexWarning``'s which may be raised during optimization. +# The warnings do not impact the correctness of the results, but make it harder to view outputs. +# + +import pennylane as qml +import numpy as np + +import warnings +warnings.filterwarnings(action="ignore", category=np.ComplexWarning) + +# Load the dataset +h2 = qml.data.load("qchem", molname="H2", bondlength=0.742, basis="STO-3G")[0] + +# Extract the Hamiltonian +H, n_qubits = h2.hamiltonian, len(h2.hamiltonian.wires) + + +# Obtain the ground state from the operations given by the dataset +def generate_ground_state(wires): + qml.BasisState(np.array(h2.hf_state), wires=wires) + + for op in h2.vqe_gates: # use the gates data from the dataset + op = qml.map_wires(op, {op.wires[i]: wires[i] for i in range(len(wires))}) + qml.apply(op) + +###################################################################### +# The ``generate_ground_state`` function prepares the ground state of the molecule using the data obtained from the dataset. +# Let's use it to check the energy of that state: +# + +dev = qml.device("default.qubit") + +@qml.qnode(dev) +def circuit(): + generate_ground_state(range(n_qubits)) + return qml.expval(H) + +print(f"Ground state energy: {circuit()}") + +###################################################################### +# We now use the ground state to find the first excited state. +# +# Finding the excited state +# ---------------------------- +# +# To obtain the excited state we must define our ansatz that generates the state :math:`|\Psi(\theta)\rangle.` +# +# We use an ansatz constructed with :doc:`Givens rotations `, and we define the circuit for finding the excited state. +# + +from functools import partial + +# This line is added to better visualise the circuit +@partial(qml.devices.preprocess.decompose, stopping_condition = lambda obj:False, max_expansion=1) + +def ansatz(theta, wires): + singles, doubles = qml.qchem.excitations(2, n_qubits) + singles = [[wires[i] for i in single] for single in singles] + doubles = [[wires[i] for i in double] for double in doubles] + qml.AllSinglesDoubles(theta, wires, np.array([1,1,0,0]), singles, doubles) + +theta = np.random.rand(3) # 3 parameters for the ansatz +print(qml.draw(ansatz, decimals = 2)(theta, range(4))) + +###################################################################### +# The ``ansatz`` function is the one that generates the state :math:`|\Psi(\theta)\rangle.` +# The next step is to calculate the overlap between our generated state and the ground state, using a technique +# known as `swap test `__. + + +@qml.qnode(dev) +def swap_test(params): + generate_ground_state(range(1, n_qubits + 1)) + ansatz(params, range(n_qubits + 1, 2 * n_qubits + 1)) + + qml.Barrier() # added to better visualise the circuit + qml.Hadamard(wires=0) + for i in range(n_qubits): + qml.CSWAP(wires=[0, 1 + i + n_qubits, 1 + i]) + qml.Hadamard(wires=0) + return qml.expval(qml.Z(0)) + +print(qml.draw(swap_test)(theta)) +print(f"\nOverlap between the ground state and the ansatz: {swap_test(theta)}") + +###################################################################### +# The ``swap_test`` function returns the overlap between the generated state and the ground state. +# +# With this we have all the ingredients to define the loss function that we want to minimize: +# + +@qml.qnode(dev) +def expected_value(theta): + ansatz(theta, range(n_qubits)) + return qml.expval(H) + +def loss_f(theta, beta): + return expected_value(theta) + beta * swap_test(theta) + +###################################################################### +# The ``loss_f`` function returns the value of the cost function. +# The next step is to optimize the parameters of the ansatz to minimize the cost function. + +import jax +import optax + +jax.config.update("jax_platform_name", "cpu") +jax.config.update("jax_enable_x64", True) +print() + +theta = jax.numpy.array([0.1, 0.2, 0.3]) +beta = 2 + +# Store the values of the cost function +energy = [loss_f(theta, beta)] + +conv_tol = 1e-6 +max_iterations = 100 + +opt = optax.sgd(learning_rate=0.4) + +# Store the values of the circuit parameter +angle = [theta] + +opt_state = opt.init(theta) + +for n in range(max_iterations): + gradient = jax.grad(loss_f)(theta, beta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + angle.append(theta) + energy.append(loss_f(theta, beta)) + + conv = jax.numpy.abs(energy[-1] - energy[-2]) + + if n % 5 == 0: + print(f"Step = {n}, Energy = {energy[-1]:.8f} Ha") + + if conv <= conv_tol: + break + +print(f"\nEstimated energy: {energy[-1].real:.8f}") + +###################################################################### +# Great! We have found a new energy value, but is this the right one? +# One way to check is to access the eigenvalues of the Hamiltonian directly: + +print(np.sort(np.linalg.eigvals(H.matrix()))) + +###################################################################### +# We have indeed found an eigenvalue of the Hamiltonian. It may seem that we have skipped the value :math:`-0.5389,` +# however the eigenvector corresponding to this eigenvalue belongs to a different particle number sector. +# The correct energy value for the first excited state of hydrogen is :math:`-0.53320939,` consistent with what we obtained with VQD! +# We have successfully found the first excited state! +# +# Conclusion +# ---------- +# +# VQD is a variational method that can be used for calculating low-level excited state energies of quantum systems. Leveraging the +# orthogonality of the eigenstates, it adds a regularization penalty to the cost function to allow the search for +# the next excited state from the ground state discovered by VQE. +# +# In this tutorial we delved into the capabilities of variational quantum deflation (VQD) using PennyLane to compute +# the excited states of a hydrogen molecule. +# This illustrated how advanced quantum algorithms can extend beyond basic applications, +# offering deeper insights into quantum systems. We invite you to continue exploring these techniques and find more interesting use cases. +# +# References +# ---------- +# +# .. [#Vqd] +# +# Higgott, Oscar and Wang, Daochen and Brierley, Stephen +# "Variational Quantum Computation of Excited States" +# `Quantum 3, 156 (2019) `__. +# diff --git a/demonstrations_v2/tutorial_vqe_vqd/metadata.json b/demonstrations_v2/tutorial_vqe_vqd/metadata.json new file mode 100644 index 0000000000..41b72b5703 --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_vqd/metadata.json @@ -0,0 +1,56 @@ +{ + "title": "How to implement VQD with PennyLane", + "authors": [ + { + "username": "mchau" + }, + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-08-26T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_vqd_pennylane.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_vqd_pennylane.png" + } + ], + "seoDescription": "Learn how to use variational quantum deflation (VQD) to find the first excited state energy of the hydrogen molecule.", + "doi": "", + "references": [ + { + "id": "Higgott2019variationalquantum", + "type": "article", + "title": "Variational Quantum Computation of Excited States", + "authors": "Higgott, Oscar and Wang, Daochen and Brierley, Stephen", + "year": "2019", + "journal": "Quantum", + "url": "https://doi.org/10.22331/q-2019-07-01-156" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + } + ], + "hardware": [] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqe_vqd/requirements.in b/demonstrations_v2/tutorial_vqe_vqd/requirements.in new file mode 100644 index 0000000000..492121497d --- /dev/null +++ b/demonstrations_v2/tutorial_vqe_vqd/requirements.in @@ -0,0 +1,5 @@ +jax +jaxlib +numpy +optax +pennylane diff --git a/demonstrations_v2/tutorial_vqls/demo.py b/demonstrations_v2/tutorial_vqls/demo.py new file mode 100644 index 0000000000..2bd1bd0a4e --- /dev/null +++ b/demonstrations_v2/tutorial_vqls/demo.py @@ -0,0 +1,530 @@ +r""" +.. _vqls: + +Variational Quantum Linear Solver +==================================== + +.. meta:: + :property="og:description": Implementing the variational + quantum linear solver to solve a system of linear equation with a quantum device. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqls_zoom.png + +.. related:: + + tutorial_coherent_vqls Coherent Variational Quantum Linear Solver + +*Author: Andrea Mari — Posted: 04 November 2019. Last updated: 20 January 2021.* + +In this tutorial we implement a quantum algorithm known as the *variational quantum linear +solver* (VQLS), originally introduced in +`Bravo-Prieto et al. (2019) `_. + +.. figure:: ../_static/demonstration_assets/vqls/vqls_circuit.png + :align: center + :width: 100% + :target: javascript:void(0) + +Introduction +------------ + +We first define the problem and the general structure of a VQLS. +As a second step, we consider a particular case and we solve it explicitly with PennyLane. + +The problem +^^^^^^^^^^^ + +We are given a :math:`2^n \times 2^n` matrix :math:`A` which can be expressed as a linear +combination of :math:`L` unitary matrices :math:`A_0, A_1, \dots A_{L-1},` i.e., + +.. math:: + + A = \sum_{l=0}^{L-1} c_l A_l, + +where :math:`c_l` are arbitrary complex numbers. Importantly, we assume that each of the +unitary components :math:`A_l` can be efficiently implemented with a quantum circuit +acting on :math:`n` qubits. + +We are also given a normalized complex vector in the physical form of a quantum +state :math:`|b\rangle,` which can be generated by a unitary operation :math:`U` +applied to the ground state of :math:`n` qubits. , i.e., + +.. math:: + + |b\rangle = U |0\rangle, + +where again we assume that :math:`U` can be efficiently implemented with a quantum circuit. + +The problem that we aim to solve is that of preparing a quantum state :math:`|x\rangle,` such that +:math:`A |x\rangle` is proportional to :math:`|b\rangle` or, equivalently, such that + +.. math:: + + |\Psi\rangle := \frac{A |x\rangle}{\sqrt{\langle x |A^\dagger A |x\rangle}} \approx |b\rangle. + + +Variational quantum linear solver +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The approach used in a VQLS is to approximate the solution :math:`|x\rangle` with a variational quantum +circuit, i.e., a unitary circuit :math:`V` depending on a finite number of classical real parameters +:math:`w = (w_0, w_1, \dots):` + +.. math:: + + |x \rangle = V(w) |0\rangle. + +The parameters should be optimized in order to maximize the overlap between the quantum states +:math:`|\Psi\rangle` and :math:`|b\rangle.` This suggests to define the following cost function: + +.. math:: + + C_G = 1- |\langle b | \Psi \rangle|^2, + +such that its minimization with respect to the variational parameters should lead towards the problem solution. + +Now we discuss two alternative methods which could be used to experimentally solve the minimization problem. + +First method +>>>>>>>>>>>>> + +Let us write :math:`C_G` more explicitly: + +.. math:: + + C_G = 1- \frac{ \sum_{l, l'} c_l c_{l'}^* \langle 0| V^\dagger A_{l'}^\dagger U \color{blue}{|0\rangle \langle 0|} U^\dagger A_l V |0\rangle} + {\sum_{l,l'} c_l c_{l'}^* \langle 0| V^\dagger A_{l'}^\dagger A_l V |0\rangle} . + +All expectation values of the previous expression could be estimated with a +`Hadamard test `_, +which is a standard quantum computation technique. This method however might be experimentally challenging since it requires us to apply +all the unitaries (:math:`U^\dagger, A_l` and :math:`V`) in a controlled way, +i.e., conditioned on the state of an ancillary qubit. A possible workaround for estimating the same expectation values in a simpler +way has been proposed in Ref. [1], but will not be considered here. + +Second method +>>>>>>>>>>>>> + +The second method, which is the one used in this tutorial, is to minimize a "local" version of the cost function which is easier to +measure and, at the same time, leads to the same optimal solution. +This local cost function, originally proposed in Ref. [1], can be obtained by replacing the blue-colored projector +:math:`\color{blue}{|0\rangle\langle 0|}` in the previous expression with the following positive operator: + +.. math:: + + \color{blue}{P} = \frac{1}{2} + \frac{1}{2n}\sum_{j=0}^{n-1} Z_j, + +where :math:`Z_j` is the Pauli :math:`Z` operator locally applied to the :math:`j\rm{th}` qubit. This gives a new cost function: + +.. math:: + + C_L = 1- \frac{ \sum_{l, l'} c_l c_{l'}^* \langle 0| V^\dagger A_{l'}^\dagger U \color{blue}{P} U^\dagger A_l V |0\rangle} + {\sum_{l,l'} c_l c_{l'}^* \langle 0| V^\dagger A_{l'}^\dagger A_l V |0\rangle}, + +which, as shown in Ref. [1], satisfies + +.. math:: + + C_G \rightarrow 0 \Leftrightarrow C_L \rightarrow 0, + +and so we can solve our problem by minimizing :math:`C_L` instead of :math:`C_G.` + +Substituting the definition of :math:`P` into the expression for :math:`C_L` we get: + +.. math:: + + C_L + &=& \frac{1}{2} - \frac{1}{2n} \frac{ \sum_{j=0}^{n-1} \sum_{l, l'} c_l c_{l'}^* \langle 0| V^\dagger A_{l'}^\dagger U Z_j U^\dagger A_l V |0\rangle} + {\sum_{l,l'} c_l c_{l'}^* \langle 0| V^\dagger A_{l'}^\dagger A_l V |0\rangle} \\ + &&\\ + &=& \frac{1}{2} - \frac{1}{2n} \frac{ \sum_{j=0}^{n-1} \sum_{l, l'} c_l c_{l'}^* \mu_{l,l',j}} + {\sum_{l,l'} c_l c_{l'}^* \mu_{l,l',-1}}, + +which can be computed whenever we are able to measure the following coefficients + +.. math:: + \mu_{l, l', j} = \langle 0| V^\dagger A_{l'}^\dagger U Z_j U^\dagger A_l V |0\rangle, + +where we used the convention that if :math:`j=-1,` :math:`Z_{-1}` is replaced with the identity. + +Also in this case the complex coefficients :math:`\mu_{l, l', j}` can be experimentally measured with a Hadamard test. +The corresponding quantum circuit is shown in the image at the top of this tutorial. +Compared with the previous method, the main advantage of this approach is that only the unitary operations +:math:`A_l, A_l^\dagger` and :math:`Z_j` need to be controlled by an external ancillary qubit, +while :math:`V, V^\dagger, U` and :math:`U^\dagger` can be directly applied to the system. +This is particularly convenient whenever :math:`V` has a complex structure, e.g., if it is composed of +many variational layers. + +A simple example +^^^^^^^^^^^^^^^^ + +In this tutorial we consider the following simple example based on a system of 3 qubits (plus an ancilla), +which is very similar to the one experimentally tested in Ref. [1]: + +.. math:: + \begin{align} + A &= c_0 A_0 + c_1 A_1 + c_2 A_2 = \mathbb{I} + 0.2 X_0 Z_1 + 0.2 X_0, \\ + \\ + |b\rangle &= U |0 \rangle = H_0 H_1 H_2 |0\rangle, + \end{align} + +where :math:`Z_j, X_j, H_j` represent the Pauli :math:`Z,` Pauli :math:`X` and Hadamard gates applied to the qubit with index :math:`j.` + +This problem is computationally quite easy since a single layer of local rotations is enough to generate the +solution state, i.e., we can use the following simple ansatz: + +.. math:: + |x\rangle = V(w) |0\rangle = \Big [ R_y(w_0) \otimes R_y(w_1) \otimes R_y(w_2) \Big ] H_0 H_1 H_2 |0\rangle. + + +In the code presented below we solve this particular problem by minimizing the local cost function :math:`C_L.` +Eventually we will compare the quantum solution with the classical one. + +General setup +------------------------ +This Python code requires *PennyLane* and the plotting library *matplotlib*. +""" + +# Pennylane +import pennylane as qml +from pennylane import numpy as np + +# Plotting +import matplotlib.pyplot as plt + +############################################################################## +# Setting of the main hyper-parameters of the model +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +n_qubits = 3 # Number of system qubits. +n_shots = 10 ** 6 # Number of quantum measurements. +tot_qubits = n_qubits + 1 # Addition of an ancillary qubit. +ancilla_idx = n_qubits # Index of the ancillary qubit (last position). +steps = 30 # Number of optimization steps +eta = 0.8 # Learning rate +q_delta = 0.001 # Initial spread of random quantum weights +rng_seed = 0 # Seed for random number generator + + +############################################################################## +# Circuits of the quantum linear problem +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +############################################################################## +# We now define the unitary operations associated to the simple example +# presented in the introduction. +# Since we want to implement a Hadamard test, we need the unitary operations +# :math:`A_j` to be controlled by the state of an ancillary qubit. + +# Coefficients of the linear combination A = c_0 A_0 + c_1 A_1 ... +c = np.array([1.0, 0.2, 0.2]) + +def U_b(): + """Unitary matrix rotating the ground state to the problem vector |b> = U_b |0>.""" + for idx in range(n_qubits): + qml.Hadamard(wires=idx) + +def CA(idx): + """Controlled versions of the unitary components A_l of the problem matrix A.""" + if idx == 0: + # Identity operation + None + + elif idx == 1: + qml.CNOT(wires=[ancilla_idx, 0]) + qml.CZ(wires=[ancilla_idx, 1]) + + elif idx == 2: + qml.CNOT(wires=[ancilla_idx, 0]) + + +############################################################################## +# Variational quantum circuit +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# What follows is the variational quantum circuit that should generate the solution +# state :math:`|x\rangle= V(w)|0\rangle.` +# +# The first layer of the circuit is a product of Hadamard gates preparing a +# balanced superposition of all basis states. +# +# After that, we apply a very simple variational ansatz +# which is just a single layer of qubit rotations +# :math:`R_y(w_0) \otimes R_y(w_1) \otimes R_y(w_2).` +# For solving more complex problems, we suggest to use more expressive circuits as, +# e.g., the PennyLane :func:`~.StronglyEntanglingLayers` template. + + +def variational_block(weights): + """Variational circuit mapping the ground state |0> to the ansatz state |x>.""" + # We first prepare an equal superposition of all the states of the computational basis. + for idx in range(n_qubits): + qml.Hadamard(wires=idx) + + # A very minimal variational circuit. + for idx, element in enumerate(weights): + qml.RY(element, wires=idx) + + +############################################################################## +# Hadamard test +# -------------- +# +# We first initialize a PennyLane device with the ``lightning.qubit`` backend. +# +# As a second step, we define a PennyLane ``QNode`` representing a model of the actual quantum computation. +# +# The circuit is based on the +# `Hadamard test `_ +# and will be used to estimate the coefficients :math:`\mu_{l,l',j}` defined in the introduction. +# A graphical representation of this circuit is shown at the top of this tutorial. + +dev_mu = qml.device("lightning.qubit", wires=tot_qubits) + +@qml.qnode(dev_mu, interface="autograd") +def local_hadamard_test(weights, l=None, lp=None, j=None, part=None): + + # First Hadamard gate applied to the ancillary qubit. + qml.Hadamard(wires=ancilla_idx) + + # For estimating the imaginary part of the coefficient "mu", we must add a "-i" + # phase gate. + if part == "Im" or part == "im": + qml.PhaseShift(-np.pi / 2, wires=ancilla_idx) + + # Variational circuit generating a guess for the solution vector |x> + variational_block(weights) + + # Controlled application of the unitary component A_l of the problem matrix A. + CA(l) + + # Adjoint of the unitary U_b associated to the problem vector |b>. + # In this specific example Adjoint(U_b) = U_b. + U_b() + + # Controlled Z operator at position j. If j = -1, apply the identity. + if j != -1: + qml.CZ(wires=[ancilla_idx, j]) + + # Unitary U_b associated to the problem vector |b>. + U_b() + + # Controlled application of Adjoint(A_lp). + # In this specific example Adjoint(A_lp) = A_lp. + CA(lp) + + # Second Hadamard gate applied to the ancillary qubit. + qml.Hadamard(wires=ancilla_idx) + + # Expectation value of Z for the ancillary qubit. + return qml.expval(qml.PauliZ(wires=ancilla_idx)) + + +############################################################################################## +# To get the real and imaginary parts of :math:`\mu_{l,l',j},` one needs to run the previous +# quantum circuit with and without a phase-shift of the ancillary qubit. This is automatically +# done by the following function. + + +def mu(weights, l=None, lp=None, j=None): + """Generates the coefficients to compute the "local" cost function C_L.""" + + mu_real = local_hadamard_test(weights, l=l, lp=lp, j=j, part="Re") + mu_imag = local_hadamard_test(weights, l=l, lp=lp, j=j, part="Im") + + return mu_real + 1.0j * mu_imag + + +############################################################################## +# Local cost function +# ------------------------------------ +# +# Let us first define a function for estimating :math:`\langle x| A^\dagger A|x\rangle.` + + +def psi_norm(weights): + """Returns the normalization constant , where |psi> = A |x>.""" + norm = 0.0 + + for l in range(0, len(c)): + for lp in range(0, len(c)): + norm = norm + c[l] * np.conj(c[lp]) * mu(weights, l, lp, -1) + + return abs(norm) + + +############################################################################## +# We can finally define the cost function of our minimization problem. +# We use the analytical expression of :math:`C_L` in terms of the +# coefficients :math:`\mu_{l,l',j}` given in the introduction. + + +def cost_loc(weights): + """Local version of the cost function. Tends to zero when A|x> is proportional to |b>.""" + mu_sum = 0.0 + + for l in range(0, len(c)): + for lp in range(0, len(c)): + for j in range(0, n_qubits): + mu_sum = mu_sum + c[l] * np.conj(c[lp]) * mu(weights, l, lp, j) + + mu_sum = abs(mu_sum) + + # Cost function C_L + return 0.5 - 0.5 * mu_sum / (n_qubits * psi_norm(weights)) + + +############################################################################## +# Variational optimization +# ----------------------------- +# +# We first initialize the variational weights with random parameters (with a fixed seed). + +np.random.seed(rng_seed) +w = q_delta * np.random.randn(n_qubits, requires_grad=True) + +############################################################################## +# To minimize the cost function we use the gradient-descent optimizer. +opt = qml.GradientDescentOptimizer(eta) + + +############################################################################## +# We are ready to perform the optimization loop. + +cost_history = [] +for it in range(steps): + w, cost = opt.step_and_cost(cost_loc, w) + print("Step {:3d} Cost_L = {:9.7f}".format(it, cost)) + cost_history.append(cost) + + +############################################################################## +# We plot the cost function with respect to the optimization steps. +# We remark that this is not an abstract mathematical quantity +# since it also represents a bound for the error between the generated state +# and the exact solution of the problem. + +plt.style.use("seaborn") +plt.plot(cost_history, "g") +plt.ylabel("Cost function") +plt.xlabel("Optimization steps") +plt.show() + +############################################################################## +# Comparison of quantum and classical results +# ------------------------------------------- +# +# Since the specific problem considered in this tutorial has a small size, we can also +# solve it in a classical way and then compare the results with our quantum solution. +# + +############################################################################## +# Classical algorithm +# ^^^^^^^^^^^^^^^^^^^ +# To solve the problem in a classical way, we use the explicit matrix representation in +# terms of numerical NumPy arrays. + +Id = np.identity(2) +Z = np.array([[1, 0], [0, -1]]) +X = np.array([[0, 1], [1, 0]]) + +A_0 = np.identity(8) +A_1 = np.kron(np.kron(X, Z), Id) +A_2 = np.kron(np.kron(X, Id), Id) + +A_num = c[0] * A_0 + c[1] * A_1 + c[2] * A_2 +b = np.ones(8) / np.sqrt(8) + +############################################################################## +# We can print the explicit values of :math:`A` and :math:`b:` + +print("A = \n", A_num) +print("b = \n", b) + + +############################################################################## +# The solution can be computed via a matrix inversion: + +A_inv = np.linalg.inv(A_num) +x = np.dot(A_inv, b) + +############################################################################## +# Finally, in order to compare x with the quantum state :math:`|x\rangle,` we normalize +# and square its elements. +c_probs = (x / np.linalg.norm(x)) ** 2 + +############################################################################## +# Preparation of the quantum solution +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +############################################################################## +# Given the variational weights ``w`` that we have previously optimized, +# we can generate the quantum state :math:`|x\rangle.` By measuring :math:`|x\rangle` +# in the computational basis we can estimate the probability of each basis state. +# +# For this task, we initialize a new PennyLane device and define the associated +# *qnode* circuit. + +dev_x = qml.device("lightning.qubit", wires=n_qubits, shots=n_shots) + +@qml.qnode(dev_x, interface="autograd") +def prepare_and_sample(weights): + + # Variational circuit generating a guess for the solution vector |x> + variational_block(weights) + + # We assume that the system is measured in the computational basis. + # then sampling the device will give us a value of 0 or 1 for each qubit (n_qubits) + # this will be repeated for the total number of shots provided (n_shots) + return qml.sample() + + +############################################################################## +# To estimate the probability distribution over the basis states we first take ``n_shots`` +# samples and then compute the relative frequency of each outcome. + +raw_samples = prepare_and_sample(w) + +# convert the raw samples (bit strings) into integers and count them +samples = [] +for sam in raw_samples: + samples.append(int("".join(str(bs) for bs in sam), base=2)) + +q_probs = np.bincount(samples) / n_shots +############################################################################## +# Comparison +# ^^^^^^^^^^ +# +# Let us print the classical result. +print("x_n^2 =\n", c_probs) + +############################################################################## +# The previous probabilities should match the following quantum state probabilities. +print("||^2=\n", q_probs) + +############################################################################## +# Let us graphically visualize both distributions. + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(7, 4)) + +ax1.bar(np.arange(0, 2 ** n_qubits), c_probs, color="blue") +ax1.set_xlim(-0.5, 2 ** n_qubits - 0.5) +ax1.set_xlabel("Vector space basis") +ax1.set_title("Classical probabilities") + +ax2.bar(np.arange(0, 2 ** n_qubits), q_probs, color="green") +ax2.set_xlim(-0.5, 2 ** n_qubits - 0.5) +ax2.set_xlabel("Hilbert space basis") +ax2.set_title("Quantum probabilities") + +plt.show() + +############################################################################## +# References +# ---------- +# +# 1. Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles. +# "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems." +# `arXiv:1909.05820 `__, 2019. +# +# diff --git a/demonstrations_v2/tutorial_vqls/metadata.json b/demonstrations_v2/tutorial_vqls/metadata.json new file mode 100644 index 0000000000..feb4e8a6cd --- /dev/null +++ b/demonstrations_v2/tutorial_vqls/metadata.json @@ -0,0 +1,45 @@ +{ + "title": "Variational Quantum Linear Solver", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2019-11-04T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_quantum_linear_solver.png" + } + ], + "seoDescription": "Implementing the variational quantum linear solver to solve a system of linear equation with a quantum device.", + "doi": "", + "references": [ + { + "id": "BravoPrieto2019", + "type": "article", + "title": "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems.", + "authors": "Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles", + "year": "2019", + "journal": "", + "doi": "10.48550/arXiv.1909.05820", + "url": "https://arxiv.org/abs/1909.05820" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1909.05820" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_coherent_vqls", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqls/requirements.in b/demonstrations_v2/tutorial_vqls/requirements.in new file mode 100644 index 0000000000..5e98280e02 --- /dev/null +++ b/demonstrations_v2/tutorial_vqls/requirements.in @@ -0,0 +1,2 @@ +matplotlib +pennylane diff --git a/demonstrations_v2/tutorial_vqt/demo.py b/demonstrations_v2/tutorial_vqt/demo.py new file mode 100644 index 0000000000..cc5dabc7d8 --- /dev/null +++ b/demonstrations_v2/tutorial_vqt/demo.py @@ -0,0 +1,572 @@ +""" +Variational Quantum Thermalizer +=================================== + +.. meta:: + :property="og:description": Using the Variational Quantum Thermalizer to prepare the thermal state of a Heisenberg model Hamiltonian. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_vqt.png + +.. related:: + + tutorial_vqe Variational quantum eigensolver + +*Author: Jack Ceroni — Posted: 7 July 2020. Last updated: 28 January 2021.* + +This demonstration discusses theory and experiments relating to a recently proposed quantum algorithm called the +`Variational Quantum Thermalizer `__ (VQT): a generalization of the well-know +:doc:`Variational Quantum Eigensolver ` (VQE) to systems with non-zero temperatures. + +""" + +###################################################################### +# The Idea +# -------- +# + + +###################################################################### +# The goal of the VQT is to prepare +# the `thermal state `__ +# of a given Hamiltonian :math:`\hat{H}` at temperature :math:`T,` +# which is defined as +# +# .. math:: \rho_\text{thermal} \ = \ \frac{e^{- \hat{H} \beta}}{\text{Tr}(e^{- \hat{H} \beta})} \ = \ \frac{e^{- \hat{H} \beta}}{Z_{\beta}}, +# +# where :math:`\beta \ = \ 1/T.` The thermal state is a `mixed state +# `__, +# which means that can be described by an ensemble of pure states. +# Since we are attempting to learn a mixed state, we must +# deviate from the standard variational method of passing a pure state +# through an ansatz circuit, and minimizing the energy expectation. +# +# The VQT begins with an initial `density matrix +# `__, :math:`\rho_{\theta},` +# described by a probability distribution parametrized by some collection +# of parameters :math:`\theta,` and an ensemble of pure states, +# :math:`\{|\psi_i\rangle\}.` Let :math:`p_i(\theta_i)` be the +# probability corresponding to the :math:`i`-th pure state. We sample from +# this probability distribution to get some pure state +# :math:`|\psi_k\rangle,` which we pass through a parametrized circuit, +# :math:`U(\phi).` From the results of this circuit, we then calculate +# :math:`\langle \psi_k | U^{\dagger}(\phi) \hat{H}\, U(\phi) |\psi_k\rangle.` +# Repeating this process multiple times and taking the average of these +# expectation values gives us the the expectation value of :math:`\hat{H}` +# with respect to :math:`U \rho_{\theta} U^{\dagger}.` +# +# .. figure:: ../_static/demonstration_assets/vqt/ev.png +# :width: 100% +# :align: center +# +# Inputted parameters create an initial density matrix and a parametrized +# ansatz, which are used to calculate the expectation value of the Hamiltonian +# with respect to a new mixed state. +# +# Arguably, the most important part of a variational circuit is its cost +# function, which we attempt to minimize with a classical optimizer. +# In VQE, we generally try to +# minimize :math:`\langle \psi(\theta) | \hat{H} | \psi(\theta) \rangle` +# which, upon minimization, gives us a parametrized circuit that prepares +# a good approximation to the ground state of :math:`\hat{H}.` In the VQT, +# the goal is to arrive at a parametrized probability distribution, and a +# parametrized ansatz, that generate a good approximation to the thermal +# state. This generally involves more than calculating the energy +# expectation value. Luckily, we know that the thermal state of +# :math:`\hat{H}` minimizes the following free-energy cost function +# +# .. math:: \mathcal{L}(\theta, \ \phi) \ = \ \beta \ \text{Tr}( \hat{H} \ \hat{U}(\phi) \rho_{\theta} \hat{U}(\phi)^{\dagger} ) \ - \ S_\theta, +# +# where :math:`S_{\theta}` is the `von Neumann entropy +# `__ of +# :math:`U \rho_{\theta} U^{\dagger},` which is the same as the von +# Neumann entropy of :math:`\rho_{\theta}` due to invariance of entropy +# under unitary transformations. This cost function is minimized when +# :math:`\hat{U}(\phi) \rho_{\theta} \hat{U}(\phi)^{\dagger} \ = \ \rho_{\text{thermal}},` +# so similarly to VQE, we minimize it with a classical optimizer to obtain +# the target parameters, and thus the target state. +# +# .. figure:: ../_static/demonstration_assets/vqt/vqt.png +# :width: 80% +# :align: center +# +# A high-level representation of how the VQT works. +# +# All together, the outlined processes give us a general protocol to +# generate thermal states. +# + + +###################################################################### +# Simulating the VQT for a 4-Qubit Heisenberg Model +# -------------------------------------------------- +# + + +###################################################################### +# In this demonstration, we simulate the 4-qubit Heisenberg model. We can +# begin by importing the necessary dependencies. +# + + +import pennylane as qml +from matplotlib import pyplot as plt +import numpy as np +import scipy +from scipy.optimize import minimize +import networkx as nx +import seaborn +import itertools + +np.random.seed(42) +###################################################################### +# The Heisenberg Hamiltonian is defined as +# +# .. math:: \hat{H} \ = \ \displaystyle\sum_{(i, j) \in E} X_i X_j \ + \ Z_i Z_j \ + \ Y_i Y_j, +# +# where :math:`X_i,` :math:`Y_i` and :math:`Z_i` are the Pauli gates +# acting on the :math:`i`-th qubit. In addition, :math:`E` is the set of +# edges in the graph :math:`G \ = \ (V, \ E)` describing the interactions +# between the qubits. In this demonstration, we define the interaction graph to +# be the cycle graph: +# + + +interaction_graph = nx.cycle_graph(4) +nx.draw(interaction_graph) + + +###################################################################### +# With this, we can calculate the matrix representation of the Heisenberg +# Hamiltonian in the computational basis: +# + + +def create_hamiltonian_matrix(n, graph): + + matrix = np.zeros((2**n, 2**n)) + + for i in graph.edges: + x = y = z = 1 + for j in range(0, n): + if j == i[0] or j == i[1]: + x = np.kron(x, qml.matrix(qml.PauliX(0))) + y = np.kron(y, qml.matrix(qml.PauliY(0))) + z = np.kron(z, qml.matrix(qml.PauliZ(0))) + else: + x = np.kron(x, np.identity(2)) + y = np.kron(y, np.identity(2)) + z = np.kron(z, np.identity(2)) + + matrix = np.add(matrix, np.add(x, np.add(y, z))) + + return matrix + + +ham_matrix = create_hamiltonian_matrix(4, interaction_graph) + +# Prints a visual representation of the Hamiltonian matrix +seaborn.heatmap(ham_matrix.real) +plt.show() + + +###################################################################### +# With this done, we construct the VQT. We begin by defining some +# fixed variables that are used throughout the simulation: +# + + +beta = 2 # beta = 1/T +nr_qubits = 4 + + +###################################################################### +# The first step of the VQT is to create the initial density matrix, +# :math:`\rho_\theta.` In this demonstration, we let :math:`\rho_\theta` be +# *factorized*, meaning that it can be written as an uncorrelated tensor +# product of :math:`4` one-qubit density matrices that are diagonal in +# the computational basis. The motivation is that in this factorized model, +# the number of :math:`\theta_i` parameters needed to describe +# :math:`\rho_\theta` scales linearly rather than exponentially with +# the number of qubits. For each one-qubit system described by +# :math:`\rho_\theta^i,` we have +# +# .. math:: \rho_{\theta}^{i} \ = \ p_i(\theta_i) |0\rangle \langle 0| \ + \ (1 \ - \ p_i(\theta_i))|1\rangle \langle1|. +# +# From here, all we have to do is define :math:`p_i(\theta_i),` which we +# choose to be the sigmoid +# +# .. math:: p_{i}(\theta_{i}) \ = \ \frac{e^{\theta_i}}{e^{\theta_i} \ + \ 1}. +# + + +def sigmoid(x): + return np.exp(x) / (np.exp(x) + 1) + + +###################################################################### +# This is a natural choice for probability function, as it has a range of +# :math:`[0, \ 1],` meaning that we don’t need to restrict the domain of +# :math:`\theta_i` to some subset of the real numbers. With the probability +# function defined, we can write a method that gives us the diagonal +# elements of each one-qubit density matrix, for some parameters +# :math:`\theta:` +# + + +def prob_dist(params): + return np.vstack([sigmoid(params), 1 - sigmoid(params)]).T + + +###################################################################### +# Creating the Ansatz Circuit +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# + + +###################################################################### +# With this done, we can move on to defining the ansatz circuit, +# :math:`U(\phi),` composed of rotational and coupling layers. The +# rotation layer is simply ``RX``, ``RY``, and ``RZ`` +# gates applied to each qubit. We make use of the +# ``AngleEmbedding`` +# function, which allows us to easily pass parameters into rotational +# layers. +# + + +def single_rotation(phi_params, qubits): + + rotations = ["Z", "Y", "X"] + for i in range(0, len(rotations)): + qml.AngleEmbedding(phi_params[i], wires=qubits, rotation=rotations[i]) + + +###################################################################### +# To construct the general ansatz, we combine the method we have just +# defined with a collection of parametrized coupling gates placed between +# qubits that share an edge in the interaction graph. In addition, we +# define the depth of the ansatz, and the device on which the simulations +# are run: +# + + +def CRX_ring(parameters, wires): + """Apply a controlled RX on all wires in a ring pattern""" + n_wires = len(wires) + + for param, w in zip(parameters, wires): + qml.CRX(param, wires=[w % n_wires, (w + 1) % n_wires]) + + +depth = 4 +dev = qml.device("lightning.qubit", wires=nr_qubits) + + +def quantum_circuit(rotation_params, coupling_params, sample=None, return_state=False): + + # Prepares the initial basis state corresponding to the sample + qml.BasisState(sample, wires=range(nr_qubits)) + + # Prepares the variational ansatz for the circuit + for i in range(0, depth): + single_rotation(rotation_params[i], range(nr_qubits)) + CRX_ring(coupling_params[i], list(range(nr_qubits))) + + if return_state: + return qml.state() + + # Calculates the expectation value of the Hamiltonian with respect to the prepared states + return qml.expval(qml.Hermitian(ham_matrix, wires=range(nr_qubits))) + + +# Constructs the QNode +qnode = qml.QNode(quantum_circuit, dev, interface="autograd") + + +###################################################################### +# We can get an idea of what this circuit looks like by printing out a +# test circuit: +# + + +rotation_params = [[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]] for i in range(0, depth)] +coupling_params = [[1, 1, 1, 1] for i in range(0, depth)] +print( + qml.draw(qnode, level="device", show_matrices=True)( + rotation_params, coupling_params, sample=[1, 0, 1, 0] + ) +) + + +###################################################################### +# Recall that the final cost function depends not only on the expectation +# value of the Hamiltonian, but also the von Neumann entropy of the state, +# which is determined by the collection of :math:`p_i(\theta_i)`\ s. Since +# the entropy of a collection of multiple uncorrelated subsystems is the +# same as the sum of the individual values of entropy for each subsystem, +# we can sum the entropy values of each one-qubit system in the factorized +# space to get the total: +# + + +def calculate_entropy(distribution): + + total_entropy = 0 + for d in distribution: + total_entropy += -1 * d[0] * np.log(d[0]) + -1 * d[1] * np.log(d[1]) + + # Returns an array of the entropy values of the different initial density matrices + + return total_entropy + + +###################################################################### +# The Cost Function +# ^^^^^^^^^^^^^^^^^ +# + + +###################################################################### +# Finally, we combine the ansatz and the entropy function to get the cost +# function. In this demonstration, we deviate slightly from how VQT would be +# performed in practice. Instead of sampling from the probability +# distribution describing the initial mixed state, we use the ansatz to +# calculate +# :math:`\langle x_i | U^{\dagger}(\phi) \hat{H} \,U(\phi) |x_i\rangle` for +# each basis state :math:`|x_i\rangle.` We then multiply each of these +# expectation values by their corresponding :math:`(\rho_\theta)_{ii},` +# which is exactly the probability of sampling :math:`|x_i\rangle` from +# the distribution. Summing each of these terms together gives us the +# expected value of the Hamiltonian with respect to the transformed +# density matrix. +# +# In the case of this small, simple model, exact +# calculations such as this reduce the number of circuit executions, and thus the total +# execution time. +# +# You may have noticed previously that the “structure” of the +# parameters list passed into the ansatz is quite complicated. We write a +# general function that takes a one-dimensional list, and converts it into +# the nested list structure that can be inputed into the ansatz: +# + + +def convert_list(params): + + # Separates the list of parameters + dist_params = params[0:nr_qubits] + ansatz_params_1 = params[nr_qubits : ((depth + 1) * nr_qubits)] + ansatz_params_2 = params[((depth + 1) * nr_qubits) :] + + coupling = np.split(ansatz_params_1, depth) + + # Partitions the parameters into multiple lists + split = np.split(ansatz_params_2, depth) + rotation = [] + for s in split: + rotation.append(np.split(s, 3)) + + ansatz_params = [rotation, coupling] + + return [dist_params, ansatz_params] + + +###################################################################### +# We then pass this function, along with the ansatz and the entropy +# function into the final cost function: +# + + +def exact_cost(params): + + global iterations + + # Transforms the parameter list + parameters = convert_list(params) + dist_params = parameters[0] + ansatz_params = parameters[1] + + # Creates the probability distribution + distribution = prob_dist(dist_params) + + # Generates a list of all computational basis states of our qubit system + combos = itertools.product([0, 1], repeat=nr_qubits) + s = [list(c) for c in combos] + + # Passes each basis state through the variational circuit and multiplies + # the calculated energy EV with the associated probability from the distribution + cost = 0 + for i in s: + result = qnode(ansatz_params[0], ansatz_params[1], sample=i) + for j in range(0, len(i)): + result = result * distribution[j][i[j]] + cost += result + + # Calculates the entropy and the final cost function + entropy = calculate_entropy(distribution) + final_cost = beta * cost - entropy + + return final_cost + + +###################################################################### +# We then create the function that is passed into the optimizer: +# + + +def cost_execution(params): + + global iterations + + cost = exact_cost(params) + + if iterations % 50 == 0: + print("Cost at Step {}: {}".format(iterations, cost)) + + iterations += 1 + return cost + + +###################################################################### +# The last step is to define the optimizer, and execute the optimization +# method. We use the "Constrained Optimization by Linear Approximation" +# (`COBYLA `__) optimization method, +# which is a gradient-free optimizer. We observe that for this algorithm, COBYLA +# has a lower runtime than its gradient-based counterparts, so we utilize it +# in this demonstration: +# + + +iterations = 0 + +number = nr_qubits * (1 + depth * 4) +params = [np.random.randint(-300, 300) / 100 for i in range(0, number)] +out = minimize(cost_execution, x0=params, method="COBYLA", options={"maxiter": 1600}) +out_params = out["x"] + + +###################################################################### +# We can now check to see how well our optimization method performed by +# writing a function that reconstructs the transformed density +# matrix of some initial state, with respect to lists of +# :math:`\theta` and :math:`\phi` parameters: +# + + +def prepare_state(params, device): + + # Initializes the density matrix + + final_density_matrix = np.zeros((2**nr_qubits, 2**nr_qubits)) + + # Prepares the optimal parameters, creates the distribution and the bitstrings + parameters = convert_list(params) + dist_params = parameters[0] + unitary_params = parameters[1] + + distribution = prob_dist(dist_params) + + combos = itertools.product([0, 1], repeat=nr_qubits) + s = [list(c) for c in combos] + + # Runs the circuit in the case of the optimal parameters, for each bitstring, + # and adds the result to the final density matrix + + for i in s: + state = qnode(unitary_params[0], unitary_params[1], sample=i, return_state=True) + for j in range(0, len(i)): + state = np.sqrt(distribution[j][i[j]]) * state + final_density_matrix = np.add(final_density_matrix, np.outer(state, np.conj(state))) + + return final_density_matrix + + +# Prepares the density matrix +prep_density_matrix = prepare_state(out_params, dev) + + +###################################################################### +# We then display the prepared state by plotting a heatmap of the +# entry-wise absolute value of the density matrix: +# + +seaborn.heatmap(abs(prep_density_matrix)) +plt.show() + + +###################################################################### +# Numerical Calculations +# ^^^^^^^^^^^^^^^^^^^^^^ +# + + +###################################################################### +# To verify that we have in fact prepared a good approximation of the +# thermal state, let’s calculate it numerically by taking the matrix +# exponential of the Heisenberg Hamiltonian, as was outlined earlier. +# + + +def create_target(qubit, beta, ham, graph): + + # Calculates the matrix form of the density matrix, by taking + # the exponential of the Hamiltonian + + h = ham(qubit, graph) + y = -1 * float(beta) * h + new_matrix = scipy.linalg.expm(np.array(y)) + norm = np.trace(new_matrix) + final_target = (1 / norm) * new_matrix + + return final_target + + +target_density_matrix = create_target(nr_qubits, beta, create_hamiltonian_matrix, interaction_graph) + + +###################################################################### +# Finally, we can plot a heatmap of the target density matrix: +# + + +seaborn.heatmap(abs(target_density_matrix)) +plt.show() + + +###################################################################### +# The two images look very similar, which suggests that we have +# constructed a good approximation of the thermal state! Alternatively, if +# you prefer a more quantitative measure of similarity, we can calculate +# the trace distance between the two density matrices, which is defined +# as +# +# .. math:: T(\rho, \ \sigma) \ = \ \frac{1}{2} \text{Tr} \sqrt{(\rho \ - \ \sigma)^{\dagger} (\rho \ - \ \sigma)}, +# +# and is a metric on the space of density matrices: +# + + +def trace_distance(one, two): + + return 0.5 * np.trace(np.absolute(np.add(one, -1 * two))) + + +print("Trace Distance: " + str(trace_distance(target_density_matrix, prep_density_matrix))) + + +###################################################################### +# The closer to zero, the more similar the two states are. Thus, we +# have found a close approximation of the thermal state +# of :math:`H` with the VQT! +# + + +###################################################################### +# References +# ---------- +# +# 1. Verdon, G., Marks, J., Nanda, S., Leichenauer, S., & Hidary, J. +# (2019). Quantum Hamiltonian-Based Models and the Variational Quantum +# Thermalizer Algorithm. arXiv preprint +# `arXiv:1910.02071 `__. +# +# diff --git a/demonstrations_v2/tutorial_vqt/metadata.json b/demonstrations_v2/tutorial_vqt/metadata.json new file mode 100644 index 0000000000..cf0c972795 --- /dev/null +++ b/demonstrations_v2/tutorial_vqt/metadata.json @@ -0,0 +1,44 @@ +{ + "title": "Variational Quantum Thermalizer", + "authors": [ + { + "username": "jceroni" + } + ], + "dateOfPublication": "2020-07-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_quantum_thermalizer.png" + } + ], + "seoDescription": "Using the Variational Quantum Thermalizer to prepare the thermal state of a Heisenberg model Hamiltonian.", + "doi": "", + "references": [ + { + "id": "Verdon2019", + "type": "article", + "title": "Quantum Hamiltonian-Based Models and the Variational Quantum Thermalizer Algorithm", + "authors": "Verdon, G., Marks, J., Nanda, S., Leichenauer, S., & Hidary, J.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.02071" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1910.02071" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqt/requirements.in b/demonstrations_v2/tutorial_vqt/requirements.in new file mode 100644 index 0000000000..b57a1d96ba --- /dev/null +++ b/demonstrations_v2/tutorial_vqt/requirements.in @@ -0,0 +1,6 @@ +matplotlib +networkx +numpy +pennylane +scipy +seaborn diff --git a/demonstrations_v2/tutorial_zne_catalyst/demo.py b/demonstrations_v2/tutorial_zne_catalyst/demo.py new file mode 100644 index 0000000000..0addd8d542 --- /dev/null +++ b/demonstrations_v2/tutorial_zne_catalyst/demo.py @@ -0,0 +1,312 @@ +r""" +Digital zero-noise extrapolation (ZNE) with Catalyst +==================================================== + +In this tutorial, you will learn how to use :doc:`error mitigation `, and in particular +the zero-noise extrapolation (ZNE) technique, in combination with +`Catalyst `_, a framework for quantum +just-in-time (JIT) compilation with PennyLane. +We'll demonstrate how to generate noise-scaled circuits, execute them on a noisy quantum +simulator, and use extrapolation techniques to estimate the zero-noise result, all while +leveraging JIT compilation through Catalyst. + +.. image:: ../_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_zne_catalyst.png + :width: 70% + :align: center + +The demo :doc:`Error mitigation with Mitiq and PennyLane ` +shows how ZNE, along with other error mitigation techniques, can be carried out in PennyLane +by using `Mitiq `__, a Python library developed +by `Unitary Fund `__. + +ZNE in particular is also offered out of the box in PennyLane as a *differentiable* error mitigation technique, +for usage in combination with variational workflows. More on this in the tutorial +:doc:`Differentiating quantum error mitigation transforms `. + +On top of the error mitigation routines offered in PennyLane, ZNE is also available for just-in-time +(JIT) compilation. In this tutorial we see how an error mitigation routine can be +integrated in a Catalyst workflow. + +At the end of the tutorial, we will compare the execution time of ZNE routines in +pure PennyLane vs. PennyLane and Catalyst with JIT. + +What is zero-noise extrapolation (ZNE) +-------------------------------------- +Zero-noise extrapolation (ZNE) is a technique used to mitigate the effect of noise on quantum +computations. First introduced in [#zne-2017]_, it helps improve the accuracy of quantum +results by running circuits at varying noise levels and extrapolating back to a hypothetical +zero-noise case. While this tutorial won't delve into the theory behind ZNE in detail (for which we +recommend reading the `Mitiq docs `_ +and the references, including Mitiq's whitepaper [#mitiq-2022]_), let's first review what happens when using the protocol in practice. + +Stage 1: Generating noise-scaled circuits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +ZNE works by generating circuits with **increased** noise. Catalyst implements the unitary folding +framework introduced in [#dzne-2020]_ for generating noise-scaled circuits. In particular, +the following two methods are available: + +1. **Global folding**: If a circuit implements a global unitary :math:`U`, global folding applies + :math:`U(U^\dagger U)^n` for some integer :math:`n`, + effectively scaling the noise in the entire circuit. +2. **Local folding**: Individual gates are repeated (or folded) in contrast with the entire + circuit. + +Stage 2: Running the circuits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once noise-scaled circuits are created, they need to be run! These can be executed on either real +quantum hardware or a noisy quantum simulator. In this tutorial, we'll use the +`Qrack quantum simulator `_, which is both compatible with Catalyst, +and implements a noise model. For more about the integration of Qrack and Catalyst, see +the demo :doc:`QJIT compilation with Qrack and Catalyst `. + +Stage 3: Combining the results +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +After executing the noise-scaled circuits, an extrapolation on the results is performed +to estimate the zero-noise limit---the result we would expect in a noise-free scenario. +Catalyst provides **polynomial** and **exponential** extrapolation methods. + +These three stages illustrate what happens behind the scenes when using a ZNE routine. +However, from the user's perspective, one only needs to define the initial circuit, +the noise scaling method, and the extrapolation method. The rest is taken care of by Catalyst. +""" + +############################################################################## +# .. note :: +# +# To follow along with this demonstration, it is required to +# `install Catalyst `__, +# as well as the `PennyLane-Qrack plugin `__. +# +# .. code-block:: bash +# +# pip install -U pennylane-catalyst pennylane-qrack +# + +############################################################################## +# Defining the mirror circuit +# --------------------------- +# The first step for demoing an error mitigation routine is to define a circuit. +# Here we build a simple mirror circuit starting off a `unitary 2-design `__. +# This is a typical construction for a randomized benchmarking circuit, which is used in many tasks +# in quantum computing. Given such circuit, we measure the expectation value :math:`\langle Z\rangle` +# on the state of the first qubit, and by construction of the circuit, we expect this value to be +# equal to 1. + +import numpy as np +import pennylane as qml +from catalyst import mitigate_with_zne + +n_wires = 3 + +np.random.seed(42) + +n_layers = 5 +template = qml.SimplifiedTwoDesign +weights_shape = template.shape(n_layers, n_wires) +w1, w2 = [2 * np.pi * np.random.random(s) for s in weights_shape] + +def circuit(w1, w2): + template(w1, w2, wires=range(n_wires)) + qml.adjoint(template)(w1, w2, wires=range(n_wires)) + return qml.expval(qml.PauliZ(0)) + +############################################################################## +# As a sanity check, we first execute the circuit on the Qrack simulator without any noise. + +noiseless_device = qml.device("qrack.simulator", n_wires, noise=0) + +ideal_value = qml.QNode(circuit, device=noiseless_device)(w1, w2) +print(f"Ideal value: {ideal_value}") + +############################################################################## +# In the noiseless scenario, the expectation value of the Pauli-Z measurement +# is equal to 1, since the first qubit is back in the :math:`|0\rangle` state. +# +# Mitigating the noisy circuit +# ---------------------------- +# Let's now run the circuit through a noisy scenario. The Qrack simulator models noise by +# applying single-qubit depolarizing noise channels to all qubits in all gates of the circuit. +# The probability of error is specified by the value of the ``noise`` constructor argument. + +NOISE_LEVEL = 0.01 +noisy_device = qml.device("qrack.simulator", n_wires, shots=1000, noise=NOISE_LEVEL) + +noisy_qnode = qml.QNode(circuit, device=noisy_device, mcm_method="one-shot") +noisy_value = noisy_qnode(w1, w2) +print(f"Error without mitigation: {abs(ideal_value - noisy_value):.3f}") + +############################################################################## +# Again expected, we obtain a noisy value that diverges from the ideal value we obtained above. +# Fortunately, we have error mitigation to the rescue! We can apply ZNE, however we are still +# missing some necessary parameters. In particular we still need to specify: +# +# 1. The method for scaling this noise up (in Catalyst there are two options: ``global`` and +# ``local``). +# 2. The noise scaling factors (i.e. how much to increase the depth of the circuit). +# 3. The extrapolation technique used to estimate the ideal value (available in Catalyst are +# polynomial and exponential extrapolation). +# +# First, we choose a method to scale the noise. This needs to be specified as a Python string. + +folding_method = "global" + +############################################################################## +# Next, we pick a list of scale factors. At the time of writing this tutorial, +# Catalyst supports only odd integer scale factors. In the global folding setting, +# a scale factor :math:`s` correspond to the circuit being folded +# :math:`\frac{s - 1}{2}` times. +scale_factors = [1, 3, 5] + +############################################################################## +# Finally, we'll choose the extrapolation technique. Both exponential and polynomial extrapolation +# is available in the :mod:`qml.transforms ` module, and both of these functions can be passed directly +# into Catalyst's :func:`catalyst.mitigate_with_zne` function. In this tutorial we use polynomial extrapolation, +# which we hypothesize best models the behavior of the noise scenario we are considering. + +from pennylane.transforms import poly_extrapolate +from functools import partial + +extrapolation_method = partial(poly_extrapolate, order=2) + +############################################################################## +# We're now ready to run our example using ZNE with Catalyst! Putting these all together we're able +# to define a very simple :func:`~.QNode`, which represents the mitigated version of the original circuit. + + +@qml.qjit +def mitigated_circuit_qjit(w1, w2): + return mitigate_with_zne( + noisy_qnode, + scale_factors=scale_factors, + extrapolate=extrapolation_method, + folding=folding_method, + )(w1, w2) + + +zne_value = mitigated_circuit_qjit(w1, w2) + +print(f"Error with ZNE in Catalyst: {abs(ideal_value - zne_value):.3f}") + +############################################################################## +# It's crucial to note that we can use the :func:`~.qjit` decorator here, as all the functions used +# to define the node are compatible with Catalyst, and we can therefore +# exploit the potential of just-in-time compilation. +# +# Benchmarking +# ------------ +# For comparison, let's define a very similar :func:`~.qnode`, but this time we don't decorate the node +# as just-in-time compilable. +# When it comes to the parameters, the only difference here (due to an implementation technicality) +# is the type of the ``folding`` argument. Despite the type being different, however, +# the value of the folding method is the same, i.e., global folding. + + +def mitigated_circuit(w1, w2): + return qml.transforms.mitigate_with_zne( + noisy_qnode, + scale_factors=scale_factors, + extrapolate=extrapolation_method, + folding=qml.transforms.fold_global, + )(w1, w2) + + +zne_value = mitigated_circuit(w1, w2) + +print(f"Error with ZNE in PennyLane: {abs(ideal_value - zne_value):.3f}") + +############################################################################## +# To showcase the impact of JIT compilation, we use Python's ``timeit`` module +# to measure execution time of ``mitigated_circuit_qjit`` vs. ``mitigated_circuit``. +# +# Note: for the purpose of this last example, we reduce the number of shots of the simulator to 100, +# since we don't need the accuracy required for the previous demonstration. We do so in order to +# reduce the running time of this tutorial, while still showcasing the performance differences. +import timeit + +noisy_device = qml.device("qrack.simulator", n_wires, shots=100, noise=NOISE_LEVEL) +noisy_qnode = qml.QNode(circuit, device=noisy_device, mcm_method="one-shot") + +@qml.qjit +def mitigated_circuit_qjit(w1, w2): + return mitigate_with_zne( + noisy_qnode, + scale_factors=scale_factors, + extrapolate=extrapolation_method, + folding=folding_method, + )(w1, w2) + +repeat = 5 # number of timing runs +number = 5 # number of loops executed in each timing run + +times = timeit.repeat("mitigated_circuit(w1, w2)", globals=globals(), number=number, repeat=repeat) + +print(f"mitigated_circuit running time (best of {repeat}): {min(times):.3f}s") + +times = timeit.repeat( + "mitigated_circuit_qjit(w1, w2)", globals=globals(), number=number, repeat=repeat +) + +print(f"mitigated_circuit_qjit running time (best of {repeat}): {min(times):.3f}s") + +############################################################################## +# Already with the simple circuit we started with, and with the simple parameters in our example, +# we can appreciate the performance differences. That was at the cost of very minimal syntax change. +# +# There are still reasons to use ZNE in PennyLane without :func:`~.qjit`, for instance, +# whenever the device of choice is not supported by Catalyst. To help, +# we conclude with a landscape of the QEM techniques available in the PennyLane ecosystem. +# +# .. list-table:: +# :widths: 30 20 20 20 20 30 +# :align: center +# :header-rows: 0 +# +# * - **Framework** +# - **ZNE folding** +# - **ZNE extrapolation** +# - **Differentiable** +# - **JIT** +# - **Other QEM techniques** +# * - PennyLane + Mitiq +# - global, local, random +# - polynomial, exponential +# - – +# - – +# - ✅ +# * - PennyLane transforms +# - global, local +# - polynomial, exponential +# - ✅ +# - – +# - – +# * - Catalyst (experimental) +# - global, local +# - polynomial, exponential +# - ✅ +# - ✅ +# - – + + +############################################################################## +# +# References +# ---------- +# +# .. [#zne-2017] K. Temme, S. Bravyi, J. M. Gambetta +# `"Error Mitigation for Short-Depth Quantum Circuits" `_, +# Phys. Rev. Lett. 119, 180509 (2017). +# +# .. [#dzne-2020] Tudor Giurgica-Tiron, Yousef Hindy, Ryan LaRose, Andrea Mari, and William J. Zeng, +# `"Digital zero noise extrapolation for quantum error mitigation" `__, +# IEEE International Conference on Quantum Computing and Engineering (2020). +# +# .. [#mitiq-2022] +# Ryan LaRose and Andrea Mari and Sarah Kaiser and Peter J. Karalekas and Andre A. Alves and +# Piotr Czarnik and Mohamed El Mandouh and Max H. Gordon and Yousef Hindy and Aaron Robertson +# and Purva Thakre and Misty Wahl and Danny Samuel and Rahul Mistri and Maxime Tremblay +# and Nick Gardner and Nathaniel T. Stemen and Nathan Shammah and William J. Zeng, +# `"Mitiq: A software package for error mitigation on noisy quantum computers" `__, +# Quantum (2022). diff --git a/demonstrations_v2/tutorial_zne_catalyst/metadata.json b/demonstrations_v2/tutorial_zne_catalyst/metadata.json new file mode 100644 index 0000000000..40afd668ce --- /dev/null +++ b/demonstrations_v2/tutorial_zne_catalyst/metadata.json @@ -0,0 +1,84 @@ +{ + "title": "Digital zero-noise extrapolation with Catalyst", + "authors": [ + { + "username": "cosenal" + }, + { + "username": "natestemen" + } + ], + "dateOfPublication": "2024-11-15T00:00:00+00:00", + "dateOfLastModification": "2024-11-25T09:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_zne_catalyst.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_zne_catalyst.png" + } + ], + "seoDescription": "Learn to use error mitigation and the digital zero-noise extrapolation (ZNE) technique with Catalyst, the PennyLane framework for quantum JIT compilation.", + "doi": "", + "references": [ + { + "id": "dzne-2020", + "type": "article", + "title": "Digital zero noise extrapolation for quantum error mitigation", + "authors": "Tudor Giurgica-Tiron, Yousef Hindy, Ryan LaRose, Andrea Mari, and William J. Zeng", + "year": "2020", + "publisher": "IEEE", + "journal": "2020 IEEE International Conference on Quantum Computing and Engineering (QCE)", + "doi": "10.1109/QCE49297.2020.00045", + "url": "https://arxiv.org/abs/2005.10921v2" + }, + { + "id": "zne-2017", + "type": "article", + "title": "Error Mitigation for Short-Depth Quantum Circuits", + "authors": "K. Temme, S. Bravyi, J. M. Gambetta", + "year": "2017", + "publisher": "", + "journal": "Phys. Rev. Lett. 119, 180509", + "doi": "10.1103/PhysRevLett.119.180509", + "url": "https://arxiv.org/abs/1612.02058" + }, + { + "id": "mitiq-2022", + "type": "article", + "title": "Mitiq: A software package for error mitigation on noisy quantum computers", + "authors": "Ryan LaRose and Andrea Mari and Sarah Kaiser and Peter J. Karalekas and Andre A. Alves and Piotr Czarnik and Mohamed El Mandouh and Max H. Gordon and Yousef Hindy and Aaron Robertson and Purva Thakre and Misty Wahl and Danny Samuel and Rahul Mistri and Maxime Tremblay and Nick Gardner and Nathaniel T. Stemen and Nathan Shammah and William J. Zeng", + "year": "2022", + "publisher": "Verein zur Forderung des Open Access Publizierens in den Quantenwissenschaften", + "journal": "Quantum", + "doi": "10.22331/q-2022-08-11-774", + "url": "https://doi.org/10.22331/q-2022-08-11-774" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_error_mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qrack", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_zne_catalyst/requirements.in b/demonstrations_v2/tutorial_zne_catalyst/requirements.in new file mode 100644 index 0000000000..b16382ce3f --- /dev/null +++ b/demonstrations_v2/tutorial_zne_catalyst/requirements.in @@ -0,0 +1,4 @@ +pennylane-catalyst +pennylane-qrack +numpy +pennylane diff --git a/demonstrations_v2/tutorial_zx_calculus/demo.py b/demonstrations_v2/tutorial_zx_calculus/demo.py new file mode 100644 index 0000000000..08beeb3fd7 --- /dev/null +++ b/demonstrations_v2/tutorial_zx_calculus/demo.py @@ -0,0 +1,867 @@ +r""" +Introduction to the ZX-calculus +=============================== + +.. meta:: + :property="og:description": Investigation of the ZX-calculus and its applications to quantum computing + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/thumbnail_tutorial_zx_calculus.png + +.. related:: + + tutorial_backprop Quantum gradients with backpropagation + tutorial_mbqc Measurement-based quantum computation + +*Author: Romain Moyard. Posted: 6 June, 2023.* + + +The ZX-calculus is a graphical language for reasoning about quantum computations and circuits. Introduced by Coecke +and Duncan [#Coecke]_, it can represent any linear map, and can be considered a diagrammatically complete +generalization of the usual circuit representation. The ZX-calculus is based on category theory, an approach to +mathematics which studies objects in terms of their relations rather than in isolation. Thus, the ZX-calculus +provides a rigorous way to understand the structure underlying quantum problems, using the link between quantum +operations rather than the quantum operations themselves. + +.. figure:: ../_static/demonstration_assets/zx_calculus/ZX_Calculus_animation.gif + :align: center + :width: 100% + + After this tutorial you will understand how to represent quantum teleportation and simplify it in the ZX-calculus! + +In this tutorial, we first give an overview of the building blocks of the ZX-calculus, called *ZX-diagrams*, +and the rules for transforming them, called *rewriting rules*. We also show how the ZX-calculus can be extended to ZXH +calculus. The ZX-calculus is also promising for quantum machine learning, thus we present how the parameter-shift rule +can be derived using ZX-diagrams. We will then jump to the coding part of the tutorial and show how PennyLane is +integrated with PyZX [#PyZX]_, a Python library for ZX-calculus, and how you can transform your circuit to a +ZX-diagram. We then apply what we've learned in order to optimize the number of T-gates of a known benchmark circuit. +We also show that simplifying a ZX-diagram does not always end up with a diagram-like graph, and that circuit +extraction is a main pain point of the ZX framework. This tutorial will give a broad overview of what ZX-calculus can +offer when you want to analyze quantum problems. + +ZX-diagrams +----------- + +This introduction follows the works of the East et al. [#East2021]_ and van de Wetering [#JvdW2020]_. Our goal is to +introduce a complete language for quantum information, for which we need two elements: ZX-diagrams and their +rewriting rules. We start by introducing ZX-diagrams, a graphical depiction of a tensor network representing an +arbitrary linear map. Later, we will introduce ZX rewriting rules, which together with diagrams defines the +ZX-calculus. We follow the scalar convention of East et al. [#East2021]_ (it is more suitable to the multi-H box +situations, see the ZXH section). + +A ZX-diagram is an undirected multi-graph; you can move vertices without affecting the underlying +linear map. The vertices are called Z- and X-spiders, which represent two kinds of linear maps. The edges are called +wires, and represent the dimensions on which the linear maps are acting. Therefore, the edges represent qubits in +quantum computing. The diagram's wires on the left and right are called inputs and outputs, respectively. + +The first building block of the ZX-diagram is the Z-spider. In most of the literature, it is depicted as a green vertex. +The Z-spider takes a real phase :math:`\alpha \in \mathbb{R}` and represents the following linear map (it accepts any +number of inputs and outputs, and the number of inputs does not need to match the number of outputs): + +.. figure:: ../_static/demonstration_assets/zx_calculus/z_spider.jpeg + :align: center + :width: 70% + + The Z-spider. + +It is easy to see that the usual Z-gate can be represented with a single-wire Z-gate: + +.. figure:: ../_static/demonstration_assets/zx_calculus/z_gate.jpeg + :align: center + :width: 70% + + The Z-gate. + + +As you've probably already guessed, the second building block of the ZX-diagram is the X-spider. It is usually depicted +as a red vertex. The X-spider also takes a real phase :math:`\alpha \in \mathbb{R}` and it represents the following +linear map (it accepts any number of inputs and outputs): + +.. figure:: ../_static/demonstration_assets/zx_calculus/x_spider.jpeg + :align: center + :width: 70% + + The X-spider. + +It is easy to see that the usual X-gate can be represented with a single-wire X-spider: + +.. figure:: ../_static/demonstration_assets/zx_calculus/x_gate.jpeg + :align: center + :width: 70% + + The X-gate. + +From ordinary quantum theory, we know that the Hadamard gate can be decomposed into X and Z rotations, and can therefore +be represented in ZX-calculus. In order to make the diagram easier to read, we introduce the Hadamard gate as a yellow +box: + +.. figure:: ../_static/demonstration_assets/zx_calculus/hadamard_gate.png + :align: center + :width: 70% + + The Hadamard gate as a yellow box and its ZX decomposition. + +This yellow box is also often represented as a blue edge in order to further simplify the display of the diagram. +Below, we will discuss a generalization of the yellow box to a third spider, forming the ZXH-calculus. It is important +to note that the yellow box is by itself a rewrite rule for the decomposition of the Hadamard gate. The yellow box +allows us to write the relationship between the X- and Z-spider as follows. + +.. figure:: ../_static/demonstration_assets/zx_calculus/hxhz.jpg + :align: center + :width: 70% + + How to transform an X-spider to a Z-spider with the Hadamard gate. + +.. figure:: ../_static/demonstration_assets/zx_calculus/hzhx.jpg + :align: center + :width: 70% + + How to transform an Z-spider to a X-spider with the Hadamard gate. + +A special case of the Z- and X-spiders are diagrams with no inputs (or outputs). They are used to represent states that +are unnormalized. If a spider has no inputs and outputs, it simply represents a complex scalar. You can find the usual +representation of quantum states below: + +.. figure:: ../_static/demonstration_assets/zx_calculus/zero_state_plus_state.jpeg + :align: center + :width: 70% + + The zero state and plus state as a ZX-diagram. + +Similarly, you get the :math:`\vert 1\rangle` state and :math:`\vert -\rangle` state by replacing the zero phase with +:math:`\pi.` + +The phases are :math:`2\pi` periodic, and when a phase is equal to :math:`0` we omit the zero symbol from the spider. +A simple green vertex is a Z-spider with zero phase and a simple red vertex is an X-spider with zero phase. + +Now that we have these two basic building blocks, we can start composing them and stacking them on top of each other. +Composition consists of joining the outputs of a diagram to the inputs of another diagram. Stacking two +ZX-diagrams on top of each other represents the tensor product of the corresponding tensors. + +We illustrate the rules of stacking and composition by building an equivalent CNOT gate (up to a global phase). We +start by stacking a single wire with a phaseless Z-spider with one input wire and two output wires. We show the +ZX-diagram and corresponding matrix below: + +.. figure:: ../_static/demonstration_assets/zx_calculus/stack_z_w.jpg + :align: center + :width: 100% + + Phaseless Z-spider with one input wire and two output wires (see the definition of the Z-spider) stacked with a + single wire. + +Next, we stack a single wire with a phaseless X-spider with two input wires and single output wire. Again, we provide +the matrix: + +.. figure:: ../_static/demonstration_assets/zx_calculus/stack_w_x.jpg + :align: center + :width: 100% + + Single wire stacked with a phaseless X-spider with two inputs wires and one output wire. + +Finally, we compose the two diagrams, meaning that we join the two outputs of the first diagram with the two inputs of +the second diagram. By doing this we obtain a CNOT gate — you can convince yourself by doing the matrix multiplication +between the two diagrams. + +.. figure:: ../_static/demonstration_assets/zx_calculus/compose_zw_wx.jpg + :align: center + :width: 70% + + The composition of the two diagrams is a CNOT gate. + +We've already mentioned that a ZX-diagram is an undirected multi-graph; the position of the vertices does not matter, +nor does the trajectory of the wires. We can move vertices around, bend and unbend, and cross and uncross wires +as long as the connectivity and the order of the inputs and outputs is maintained. In particular, bending a line so +that it changes direction from left to right, or vice-versa, is not allowed. None of these deformations affect the +underlying linear map, meaning that ZX-diagrams have all sorts of *topological* symmetries. For instance, +the two diagrams below both represent the CNOT gate: + +.. figure:: ../_static/demonstration_assets/zx_calculus/cnot_moved.jpeg + :align: center + :width: 70% + + Both diagrams represent the same CNOT gate. + +This means that we can draw a vertical line without ambiguity, which is the usual way of representing the CNOT gate: + +.. figure:: ../_static/demonstration_assets/zx_calculus/cnot.jpeg + :align: center + :width: 70% + + Usual representation of the CNOT gate as a ZX-diagram. + + +We've just shown that we can express any Z rotation and X rotation with Z- and X-spiders. Therefore, it is sufficient +to create any one-qubit rotation on the Bloch sphere. By composing and stacking, +we can also create the CNOT gate. Therefore, we have a universal gate set! We can also create the :math:`0` state and +:math:`+` state on any number of qubits. Therefore, we can represent any quantum state. Normalization might be needed +(e.g., for the CNOT gate) and we perform this by adding complex scalar vertices. + +It turns out that the ability to represent an arbitrary state implies the ability to represent an arbitrary linear +map. Using a mathematical result called the Choi-Jamiolkowski isomorphism [#JvdW2020]_, for any linear map :math:`L` +from :math:`n` to :math:`m` wires, we can bend the incoming wires to the right and find an equivalent state on +:math:`n + m` wires. Thus, any linear map is equivalent to some state, and since we can create any state, +we can create any map! This shows that ZX-diagrams are a universal tool for reasoning about linear maps. But this +doesn't mean the representation is simple! + +For a more in-depth introduction, see [#Coecke]_ and [#JvdW2020]_. + +ZX-calculus: rewriting rules +---------------------------- + +ZX-diagrams coupled with rewriting rules form the ZX-calculus. Previously, we presented the rules for composing and +stacking diagrams and talked about the topological symmetries corresponding to deformations. In this section, +we provide rewriting rules that can be used to simplify diagrams without changing the underlying linear map. This can +be very useful for quantum circuit optimization and for showing that some computations have a very simple form in the +ZX framework (e.g., teleportation). + +In the following rules, the colours are interchangeable. + +1. Since the X-gate and Z-gate do not commute, non-phaseless vertices of different color do not commute. + +2. The **fuse** rule applies when two spiders of the same type are connected by one or more wires. We can fuse + spiders by simply adding the two spiders' phases and removing the connecting wires. + + .. figure:: ../_static/demonstration_assets/zx_calculus/f_rule.jpeg + :align: center + :width: 70% + + The (f)use rule. + +3. The :math:`\pi` **-copy** rule describes how to pull an X-gate through a Z-spider (or a Z-gate through an X-spider). + Since X and Z anticommute, pulling the X-gate through a Z-spider introduces a minus sign into the Z phase. + + .. figure:: ../_static/demonstration_assets/zx_calculus/pi_rule.jpeg + :align: center + :width: 70% + + The (:math:`\pi`)-copy rule. + +4. The **state-copy** rule captures how simple one-qubit states interact with a spider of the opposite colour. It + is only valid for states that are multiples of :math:`\pi` (therefore :math:`a` is an integer), so we have + computational basis states (in the X or Z basis). Basically, if you pull a basis state through a spider of the + opposite color, it copies it onto each outgoing wire. + + .. figure:: ../_static/demonstration_assets/zx_calculus/c_rule.jpg + :align: center + :width: 70% + + The state (c)opy rule, where :math:`a` is an integer. + +5. The **identity** rule states that phaseless spiders with one input and one output are equivalent to the identity + and can therefore be removed. This is similar to the rule that Z and X rotation gates, which are phaseless, + are equivalent to the identity. This rule provides a way to get rid of self-loops. + + .. figure:: ../_static/demonstration_assets/zx_calculus/id_rule.jpeg + :align: center + :width: 70% + + The (id)entity removal rule. + +6. A **bialgebra** is a mathematical structure with a product (combining two wires into one) and a coproduct ( + splitting a wire into two wires) where, roughly speaking, we can pull a product through a coproduct at the cost of + doubling. This is similar to the relation enjoyed by the XOR algebra and the COPY coalgebra. This rule is not + straightforward to verify and details can be found in [#JvdW2020]_ . + + .. figure:: ../_static/demonstration_assets/zx_calculus/b_rule.jpg + :align: center + :width: 70% + + The (b)ialgebra rule. + +7. The **Hopf** rule is a bit like the bialgebra rule, telling us what happens when we try to pull a coproduct + through a product. Instead of doubling, however, they decouple, leaving us with an unconnected projector and a state. + Again, this relation is satisfied by XOR and COPY, and the corresponding algebraic structure is called a Hopf + algebra. This turns out to follow from the bialgebra and the state-copy rule [#JvdW2020]_, but it's useful to record + it as a separate rule. + + .. figure:: ../_static/demonstration_assets/zx_calculus/hopf_rule.jpeg + :align: center + :width: 70% + + The (ho)pf rule. + + +Teleportation +------------- + +Now that we have all the necessary tools, let's see how to describe teleportation as a ZX-diagram and simplify it +with our rewriting rules. The results are surprisingly elegant! We follow the explanation from [#JvdW2020]_. You can +find an introduction to teleportation in +`the MBQC demo `__. + +Teleportation is a protocol for transferring quantum information (a state) from Alice (the sender) to Bob (the +receiver). To perform this, Alice and Bob first need to share a maximally entangled state. The protocol for Alice to send +her quantum state to Bob is as follows: + +1. Alice applies the CNOT gate followed by the Hadamard gate. +2. Alice measures the two qubits that she has. +3. Alice sends the two measurement results to Bob. +4. Given the results, Bob conditionally applies the Z- and X-gate to his qubit. +5. Bob ends up with the same state as Alice previously had. Teleportation is complete! + +In the ordinary quantum circuit notation, we can summarize the procedure as follows: + +.. figure:: ../_static/demonstration_assets/zx_calculus/teleportation_circuit.jpeg + :align: center + :width: 70% + + The teleportation circuit. + +Let us convert this quantum circuit into a ZX-diagram. The measurements are represented by the state X-spider +parameterized with boolean parameters :math:`\alpha` and :math:`\beta.` The cup represents the maximally entangled +state shared between Alice and Bob. As you might expect from earlier comments about bending wires, their shared state +is Choi-Jamiolkowski-equivalent to the identity linear map. + +Let's simplify the diagram by applying some rewriting rules. The first step is to fuse the :math:`a` state with the +X-spider of the CNOT. We also merge the Hadamard gate with the :math:`\beta` state, because together it represents a +Z-spider. Then we can fuse the three Z-spiders by simply adding their phases. After that, we see that the Z-spider +phase vanishes (modulo :math:`2\pi`) and can therefore be simplified using the identity rule. Then we can fuse the +two X-spiders by adding their phases. We notice that the phase again vanishes modulo :math:`2\pi` and we can get rid +of the last X-spider. Teleportation is a simple wire connecting Alice and Bob! + +.. figure:: ../_static/demonstration_assets/zx_calculus/teleportation.png + :align: center + :width: 100% + + The teleportation ZX-diagram simplified from [#JvdW2020]_ but there is a scalar factor because of a different + scalar convention. You are now able to understand, step by step, the teleportation gif from the beginning of this + tutorial! + + +The ZXH-calculus +---------------- + +In this section, we introduce the ZH-calculus [#Backens2018]_ .The universality of the ZX-calculus does not guarantee +the existence of a simple representation, even for simple linear maps. For example, the Toffoli gate (the quantum AND +gate) requires around 25 spiders (Z and X)! We previously introduced the Hadamard gate as a yellow box, which motivates +the introduction of a new generator: the multi-leg H-box, defined as follows: + +.. figure:: ../_static/demonstration_assets/zx_calculus/h_box.jpg + :align: center + :width: 70% + + The H-box, a third generator. + +The parameter :math:`a` can be any complex number, and the sum is over all :math:`i_1, ... , i_m, j_1, ... , +j_n \in \{0, 1\}`. Therefore, an H-box represents a matrix where its entries are equal to :math:`1` except for the +bottom right element, which is \ :math:`a.` This will allow us to concisely express the Toffoli gate, as we will see +shortly. + +An H-box with one input wire and one output wire, with :math:`a=-1,` is a Hadamard gate up to global phase. Thus, +we omit the parameter when it is equal to :math:`-1.` The Hadamard gate is sometimes represented by a blue edge +rather than a box. + +Thanks to the introduction of the multi-leg H-box, the Toffoli gate can be represented with three Z-spiders and three +H-boxes — two simple Hadamard gates and one three-ary H-box — as shown below: + +.. figure:: ../_static/demonstration_assets/zx_calculus/toffoli.jpg + :align: center + :width: 100% + + Toffoli + +The addition of the multi-leg H-box together with an additional set of rewriting rules forms the ZXH-calculus. You can +find more details and the rewriting rules in the literature [#East2021]_. + +Let's show that this ZXH-diagram is indeed a Toffoli-gate. This operation is defined by conditionally applying an +X-gate on the target wire, it means that only the state :math:`|110\rangle` and :math:`|111\rangle` will not map to +themselves (:math:`|110\rangle` to :math:`|111\rangle` and :math:`|111\rangle` to :math:`|110\rangle`). We will show that if +one provides the state :math:`|11\rangle` on the two first wires, it results to a bit flip on the third wire (X-gate). +For that purpose, we need to add a new rewriting rule that is part of the ZXH-calculus: the **absorb** rule. + +.. figure:: ../_static/demonstration_assets/zx_calculus/absorb.jpg + :align: center + :width: 70% + + The (ab)sorb rule. + +We start by applying our Toffoli diagram on a :math:`|11\rangle` state, which corresponds to two X-spiders with a phase +of :math:`\pi` stacked with our diagram. We apply the copy rule on the two groups of X-spiders and Z-spiders on the +wires 0 and 1. After that we can apply the newly introduced absorb rule on one of the X-spiders connected to the +H-Box. Then we recognize the Fourier relation and can replace the X-spider and H-Box by a Z-spider. Then it is easy +to apply the fuse rule on the two Z-spiders. Again, we recognize the Fourier relation and obtain a single X-spider on +the target wire. We just proved that by providing the :math:`|11\rangle` state on the two control wires, +it always applies an X-spider on the target. It means that we have a bit flip on the target. + +.. figure:: ../_static/demonstration_assets/zx_calculus/11ccnot.jpg + :align: center + :width: 100% + + Toffoli-diagram applied on the :math:`|11\rangle` state. + +If you do the same procedure with the others states on the two controls (:math:`|00\rangle`, :math:`|11\rangle,` +:math:`|10\rangle,` :math:`|01\rangle`) with slightly different rules (the explosion rule), you will always end up with +an empty target and identical states for the controls. We then have proved that our ZXH-diagram is indeed the Toffoli +gate! + +The ZX-calculus for quantum machine learning +-------------------------------------------- + +We now move away from the standard use of the ZX-calculus in order to show its utility for calculus and, +more specifically, for quantum derivatives (`the parameter-shift rule +`__). What follows is not implemented in PennyLane or PyZX. +By adding derivatives to the framework, it shows that the ZX-calculus has a role to play in analyzing quantum machine +learning problems. After reading this section, you should be convinced that the ZX-calculus can be used to study any +kind of quantum-related problem. + +Indeed, not only is the ZX-calculus useful for representing and simplifying quantum circuits, but it was shown that we +can use it to represent gradients and integrals of parameterized quantum circuits [#Zhao2021]_ . In this section, +we will follow the proof of the theorem that shows how the derivative of the expectation value of a Hamiltonian +given a parameterized state can be derived as a ZX-diagram (theorem 2 of Zhao et al. [#Zhao2021]_). We will also show +that the theorem can be used to prove the parameter-shift rule! + +Partial derivative as a ZX-diagram +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Let's first describe the problem. Without loss of generalization, let's suppose that we begin with the pure state +:math:`|0\rangle` on all :math:`n` qubits. Then we apply a parameterized unitary :math:`U` that depends on :math:`\vec{ +\theta}=(\theta_1, ..., \theta_m)`, where :math:`\theta_i \in [0, 2\pi].` + +Consequently, the expectation value of a Hamiltonian :math:`H` is given by: + +.. math:: \langle H \rangle = \langle 0 | U(\vec{\theta}) H U(\vec{\theta})^{\dagger} |0 \rangle. + +We have seen that any circuit can be represented by a ZX diagram, but once again, we want to use the graph-like form +(see the Graph optimization and circuit extraction section). There are multiple rules that ensure the transformation +to a graph-like diagram. We replace the 0 state by red phaseless spiders, and we transform the parameterized circuit +to its graph-like ZX diagram. We call the obtained diagram :math:`G_U(\vec{\theta}),` this diagram is equal to +the unitary up to a constant :math:`c.` + +.. figure:: ../_static/demonstration_assets/zx_calculus/hamiltonian_diagram.jpg + :align: center + :width: 100% + +Now we will investigate the partial derivative of the diagram representing the expectation value. The theorem is +the following: + +.. figure:: ../_static/demonstration_assets/zx_calculus/theorem2.jpg + :align: center + :width: 100% + + Theorem 2: The derivative of the expectation value of a Hamiltonian given a parameterized as a ZX-diagram. + +Let's prove theorem 2, and first we consider a partial derivative on the spider with respect to :math:`\theta_j.` The +spider necessarily appears on both sides, but they have phases of opposite signs and inverse inputs/outputs. By simply +writing their definitions and expanding the formula, we obtain: + +.. figure:: ../_static/demonstration_assets/zx_calculus/symmetric_spiders.jpg + :align: center + :width: 100% + + Two Z-spiders depending on the :math:`j`-th angle. + +Now we have a simple formula where we can easily take the derivative: + +.. figure:: ../_static/demonstration_assets/zx_calculus/derivative_symmetric_spiders.jpg + :align: center + :width: 100% + + The derivative of two spiders depending on the :math:`j`-th angle. + +The theorem 2 is proved — we just expressed the partial derivative as a ZX-diagram! + +Parameter-shift rule as a ZX-diagram +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This theorem can be used to prove the parameter-shift rule. Let's consider the following ansatz that we transform to +its graph-like diagram. + +.. figure:: ../_static/demonstration_assets/zx_calculus/param_shift_1_0.jpg + :align: center + :width: 100% + + The circuit (on the left) is translated to a ZX-diagram. + +.. figure:: ../_static/demonstration_assets/zx_calculus/param_shift_1_1.jpg + :align: center + :width: 100% + + The whole circuit is translated to a graph-like ZX-diagram. + +We then apply the previous theorem to get the partial derivative relative to :math:`\theta_1.` + +.. figure:: ../_static/demonstration_assets/zx_calculus/param_shift_1_2.jpg + :align: center + :width: 100% + + The derivative is applied on the ZX-diagram + +The second step is to take the X-spider with phase :math:`\pi` and explicitly write the formula :math:`|+\rangle\langle +| +- |-\rangle \langle -|`. We can then separate the diagram into two parts by recalling the definition of the :math:`|+\rangle` +(phaseless Z-spider) and :math:`|- \rangle` (:math:`2\pi` Z-spider) states and using the fusion rule for the Z-spider. +We obtain the parameter-shift rule! + +.. figure:: ../_static/demonstration_assets/zx_calculus/param_shift_2.jpg + :align: center + :width: 100% + + By using theorem 2, we can add an X-spider and shift the phases in the Z-spiders. Then, by explicitly decomposing + the spider with the :math:`|+\rangle` and :math:`|-\rangle` states, we prove the parameter-shift rule! + +You can find more information about the differentiation and integration of ZX-diagrams with QML applications in the +following paper [#Wang2022]_. + + +ZX-diagrams with PennyLane +-------------------------- + +Now that we have introduced the formalism of the ZX-calculus, let's dive into some code and show what you can do with +PennyLane! PennyLane v0.28 added ZX-calculus functionality to the fold. You can use the +:func:`~.pennylane.transforms.to_zx` transform decorator to get a ZX-diagram from a PennyLane +:class:`~.pennylane.QNode`, while :func:`~.pennylane.transforms.from_zx` transforms a ZX-diagram into a PennyLane +tape. We are using the PyZX library [#PyZX]_ under the hood to represent the ZX diagram. Once your circuit is a PyZX +graph, you can draw it, apply some optimization, extract the underlying circuit, and go back to PennyLane. + +Let's start with a very simple circuit consisting of three gates and show that you can represent the +:class:`~.pennylane.QNode` as a PyZX diagram: """ + +import matplotlib.pyplot as plt + +import pennylane as qml +import pyzx + +dev = qml.device("default.qubit", wires=2) + + +@qml.transforms.to_zx +@qml.qnode(device=dev) +def circuit(): + qml.PauliX(wires=0), + qml.PauliY(wires=1), + qml.CNOT(wires=[0, 1]), + return qml.expval(qml.PauliZ(wires=0)) + + +g = circuit() + +############################################################################# +# Now that you have a ZX-diagram as a PyZx object, you can use all the tools from the PyZX library to transform the +# graph. You can simplify the circuit, draw it, and get a new understanding of your quantum computation. +# +# For example, you can use the matplotlib drawer to get a visualization of the diagram. The drawer returns a +# ``matplotlib`` figure, and therefore you can save it locally with ``savefig`` function, or simply show it locally. + + +fig = pyzx.draw_matplotlib(g) + +# The following lines are added because the figure is automatically closed by PyZX. +manager = plt.figure().canvas.manager +manager.canvas.figure = fig +fig.set_canvas(manager.canvas) + +plt.show() + +############################################################################# +# You can also take a ZX-diagram in PyZX, convert it into a PennyLane tape and use it in your +# :class:`~.pennylane.QNode`. Invoking the PyZX circuit generator: + + +import random + +random.seed(42) +random_circuit = pyzx.generate.CNOT_HAD_PHASE_circuit(qubits=3, depth=10) +print(random_circuit.stats()) + +graph = random_circuit.to_graph() + +tape = qml.transforms.from_zx(graph) +print(tape.operations) + +############################################################################# +# We get a tape corresponding to the randomly generated circuit that we can use in any :class:`~.pennylane.QNode`. This +# functionality will be very useful for our next topic: circuit optimization. +# +# Diagram optimization and circuit extraction +# ------------------------------------------- +# +# The ZX-calculus is more general and more flexible than the usual circuit representation. We can therefore represent +# circuits with ZX-diagrams and apply rewriting rules to simplify them — like we did for teleportation. But, not every +# ZX-diagram has a corresponding circuit. To get back to circuits, a method for circuit extraction is needed. For +# a rigorous introduction to this active and promising field of application, see [#Duncan2020]_. The basic idea is +# captured below: +# +# .. figure:: ../_static/demonstration_assets/zx_calculus/circuit_opt.jpg +# :align: center +# :width: 70% +# +# The simplification and extraction of ZX-diagrams, content from page 2 of [#Duncan2020]_. +# +# To simplify ZX-diagrams, not only can we use the rewriting rules defined previously, but we can also use graph-theoretic +# transformations called local complementation and pivoting. These are special transformations that can only be +# applied to "graph-like" ZX-diagrams. As defined in [#Duncan2020]_, a ZX-diagram is graph-like if +# +# 1. All spiders are Z-spiders. +# 2. Z-spiders are only connected via Hadamard edges. +# 3. There are no parallel Hadamard edges or self-loops. +# 4. Every input or output is connected to a Z-spider and every Z-spider is connected to at most one input or output. +# +# A ZX-diagram is called a graph state if it is graph-like: every spider is connected to an output and there are no +# phaseless spiders. Furthermore, it was proved that every ZX-diagram is equal to a graph-like ZX-diagram. Thus, +# after conversion into graph-like form, we can use graph-theoretic tools on all ZX-diagrams. +# +# The basic idea is to use the graph-theoretic transformations to get rid of as many interior spiders as possible. +# Interior spiders are the one without inputs or outputs connected to them. We introduce some names for the spiders +# depending on their phases: +# +# 1. A Pauli spider has a phase that is a multiple of :math:`\pi.` +# 2. A Clifford spider has a phase that is a multiple of :math:`\frac{\pi}{2}.` +# 3. A proper Clifford spider is a Clifford spider with a phase which is an odd multiple of :math:`\frac{\pi}{2}.` +# +# Theorem 5.4 in [#Duncan2020]_ provides an algorithm which takes a graph-like diagram and performs the following: +# +# 1. Remove all interior proper Clifford spiders, +# 2. Remove adjacent pairs of interior Pauli spiders, +# 3. Remove interior Pauli spiders adjacent to a boundary spider. +# +# This procedure is implemented in PyZX as the :func:`~.pyzx.full_reduce` function. The complexity of the procedure is +# :math:`\mathcal{O}(n^3),` where :math:`n` is the number of spiders. Let's create an example with the circuit +# `mod_5_4 `__. The circuit +# :math:`63` gates: :math:`28` :class:`~.pennylane.T.` gates, :math:`28` :class:`~.pennylane.CNOT,` :math:`6` +# :class:`~.pennylane.Hadamard` and :math:`1` :class:`~.pennylane.PauliX.` +# + + +dev = qml.device("default.qubit", wires=5) + + +@qml.transforms.to_zx +@qml.qnode(device=dev) +def mod_5_4(): + qml.PauliX(wires=4), + qml.Hadamard(wires=4), + qml.CNOT(wires=[3, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[0, 4]), + qml.T(wires=[4]), + qml.CNOT(wires=[3, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[0, 4]), + qml.T(wires=[3]), + qml.T(wires=[4]), + qml.CNOT(wires=[0, 3]), + qml.T(wires=[0]), + qml.adjoint(qml.T(wires=[3])) + qml.CNOT(wires=[0, 3]), + qml.CNOT(wires=[3, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[2, 4]), + qml.T(wires=[4]), + qml.CNOT(wires=[3, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[2, 4]), + qml.T(wires=[3]), + qml.T(wires=[4]), + qml.CNOT(wires=[2, 3]), + qml.T(wires=[2]), + qml.adjoint(qml.T(wires=[3])) + qml.CNOT(wires=[2, 3]), + qml.Hadamard(wires=[4]), + qml.CNOT(wires=[3, 4]), + qml.Hadamard(wires=4), + qml.CNOT(wires=[2, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[1, 4]), + qml.T(wires=[4]), + qml.CNOT(wires=[2, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[1, 4]), + qml.T(wires=[4]), + qml.T(wires=[2]), + qml.CNOT(wires=[1, 2]), + qml.T(wires=[1]), + qml.adjoint(qml.T(wires=[2])) + qml.CNOT(wires=[1, 2]), + qml.Hadamard(wires=[4]), + qml.CNOT(wires=[2, 4]), + qml.Hadamard(wires=4), + qml.CNOT(wires=[1, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[0, 4]), + qml.T(wires=[4]), + qml.CNOT(wires=[1, 4]), + qml.adjoint(qml.T(wires=[4])), + qml.CNOT(wires=[0, 4]), + qml.T(wires=[4]), + qml.T(wires=[1]), + qml.CNOT(wires=[0, 1]), + qml.T(wires=[0]), + qml.adjoint(qml.T(wires=[1])), + qml.CNOT(wires=[0, 1]), + qml.Hadamard(wires=[4]), + qml.CNOT(wires=[1, 4]), + qml.CNOT(wires=[0, 4]), + return qml.expval(qml.PauliZ(wires=0)) + + +g = mod_5_4() +pyzx.simplify.full_reduce(g) + +fig = pyzx.draw_matplotlib(g) + +# The following lines are added because the figure is automatically closed by PyZX. +manager = plt.figure().canvas.manager +manager.canvas.figure = fig +fig.set_canvas(manager.canvas) + +plt.show() + +############################################################################# +# +# We see that after applying the procedure, we end up with only 16 interior Z-spiders and 5 boundary spiders. We also +# see that all non-Clifford phases appear on the interior spiders. The simplification procedure was successful, +# but we have a graph-like ZX-diagram with no quantum circuit equivalent. We need to extract a circuit! +# +# The extraction of circuits [#Duncan2020]_ is a highly non-trivial task and can be a #P-hard problem as shown in this +# paper [#Beaudrap2021]_ by de Beaudrap et al . There are two different algorithms introduced in the same paper. First, +# for Clifford circuits, the procedure will erase all interior spiders, and the diagram is left in a graph-state from +# which a Clifford circuit can be extracted using a total of eight layers with only one layer of CNOTs. +# +# For non-Clifford circuits, the problem is more complex because we are left with non-Clifford interior spiders. From +# the diagram produced by the simplification procedure, the extraction progresses through the diagram from +# right-to-left, consuming gates on the left and adding gates on the right. It produces better results than other +# cut-and-resynthesize techniques. The extraction procedure is implemented in PyZX as the function +# ``pyzx.circuit.extract_circuit``. We can apply this procedure to the example `mod_5_4` above: + +circuit_extracted = pyzx.extract_circuit(g.copy()) +print(circuit_extracted.stats()) + +############################################################################# +# +# Example: T-count optimization +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# A concrete application of these ZX optimization techniques is the reduction of the expensive non-Clifford T-count +# of a quantum circuit. Indeed, T-count optimization is an area where the ZX-calculus has shown very good results [#Kissinger2021]_ . +# +# Let’s start by using with the `mod_5_4` circuit introduced above. We applied the :func:`~.pennylane.transforms.to_zx` +# decorator in order to transform our circuit to a ZX graph. You can get this PyZX graph by calling the +# :class:`~.pennylane.QNode`: + + +g = mod_5_4() +t_count = pyzx.tcount(g) +print("T count before optimization:", t_count) + +############################################################################# +# +# PyZX gives multiple options for optimizing ZX graphs: :func:`~.pyzx.full_reduce` and :func:`~.pyzx.teleport_reduce` +# to name a couple. The :func:`~.pyzx.full_reduce` applies all optimization passes, but the final result may not be +# circuit-like. Converting back to a quantum circuit from a fully reduced graph might be difficult or impossible. +# Therefore, we instead recommend using :func:`~.pyzx.teleport_reduce`, as it preserves the diagram structure. +# Because of this, the circuit does not need to be extracted and can be directly sent back to PennyLane. Let's see +# how it works: + + +g = pyzx.simplify.teleport_reduce(g.copy()) +opt_t_count = pyzx.tcount(g) +print("T count after optimization:", opt_t_count) + +############################################################################# +# +# The :func:`~.pennylane.transforms.from_zx()` transform converts the optimized circuit back into PennyLane format, +# and which is made possible because we used `pyzx.teleport_reduce` and do not need to extract +# the circuit. + +qscript_opt = qml.transforms.from_zx(g) + +wires = qml.wires.Wires([4, 3, 0, 2, 1]) +wires_map = dict(zip(qscript_opt.wires, wires)) +qscript_opt_reorder, processing = qml.map_wires(input=qscript_opt, wire_map=wires_map) + +@qml.qnode(device=dev) +def mod_5_4(): + for o in processing(qscript_opt_reorder): + qml.apply(o) + return qml.expval(qml.PauliZ(wires=0)) + + +specs = qml.specs(mod_5_4)() + +print("Number of quantum gates:", specs["resources"].num_gates) +print("Circuit gates:", specs["resources"].gate_types) + +############################################################################# +# +# We have reduced the T-count! Taking a full census, the circuit contains :math:`53` gates: :math:`8` +# :class:`~.pennylane.T` gates, :math:`28` :class:`~.pennylane.CNOT`, :math:`6` :class:`~.pennylane.Hadamard,` +# :math:`1` :class:`~.pennylane.PauliX` and :math:`10` :class:`~.pennylane.S.` We successfully reduced the T-count by +# 20 and have 10 additional :class:`~.pennylane.S` gates. The number of :class:`~.pennylane.CNOT` gates remained the +# same. +# +# +# Conclusion +# ----------- +# +# Now that you have read this tutorial, you should be able to use the ZX-calculus to solve your quantum problems. +# You can describe quantum circuits with the ZX-spiders and the H-box and create ZXH-diagrams. +# Furthermore, you can use the simplifying rules to get another view of the underlying structure of your +# circuit. We have proved its utility for optimizing quantum circuits and shown that the +# ZX-calculus is more than promising for quantum machine learning. It was not covered in this introduction, but the +# ZX-calculus can also be used for quantum-error correction — it's no wonder why some quantum physicists +# call ZX-calculus the "Swiss army knife" of quantum computing tools! +# +# +# Acknowledgement +# --------------- +# +# The author would also like to acknowledge the helpful inputs of Richard East, David Wakeham and Isaac De Vlugt. The +# author is also thankful for the beautiful drawings by Guillermo Alonso and for the great thumbnail and teleportation +# gif by Tarik El-Khateeb. +# +# +# References +# ---------- +# +# .. [#Coecke] +# +# Bob Coecke and Ross Duncan. "Interacting Quantum Observables: Categorical Algebra and Diagrammatics." +# `ArXiv `__. +# +# .. [#PyZX] +# +# John van de Wetering. "PyZX." +# `PyZX GitHub `__. +# +# .. [#East2021] +# +# Richard D. P. East, John van de Wetering, Nicholas Chancellor and Adolfo G. Grushin. "AKLT-states as ZX-diagrams: +# diagrammatic reasoning for quantum states." +# `ArXiv `__. +# +# +# .. [#JvdW2020] +# +# John van de Wetering. "ZX-calculus for the working quantum computer scientist." +# `ArXiv `__. +# +# .. [#Backens2018] +# +# Miriam Backens and Aleks Kissinger. "ZH: A Complete Graphical Calculus for Quantum Computations Involving Classical Non-linearity." +# `ArXiv `__. +# +# .. [#Zhao2021] +# +# Chen Zhao and Xiao-Shan Gao. "Analyzing the barren plateau phenomenon in training quantum neural networks with the +# ZX-calculus" `Quantum Journal `__. +# +# .. [#Wang2022] +# +# Quanlong Wang, Richie Yeung, and Mark Koch. "Differentiating and Integrating ZX Diagrams with Applications to +# Quantum Machine Learning" `Arxiv `__. +# +# +# .. [#Duncan2020] +# +# Ross Duncan, Aleks Kissinger, Simon Perdrix, and John van de Wetering. "Graph-theoretic Simplification of Quantum +# Circuits with the ZX-calculus." +# `Quantum Journal `__. +# +# .. [#Kissinger2021] +# +# Aleks Kissinger and John van de Wetering. "Reducing T-count with the ZX-calculus." +# `ArXiv `__. +# +# .. [#Beaudrap2021] +# +# Niel de Beaudrap, Aleks Kissinger and John van de Wetering. "Circuit Extraction for ZX-diagrams can be #P-hard." +# `ArXiv `__. +# +# diff --git a/demonstrations_v2/tutorial_zx_calculus/metadata.json b/demonstrations_v2/tutorial_zx_calculus/metadata.json new file mode 100644 index 0000000000..18795bd74a --- /dev/null +++ b/demonstrations_v2/tutorial_zx_calculus/metadata.json @@ -0,0 +1,142 @@ +{ + "title": "Introduction to the ZX-calculus", + "authors": [ + { + "username": "rmoyard" + } + ], + "dateOfPublication": "2023-06-06T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/zx_calculus/thumbnail_tutorial_zx_calculus.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_zx_calculus.png" + } + ], + "seoDescription": "Introduction to the ZX-calculus and use cases.", + "doi": "", + "references": [ + { + "id": "Coecke", + "type": "article", + "title": "Interacting Quantum Observables: Categorical Algebra and Diagrammatics.", + "authors": "Bob Coecke and Ross Duncan.", + "year": "2008", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/0906.4725.pdf" + }, + { + "id": "PyZX", + "type": "webpage", + "title": "PyZX GitHub.", + "authors": "John van de Wetering.", + "year": "2018", + "publisher": "", + "journal": "", + "url": "https://github.com/Quantomatic/pyzx" + }, + { + "id": "Backens2018", + "type": "article", + "title": "ZH: A Complete Graphical Calculus for Quantum Computations Involving Classical Non-linearity", + "authors": "Miriam Backens and Aleks Kissinger", + "year": "2018", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/1805.02175.pdf" + }, + { + "id": "East2021", + "type": "article", + "title": "AKLT-states as ZX-diagrams: diagrammatic reasoning for quantum states.", + "authors": "Richard D. P. East, John van de Wetering, Nicholas Chancellor and Adolfo G. Grushin.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/2012.01219.pdf" + }, + { + "id": "JvdW2020", + "type": "article", + "title": "ZX-calculus for the working quantum computer scientist.", + "authors": "John van de Wetering.", + "year": "2020", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2012.13966" + }, + { + "id": "Zhao2021", + "type": "article", + "title": "Analyzing the barren plateau phenomenon in training quantum neural networks with the ZX-calculus.", + "authors": "Chen Zhao and Xiao-Shan Gao.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://quantum-journal.org/papers/q-2021-06-04-466/pdf/" + }, + { + "id": "Wang2022", + "type": "article", + "title": "Differentiating and Integrating ZX Diagrams with Applications to Quantum Machine Learning.", + "authors": "Quanlong Wang, Richie Yeung, and Mark Koch.", + "year": "2022", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/2201.13250.pdf" + }, + { + "id": "Duncan2020", + "type": "article", + "title": "Graph-theoretic Simplification of Quantum Circuits with the ZX-calculus.", + "authors": "Ross Duncan, Aleks Kissinger, Simon Perdrix, and John van de Wetering.", + "year": "2020", + "publisher": "", + "journal": "", + "url": "https://quantum-journal.org/papers/q-2020-06-04-279/pdf/" + }, + { + "id": "Kissinger2021", + "type": "article", + "title": "Reducing T-count with the ZX-calculus.", + "authors": "Aleks Kissinger and John van de Wetering.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://quantum-journal.org/papers/q-2020-06-04-279/pdf/" + }, + { + "id": "Beaudrap2021", + "type": "article", + "title": "Circuit Extraction for ZX-diagrams can be #P-hard.", + "authors": "Niel de Beaudrap, Aleks Kissinger and John van de Wetering.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/2202.09194.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_zx_calculus/requirements.in b/demonstrations_v2/tutorial_zx_calculus/requirements.in new file mode 100644 index 0000000000..48bae8219a --- /dev/null +++ b/demonstrations_v2/tutorial_zx_calculus/requirements.in @@ -0,0 +1,3 @@ +matplotlib +pennylane +pyzx diff --git a/demonstrations_v2/vqe_parallel/demo.py b/demonstrations_v2/vqe_parallel/demo.py new file mode 100644 index 0000000000..351c9dc13c --- /dev/null +++ b/demonstrations_v2/vqe_parallel/demo.py @@ -0,0 +1,387 @@ + +# coding=utf-8 +r""" +VQE with parallel QPUs with Rigetti +======================================== + +.. meta:: + :property="og:description": Using parallel QPUs to + speed up the calculation of the potential energy surface of molecular Hamiltonian. + :property="og:image": https://pennylane.ai/qml/_static/demonstration_assets/vqe_diagram.png + +.. related:: + + tutorial_vqe A brief overview of VQE + +*Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* + +This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the +calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). + +Using a VQE setup, we task two devices from the +`PennyLane-Rigetti `__ plugin with evaluating +separate terms in the qubit Hamiltonian of :math:`H_2.` As these devices are allowed to operate +asynchronously, i.e., at the same time and without having to wait for each other, +the calculation can be performed in roughly half the time. + +We begin by importing the prerequisite libraries: +""" + +import time +import dask + +import matplotlib.pyplot as plt +from pennylane import numpy as np +import pennylane as qml +from pennylane import qchem + +############################################################################## +# +# This tutorial requires the ``pennylane-rigetti`` and ``dask`` +# packages, which are installed separately using: +# +# .. code-block:: bash +# +# pip install pennylane-rigetti +# pip install "dask[delayed]" +# +# Finding the qubit Hamiltonians of :math:`H_{2}` +# ----------------------------------------------- +# +# The objective of this tutorial is to evaluate the potential energy surface of molecular +# hydrogen. This is achieved by finding the ground state energy of :math:`H_{2}` as we increase +# the bond length between the hydrogen atoms. +# +# Each inter-atomic distance results in a different qubit Hamiltonian. Further +# details on the mapping from the electronic Hamiltonian of a molecule to a +# qubit Hamiltonian can be found in the +# :doc:`tutorial_quantum_chemistry` and :doc:`tutorial_vqe` +# tutorials. +# +# We begin by downloading a selection of datasets of :math:`H_2` molecule for +# various bond lengths using the +# `PennyLane Datasets library `__: + +bonds = [0.5, 0.58, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7, 1.9, 2.1] +datasets = qml.data.load("qchem", molname="H2", bondlength=bonds, basis="STO-3G") + +############################################################################## +# We can now extract the qubit Hamiltonians from these datasets for each bond length: + +hamiltonians = [d.hamiltonian for d in datasets] + +############################################################################## +# Each Hamiltonian can be written as a linear combination of fifteen tensor products of Pauli +# matrices. Let's take a look more closely at one of the Hamiltonians: + +h = hamiltonians[0] +_, h_ops = h.terms() + +print("Number of terms: {}\n".format(len(h_ops))) +for op in h_ops: + print("Measurement {} on wires {}".format(op.name, op.wires)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Number of terms: 15 +# +# Measurement Identity on wires +# Measurement PauliZ on wires +# Measurement PauliZ on wires +# Measurement ['PauliZ', 'PauliZ'] on wires +# Measurement ['PauliY', 'PauliX', 'PauliX', 'PauliY'] on wires +# Measurement ['PauliY', 'PauliY', 'PauliX', 'PauliX'] on wires +# Measurement ['PauliX', 'PauliX', 'PauliY', 'PauliY'] on wires +# Measurement ['PauliX', 'PauliY', 'PauliY', 'PauliX'] on wires +# Measurement PauliZ on wires +# Measurement ['PauliZ', 'PauliZ'] on wires +# Measurement PauliZ on wires +# Measurement ['PauliZ', 'PauliZ'] on wires +# Measurement ['PauliZ', 'PauliZ'] on wires +# Measurement ['PauliZ', 'PauliZ'] on wires +# Measurement ['PauliZ', 'PauliZ'] on wires + +############################################################################## +# Defining the energy function +# ---------------------------- +# +# The fifteen Pauli terms comprising each Hamiltonian can conventionally be evaluated in a +# sequential manner: we evaluate one expectation value at a time before moving on to the next. +# However, this task is highly suited to parallelization. With access to multiple QPUs, +# we can split up evaluating the terms between the QPUs and gain an increase in processing speed. +# +# +# .. note:: +# Some of the Pauli terms commute, and so they can be evaluated in practice with fewer than +# fifteen quantum circuit runs. Nevertheless, these quantum circuit runs can still be +# parallelized to multiple QPUs. +# +# Let's suppose we have access to two quantum devices. In this tutorial we consider two +# simulators from Rigetti: ``4q-qvm`` and ``9q-square-qvm``, but we could also run on hardware +# devices from Rigetti or other providers. +# +# We can evaluate the expectation value of each Hamiltonian with eight terms run on +# one device and seven terms run on the other, as summarized by the diagram below: +# +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_diagram.png +# :width: 65% +# :align: center +# +# To do this, start by instantiating a device for each term: + +dev1 = [qml.device("rigetti.qvm", device="4q-qvm") for _ in range(8)] +dev2 = [qml.device("rigetti.qvm", device="9q-square-qvm") for _ in range(7)] +devs = dev1 + dev2 + +############################################################################## +# .. note:: +# +# For the purposes of this demonstration, we are simulating the QPUs using the +# ``rigetti.qvm`` simulator. To run this demonstration on hardware, simply +# swap ``rigetti.qvm`` for ``rigetti.qpu`` and specify the hardware device to run on. +# +# Please refer to the `Rigetti website `__ for an up-to-date +# list on available QPUs. +# +# .. warning:: +# Rigetti's QVM and Quil Compiler services must be running for this tutorial to execute. They +# can be installed by consulting the `Rigetti documentation +# `__ or, for users with Docker, by running: +# +# .. code-block:: bash +# +# docker run -d -p 5555:5555 rigetti/quilc -R -p 5555 +# docker run -d -p 5000:5000 rigetti/qvm -S -p 5000 +# +# We must also define a circuit to prepare the ground state, which is a superposition of the +# Hartree-Fock (:math:`|1100\rangle`) and doubly-excited (:math:`|0011\rangle`) configurations. +# The simple circuit below is able to prepare states of the form :math:`\alpha |1100\rangle + +# \beta |0011\rangle` and hence encode the ground state wave function of the hydrogen molecule. The +# circuit has a single free parameter, which controls a Y-rotation on the third qubit. + + +def circuit(param, H): + qml.BasisState(np.array([1, 1, 0, 0], requires_grad=False), wires=[0, 1, 2, 3]) + qml.RY(param, wires=2) + qml.CNOT(wires=[2, 3]) + qml.CNOT(wires=[2, 0]) + qml.CNOT(wires=[3, 1]) + return qml.expval(H) + + +############################################################################## +# The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. +# The values of these Y-rotations can be found by minimizing the ground state energy as outlined in +# :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on +# comparing the speed of evaluating the potential energy surface with sequential and parallel +# evaluation. These parameters can be downloaded by clicking :download:`here +# <../_static/demonstration_assets/vqe_parallel/RY_params.npy>`. + +params = np.load("vqe_parallel/RY_params.npy") + +############################################################################## +# Calculating the potential energy surface +# ---------------------------------------- +# The most vanilla execution of these 10 energy surfaces is using the standard PennyLane functionalities by executing the QNodes. +# Internally, this creates a measurement for each term in the Hamiltonian that are then sequentially computed. + +print("Evaluating the potential energy surface sequentially") +t0 = time.time() + +energies_seq = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Sequential execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_seq.append(qml.QNode(circuit, devs[0])(param, h)) + +dt_seq = time.time() - t0 + +print(f"Evaluation time: {dt_seq:.2f} s") + +############################################################################## +# We can parallelize the individual evaluations using ``dask`` in the following way: We take the 15 terms of the Hamiltonian and +# distribute them to the 15 devices in ``devs``. This evaluation is delayed using ``dask.delayed`` and later computed +# in parallel using ``dask.compute``, which asynchronously executes the delayed objects in ``results``. + + +def compute_energy_parallel(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + for i in range(len(H_ops)): + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_ops[i])) + + results = dask.compute(*results, scheduler="threads") + result = sum(c * r for c, r in zip(H_coeffs, results)) + return result + + +############################################################################## +# We can now compute all 10 samples from the energy surface sequentially, where each execution is making use of +# parallel device execution. Curiously, in this example the overhead from doing so outweighs the speed-up +# and the execution is slower than standard execution using ``qml.expval``. For different circuits and +# different Hamiltonians, however, parallelization may provide significant speed-ups. + +print("Evaluating the potential energy surface in parallel") +t0 = time.time() + +energies_par = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par.append(compute_energy_parallel(h, devs, param)) + +dt_par = time.time() - t0 + +print(f"Evaluation time: {dt_par:.2f} s") + + +############################################################################## +# We can improve this procedure further by optimizing the measurement. Currently, we are measuring each term of the Hamiltonian +# in a separate measurement. This is not necessary as there are sub-groups of commuting terms in the Hamiltonian that can be measured +# simultaneously. We can utilize the grouping function :func:`~.pennylane.pauli.group_observables` to generate few measurements that +# are executed in parallel: + + +def compute_energy_parallel_optimized(H, devs, param): + H_coeffs, H_ops = H.terms() + assert len(H_ops) == len(devs) + results = [] + + obs_groupings, coeffs_groupings = qml.pauli.group_observables( + H_ops, H_coeffs, "qwc" + ) + + for i, (obs, coeffs) in enumerate(zip(obs_groupings, coeffs_groupings)): + H_part = qml.Hamiltonian(coeffs, obs) + qnode = qml.QNode(circuit, devs[i]) + results.append(dask.delayed(qnode)(param, H_part)) + + result = qml.math.sum(dask.compute(*results, scheduler="threads")) + return result + +print( + "Evaluating the potential energy surface in parallel with measurement optimization" +) +t0 = time.time() + +energies_par_opt = [] +for i, (h, param) in enumerate(zip(hamiltonians, params)): + print( + f"{i+1} / {len(bonds)}: Parallel execution and measurement optimization; Running for inter-atomic distance {bonds[i]} Å" + ) + energies_par_opt.append(compute_energy_parallel_optimized(h, devs, param)) + +dt_par_opt = time.time() - t0 + +print(f"Evaluation time: {dt_par_opt:.2f} s") + + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Evaluating the potential energy surface sequentially +# 1 / 10: Sequential execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Sequential execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Sequential execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Sequential execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Sequential execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Sequential execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Sequential execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Sequential execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Sequential execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Sequential execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 39.33 s +# +# Evaluating the potential energy surface in parallel +# 1 / 10: Parallel execution; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution; Running for inter-atomic distance 2.1 Å +# Evaluation time: 73.42 s +# +# Evaluating the potential energy surface in parallel with measurement optimization +# 1 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.3 Å +# 2 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.5 Å +# 3 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.7 Å +# 4 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 0.9 Å +# 5 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.1 Å +# 6 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.3 Å +# 7 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.5 Å +# 8 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.7 Å +# 9 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 1.9 Å +# 10 / 10: Parallel execution and measurement optimization; Running for inter-atomic distance 2.1 Å +# Evaluation time: 26.51 s + + +############################################################################## +# We have seen how Hamiltonian measurements can be parallelized and optimized at the same time. + +print("Speed up: {0:.2f}".format(dt_seq / dt_par_opt)) + +############################################################################## +# .. rst-class:: sphx-glr-script-out +# +# +# .. code-block:: none +# +# Speed up: 1.48 + +############################################################################## +# To conclude the tutorial, let's plot the calculated +# potential energy surfaces: + +np.savez( + "vqe_parallel", + energies_seq=energies_seq, + energies_par=energies_par, + energies_par_opt=energies_par_opt, +) + +plt.plot( + bonds, energies_seq, linewidth=2.2, marker="d", color="green", label="sequential" +) +plt.plot(bonds, energies_par, linewidth=2.2, marker="o", color="red", label="parallel") +plt.plot( + bonds, + energies_par_opt, + linewidth=2.2, + marker="d", + color="blue", + label="paralell and optimized", +) +plt.legend(fontsize=12) +plt.title("Potential energy surface for molecular hydrogen", fontsize=12) +plt.xlabel("Atomic separation (Å)", fontsize=16) +plt.ylabel("Ground state energy (Ha)", fontsize=16) +plt.grid(True) + +############################################################################## +# .. figure:: /_static/demonstration_assets/vqe_parallel/vqe_parallel_001.png +# :width: 80% +# :align: center +# + +############################################################################## +# These surfaces overlap, with any variation due to the limited number of shots used to evaluate the +# expectation values in the ``rigetti.qvm`` device (we are using the default value of +# ``shots=1024``). + +############################################################################## diff --git a/demonstrations_v2/vqe_parallel/metadata.json b/demonstrations_v2/vqe_parallel/metadata.json new file mode 100644 index 0000000000..032b3bd621 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/metadata.json @@ -0,0 +1,32 @@ +{ + "title": "VQE with parallel QPUs with Rigetti", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-02-14T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_VQE_parallel_QPUs_Rigetti.png" + } + ], + "seoDescription": "Using parallel QPUs to speed up the calculation of the potential energy surface of molecular Hamiltonian.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/vqe_parallel/requirements.in b/demonstrations_v2/vqe_parallel/requirements.in new file mode 100644 index 0000000000..f0de54fb32 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/requirements.in @@ -0,0 +1,3 @@ +dask +matplotlib +pennylane diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/RY_params.npy b/demonstrations_v2/vqe_parallel/vqe_parallel/RY_params.npy new file mode 100644 index 0000000000000000000000000000000000000000..b1b448f3ccb190b410b71598b08ad75c1f17c2e0 GIT binary patch literal 208 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-20EHL3bhL411`6(Mh!vnJNIk3#>Iwb9NNER?R|- + + +image/svg+xml \ No newline at end of file diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.30.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.30.xyz new file mode 100644 index 0000000000..6f11359b42 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.30.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.1500 +H 0.00000 0.00000 0.1500 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.50.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.50.xyz new file mode 100644 index 0000000000..39aff51153 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.50.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.25000 +H 0.00000 0.00000 0.25000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.70.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.70.xyz new file mode 100644 index 0000000000..13fce0d080 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.70.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.35000 +H 0.00000 0.00000 0.35000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.90.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.90.xyz new file mode 100644 index 0000000000..545a409730 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_0.90.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.45000 +H 0.00000 0.00000 0.45000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.10.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.10.xyz new file mode 100644 index 0000000000..625c01cb99 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.10.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.55000 +H 0.00000 0.00000 0.55000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.30.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.30.xyz new file mode 100644 index 0000000000..15f60cfaa1 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.30.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.65000 +H 0.00000 0.00000 0.65000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.50.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.50.xyz new file mode 100644 index 0000000000..5c595ab62a --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.50.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.75000 +H 0.00000 0.00000 0.75000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.70.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.70.xyz new file mode 100644 index 0000000000..7ba4b669a0 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.70.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.85000 +H 0.00000 0.00000 0.85000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.90.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.90.xyz new file mode 100644 index 0000000000..920fa3a387 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_1.90.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -0.95000 +H 0.00000 0.00000 0.95000 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/h2_2.10.xyz b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_2.10.xyz new file mode 100644 index 0000000000..5b992d6a80 --- /dev/null +++ b/demonstrations_v2/vqe_parallel/vqe_parallel/h2_2.10.xyz @@ -0,0 +1,4 @@ +2 +in Angstrom +H 0.00000 0.00000 -1.0500 +H 0.00000 0.00000 1.0500 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/vqe_diagram.png b/demonstrations_v2/vqe_parallel/vqe_parallel/vqe_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..652890021f5da7d2720bd54aa722e2d2a2cd7e36 GIT binary patch literal 273301 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⁡QHDV7->I!b>=%$ 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 z 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�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 literal 0 HcmV?d00001 diff --git a/demonstrations_v2/vqe_parallel/vqe_parallel/vqe_parallel.zip b/demonstrations_v2/vqe_parallel/vqe_parallel/vqe_parallel.zip new file mode 100644 index 0000000000000000000000000000000000000000..896a833606dd86a1fde9b3c1cf183458dc5f8b89 GIT binary patch literal 1382 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 Date: Fri, 20 Dec 2024 11:02:43 -0500 Subject: [PATCH 02/29] pick lib.qml tool --- .gitignore | 1 + .vscode/settings.json | 7 + Makefile | 4 + conf.py | 9 +- lib/qml/.python-version | 1 + lib/qml/README.md | 0 lib/qml/pyproject.toml | 14 + lib/qml/qml/__init__.py | 0 lib/qml/qml/app.py | 127 ++ lib/qml/qml/context.py | 41 + lib/qml/qml/lib/__init__.py | 0 lib/qml/qml/lib/demo.py | 55 + lib/qml/qml/lib/fs.py | 10 + lib/qml/qml/lib/virtual_env.py | 20 + poetry.lock | 3814 +++++++++++++++++++------------- pyproject.toml | 15 +- 16 files changed, 2612 insertions(+), 1506 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 lib/qml/.python-version create mode 100644 lib/qml/README.md create mode 100644 lib/qml/pyproject.toml create mode 100644 lib/qml/qml/__init__.py create mode 100644 lib/qml/qml/app.py create mode 100644 lib/qml/qml/context.py create mode 100644 lib/qml/qml/lib/__init__.py create mode 100644 lib/qml/qml/lib/demo.py create mode 100644 lib/qml/qml/lib/fs.py create mode 100644 lib/qml/qml/lib/virtual_env.py diff --git a/.gitignore b/.gitignore index beb2939413..bc97193548 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ backreferences .DS_Store demos_community.rst .venv/ +.venv-build/ **/*.egg-info/ # Generated after installation diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..388c155d48 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.analysis.ignore": [ + ".venv/**", + ".venv-build/**", + "_build/**" + ] +} diff --git a/Makefile b/Makefile index ff5c334968..95f29b7031 100644 --- a/Makefile +++ b/Makefile @@ -81,3 +81,7 @@ environment: $$PYTHON_VENV_PATH/bin/python -m pip install --upgrade git+https://github.com/PennyLaneAI/pennylane.git#egg=pennylane;\ fi;\ fi + +fmt: + poetry run ruff check --fix lib/ + poetry run ruff format lib/ diff --git a/conf.py b/conf.py index 68e7bf060e..689306d019 100644 --- a/conf.py +++ b/conf.py @@ -19,6 +19,7 @@ from jinja2 import FileSystemLoader, Environment import yaml from pennylane import PennyLaneDeprecationWarning +from pathlib import Path sys.path.insert(0, os.path.abspath(".")) sys.path.insert(0, os.path.join(os.path.dirname(__file__))) @@ -56,12 +57,12 @@ "extension", ] - html_baseurl = "https://pennylane.ai/qml/" +demo_staging_dir = os.getenv("DEMO_STAGING_DIR", "demonstrations") sphinx_gallery_conf = { # path to your example scripts - "examples_dirs": ["demonstrations"], + "examples_dirs": [demo_staging_dir], # path where to save gallery generated examples "gallery_dirs": ["demos"], # execute files that match the following filename pattern, @@ -85,6 +86,8 @@ "doc_module" : ("pennylane"), "junit": "../test-results/sphinx-gallery/junit.xml", "reset_modules": ("module_resets.reset_jax", "matplotlib", "seaborn"), + "show_signature": False, + 'download_all_examples': False, } @@ -136,7 +139,7 @@ # 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 = ["_build", "Thumbs.db", ".DS_Store", "*venv", "sphinxext"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "*venv", "*venv-build", "sphinxext"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" diff --git a/lib/qml/.python-version b/lib/qml/.python-version new file mode 100644 index 0000000000..c8cfe39591 --- /dev/null +++ b/lib/qml/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/lib/qml/README.md b/lib/qml/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/qml/pyproject.toml b/lib/qml/pyproject.toml new file mode 100644 index 0000000000..ae6c67ff19 --- /dev/null +++ b/lib/qml/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "qml-tool" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "cyclopts>=3.1.2", + "dulwich>=0.22.6", + "uv>=0.5.6", +] + +[project.scripts] +qml = "qml.app:app" diff --git a/lib/qml/qml/__init__.py b/lib/qml/qml/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/qml/qml/app.py b/lib/qml/qml/app.py new file mode 100644 index 0000000000..c7b46e487e --- /dev/null +++ b/lib/qml/qml/app.py @@ -0,0 +1,127 @@ +import typer +from pathlib import Path +from qml.lib.demo import Demo, find_demos +from qml.context import Context +from qml.lib.virtual_env import Virtualenv +from qml.lib.fs import copy_any +import shutil +import os +import subprocess +import sys + +app = typer.Typer(name="qml", no_args_is_help=True) + + +def install_build_dependencies(build_dir: Path, venv: Virtualenv): + build_requirements_file = build_dir / "requirements-build.txt" + subprocess.run( + [ + sys.executable, + "-m", + "poetry", + "export", + "--without-hashes", + "--format", + "requirements.txt", + "--only", + "base", + "--output", + str(build_requirements_file), + ] + ).check_returncode() + + print("Installing build dependencies...") + subprocess.run( + [venv.python, "-m", "pip", "install", "-r", str(build_requirements_file)] + ).check_returncode() + + +def install_dependencies(build_dir: Path, venv: Virtualenv, demos: list[Demo]): + constraints_file = (build_dir / "constraints.txt").resolve() + + print("Generating constraints...") + subprocess.run( + [ + sys.executable, + "-m", + "poetry", + "export", + "--without-hashes", + "--format", + "constraints.txt", + "--only", + "executable-dependencies", + "--output", + str(constraints_file), + ] + ).check_returncode() + + print("Generating requirements...") + requirements: set[str] = set() + for demo in demos: + if demo.executable: + requirements.update(demo.requirements()) + + print("Installing runtime dependencies...") + subprocess.run( + [ + venv.python, + "-m", + "pip", + "install", + "--constraint", + str(constraints_file), + *requirements, + ] + ).check_returncode() + + +@app.command() +def help(): + print("Help!") + + +@app.command() +def build(demo_names: list[str], target: str = "html", execute: bool = False): + ctx = Context() + + demos = list(find_demos(ctx.demos_dir, *demo_names)) + ctx.build_dir.mkdir(exist_ok=True) + shutil.copytree( + ctx.repo_root / "_static", ctx.build_dir / "_static", dirs_exist_ok=True + ) + stage_dir = ctx.build_dir / "demonstrations" + if stage_dir.exists(): + shutil.rmtree(stage_dir) + + stage_dir.mkdir(parents=True) + # Need a 'GALLERY_HEADER' file for sphinx-gallery + with open(stage_dir / "GALLERY_HEADER.rst", "w"): + pass + + print(f"Building {len(demos)} demos") + for demo in demos: + # Use copy2 to perserve file modification time + shutil.copy2(demo.py_file, (stage_dir / demo.name).with_suffix(".py")) + + for resource in demo.resources: + copy_any(resource, (stage_dir / resource.name)) + + env = os.environ | {"DEMO_STAGING_DIR": str(stage_dir.resolve())} + build_venv = ctx.build_venv() + install_build_dependencies(ctx.build_dir, build_venv) + + cmd = [ + str(build_venv.path / "bin" / "sphinx-build"), + "-b", + target, + ] + if execute: + install_dependencies(ctx.build_dir, build_venv, demos) + else: + cmd.extend(("-D", "plot_gallery=0")) + + cmd.append(str(ctx.repo_root)) + cmd.append(str((ctx.build_dir / target).resolve())) + + subprocess.run(cmd, env=env).check_returncode() diff --git a/lib/qml/qml/context.py b/lib/qml/qml/context.py new file mode 100644 index 0000000000..377810d6ee --- /dev/null +++ b/lib/qml/qml/context.py @@ -0,0 +1,41 @@ +from dulwich.repo import Repo +from pathlib import Path +import functools +from qml.lib.virtual_env import Virtualenv + + +class Context: + """Context for CLI commands.""" + + @property + def repo_root(self) -> Path: + """Absolute path to repository root.""" + return Path(self.repo.path).resolve() + + @property + def demos_dir(self) -> Path: + """Path to the content dir, relative to the current + working directory.""" + return self.repo_root / "demonstrations_v2" + + @property + def build_dir(self) -> Path: + return self.repo_root / "_build" + + @property + def build_venv_path(self) -> Path: + return self.repo_root / ".venv-build" + + def build_venv(self) -> "Virtualenv": + venv = Virtualenv(self.build_venv_path) + + return venv + + @functools.cached_property + def cwd(self) -> Path: + return Path.cwd().resolve() + + @functools.cached_property + def repo(self) -> Repo: + """dulwich ``Repo`` object for the repo.""" + return Repo.discover() diff --git a/lib/qml/qml/lib/__init__.py b/lib/qml/qml/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/qml/qml/lib/demo.py b/lib/qml/qml/lib/demo.py new file mode 100644 index 0000000000..269416d88f --- /dev/null +++ b/lib/qml/qml/lib/demo.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass +from pathlib import Path +from collections.abc import Sequence, Iterator + + +@dataclass +class Demo: + name: str + path: Path + + @property + def py_file(self) -> Path: + return self.path / "demo.py" + + @property + def metadata_file(self) -> Path: + return self.path / "metadata.json" + + @property + def requirements_file(self) -> Path: + return self.path / "requirements.in" + + @property + def resources(self) -> Sequence[Path]: + return tuple( + p + for p in self.path.iterdir() + if p not in {self.py_file, self.metadata_file, self.requirements_file} + ) + + @property + def executable(self) -> bool: + return self.name.startswith("tutorial_") + + def requirements(self): + with open(self.requirements_file, "r") as f: + return f.read().splitlines() + + +def find_demos(search_dir: Path, *names: str) -> Iterator[Demo]: + if not names: + yield from ( + Demo(name=demo_dir.name, path=demo_dir.resolve()) + for demo_dir in search_dir.iterdir() + if demo_dir.is_dir() + ) + + return + + for name in set(names): + demo_dir = search_dir / name + if not (demo_dir / "demo.py").exists(): + raise ValueError(f"No demo exists with name '{name}") + + yield Demo(name=name, path=demo_dir.resolve()) diff --git a/lib/qml/qml/lib/fs.py b/lib/qml/qml/lib/fs.py new file mode 100644 index 0000000000..c1957cb475 --- /dev/null +++ b/lib/qml/qml/lib/fs.py @@ -0,0 +1,10 @@ +from pathlib import Path +import shutil + + +def copy_any(src: Path, dest: Path, exist_ok: bool = False): + """Copy `src` to `path`. If `src` is a directory, copy it recursively.""" + if src.is_dir(): + shutil.copytree(src, dest, dirs_exist_ok=exist_ok) + else: + shutil.copy2(src, dest) diff --git a/lib/qml/qml/lib/virtual_env.py b/lib/qml/qml/lib/virtual_env.py new file mode 100644 index 0000000000..30a915b7cc --- /dev/null +++ b/lib/qml/qml/lib/virtual_env.py @@ -0,0 +1,20 @@ +from pathlib import Path +import subprocess +import sys + + +class Virtualenv: + def __init__(self, path: Path): + self.path = path.resolve() + + if not self.python.exists(): + self.init() + + @property + def python(self) -> Path: + return self.path / "bin" / "python" + + def init(self): + self.path.parent.mkdir(exist_ok=True) + + subprocess.run([sys.executable, "-m", "venv", self.path]).check_returncode() diff --git a/poetry.lock b/poetry.lock index 1a93345c53..0396ab1dab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "absl-py" @@ -120,13 +120,13 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" -version = "1.3.1" +version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, ] [package.dependencies] @@ -145,13 +145,13 @@ files = [ [[package]] name = "alembic" -version = "1.13.3" +version = "1.14.0" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" files = [ - {file = "alembic-1.13.3-py3-none-any.whl", hash = "sha256:908e905976d15235fae59c9ac42c4c5b75cfcefe3d27c0fbf7ae15a37715d80e"}, - {file = "alembic-1.13.3.tar.gz", hash = "sha256:203503117415561e203aa14541740643a611f641517f0209fcae63e9fa09f1a2"}, + {file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25"}, + {file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b"}, ] [package.dependencies] @@ -175,24 +175,24 @@ files = [ [[package]] name = "anyio" -version = "4.6.0" +version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a"}, - {file = "anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb"}, + {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, + {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -227,21 +227,18 @@ test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock [[package]] name = "asttokens" -version = "2.4.1" +version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, ] -[package.dependencies] -six = ">=1.12.0" - [package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "astunparse" @@ -271,32 +268,32 @@ files = [ [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, + {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "authlib" -version = "1.3.2" +version = "1.4.0" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc"}, - {file = "authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2"}, + {file = "Authlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"}, + {file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"}, ] [package.dependencies] @@ -321,13 +318,13 @@ scipy = ["scipy"] [[package]] name = "autoray" -version = "0.6.12" +version = "0.7.0" description = "Abstract your array operations." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "autoray-0.6.12-py3-none-any.whl", hash = "sha256:3ed7a4abcec052bcbb4f0447c426d0a0b9b9fa03ab71e76eaa77747ca43ac3e2"}, - {file = "autoray-0.6.12.tar.gz", hash = "sha256:721328aa06fc3577155d988052614a7b4bd6e4d01b340695344031ee4abd2a1e"}, + {file = "autoray-0.7.0-py3-none-any.whl", hash = "sha256:03103957df3d1b66b8068158056c2909a72095b19d1b24262261276a714a5d07"}, + {file = "autoray-0.7.0.tar.gz", hash = "sha256:7829d21258512f87e02f23ce74ae5759af4ce8998069d2cce53468f1d701a219"}, ] [package.extras] @@ -376,6 +373,52 @@ packaging = "*" python-dateutil = ">=2.8,<3.0" requests = ">=2.28,<3.0" +[[package]] +name = "build" +version = "1.2.2.post1" +description = "A simple, correct Python build frontend" +optional = false +python-versions = ">=3.8" +files = [ + {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, + {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} +packaging = ">=19.1" +pyproject_hooks = "*" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] +test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] +typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] +uv = ["uv (>=0.1.18)"] +virtualenv = ["virtualenv (>=20.0.35)"] + +[[package]] +name = "cachecontrol" +version = "0.14.1" +description = "httplib2 caching for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cachecontrol-0.14.1-py3-none-any.whl", hash = "sha256:65e3abd62b06382ce3894df60dde9e0deb92aeb734724f68fa4f3b91e97206b9"}, + {file = "cachecontrol-0.14.1.tar.gz", hash = "sha256:06ef916a1e4eb7dba9948cdfc9c76e749db2e02104a9a1277e8b642591a0f717"}, +] + +[package.dependencies] +filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2,<2.0.0" +requests = ">=2.16.0" + +[package.extras] +dev = ["CacheControl[filecache,redis]", "build", "cherrypy", "codespell[tomli]", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"] +filecache = ["filelock (>=3.8.0)"] +redis = ["redis (>=2.10.5)"] + [[package]] name = "cachetools" version = "5.5.0" @@ -389,13 +432,13 @@ files = [ [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -479,101 +522,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -599,22 +657,22 @@ docs = ["furo (==2023.5.20)", "sphinx (<8)", "sphinx-issues (<4)"] [[package]] name = "chex" -version = "0.1.86" +version = "0.1.88" description = "Chex: Testing made fun, in JAX!" optional = false python-versions = ">=3.9" files = [ - {file = "chex-0.1.86-py3-none-any.whl", hash = "sha256:251c20821092323a3d9c28e1cf80e4a58180978bec368f531949bd9847eee568"}, - {file = "chex-0.1.86.tar.gz", hash = "sha256:e8b0f96330eba4144659e1617c0f7a57b161e8cbb021e55c6d5056c7378091d1"}, + {file = "chex-0.1.88-py3-none-any.whl", hash = "sha256:234b61a5baa8132802e4b9c5657167d6c8a911d90a59a0bec47d537567e41b75"}, + {file = "chex-0.1.88.tar.gz", hash = "sha256:565de897b1373232cdfca5e699f50fa49403d2c7d23f6c5a75a97ef713d2fe36"}, ] [package.dependencies] absl-py = ">=0.9.0" -jax = ">=0.4.16" -jaxlib = ">=0.1.37" +jax = ">=0.4.27" +jaxlib = ">=0.4.27" numpy = ">=1.24.1" toolz = ">=0.9.0" -typing-extensions = ">=4.2.0" +typing_extensions = ">=4.2.0" [[package]] name = "cirq" @@ -748,6 +806,21 @@ files = [ [package.dependencies] cirq-core = "1.0.0" +[[package]] +name = "cleo" +version = "2.1.0" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, + {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, +] + +[package.dependencies] +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=3.0.0,<4.0.0" + [[package]] name = "click" version = "8.1.7" @@ -764,13 +837,13 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "cloudpickle" -version = "3.0.0" +version = "3.1.0" description = "Pickler class to extend the standard pickle.Pickler functionality" optional = false python-versions = ">=3.8" files = [ - {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, - {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, + {file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"}, + {file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"}, ] [[package]] @@ -786,76 +859,65 @@ files = [ [[package]] name = "contourpy" -version = "1.3.0" +version = "1.3.1" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, - {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, - {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, - {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, - {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, - {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, - {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, - {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, - {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, - {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, - {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, - {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, - {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, + {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"}, + {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595"}, + {file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697"}, + {file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f"}, + {file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"}, + {file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d"}, + {file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e"}, + {file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1"}, + {file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82"}, + {file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53"}, + {file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699"}, ] [package.dependencies] @@ -929,53 +991,66 @@ gcp = ["google-auth (>=2.16.2)", "google-cloud-storage (>=2.7.0)"] mysql = ["mysqlclient (>=2.1.1)"] postgres = ["psycopg2-binary (>=2.9.5)"] +[[package]] +name = "crashtest" +version = "0.4.1" +description = "Manage Python errors with ease" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, +] + [[package]] name = "cryptography" -version = "43.0.1" +version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, - {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, - {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, - {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, - {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, - {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, - {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -995,115 +1070,111 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cytoolz" -version = "0.12.3" +version = "1.0.1" description = "Cython implementation of Toolz: High performance functional utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, - {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, - {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, - {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, - {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, - {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, - {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, - {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, - {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, - {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, - {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, - {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, - {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, - {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, - {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, - {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, - {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, - {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, - {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, - {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, - {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, - {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, - {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, - {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, + {file = "cytoolz-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cec9af61f71fc3853eb5dca3d42eb07d1f48a4599fa502cbe92adde85f74b042"}, + {file = "cytoolz-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:140bbd649dbda01e91add7642149a5987a7c3ccc251f2263de894b89f50b6608"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90124bdc42ff58b88cdea1d24a6bc5f776414a314cc4d94f25c88badb3a16d1"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e74801b751e28f7c5cc3ad264c123954a051f546f2fdfe089f5aa7a12ccfa6da"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:582dad4545ddfb5127494ef23f3fa4855f1673a35d50c66f7638e9fb49805089"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7bd0618e16efe03bd12f19c2a26a27e6e6b75d7105adb7be1cd2a53fa755d8"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d74cca6acf1c4af58b2e4a89cc565ed61c5e201de2e434748c93e5a0f5c541a5"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:823a3763828d8d457f542b2a45d75d6b4ced5e470b5c7cf2ed66a02f508ed442"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:51633a14e6844c61db1d68c1ffd077cf949f5c99c60ed5f1e265b9e2966f1b52"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3ec9b01c45348f1d0d712507d54c2bfd69c62fbd7c9ef555c9d8298693c2432"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1855022b712a9c7a5bce354517ab4727a38095f81e2d23d3eabaf1daeb6a3b3c"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9930f7288c4866a1dc1cc87174f0c6ff4cad1671eb1f6306808aa6c445857d78"}, + {file = "cytoolz-1.0.1-cp310-cp310-win32.whl", hash = "sha256:a9baad795d72fadc3445ccd0f122abfdbdf94269157e6d6d4835636dad318804"}, + {file = "cytoolz-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:ad95b386a84e18e1f6136f6d343d2509d4c3aae9f5a536f3dc96808fcc56a8cf"}, + {file = "cytoolz-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d958d4f04d9d7018e5c1850790d9d8e68b31c9a2deebca74b903706fdddd2b6"}, + {file = "cytoolz-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f445b8b731fc0ecb1865b8e68a070084eb95d735d04f5b6c851db2daf3048ab"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f546a96460a7e28eb2ec439f4664fa646c9b3e51c6ebad9a59d3922bbe65e30"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0317681dd065532d21836f860b0563b199ee716f55d0c1f10de3ce7100c78a3b"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c0ef52febd5a7821a3fd8d10f21d460d1a3d2992f724ba9c91fbd7a96745d41"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebaf419acf2de73b643cf96108702b8aef8e825cf4f63209ceb078d5fbbbfd"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f7f04eeb4088947585c92d6185a618b25ad4a0f8f66ea30c8db83cf94a425e3"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f61928803bb501c17914b82d457c6f50fe838b173fb40d39c38d5961185bd6c7"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2960cb4fa01ccb985ad1280db41f90dc97a80b397af970a15d5a5de403c8c61"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2b407cc3e9defa8df5eb46644f6f136586f70ba49eba96f43de67b9a0984fd3"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8245f929144d4d3bd7b972c9593300195c6cea246b81b4c46053c48b3f044580"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37385db03af65763933befe89fa70faf25301effc3b0485fec1c15d4ce4f052"}, + {file = "cytoolz-1.0.1-cp311-cp311-win32.whl", hash = "sha256:50f9c530f83e3e574fc95c264c3350adde8145f4f8fc8099f65f00cc595e5ead"}, + {file = "cytoolz-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:b7f6b617454b4326af7bd3c7c49b0fc80767f134eb9fd6449917a058d17a0e3c"}, + {file = "cytoolz-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4"}, + {file = "cytoolz-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:207d4e4b445e087e65556196ff472ff134370d9a275d591724142e255f384662"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21cdf6bac6fd843f3b20280a66fd8df20dea4c58eb7214a2cd8957ec176f0bb3"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a55ec098036c0dea9f3bdc021f8acd9d105a945227d0811589f0573f21c9ce1"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a13ab79ff4ce202e03ab646a2134696988b554b6dc4b71451e948403db1331d8"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2d944799026e1ff08a83241f1027a2d9276c41f7a74224cd98b7df6e03957d"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88ba85834cd523b91fdf10325e1e6d71c798de36ea9bdc187ca7bd146420de6f"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a750b1af7e8bf6727f588940b690d69e25dc47cce5ce467925a76561317eaf7"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44a71870f7eae31d263d08b87da7c2bf1176f78892ed8bdade2c2850478cb126"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa87599ccc755de5a096a4d6c34984de6cd9dc928a0c5eaa7607457317aeaf9b"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67cd16537df51baabde3baa770ab7b8d16839c4d21219d5b96ac59fb012ebd2d"}, + {file = "cytoolz-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9"}, + {file = "cytoolz-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f89c48d8e5aec55ffd566a8ec858706d70ed0c6a50228eca30986bfa5b4da8b"}, + {file = "cytoolz-1.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6944bb93b287032a4c5ca6879b69bcd07df46f3079cf8393958cf0b0454f50c0"}, + {file = "cytoolz-1.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e027260fd2fc5cb041277158ac294fc13dca640714527219f702fb459a59823a"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88662c0e07250d26f5af9bc95911e6137e124a5c1ec2ce4a5d74de96718ab242"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309dffa78b0961b4c0cf55674b828fbbc793cf2d816277a5c8293c0c16155296"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edb34246e6eb40343c5860fc51b24937698e4fa1ee415917a73ad772a9a1746b"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54da7a8e4348a18d45d4d5bc84af6c716d7f131113a4f1cc45569d37edff1b"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:241c679c3b1913c0f7259cf1d9639bed5084c86d0051641d537a0980548aa266"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bfc860251a8f280ac79696fc3343cfc3a7c30b94199e0240b6c9e5b6b01a2a5"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8edd1547014050c1bdad3ff85d25c82bd1c2a3c96830c6181521eb78b9a42b3"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b349bf6162e8de215403d7f35f8a9b4b1853dc2a48e6e1a609a5b1a16868b296"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1b18b35256219b6c3dd0fa037741b85d0bea39c552eab0775816e85a52834140"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:738b2350f340ff8af883eb301054eb724997f795d20d90daec7911c389d61581"}, + {file = "cytoolz-1.0.1-cp313-cp313-win32.whl", hash = "sha256:9cbd9c103df54fcca42be55ef40e7baea624ac30ee0b8bf1149f21146d1078d9"}, + {file = "cytoolz-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:90e577e08d3a4308186d9e1ec06876d4756b1e8164b92971c69739ea17e15297"}, + {file = "cytoolz-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3a509e4ac8e711703c368476b9bbce921fcef6ebb87fa3501525f7000e44185"}, + {file = "cytoolz-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7eecab6373e933dfbf4fdc0601d8fd7614f8de76793912a103b5fccf98170cd"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e55ed62087f6e3e30917b5f55350c3b6be6470b849c6566018419cd159d2cebc"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43de33d99a4ccc07234cecd81f385456b55b0ea9c39c9eebf42f024c313728a5"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139bed875828e1727018aa0982aa140e055cbafccb7fd89faf45cbb4f2a21514"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22c12671194b518aa8ce2f4422bd5064f25ab57f410ba0b78705d0a219f4a97a"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79888f2f7dc25709cd5d37b032a8833741e6a3692c8823be181d542b5999128e"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:51628b4eb41fa25bd428f8f7b5b74fbb05f3ae65fbd265019a0dd1ded4fdf12a"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1db9eb7179285403d2fb56ba1ff6ec35a44921b5e2fa5ca19d69f3f9f0285ea5"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:08ab7efae08e55812340bfd1b3f09f63848fe291675e2105eab1aa5327d3a16e"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e5fdc5264f884e7c0a1711a81dff112708a64b9c8561654ee578bfdccec6be09"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:90d6a2e6ab891043ee655ec99d5e77455a9bee9e1131bdfcfb745edde81200dd"}, + {file = "cytoolz-1.0.1-cp38-cp38-win32.whl", hash = "sha256:08946e083faa5147751b34fbf78ab931f149ef758af5c1092932b459e18dcf5c"}, + {file = "cytoolz-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:a91b4e10a9c03796c0dc93e47ebe25bb41ecc6fafc3cf5197c603cf767a3d44d"}, + {file = "cytoolz-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:980c323e626ba298b77ae62871b2de7c50b9d7219e2ddf706f52dd34b8be7349"}, + {file = "cytoolz-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:45f6fa1b512bc2a0f2de5123db932df06c7f69d12874fe06d67772b2828e2c8b"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93f42d9100c415155ad1f71b0de362541afd4ac95e3153467c4c79972521b6b"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a76d20dec9c090cdf4746255bbf06a762e8cc29b5c9c1d138c380bbdb3122ade"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:239039585487c69aa50c5b78f6a422016297e9dea39755761202fb9f0530fe87"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28307640ca2ab57b9fbf0a834b9bf563958cd9e038378c3a559f45f13c3c541"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:454880477bb901cee3a60f6324ec48c95d45acc7fecbaa9d49a5af737ded0595"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:902115d1b1f360fd81e44def30ac309b8641661150fcbdde18ead446982ada6a"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e68e6b38473a3a79cee431baa22be31cac39f7df1bf23eaa737eaff42e213883"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:32fba3f63fcb76095b0a22f4bdcc22bc62a2bd2d28d58bf02fd21754c155a3ec"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0724ba4cf41eb40b6cf75250820ab069e44bdf4183ff78857aaf4f0061551075"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c42420e0686f887040d5230420ed44f0e960ccbfa29a0d65a3acd9ca52459209"}, + {file = "cytoolz-1.0.1-cp39-cp39-win32.whl", hash = "sha256:4ba8b16358ea56b1fe8e637ec421e36580866f2e787910bac1cf0a6997424a34"}, + {file = "cytoolz-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:92d27f84bf44586853d9562bfa3610ecec000149d030f793b4cb614fd9da1813"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:83d19d55738ad9c60763b94f3f6d3c6e4de979aeb8d76841c1401081e0e58d96"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f112a71fad6ea824578e6393765ce5c054603afe1471a5c753ff6c67fd872d10"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a515df8f8aa6e1eaaf397761a6e4aff2eef73b5f920aedf271416d5471ae5ee"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c398e7b7023460bea2edffe5fcd0a76029580f06c3f6938ac3d198b47156f3"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3237e56211e03b13df47435b2369f5df281e02b04ad80a948ebd199b7bc10a47"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba0d1da50aab1909b165f615ba1125c8b01fcc30d606c42a61c42ea0269b5e2c"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25b6e8dec29aa5a390092d193abd673e027d2c0b50774ae816a31454286c45c7"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36cd6989ebb2f18fe9af8f13e3c61064b9f741a40d83dc5afeb0322338ad25f2"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47394f8ab7fca3201f40de61fdeea20a2baffb101485ae14901ea89c3f6c95d"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d00ac423542af944302e034e618fb055a0c4e87ba704cd6a79eacfa6ac83a3c9"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a5ca923d1fa632f7a4fb33c0766c6fba7f87141a055c305c3e47e256fb99c413"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058bf996bcae9aad3acaeeb937d42e0c77c081081e67e24e9578a6a353cb7fb2"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69e2a1f41a3dad94a17aef4a5cc003323359b9f0a9d63d4cc867cb5690a2551d"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67daeeeadb012ec2b59d63cb29c4f2a2023b0c4957c3342d354b8bb44b209e9a"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:54d3d36bbf0d4344d1afa22c58725d1668e30ff9de3a8f56b03db1a6da0acb11"}, + {file = "cytoolz-1.0.1.tar.gz", hash = "sha256:89cc3161b89e1bb3ed7636f74ed2e55984fd35516904fc878cae216e42b2c7d6"}, ] [package.dependencies] @@ -1114,21 +1185,21 @@ cython = ["cython"] [[package]] name = "dask" -version = "2024.9.0" +version = "2024.12.1" description = "Parallel PyData with Task Scheduling" optional = false python-versions = ">=3.10" files = [ - {file = "dask-2024.9.0-py3-none-any.whl", hash = "sha256:ceede9cfd418178a01ec3d11a0cde3f46678bd4a292ba84b57bbb401ce3f1cb8"}, - {file = "dask-2024.9.0.tar.gz", hash = "sha256:bfbe5b6c3b7937426539be27029800178ce63cea4da8d7e7de836a98384aa1d6"}, + {file = "dask-2024.12.1-py3-none-any.whl", hash = "sha256:1f32acddf1a6994e3af6734756f0a92467c47050bc29f3555bb9b140420e8e19"}, + {file = "dask-2024.12.1.tar.gz", hash = "sha256:bac809af21c2dd7eb06827bccbfc612504f3ee6435580e548af912828f823195"}, ] [package.dependencies] click = ">=8.1" cloudpickle = ">=3.0.0" -distributed = {version = "2024.9.0", optional = true, markers = "extra == \"distributed\""} +distributed = {version = "2024.12.1", optional = true, markers = "extra == \"distributed\""} fsspec = ">=2021.09.0" -importlib-metadata = {version = ">=4.13.0", markers = "python_version < \"3.12\""} +importlib_metadata = {version = ">=4.13.0", markers = "python_version < \"3.12\""} packaging = ">=20.0" partd = ">=1.4.0" pyyaml = ">=5.3.1" @@ -1139,7 +1210,7 @@ array = ["numpy (>=1.24)"] complete = ["dask[array,dataframe,diagnostics,distributed]", "lz4 (>=4.3.2)", "pyarrow (>=14.0.1)"] dataframe = ["dask-expr (>=1.1,<1.2)", "dask[array]", "pandas (>=2.0)"] diagnostics = ["bokeh (>=3.1.0)", "jinja2 (>=2.10.3)"] -distributed = ["distributed (==2024.9.0)"] +distributed = ["distributed (==2024.12.1)"] test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist"] [[package]] @@ -1155,20 +1226,20 @@ files = [ [[package]] name = "deprecated" -version = "1.2.14" +version = "1.2.15" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, + {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, + {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, ] [package.dependencies] wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] [[package]] name = "deprecation" @@ -1202,13 +1273,13 @@ termcolor = "*" [[package]] name = "dill" -version = "0.3.8" +version = "0.3.9" description = "serialize all of Python" optional = false python-versions = ">=3.8" files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, + {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, + {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, ] [package.extras] @@ -1264,21 +1335,32 @@ files = [ {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, ] +[[package]] +name = "distlib" +version = "0.3.9" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, +] + [[package]] name = "distributed" -version = "2024.9.0" +version = "2024.12.1" description = "Distributed scheduler for Dask" optional = false python-versions = ">=3.10" files = [ - {file = "distributed-2024.9.0-py3-none-any.whl", hash = "sha256:d3a6407efffd3ab70a7f3a068be0f15c7ba36adc4fc666ed3d3505ced2c79cbe"}, - {file = "distributed-2024.9.0.tar.gz", hash = "sha256:cb5f76ff230fc2249b15b0a66da46982e23249e5d8389c16dfe427e598b71c57"}, + {file = "distributed-2024.12.1-py3-none-any.whl", hash = "sha256:87e31abaa0ee3dc517b44fec4993d4b5d92257f926a8d2a12d52c005227154e7"}, + {file = "distributed-2024.12.1.tar.gz", hash = "sha256:438aa3ae48bfac9c2bb2ad03f9d47899286f9cb3db8a627b3b8c0de9e26f53dd"}, ] [package.dependencies] click = ">=8.0" cloudpickle = ">=3.0.0" -dask = "2024.9.0" +dask = "2024.12.1" jinja2 = ">=2.10.3" locket = ">=1.0.0" msgpack = ">=1.0.2" @@ -1385,6 +1467,93 @@ files = [ [package.extras] dev-env = ["black (==22.3.0)", "isort (==5.7.*)", "mypy (==0.931.*)", "pylint (==2.10.*)", "pytest (==6.2.*)", "twine (==3.3.*)", "wheel"] +[[package]] +name = "dulwich" +version = "0.21.7" +description = "Python Git Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c"}, + {file = "dulwich-0.21.7-cp310-cp310-win32.whl", hash = "sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08"}, + {file = "dulwich-0.21.7-cp310-cp310-win_amd64.whl", hash = "sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1"}, + {file = "dulwich-0.21.7-cp311-cp311-win32.whl", hash = "sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde"}, + {file = "dulwich-0.21.7-cp311-cp311-win_amd64.whl", hash = "sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91"}, + {file = "dulwich-0.21.7-cp312-cp312-win32.whl", hash = "sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec"}, + {file = "dulwich-0.21.7-cp312-cp312-win_amd64.whl", hash = "sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503"}, + {file = "dulwich-0.21.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a"}, + {file = "dulwich-0.21.7-cp37-cp37m-win32.whl", hash = "sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9"}, + {file = "dulwich-0.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041"}, + {file = "dulwich-0.21.7-cp38-cp38-win32.whl", hash = "sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b"}, + {file = "dulwich-0.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed"}, + {file = "dulwich-0.21.7-cp39-cp39-win32.whl", hash = "sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1"}, + {file = "dulwich-0.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31"}, + {file = "dulwich-0.21.7.tar.gz", hash = "sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968"}, +] + +[package.dependencies] +urllib3 = ">=1.25" + +[package.extras] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + [[package]] name = "dwave-cloud-client" version = "0.12.0" @@ -1722,13 +1891,13 @@ penaltymodel = ">=1.0.0" [[package]] name = "etils" -version = "1.9.4" +version = "1.11.0" description = "Collection of common python utils" optional = false python-versions = ">=3.10" files = [ - {file = "etils-1.9.4-py3-none-any.whl", hash = "sha256:4387e7a4911a3b5cc4b92b99a9211386d176b43bae1dac8e2fe345fc2cb95e4b"}, - {file = "etils-1.9.4.tar.gz", hash = "sha256:fad950414f0a1ca58c70c70915b0014f9953dd9bcf8aa951a0f75ff9becbeb24"}, + {file = "etils-1.11.0-py3-none-any.whl", hash = "sha256:a394cf3476bcec51c221426a70c39cd1006e889456ba41e4d7f12fd6814be7a5"}, + {file = "etils-1.11.0.tar.gz", hash = "sha256:aff3278a3be7fddf302dfd80335e9f924244666c71239cd91e836f3d055f1c4a"}, ] [package.dependencies] @@ -1740,7 +1909,7 @@ zipp = {version = "*", optional = true, markers = "extra == \"epath\""} [package.extras] all = ["etils[array-types]", "etils[eapp]", "etils[ecolab]", "etils[edc]", "etils[enp]", "etils[epath-gcs]", "etils[epath-s3]", "etils[epath]", "etils[epy]", "etils[etqdm]", "etils[etree-dm]", "etils[etree-jax]", "etils[etree-tf]", "etils[etree]"] array-types = ["etils[enp]"] -dev = ["chex", "optree", "pydantic", "pyink", "pylint (>=2.6.0)", "pytest", "pytest-subtests", "pytest-xdist", "tensorflow_datasets", "torch"] +dev = ["chex", "fiddle", "optree", "pydantic", "pyink", "pylint (>=2.6.0)", "pytest", "pytest-subtests", "pytest-xdist", "tensorflow_datasets", "torch"] docs = ["etils[all,dev]", "sphinx-apitree[ext]"] eapp = ["absl-py", "etils[epy]", "simple_parsing"] ecolab = ["etils[enp]", "etils[epy]", "etils[etree]", "jupyter", "mediapy", "numpy", "packaging", "protobuf"] @@ -1787,18 +1956,18 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "fastapi" -version = "0.115.0" +version = "0.115.6" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.0-py3-none-any.whl", hash = "sha256:17ea427674467486e997206a5ab25760f6b09e069f099b96f5b55a32fb6f1631"}, - {file = "fastapi-0.115.0.tar.gz", hash = "sha256:f93b4ca3529a8ebc6fc3fcf710e5efa8de3df9b41570958abf1d97d843138004"}, + {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, + {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.39.0" +starlette = ">=0.40.0,<0.42.0" typing-extensions = ">=4.8.0" [package.extras] @@ -1816,6 +1985,20 @@ files = [ {file = "fasteners-0.19.tar.gz", hash = "sha256:b4f37c3ac52d8a445af3a66bce57b33b5e90b97c696b7b984f530cf8f0ded09c"}, ] +[[package]] +name = "fastjsonschema" +version = "2.21.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, + {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + [[package]] name = "filelock" version = "3.16.1" @@ -1920,59 +2103,61 @@ testing = ["clu", "clu (<=0.0.9)", "einops", "gymnasium[accept-rom-license,atari [[package]] name = "fonttools" -version = "4.54.1" +version = "4.55.3" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, - {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, - {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, - {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, - {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, - {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, - {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, - {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, - {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, - {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, - {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, - {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, - {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, - {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, - {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, - {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, - {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, - {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, + {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0"}, + {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f"}, + {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841"}, + {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674"}, + {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276"}, + {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5"}, + {file = "fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261"}, + {file = "fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5"}, + {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e"}, + {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b"}, + {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90"}, + {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0"}, + {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b"}, + {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765"}, + {file = "fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f"}, + {file = "fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72"}, + {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35"}, + {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c"}, + {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7"}, + {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314"}, + {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427"}, + {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a"}, + {file = "fonttools-4.55.3-cp312-cp312-win32.whl", hash = "sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07"}, + {file = "fonttools-4.55.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54"}, + {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29"}, + {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4"}, + {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca"}, + {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b"}, + {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048"}, + {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe"}, + {file = "fonttools-4.55.3-cp313-cp313-win32.whl", hash = "sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628"}, + {file = "fonttools-4.55.3-cp313-cp313-win_amd64.whl", hash = "sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b"}, + {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:caf8230f3e10f8f5d7593eb6d252a37caf58c480b19a17e250a63dad63834cf3"}, + {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b586ab5b15b6097f2fb71cafa3c98edfd0dba1ad8027229e7b1e204a58b0e09d"}, + {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8c2794ded89399cc2169c4d0bf7941247b8d5932b2659e09834adfbb01589aa"}, + {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf4fe7c124aa3f4e4c1940880156e13f2f4d98170d35c749e6b4f119a872551e"}, + {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:86721fbc389ef5cc1e2f477019e5069e8e4421e8d9576e9c26f840dbb04678de"}, + {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:89bdc5d88bdeec1b15af790810e267e8332d92561dce4f0748c2b95c9bdf3926"}, + {file = "fonttools-4.55.3-cp38-cp38-win32.whl", hash = "sha256:bc5dbb4685e51235ef487e4bd501ddfc49be5aede5e40f4cefcccabc6e60fb4b"}, + {file = "fonttools-4.55.3-cp38-cp38-win_amd64.whl", hash = "sha256:cd70de1a52a8ee2d1877b6293af8a2484ac82514f10b1c67c1c5762d38073e56"}, + {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bdcc9f04b36c6c20978d3f060e5323a43f6222accc4e7fcbef3f428e216d96af"}, + {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c3ca99e0d460eff46e033cd3992a969658c3169ffcd533e0a39c63a38beb6831"}, + {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22f38464daa6cdb7b6aebd14ab06609328fe1e9705bb0fcc7d1e69de7109ee02"}, + {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed63959d00b61959b035c7d47f9313c2c1ece090ff63afea702fe86de00dbed4"}, + {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5e8d657cd7326eeaba27de2740e847c6b39dde2f8d7cd7cc56f6aad404ddf0bd"}, + {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb594b5a99943042c702c550d5494bdd7577f6ef19b0bc73877c948a63184a32"}, + {file = "fonttools-4.55.3-cp39-cp39-win32.whl", hash = "sha256:dc5294a3d5c84226e3dbba1b6f61d7ad813a8c0238fceea4e09aa04848c3d851"}, + {file = "fonttools-4.55.3-cp39-cp39-win_amd64.whl", hash = "sha256:aedbeb1db64496d098e6be92b2e63b5fac4e53b1b92032dfc6988e1ea9134a4d"}, + {file = "fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977"}, + {file = "fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45"}, ] [package.extras] @@ -2002,130 +2187,151 @@ files = [ [[package]] name = "frozendict" -version = "2.4.4" +version = "2.4.6" description = "A simple immutable dictionary" optional = false python-versions = ">=3.6" files = [ - {file = "frozendict-2.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a59578d47b3949437519b5c39a016a6116b9e787bb19289e333faae81462e59"}, - {file = "frozendict-2.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a342e439aef28ccec533f0253ea53d75fe9102bd6ea928ff530e76eac38906"}, - {file = "frozendict-2.4.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f79c26dff10ce11dad3b3627c89bb2e87b9dd5958c2b24325f16a23019b8b94"}, - {file = "frozendict-2.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2bd009cf4fc47972838a91e9b83654dc9a095dc4f2bb3a37c3f3124c8a364543"}, - {file = "frozendict-2.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:87ebcde21565a14fe039672c25550060d6f6d88cf1f339beac094c3b10004eb0"}, - {file = "frozendict-2.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:fefeb700bc7eb8b4c2dc48704e4221860d254c8989fb53488540bc44e44a1ac2"}, - {file = "frozendict-2.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:4297d694eb600efa429769125a6f910ec02b85606f22f178bafbee309e7d3ec7"}, - {file = "frozendict-2.4.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:812ab17522ba13637826e65454115a914c2da538356e85f43ecea069813e4b33"}, - {file = "frozendict-2.4.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fee9420475bb6ff357000092aa9990c2f6182b2bab15764330f4ad7de2eae49"}, - {file = "frozendict-2.4.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3148062675536724502c6344d7c485dd4667fdf7980ca9bd05e338ccc0c4471e"}, - {file = "frozendict-2.4.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:78c94991944dd33c5376f720228e5b252ee67faf3bac50ef381adc9e51e90d9d"}, - {file = "frozendict-2.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:1697793b5f62b416c0fc1d94638ec91ed3aa4ab277f6affa3a95216ecb3af170"}, - {file = "frozendict-2.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:199a4d32194f3afed6258de7e317054155bc9519252b568d9cfffde7e4d834e5"}, - {file = "frozendict-2.4.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85375ec6e979e6373bffb4f54576a68bf7497c350861d20686ccae38aab69c0a"}, - {file = "frozendict-2.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2d8536e068d6bf281f23fa835ac07747fb0f8851879dd189e9709f9567408b4d"}, - {file = "frozendict-2.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:259528ba6b56fa051bc996f1c4d8b57e30d6dd3bc2f27441891b04babc4b5e73"}, - {file = "frozendict-2.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:07c3a5dee8bbb84cba770e273cdbf2c87c8e035903af8f781292d72583416801"}, - {file = "frozendict-2.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6874fec816b37b6eb5795b00e0574cba261bf59723e2de607a195d5edaff0786"}, - {file = "frozendict-2.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f92425686323a950337da4b75b4c17a3327b831df8c881df24038d560640d4"}, - {file = "frozendict-2.4.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d58d9a8d9e49662c6dafbea5e641f97decdb3d6ccd76e55e79818415362ba25"}, - {file = "frozendict-2.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:93a7b19afb429cbf99d56faf436b45ef2fa8fe9aca89c49eb1610c3bd85f1760"}, - {file = "frozendict-2.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b70b431e3a72d410a2cdf1497b3aba2f553635e0c0f657ce311d841bf8273b6"}, - {file = "frozendict-2.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:e1b941132d79ce72d562a13341d38fc217bc1ee24d8c35a20d754e79ff99e038"}, - {file = "frozendict-2.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc2228874eacae390e63fd4f2bb513b3144066a977dc192163c9f6c7f6de6474"}, - {file = "frozendict-2.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63aa49f1919af7d45fb8fd5dec4c0859bc09f46880bd6297c79bb2db2969b63d"}, - {file = "frozendict-2.4.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6bf9260018d653f3cab9bd147bd8592bf98a5c6e338be0491ced3c196c034a3"}, - {file = "frozendict-2.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6eb716e6a6d693c03b1d53280a1947716129f5ef9bcdd061db5c17dea44b80fe"}, - {file = "frozendict-2.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d13b4310db337f4d2103867c5a05090b22bc4d50ca842093779ef541ea9c9eea"}, - {file = "frozendict-2.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:b3b967d5065872e27b06f785a80c0ed0a45d1f7c9b85223da05358e734d858ca"}, - {file = "frozendict-2.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:4ae8d05c8d0b6134bfb6bfb369d5fa0c4df21eabb5ca7f645af95fdc6689678e"}, - {file = "frozendict-2.4.4-py311-none-any.whl", hash = "sha256:705efca8d74d3facbb6ace80ab3afdd28eb8a237bfb4063ed89996b024bc443d"}, - {file = "frozendict-2.4.4-py312-none-any.whl", hash = "sha256:d9647563e76adb05b7cde2172403123380871360a114f546b4ae1704510801e5"}, - {file = "frozendict-2.4.4.tar.gz", hash = "sha256:3f7c031b26e4ee6a3f786ceb5e3abf1181c4ade92dce1f847da26ea2c96008c7"}, + {file = "frozendict-2.4.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3a05c0a50cab96b4bb0ea25aa752efbfceed5ccb24c007612bc63e51299336f"}, + {file = "frozendict-2.4.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5b94d5b07c00986f9e37a38dd83c13f5fe3bf3f1ccc8e88edea8fe15d6cd88c"}, + {file = "frozendict-2.4.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c789fd70879ccb6289a603cdebdc4953e7e5dea047d30c1b180529b28257b5"}, + {file = "frozendict-2.4.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da6a10164c8a50b34b9ab508a9420df38f4edf286b9ca7b7df8a91767baecb34"}, + {file = "frozendict-2.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9a8a43036754a941601635ea9c788ebd7a7efbed2becba01b54a887b41b175b9"}, + {file = "frozendict-2.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9905dcf7aa659e6a11b8051114c9fa76dfde3a6e50e6dc129d5aece75b449a2"}, + {file = "frozendict-2.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:323f1b674a2cc18f86ab81698e22aba8145d7a755e0ac2cccf142ee2db58620d"}, + {file = "frozendict-2.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:eabd21d8e5db0c58b60d26b4bb9839cac13132e88277e1376970172a85ee04b3"}, + {file = "frozendict-2.4.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eddabeb769fab1e122d3a6872982c78179b5bcc909fdc769f3cf1964f55a6d20"}, + {file = "frozendict-2.4.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:377a65be0a700188fc21e669c07de60f4f6d35fae8071c292b7df04776a1c27b"}, + {file = "frozendict-2.4.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce1e9217b85eec6ba9560d520d5089c82dbb15f977906eb345d81459723dd7e3"}, + {file = "frozendict-2.4.6-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:7291abacf51798d5ffe632771a69c14fb423ab98d63c4ccd1aa382619afe2f89"}, + {file = "frozendict-2.4.6-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:e72fb86e48811957d66ffb3e95580af7b1af1e6fbd760ad63d7bd79b2c9a07f8"}, + {file = "frozendict-2.4.6-cp36-cp36m-win_amd64.whl", hash = "sha256:622301b1c29c4f9bba633667d592a3a2b093cb408ba3ce578b8901ace3931ef3"}, + {file = "frozendict-2.4.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a4e3737cb99ed03200cd303bdcd5514c9f34b29ee48f405c1184141bd68611c9"}, + {file = "frozendict-2.4.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49ffaf09241bc1417daa19362a2241a4aa435f758fd4375c39ce9790443a39cd"}, + {file = "frozendict-2.4.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d69418479bfb834ba75b0e764f058af46ceee3d655deb6a0dd0c0c1a5e82f09"}, + {file = "frozendict-2.4.6-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c131f10c4d3906866454c4e89b87a7e0027d533cce8f4652aa5255112c4d6677"}, + {file = "frozendict-2.4.6-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:fc67cbb3c96af7a798fab53d52589752c1673027e516b702ab355510ddf6bdff"}, + {file = "frozendict-2.4.6-cp37-cp37m-win_amd64.whl", hash = "sha256:7730f8ebe791d147a1586cbf6a42629351d4597773317002181b66a2da0d509e"}, + {file = "frozendict-2.4.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:807862e14b0e9665042458fde692c4431d660c4219b9bb240817f5b918182222"}, + {file = "frozendict-2.4.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9647c74efe3d845faa666d4853cfeabbaee403b53270cabfc635b321f770e6b8"}, + {file = "frozendict-2.4.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:665fad3f0f815aa41294e561d98dbedba4b483b3968e7e8cab7d728d64b96e33"}, + {file = "frozendict-2.4.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f42e6b75254ea2afe428ad6d095b62f95a7ae6d4f8272f0bd44a25dddd20f67"}, + {file = "frozendict-2.4.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:02331541611f3897f260900a1815b63389654951126e6e65545e529b63c08361"}, + {file = "frozendict-2.4.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:18d50a2598350b89189da9150058191f55057581e40533e470db46c942373acf"}, + {file = "frozendict-2.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:1b4a3f8f6dd51bee74a50995c39b5a606b612847862203dd5483b9cd91b0d36a"}, + {file = "frozendict-2.4.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a76cee5c4be2a5d1ff063188232fffcce05dde6fd5edd6afe7b75b247526490e"}, + {file = "frozendict-2.4.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba5ef7328706db857a2bdb2c2a17b4cd37c32a19c017cff1bb7eeebc86b0f411"}, + {file = "frozendict-2.4.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669237c571856be575eca28a69e92a3d18f8490511eff184937283dc6093bd67"}, + {file = "frozendict-2.4.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0aaa11e7c472150efe65adbcd6c17ac0f586896096ab3963775e1c5c58ac0098"}, + {file = "frozendict-2.4.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b8f2829048f29fe115da4a60409be2130e69402e29029339663fac39c90e6e2b"}, + {file = "frozendict-2.4.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:94321e646cc39bebc66954a31edd1847d3a2a3483cf52ff051cd0996e7db07db"}, + {file = "frozendict-2.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:74b6b26c15dddfefddeb89813e455b00ebf78d0a3662b89506b4d55c6445a9f4"}, + {file = "frozendict-2.4.6-cp39-cp39-win_arm64.whl", hash = "sha256:7088102345d1606450bd1801a61139bbaa2cb0d805b9b692f8d81918ea835da6"}, + {file = "frozendict-2.4.6-py311-none-any.whl", hash = "sha256:d065db6a44db2e2375c23eac816f1a022feb2fa98cbb50df44a9e83700accbea"}, + {file = "frozendict-2.4.6-py312-none-any.whl", hash = "sha256:49344abe90fb75f0f9fdefe6d4ef6d4894e640fadab71f11009d52ad97f370b9"}, + {file = "frozendict-2.4.6-py313-none-any.whl", hash = "sha256:7134a2bb95d4a16556bb5f2b9736dceb6ea848fa5b6f3f6c2d6dba93b44b4757"}, + {file = "frozendict-2.4.6.tar.gz", hash = "sha256:df7cd16470fbd26fc4969a208efadc46319334eb97def1ddf48919b351192b8e"}, ] [[package]] name = "frozenlist" -version = "1.4.1" +version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, ] [[package]] @@ -2219,13 +2425,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] [[package]] name = "google-auth" -version = "2.35.0" +version = "2.37.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"}, - {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"}, + {file = "google_auth-2.37.0-py2.py3-none-any.whl", hash = "sha256:42664f18290a6be591be5329a96fe30184be1a1badb7292a7f686a9659de9ca0"}, + {file = "google_auth-2.37.0.tar.gz", hash = "sha256:0054623abf1f9c83492c63d3f47e77f0a544caa3d40b2d98e099a611c2dd5d00"}, ] [package.dependencies] @@ -2236,6 +2442,7 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] enterprise-cert = ["cryptography", "pyopenssl"] +pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"] pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] @@ -2275,13 +2482,13 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.65.0" +version = "1.66.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, - {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, + {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, + {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, ] [package.dependencies] @@ -2378,61 +2585,70 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.66.1" +version = "1.68.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:4877ba180591acdf127afe21ec1c7ff8a5ecf0fe2600f0d3c50e8c4a1cbc6492"}, - {file = "grpcio-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3750c5a00bd644c75f4507f77a804d0189d97a107eb1481945a0cf3af3e7a5ac"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a013c5fbb12bfb5f927444b477a26f1080755a931d5d362e6a9a720ca7dbae60"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1b24c23d51a1e8790b25514157d43f0a4dce1ac12b3f0b8e9f66a5e2c4c132f"}, - {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffb8ea674d68de4cac6f57d2498fef477cef582f1fa849e9f844863af50083"}, - {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:307b1d538140f19ccbd3aed7a93d8f71103c5d525f3c96f8616111614b14bf2a"}, - {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c17ebcec157cfb8dd445890a03e20caf6209a5bd4ac5b040ae9dbc59eef091d"}, - {file = "grpcio-1.66.1-cp310-cp310-win32.whl", hash = "sha256:ef82d361ed5849d34cf09105d00b94b6728d289d6b9235513cb2fcc79f7c432c"}, - {file = "grpcio-1.66.1-cp310-cp310-win_amd64.whl", hash = "sha256:292a846b92cdcd40ecca46e694997dd6b9be6c4c01a94a0dfb3fcb75d20da858"}, - {file = "grpcio-1.66.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:c30aeceeaff11cd5ddbc348f37c58bcb96da8d5aa93fed78ab329de5f37a0d7a"}, - {file = "grpcio-1.66.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a1e224ce6f740dbb6b24c58f885422deebd7eb724aff0671a847f8951857c26"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a66fe4dc35d2330c185cfbb42959f57ad36f257e0cc4557d11d9f0a3f14311df"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ba04659e4fce609de2658fe4dbf7d6ed21987a94460f5f92df7579fd5d0e22"}, - {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4573608e23f7e091acfbe3e84ac2045680b69751d8d67685ffa193a4429fedb1"}, - {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7e06aa1f764ec8265b19d8f00140b8c4b6ca179a6dc67aa9413867c47e1fb04e"}, - {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3885f037eb11f1cacc41f207b705f38a44b69478086f40608959bf5ad85826dd"}, - {file = "grpcio-1.66.1-cp311-cp311-win32.whl", hash = "sha256:97ae7edd3f3f91480e48ede5d3e7d431ad6005bfdbd65c1b56913799ec79e791"}, - {file = "grpcio-1.66.1-cp311-cp311-win_amd64.whl", hash = "sha256:cfd349de4158d797db2bd82d2020554a121674e98fbe6b15328456b3bf2495bb"}, - {file = "grpcio-1.66.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:a92c4f58c01c77205df6ff999faa008540475c39b835277fb8883b11cada127a"}, - {file = "grpcio-1.66.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fdb14bad0835914f325349ed34a51940bc2ad965142eb3090081593c6e347be9"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f03a5884c56256e08fd9e262e11b5cfacf1af96e2ce78dc095d2c41ccae2c80d"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2559692d8e7e245d456877a85ee41525f3ed425aa97eb7a70fc9a79df91a0"}, - {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca1be089fb4446490dd1135828bd42a7c7f8421e74fa581611f7afdf7ab761"}, - {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d639c939ad7c440c7b2819a28d559179a4508783f7e5b991166f8d7a34b52815"}, - {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9feb4e5ec8dc2d15709f4d5fc367794d69277f5d680baf1910fc9915c633524"}, - {file = "grpcio-1.66.1-cp312-cp312-win32.whl", hash = "sha256:7101db1bd4cd9b880294dec41a93fcdce465bdbb602cd8dc5bd2d6362b618759"}, - {file = "grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734"}, - {file = "grpcio-1.66.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:ecfe735e7a59e5a98208447293ff8580e9db1e890e232b8b292dc8bd15afc0d2"}, - {file = "grpcio-1.66.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4825a3aa5648010842e1c9d35a082187746aa0cdbf1b7a2a930595a94fb10fce"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f517fd7259fe823ef3bd21e508b653d5492e706e9f0ef82c16ce3347a8a5620c"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1fe60d0772831d96d263b53d83fb9a3d050a94b0e94b6d004a5ad111faa5b5b"}, - {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31a049daa428f928f21090403e5d18ea02670e3d5d172581670be006100db9ef"}, - {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f914386e52cbdeb5d2a7ce3bf1fdfacbe9d818dd81b6099a05b741aaf3848bb"}, - {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bff2096bdba686019fb32d2dde45b95981f0d1490e054400f70fc9a8af34b49d"}, - {file = "grpcio-1.66.1-cp38-cp38-win32.whl", hash = "sha256:aa8ba945c96e73de29d25331b26f3e416e0c0f621e984a3ebdb2d0d0b596a3b3"}, - {file = "grpcio-1.66.1-cp38-cp38-win_amd64.whl", hash = "sha256:161d5c535c2bdf61b95080e7f0f017a1dfcb812bf54093e71e5562b16225b4ce"}, - {file = "grpcio-1.66.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:d0cd7050397b3609ea51727b1811e663ffda8bda39c6a5bb69525ef12414b503"}, - {file = "grpcio-1.66.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0e6c9b42ded5d02b6b1fea3a25f036a2236eeb75d0579bfd43c0018c88bf0a3e"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c9f80f9fad93a8cf71c7f161778ba47fd730d13a343a46258065c4deb4b550c0"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dd67ed9da78e5121efc5c510f0122a972216808d6de70953a740560c572eb44"}, - {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48b0d92d45ce3be2084b92fb5bae2f64c208fea8ceed7fccf6a7b524d3c4942e"}, - {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d813316d1a752be6f5c4360c49f55b06d4fe212d7df03253dfdae90c8a402bb"}, - {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c9bebc6627873ec27a70fc800f6083a13c70b23a5564788754b9ee52c5aef6c"}, - {file = "grpcio-1.66.1-cp39-cp39-win32.whl", hash = "sha256:30a1c2cf9390c894c90bbc70147f2372130ad189cffef161f0432d0157973f45"}, - {file = "grpcio-1.66.1-cp39-cp39-win_amd64.whl", hash = "sha256:17663598aadbedc3cacd7bbde432f541c8e07d2496564e22b214b22c7523dac8"}, - {file = "grpcio-1.66.1.tar.gz", hash = "sha256:35334f9c9745add3e357e3372756fd32d925bd52c41da97f4dfdafbde0bf0ee2"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.66.1)"] + {file = "grpcio-1.68.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:d35740e3f45f60f3c37b1e6f2f4702c23867b9ce21c6410254c9c682237da68d"}, + {file = "grpcio-1.68.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d99abcd61760ebb34bdff37e5a3ba333c5cc09feda8c1ad42547bea0416ada78"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f8261fa2a5f679abeb2a0a93ad056d765cdca1c47745eda3f2d87f874ff4b8c9"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0feb02205a27caca128627bd1df4ee7212db051019a9afa76f4bb6a1a80ca95e"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:919d7f18f63bcad3a0f81146188e90274fde800a94e35d42ffe9eadf6a9a6330"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:963cc8d7d79b12c56008aabd8b457f400952dbea8997dd185f155e2f228db079"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ccf2ebd2de2d6661e2520dae293298a3803a98ebfc099275f113ce1f6c2a80f1"}, + {file = "grpcio-1.68.1-cp310-cp310-win32.whl", hash = "sha256:2cc1fd04af8399971bcd4f43bd98c22d01029ea2e56e69c34daf2bf8470e47f5"}, + {file = "grpcio-1.68.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2e743e51cb964b4975de572aa8fb95b633f496f9fcb5e257893df3be854746"}, + {file = "grpcio-1.68.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:55857c71641064f01ff0541a1776bfe04a59db5558e82897d35a7793e525774c"}, + {file = "grpcio-1.68.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4b177f5547f1b995826ef529d2eef89cca2f830dd8b2c99ffd5fde4da734ba73"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3522c77d7e6606d6665ec8d50e867f13f946a4e00c7df46768f1c85089eae515"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d1fae6bbf0816415b81db1e82fb3bf56f7857273c84dcbe68cbe046e58e1ccd"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298ee7f80e26f9483f0b6f94cc0a046caf54400a11b644713bb5b3d8eb387600"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb5780e2e740b6b4f2d208e90453591036ff80c02cc605fea1af8e6fc6b1bbe"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ddda1aa22495d8acd9dfbafff2866438d12faec4d024ebc2e656784d96328ad0"}, + {file = "grpcio-1.68.1-cp311-cp311-win32.whl", hash = "sha256:b33bd114fa5a83f03ec6b7b262ef9f5cac549d4126f1dc702078767b10c46ed9"}, + {file = "grpcio-1.68.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f20ebec257af55694d8f993e162ddf0d36bd82d4e57f74b31c67b3c6d63d8b2"}, + {file = "grpcio-1.68.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8829924fffb25386995a31998ccbbeaa7367223e647e0122043dfc485a87c666"}, + {file = "grpcio-1.68.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3aed6544e4d523cd6b3119b0916cef3d15ef2da51e088211e4d1eb91a6c7f4f1"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:4efac5481c696d5cb124ff1c119a78bddbfdd13fc499e3bc0ca81e95fc573684"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ab2d912ca39c51f46baf2a0d92aa265aa96b2443266fc50d234fa88bf877d8e"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c87ce2a97434dffe7327a4071839ab8e8bffd0054cc74cbe971fba98aedd60"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e4842e4872ae4ae0f5497bf60a0498fa778c192cc7a9e87877abd2814aca9475"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:255b1635b0ed81e9f91da4fcc8d43b7ea5520090b9a9ad9340d147066d1d3613"}, + {file = "grpcio-1.68.1-cp312-cp312-win32.whl", hash = "sha256:7dfc914cc31c906297b30463dde0b9be48e36939575eaf2a0a22a8096e69afe5"}, + {file = "grpcio-1.68.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0c8ddabef9c8f41617f213e527254c41e8b96ea9d387c632af878d05db9229c"}, + {file = "grpcio-1.68.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:a47faedc9ea2e7a3b6569795c040aae5895a19dde0c728a48d3c5d7995fda385"}, + {file = "grpcio-1.68.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:390eee4225a661c5cd133c09f5da1ee3c84498dc265fd292a6912b65c421c78c"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:66a24f3d45c33550703f0abb8b656515b0ab777970fa275693a2f6dc8e35f1c1"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c08079b4934b0bf0a8847f42c197b1d12cba6495a3d43febd7e99ecd1cdc8d54"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8720c25cd9ac25dd04ee02b69256d0ce35bf8a0f29e20577427355272230965a"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:04cfd68bf4f38f5bb959ee2361a7546916bd9a50f78617a346b3aeb2b42e2161"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c28848761a6520c5c6071d2904a18d339a796ebe6b800adc8b3f474c5ce3c3ad"}, + {file = "grpcio-1.68.1-cp313-cp313-win32.whl", hash = "sha256:77d65165fc35cff6e954e7fd4229e05ec76102d4406d4576528d3a3635fc6172"}, + {file = "grpcio-1.68.1-cp313-cp313-win_amd64.whl", hash = "sha256:a8040f85dcb9830d8bbb033ae66d272614cec6faceee88d37a88a9bd1a7a704e"}, + {file = "grpcio-1.68.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:eeb38ff04ab6e5756a2aef6ad8d94e89bb4a51ef96e20f45c44ba190fa0bcaad"}, + {file = "grpcio-1.68.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a3869a6661ec8f81d93f4597da50336718bde9eb13267a699ac7e0a1d6d0bea"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2c4cec6177bf325eb6faa6bd834d2ff6aa8bb3b29012cceb4937b86f8b74323c"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12941d533f3cd45d46f202e3667be8ebf6bcb3573629c7ec12c3e211d99cfccf"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80af6f1e69c5e68a2be529990684abdd31ed6622e988bf18850075c81bb1ad6e"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e8dbe3e00771bfe3d04feed8210fc6617006d06d9a2679b74605b9fed3e8362c"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:83bbf5807dc3ee94ce1de2dfe8a356e1d74101e4b9d7aa8c720cc4818a34aded"}, + {file = "grpcio-1.68.1-cp38-cp38-win32.whl", hash = "sha256:8cb620037a2fd9eeee97b4531880e439ebfcd6d7d78f2e7dcc3726428ab5ef63"}, + {file = "grpcio-1.68.1-cp38-cp38-win_amd64.whl", hash = "sha256:52fbf85aa71263380d330f4fce9f013c0798242e31ede05fcee7fbe40ccfc20d"}, + {file = "grpcio-1.68.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb400138e73969eb5e0535d1d06cae6a6f7a15f2cc74add320e2130b8179211a"}, + {file = "grpcio-1.68.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a1b988b40f2fd9de5c820f3a701a43339d8dcf2cb2f1ca137e2c02671cc83ac1"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:96f473cdacfdd506008a5d7579c9f6a7ff245a9ade92c3c0265eb76cc591914f"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37ea3be171f3cf3e7b7e412a98b77685eba9d4fd67421f4a34686a63a65d99f9"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ceb56c4285754e33bb3c2fa777d055e96e6932351a3082ce3559be47f8024f0"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dffd29a2961f3263a16d73945b57cd44a8fd0b235740cb14056f0612329b345e"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:025f790c056815b3bf53da850dd70ebb849fd755a4b1ac822cb65cd631e37d43"}, + {file = "grpcio-1.68.1-cp39-cp39-win32.whl", hash = "sha256:1098f03dedc3b9810810568060dea4ac0822b4062f537b0f53aa015269be0a76"}, + {file = "grpcio-1.68.1-cp39-cp39-win_amd64.whl", hash = "sha256:334ab917792904245a028f10e803fcd5b6f36a7b2173a820c0b5b076555825e1"}, + {file = "grpcio-1.68.1.tar.gz", hash = "sha256:44a8502dd5de653ae6a73e2de50a401d84184f0331d0ac3daeb044e66d5c5054"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.68.1)"] [[package]] name = "grpcio-status" @@ -2507,61 +2723,68 @@ files = [ [[package]] name = "httptools" -version = "0.6.1" +version = "0.6.4" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] + {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, + {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"}, + {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"}, + {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"}, + {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"}, + {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"}, + {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"}, + {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"}, + {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"}, +] + +[package.extras] +test = ["Cython (>=0.29.24)"] [[package]] name = "ibm-cloud-sdk-core" -version = "3.21.0" +version = "3.22.0" description = "Core library used by SDKs for IBM Cloud Services" optional = false python-versions = ">=3.8" files = [ - {file = "ibm_cloud_sdk_core-3.21.0-py3-none-any.whl", hash = "sha256:0d7c4ba6e22c21479479d78251451832d9aa1515dd4625893ab36087b071aaef"}, - {file = "ibm_cloud_sdk_core-3.21.0.tar.gz", hash = "sha256:1b72ef675ddffda25b08c43b84b8b6e54d2b5e21af1e1bd3e8507a2d2571a544"}, + {file = "ibm_cloud_sdk_core-3.22.0-py3-none-any.whl", hash = "sha256:a86e9006d98f32510d47d4751d8ba04b64514e3c94b851511fd55d7b094c87b1"}, + {file = "ibm_cloud_sdk_core-3.22.0.tar.gz", hash = "sha256:0aa5abc972b7d70ee4c39e3ad69c82c9717be847e5672928b416b8ea7d57a297"}, ] [package.dependencies] @@ -2576,17 +2799,17 @@ publish = ["build", "twine"] [[package]] name = "ibm-platform-services" -version = "0.57.1" +version = "0.59.0" description = "Python client library for IBM Cloud Platform Services" optional = false python-versions = ">=3.8" files = [ - {file = "ibm_platform_services-0.57.1-py3-none-any.whl", hash = "sha256:e005fd18a447481356f11ba523dd5e2d8b024e6b56d9a3125c6b19b76982f6ac"}, - {file = "ibm_platform_services-0.57.1.tar.gz", hash = "sha256:dc1c710faffc2ad0fd57c13b5d86bf8ef49890361a11d26ba483f7d406037b6f"}, + {file = "ibm_platform_services-0.59.0-py3-none-any.whl", hash = "sha256:2deba369f03843a0b628c03065b791f0738cd7a570b51836cb68ac7629ae545d"}, + {file = "ibm_platform_services-0.59.0.tar.gz", hash = "sha256:4d63bfa25d9bf995ea75ed449ce26ce4d21c8e2385e04f6da864bdc97c4e62a0"}, ] [package.dependencies] -ibm-cloud-sdk-core = ">=3.21.0,<4.0.0" +ibm-cloud-sdk-core = ">=3.22.0,<4.0.0" [package.extras] dev = ["black (>=24.0.0,<25.0.0)", "coverage (>=7.3.2,<8.0.0)", "pylint (>=3.0.0,<4.0.0)", "pytest (>=7.4.2,<8.0.0)", "pytest-cov (>=4.1.0,<5.0.0)", "responses (>=0.23.3,<1.0.0)"] @@ -2670,15 +2893,26 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "installer" +version = "0.7.0" +description = "A library for installing Python wheels." +optional = false +python-versions = ">=3.7" +files = [ + {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, + {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, +] + [[package]] name = "ipython" -version = "8.27.0" +version = "8.31.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.27.0-py3-none-any.whl", hash = "sha256:f68b3cb8bde357a5d7adc9598d57e22a45dfbea19eb6b98286fa3b288c9cd55c"}, - {file = "ipython-8.27.0.tar.gz", hash = "sha256:0b99a2dc9f15fd68692e898e5568725c6d49c527d36a9fb5960ffbdeaa82ff7e"}, + {file = "ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6"}, + {file = "ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b"}, ] [package.dependencies] @@ -2688,16 +2922,16 @@ exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt-toolkit = ">=3.0.41,<3.1.0" +prompt_toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" -stack-data = "*" +stack_data = "*" traitlets = ">=5.13.0" -typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] kernel = ["ipykernel"] matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] @@ -2733,6 +2967,24 @@ files = [ {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] +[[package]] +name = "jaraco-classes" +version = "3.4.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + [[package]] name = "jax" version = "0.4.28" @@ -2818,22 +3070,37 @@ scipy = ">=1.0.0" [[package]] name = "jedi" -version = "0.19.1" +version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] [package.dependencies] -parso = ">=0.8.3,<0.9.0" +parso = ">=0.8.4,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] [[package]] name = "jinja2" @@ -2939,6 +3206,29 @@ files = [ {file = "keras-2.14.0.tar.gz", hash = "sha256:22788bdbc86d9988794fe9703bb5205141da797c4faeeb59497c58c3d94d34ed"}, ] +[[package]] +name = "keyring" +version = "24.3.1" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, + {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab (>=1.1.0)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + [[package]] name = "kiwisolver" version = "1.4.7" @@ -3139,13 +3429,13 @@ files = [ [[package]] name = "mako" -version = "1.3.5" +version = "1.3.8" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" files = [ - {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, - {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, + {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, + {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, ] [package.dependencies] @@ -3425,6 +3715,17 @@ numpy = {version = ">=1.21.2", markers = "python_version > \"3.9\""} [package.extras] dev = ["absl-py", "pyink", "pylint (>=2.6.0)", "pytest", "pytest-xdist"] +[[package]] +name = "more-itertools" +version = "10.5.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, + {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -3843,22 +4144,15 @@ tests = ["cplex (>=22.1.0.0)", "nbconvert (>=6.5.1)", "pandas (>=1.4.3)", "plotl [[package]] name = "opt-einsum" -version = "3.3.0" -description = "Optimizing numpys einsum function" +version = "3.4.0" +description = "Path optimization of einsum functions." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "opt_einsum-3.3.0-py3-none-any.whl", hash = "sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147"}, - {file = "opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549"}, + {file = "opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd"}, + {file = "opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac"}, ] -[package.dependencies] -numpy = ">=1.7" - -[package.extras] -docs = ["numpydoc", "sphinx (==1.2.3)", "sphinx-rtd-theme", "sphinxcontrib-napoleon"] -tests = ["pytest", "pytest-cov", "pytest-pep8"] - [[package]] name = "optax" version = "0.2.3" @@ -3927,68 +4221,86 @@ six = ">=1.8.0" [[package]] name = "orjson" -version = "3.10.7" +version = "3.10.12" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, - {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, - {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, - {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, - {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, - {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, - {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, - {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, - {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, - {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, - {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, - {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, - {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, - {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, - {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, - {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, - {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, - {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, - {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, - {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, + {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, + {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, + {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, + {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, + {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, + {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, + {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, + {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, + {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, + {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, + {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, + {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, + {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, + {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, + {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, + {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, + {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, + {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, + {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, + {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, + {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, + {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, + {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, + {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, + {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, + {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, + {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, + {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, + {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, + {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, + {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, + {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, ] [[package]] @@ -4354,95 +4666,90 @@ ptyprocess = ">=0.5" [[package]] name = "pillow" -version = "10.4.0" +version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -4451,15 +4758,45 @@ xmp = ["defusedxml"] [[package]] name = "pip" -version = "24.2" +version = "24.3.1" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" files = [ - {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, - {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, + {file = "pip-24.3.1-py3-none-any.whl", hash = "sha256:3790624780082365f47549d032f3770eeb2b1e8bd1f7b2e02dace1afa361b4ed"}, + {file = "pip-24.3.1.tar.gz", hash = "sha256:ebcb60557f2aefabc2e0f918751cd24ea0d56d8ec5445fe1807f1d2109660b99"}, +] + +[[package]] +name = "pkginfo" +version = "1.12.0" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pkginfo-1.12.0-py3-none-any.whl", hash = "sha256:dcd589c9be4da8973eceffa247733c144812759aa67eaf4bbf97016a02f39088"}, + {file = "pkginfo-1.12.0.tar.gz", hash = "sha256:8ad91a0445a036782b9366ef8b8c2c50291f83a553478ba8580c73d3215700cf"}, ] +[package.extras] +testing = ["pytest", "pytest-cov", "wheel"] + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + [[package]] name = "plotly" version = "5.24.1" @@ -4512,29 +4849,182 @@ files = [ {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, ] +[[package]] +name = "poetry" +version = "1.8.5" +description = "Python dependency management and packaging made easy." +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "poetry-1.8.5-py3-none-any.whl", hash = "sha256:5505fba69bf2a792b5d7402d21839c853644337392b745109b86a23010cce5f3"}, + {file = "poetry-1.8.5.tar.gz", hash = "sha256:eb2c88d224f58f36df8f7b36d6c380c07d1001bca28bde620f68fc086e881b70"}, +] + +[package.dependencies] +build = ">=1.0.3,<2.0.0" +cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]} +cleo = ">=2.1.0,<3.0.0" +crashtest = ">=0.4.1,<0.5.0" +dulwich = ">=0.21.2,<0.22.0" +fastjsonschema = ">=2.18.0,<3.0.0" +installer = ">=0.7.0,<0.8.0" +keyring = ">=24.0.0,<25.0.0" +packaging = ">=23.1" +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.12,<2.0" +platformdirs = ">=3.0.0,<5" +poetry-core = "1.9.1" +poetry-plugin-export = ">=1.6.0,<2.0.0" +pyproject-hooks = ">=1.0.0,<2.0.0" +requests = ">=2.26,<3.0" +requests-toolbelt = ">=1.0.0,<2.0.0" +shellingham = ">=1.5,<2.0" +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.11.4,<1.0.0" +trove-classifiers = ">=2022.5.19" +virtualenv = ">=20.26.6,<21.0.0" +xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "poetry-core" +version = "1.9.1" +description = "Poetry PEP 517 Build Backend" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "poetry_core-1.9.1-py3-none-any.whl", hash = "sha256:6f45dd3598e0de8d9b0367360253d4c5d4d0110c8f5c71120a14f0e0f116c1a0"}, + {file = "poetry_core-1.9.1.tar.gz", hash = "sha256:7a2d49214bf58b4f17f99d6891d947a9836c9899a67a5069f52d7b67217f61b8"}, +] + +[[package]] +name = "poetry-plugin-export" +version = "1.8.0" +description = "Poetry plugin to export the dependencies to various formats" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "poetry_plugin_export-1.8.0-py3-none-any.whl", hash = "sha256:adbe232cfa0cc04991ea3680c865cf748bff27593b9abcb1f35fb50ed7ba2c22"}, + {file = "poetry_plugin_export-1.8.0.tar.gz", hash = "sha256:1fa6168a85d59395d835ca564bc19862a7c76061e60c3e7dfaec70d50937fc61"}, +] + +[package.dependencies] +poetry = ">=1.8.0,<3.0.0" +poetry-core = ">=1.7.0,<3.0.0" + [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] wcwidth = "*" +[[package]] +name = "propcache" +version = "0.2.1" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +files = [ + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, + {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, + {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, + {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, + {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, + {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, + {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, + {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, + {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, + {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, + {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, + {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, + {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, +] + [[package]] name = "proto-plus" -version = "1.24.0" +version = "1.25.0" description = "Beautiful, Pythonic protocol buffers." optional = false python-versions = ">=3.7" files = [ - {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, - {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, + {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, + {file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"}, ] [package.dependencies] @@ -4576,32 +5066,33 @@ files = [ [[package]] name = "psutil" -version = "6.0.0" +version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, - {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, - {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, - {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, - {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, - {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, - {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, - {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, - {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, - {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, + {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, + {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, + {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"}, + {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"}, + {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"}, + {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"}, + {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"}, + {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"}, + {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"}, + {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"}, + {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"}, + {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"}, + {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"}, + {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"}, + {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"}, + {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"}, + {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"}, ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "ptyprocess" @@ -4679,19 +5170,19 @@ files = [ [[package]] name = "pydantic" -version = "2.9.2" +version = "2.10.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, + {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -4699,100 +5190,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, ] [package.dependencies] @@ -4814,13 +5316,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.9.0" +version = "2.10.1" description = "JSON Web Token implementation in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, - {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, ] [package.extras] @@ -4869,6 +5371,17 @@ files = [ {file = "pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310"}, ] +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, + {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, +] + [[package]] name = "pyqrack" version = "1.32.12" @@ -4963,22 +5476,22 @@ files = [ [[package]] name = "pyscf" -version = "2.6.2" +version = "2.7.0" description = "PySCF: Python-based Simulations of Chemistry Framework" optional = false python-versions = "*" files = [ - {file = "pyscf-2.6.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:e1ac610e026066940c399fb3f56d03d8ccef2273a740e4dcb9f1ebdce17682de"}, - {file = "pyscf-2.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:670c17dfc886dca005dbe0f2e44a2812e3b1cb7afc2ed6b9e32da5fd677835da"}, - {file = "pyscf-2.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:693245bf12949f5dedb657de965bf7848145d93ade4d711f04eb5f13c184c5f7"}, - {file = "pyscf-2.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb86aec02b3dc78d263176f2e8498a2e1d4f73191c2d90e7954e0c3cf748576c"}, - {file = "pyscf-2.6.2.tar.gz", hash = "sha256:744c89a8e4d38c4b5562f75fa68f9d079faeb23602d255fba0eb6d1bac97bca2"}, + {file = "pyscf-2.7.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:ec4b4356a2e1d801b92757235925efdde1ff6ecf99f0ec1d6d9c1307b5468d6f"}, + {file = "pyscf-2.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e76aee018da70ccd393a8a5f851a1738098c7289f4394ea840b441aaa692b470"}, + {file = "pyscf-2.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:486638182902963f67224de5e35dae28223b7e1df852c6613ce581259489364f"}, + {file = "pyscf-2.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b83a6685dceac36173a75fcd3366cf528527c5c9236bfa86845b9ebcc490d5a9"}, + {file = "pyscf-2.7.0.tar.gz", hash = "sha256:ca8efc2f28d72c3130f26a967e7fa8d0bbc4a6b47d16a7c4c732ec85a31b7eec"}, ] [package.dependencies] h5py = ">=2.7" numpy = ">=1.13,<1.16 || >1.16,<1.17 || >1.17" -scipy = "<1.5.0 || >1.5.0,<1.5.1 || >1.5.1" +scipy = ">=1.6.0" setuptools = "*" [package.extras] @@ -5013,13 +5526,13 @@ files = [ [[package]] name = "pyspnego" -version = "0.11.1" +version = "0.11.2" description = "Windows Negotiate Authentication Client and Server" optional = false python-versions = ">=3.8" files = [ - {file = "pyspnego-0.11.1-py3-none-any.whl", hash = "sha256:129a4294f2c4d681d5875240ef87accc6f1d921e8983737fb0b59642b397951e"}, - {file = "pyspnego-0.11.1.tar.gz", hash = "sha256:e92ed8b0a62765b9d6abbb86a48cf871228ddb97678598dc01c9c39a626823f6"}, + {file = "pyspnego-0.11.2-py3-none-any.whl", hash = "sha256:74abc1fb51e59360eb5c5c9086e5962174f1072c7a50cf6da0bda9a4bcfdfbd4"}, + {file = "pyspnego-0.11.2.tar.gz", hash = "sha256:994388d308fb06e4498365ce78d222bf4f3570b6df4ec95738431f61510c971b"}, ] [package.dependencies] @@ -5032,13 +5545,13 @@ yaml = ["ruamel.yaml"] [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -5082,13 +5595,13 @@ cli = ["click (>=5.0)"] [[package]] name = "python-engineio" -version = "4.9.1" +version = "4.11.1" description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.6" files = [ - {file = "python_engineio-4.9.1-py3-none-any.whl", hash = "sha256:f995e702b21f6b9ebde4e2000cd2ad0112ba0e5116ec8d22fe3515e76ba9dddd"}, - {file = "python_engineio-4.9.1.tar.gz", hash = "sha256:7631cf5563086076611e494c643b3fa93dd3a854634b5488be0bba0ef9b99709"}, + {file = "python_engineio-4.11.1-py3-none-any.whl", hash = "sha256:8ff9ec366724cd9b0fd92acf7a61b15ae923d28f37f842304adbd7f71b3d6672"}, + {file = "python_engineio-4.11.1.tar.gz", hash = "sha256:ff8a23a843c223ec793835f1bcf584ff89ce0f1c2bcce37dffa6436c6fa74133"}, ] [package.dependencies] @@ -5183,18 +5696,18 @@ files = [ [[package]] name = "python-socketio" -version = "5.11.4" +version = "5.12.0" description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_socketio-5.11.4-py3-none-any.whl", hash = "sha256:42efaa3e3e0b166fc72a527488a13caaac2cefc76174252486503bd496284945"}, - {file = "python_socketio-5.11.4.tar.gz", hash = "sha256:8b0b8ff2964b2957c865835e936310190639c00310a47d77321a594d1665355e"}, + {file = "python_socketio-5.12.0-py3-none-any.whl", hash = "sha256:50fe22fd2b0aa634df3e74489e42217b09af2fb22eee45f2c006df36d1d08cb9"}, + {file = "python_socketio-5.12.0.tar.gz", hash = "sha256:39b55bff4ef6ac5c39b8bbc38fa61962e22e15349b038c1ca7ee2e18824e06dc"}, ] [package.dependencies] bidict = ">=0.21.0" -python-engineio = ">=4.8.0" +python-engineio = ">=4.11.0" [package.extras] asyncio-client = ["aiohttp (>=3.4)"] @@ -5212,6 +5725,17 @@ files = [ {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -5447,22 +5971,21 @@ quil = "0.10.0" [[package]] name = "qiskit" -version = "1.2.2" +version = "1.3.1" description = "An open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "qiskit-1.2.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d62c834b3da9c033d5c705ba0f479e70fdf9c78d1d6f058bf350f54bd305cf57"}, - {file = "qiskit-1.2.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ea12858fa3425809c939d3cedfef48fb2f2b5cfe47e2576a5d04ec35358ebcf"}, - {file = "qiskit-1.2.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:2942f4b47bbd251aafc1b006cdab823f1977d6219136901dd6084abb0205ed6c"}, - {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd8e8071b3311ba6debfc31bb103469f863b12c0080db82bd49f9b6ae93e82d1"}, - {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3944796a619c8c789e7c0ec9fad3c0ab2a0eb3990fee7da6da652ba83c7a5f8e"}, - {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d5b544f82dc34e822c51ab762038c9ba47c76d3c9a510f78389859fd800c88"}, - {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4c25135d4ebdff0ca084d52e20d1040c073c39632a073c7c9a3149871d73c0"}, - {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f37e6acc465874f070c499b67cfccac9eac671c12cc8d304251fd8ddaadba1a"}, - {file = "qiskit-1.2.2-cp38-abi3-win32.whl", hash = "sha256:a2317ef3bfbe49d71dd10077b3712001b8017453247bfe43ad0b6280f9a0fd6d"}, - {file = "qiskit-1.2.2-cp38-abi3-win_amd64.whl", hash = "sha256:9e54c253713ae06e1aadb3399ff39ccc4fda0826741595514f7b489a957e4f33"}, - {file = "qiskit-1.2.2.tar.gz", hash = "sha256:e16dd0f99de8caaec85a6620fccc9acf3ac2c9368f465e4d9e7ab445d3c7428a"}, + {file = "qiskit-1.3.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:78ce80685e187dff53a824ca27891c5860ee989f28e3da9ef6c356e40e39ef35"}, + {file = "qiskit-1.3.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c1406190aab851468873c185c4f19d00e842dad8d17bd674acd52d9d5185f162"}, + {file = "qiskit-1.3.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f33bcc5df7367bc68a69e890be8cddd16f2f15c3a60fa745a7437df9408493"}, + {file = "qiskit-1.3.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f121d5c4872f68d1e11b0a8a7cb6823112c126e774b296405e36d3202e47ba6"}, + {file = "qiskit-1.3.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fa3c46263a22b40bdbaace361104ac202f9af119f333a1fc45fce6f4c7bb066"}, + {file = "qiskit-1.3.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f8d1f5a6616fd95d05f838b47c9b744f635733f085574465da09090dfa819b6"}, + {file = "qiskit-1.3.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:644edc9b47c2f99d2654cca12e1a41bf51ce3dabfaddb0c1895b1ca32d790b35"}, + {file = "qiskit-1.3.1-cp39-abi3-win32.whl", hash = "sha256:7a20969ec5595df37f7944f186fcae7b8b0e91dcc6f6da313b4a99587276bf45"}, + {file = "qiskit-1.3.1-cp39-abi3-win_amd64.whl", hash = "sha256:8c26c7e1134b18764ec50049596b5abe572737c99e6e88fb65d4e01bd8e85806"}, + {file = "qiskit-1.3.1.tar.gz", hash = "sha256:7c9b34d700175db6615c9ab150b86f6f06ff7b74540e50bdf2ec7dc904639385"}, ] [package.dependencies] @@ -5472,7 +5995,7 @@ python-dateutil = ">=2.8.0" rustworkx = ">=0.15.0" scipy = ">=1.5" stevedore = ">=3.0.0" -symengine = ">=0.11" +symengine = ">=0.11,<0.14" sympy = ">=1.3" typing-extensions = "*" @@ -5731,6 +6254,106 @@ runtime-compilation = ["cython (>=0.29.20,<3.0.0)"] semidefinite = ["cvxopt", "cvxpy (>=1.0)"] tests = ["pytest (>=5.2)", "pytest-rerunfailures"] +[[package]] +name = "rapidfuzz" +version = "3.11.0" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.9" +files = [ + {file = "rapidfuzz-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e7f442fb9cca81e9df32333fb075ef729052bcabe05b0afc0441f462299114"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:906f1f2a1b91c06599b3dd1be207449c5d4fc7bd1e1fa2f6aef161ea6223f165"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed59044aea9eb6c663112170f2399b040d5d7b162828b141f2673e822093fa8"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cb1965a28b0fa64abdee130c788a0bc0bb3cf9ef7e3a70bf055c086c14a3d7e"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b488b244931d0291412917e6e46ee9f6a14376625e150056fe7c4426ef28225"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f0ba13557fec9d5ffc0a22826754a7457cc77f1b25145be10b7bb1d143ce84c6"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3871fa7dfcef00bad3c7e8ae8d8fd58089bad6fb21f608d2bf42832267ca9663"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b2669eafee38c5884a6e7cc9769d25c19428549dcdf57de8541cf9e82822e7db"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ffa1bb0e26297b0f22881b219ffc82a33a3c84ce6174a9d69406239b14575bd5"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:45b15b8a118856ac9caac6877f70f38b8a0d310475d50bc814698659eabc1cdb"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-win32.whl", hash = "sha256:22033677982b9c4c49676f215b794b0404073f8974f98739cb7234e4a9ade9ad"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:be15496e7244361ff0efcd86e52559bacda9cd975eccf19426a0025f9547c792"}, + {file = "rapidfuzz-3.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:714a7ba31ba46b64d30fccfe95f8013ea41a2e6237ba11a805a27cdd3bce2573"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-win32.whl", hash = "sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842"}, + {file = "rapidfuzz-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1b3ebc62d4bcdfdeba110944a25ab40916d5383c5e57e7c4a8dc0b6c17211a"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c6d7fea39cb33e71de86397d38bf7ff1a6273e40367f31d05761662ffda49e4"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99aebef8268f2bc0b445b5640fd3312e080bd17efd3fbae4486b20ac00466308"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4469307f464ae3089acf3210b8fc279110d26d10f79e576f385a98f4429f7d97"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb97c53112b593f89a90b4f6218635a9d1eea1d7f9521a3b7d24864228bbc0aa"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef8937dae823b889c0273dfa0f0f6c46a3658ac0d851349c464d1b00e7ff4252"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d95f9e9f3777b96241d8a00d6377cc9c716981d828b5091082d0fe3a2924b43e"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b1d67d67f89e4e013a5295e7523bc34a7a96f2dba5dd812c7c8cb65d113cbf28"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d994cf27e2f874069884d9bddf0864f9b90ad201fcc9cb2f5b82bacc17c8d5f2"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-win32.whl", hash = "sha256:ba26d87fe7fcb56c4a53b549a9e0e9143f6b0df56d35fe6ad800c902447acd5b"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f7efdd7b7adb32102c2fa481ad6f11923e2deb191f651274be559d56fc913b"}, + {file = "rapidfuzz-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:ed78c8e94f57b44292c1a0350f580e18d3a3c5c0800e253f1583580c1b417ad2"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e60814edd0c9b511b5f377d48b9782b88cfe8be07a98f99973669299c8bb318a"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f28952da055dbfe75828891cd3c9abf0984edc8640573c18b48c14c68ca5e06"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e8f93bc736020351a6f8e71666e1f486bb8bd5ce8112c443a30c77bfde0eb68"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76a4a11ba8f678c9e5876a7d465ab86def047a4fcc043617578368755d63a1bc"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc0e0d41ad8a056a9886bac91ff9d9978e54a244deb61c2972cc76b66752de9c"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8ea35f2419c7d56b3e75fbde2698766daedb374f20eea28ac9b1f668ef4f74"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd340bbd025302276b5aa221dccfe43040c7babfc32f107c36ad783f2ffd8775"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:494eef2c68305ab75139034ea25328a04a548d297712d9cf887bf27c158c388b"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a167344c1d6db06915fb0225592afdc24d8bafaaf02de07d4788ddd37f4bc2f"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c7af25bda96ac799378ac8aba54a8ece732835c7b74cfc201b688a87ed11152"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d2a0f7e17f33e7890257367a1662b05fecaf56625f7dbb6446227aaa2b86448b"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d0d26c7172bdb64f86ee0765c5b26ea1dc45c52389175888ec073b9b28f4305"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-win32.whl", hash = "sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b"}, + {file = "rapidfuzz-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1bac4873f6186f5233b0084b266bfb459e997f4c21fc9f029918f44a9eccd304"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f9f12c2d0aa52b86206d2059916153876a9b1cf9dfb3cf2f344913167f1c3d4"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd501de6f7a8f83557d20613b58734d1cb5f0be78d794cde64fe43cfc63f5f2"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4416ca69af933d4a8ad30910149d3db6d084781d5c5fdedb713205389f535385"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0821b9bdf18c5b7d51722b906b233a39b17f602501a966cfbd9b285f8ab83cd"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0edecc3f90c2653298d380f6ea73b536944b767520c2179ec5d40b9145e47aa"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4513dd01cee11e354c31b75f652d4d466c9440b6859f84e600bdebfccb17735a"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d9727b85511b912571a76ce53c7640ba2c44c364e71cef6d7359b5412739c570"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ab9eab33ee3213f7751dc07a1a61b8d9a3d748ca4458fffddd9defa6f0493c16"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6b01c1ddbb054283797967ddc5433d5c108d680e8fa2684cf368be05407b07e4"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3857e335f97058c4b46fa39ca831290b70de554a5c5af0323d2f163b19c5f2a6"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d98a46cf07c0c875d27e8a7ed50f304d83063e49b9ab63f21c19c154b4c0d08d"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-win32.whl", hash = "sha256:c36539ed2c0173b053dafb221458812e178cfa3224ade0960599bec194637048"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:ec8d7d8567e14af34a7911c98f5ac74a3d4a743cd848643341fc92b12b3784ff"}, + {file = "rapidfuzz-3.11.0-cp39-cp39-win_arm64.whl", hash = "sha256:62171b270ecc4071be1c1f99960317db261d4c8c83c169e7f8ad119211fe7397"}, + {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f06e3c4c0a8badfc4910b9fd15beb1ad8f3b8fafa8ea82c023e5e607b66a78e4"}, + {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fe7aaf5a54821d340d21412f7f6e6272a9b17a0cbafc1d68f77f2fc11009dcd5"}, + {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25398d9ac7294e99876a3027ffc52c6bebeb2d702b1895af6ae9c541ee676702"}, + {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a52eea839e4bdc72c5e60a444d26004da00bb5bc6301e99b3dde18212e41465"}, + {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c87319b0ab9d269ab84f6453601fd49b35d9e4a601bbaef43743f26fabf496c"}, + {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3048c6ed29d693fba7d2a7caf165f5e0bb2b9743a0989012a98a47b975355cca"}, + {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b04f29735bad9f06bb731c214f27253bd8bedb248ef9b8a1b4c5bde65b838454"}, + {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7864e80a0d4e23eb6194254a81ee1216abdc53f9dc85b7f4d56668eced022eb8"}, + {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3794df87313dfb56fafd679b962e0613c88a293fd9bd5dd5c2793d66bf06a101"}, + {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d71da0012face6f45432a11bc59af19e62fac5a41f8ce489e80c0add8153c3d1"}, + {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff38378346b7018f42cbc1f6d1d3778e36e16d8595f79a312b31e7c25c50bd08"}, + {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6668321f90aa02a5a789d4e16058f2e4f2692c5230252425c3532a8a62bc3424"}, + {file = "rapidfuzz-3.11.0.tar.gz", hash = "sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f"}, +] + +[package.extras] +all = ["numpy"] + [[package]] name = "requests" version = "2.32.3" @@ -5787,6 +6410,20 @@ requests = ">=2.0.0" [package.extras] rsa = ["oauthlib[signedtoken] (>=3.0.0)"] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + [[package]] name = "retworkx" version = "0.15.1" @@ -5899,61 +6536,79 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" -version = "0.2.8" +version = "0.2.12" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" files = [ - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, - {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, - {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, + {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, +] + +[[package]] +name = "ruff" +version = "0.8.4" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60"}, + {file = "ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac"}, + {file = "ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296"}, + {file = "ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643"}, + {file = "ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e"}, + {file = "ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3"}, + {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f"}, + {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604"}, + {file = "ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf"}, + {file = "ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720"}, + {file = "ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae"}, + {file = "ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7"}, + {file = "ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111"}, + {file = "ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8"}, + {file = "ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835"}, + {file = "ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d"}, + {file = "ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08"}, + {file = "ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8"}, ] [[package]] @@ -6090,6 +6745,21 @@ dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx (<6.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + [[package]] name = "semantic-version" version = "2.10.0" @@ -6107,39 +6777,51 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "setuptools" -version = "75.1.0" +version = "75.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, - {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, + {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, + {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] [[package]] name = "simple-websocket" -version = "1.0.0" +version = "1.1.0" description = "Simple WebSocket server and client for Python" optional = false python-versions = ">=3.6" files = [ - {file = "simple-websocket-1.0.0.tar.gz", hash = "sha256:17d2c72f4a2bd85174a97e3e4c88b01c40c3f81b7b648b0cc3ce1305968928c8"}, - {file = "simple_websocket-1.0.0-py3-none-any.whl", hash = "sha256:1d5bf585e415eaa2083e2bcf02a3ecf91f9712e7b3e6b9fa0b461ad04e0837bc"}, + {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, + {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, ] [package.dependencies] wsproto = "*" [package.extras] +dev = ["flake8", "pytest", "pytest-cov", "tox"] docs = ["sphinx"] [[package]] @@ -6263,13 +6945,13 @@ files = [ [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -6564,41 +7246,47 @@ url = ["furl (>=0.4.1)"] [[package]] name = "sspilib" -version = "0.1.0" +version = "0.2.0" description = "SSPI API bindings for Python" optional = false python-versions = ">=3.8" files = [ - {file = "sspilib-0.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e43f3e684e9d29c80324bd54f52dac65ac4b18d81a2dcd529dce3994369a14d"}, - {file = "sspilib-0.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eb34eda5d362b6603707a55751f1eff81775709b821e51cb64d1d2fa2bb8b6e"}, - {file = "sspilib-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ffe123f056f78cbe18aaed6b15f06e252020061c3387a72615abd46699a0b24"}, - {file = "sspilib-0.1.0-cp310-cp310-win32.whl", hash = "sha256:a4151072e28ec3b7d785beac9548a3d6a4549c431eb5487a5b8a1de028e9fef0"}, - {file = "sspilib-0.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2a19696c7b96b6bbef2b2ddf35df5a92f09b268476a348390a2f0da18cf29510"}, - {file = "sspilib-0.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:d2778e5e2881405b4d359a604e2802f5b7a7ed433ff62d6073d04c203af10eb1"}, - {file = "sspilib-0.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:09d7f72ad5e4bbf9a8f1acf0d5f0c3f9fbe500f44c4a45ac24a99ece84f5654f"}, - {file = "sspilib-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e5705e11aaa030a61d2b0a2ce09d2b8a1962dd950e55adc7a3c87dd463c6878"}, - {file = "sspilib-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dced8213d311c56f5f38044716ebff5412cc156f19678659e8ffa9bb6a642bd7"}, - {file = "sspilib-0.1.0-cp311-cp311-win32.whl", hash = "sha256:d30d38d52dbd857732224e86ae3627d003cc510451083c69fa481fc7de88a7b6"}, - {file = "sspilib-0.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:61c9067168cce962f7fead42c28804c3a39a164b9a7b660200b8cfe31e3af071"}, - {file = "sspilib-0.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:b526b8e5a236553f5137b951b89a2f108f56138ad05f31fd0a51b10f80b6c3cc"}, - {file = "sspilib-0.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3ff356d40cd34c900f94f1591eaabd458284042af611ebc1dbf609002066dba5"}, - {file = "sspilib-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b0fee3a52d0acef090f6c9b49953a8400fdc1c10aca7334319414a3038aa493"}, - {file = "sspilib-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab52d190dad1d578ec40d1fb417a8571954f4e32f35442a14cb709f57d3acbc9"}, - {file = "sspilib-0.1.0-cp312-cp312-win32.whl", hash = "sha256:b3cf819094383ec883e9a63c11b81d622618c815c18a6c9d761d9a14d9f028d1"}, - {file = "sspilib-0.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:b83825a2c43ff84ddff72d09b098057efaabf3841d3c42888078e154cf8e9595"}, - {file = "sspilib-0.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:9aa6ab4c3fc1057251cf1f3f199daf90b99599cdfafc9eade8fdf0c01526dec8"}, - {file = "sspilib-0.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82bff5df178386027d0112458b6971bbd18c76eb9e7be53fd61dab33d7bf8417"}, - {file = "sspilib-0.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:18393a9e6e0447cb7f319d361b65e9a0eaa5484705f16787133ffc49ad364c28"}, - {file = "sspilib-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a423fbca206ba0ca811dc995d8c3af045402b7d330f033e938b24f3a1d93fc"}, - {file = "sspilib-0.1.0-cp38-cp38-win32.whl", hash = "sha256:86bd936b1ef0aa63c6d9623ad08473e74ceb15f342f6e92cbade15ed9574cd33"}, - {file = "sspilib-0.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f688b94f0a64128444063e1d3d59152614175999222f6e2920681faea833f4"}, - {file = "sspilib-0.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2acef24e13e40d9dd8697eaae84ead9f417528ff741d087ec4eb4260518f4dc7"}, - {file = "sspilib-0.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b625802d80144d856d5eb6e8f4412f186565758da4493c7ad1b88e3d6d353de"}, - {file = "sspilib-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06ca1e34702bca1c750dcb5133b716f316b38dccb28d55a1a44d9842bc3f391"}, - {file = "sspilib-0.1.0-cp39-cp39-win32.whl", hash = "sha256:68496c9bd52b57a1b6d2e5529b43c30060249b8db901127b8343c4ad8cd93670"}, - {file = "sspilib-0.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:369727097f07a440099882580e284e137d9c27b7de354d63b65e327a454e7bee"}, - {file = "sspilib-0.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:87d8268c0517149c51a53b3888961ebf66826bb3dbb82c4e5cf10108f5456104"}, - {file = "sspilib-0.1.0.tar.gz", hash = "sha256:58b5291553cf6220549c0f855e0e6973f4977375d8236ce47bb581efb3e9b1cf"}, + {file = "sspilib-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34f566ba8b332c91594e21a71200de2d4ce55ca5a205541d4128ed23e3c98777"}, + {file = "sspilib-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b11e4f030de5c5de0f29bcf41a6e87c9fd90cb3b0f64e446a6e1d1aef4d08f5"}, + {file = "sspilib-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e82f87d77a9da62ce1eac22f752511a99495840177714c772a9d27b75220f78"}, + {file = "sspilib-0.2.0-cp310-cp310-win32.whl", hash = "sha256:e436fa09bcf353a364a74b3ef6910d936fa8cd1493f136e517a9a7e11b319c57"}, + {file = "sspilib-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:850a17c98d2b8579b183ce37a8df97d050bc5b31ab13f5a6d9e39c9692fe3754"}, + {file = "sspilib-0.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:a4d788a53b8db6d1caafba36887d5ac2087e6b6be6f01eb48f8afea6b646dbb5"}, + {file = "sspilib-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e0943204c8ba732966fdc5b69e33cf61d8dc6b24e6ed875f32055d9d7e2f76cd"}, + {file = "sspilib-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1cdfc5ec2f151f26e21aa50ccc7f9848c969d6f78264ae4f38347609f6722df"}, + {file = "sspilib-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6c33495a3de1552120c4a99219ebdd70e3849717867b8cae3a6a2f98fef405"}, + {file = "sspilib-0.2.0-cp311-cp311-win32.whl", hash = "sha256:400d5922c2c2261009921157c4b43d868e84640ad86e4dc84c95b07e5cc38ac6"}, + {file = "sspilib-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3e7d19c16ba9189ef8687b591503db06cfb9c5eb32ab1ca3bb9ebc1a8a5f35c"}, + {file = "sspilib-0.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:f65c52ead8ce95eb78a79306fe4269ee572ef3e4dcc108d250d5933da2455ecc"}, + {file = "sspilib-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:abac93a90335590b49ef1fc162b538576249c7f58aec0c7bcfb4b860513979b4"}, + {file = "sspilib-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1208720d8e431af674c5645cec365224d035f241444d5faa15dc74023ece1277"}, + {file = "sspilib-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48dceb871ecf9cf83abdd0e6db5326e885e574f1897f6ae87d736ff558f4bfa"}, + {file = "sspilib-0.2.0-cp312-cp312-win32.whl", hash = "sha256:bdf9a4f424add02951e1f01f47441d2e69a9910471e99c2c88660bd8e184d7f8"}, + {file = "sspilib-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:40a97ca83e503a175d1dc9461836994e47e8b9bcf56cab81a2c22e27f1993079"}, + {file = "sspilib-0.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8ffc09819a37005c66a580ff44f544775f9745d5ed1ceeb37df4e5ff128adf36"}, + {file = "sspilib-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:40ff410b64198cf1d704718754fc5fe7b9609e0c49bf85c970f64c6fc2786db4"}, + {file = "sspilib-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:02d8e0b6033de8ccf509ba44fdcda7e196cdedc0f8cf19eb22c5e4117187c82f"}, + {file = "sspilib-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7943fe14f8f6d72623ab6401991aa39a2b597bdb25e531741b37932402480f"}, + {file = "sspilib-0.2.0-cp313-cp313-win32.whl", hash = "sha256:b9044d6020aa88d512e7557694fe734a243801f9a6874e1c214451eebe493d92"}, + {file = "sspilib-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:c39a698491f43618efca8776a40fb7201d08c415c507f899f0df5ada15abefaa"}, + {file = "sspilib-0.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:863b7b214517b09367511c0ef931370f0386ed2c7c5613092bf9b106114c4a0e"}, + {file = "sspilib-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a0ede7afba32f2b681196c0b8520617d99dc5d0691d04884d59b476e31b41286"}, + {file = "sspilib-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bd95df50efb6586054963950c8fa91ef994fb73c5c022c6f85b16f702c5314da"}, + {file = "sspilib-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9460258d3dc3f71cc4dcfd6ac078e2fe26f272faea907384b7dd52cb91d9ddcc"}, + {file = "sspilib-0.2.0-cp38-cp38-win32.whl", hash = "sha256:6fa9d97671348b97567020d82fe36c4211a2cacf02abbccbd8995afbf3a40bfc"}, + {file = "sspilib-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:32422ad7406adece12d7c385019b34e3e35ff88a7c8f3d7c062da421772e7bfa"}, + {file = "sspilib-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6944a0d7fe64f88c9bde3498591acdb25b178902287919b962c398ed145f71b9"}, + {file = "sspilib-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0216344629b0f39c2193adb74d7e1bed67f1bbd619e426040674b7629407eba9"}, + {file = "sspilib-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5f84b9f614447fc451620c5c44001ed48fead3084c7c9f2b9cefe1f4c5c3d0"}, + {file = "sspilib-0.2.0-cp39-cp39-win32.whl", hash = "sha256:b290eb90bf8b8136b0a61b189629442052e1a664bd78db82928ec1e81b681fb5"}, + {file = "sspilib-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:404c16e698476e500a7fe67be5457fadd52d8bdc9aeb6c554782c8f366cc4fc9"}, + {file = "sspilib-0.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:8697e5dd9229cd3367bca49fba74e02f867759d1d416a717e26c3088041b9814"}, + {file = "sspilib-0.2.0.tar.gz", hash = "sha256:4d6cd4290ca82f40705efeb5e9107f7abcd5e647cb201a3d04371305938615b8"}, ] [[package]] @@ -6622,13 +7310,13 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "starlette" -version = "0.38.6" +version = "0.41.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05"}, - {file = "starlette-0.38.6.tar.gz", hash = "sha256:863a1588f5574e70a821dadefb41e4881ea451a47a3cd1b4df359d4ffefe5ead"}, + {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, + {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, ] [package.dependencies] @@ -6639,13 +7327,13 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "stevedore" -version = "5.3.0" +version = "5.4.0" description = "Manage dynamic plugins for Python applications" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, - {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, + {file = "stevedore-5.4.0-py3-none-any.whl", hash = "sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857"}, + {file = "stevedore-5.4.0.tar.gz", hash = "sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d"}, ] [package.dependencies] @@ -6696,46 +7384,53 @@ numpy = "*" [[package]] name = "symengine" -version = "0.11.0" +version = "0.13.0" description = "Python library providing wrappers to SymEngine" optional = false -python-versions = ">=3.8,<4" -files = [ - {file = "symengine-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a5ab2281fe5b3cbe8fbe4411c4e2e4ce6788e4d653b5d31b002d05f4af13b7c7"}, - {file = "symengine-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1f0c83e91c90a0344fdb9f1d71970f09d4fad5715110c9651d6ebc9ac995f97e"}, - {file = "symengine-0.11.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94c3e143bad732f858f5a8740bda640e24b491aa8cea30b2a68517a552060c84"}, - {file = "symengine-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8994e8e9b7273ce48e045d2a97e88b034bb99566bb3d0dd2a2ff79f2dccd4765"}, - {file = "symengine-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36c96584b5f852990e09904bb890623666cdc0bb83c5758f00939b540fcb2dc9"}, - {file = "symengine-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:fcfaf43781ae6e3e0b6b2f1da700e8ee6a0389426d3300925f4611323eb9b616"}, - {file = "symengine-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:292efdba67ce3465c99ff1e35338857d982583bbf505ae79f111f8f4fa670367"}, - {file = "symengine-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e935e0f5274d1ab456f3ba75a03dea1ee8a422dbd4d5d4f316653310a177962"}, - {file = "symengine-0.11.0-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f588199cb79b1c365005d52a3a8bc0967487dce5db77b864ee73735a5f4d2ec"}, - {file = "symengine-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb419847f24944fd29e834a342c6d05577dd66b34942f65131d417d70c2e7926"}, - {file = "symengine-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10fe4f99d95dc222fd7c37217f80f06f15b9e9b78b951de3c5dc0648cbd1572e"}, - {file = "symengine-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:468b42d2626495808b21cc3b73a162d43c757cc97e4549b1911ec40331d3d491"}, - {file = "symengine-0.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:07acd4ab7b147b913fdb62444ced626a9b66b9eefef5d3abfc4f65dccc411403"}, - {file = "symengine-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5fb1d62479c18cc00f025b53ba5f72a7bce92eabee9fec44256bb08d3c28d851"}, - {file = "symengine-0.11.0-cp312-cp312-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ccd28886f3eea78a1b9150b636ae40acf7d27ac79b4cc9161ec46f592e366a47"}, - {file = "symengine-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:517fec27be3741f89e1eea92b9e2f0cdca70fcd77d3fd5c4f8e4f13e6f04e8e4"}, - {file = "symengine-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54c5bcca900ec3bc6a20c749957310d52132a815bb3fca614fd66a7881626814"}, - {file = "symengine-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:6123f7e287cebeac9168e60eb6a9e9641f457617dbeee7b117fa7606fde84151"}, - {file = "symengine-0.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:116e1f644b3505608b55e745d71b80521b2e72042f421cb70c91dbe7c6710655"}, - {file = "symengine-0.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1c7913e264c95f44a52f181e4fff98afd9640651de149d0aee42d73b8b89860b"}, - {file = "symengine-0.11.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ce5e0c14e925fb3600d6955b07314e9981ed389240e437f5dd42f08f70a18af8"}, - {file = "symengine-0.11.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6323c43a703fefb0a50d42430e63a590c18b2955cfa8022cf7c0f6d0f32b86"}, - {file = "symengine-0.11.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d20163ad6c266e67fb9ad27970c7939bc3d5627487c8f0dc24cd26b9028e3f"}, - {file = "symengine-0.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:1f79feeeb1af286c29fb791537f243798229020a1ae6340b3d21eece3992783d"}, - {file = "symengine-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a78f905944ea9b9ef97e1afb355de4972fe84ed4cceadfc84b01366ce1f50d5b"}, - {file = "symengine-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33d2dfea163b77b41fa4c56ade5c6ac4a91693502bc282ba2c645591ef94775d"}, - {file = "symengine-0.11.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a4412da691328a8e6b0622067807ec61ea97b87dee8d94b1eea2fab2eab8d91"}, - {file = "symengine-0.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c42229ba2fbf70975ebebb0ca5114f4633ba4158246bd7ce14ba9cf4a19828f8"}, - {file = "symengine-0.11.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66def111281fed93da17afd0c787abca48f651078bd3f7987ee4bfb997e26a15"}, - {file = "symengine-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:76b3fceb99d6967d207fbaa93a9cd477949093a63bdb4652cb3c601de85d5229"}, - {file = "symengine-0.11.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7cde52f8c3e8eaa1bf7347a20e50445206cfe058815c61a2a7b86701b1690133"}, - {file = "symengine-0.11.0-pp39-pypy39_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:83da94f8487b54e391367d66cfdd268636811c4c660daa7b7e90ef9d7b659467"}, - {file = "symengine-0.11.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ae18741ee870ad897f4b1e45ad37587ab78c90a195083d87eb3db2ba66b12c"}, - {file = "symengine-0.11.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb8cc1314312af70b8a148611680f4cd91f8ef971c4fd4befa968cf7d6a2bd19"}, - {file = "symengine-0.11.0.tar.gz", hash = "sha256:0dd30d29b804ebb7251bddec29c38c3b1fc15ea6953a2c57ee758d5f6fcba458"}, +python-versions = "<4,>=3.8" +files = [ + {file = "symengine-0.13.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:259fd4111c7a70c72bdff5686de1949e8132baeb612eacdaf8837720c6fe449b"}, + {file = "symengine-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44f2eb28a1e36db0bbd6679435412f79da9743bf9c1cb3eff25e0c343b7ddd48"}, + {file = "symengine-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d141712fa14d9138bd19e64b10392f850c68d88cd7db29f1bda33e32d1095559"}, + {file = "symengine-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:830226d933bfcdb93546e4062541627d9a3bc7a178a63fb16c002eb5c5221938"}, + {file = "symengine-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08090163819a0bbfa97d64bd2d8dac2c5268147ed9c242799d7f7e8728a6f4e"}, + {file = "symengine-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1e435dcd8ed25e4c7c21ab1c0376be910efc7f35da76d532367df27b359f0358"}, + {file = "symengine-0.13.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:da0eba7e106095cdce88eb275c8a9d7c4586ad88f229394c53e1184155c00745"}, + {file = "symengine-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b0c175f4f895a73a925508af03faf7efd6cad8593256bbdb5346bd996d3ec5c8"}, + {file = "symengine-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e58d1e2abd08381aa0cf24c88c0e8b7f592df92619b51e32d36835fbd2dd6ae8"}, + {file = "symengine-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db745f2c7a3c5e83510cf4decb43201f43552dfb05ad8af9787c89708be9ede"}, + {file = "symengine-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2572c98b09ac284db6ecff63f6170461194dc94c4209afd34c092ec67873d85"}, + {file = "symengine-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:12727f02a2919f005aee48e68e0cbb70cf857b19385857b4d985d1c9b075f620"}, + {file = "symengine-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf91d24f1bfd6d53228593c7804dd106b71b19674d5afc4fa322d516e1793bdd"}, + {file = "symengine-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5615b7eb68890917abd390ebb10434a949165f6064741c1a8cc345fee14e855"}, + {file = "symengine-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb92bdf0890de264abaeacbfbdbd4dd7444b94057bd47958d913b662e549ad8a"}, + {file = "symengine-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3bce486fbc0b87970ed1b10ca9d5cafb1fd6b66382fe631261d83592851d7e"}, + {file = "symengine-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7e6bae9cfcdde2775d92fbb0abe3ef04e32f65ebc4c2d164ca33f4da202d4a7"}, + {file = "symengine-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:bced0a1dbdb94737c299384c85ddbad6944ce8dadc334f7bb8dbbd8f6c965807"}, + {file = "symengine-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d34df77971538e4c29f2d8e5ef7f459c2179465e6cdb7dfd48b79b87ecd8f4d"}, + {file = "symengine-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2661d9b18867e7c6edbfa7a74b8b0a2a694bd24aa08003dc3214f77cb9d6f2"}, + {file = "symengine-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53f27b9013878ee4419d8e853664d8ae4b68419e3f4b9b5b7f503d32bf904755"}, + {file = "symengine-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27987f75ce08c64f453e2b9b74fec6ffc5ca418c4deca0b75580979d4a4e242a"}, + {file = "symengine-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ea9410330ea15ed4137d7a0a3c43caccacb71490e18036ce5182d08c93baf8"}, + {file = "symengine-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:5031eb7a5c6675d5195bb57f93cc7d9ac5a7a9a826d4ad6f6b2927746ed7e6e6"}, + {file = "symengine-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ce0e5dfb19943bcf3e44a4485bcac4c5533ba3705c63083494eed0b3bf246076"}, + {file = "symengine-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c3b77dc54bf1181f6bd3b3338c4e6e5973a8b0fa20a189d15563ef5626e57b04"}, + {file = "symengine-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca7c3f6c168f6f5b06b421833c3d3baae56067a94b671bdffbe09b8e4fefd9be"}, + {file = "symengine-0.13.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:847523de682416811bacb3ad11507e663b3522fbb35cd27184757e9956d0eaf0"}, + {file = "symengine-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2fc1b7d96426463f0c9011e9fb88459d906477c1baa8a996dde6fb2bfa99d4"}, + {file = "symengine-0.13.0-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:6e371bb2da3867085779c1c21bbb1c85f9634c76c8a76c08562ea113e3dfcd85"}, + {file = "symengine-0.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf7c62478b19683d54e4d93faa5b89303beae25db0c503a105a70d266dc99fa9"}, + {file = "symengine-0.13.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdb21158cf3e2ba87e441f21ecc7724f108b8db17c0fd1880f9f531602bab1f3"}, + {file = "symengine-0.13.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1848d366b359ff69ef5dac148b30ca04c7339a7d3bcab28419d411e68c0cc011"}, + {file = "symengine-0.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:242f817e890a0a50d52ed6b2bfd1aad19636a58db700c7995bbe1ceeaebd9d08"}, + {file = "symengine-0.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:af79cf2b9645fb55216850185987b2e9347db71e42e87b6402e4bbd41710b316"}, + {file = "symengine-0.13.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:17ae9d1c781a60ac48d07fa30f39a2d237f9da95e9e81f6a24b1c16908e9cad2"}, + {file = "symengine-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5264157a95f6d09dd044cee6abcbc176e649c487638b7f32199f387f37ad82a5"}, + {file = "symengine-0.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01a6d0829b4881d5f831ae7848eb0d82b80d8b46b5689f1bf27069672370f75"}, + {file = "symengine-0.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35ec68b11c2df2be1a236d0c028edb5b331909b16666d7a9fe99a4a5810afec7"}, + {file = "symengine-0.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4357fed87083e8719fcffd8bd0e7ddd16172e319343362512f681e472ac5668"}, + {file = "symengine-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a41be31816e5e51e9063bf26de07faf3751de7a133dbbec149632de702a28e18"}, + {file = "symengine-0.13.0.tar.gz", hash = "sha256:ab83a08897ebf12579702c2b71ba73d4732fb706cc4291d810aedf39c690c14c"}, ] [[package]] @@ -6991,13 +7686,13 @@ numpy = ">=1.16.0" [[package]] name = "termcolor" -version = "2.4.0" +version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, - {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, ] [package.extras] @@ -7065,13 +7760,43 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -7087,13 +7812,13 @@ files = [ [[package]] name = "toolz" -version = "0.12.1" +version = "1.0.0" description = "List processing tools and functional utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, - {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, + {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, + {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, ] [[package]] @@ -7242,40 +7967,41 @@ reference = "pytorch-cpu" [[package]] name = "tornado" -version = "6.4.1" +version = "6.4.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">=3.8" files = [ - {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, - {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, - {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, - {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, - {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, + {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, + {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, + {file = "tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec"}, + {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946"}, + {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf"}, + {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634"}, + {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73"}, + {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c"}, + {file = "tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482"}, + {file = "tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38"}, + {file = "tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b"}, ] [[package]] name = "tqdm" -version = "4.66.5" +version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] @@ -7295,26 +8021,54 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] +[[package]] +name = "trove-classifiers" +version = "2024.10.21.16" +description = "Canonical source for classifiers on PyPI (pypi.org)." +optional = false +python-versions = "*" +files = [ + {file = "trove_classifiers-2024.10.21.16-py3-none-any.whl", hash = "sha256:0fb11f1e995a757807a8ef1c03829fbd4998d817319abcef1f33165750f103be"}, + {file = "trove_classifiers-2024.10.21.16.tar.gz", hash = "sha256:17cbd055d67d5e9d9de63293a8732943fabc21574e4c7b74edf112b4928cf5f3"}, +] + +[[package]] +name = "typer" +version = "0.15.1" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, + {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + [[package]] name = "types-deprecated" -version = "1.2.9.20240311" +version = "1.2.15.20241117" description = "Typing stubs for Deprecated" optional = false python-versions = ">=3.8" files = [ - {file = "types-Deprecated-1.2.9.20240311.tar.gz", hash = "sha256:0680e89989a8142707de8103f15d182445a533c1047fd9b7e8c5459101e9b90a"}, - {file = "types_Deprecated-1.2.9.20240311-py3-none-any.whl", hash = "sha256:d7793aaf32ff8f7e49a8ac781de4872248e0694c4b75a7a8a186c51167463f9d"}, + {file = "types-Deprecated-1.2.15.20241117.tar.gz", hash = "sha256:924002c8b7fddec51ba4949788a702411a2e3636cd9b2a33abd8ee119701d77e"}, + {file = "types_Deprecated-1.2.15.20241117-py3-none-any.whl", hash = "sha256:a0cc5e39f769fc54089fd8e005416b55d74aa03f6964d2ed1a0b0b2e28751884"}, ] [[package]] name = "types-python-dateutil" -version = "2.9.0.20240906" +version = "2.9.0.20241206" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.9.0.20240906.tar.gz", hash = "sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e"}, - {file = "types_python_dateutil-2.9.0.20240906-py3-none-any.whl", hash = "sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6"}, + {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, + {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, ] [[package]] @@ -7408,85 +8162,112 @@ standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", [[package]] name = "uvloop" -version = "0.20.0" +version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" files = [ - {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996"}, - {file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b"}, - {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10"}, - {file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae"}, - {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006"}, - {file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73"}, - {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037"}, - {file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9"}, - {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e"}, - {file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756"}, - {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0"}, - {file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf"}, - {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d"}, - {file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e"}, - {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9"}, - {file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab"}, - {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5"}, - {file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00"}, - {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba"}, - {file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf"}, - {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847"}, - {file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b"}, - {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6"}, - {file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2"}, - {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95"}, - {file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7"}, - {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a"}, - {file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541"}, - {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315"}, - {file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66"}, - {file = "uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469"}, + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"}, + {file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"}, +] + +[package.extras] +dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "virtualenv" +version = "20.28.0" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +files = [ + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, ] +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + [package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "watchdog" -version = "5.0.2" +version = "6.0.0" description = "Filesystem events monitoring" optional = false python-versions = ">=3.9" files = [ - {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d961f4123bb3c447d9fcdcb67e1530c366f10ab3a0c7d1c0c9943050936d4877"}, - {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72990192cb63872c47d5e5fefe230a401b87fd59d257ee577d61c9e5564c62e5"}, - {file = "watchdog-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bec703ad90b35a848e05e1b40bf0050da7ca28ead7ac4be724ae5ac2653a1a0"}, - {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae7a1879918f6544201d33666909b040a46421054a50e0f773e0d870ed7438d"}, - {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4a440f725f3b99133de610bfec93d570b13826f89616377715b9cd60424db6e"}, - {file = "watchdog-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b2918c19e0d48f5f20df458c84692e2a054f02d9df25e6c3c930063eca64c1"}, - {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa9cd6e24126d4afb3752a3e70fce39f92d0e1a58a236ddf6ee823ff7dba28ee"}, - {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f627c5bf5759fdd90195b0c0431f99cff4867d212a67b384442c51136a098ed7"}, - {file = "watchdog-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7594a6d32cda2b49df3fd9abf9b37c8d2f3eab5df45c24056b4a671ac661619"}, - {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba32efcccfe2c58f4d01115440d1672b4eb26cdd6fc5b5818f1fb41f7c3e1889"}, - {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:963f7c4c91e3f51c998eeff1b3fb24a52a8a34da4f956e470f4b068bb47b78ee"}, - {file = "watchdog-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c47150aa12f775e22efff1eee9f0f6beee542a7aa1a985c271b1997d340184f"}, - {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14dd4ed023d79d1f670aa659f449bcd2733c33a35c8ffd88689d9d243885198b"}, - {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84bff0391ad4abe25c2740c7aec0e3de316fdf7764007f41e248422a7760a7f"}, - {file = "watchdog-5.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e8d5ff39f0a9968952cce548e8e08f849141a4fcc1290b1c17c032ba697b9d7"}, - {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fb223456db6e5f7bd9bbd5cd969f05aae82ae21acc00643b60d81c770abd402b"}, - {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9814adb768c23727a27792c77812cf4e2fd9853cd280eafa2bcfa62a99e8bd6e"}, - {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:901ee48c23f70193d1a7bc2d9ee297df66081dd5f46f0ca011be4f70dec80dab"}, - {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:638bcca3d5b1885c6ec47be67bf712b00a9ab3d4b22ec0881f4889ad870bc7e8"}, - {file = "watchdog-5.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5597c051587f8757798216f2485e85eac583c3b343e9aa09127a3a6f82c65ee8"}, - {file = "watchdog-5.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:53ed1bf71fcb8475dd0ef4912ab139c294c87b903724b6f4a8bd98e026862e6d"}, - {file = "watchdog-5.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:29e4a2607bd407d9552c502d38b45a05ec26a8e40cc7e94db9bb48f861fa5abc"}, - {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:b6dc8f1d770a8280997e4beae7b9a75a33b268c59e033e72c8a10990097e5fde"}, - {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:d2ab34adc9bf1489452965cdb16a924e97d4452fcf88a50b21859068b50b5c3b"}, - {file = "watchdog-5.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:7d1aa7e4bb0f0c65a1a91ba37c10e19dabf7eaaa282c5787e51371f090748f4b"}, - {file = "watchdog-5.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:726eef8f8c634ac6584f86c9c53353a010d9f311f6c15a034f3800a7a891d941"}, - {file = "watchdog-5.0.2-py3-none-win32.whl", hash = "sha256:bda40c57115684d0216556671875e008279dea2dc00fcd3dde126ac8e0d7a2fb"}, - {file = "watchdog-5.0.2-py3-none-win_amd64.whl", hash = "sha256:d010be060c996db725fbce7e3ef14687cdcc76f4ca0e4339a68cc4532c382a73"}, - {file = "watchdog-5.0.2-py3-none-win_ia64.whl", hash = "sha256:3960136b2b619510569b90f0cd96408591d6c251a75c97690f4553ca88889769"}, - {file = "watchdog-5.0.2.tar.gz", hash = "sha256:dcebf7e475001d2cdeb020be630dc5b687e9acdd60d16fea6bb4508e7b94cf76"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ] [package.extras] @@ -7494,94 +8275,82 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "watchfiles" -version = "0.24.0" +version = "1.0.3" description = "Simple, modern and high performance file watching and code reload in python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0"}, - {file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e"}, - {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c"}, - {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188"}, - {file = "watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735"}, - {file = "watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04"}, - {file = "watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428"}, - {file = "watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823"}, - {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab"}, - {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec"}, - {file = "watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d"}, - {file = "watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c"}, - {file = "watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633"}, - {file = "watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a"}, - {file = "watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234"}, - {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef"}, - {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968"}, - {file = "watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444"}, - {file = "watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896"}, - {file = "watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418"}, - {file = "watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48"}, - {file = "watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f"}, - {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b"}, - {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18"}, - {file = "watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07"}, - {file = "watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366"}, - {file = "watchfiles-0.24.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ee82c98bed9d97cd2f53bdb035e619309a098ea53ce525833e26b93f673bc318"}, - {file = "watchfiles-0.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd92bbaa2ecdb7864b7600dcdb6f2f1db6e0346ed425fbd01085be04c63f0b05"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83df90191d67af5a831da3a33dd7628b02a95450e168785586ed51e6d28943c"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fca9433a45f18b7c779d2bae7beeec4f740d28b788b117a48368d95a3233ed83"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b995bfa6bf01a9e09b884077a6d37070464b529d8682d7691c2d3b540d357a0c"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9aba6e01ff6f2e8285e5aa4154e2970068fe0fc0998c4380d0e6278222269b"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5171ef898299c657685306d8e1478a45e9303ddcd8ac5fed5bd52ad4ae0b69b"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4933a508d2f78099162da473841c652ad0de892719043d3f07cc83b33dfd9d91"}, - {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95cf3b95ea665ab03f5a54765fa41abf0529dbaf372c3b83d91ad2cfa695779b"}, - {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01def80eb62bd5db99a798d5e1f5f940ca0a05986dcfae21d833af7a46f7ee22"}, - {file = "watchfiles-0.24.0-cp38-none-win32.whl", hash = "sha256:4d28cea3c976499475f5b7a2fec6b3a36208656963c1a856d328aeae056fc5c1"}, - {file = "watchfiles-0.24.0-cp38-none-win_amd64.whl", hash = "sha256:21ab23fdc1208086d99ad3f69c231ba265628014d4aed31d4e8746bd59e88cd1"}, - {file = "watchfiles-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b665caeeda58625c3946ad7308fbd88a086ee51ccb706307e5b1fa91556ac886"}, - {file = "watchfiles-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c51749f3e4e269231510da426ce4a44beb98db2dce9097225c338f815b05d4f"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b2509f08761f29a0fdad35f7e1638b8ab1adfa2666d41b794090361fb8b855"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a60e2bf9dc6afe7f743e7c9b149d1fdd6dbf35153c78fe3a14ae1a9aee3d98b"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7d9b87c4c55e3ea8881dfcbf6d61ea6775fffed1fedffaa60bd047d3c08c430"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78470906a6be5199524641f538bd2c56bb809cd4bf29a566a75051610bc982c3"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07cdef0c84c03375f4e24642ef8d8178e533596b229d32d2bbd69e5128ede02a"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d337193bbf3e45171c8025e291530fb7548a93c45253897cd764a6a71c937ed9"}, - {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec39698c45b11d9694a1b635a70946a5bad066b593af863460a8e600f0dff1ca"}, - {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e28d91ef48eab0afb939fa446d8ebe77e2f7593f5f463fd2bb2b14132f95b6e"}, - {file = "watchfiles-0.24.0-cp39-none-win32.whl", hash = "sha256:7138eff8baa883aeaa074359daabb8b6c1e73ffe69d5accdc907d62e50b1c0da"}, - {file = "watchfiles-0.24.0-cp39-none-win_amd64.whl", hash = "sha256:b3ef2c69c655db63deb96b3c3e587084612f9b1fa983df5e0c3379d41307467f"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:96619302d4374de5e2345b2b622dc481257a99431277662c30f606f3e22f42be"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:85d5f0c7771dcc7a26c7a27145059b6bb0ce06e4e751ed76cdf123d7039b60b5"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951088d12d339690a92cef2ec5d3cfd957692834c72ffd570ea76a6790222777"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fb58bcaa343fedc6a9e91f90195b20ccb3135447dc9e4e2570c3a39565853e"}, - {file = "watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1"}, + {file = "watchfiles-1.0.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1da46bb1eefb5a37a8fb6fd52ad5d14822d67c498d99bda8754222396164ae42"}, + {file = "watchfiles-1.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2b961b86cd3973f5822826017cad7f5a75795168cb645c3a6b30c349094e02e3"}, + {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34e87c7b3464d02af87f1059fedda5484e43b153ef519e4085fe1a03dd94801e"}, + {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9dd2b89a16cf7ab9c1170b5863e68de6bf83db51544875b25a5f05a7269e678"}, + {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b4691234d31686dca133c920f94e478b548a8e7c750f28dbbc2e4333e0d3da9"}, + {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90b0fe1fcea9bd6e3084b44875e179b4adcc4057a3b81402658d0eb58c98edf8"}, + {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b90651b4cf9e158d01faa0833b073e2e37719264bcee3eac49fc3c74e7d304b"}, + {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2e9fe695ff151b42ab06501820f40d01310fbd58ba24da8923ace79cf6d702d"}, + {file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62691f1c0894b001c7cde1195c03b7801aaa794a837bd6eef24da87d1542838d"}, + {file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:275c1b0e942d335fccb6014d79267d1b9fa45b5ac0639c297f1e856f2f532552"}, + {file = "watchfiles-1.0.3-cp310-cp310-win32.whl", hash = "sha256:06ce08549e49ba69ccc36fc5659a3d0ff4e3a07d542b895b8a9013fcab46c2dc"}, + {file = "watchfiles-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f280b02827adc9d87f764972fbeb701cf5611f80b619c20568e1982a277d6146"}, + {file = "watchfiles-1.0.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ffe709b1d0bc2e9921257569675674cafb3a5f8af689ab9f3f2b3f88775b960f"}, + {file = "watchfiles-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:418c5ce332f74939ff60691e5293e27c206c8164ce2b8ce0d9abf013003fb7fe"}, + {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f492d2907263d6d0d52f897a68647195bc093dafed14508a8d6817973586b6b"}, + {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48c9f3bc90c556a854f4cab6a79c16974099ccfa3e3e150673d82d47a4bc92c9"}, + {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75d3bcfa90454dba8df12adc86b13b6d85fda97d90e708efc036c2760cc6ba44"}, + {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5691340f259b8f76b45fb31b98e594d46c36d1dc8285efa7975f7f50230c9093"}, + {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e263cc718545b7f897baeac1f00299ab6fabe3e18caaacacb0edf6d5f35513c"}, + {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c6cf7709ed3e55704cc06f6e835bf43c03bc8e3cb8ff946bf69a2e0a78d9d77"}, + {file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:703aa5e50e465be901e0e0f9d5739add15e696d8c26c53bc6fc00eb65d7b9469"}, + {file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bfcae6aecd9e0cb425f5145afee871465b98b75862e038d42fe91fd753ddd780"}, + {file = "watchfiles-1.0.3-cp311-cp311-win32.whl", hash = "sha256:6a76494d2c5311584f22416c5a87c1e2cb954ff9b5f0988027bc4ef2a8a67181"}, + {file = "watchfiles-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:cf745cbfad6389c0e331786e5fe9ae3f06e9d9c2ce2432378e1267954793975c"}, + {file = "watchfiles-1.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:2dcc3f60c445f8ce14156854a072ceb36b83807ed803d37fdea2a50e898635d6"}, + {file = "watchfiles-1.0.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:93436ed550e429da007fbafb723e0769f25bae178fbb287a94cb4ccdf42d3af3"}, + {file = "watchfiles-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c18f3502ad0737813c7dad70e3e1cc966cc147fbaeef47a09463bbffe70b0a00"}, + {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5bc3ca468bb58a2ef50441f953e1f77b9a61bd1b8c347c8223403dc9b4ac9a"}, + {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d1ec043f02ca04bf21b1b32cab155ce90c651aaf5540db8eb8ad7f7e645cba8"}, + {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f58d3bfafecf3d81c15d99fc0ecf4319e80ac712c77cf0ce2661c8cf8bf84066"}, + {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1df924ba82ae9e77340101c28d56cbaff2c991bd6fe8444a545d24075abb0a87"}, + {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:632a52dcaee44792d0965c17bdfe5dc0edad5b86d6a29e53d6ad4bf92dc0ff49"}, + {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bf4b459d94a0387617a1b499f314aa04d8a64b7a0747d15d425b8c8b151da0"}, + {file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca94c85911601b097d53caeeec30201736ad69a93f30d15672b967558df02885"}, + {file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65ab1fb635476f6170b07e8e21db0424de94877e4b76b7feabfe11f9a5fc12b5"}, + {file = "watchfiles-1.0.3-cp312-cp312-win32.whl", hash = "sha256:49bc1bc26abf4f32e132652f4b3bfeec77d8f8f62f57652703ef127e85a3e38d"}, + {file = "watchfiles-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:48681c86f2cb08348631fed788a116c89c787fdf1e6381c5febafd782f6c3b44"}, + {file = "watchfiles-1.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:9e080cf917b35b20c889225a13f290f2716748362f6071b859b60b8847a6aa43"}, + {file = "watchfiles-1.0.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e153a690b7255c5ced17895394b4f109d5dcc2a4f35cb809374da50f0e5c456a"}, + {file = "watchfiles-1.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac1be85fe43b4bf9a251978ce5c3bb30e1ada9784290441f5423a28633a958a7"}, + {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec98e31e1844eac860e70d9247db9d75440fc8f5f679c37d01914568d18721"}, + {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0179252846be03fa97d4d5f8233d1c620ef004855f0717712ae1c558f1974a16"}, + {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:995c374e86fa82126c03c5b4630c4e312327ecfe27761accb25b5e1d7ab50ec8"}, + {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b9cb35b7f290db1c31fb2fdf8fc6d3730cfa4bca4b49761083307f441cac5a"}, + {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f8dc09ae69af50bead60783180f656ad96bd33ffbf6e7a6fce900f6d53b08f1"}, + {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:489b80812f52a8d8c7b0d10f0d956db0efed25df2821c7a934f6143f76938bd6"}, + {file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:228e2247de583475d4cebf6b9af5dc9918abb99d1ef5ee737155bb39fb33f3c0"}, + {file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1550be1a5cb3be08a3fb84636eaafa9b7119b70c71b0bed48726fd1d5aa9b868"}, + {file = "watchfiles-1.0.3-cp313-cp313-win32.whl", hash = "sha256:16db2d7e12f94818cbf16d4c8938e4d8aaecee23826344addfaaa671a1527b07"}, + {file = "watchfiles-1.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:160eff7d1267d7b025e983ca8460e8cc67b328284967cbe29c05f3c3163711a3"}, + {file = "watchfiles-1.0.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c05b021f7b5aa333124f2a64d56e4cb9963b6efdf44e8d819152237bbd93ba15"}, + {file = "watchfiles-1.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:310505ad305e30cb6c5f55945858cdbe0eb297fc57378f29bacceb534ac34199"}, + {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddff3f8b9fa24a60527c137c852d0d9a7da2a02cf2151650029fdc97c852c974"}, + {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46e86ed457c3486080a72bc837300dd200e18d08183f12b6ca63475ab64ed651"}, + {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f79fe7993e230a12172ce7d7c7db061f046f672f2b946431c81aff8f60b2758b"}, + {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea2b51c5f38bad812da2ec0cd7eec09d25f521a8b6b6843cbccedd9a1d8a5c15"}, + {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fe4e740ea94978b2b2ab308cbf9270a246bcbb44401f77cc8740348cbaeac3d"}, + {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af037d3df7188ae21dc1c7624501f2f90d81be6550904e07869d8d0e6766655"}, + {file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52bb50a4c4ca2a689fdba84ba8ecc6a4e6210f03b6af93181bb61c4ec3abaf86"}, + {file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c14a07bdb475eb696f85c715dbd0f037918ccbb5248290448488a0b4ef201aad"}, + {file = "watchfiles-1.0.3-cp39-cp39-win32.whl", hash = "sha256:be37f9b1f8934cd9e7eccfcb5612af9fb728fecbe16248b082b709a9d1b348bf"}, + {file = "watchfiles-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ef9ec8068cf23458dbf36a08e0c16f0a2df04b42a8827619646637be1769300a"}, + {file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:84fac88278f42d61c519a6c75fb5296fd56710b05bbdcc74bdf85db409a03780"}, + {file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c68be72b1666d93b266714f2d4092d78dc53bd11cf91ed5a3c16527587a52e29"}, + {file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889a37e2acf43c377b5124166bece139b4c731b61492ab22e64d371cce0e6e80"}, + {file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca05cacf2e5c4a97d02a2878a24020daca21dbb8823b023b978210a75c79098"}, + {file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8af4b582d5fc1b8465d1d2483e5e7b880cc1a4e99f6ff65c23d64d070867ac58"}, + {file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:127de3883bdb29dbd3b21f63126bb8fa6e773b74eaef46521025a9ce390e1073"}, + {file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713f67132346bdcb4c12df185c30cf04bdf4bf6ea3acbc3ace0912cab6b7cb8c"}, + {file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abd85de513eb83f5ec153a802348e7a5baa4588b818043848247e3e8986094e8"}, + {file = "watchfiles-1.0.3.tar.gz", hash = "sha256:f3ff7da165c99a5412fe5dd2304dd2dbaaaa5da718aad942dcb3a178eaa70c56"}, ] [package.dependencies] @@ -7600,19 +8369,15 @@ files = [ [[package]] name = "webcolors" -version = "24.8.0" +version = "24.11.1" description = "A library for working with the color formats defined by HTML and CSS." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"}, - {file = "webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d"}, + {file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"}, + {file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"}, ] -[package.extras] -docs = ["furo", "sphinx", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinxext-opengraph"] -tests = ["coverage[toml]"] - [[package]] name = "websocket-client" version = "1.8.0" @@ -7631,108 +8396,91 @@ test = ["websockets"] [[package]] name = "websockets" -version = "13.1" +version = "14.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, - {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, - {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, - {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, - {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, - {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, - {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, - {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, - {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, - {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, - {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, - {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, - {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, - {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, - {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, - {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, - {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, - {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, - {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, - {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, - {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, - {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, - {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, - {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, - {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, - {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"}, - {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"}, - {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"}, - {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"}, - {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"}, - {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"}, - {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"}, - {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"}, - {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"}, - {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"}, - {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"}, - {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"}, - {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"}, - {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, - {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, - {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, - {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, - {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"}, - {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"}, - {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"}, - {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"}, - {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"}, - {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"}, - {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"}, - {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"}, - {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, - {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, + {file = "websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29"}, + {file = "websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179"}, + {file = "websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0"}, + {file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078"}, + {file = "websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434"}, + {file = "websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10"}, + {file = "websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e"}, + {file = "websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512"}, + {file = "websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac"}, + {file = "websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3"}, + {file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89"}, + {file = "websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23"}, + {file = "websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e"}, + {file = "websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09"}, + {file = "websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed"}, + {file = "websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d"}, + {file = "websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45"}, + {file = "websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4"}, + {file = "websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05"}, + {file = "websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0"}, + {file = "websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f"}, + {file = "websockets-14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9"}, + {file = "websockets-14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b"}, + {file = "websockets-14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2"}, + {file = "websockets-14.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7"}, + {file = "websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a"}, + {file = "websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6"}, + {file = "websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0"}, + {file = "websockets-14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a"}, + {file = "websockets-14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6"}, + {file = "websockets-14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b"}, + {file = "websockets-14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a"}, + {file = "websockets-14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc"}, + {file = "websockets-14.1-cp39-cp39-win32.whl", hash = "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4"}, + {file = "websockets-14.1-cp39-cp39-win_amd64.whl", hash = "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979"}, + {file = "websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8"}, + {file = "websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb"}, + {file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7"}, + {file = "websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d"}, + {file = "websockets-14.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370"}, + {file = "websockets-14.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0"}, + {file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1"}, + {file = "websockets-14.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5"}, + {file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"}, + {file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"}, ] [[package]] name = "werkzeug" -version = "3.0.4" +version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, - {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, ] [package.dependencies] @@ -7743,13 +8491,13 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wheel" -version = "0.44.0" +version = "0.45.1" description = "A built-package format for Python" optional = false python-versions = ">=3.8" files = [ - {file = "wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f"}, - {file = "wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49"}, + {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, + {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, ] [package.extras] @@ -7869,110 +8617,174 @@ pillow = "*" sphinx = "*" sphinx-gallery = "*" +[[package]] +name = "xattr" +version = "1.1.0" +description = "Python wrapper for extended filesystem attributes" +optional = false +python-versions = ">=3.8" +files = [ + {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef2fa0f85458736178fd3dcfeb09c3cf423f0843313e25391db2cfd1acec8888"}, + {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccab735d0632fe71f7d72e72adf886f45c18b7787430467ce0070207882cfe25"}, + {file = "xattr-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9013f290387f1ac90bccbb1926555ca9aef75651271098d99217284d9e010f7c"}, + {file = "xattr-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcd5dfbcee73c7be057676ecb900cabb46c691aff4397bf48c579ffb30bb963"}, + {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6480589c1dac7785d1f851347a32c4a97305937bf7b488b857fe8b28a25de9e9"}, + {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08f61cbed52dc6f7c181455826a9ff1e375ad86f67dd9d5eb7663574abb32451"}, + {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:918e1f83f2e8a072da2671eac710871ee5af337e9bf8554b5ce7f20cdb113186"}, + {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0f06e0c1e4d06b4e0e49aaa1184b6f0e81c3758c2e8365597918054890763b53"}, + {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a641ac038a9f53d2f696716147ca4dbd6a01998dc9cd4bc628801bc0df7f4d"}, + {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e4ca0956fd11679bb2e0c0d6b9cdc0f25470cc00d8da173bb7656cc9a9cf104"}, + {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6881b120f9a4b36ccd8a28d933bc0f6e1de67218b6ce6e66874e0280fc006844"}, + {file = "xattr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab29d9288aa28e68a6f355ddfc3f0a7342b40c9012798829f3e7bd765e85c2c"}, + {file = "xattr-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c80bbf55339c93770fc294b4b6586b5bf8e85ec00a4c2d585c33dbd84b5006"}, + {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1418705f253b6b6a7224b69773842cac83fcbcd12870354b6e11dd1cd54630f"}, + {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687e7d18611ef8d84a6ecd8f4d1ab6757500c1302f4c2046ce0aa3585e13da3f"}, + {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6ceb9efe0657a982ccb8b8a2efe96b690891779584c901d2f920784e5d20ae3"}, + {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b489b7916f239100956ea0b39c504f3c3a00258ba65677e4c8ba1bd0b5513446"}, + {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0a9c431b0e66516a078125e9a273251d4b8e5ba84fe644b619f2725050d688a0"}, + {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1a5921ea3313cc1c57f2f53b63ea8ca9a91e48f4cc7ebec057d2447ec82c7efe"}, + {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6ad2a7bd5e6cf71d4a862413234a067cf158ca0ae94a40d4b87b98b62808498"}, + {file = "xattr-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0683dae7609f7280b0c89774d00b5957e6ffcb181c6019c46632b389706b77e6"}, + {file = "xattr-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cb15cd94e5ef8a0ef02309f1bf973ba0e13c11e87686e983f371948cfee6af"}, + {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff6223a854229055e803c2ad0c0ea9a6da50c6be30d92c198cf5f9f28819a921"}, + {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d44e8f955218638c9ab222eed21e9bd9ab430d296caf2176fb37abe69a714e5c"}, + {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:caab2c2986c30f92301f12e9c50415d324412e8e6a739a52a603c3e6a54b3610"}, + {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d6eb7d5f281014cd44e2d847a9107491af1bf3087f5afeded75ed3e37ec87239"}, + {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:47a3bdfe034b4fdb70e5941d97037405e3904accc28e10dbef6d1c9061fb6fd7"}, + {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00d2b415cf9d6a24112d019e721aa2a85652f7bbc9f3b9574b2d1cd8668eb491"}, + {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:78b377832dd0ee408f9f121a354082c6346960f7b6b1480483ed0618b1912120"}, + {file = "xattr-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6461a43b585e5f2e049b39bcbfcb6391bfef3c5118231f1b15d10bdb89ef17fe"}, + {file = "xattr-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24d97f0d28f63695e3344ffdabca9fcc30c33e5c8ccc198c7524361a98d526f2"}, + {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad47d89968c9097900607457a0c89160b4771601d813e769f68263755516065"}, + {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc53cab265f6e8449bd683d5ee3bc5a191e6dd940736f3de1a188e6da66b0653"}, + {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cd11e917f5b89f2a0ad639d9875943806c6c9309a3dd02da5a3e8ef92db7bed9"}, + {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9c5a78c7558989492c4cb7242e490ffb03482437bf782967dfff114e44242343"}, + {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cebcf8a303a44fbc439b68321408af7267507c0d8643229dbb107f6c132d389c"}, + {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b0d73150f2f9655b4da01c2369eb33a294b7f9d56eccb089819eafdbeb99f896"}, + {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:793c01deaadac50926c0e1481702133260c7cb5e62116762f6fe1543d07b826f"}, + {file = "xattr-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e189e440bcd04ccaad0474720abee6ee64890823ec0db361fb0a4fb5e843a1bf"}, + {file = "xattr-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afacebbc1fa519f41728f8746a92da891c7755e6745164bd0d5739face318e86"}, + {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b1664edf003153ac8d1911e83a0fc60db1b1b374ee8ac943f215f93754a1102"}, + {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda2684228798e937a7c29b0e1c7ef3d70e2b85390a69b42a1c61b2039ba81de"}, + {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b735ac2625a4fc2c9343b19f806793db6494336338537d2911c8ee4c390dda46"}, + {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fa6a7af7a4ada43f15ccc58b6f9adcdbff4c36ba040013d2681e589e07ae280a"}, + {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1059b2f726e2702c8bbf9bbf369acfc042202a4cc576c2dec6791234ad5e948"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2255f36ebf2cb2dbf772a7437ad870836b7396e60517211834cf66ce678b595"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba4f80b9855cc98513ddf22b7ad8551bc448c70d3147799ea4f6c0b758fb466"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb70c16e7c3ae6ba0ab6c6835c8448c61d8caf43ea63b813af1f4dbe83dd156"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83652910ef6a368b77b00825ad67815e5c92bfab551a848ca66e9981d14a7519"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a92aff66c43fa3e44cbeab7cbeee66266c91178a0f595e044bf3ce51485743b"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f71b673339aeaae1f6ea9ef8ea6c9643c8cd0df5003b9a0eaa75403e2e06c"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a20de1c47b5cd7b47da61799a3b34e11e5815d716299351f82a88627a43f9a96"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23705c7079b05761ff2fa778ad17396e7599c8759401abc05b312dfb3bc99f69"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27272afeba8422f2a9d27e1080a9a7b807394e88cce73db9ed8d2dde3afcfb87"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd43978966de3baf4aea367c99ffa102b289d6c2ea5f3d9ce34a203dc2f2ab73"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded771eaf27bb4eb3c64c0d09866460ee8801d81dc21097269cf495b3cac8657"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca300c0acca4f0cddd2332bb860ef58e1465d376364f0e72a1823fdd58e90d"}, + {file = "xattr-1.1.0.tar.gz", hash = "sha256:fecbf3b05043ed3487a28190dec3e4c4d879b2fcec0e30bafd8ec5d4b6043630"}, +] + +[package.dependencies] +cffi = ">=1.16.0" + +[package.extras] +test = ["pytest"] + [[package]] name = "yarl" -version = "1.12.1" +version = "1.18.3" description = "Yet another URL library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "yarl-1.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:64c5b0f2b937fe40d0967516eee5504b23cb247b8b7ffeba7213a467d9646fdc"}, - {file = "yarl-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e430ac432f969ef21770645743611c1618362309e3ad7cab45acd1ad1a540ff"}, - {file = "yarl-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e26e64f42bce5ddf9002092b2c37b13071c2e6413d5c05f9fa9de58ed2f7749"}, - {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0103c52f8dfe5d573c856322149ddcd6d28f51b4d4a3ee5c4b3c1b0a05c3d034"}, - {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b63465b53baeaf2122a337d4ab57d6bbdd09fcadceb17a974cfa8a0300ad9c67"}, - {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17d4dc4ff47893a06737b8788ed2ba2f5ac4e8bb40281c8603920f7d011d5bdd"}, - {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b54949267bd5704324397efe9fbb6aa306466dee067550964e994d309db5f1"}, - {file = "yarl-1.12.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10b690cd78cbaca2f96a7462f303fdd2b596d3978b49892e4b05a7567c591572"}, - {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c85ab016e96a975afbdb9d49ca90f3bca9920ef27c64300843fe91c3d59d8d20"}, - {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c1caa5763d1770216596e0a71b5567f27aac28c95992110212c108ec74589a48"}, - {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:595bbcdbfc4a9c6989d7489dca8510cba053ff46b16c84ffd95ac8e90711d419"}, - {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e64f0421892a207d3780903085c1b04efeb53b16803b23d947de5a7261b71355"}, - {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:319c206e83e46ec2421b25b300c8482b6fe8a018baca246be308c736d9dab267"}, - {file = "yarl-1.12.1-cp310-cp310-win32.whl", hash = "sha256:da045bd1147d12bd43fb032296640a7cc17a7f2eaba67495988362e99db24fd2"}, - {file = "yarl-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:aebbd47df77190ada603157f0b3670d578c110c31746ecc5875c394fdcc59a99"}, - {file = "yarl-1.12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:28389a68981676bf74e2e199fe42f35d1aa27a9c98e3a03e6f58d2d3d054afe1"}, - {file = "yarl-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f736f54565f8dd7e3ab664fef2bc461d7593a389a7f28d4904af8d55a91bd55f"}, - {file = "yarl-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dee0496d5f1a8f57f0f28a16f81a2033fc057a2cf9cd710742d11828f8c80e2"}, - {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8981a94a27ac520a398302afb74ae2c0be1c3d2d215c75c582186a006c9e7b0"}, - {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff54340fc1129e8e181827e2234af3ff659b4f17d9bbe77f43bc19e6577fadec"}, - {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54c8cee662b5f8c30ad7eedfc26123f845f007798e4ff1001d9528fe959fd23c"}, - {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e97a29b37830ba1262d8dfd48ddb5b28ad4d3ebecc5d93a9c7591d98641ec737"}, - {file = "yarl-1.12.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c89894cc6f6ddd993813e79244b36b215c14f65f9e4f1660b1f2ba9e5594b95"}, - {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:712ba8722c0699daf186de089ddc4677651eb9875ed7447b2ad50697522cbdd9"}, - {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6e9a9f50892153bad5046c2a6df153224aa6f0573a5a8ab44fc54a1e886f6e21"}, - {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1d4017e78fb22bc797c089b746230ad78ecd3cdb215bc0bd61cb72b5867da57e"}, - {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f494c01b28645c431239863cb17af8b8d15b93b0d697a0320d5dd34cd9d7c2fa"}, - {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:de4544b1fb29cf14870c4e2b8a897c0242449f5dcebd3e0366aa0aa3cf58a23a"}, - {file = "yarl-1.12.1-cp311-cp311-win32.whl", hash = "sha256:7564525a4673fde53dee7d4c307a961c0951918f0b8c7f09b2c9e02067cf6504"}, - {file = "yarl-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:f23bb1a7a6e8e8b612a164fdd08e683bcc16c76f928d6dbb7bdbee2374fbfee6"}, - {file = "yarl-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3e2aff8b822ab0e0bdbed9f50494b3a35629c4b9488ae391659973a37a9f53f"}, - {file = "yarl-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:22dda2799c8d39041d731e02bf7690f0ef34f1691d9ac9dfcb98dd1e94c8b058"}, - {file = "yarl-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18c2a7757561f05439c243f517dbbb174cadfae3a72dee4ae7c693f5b336570f"}, - {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:835010cc17d0020e7931d39e487d72c8e01c98e669b6896a8b8c9aa8ca69a949"}, - {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2254fe137c4a360b0a13173a56444f756252c9283ba4d267ca8e9081cd140ea"}, - {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a071d2c3d39b4104f94fc08ab349e9b19b951ad4b8e3b6d7ea92d6ef7ccaf8"}, - {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73a183042ae0918c82ce2df38c3db2409b0eeae88e3afdfc80fb67471a95b33b"}, - {file = "yarl-1.12.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326b8a079a9afcac0575971e56dabdf7abb2ea89a893e6949b77adfeb058b50e"}, - {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:126309c0f52a2219b3d1048aca00766429a1346596b186d51d9fa5d2070b7b13"}, - {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba1c779b45a399cc25f511c681016626f69e51e45b9d350d7581998722825af9"}, - {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:af1107299cef049ad00a93df4809517be432283a0847bcae48343ebe5ea340dc"}, - {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:20d817c0893191b2ab0ba30b45b77761e8dfec30a029b7c7063055ca71157f84"}, - {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d4f818f6371970d6a5d1e42878389bbfb69dcde631e4bbac5ec1cb11158565ca"}, - {file = "yarl-1.12.1-cp312-cp312-win32.whl", hash = "sha256:0ac33d22b2604b020569a82d5f8a03ba637ba42cc1adf31f616af70baf81710b"}, - {file = "yarl-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:fd24996e12e1ba7c397c44be75ca299da14cde34d74bc5508cce233676cc68d0"}, - {file = "yarl-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dea360778e0668a7ad25d7727d03364de8a45bfd5d808f81253516b9f2217765"}, - {file = "yarl-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1f50a37aeeb5179d293465e522fd686080928c4d89e0ff215e1f963405ec4def"}, - {file = "yarl-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0274b1b7a9c9c32b7bf250583e673ff99fb9fccb389215841e2652d9982de740"}, - {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4f3ab9eb8ab2d585ece959c48d234f7b39ac0ca1954a34d8b8e58a52064bdb3"}, - {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d31dd0245d88cf7239e96e8f2a99f815b06e458a5854150f8e6f0e61618d41b"}, - {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a96198d5d26f40557d986c1253bfe0e02d18c9d9b93cf389daf1a3c9f7c755fa"}, - {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddae504cfb556fe220efae65e35be63cd11e3c314b202723fc2119ce19f0ca2e"}, - {file = "yarl-1.12.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bce00f3b1f7f644faae89677ca68645ed5365f1c7f874fdd5ebf730a69640d38"}, - {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eee5ff934b0c9f4537ff9596169d56cab1890918004791a7a06b879b3ba2a7ef"}, - {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4ea99e64b2ad2635e0f0597b63f5ea6c374791ff2fa81cdd4bad8ed9f047f56f"}, - {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c667b383529520b8dd6bd496fc318678320cb2a6062fdfe6d3618da6b8790f6"}, - {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d920401941cb898ef089422e889759dd403309eb370d0e54f1bdf6ca07fef603"}, - {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:501a1576716032cc6d48c7c47bcdc42d682273415a8f2908e7e72cb4625801f3"}, - {file = "yarl-1.12.1-cp313-cp313-win32.whl", hash = "sha256:24416bb5e221e29ddf8aac5b97e94e635ca2c5be44a1617ad6fe32556df44294"}, - {file = "yarl-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:71af3766bb46738d12cc288d9b8de7ef6f79c31fd62757e2b8a505fe3680b27f"}, - {file = "yarl-1.12.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c924deab8105f86980983eced740433fb7554a7f66db73991affa4eda99d5402"}, - {file = "yarl-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5fb475a4cdde582c9528bb412b98f899680492daaba318231e96f1a0a1bb0d53"}, - {file = "yarl-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:36ee0115b9edca904153a66bb74a9ff1ce38caff015de94eadfb9ba8e6ecd317"}, - {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2631c9d7386bd2d4ce24ecc6ebf9ae90b3efd713d588d90504eaa77fec4dba01"}, - {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2376d8cf506dffd0e5f2391025ae8675b09711016656590cb03b55894161fcfa"}, - {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24197ba3114cc85ddd4091e19b2ddc62650f2e4a899e51b074dfd52d56cf8c72"}, - {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfdf419bf5d3644f94cd7052954fc233522f5a1b371fc0b00219ebd9c14d5798"}, - {file = "yarl-1.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8112f640a4f7e7bf59f7cabf0d47a29b8977528c521d73a64d5cc9e99e48a174"}, - {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:607d12f0901f6419a8adceb139847c42c83864b85371f58270e42753f9780fa6"}, - {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:664380c7ed524a280b6a2d5d9126389c3e96cd6e88986cdb42ca72baa27421d6"}, - {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:0d0a5e87bc48d76dfcfc16295201e9812d5f33d55b4a0b7cad1025b92bf8b91b"}, - {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:eff6bac402719c14e17efe845d6b98593c56c843aca6def72080fbede755fd1f"}, - {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:22839d1d1eab9e4b427828a88a22beb86f67c14d8ff81175505f1cc8493f3500"}, - {file = "yarl-1.12.1-cp38-cp38-win32.whl", hash = "sha256:717f185086bb9d817d4537dd18d5df5d657598cd00e6fc22e4d54d84de266c1d"}, - {file = "yarl-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:71978ba778948760cff528235c951ea0ef7a4f9c84ac5a49975f8540f76c3f73"}, - {file = "yarl-1.12.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ffc046ebddccb3c4cac72c1a3e1bc343492336f3ca86d24672e90ccc5e788a"}, - {file = "yarl-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f10954b233d4df5cc3137ffa5ced97f8894152df817e5d149bf05a0ef2ab8134"}, - {file = "yarl-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2e912b282466444023610e4498e3795c10e7cfd641744524876239fcf01d538d"}, - {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af871f70cfd5b528bd322c65793b5fd5659858cdfaa35fbe563fb99b667ed1f"}, - {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3e4e1f7b08d1ec6b685ccd3e2d762219c550164fbf524498532e39f9413436e"}, - {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a7ee79183f0b17dcede8b6723e7da2ded529cf159a878214be9a5d3098f5b1e"}, - {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c8ff1e1dd680e38af0887927cab407a4e51d84a5f02ae3d6eb87233036c763"}, - {file = "yarl-1.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e9905fc2dc1319e4c39837b906a024cf71b1261cc66b0cd89678f779c0c61f5"}, - {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:01549468858b87d36f967c97d02e6e54106f444aeb947ed76f8f71f85ed07cec"}, - {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:96b34830bd6825ca0220bf005ea99ac83eb9ce51301ddb882dcf613ae6cd95fb"}, - {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2aee7594d2c2221c717a8e394bbed4740029df4c0211ceb0f04815686e99c795"}, - {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:15871130439ad10abb25a4631120d60391aa762b85fcab971411e556247210a0"}, - {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:838dde2cb570cfbb4cab8a876a0974e8b90973ea40b3ac27a79b8a74c8a2db15"}, - {file = "yarl-1.12.1-cp39-cp39-win32.whl", hash = "sha256:eacbcf30efaca7dc5cb264228ffecdb95fdb1e715b1ec937c0ce6b734161e0c8"}, - {file = "yarl-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:76a59d1b63de859398bc7764c860a769499511463c1232155061fe0147f13e01"}, - {file = "yarl-1.12.1-py3-none-any.whl", hash = "sha256:dc3192a81ecd5ff954cecd690327badd5a84d00b877e1573f7c9097ce13e5bfb"}, - {file = "yarl-1.12.1.tar.gz", hash = "sha256:5b860055199aec8d6fe4dcee3c5196ce506ca198a50aab0059ffd26e8e815828"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +propcache = ">=0.2.0" [[package]] name = "zict" @@ -7987,13 +8799,13 @@ files = [ [[package]] name = "zipp" -version = "3.20.2" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] @@ -8103,4 +8915,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "18a7d6691c3e680b9b60a91d5401bf69ffaa028b1fd8a6aa9684149b6d7ab45b" +content-hash = "f76acba36046dee26811a0c5dded280cca0a46a0206ba10cac60745d0bd35d34" diff --git a/pyproject.toml b/pyproject.toml index 9bed5d9256..8118e7fdfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,9 @@ readme = "README.md" homepage = "https://pennylane.ai/qml/" repository = "https://github.com/PennyLaneAI/qml" keywords = ["demo", "qml", "tensorflow", "automatic-differentiation", "tutorials", "pytorch", "autograd", "quantum-computing", "neural-networks", "quantum-chemistry", "key-concepts", "quantum-machine-learning"] - +packages = [ + {include = "qml", from = "lib"} +] [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" @@ -18,8 +20,14 @@ name = "pytorch-cpu" url = "https://download.pytorch.org/whl/cpu" priority = "explicit" +[tool.poetry.scripts] +my_package_cli = 'qml.app:app' + [tool.poetry.dependencies] python = "~3.10.0" +typer = "^0.15.1" +poetry = ">1.7" +poetry-plugin-export = "^1.8.0" # Base dependencies needed to build the website without any code execution (*-norun) [tool.poetry.group.base.dependencies] @@ -31,6 +39,7 @@ numpy = "~1.24" pyyaml = "^6.0.1" pennylane-sphinx-theme = { git = "https://github.com/PennyLaneAI/pennylane-sphinx-theme.git", branch = "sphinx-update" } pypandoc = "1.5" +pennylane = "0.39.0" [tool.poetry.group.executable-dependencies.dependencies] ########################################################### @@ -38,7 +47,6 @@ pypandoc = "1.5" # These pinned versions of PL are NOT used for Dev builds # # The latest commit from GitHub is used instead # ########################################################### -pennylane = "0.39.0" pennylane-cirq = "0.39.0" pennylane-qiskit = "0.39.0" pennylane-qulacs = "0.39.0" @@ -107,3 +115,6 @@ optional = true jsonschema = { extras = ["format"], version = "~4.17.3" } check-jsonschema = "^0.23.3" +[tool.poetry.group.dev.dependencies] +ruff = "^0.8.4" + From b2e3edb2a0a074b7a6a65196ebd6fdfd9805c864 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 20 Dec 2024 13:55:26 -0500 Subject: [PATCH 03/29] fix pytorch constraint --- .../tutorial_givens_rotations/requirements.in | 3 + lib/qml/qml/app.py | 139 +++----------- lib/qml/qml/context.py | 6 - lib/qml/qml/lib/cmds.py | 64 +++++++ lib/qml/qml/lib/demo.py | 149 +++++++++++++- poetry.lock | 181 +++++++++++++++++- pyproject.toml | 9 +- 7 files changed, 430 insertions(+), 121 deletions(-) create mode 100644 lib/qml/qml/lib/cmds.py diff --git a/demonstrations_v2/tutorial_givens_rotations/requirements.in b/demonstrations_v2/tutorial_givens_rotations/requirements.in index 002324f217..a5f5319ab0 100644 --- a/demonstrations_v2/tutorial_givens_rotations/requirements.in +++ b/demonstrations_v2/tutorial_givens_rotations/requirements.in @@ -1,2 +1,5 @@ numpy pennylane +jax +jaxlib +matplotlib diff --git a/lib/qml/qml/app.py b/lib/qml/qml/app.py index c7b46e487e..2c895fbe32 100644 --- a/lib/qml/qml/app.py +++ b/lib/qml/qml/app.py @@ -1,127 +1,50 @@ import typer -from pathlib import Path -from qml.lib.demo import Demo, find_demos from qml.context import Context -from qml.lib.virtual_env import Virtualenv -from qml.lib.fs import copy_any +from qml.lib import demo import shutil -import os -import subprocess -import sys +import logging +from typing import Annotated -app = typer.Typer(name="qml", no_args_is_help=True) +logging.basicConfig(level=logging.INFO) -def install_build_dependencies(build_dir: Path, venv: Virtualenv): - build_requirements_file = build_dir / "requirements-build.txt" - subprocess.run( - [ - sys.executable, - "-m", - "poetry", - "export", - "--without-hashes", - "--format", - "requirements.txt", - "--only", - "base", - "--output", - str(build_requirements_file), - ] - ).check_returncode() - - print("Installing build dependencies...") - subprocess.run( - [venv.python, "-m", "pip", "install", "-r", str(build_requirements_file)] - ).check_returncode() - - -def install_dependencies(build_dir: Path, venv: Virtualenv, demos: list[Demo]): - constraints_file = (build_dir / "constraints.txt").resolve() - - print("Generating constraints...") - subprocess.run( - [ - sys.executable, - "-m", - "poetry", - "export", - "--without-hashes", - "--format", - "constraints.txt", - "--only", - "executable-dependencies", - "--output", - str(constraints_file), - ] - ).check_returncode() - - print("Generating requirements...") - requirements: set[str] = set() - for demo in demos: - if demo.executable: - requirements.update(demo.requirements()) - - print("Installing runtime dependencies...") - subprocess.run( - [ - venv.python, - "-m", - "pip", - "install", - "--constraint", - str(constraints_file), - *requirements, - ] - ).check_returncode() +app = typer.Typer(name="qml") @app.command() def help(): - print("Help!") + print("QML Demo build tool.") @app.command() -def build(demo_names: list[str], target: str = "html", execute: bool = False): +def build( + demo_names: Annotated[ + list[str], + typer.Argument( + help="Names of demos to build. If not provided, build all demos." + ), + ] = None, + target: Annotated[ + demo.BuildTarget, typer.Option(help="Format to build demos") + ] = "html", + execute: Annotated[ + bool, typer.Option(help="Whether to execute demos and generate output cells") + ] = False, +): + """Build the named demos.""" ctx = Context() + demo_names = demo_names or [] + demos = list(demo.find(ctx.demos_dir, *demo_names)) - demos = list(find_demos(ctx.demos_dir, *demo_names)) ctx.build_dir.mkdir(exist_ok=True) shutil.copytree( ctx.repo_root / "_static", ctx.build_dir / "_static", dirs_exist_ok=True ) - stage_dir = ctx.build_dir / "demonstrations" - if stage_dir.exists(): - shutil.rmtree(stage_dir) - - stage_dir.mkdir(parents=True) - # Need a 'GALLERY_HEADER' file for sphinx-gallery - with open(stage_dir / "GALLERY_HEADER.rst", "w"): - pass - - print(f"Building {len(demos)} demos") - for demo in demos: - # Use copy2 to perserve file modification time - shutil.copy2(demo.py_file, (stage_dir / demo.name).with_suffix(".py")) - - for resource in demo.resources: - copy_any(resource, (stage_dir / resource.name)) - - env = os.environ | {"DEMO_STAGING_DIR": str(stage_dir.resolve())} - build_venv = ctx.build_venv() - install_build_dependencies(ctx.build_dir, build_venv) - - cmd = [ - str(build_venv.path / "bin" / "sphinx-build"), - "-b", - target, - ] - if execute: - install_dependencies(ctx.build_dir, build_venv, demos) - else: - cmd.extend(("-D", "plot_gallery=0")) - - cmd.append(str(ctx.repo_root)) - cmd.append(str((ctx.build_dir / target).resolve())) - - subprocess.run(cmd, env=env).check_returncode() + demo.build( + sphinx_dir=ctx.repo_root, + build_dir=ctx.build_dir, + venv_path=ctx.build_venv_path, + demos=demos, + target=target, + execute=execute, + ) diff --git a/lib/qml/qml/context.py b/lib/qml/qml/context.py index 377810d6ee..18562042cd 100644 --- a/lib/qml/qml/context.py +++ b/lib/qml/qml/context.py @@ -1,7 +1,6 @@ from dulwich.repo import Repo from pathlib import Path import functools -from qml.lib.virtual_env import Virtualenv class Context: @@ -26,11 +25,6 @@ def build_dir(self) -> Path: def build_venv_path(self) -> Path: return self.repo_root / ".venv-build" - def build_venv(self) -> "Virtualenv": - venv = Virtualenv(self.build_venv_path) - - return venv - @functools.cached_property def cwd(self) -> Path: return Path.cwd().resolve() diff --git a/lib/qml/qml/lib/cmds.py b/lib/qml/qml/lib/cmds.py new file mode 100644 index 0000000000..bcaf62d6b6 --- /dev/null +++ b/lib/qml/qml/lib/cmds.py @@ -0,0 +1,64 @@ +import subprocess +from pathlib import Path +from collections.abc import Iterable +from typing import Literal + + +def poetry_export( + python: str | Path, + output: Path, + *, + format: Literal["requirements.txt", "constraints.txt"] = "requirements.txt", + groups: Iterable[str] = (), +) -> None: + """Executes `poetry export` with the given Python interpreter. + + Args: + output: Path to output file + format: Format, either 'constraints.txt' or 'requirements.txt' + groups: If provided, only include dependencies from the groups + listed. Otherwise, include only dependencies from the main + group. + + Raies: + CalledProcessError: The command does not complete successfully + """ + cmd = [ + str(python), + "-m", + "poetry", + "export", + "--without-hashes", + "--all-extras", + "--format", + format, + "--output", + str(output), + ] + for group in groups: + cmd.extend(("--only", group)) + + subprocess.run( + cmd, + ).check_returncode() + + +def pip_install( + python: str | Path, + *args: str | Path, + requirements: Path | None = None, + constraints: Path | None = None, + quiet: bool = True, +): + """Executes `pip install` with the given python + interpreter and args.""" + cmd = [str(python), "-m", "pip", "install"] + if requirements: + cmd.extend(("--requirement", str(requirements))) + if constraints: + cmd.extend(("--constraint", str(constraints))) + if quiet: + cmd.append("--quiet") + + cmd.extend(str(arg) for arg in args) + subprocess.run(cmd).check_returncode() diff --git a/lib/qml/qml/lib/demo.py b/lib/qml/qml/lib/demo.py index 269416d88f..7d505e62e8 100644 --- a/lib/qml/qml/lib/demo.py +++ b/lib/qml/qml/lib/demo.py @@ -1,27 +1,53 @@ from dataclasses import dataclass from pathlib import Path from collections.abc import Sequence, Iterator +import shutil +from qml.lib import fs, cmds +from qml.lib.virtual_env import Virtualenv +import os +import sys +from logging import getLogger +import subprocess +from enum import Enum +import re + +logger = getLogger("qml") + + +class BuildTarget(Enum): + """Sphinx-build targets.""" + + HTML = "html" + JSON = "json" @dataclass class Demo: + """Represents a demo and its metadata.""" + name: str path: Path @property def py_file(self) -> Path: + """The python file containing this demo's code and + markup.""" return self.path / "demo.py" @property def metadata_file(self) -> Path: + """Metadata for this demo.""" return self.path / "metadata.json" @property def requirements_file(self) -> Path: + """Path to a requirements file containing + unversioned dependencies for this demo.""" return self.path / "requirements.in" @property def resources(self) -> Sequence[Path]: + """Other files in the demo's directory.""" return tuple( p for p in self.path.iterdir() @@ -30,14 +56,18 @@ def resources(self) -> Sequence[Path]: @property def executable(self) -> bool: + """Whether this demo can be exeucted.""" return self.name.startswith("tutorial_") def requirements(self): + """Return a list of this demo's unversioned + requirements.""" with open(self.requirements_file, "r") as f: return f.read().splitlines() -def find_demos(search_dir: Path, *names: str) -> Iterator[Demo]: +def find(search_dir: Path, *names: str) -> Iterator[Demo]: + """Find demos with given names in `search_dir`.""" if not names: yield from ( Demo(name=demo_dir.name, path=demo_dir.resolve()) @@ -53,3 +83,120 @@ def find_demos(search_dir: Path, *names: str) -> Iterator[Demo]: raise ValueError(f"No demo exists with name '{name}") yield Demo(name=name, path=demo_dir.resolve()) + + +def build( + sphinx_dir: Path, + build_dir: Path, + venv_path: Path, + demos: Sequence[Demo], + target: BuildTarget, + execute: bool, +): + """Build the provided demos using 'sphinx-build', optionally + executing them to generate plots and cell outputs. + + Args: + sphinx_dir: The directory containing the sphinx conf.py file + build_dir: Build directory + venv_path: Path to virtual environment into which build and + dependencies will be installed + demos: List of demos to build + target: The target build format + execute: Whether to execute demos + """ + logger.info("Building %d demos", len(demos)) + + build_venv = Virtualenv(venv_path) + stage_dir = build_dir / "demonstrations" + if stage_dir.exists(): + shutil.rmtree(stage_dir) + + stage_dir.mkdir(parents=True) + # Need a 'GALLERY_HEADER' file for sphinx-gallery + with open(stage_dir / "GALLERY_HEADER.rst", "w"): + pass + + for demo in demos: + # Use copy2 to perserve file modification time + shutil.copy2(demo.py_file, (stage_dir / demo.name).with_suffix(".py")) + + for resource in demo.resources: + fs.copy_any(resource, (stage_dir / resource.name)) + + _install_build_dependencies(build_venv, build_dir) + if execute: + _install_execution_dependencies(build_venv, build_dir, demos) + + cmd = [ + str(build_venv.path / "bin" / "sphinx-build"), + "-b", + target.value, + ] + if not execute: + cmd.extend(("-D", "plot_gallery=0")) + + cmd.extend((str(sphinx_dir), str(build_dir / target.value))) + sphinx_env = os.environ | {"DEMO_STAGING_DIR": str(stage_dir.resolve())} + subprocess.run(cmd, env=sphinx_env).check_returncode() + + +def _install_build_dependencies(venv: Virtualenv, build_dir: Path): + """Install dependencies for running sphinx-build into `venv`.""" + logger.info("Installing sphinx-build dependencies") + + build_requirements_file = build_dir / "requirements-build.txt" + cmds.poetry_export( + sys.executable, + build_requirements_file, + groups=("base",), + format="requirements.txt", + ) + cmds.pip_install(venv.python, "-r", build_requirements_file) + + +def _install_execution_dependencies( + venv: Virtualenv, build_dir: Path, demos: Sequence[Demo] +): + """Install dependencies for executing provided demos into + `venv`.""" + constraints_file = (build_dir / "constraints.txt").resolve() + cmds.poetry_export( + sys.executable, + constraints_file, + format="constraints.txt", + groups=("executable-dependencies",), + ) + if sys.platform == "darwin": + _fix_pytorch_constraint_macos(constraints_file) + + requirements: set[str] = set() + for demo in demos: + if demo.executable: + requirements.update(demo.requirements()) + + logger.info("Installing execution dependencies") + cmds.pip_install( + venv.python, + *requirements, + constraints=constraints_file, + ) + + +def _fix_pytorch_constraint_macos(constraints_file: Path): + """`poetry export` keeps the '+cpu' extra on torch and torchvision when exporting + on MacOS, which uses the PyPi wheels. Because the wheels have no '+cpu' extra on + PyPi, package resolution will fail. + + This function strips the +cpu extra from the constraint. + """ + with open(constraints_file, "r") as f: + constraints = f.read() + + for pattern in (r"(torch==[0-9\.]+)\+cpu", r"(torchvision==[0-9\.]+)\+cpu"): + constraints = re.sub( + pattern, lambda m: m.group(1), constraints, flags=re.MULTILINE + ) + + with open(constraints_file, "w") as f: + f.write(constraints) diff --git a/poetry.lock b/poetry.lock index 0396ab1dab..95daf8cc17 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4055,6 +4055,148 @@ files = [ {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, ] +[[package]] +name = "nvidia-cublas-cu12" +version = "12.1.3.1" +description = "CUBLAS native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728"}, + {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-win_amd64.whl", hash = "sha256:2b964d60e8cf11b5e1073d179d85fa340c120e99b3067558f3cf98dd69d02906"}, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.1.105" +description = "CUDA profiling tools runtime libs." +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e"}, + {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:bea8236d13a0ac7190bd2919c3e8e6ce1e402104276e6f9694479e48bb0eb2a4"}, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.1.105" +description = "NVRTC native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2"}, + {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:0a98a522d9ff138b96c010a65e145dc1b4850e9ecb75a0172371793752fd46ed"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.1.105" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40"}, + {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:dfb46ef84d73fababab44cf03e3b83f80700d27ca300e537f85f636fac474344"}, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "8.9.2.26" +description = "cuDNN runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.0.2.54" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56"}, + {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-win_amd64.whl", hash = "sha256:d9ac353f78ff89951da4af698f80870b1534ed69993f10a4cf1d96f21357e253"}, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.2.106" +description = "CURAND native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0"}, + {file = "nvidia_curand_cu12-10.3.2.106-py3-none-win_amd64.whl", hash = "sha256:75b6b0c574c0037839121317e17fd01f8a69fd2ef8e25853d826fec30bdba74a"}, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.4.5.107" +description = "CUDA solver native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd"}, + {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-win_amd64.whl", hash = "sha256:74e0c3a24c78612192a74fcd90dd117f1cf21dea4822e66d89e8ea80e3cd2da5"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" +nvidia-cusparse-cu12 = "*" +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.1.0.106" +description = "CUSPARSE native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c"}, + {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-win_amd64.whl", hash = "sha256:b798237e81b9719373e8fae8d4f091b70a0cf09d9d85c95a557e11df2d8e9a5a"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.18.1" +description = "NVIDIA Collective Communication Library (NCCL) Runtime" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nccl_cu12-2.18.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:1a6c4acefcbebfa6de320f412bf7866de856e786e0462326ba1bac40de0b5e71"}, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.85" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a"}, + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cf4eaa7d4b6b543ffd69d6abfb11efdeb2db48270d94dfd3a452c24150829e41"}, + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-win_amd64.whl", hash = "sha256:e61120e52ed675747825cdd16febc6a0730537451d867ee58bee3853b1b13d1c"}, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.1.105" +description = "NVIDIA Tools Extension" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5"}, + {file = "nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82"}, +] + [[package]] name = "oauthlib" version = "3.2.2" @@ -7855,7 +7997,19 @@ filelock = "*" fsspec = "*" jinja2 = "*" networkx = "*" +nvidia-cublas-cu12 = {version = "12.1.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "8.9.2.26", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.0.2.54", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.2.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.4.5.107", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.1.0.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.18.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} sympy = "*" +triton = {version = "2.1.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} typing-extensions = "*" [package.extras] @@ -8021,6 +8175,31 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] +[[package]] +name = "triton" +version = "2.1.0" +description = "A language and compiler for custom Deep Learning operations" +optional = false +python-versions = "*" +files = [ + {file = "triton-2.1.0-0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:66439923a30d5d48399b08a9eae10370f6c261a5ec864a64983bae63152d39d7"}, + {file = "triton-2.1.0-0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:919b06453f0033ea52c13eaf7833de0e57db3178d23d4e04f9fc71c4f2c32bf8"}, + {file = "triton-2.1.0-0-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae4bb8a91de790e1866405211c4d618379781188f40d5c4c399766914e84cd94"}, + {file = "triton-2.1.0-0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39f6fb6bdccb3e98f3152e3fbea724f1aeae7d749412bbb1fa9c441d474eba26"}, + {file = "triton-2.1.0-0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21544e522c02005a626c8ad63d39bdff2f31d41069592919ef281e964ed26446"}, + {file = "triton-2.1.0-0-pp37-pypy37_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:143582ca31dd89cd982bd3bf53666bab1c7527d41e185f9e3d8a3051ce1b663b"}, + {file = "triton-2.1.0-0-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82fc5aeeedf6e36be4e4530cbdcba81a09d65c18e02f52dc298696d45721f3bd"}, + {file = "triton-2.1.0-0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:81a96d110a738ff63339fc892ded095b31bd0d205e3aace262af8400d40b6fa8"}, +] + +[package.dependencies] +filelock = "*" + +[package.extras] +build = ["cmake (>=3.18)", "lit"] +tests = ["autopep8", "flake8", "isort", "numpy", "pytest", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + [[package]] name = "trove-classifiers" version = "2024.10.21.16" @@ -8915,4 +9094,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "f76acba36046dee26811a0c5dded280cca0a46a0206ba10cac60745d0bd35d34" +content-hash = "abf7cd9ec8d973416a3721003ed39f44d9c6d58a384597905f4fa25a230fed58" diff --git a/pyproject.toml b/pyproject.toml index 8118e7fdfe..b7165179be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,17 +86,16 @@ h5py = "3.11.0" qiskit = ">=1.0.0" qiskit-aer = ">=0.14.0" sphinxcontrib-applehelp = "1.0.8" -sphinx-gallery = "0.17.1" # Install a difference version of torch from PyPI as the one from PyTorch repo is not compatible with MacOS torch = [ - { version = "2.1.2", source = "PyPI", markers = "sys_platform == 'darwin' and platform_machine == 'arm64'" }, - { version = "2.1.2+cpu", source = "pytorch-cpu", markers = "platform_machine == 'x86_64'" } + { version = "2.1.2", source = "PyPI", markers = "sys_platform == 'darwin'" }, + { version = "2.1.2+cpu", source = "pytorch-cpu", markers = "sys_platform != 'darwin'" } ] torchvision = [ - { version = "0.16.2", source = "PyPI", markers = "sys_platform == 'darwin' and platform_machine == 'arm64'" }, - { version = "0.16.2+cpu", source = "pytorch-cpu", markers = "platform_machine == 'x86_64'" } + { version = "0.16.2", source = "PyPI", markers = "sys_platform == 'darwin'" }, + { version = "0.16.2+cpu", source = "pytorch-cpu", markers = "sys_platform != 'darwin'"} ] # The following packages are platform locked to not install on MacOS as the installation breaks From bb76dae73c77eac46d915ae27e2ab88ed36c47c0 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 20 Dec 2024 14:03:10 -0500 Subject: [PATCH 04/29] restructure lib --- lib/qml/.python-version | 1 - lib/qml/README.md | 0 lib/qml/{qml => }/__init__.py | 0 lib/qml/{qml => }/app.py | 0 lib/qml/{qml => }/context.py | 0 lib/qml/{qml => }/lib/__init__.py | 0 lib/qml/{qml => }/lib/cmds.py | 0 lib/qml/{qml => }/lib/demo.py | 0 lib/qml/{qml => }/lib/fs.py | 0 lib/qml/{qml => }/lib/virtual_env.py | 0 lib/qml/pyproject.toml | 14 -------------- poetry.lock | 2 +- pyproject.toml | 7 +++++-- 13 files changed, 6 insertions(+), 18 deletions(-) delete mode 100644 lib/qml/.python-version delete mode 100644 lib/qml/README.md rename lib/qml/{qml => }/__init__.py (100%) rename lib/qml/{qml => }/app.py (100%) rename lib/qml/{qml => }/context.py (100%) rename lib/qml/{qml => }/lib/__init__.py (100%) rename lib/qml/{qml => }/lib/cmds.py (100%) rename lib/qml/{qml => }/lib/demo.py (100%) rename lib/qml/{qml => }/lib/fs.py (100%) rename lib/qml/{qml => }/lib/virtual_env.py (100%) delete mode 100644 lib/qml/pyproject.toml diff --git a/lib/qml/.python-version b/lib/qml/.python-version deleted file mode 100644 index c8cfe39591..0000000000 --- a/lib/qml/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10 diff --git a/lib/qml/README.md b/lib/qml/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/qml/qml/__init__.py b/lib/qml/__init__.py similarity index 100% rename from lib/qml/qml/__init__.py rename to lib/qml/__init__.py diff --git a/lib/qml/qml/app.py b/lib/qml/app.py similarity index 100% rename from lib/qml/qml/app.py rename to lib/qml/app.py diff --git a/lib/qml/qml/context.py b/lib/qml/context.py similarity index 100% rename from lib/qml/qml/context.py rename to lib/qml/context.py diff --git a/lib/qml/qml/lib/__init__.py b/lib/qml/lib/__init__.py similarity index 100% rename from lib/qml/qml/lib/__init__.py rename to lib/qml/lib/__init__.py diff --git a/lib/qml/qml/lib/cmds.py b/lib/qml/lib/cmds.py similarity index 100% rename from lib/qml/qml/lib/cmds.py rename to lib/qml/lib/cmds.py diff --git a/lib/qml/qml/lib/demo.py b/lib/qml/lib/demo.py similarity index 100% rename from lib/qml/qml/lib/demo.py rename to lib/qml/lib/demo.py diff --git a/lib/qml/qml/lib/fs.py b/lib/qml/lib/fs.py similarity index 100% rename from lib/qml/qml/lib/fs.py rename to lib/qml/lib/fs.py diff --git a/lib/qml/qml/lib/virtual_env.py b/lib/qml/lib/virtual_env.py similarity index 100% rename from lib/qml/qml/lib/virtual_env.py rename to lib/qml/lib/virtual_env.py diff --git a/lib/qml/pyproject.toml b/lib/qml/pyproject.toml deleted file mode 100644 index ae6c67ff19..0000000000 --- a/lib/qml/pyproject.toml +++ /dev/null @@ -1,14 +0,0 @@ -[project] -name = "qml-tool" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -requires-python = ">=3.10" -dependencies = [ - "cyclopts>=3.1.2", - "dulwich>=0.22.6", - "uv>=0.5.6", -] - -[project.scripts] -qml = "qml.app:app" diff --git a/poetry.lock b/poetry.lock index 95daf8cc17..8e3ab55d65 100644 --- a/poetry.lock +++ b/poetry.lock @@ -9094,4 +9094,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "abf7cd9ec8d973416a3721003ed39f44d9c6d58a384597905f4fa25a230fed58" +content-hash = "838db84d6f4f4606c1d49f4e370ea0ef0b519566662bbff11b2e905fb7e07e31" diff --git a/pyproject.toml b/pyproject.toml index b7165179be..9d2a1b7cb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ keywords = ["demo", "qml", "tensorflow", "automatic-differentiation", "tutorials packages = [ {include = "qml", from = "lib"} ] + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" @@ -21,16 +22,17 @@ url = "https://download.pytorch.org/whl/cpu" priority = "explicit" [tool.poetry.scripts] -my_package_cli = 'qml.app:app' +qml = 'qml.app:app' [tool.poetry.dependencies] python = "~3.10.0" typer = "^0.15.1" poetry = ">1.7" poetry-plugin-export = "^1.8.0" +dulwich = "<0.22" -# Base dependencies needed to build the website without any code execution (*-norun) [tool.poetry.group.base.dependencies] +# Base dependencies needed to build the website without any code execution (*-norun) sphinx = ">=5.0.2" sphinx_gallery = "0.17.1" Jinja2 = "3.0.3" @@ -47,6 +49,7 @@ pennylane = "0.39.0" # These pinned versions of PL are NOT used for Dev builds # # The latest commit from GitHub is used instead # ########################################################### +pennylane = "0.39.0" pennylane-cirq = "0.39.0" pennylane-qiskit = "0.39.0" pennylane-qulacs = "0.39.0" From 22acefc08211c053ad3396651c10361e2152b2d2 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 11:59:45 -0500 Subject: [PATCH 05/29] Apply suggestions from code review Co-authored-by: anthayes92 <34694788+anthayes92@users.noreply.github.com> --- lib/qml/app.py | 2 +- lib/qml/lib/cmds.py | 2 +- lib/qml/lib/demo.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/qml/app.py b/lib/qml/app.py index 2c895fbe32..2897549355 100644 --- a/lib/qml/app.py +++ b/lib/qml/app.py @@ -30,7 +30,7 @@ def build( execute: Annotated[ bool, typer.Option(help="Whether to execute demos and generate output cells") ] = False, -): +) -> None: """Build the named demos.""" ctx = Context() demo_names = demo_names or [] diff --git a/lib/qml/lib/cmds.py b/lib/qml/lib/cmds.py index bcaf62d6b6..8ffb0ace95 100644 --- a/lib/qml/lib/cmds.py +++ b/lib/qml/lib/cmds.py @@ -20,7 +20,7 @@ def poetry_export( listed. Otherwise, include only dependencies from the main group. - Raies: + Raises: CalledProcessError: The command does not complete successfully """ cmd = [ diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index 7d505e62e8..62c38fc425 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -92,7 +92,7 @@ def build( demos: Sequence[Demo], target: BuildTarget, execute: bool, -): +) -> None: """Build the provided demos using 'sphinx-build', optionally executing them to generate plots and cell outputs. From 326e586744f94c44fbf4bfb0265c3d03073453a2 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 12:01:26 -0500 Subject: [PATCH 06/29] docstrings --- lib/qml/context.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/qml/context.py b/lib/qml/context.py index 18562042cd..ad32b6b9c6 100644 --- a/lib/qml/context.py +++ b/lib/qml/context.py @@ -19,14 +19,17 @@ def demos_dir(self) -> Path: @property def build_dir(self) -> Path: + """Path to the build directory.""" return self.repo_root / "_build" @property def build_venv_path(self) -> Path: + """Path to virtual environment for building demos.""" return self.repo_root / ".venv-build" @functools.cached_property def cwd(self) -> Path: + """Current working directory of the process.""" return Path.cwd().resolve() @functools.cached_property From 5fcda7f06c3d2b92888f1d190dc1537b2e1048d6 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 12:02:38 -0500 Subject: [PATCH 07/29] specify poetry<2 --- poetry.lock | 1563 +++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 752 insertions(+), 813 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8e3ab55d65..3da7a9c7da 100644 --- a/poetry.lock +++ b/poetry.lock @@ -175,13 +175,13 @@ files = [ [[package]] name = "anyio" -version = "4.7.0" +version = "4.8.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, - {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, ] [package.dependencies] @@ -192,7 +192,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -400,13 +400,13 @@ virtualenv = ["virtualenv (>=20.0.35)"] [[package]] name = "cachecontrol" -version = "0.14.1" +version = "0.14.2" description = "httplib2 caching for requests" optional = false python-versions = ">=3.8" files = [ - {file = "cachecontrol-0.14.1-py3-none-any.whl", hash = "sha256:65e3abd62b06382ce3894df60dde9e0deb92aeb734724f68fa4f3b91e97206b9"}, - {file = "cachecontrol-0.14.1.tar.gz", hash = "sha256:06ef916a1e4eb7dba9948cdfc9c76e749db2e02104a9a1277e8b642591a0f717"}, + {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"}, + {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"}, ] [package.dependencies] @@ -522,116 +522,103 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] @@ -823,13 +810,13 @@ rapidfuzz = ">=3.0.0,<4.0.0" [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -837,13 +824,13 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "cloudpickle" -version = "3.1.0" +version = "3.1.1" description = "Pickler class to extend the standard pickle.Pickler functionality" optional = false python-versions = ">=3.8" files = [ - {file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"}, - {file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"}, + {file = "cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e"}, + {file = "cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64"}, ] [[package]] @@ -932,13 +919,13 @@ test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist" [[package]] name = "cotengra" -version = "0.6.2" +version = "0.7.0" description = "Hyper optimized contraction trees for large tensor networks and einsums." optional = false python-versions = ">=3.8" files = [ - {file = "cotengra-0.6.2-py3-none-any.whl", hash = "sha256:524711515cfbaae22ae56a8c6bf3339dfe50485b9b615de52f8f7c330847b196"}, - {file = "cotengra-0.6.2.tar.gz", hash = "sha256:a56b921d0339d1397e925a7b69029d3301636d09b99b509368751753bda39d24"}, + {file = "cotengra-0.7.0-py3-none-any.whl", hash = "sha256:ca1c75fec462fb75130a3a826db2d4565bbbf0641d5ce0a617e643211cd98307"}, + {file = "cotengra-0.7.0.tar.gz", hash = "sha256:8ad9fabee899ac2f7b72359ace79a5496f15fd89b19049656082ef7773ca2b69"}, ] [package.dependencies] @@ -946,8 +933,8 @@ autoray = "*" [package.extras] docs = ["astroid (<3.0.0)", "furo", "ipython (!=8.7.0)", "myst-nb", "setuptools-scm", "sphinx (>=2.0)", "sphinx-autoapi", "sphinx-copybutton", "sphinx-design"] -recommended = ["cotengrust (>=0.1.3)", "cytoolz", "kahypar", "networkx", "numpy", "opt-einsum", "optuna", "ray", "tqdm"] -test = ["altair", "baytune", "chocolate", "dask", "distributed", "kahypar", "matplotlib", "networkx", "nevergrad", "numpy", "opt-einsum", "pytest", "seaborn", "skopt"] +recommended = ["cmaes", "cotengrust (>=0.1.3)", "cytoolz", "kahypar", "networkx", "numpy", "opt-einsum", "optuna", "ray", "tqdm"] +test = ["altair", "baytune", "chocolate", "cmaes", "dask", "distributed", "kahypar", "matplotlib", "networkx", "nevergrad", "numpy", "opt-einsum", "pytest", "scikit-optimize", "seaborn"] [[package]] name = "covalent" @@ -2065,13 +2052,13 @@ dotenv = ["python-dotenv"] [[package]] name = "flatbuffers" -version = "24.3.25" +version = "24.12.23" description = "The FlatBuffers serialization format for Python" optional = false python-versions = "*" files = [ - {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, - {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, + {file = "flatbuffers-24.12.23-py2.py3-none-any.whl", hash = "sha256:c418e0d48890f4142b92fd3e343e73a48f194e1f80075ddcc5793779b3585444"}, + {file = "flatbuffers-24.12.23.tar.gz", hash = "sha256:2910b0bc6ae9b6db78dd2b18d0b7a0709ba240fb5585f286a3a2b30785c22dac"}, ] [[package]] @@ -2585,70 +2572,70 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.68.1" +version = "1.69.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.68.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:d35740e3f45f60f3c37b1e6f2f4702c23867b9ce21c6410254c9c682237da68d"}, - {file = "grpcio-1.68.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d99abcd61760ebb34bdff37e5a3ba333c5cc09feda8c1ad42547bea0416ada78"}, - {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f8261fa2a5f679abeb2a0a93ad056d765cdca1c47745eda3f2d87f874ff4b8c9"}, - {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0feb02205a27caca128627bd1df4ee7212db051019a9afa76f4bb6a1a80ca95e"}, - {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:919d7f18f63bcad3a0f81146188e90274fde800a94e35d42ffe9eadf6a9a6330"}, - {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:963cc8d7d79b12c56008aabd8b457f400952dbea8997dd185f155e2f228db079"}, - {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ccf2ebd2de2d6661e2520dae293298a3803a98ebfc099275f113ce1f6c2a80f1"}, - {file = "grpcio-1.68.1-cp310-cp310-win32.whl", hash = "sha256:2cc1fd04af8399971bcd4f43bd98c22d01029ea2e56e69c34daf2bf8470e47f5"}, - {file = "grpcio-1.68.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2e743e51cb964b4975de572aa8fb95b633f496f9fcb5e257893df3be854746"}, - {file = "grpcio-1.68.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:55857c71641064f01ff0541a1776bfe04a59db5558e82897d35a7793e525774c"}, - {file = "grpcio-1.68.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4b177f5547f1b995826ef529d2eef89cca2f830dd8b2c99ffd5fde4da734ba73"}, - {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3522c77d7e6606d6665ec8d50e867f13f946a4e00c7df46768f1c85089eae515"}, - {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d1fae6bbf0816415b81db1e82fb3bf56f7857273c84dcbe68cbe046e58e1ccd"}, - {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298ee7f80e26f9483f0b6f94cc0a046caf54400a11b644713bb5b3d8eb387600"}, - {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb5780e2e740b6b4f2d208e90453591036ff80c02cc605fea1af8e6fc6b1bbe"}, - {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ddda1aa22495d8acd9dfbafff2866438d12faec4d024ebc2e656784d96328ad0"}, - {file = "grpcio-1.68.1-cp311-cp311-win32.whl", hash = "sha256:b33bd114fa5a83f03ec6b7b262ef9f5cac549d4126f1dc702078767b10c46ed9"}, - {file = "grpcio-1.68.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f20ebec257af55694d8f993e162ddf0d36bd82d4e57f74b31c67b3c6d63d8b2"}, - {file = "grpcio-1.68.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8829924fffb25386995a31998ccbbeaa7367223e647e0122043dfc485a87c666"}, - {file = "grpcio-1.68.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3aed6544e4d523cd6b3119b0916cef3d15ef2da51e088211e4d1eb91a6c7f4f1"}, - {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:4efac5481c696d5cb124ff1c119a78bddbfdd13fc499e3bc0ca81e95fc573684"}, - {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ab2d912ca39c51f46baf2a0d92aa265aa96b2443266fc50d234fa88bf877d8e"}, - {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c87ce2a97434dffe7327a4071839ab8e8bffd0054cc74cbe971fba98aedd60"}, - {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e4842e4872ae4ae0f5497bf60a0498fa778c192cc7a9e87877abd2814aca9475"}, - {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:255b1635b0ed81e9f91da4fcc8d43b7ea5520090b9a9ad9340d147066d1d3613"}, - {file = "grpcio-1.68.1-cp312-cp312-win32.whl", hash = "sha256:7dfc914cc31c906297b30463dde0b9be48e36939575eaf2a0a22a8096e69afe5"}, - {file = "grpcio-1.68.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0c8ddabef9c8f41617f213e527254c41e8b96ea9d387c632af878d05db9229c"}, - {file = "grpcio-1.68.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:a47faedc9ea2e7a3b6569795c040aae5895a19dde0c728a48d3c5d7995fda385"}, - {file = "grpcio-1.68.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:390eee4225a661c5cd133c09f5da1ee3c84498dc265fd292a6912b65c421c78c"}, - {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:66a24f3d45c33550703f0abb8b656515b0ab777970fa275693a2f6dc8e35f1c1"}, - {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c08079b4934b0bf0a8847f42c197b1d12cba6495a3d43febd7e99ecd1cdc8d54"}, - {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8720c25cd9ac25dd04ee02b69256d0ce35bf8a0f29e20577427355272230965a"}, - {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:04cfd68bf4f38f5bb959ee2361a7546916bd9a50f78617a346b3aeb2b42e2161"}, - {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c28848761a6520c5c6071d2904a18d339a796ebe6b800adc8b3f474c5ce3c3ad"}, - {file = "grpcio-1.68.1-cp313-cp313-win32.whl", hash = "sha256:77d65165fc35cff6e954e7fd4229e05ec76102d4406d4576528d3a3635fc6172"}, - {file = "grpcio-1.68.1-cp313-cp313-win_amd64.whl", hash = "sha256:a8040f85dcb9830d8bbb033ae66d272614cec6faceee88d37a88a9bd1a7a704e"}, - {file = "grpcio-1.68.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:eeb38ff04ab6e5756a2aef6ad8d94e89bb4a51ef96e20f45c44ba190fa0bcaad"}, - {file = "grpcio-1.68.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a3869a6661ec8f81d93f4597da50336718bde9eb13267a699ac7e0a1d6d0bea"}, - {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2c4cec6177bf325eb6faa6bd834d2ff6aa8bb3b29012cceb4937b86f8b74323c"}, - {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12941d533f3cd45d46f202e3667be8ebf6bcb3573629c7ec12c3e211d99cfccf"}, - {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80af6f1e69c5e68a2be529990684abdd31ed6622e988bf18850075c81bb1ad6e"}, - {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e8dbe3e00771bfe3d04feed8210fc6617006d06d9a2679b74605b9fed3e8362c"}, - {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:83bbf5807dc3ee94ce1de2dfe8a356e1d74101e4b9d7aa8c720cc4818a34aded"}, - {file = "grpcio-1.68.1-cp38-cp38-win32.whl", hash = "sha256:8cb620037a2fd9eeee97b4531880e439ebfcd6d7d78f2e7dcc3726428ab5ef63"}, - {file = "grpcio-1.68.1-cp38-cp38-win_amd64.whl", hash = "sha256:52fbf85aa71263380d330f4fce9f013c0798242e31ede05fcee7fbe40ccfc20d"}, - {file = "grpcio-1.68.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb400138e73969eb5e0535d1d06cae6a6f7a15f2cc74add320e2130b8179211a"}, - {file = "grpcio-1.68.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a1b988b40f2fd9de5c820f3a701a43339d8dcf2cb2f1ca137e2c02671cc83ac1"}, - {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:96f473cdacfdd506008a5d7579c9f6a7ff245a9ade92c3c0265eb76cc591914f"}, - {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37ea3be171f3cf3e7b7e412a98b77685eba9d4fd67421f4a34686a63a65d99f9"}, - {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ceb56c4285754e33bb3c2fa777d055e96e6932351a3082ce3559be47f8024f0"}, - {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dffd29a2961f3263a16d73945b57cd44a8fd0b235740cb14056f0612329b345e"}, - {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:025f790c056815b3bf53da850dd70ebb849fd755a4b1ac822cb65cd631e37d43"}, - {file = "grpcio-1.68.1-cp39-cp39-win32.whl", hash = "sha256:1098f03dedc3b9810810568060dea4ac0822b4062f537b0f53aa015269be0a76"}, - {file = "grpcio-1.68.1-cp39-cp39-win_amd64.whl", hash = "sha256:334ab917792904245a028f10e803fcd5b6f36a7b2173a820c0b5b076555825e1"}, - {file = "grpcio-1.68.1.tar.gz", hash = "sha256:44a8502dd5de653ae6a73e2de50a401d84184f0331d0ac3daeb044e66d5c5054"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.68.1)"] + {file = "grpcio-1.69.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:2060ca95a8db295ae828d0fc1c7f38fb26ccd5edf9aa51a0f44251f5da332e97"}, + {file = "grpcio-1.69.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2e52e107261fd8fa8fa457fe44bfadb904ae869d87c1280bf60f93ecd3e79278"}, + {file = "grpcio-1.69.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:316463c0832d5fcdb5e35ff2826d9aa3f26758d29cdfb59a368c1d6c39615a11"}, + {file = "grpcio-1.69.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26c9a9c4ac917efab4704b18eed9082ed3b6ad19595f047e8173b5182fec0d5e"}, + {file = "grpcio-1.69.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90b3646ced2eae3a0599658eeccc5ba7f303bf51b82514c50715bdd2b109e5ec"}, + {file = "grpcio-1.69.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3b75aea7c6cb91b341c85e7c1d9db1e09e1dd630b0717f836be94971e015031e"}, + {file = "grpcio-1.69.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5cfd14175f9db33d4b74d63de87c64bb0ee29ce475ce3c00c01ad2a3dc2a9e51"}, + {file = "grpcio-1.69.0-cp310-cp310-win32.whl", hash = "sha256:9031069d36cb949205293cf0e243abd5e64d6c93e01b078c37921493a41b72dc"}, + {file = "grpcio-1.69.0-cp310-cp310-win_amd64.whl", hash = "sha256:cc89b6c29f3dccbe12d7a3b3f1b3999db4882ae076c1c1f6df231d55dbd767a5"}, + {file = "grpcio-1.69.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8de1b192c29b8ce45ee26a700044717bcbbd21c697fa1124d440548964328561"}, + {file = "grpcio-1.69.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:7e76accf38808f5c5c752b0ab3fd919eb14ff8fafb8db520ad1cc12afff74de6"}, + {file = "grpcio-1.69.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:d5658c3c2660417d82db51e168b277e0ff036d0b0f859fa7576c0ffd2aec1442"}, + {file = "grpcio-1.69.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5494d0e52bf77a2f7eb17c6da662886ca0a731e56c1c85b93505bece8dc6cf4c"}, + {file = "grpcio-1.69.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ed866f9edb574fd9be71bf64c954ce1b88fc93b2a4cbf94af221e9426eb14d6"}, + {file = "grpcio-1.69.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c5ba38aeac7a2fe353615c6b4213d1fbb3a3c34f86b4aaa8be08baaaee8cc56d"}, + {file = "grpcio-1.69.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f79e05f5bbf551c4057c227d1b041ace0e78462ac8128e2ad39ec58a382536d2"}, + {file = "grpcio-1.69.0-cp311-cp311-win32.whl", hash = "sha256:bf1f8be0da3fcdb2c1e9f374f3c2d043d606d69f425cd685110dd6d0d2d61258"}, + {file = "grpcio-1.69.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb9302afc3a0e4ba0b225cd651ef8e478bf0070cf11a529175caecd5ea2474e7"}, + {file = "grpcio-1.69.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:fc18a4de8c33491ad6f70022af5c460b39611e39578a4d84de0fe92f12d5d47b"}, + {file = "grpcio-1.69.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:0f0270bd9ffbff6961fe1da487bdcd594407ad390cc7960e738725d4807b18c4"}, + {file = "grpcio-1.69.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc48f99cc05e0698e689b51a05933253c69a8c8559a47f605cff83801b03af0e"}, + {file = "grpcio-1.69.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e925954b18d41aeb5ae250262116d0970893b38232689c4240024e4333ac084"}, + {file = "grpcio-1.69.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d222569273720366f68a99cb62e6194681eb763ee1d3b1005840678d4884f9"}, + {file = "grpcio-1.69.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b62b0f41e6e01a3e5082000b612064c87c93a49b05f7602fe1b7aa9fd5171a1d"}, + {file = "grpcio-1.69.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:db6f9fd2578dbe37db4b2994c94a1d9c93552ed77dca80e1657bb8a05b898b55"}, + {file = "grpcio-1.69.0-cp312-cp312-win32.whl", hash = "sha256:b192b81076073ed46f4b4dd612b8897d9a1e39d4eabd822e5da7b38497ed77e1"}, + {file = "grpcio-1.69.0-cp312-cp312-win_amd64.whl", hash = "sha256:1227ff7836f7b3a4ab04e5754f1d001fa52a730685d3dc894ed8bc262cc96c01"}, + {file = "grpcio-1.69.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:a78a06911d4081a24a1761d16215a08e9b6d4d29cdbb7e427e6c7e17b06bcc5d"}, + {file = "grpcio-1.69.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:dc5a351927d605b2721cbb46158e431dd49ce66ffbacb03e709dc07a491dde35"}, + {file = "grpcio-1.69.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:3629d8a8185f5139869a6a17865d03113a260e311e78fbe313f1a71603617589"}, + {file = "grpcio-1.69.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9a281878feeb9ae26db0622a19add03922a028d4db684658f16d546601a4870"}, + {file = "grpcio-1.69.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc614e895177ab7e4b70f154d1a7c97e152577ea101d76026d132b7aaba003b"}, + {file = "grpcio-1.69.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1ee76cd7e2e49cf9264f6812d8c9ac1b85dda0eaea063af07292400f9191750e"}, + {file = "grpcio-1.69.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0470fa911c503af59ec8bc4c82b371ee4303ececbbdc055f55ce48e38b20fd67"}, + {file = "grpcio-1.69.0-cp313-cp313-win32.whl", hash = "sha256:b650f34aceac8b2d08a4c8d7dc3e8a593f4d9e26d86751ebf74ebf5107d927de"}, + {file = "grpcio-1.69.0-cp313-cp313-win_amd64.whl", hash = "sha256:028337786f11fecb5d7b7fa660475a06aabf7e5e52b5ac2df47414878c0ce7ea"}, + {file = "grpcio-1.69.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:b7f693db593d6bf285e015d5538bf1c86cf9c60ed30b6f7da04a00ed052fe2f3"}, + {file = "grpcio-1.69.0-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:8b94e83f66dbf6fd642415faca0608590bc5e8d30e2c012b31d7d1b91b1de2fd"}, + {file = "grpcio-1.69.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:b634851b92c090763dde61df0868c730376cdb73a91bcc821af56ae043b09596"}, + {file = "grpcio-1.69.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf5f680d3ed08c15330d7830d06bc65f58ca40c9999309517fd62880d70cb06e"}, + {file = "grpcio-1.69.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:200e48a6e7b00f804cf00a1c26292a5baa96507c7749e70a3ec10ca1a288936e"}, + {file = "grpcio-1.69.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:45a4704339b6e5b24b0e136dea9ad3815a94f30eb4f1e1d44c4ac484ef11d8dd"}, + {file = "grpcio-1.69.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85d347cb8237751b23539981dbd2d9d8f6e9ff90082b427b13022b948eb6347a"}, + {file = "grpcio-1.69.0-cp38-cp38-win32.whl", hash = "sha256:60e5de105dc02832dc8f120056306d0ef80932bcf1c0e2b4ca3b676de6dc6505"}, + {file = "grpcio-1.69.0-cp38-cp38-win_amd64.whl", hash = "sha256:282f47d0928e40f25d007f24eb8fa051cb22551e3c74b8248bc9f9bea9c35fe0"}, + {file = "grpcio-1.69.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:dd034d68a2905464c49479b0c209c773737a4245d616234c79c975c7c90eca03"}, + {file = "grpcio-1.69.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:01f834732c22a130bdf3dc154d1053bdbc887eb3ccb7f3e6285cfbfc33d9d5cc"}, + {file = "grpcio-1.69.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:a7f4ed0dcf202a70fe661329f8874bc3775c14bb3911d020d07c82c766ce0eb1"}, + {file = "grpcio-1.69.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd7ea241b10bc5f0bb0f82c0d7896822b7ed122b3ab35c9851b440c1ccf81588"}, + {file = "grpcio-1.69.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f03dc9b4da4c0dc8a1db7a5420f575251d7319b7a839004d8916257ddbe4816"}, + {file = "grpcio-1.69.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca71d73a270dff052fe4edf74fef142d6ddd1f84175d9ac4a14b7280572ac519"}, + {file = "grpcio-1.69.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ccbed100dc43704e94ccff9e07680b540d64e4cc89213ab2832b51b4f68a520"}, + {file = "grpcio-1.69.0-cp39-cp39-win32.whl", hash = "sha256:1514341def9c6ec4b7f0b9628be95f620f9d4b99331b7ef0a1845fd33d9b579c"}, + {file = "grpcio-1.69.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1fea55d26d647346acb0069b08dca70984101f2dc95066e003019207212e303"}, + {file = "grpcio-1.69.0.tar.gz", hash = "sha256:936fa44241b5379c5afc344e1260d467bee495747eaf478de825bab2791da6f5"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.69.0)"] [[package]] name = "grpcio-status" @@ -2778,18 +2765,18 @@ test = ["Cython (>=0.29.24)"] [[package]] name = "ibm-cloud-sdk-core" -version = "3.22.0" +version = "3.22.1" description = "Core library used by SDKs for IBM Cloud Services" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "ibm_cloud_sdk_core-3.22.0-py3-none-any.whl", hash = "sha256:a86e9006d98f32510d47d4751d8ba04b64514e3c94b851511fd55d7b094c87b1"}, - {file = "ibm_cloud_sdk_core-3.22.0.tar.gz", hash = "sha256:0aa5abc972b7d70ee4c39e3ad69c82c9717be847e5672928b416b8ea7d57a297"}, + {file = "ibm_cloud_sdk_core-3.22.1-py3-none-any.whl", hash = "sha256:d2195d34705b24c7e3a59ad51f01a974defc5bee3576570d9a0e219139550dd4"}, + {file = "ibm_cloud_sdk_core-3.22.1.tar.gz", hash = "sha256:096dd22c146848a87053f8f2bfbd5dfdf089eefe81ee1c448ca22c4304751dfd"}, ] [package.dependencies] PyJWT = ">=2.8.0,<3.0.0" -python-dateutil = ">=2.8.2,<3.0.0" +python_dateutil = ">=2.8.2,<3.0.0" requests = ">=2.31.0,<3.0.0" urllib3 = ">=2.1.0,<3.0.0" @@ -2799,17 +2786,17 @@ publish = ["build", "twine"] [[package]] name = "ibm-platform-services" -version = "0.59.0" +version = "0.59.1" description = "Python client library for IBM Cloud Platform Services" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "ibm_platform_services-0.59.0-py3-none-any.whl", hash = "sha256:2deba369f03843a0b628c03065b791f0738cd7a570b51836cb68ac7629ae545d"}, - {file = "ibm_platform_services-0.59.0.tar.gz", hash = "sha256:4d63bfa25d9bf995ea75ed449ce26ce4d21c8e2385e04f6da864bdc97c4e62a0"}, + {file = "ibm_platform_services-0.59.1-py3-none-any.whl", hash = "sha256:bc9a324c387d2ed5d544f5cab597f50ab55a4426f7c1b40a51162a46f9f62962"}, + {file = "ibm_platform_services-0.59.1.tar.gz", hash = "sha256:5d0f5a64ddb0cc2dfbeb89da0df611faa9b2494a091f8bba5e8f44a497e7c0c3"}, ] [package.dependencies] -ibm-cloud-sdk-core = ">=3.22.0,<4.0.0" +ibm_cloud_sdk_core = ">=3.22.0,<4.0.0" [package.extras] dev = ["black (>=24.0.0,<25.0.0)", "coverage (>=7.3.2,<8.0.0)", "pylint (>=3.0.0,<4.0.0)", "pytest (>=7.4.2,<8.0.0)", "pytest-cov (>=4.1.0,<5.0.0)", "responses (>=0.23.3,<1.0.0)"] @@ -2865,13 +2852,13 @@ type = ["pytest-mypy"] [[package]] name = "importlib-resources" -version = "6.4.5" +version = "6.5.2" description = "Read resources from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, - {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, + {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, + {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, ] [package.extras] @@ -3231,125 +3218,91 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-ena [[package]] name = "kiwisolver" -version = "1.4.7" +version = "1.4.8" description = "A fast implementation of the Cassowary constraint solver" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, - {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b"}, + {file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e"}, ] [[package]] @@ -3717,13 +3670,13 @@ dev = ["absl-py", "pyink", "pylint (>=2.6.0)", "pytest", "pytest-xdist"] [[package]] name = "more-itertools" -version = "10.5.0" +version = "10.6.0" description = "More routines for operating on iterables, beyond itertools" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, - {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, + {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, + {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, ] [[package]] @@ -4363,86 +4316,86 @@ six = ">=1.8.0" [[package]] name = "orjson" -version = "3.10.12" +version = "3.10.14" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, - {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, - {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, - {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, - {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, - {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, - {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, - {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, - {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, - {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, - {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, - {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, - {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, - {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, - {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, - {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, - {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, - {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, - {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, + {file = "orjson-3.10.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:849ea7845a55f09965826e816cdc7689d6cf74fe9223d79d758c714af955bcb6"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5947b139dfa33f72eecc63f17e45230a97e741942955a6c9e650069305eb73d"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cde6d76910d3179dae70f164466692f4ea36da124d6fb1a61399ca589e81d69a"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6dfbaeb7afa77ca608a50e2770a0461177b63a99520d4928e27591b142c74b1"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa45e489ef80f28ff0e5ba0a72812b8cfc7c1ef8b46a694723807d1b07c89ebb"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5007abfdbb1d866e2aa8990bd1c465f0f6da71d19e695fc278282be12cffa5"}, + {file = "orjson-3.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b49e2af011c84c3f2d541bb5cd1e3c7c2df672223e7e3ea608f09cf295e5f8a"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:164ac155109226b3a2606ee6dda899ccfbe6e7e18b5bdc3fbc00f79cc074157d"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6b1225024cf0ef5d15934b5ffe9baf860fe8bc68a796513f5ea4f5056de30bca"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6546e8073dc382e60fcae4a001a5a1bc46da5eab4a4878acc2d12072d6166d5"}, + {file = "orjson-3.10.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f1d2942605c894162252d6259b0121bf1cb493071a1ea8cb35d79cb3e6ac5bc"}, + {file = "orjson-3.10.14-cp310-cp310-win32.whl", hash = "sha256:397083806abd51cf2b3bbbf6c347575374d160331a2d33c5823e22249ad3118b"}, + {file = "orjson-3.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:fa18f949d3183a8d468367056be989666ac2bef3a72eece0bade9cdb733b3c28"}, + {file = "orjson-3.10.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f506fd666dd1ecd15a832bebc66c4df45c1902fd47526292836c339f7ba665a9"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe5fd254cfb0eeee13b8ef7ecb20f5d5a56ddda8a587f3852ab2cedfefdb5f6"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ddc8c866d7467f5ee2991397d2ea94bcf60d0048bdd8ca555740b56f9042725"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af8e42ae4363773658b8d578d56dedffb4f05ceeb4d1d4dd3fb504950b45526"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84dd83110503bc10e94322bf3ffab8bc49150176b49b4984dc1cce4c0a993bf9"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f5bfc0399cd4811bf10ec7a759c7ab0cd18080956af8ee138097d5b5296a95"}, + {file = "orjson-3.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868943660fb2a1e6b6b965b74430c16a79320b665b28dd4511d15ad5038d37d5"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33449c67195969b1a677533dee9d76e006001213a24501333624623e13c7cc8e"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4c9f60f9fb0b5be66e416dcd8c9d94c3eabff3801d875bdb1f8ffc12cf86905"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0de4d6315cfdbd9ec803b945c23b3a68207fd47cbe43626036d97e8e9561a436"}, + {file = "orjson-3.10.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83adda3db595cb1a7e2237029b3249c85afbe5c747d26b41b802e7482cb3933e"}, + {file = "orjson-3.10.14-cp311-cp311-win32.whl", hash = "sha256:998019ef74a4997a9d741b1473533cdb8faa31373afc9849b35129b4b8ec048d"}, + {file = "orjson-3.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:9d034abdd36f0f0f2240f91492684e5043d46f290525d1117712d5b8137784eb"}, + {file = "orjson-3.10.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2ad4b7e367efba6dc3f119c9a0fcd41908b7ec0399a696f3cdea7ec477441b09"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f496286fc85e93ce0f71cc84fc1c42de2decf1bf494094e188e27a53694777a7"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7f189bbfcded40e41a6969c1068ba305850ba016665be71a217918931416fbf"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cc8204f0b75606869c707da331058ddf085de29558b516fc43c73ee5ee2aadb"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deaa2899dff7f03ab667e2ec25842d233e2a6a9e333efa484dfe666403f3501c"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1c3ea52642c9714dc6e56de8a451a066f6d2707d273e07fe8a9cc1ba073813d"}, + {file = "orjson-3.10.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d3f9ed72e7458ded9a1fb1b4d4ed4c4fdbaf82030ce3f9274b4dc1bff7ace2b"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07520685d408a2aba514c17ccc16199ff2934f9f9e28501e676c557f454a37fe"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:76344269b550ea01488d19a2a369ab572c1ac4449a72e9f6ac0d70eb1cbfb953"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e2979d0f2959990620f7e62da6cd954e4620ee815539bc57a8ae46e2dacf90e3"}, + {file = "orjson-3.10.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03f61ca3674555adcb1aa717b9fc87ae936aa7a63f6aba90a474a88701278780"}, + {file = "orjson-3.10.14-cp312-cp312-win32.whl", hash = "sha256:d5075c54edf1d6ad81d4c6523ce54a748ba1208b542e54b97d8a882ecd810fd1"}, + {file = "orjson-3.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:175cafd322e458603e8ce73510a068d16b6e6f389c13f69bf16de0e843d7d406"}, + {file = "orjson-3.10.14-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:0905ca08a10f7e0e0c97d11359609300eb1437490a7f32bbaa349de757e2e0c7"}, + {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92d13292249f9f2a3e418cbc307a9fbbef043c65f4bd8ba1eb620bc2aaba3d15"}, + {file = "orjson-3.10.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90937664e776ad316d64251e2fa2ad69265e4443067668e4727074fe39676414"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed3d26c4cb4f6babaf791aa46a029265850e80ec2a566581f5c2ee1a14df4f1"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:56ee546c2bbe9599aba78169f99d1dc33301853e897dbaf642d654248280dc6e"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:901e826cb2f1bdc1fcef3ef59adf0c451e8f7c0b5deb26c1a933fb66fb505eae"}, + {file = "orjson-3.10.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26336c0d4b2d44636e1e1e6ed1002f03c6aae4a8a9329561c8883f135e9ff010"}, + {file = "orjson-3.10.14-cp313-cp313-win32.whl", hash = "sha256:e2bc525e335a8545c4e48f84dd0328bc46158c9aaeb8a1c2276546e94540ea3d"}, + {file = "orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364"}, + {file = "orjson-3.10.14-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a0fba3b8a587a54c18585f077dcab6dd251c170d85cfa4d063d5746cd595a0f"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:175abf3d20e737fec47261d278f95031736a49d7832a09ab684026528c4d96db"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29ca1a93e035d570e8b791b6c0feddd403c6a5388bfe870bf2aa6bba1b9d9b8e"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f77202c80e8ab5a1d1e9faf642343bee5aaf332061e1ada4e9147dbd9eb00c46"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2ec73b7099b6a29b40a62e08a23b936423bd35529f8f55c42e27acccde7954"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d1679df9f9cd9504f8dff24555c1eaabba8aad7f5914f28dab99e3c2552c9d"}, + {file = "orjson-3.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691ab9a13834310a263664313e4f747ceb93662d14a8bdf20eb97d27ed488f16"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b11ed82054fce82fb74cea33247d825d05ad6a4015ecfc02af5fbce442fbf361"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:e70a1d62b8288677d48f3bea66c21586a5f999c64ecd3878edb7393e8d1b548d"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:16642f10c1ca5611251bd835de9914a4b03095e28a34c8ba6a5500b5074338bd"}, + {file = "orjson-3.10.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3871bad546aa66c155e3f36f99c459780c2a392d502a64e23fb96d9abf338511"}, + {file = "orjson-3.10.14-cp38-cp38-win32.whl", hash = "sha256:0293a88815e9bb5c90af4045f81ed364d982f955d12052d989d844d6c4e50945"}, + {file = "orjson-3.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:6169d3868b190d6b21adc8e61f64e3db30f50559dfbdef34a1cd6c738d409dfc"}, + {file = "orjson-3.10.14-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:06d4ec218b1ec1467d8d64da4e123b4794c781b536203c309ca0f52819a16c03"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962c2ec0dcaf22b76dee9831fdf0c4a33d4bf9a257a2bc5d4adc00d5c8ad9034"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21d3be4132f71ef1360385770474f29ea1538a242eef72ac4934fe142800e37f"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28ed60597c149a9e3f5ad6dd9cebaee6fb2f0e3f2d159a4a2b9b862d4748860"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e947f70167fe18469f2023644e91ab3d24f9aed69a5e1c78e2c81b9cea553fb"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64410696c97a35af2432dea7bdc4ce32416458159430ef1b4beb79fd30093ad6"}, + {file = "orjson-3.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8050a5d81c022561ee29cd2739de5b4445f3c72f39423fde80a63299c1892c52"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b49a28e30d3eca86db3fe6f9b7f4152fcacbb4a467953cd1b42b94b479b77956"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ca041ad20291a65d853a9523744eebc3f5a4b2f7634e99f8fe88320695ddf766"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d313a2998b74bb26e9e371851a173a9b9474764916f1fc7971095699b3c6e964"}, + {file = "orjson-3.10.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7796692136a67b3e301ef9052bde6fe8e7bd5200da766811a3a608ffa62aaff0"}, + {file = "orjson-3.10.14-cp39-cp39-win32.whl", hash = "sha256:eee4bc767f348fba485ed9dc576ca58b0a9eac237f0e160f7a59bce628ed06b3"}, + {file = "orjson-3.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:96a1c0ee30fb113b3ae3c748fd75ca74a157ff4c58476c47db4d61518962a011"}, + {file = "orjson-3.10.14.tar.gz", hash = "sha256:cf31f6f071a6b8e7aa1ead1fa27b935b48d00fbfa6a28ce856cfff2d5dd68eed"}, ] [[package]] @@ -4676,32 +4629,33 @@ pennylane = ">=0.38.0" [[package]] name = "pennylane-lightning" -version = "0.39.0" +version = "0.40.0" description = "PennyLane-Lightning plugin" optional = false python-versions = ">=3.10" files = [ - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:dfdf6071337f743519d7bcbe4d1bf35f1822bf14020dbda3c734b0471c4dbb12"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:0d9d7a865cf81923a05247e811a1534f3395fc9f27ea51a0472837b2dbe88f8d"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:1d4c22f28afdb816bca37617d9d508e3a73b0bd50df4581bafd1de7f8ab028b2"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1878dbf59c779face32ee8b9291e2cf18a601c491a132afe89f9b1e477026455"}, - {file = "PennyLane_Lightning-0.39.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e5e68f467f68216d2b5f19639b4b3d621743c389a4d207282c52557e20ff10c"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:8b5dfb5ee187a98925130965b5cbfb9ecfc650ec23b2cfdbf16040db25d79f73"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:80debc3b1c7a5923509f4662b95ca1703ac8a29eaaa78a169240b23c4c1c7875"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ac2bcafd8b10b58e99cd050bb89a67e97b53e66ab8636e37bafd67a32905f935"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:de45c1c2e97572e59081878dc80bbebe17b938a783005ce0ad6632ba8dd46956"}, - {file = "PennyLane_Lightning-0.39.0-cp311-cp311-win_amd64.whl", hash = "sha256:dcbec946148018a8aebae6cf45bb0d9d6f9a418bb0655ed4b3ae2f7e948e320d"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:6c7553d206f185ab6f7688fe086d80a33f382beb56e9ad579090bdba041adfb1"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:30a08ed59fd2c11257bc989303547b8125e7453c8165f8c75ec7d8090451ec54"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c0f52da46791605e3f8f0644b45a97de799df1f69cd3659a58fc04f7288f948e"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a9ab74b0cf2cbc9556a0c85a750a5d6063721405f76cacf8b17f9e6774dd9b"}, - {file = "PennyLane_Lightning-0.39.0-cp312-cp312-win_amd64.whl", hash = "sha256:f568120eac4ed4b5d498889cb9359040dd7070ef711e98cdbace4efde794c866"}, - {file = "PennyLane_Lightning-0.39.0-py3-none-any.whl", hash = "sha256:55de99da8e513f89b416087d1665257498be244006968b472b82a56b972d0f0d"}, - {file = "PennyLane_Lightning-0.39.0.tar.gz", hash = "sha256:a1db6069c7d97eea50846d5b51a3d9ae97539bf68ac480b5c9bea1eaa5e3f1d1"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:3a6915cc17ce99cd4f3d04dcf7d579f59cecdc2866cd4e8e1c33478d86101437"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:4a66e8c644f634f9d1fda6b9223f5cc106acf4146334a25a23f948ec59b075c0"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a58ab1489fee0424840bc8f91e8f7e32ecc3338a714b2fe1bf68a9026a5a295a"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7fe9262c602b6c7f0ab893468867e24c63e9cacc7d77e19b448ac1361bb86a47"}, + {file = "PennyLane_Lightning-0.40.0-cp310-cp310-win_amd64.whl", hash = "sha256:3f75b7d39d63880e9d59562d78ae5ec63d2aed936b2feee3c937dfbcd080b834"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:99cf7bcfb3a2a29838cc12f0001e3e7e02d35c5c1d696ce2e5ba0c3f2995c4d9"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:6b89ad785e16cc3b3d155b1abd27e42fcb5854a0e2d90452f6445fc0e80b1cf4"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:20052041abc417d74d1794506d4340a8a2298b858e2b94591704e73670d913f9"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a3db3cf2c3caed80ce561b66bb4bf6b5712ecf0b08db986f24c4ff9e187e82b3"}, + {file = "PennyLane_Lightning-0.40.0-cp311-cp311-win_amd64.whl", hash = "sha256:fed88008b7d468cb1d0e5b3ef92d87f9b600d0896d21eff43655a521cc841d7b"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:e3152f6b53390281334257554ecad90566cd4d200187971d238d2c7691000466"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:8a4ff3f1d82f664e6d608d155cb019b189aac676c6c7cb40c4f92b15e0d2d485"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b2209727b8c914c8101087da188cbbc7b332bb77ceab4b6791c7ed7b3c5a942c"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2ba97b4d6aa0a6eb774413ea3272d418959b792de6df8e4196171cad450f25af"}, + {file = "PennyLane_Lightning-0.40.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7715c84290fc1291e315d2c0d2bf5719cbe86e3e92fb538393ab130b3f5ba2"}, + {file = "PennyLane_Lightning-0.40.0-py3-none-any.whl", hash = "sha256:96390ce82767b3a66c4f8e0b1bcec2f4f15fb317652cffa9815e371e74458197"}, + {file = "pennylane_lightning-0.40.0.tar.gz", hash = "sha256:edc95e75ef1b4e6dba96e914893175f9448d4d1b53a6e78614cb7587a8a22f2f"}, ] [package.dependencies] pennylane = ">=0.37" +scipy-openblas32 = ">=0.3.26" [package.extras] gpu = ["pennylane-lightning-gpu"] @@ -4808,93 +4762,89 @@ ptyprocess = ">=0.5" [[package]] name = "pillow" -version = "11.0.0" +version = "11.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" files = [ - {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, - {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, - {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, - {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, - {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, - {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, - {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, - {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, - {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, - {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, - {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, - {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, - {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, - {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, - {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, - {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, - {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, - {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, - {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, - {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, - {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, - {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, - {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, - {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, - {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, - {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, - {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, - {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, - {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, - {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, - {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, - {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, - {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, - {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, - {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, - {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, - {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, - {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, - {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, - {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"}, + {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"}, + {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"}, + {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"}, + {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"}, + {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"}, + {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"}, + {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"}, + {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"}, + {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"}, + {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"}, + {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"}, + {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"}, + {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"}, + {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"}, + {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"}, + {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"}, + {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"}, + {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"}, + {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"}, + {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] typing = ["typing-extensions"] xmp = ["defusedxml"] @@ -5312,13 +5262,13 @@ files = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, ] [package.dependencies] @@ -5444,13 +5394,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -5618,16 +5568,16 @@ files = [ [[package]] name = "pyscf" -version = "2.7.0" +version = "2.8.0" description = "PySCF: Python-based Simulations of Chemistry Framework" optional = false python-versions = "*" files = [ - {file = "pyscf-2.7.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:ec4b4356a2e1d801b92757235925efdde1ff6ecf99f0ec1d6d9c1307b5468d6f"}, - {file = "pyscf-2.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e76aee018da70ccd393a8a5f851a1738098c7289f4394ea840b441aaa692b470"}, - {file = "pyscf-2.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:486638182902963f67224de5e35dae28223b7e1df852c6613ce581259489364f"}, - {file = "pyscf-2.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b83a6685dceac36173a75fcd3366cf528527c5c9236bfa86845b9ebcc490d5a9"}, - {file = "pyscf-2.7.0.tar.gz", hash = "sha256:ca8efc2f28d72c3130f26a967e7fa8d0bbc4a6b47d16a7c4c732ec85a31b7eec"}, + {file = "pyscf-2.8.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:1de361d6abde3a758767845b3c03775aa7f2b86959ae657cea6609e76f0afc04"}, + {file = "pyscf-2.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:36f346bf60c16173f616b1f3969807c4263d08466fdf85befe8895a1bec01325"}, + {file = "pyscf-2.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1babdd355603d4bdca5ce1fed045c1b98045912ff485eabf968b21954ff5355e"}, + {file = "pyscf-2.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4baa59297de8a2992e8afa5b20a12164b6128c14ccc09431d05f73e3eefdf008"}, + {file = "pyscf-2.8.0.tar.gz", hash = "sha256:db720372e7f7d1aa2df0fb90c07f483da363730197c937a6378952d34b6abf3d"}, ] [package.dependencies] @@ -5637,8 +5587,9 @@ scipy = ">=1.6.0" setuptools = "*" [package.extras] -all = ["pyscf[bse,cppe,dispersion,doci,forge,geomopt,properties,pyqmc,semiempirical]"] +all = ["pyscf[bse,ccpy,cppe,dispersion,doci,forge,geomopt,properties,pyqmc,semiempirical]"] bse = ["basis-set-exchange"] +ccpy = ["coupled-cluster-py"] cornell-shci = ["pyscf-cornell-shci"] cppe = ["cppe"] dispersion = ["pyscf-dispersion"] @@ -5737,13 +5688,13 @@ cli = ["click (>=5.0)"] [[package]] name = "python-engineio" -version = "4.11.1" +version = "4.11.2" description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.6" files = [ - {file = "python_engineio-4.11.1-py3-none-any.whl", hash = "sha256:8ff9ec366724cd9b0fd92acf7a61b15ae923d28f37f842304adbd7f71b3d6672"}, - {file = "python_engineio-4.11.1.tar.gz", hash = "sha256:ff8a23a843c223ec793835f1bcf584ff89ce0f1c2bcce37dffa6436c6fa74133"}, + {file = "python_engineio-4.11.2-py3-none-any.whl", hash = "sha256:f0971ac4c65accc489154fe12efd88f53ca8caf04754c46a66e85f5102ef22ad"}, + {file = "python_engineio-4.11.2.tar.gz", hash = "sha256:145bb0daceb904b4bb2d3eb2d93f7dbb7bb87a6a0c4f20a94cc8654dec977129"}, ] [package.dependencies] @@ -5838,13 +5789,13 @@ files = [ [[package]] name = "python-socketio" -version = "5.12.0" +version = "5.12.1" description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_socketio-5.12.0-py3-none-any.whl", hash = "sha256:50fe22fd2b0aa634df3e74489e42217b09af2fb22eee45f2c006df36d1d08cb9"}, - {file = "python_socketio-5.12.0.tar.gz", hash = "sha256:39b55bff4ef6ac5c39b8bbc38fa61962e22e15349b038c1ca7ee2e18824e06dc"}, + {file = "python_socketio-5.12.1-py3-none-any.whl", hash = "sha256:24a0ea7cfff0e021eb28c68edbf7914ee4111bdf030b95e4d250c4dc9af7a386"}, + {file = "python_socketio-5.12.1.tar.gz", hash = "sha256:0299ff1f470b676c09c1bfab1dead25405077d227b2c13cf217a34dadc68ba9c"}, ] [package.dependencies] @@ -6150,51 +6101,16 @@ visualization = ["Pillow (>=4.2.1)", "matplotlib (>=3.3)", "pydot", "pylatexenc [[package]] name = "qiskit-aer" -version = "0.15.1" +version = "0.16.0" description = "Aer - High performance simulators for Qiskit" optional = false python-versions = ">=3.7" files = [ - {file = "qiskit-aer-0.15.1.tar.gz", hash = "sha256:45f320790c9239bbe781a1ee14a329a20ad08878f01746fe405c836d202b2560"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8c403b4895ac3f00fe55e72473b3f4e4fbc8840f93c75d4a33da5de4230dfef"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ad6cf30554cde3ae27850082c3673113385a5ee40b387557d306f35576c5d44"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02e2af134eb72bf3cde1fd959701655a392a53236d9bb9658278cba520a83aae"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5083333de4838da9436ceb76b6f964fb3184a8756561586bde03a4aa5fccf723"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d42260fad7c81d71a12870f2269a959e1c782bc72ba14c85cf107d87e53a13ce"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f07b6f937bde64cb88d037e8805cdd3b6e2985231ac7dd18f27a7af4aa653a2c"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-win32.whl", hash = "sha256:354dd010928cf2f72a92a133ff906c5d173262e6d25d06bb5823d869e2fded93"}, - {file = "qiskit_aer-0.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d5948c3f910a3f4b7e997ce8e80ca7376715b1f3556244da0c84bd7d2e4b081"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6613f1238fba954e744a16e10c61732765541fde42f17029038d0d96b78ba6ee"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:83198f4a7b9949008297675725e9fec01ba47e9d7eec3f755c3eb720aaf78932"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196c8de494ff26195ef6fb40f5c9672b6281ab3fd768dc1f1866e7b3968c4d98"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:334f8b323dd06793b11ad8aa8c7bcd56819e696017ce421db3cdc3c48f9da53e"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4cb7d606808d7b437b783d1d9ded20063ce86e463736b7d6201a93caccf050e"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabe19cfe9a93b76801da31e81b12671a301e0873d7eaf077d06c92e11394136"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-win32.whl", hash = "sha256:601ee3ad01a2aeef489f146ed0baf62965465b47324786ba88d80a1293740ac2"}, - {file = "qiskit_aer-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:8d8685f23b844352a3f8f2991adaba91a43515e8883cd1cbdc654b4c61d104a9"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df16643006cf25a1ed477a120b6146859f09e8dee09ca720befb3a1febee9546"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d22c96bae21dbe4b97c30785ead2c2b53f897938da49bca6b4ef29d187765a6"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1288fd5c36235f5fedfc228956049e87bdd804cbc2b3a487a4453d9e7e72f420"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0783607942c724329172e21b53354c9d569420e02dbfd06c407ed588833cf6"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbad79290e4b850dca163b7960769a1a8db6d44abf232ecf0a6ce88740c83ab9"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2257b4828df8cb3f37e153c220cd72f54a81d89875711efbc3ac2f265e0ae4a"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-win32.whl", hash = "sha256:0f8a3f97f1bbeabb7d229879f7a0b6b8709f864fbc13ae78ec1569a65033ea3b"}, - {file = "qiskit_aer-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:af6501808b584b764e959af7a1edb2ef890c9a78f1ce418921dbdf6fd09ce0fc"}, - {file = "qiskit_aer-0.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:adf939bfd5997043ce9910ffe9025b471f535df961ec58cf3de1627c6937eb2b"}, - {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d3442bd809ca825a3f94d39ec0a3a2d2b32518c20dba4b80d365aebbee455b8"}, - {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d346e8ee8df20fafb60158e843fac3c86f5b427ae5fc2fbfac9d48f99374abeb"}, - {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ff845bc0fd290e5ea931fc0f359016cc1a31de6cf5bc21618968db8f8c7b295"}, - {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91fbcdf34aa2dccc4424c7a3cae609b8b11cb6ee31bff33f4b54fd05f36d2f00"}, - {file = "qiskit_aer-0.15.1-cp38-cp38-win32.whl", hash = "sha256:45ef73adf280205e4a48b3be18b5d8d4e9d89ab5ac57a76daa58f6fa684c5c30"}, - {file = "qiskit_aer-0.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:4d32d6a90598e0ce529637622af077860ebc09d50b3b3ce0474a1659f9651f13"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0eb19c9cc3ff0293a6967ff0a36479383d1b15f5e20d4a63d01bc7804c62b580"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:00a26359b34bfe070549b3bf6b5a4c51d9cc16d47381a4f55bc886a55dd101f9"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f655025da0326bf2ac86757ab75db83922cdcfd67d062e345745fa9b1273aae"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a77fa5ff6c3f6210bc46de61b407c928ff3ed47c0ea6eabe94a5ba714eeff76"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad0c9982705a7bff81cd6edd157f9bbd907ab5256a6d6a3203a4a2759467ddc8"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdd2f74ac62b197a18b6846c3f1c90a85d6aa3daa7889ec380dfa3a10473627"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-win32.whl", hash = "sha256:347ce7c735b926a9cd9cb8e7cfa9446d7b46f0ea7f236ddb16d96445651f2fc1"}, - {file = "qiskit_aer-0.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:c97db2386e4236643c63b6ffa922aa7be8764746a6fd89158012d9947dabdcbb"}, + {file = "qiskit_aer-0.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd540c47f10d13b03f54003344aa55cc350ea5901780282f85dc01fc3131df39"}, + {file = "qiskit_aer-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d4be301e9252d8dce421e0a958e5dec254096564f967f3a93f7311ee9c311e1"}, + {file = "qiskit_aer-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60dcae3da0dbb158ff65438467c17bd5b71933c3a778c758b96614e43af5b4b6"}, + {file = "qiskit_aer-0.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fff96ccd3743f4b61f294953554775fcd2ea7030cf2d229b897ca53d4d5ddfe"}, + {file = "qiskit_aer-0.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4900214e33e6902a10669d60bfd0d0a7c82a61329b3a171273f494e295c9dc43"}, ] [package.dependencies] @@ -6728,29 +6644,29 @@ files = [ [[package]] name = "ruff" -version = "0.8.4" +version = "0.8.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60"}, - {file = "ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac"}, - {file = "ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf"}, - {file = "ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111"}, - {file = "ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8"}, - {file = "ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835"}, - {file = "ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d"}, - {file = "ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08"}, - {file = "ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8"}, + {file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"}, + {file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"}, + {file = "ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76"}, + {file = "ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764"}, + {file = "ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905"}, + {file = "ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162"}, + {file = "ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5"}, ] [[package]] @@ -6866,6 +6782,26 @@ dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyl doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "scipy-openblas32" +version = "0.3.28.0.2" +description = "Provides OpenBLAS for python packaging" +optional = false +python-versions = ">=3.7" +files = [ + {file = "scipy_openblas32-0.3.28.0.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:233f449a842a754a5154af399a6b4ea6ffb27457883339a6b324b1fc3e55cad8"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cb97169552004960ad6758d0c65998dbcd23bb4908e321381d288ed6c019280e"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b10b433d4c48c74ea1a3b88dae31447a1bae5597d04577a531fbc16f2bdcf84b"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:875f981d5024833775322ef2aae4aa0545283e856503326812e9dc1aeafaef14"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f0ecb748ce7dc7996220614100d7bf87fa0aab9499bbbdabbdcb6f5487a9e65"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:c4b6cf0067a6c62ef4b998a4cf59694cd1e13a7bf994346bc5dcddbf3e7a2a80"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a0597896ac1e0973b3da6ea15e03f2189ded49780d6571773698842072e66eb2"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:35051f9ee933ec642caafc1101b79a760bd536597e486e0168a7700afec256b0"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea226ce2ffd8a5bfc020aa8d80e1013c758a8032e87588681b04cd25d1b1fd48"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-win32.whl", hash = "sha256:e6595e950df5584fe9fdf5f483b321432e14c7792d61f0cf3d6bf2bc4d2c39be"}, + {file = "scipy_openblas32-0.3.28.0.2-py3-none-win_amd64.whl", hash = "sha256:7a6c0618760084278eeda0ea0d1d7ca77eebbc4fce9f0a0b4fef82000e5ec01b"}, +] + [[package]] name = "seaborn" version = "0.13.2" @@ -6919,23 +6855,23 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "setuptools" -version = "75.6.0" +version = "75.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" files = [ - {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, - {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, + {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, + {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "shellingham" @@ -8202,13 +8138,13 @@ tutorials = ["matplotlib", "pandas", "tabulate"] [[package]] name = "trove-classifiers" -version = "2024.10.21.16" +version = "2025.1.15.22" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" files = [ - {file = "trove_classifiers-2024.10.21.16-py3-none-any.whl", hash = "sha256:0fb11f1e995a757807a8ef1c03829fbd4998d817319abcef1f33165750f103be"}, - {file = "trove_classifiers-2024.10.21.16.tar.gz", hash = "sha256:17cbd055d67d5e9d9de63293a8732943fabc21574e4c7b74edf112b4928cf5f3"}, + {file = "trove_classifiers-2025.1.15.22-py3-none-any.whl", hash = "sha256:5f19c789d4f17f501d36c94dbbf969fb3e8c2784d008e6f5164dd2c3d6a2b07c"}, + {file = "trove_classifiers-2025.1.15.22.tar.gz", hash = "sha256:90af74358d3a01b3532bc7b3c88d8c6a094c2fd50a563d13d9576179326d7ed9"}, ] [[package]] @@ -8252,13 +8188,13 @@ files = [ [[package]] name = "types-retry" -version = "0.9.9.4" +version = "0.9.9.20241221" description = "Typing stubs for retry" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-retry-0.9.9.4.tar.gz", hash = "sha256:e4731dc684b56b875d9746459ad665d3bc281a56b530acdf1c97730167799941"}, - {file = "types_retry-0.9.9.4-py3-none-any.whl", hash = "sha256:f29760a9fe8b1fefe253e5fe6be7e4c0eba243932c600e0eccffb42a21d17765"}, + {file = "types_retry-0.9.9.20241221-py3-none-any.whl", hash = "sha256:d1ef1a60573470525e65267192dd712b93f0f0acf3019c4c1afe173cde3289cb"}, + {file = "types_retry-0.9.9.20241221.tar.gz", hash = "sha256:ebad6d495a5a04ab0d06d4156a665528c3b84a8461aa019dd6e5d3e33c2aa1e0"}, ] [[package]] @@ -8299,13 +8235,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "2.2.3" +version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] @@ -8392,13 +8328,13 @@ test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", [[package]] name = "virtualenv" -version = "20.28.0" +version = "20.29.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, - {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, + {file = "virtualenv-20.29.0-py3-none-any.whl", hash = "sha256:c12311863497992dc4b8644f8ea82d3b35bb7ef8ee82e6630d76d0197c39baf9"}, + {file = "virtualenv-20.29.0.tar.gz", hash = "sha256:6345e1ff19d4b1296954cee076baaf58ff2a12a84a338c62b02eda39f20aa982"}, ] [package.dependencies] @@ -8454,82 +8390,82 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "watchfiles" -version = "1.0.3" +version = "1.0.4" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.9" files = [ - {file = "watchfiles-1.0.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1da46bb1eefb5a37a8fb6fd52ad5d14822d67c498d99bda8754222396164ae42"}, - {file = "watchfiles-1.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2b961b86cd3973f5822826017cad7f5a75795168cb645c3a6b30c349094e02e3"}, - {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34e87c7b3464d02af87f1059fedda5484e43b153ef519e4085fe1a03dd94801e"}, - {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9dd2b89a16cf7ab9c1170b5863e68de6bf83db51544875b25a5f05a7269e678"}, - {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b4691234d31686dca133c920f94e478b548a8e7c750f28dbbc2e4333e0d3da9"}, - {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90b0fe1fcea9bd6e3084b44875e179b4adcc4057a3b81402658d0eb58c98edf8"}, - {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b90651b4cf9e158d01faa0833b073e2e37719264bcee3eac49fc3c74e7d304b"}, - {file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2e9fe695ff151b42ab06501820f40d01310fbd58ba24da8923ace79cf6d702d"}, - {file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62691f1c0894b001c7cde1195c03b7801aaa794a837bd6eef24da87d1542838d"}, - {file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:275c1b0e942d335fccb6014d79267d1b9fa45b5ac0639c297f1e856f2f532552"}, - {file = "watchfiles-1.0.3-cp310-cp310-win32.whl", hash = "sha256:06ce08549e49ba69ccc36fc5659a3d0ff4e3a07d542b895b8a9013fcab46c2dc"}, - {file = "watchfiles-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f280b02827adc9d87f764972fbeb701cf5611f80b619c20568e1982a277d6146"}, - {file = "watchfiles-1.0.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ffe709b1d0bc2e9921257569675674cafb3a5f8af689ab9f3f2b3f88775b960f"}, - {file = "watchfiles-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:418c5ce332f74939ff60691e5293e27c206c8164ce2b8ce0d9abf013003fb7fe"}, - {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f492d2907263d6d0d52f897a68647195bc093dafed14508a8d6817973586b6b"}, - {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48c9f3bc90c556a854f4cab6a79c16974099ccfa3e3e150673d82d47a4bc92c9"}, - {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75d3bcfa90454dba8df12adc86b13b6d85fda97d90e708efc036c2760cc6ba44"}, - {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5691340f259b8f76b45fb31b98e594d46c36d1dc8285efa7975f7f50230c9093"}, - {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e263cc718545b7f897baeac1f00299ab6fabe3e18caaacacb0edf6d5f35513c"}, - {file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c6cf7709ed3e55704cc06f6e835bf43c03bc8e3cb8ff946bf69a2e0a78d9d77"}, - {file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:703aa5e50e465be901e0e0f9d5739add15e696d8c26c53bc6fc00eb65d7b9469"}, - {file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bfcae6aecd9e0cb425f5145afee871465b98b75862e038d42fe91fd753ddd780"}, - {file = "watchfiles-1.0.3-cp311-cp311-win32.whl", hash = "sha256:6a76494d2c5311584f22416c5a87c1e2cb954ff9b5f0988027bc4ef2a8a67181"}, - {file = "watchfiles-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:cf745cbfad6389c0e331786e5fe9ae3f06e9d9c2ce2432378e1267954793975c"}, - {file = "watchfiles-1.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:2dcc3f60c445f8ce14156854a072ceb36b83807ed803d37fdea2a50e898635d6"}, - {file = "watchfiles-1.0.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:93436ed550e429da007fbafb723e0769f25bae178fbb287a94cb4ccdf42d3af3"}, - {file = "watchfiles-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c18f3502ad0737813c7dad70e3e1cc966cc147fbaeef47a09463bbffe70b0a00"}, - {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5bc3ca468bb58a2ef50441f953e1f77b9a61bd1b8c347c8223403dc9b4ac9a"}, - {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d1ec043f02ca04bf21b1b32cab155ce90c651aaf5540db8eb8ad7f7e645cba8"}, - {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f58d3bfafecf3d81c15d99fc0ecf4319e80ac712c77cf0ce2661c8cf8bf84066"}, - {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1df924ba82ae9e77340101c28d56cbaff2c991bd6fe8444a545d24075abb0a87"}, - {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:632a52dcaee44792d0965c17bdfe5dc0edad5b86d6a29e53d6ad4bf92dc0ff49"}, - {file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bf4b459d94a0387617a1b499f314aa04d8a64b7a0747d15d425b8c8b151da0"}, - {file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca94c85911601b097d53caeeec30201736ad69a93f30d15672b967558df02885"}, - {file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65ab1fb635476f6170b07e8e21db0424de94877e4b76b7feabfe11f9a5fc12b5"}, - {file = "watchfiles-1.0.3-cp312-cp312-win32.whl", hash = "sha256:49bc1bc26abf4f32e132652f4b3bfeec77d8f8f62f57652703ef127e85a3e38d"}, - {file = "watchfiles-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:48681c86f2cb08348631fed788a116c89c787fdf1e6381c5febafd782f6c3b44"}, - {file = "watchfiles-1.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:9e080cf917b35b20c889225a13f290f2716748362f6071b859b60b8847a6aa43"}, - {file = "watchfiles-1.0.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e153a690b7255c5ced17895394b4f109d5dcc2a4f35cb809374da50f0e5c456a"}, - {file = "watchfiles-1.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac1be85fe43b4bf9a251978ce5c3bb30e1ada9784290441f5423a28633a958a7"}, - {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec98e31e1844eac860e70d9247db9d75440fc8f5f679c37d01914568d18721"}, - {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0179252846be03fa97d4d5f8233d1c620ef004855f0717712ae1c558f1974a16"}, - {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:995c374e86fa82126c03c5b4630c4e312327ecfe27761accb25b5e1d7ab50ec8"}, - {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b9cb35b7f290db1c31fb2fdf8fc6d3730cfa4bca4b49761083307f441cac5a"}, - {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f8dc09ae69af50bead60783180f656ad96bd33ffbf6e7a6fce900f6d53b08f1"}, - {file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:489b80812f52a8d8c7b0d10f0d956db0efed25df2821c7a934f6143f76938bd6"}, - {file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:228e2247de583475d4cebf6b9af5dc9918abb99d1ef5ee737155bb39fb33f3c0"}, - {file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1550be1a5cb3be08a3fb84636eaafa9b7119b70c71b0bed48726fd1d5aa9b868"}, - {file = "watchfiles-1.0.3-cp313-cp313-win32.whl", hash = "sha256:16db2d7e12f94818cbf16d4c8938e4d8aaecee23826344addfaaa671a1527b07"}, - {file = "watchfiles-1.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:160eff7d1267d7b025e983ca8460e8cc67b328284967cbe29c05f3c3163711a3"}, - {file = "watchfiles-1.0.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c05b021f7b5aa333124f2a64d56e4cb9963b6efdf44e8d819152237bbd93ba15"}, - {file = "watchfiles-1.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:310505ad305e30cb6c5f55945858cdbe0eb297fc57378f29bacceb534ac34199"}, - {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddff3f8b9fa24a60527c137c852d0d9a7da2a02cf2151650029fdc97c852c974"}, - {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46e86ed457c3486080a72bc837300dd200e18d08183f12b6ca63475ab64ed651"}, - {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f79fe7993e230a12172ce7d7c7db061f046f672f2b946431c81aff8f60b2758b"}, - {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea2b51c5f38bad812da2ec0cd7eec09d25f521a8b6b6843cbccedd9a1d8a5c15"}, - {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fe4e740ea94978b2b2ab308cbf9270a246bcbb44401f77cc8740348cbaeac3d"}, - {file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af037d3df7188ae21dc1c7624501f2f90d81be6550904e07869d8d0e6766655"}, - {file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52bb50a4c4ca2a689fdba84ba8ecc6a4e6210f03b6af93181bb61c4ec3abaf86"}, - {file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c14a07bdb475eb696f85c715dbd0f037918ccbb5248290448488a0b4ef201aad"}, - {file = "watchfiles-1.0.3-cp39-cp39-win32.whl", hash = "sha256:be37f9b1f8934cd9e7eccfcb5612af9fb728fecbe16248b082b709a9d1b348bf"}, - {file = "watchfiles-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ef9ec8068cf23458dbf36a08e0c16f0a2df04b42a8827619646637be1769300a"}, - {file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:84fac88278f42d61c519a6c75fb5296fd56710b05bbdcc74bdf85db409a03780"}, - {file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c68be72b1666d93b266714f2d4092d78dc53bd11cf91ed5a3c16527587a52e29"}, - {file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889a37e2acf43c377b5124166bece139b4c731b61492ab22e64d371cce0e6e80"}, - {file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca05cacf2e5c4a97d02a2878a24020daca21dbb8823b023b978210a75c79098"}, - {file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8af4b582d5fc1b8465d1d2483e5e7b880cc1a4e99f6ff65c23d64d070867ac58"}, - {file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:127de3883bdb29dbd3b21f63126bb8fa6e773b74eaef46521025a9ce390e1073"}, - {file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713f67132346bdcb4c12df185c30cf04bdf4bf6ea3acbc3ace0912cab6b7cb8c"}, - {file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abd85de513eb83f5ec153a802348e7a5baa4588b818043848247e3e8986094e8"}, - {file = "watchfiles-1.0.3.tar.gz", hash = "sha256:f3ff7da165c99a5412fe5dd2304dd2dbaaaa5da718aad942dcb3a178eaa70c56"}, + {file = "watchfiles-1.0.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ba5bb3073d9db37c64520681dd2650f8bd40902d991e7b4cfaeece3e32561d08"}, + {file = "watchfiles-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f25d0ba0fe2b6d2c921cf587b2bf4c451860086534f40c384329fb96e2044d1"}, + {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47eb32ef8c729dbc4f4273baece89398a4d4b5d21a1493efea77a17059f4df8a"}, + {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:076f293100db3b0b634514aa0d294b941daa85fc777f9c698adb1009e5aca0b1"}, + {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1eacd91daeb5158c598fe22d7ce66d60878b6294a86477a4715154990394c9b3"}, + {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13c2ce7b72026cfbca120d652f02c7750f33b4c9395d79c9790b27f014c8a5a2"}, + {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90192cdc15ab7254caa7765a98132a5a41471cf739513cc9bcf7d2ffcc0ec7b2"}, + {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278aaa395f405972e9f523bd786ed59dfb61e4b827856be46a42130605fd0899"}, + {file = "watchfiles-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a462490e75e466edbb9fc4cd679b62187153b3ba804868452ef0577ec958f5ff"}, + {file = "watchfiles-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8d0d0630930f5cd5af929040e0778cf676a46775753e442a3f60511f2409f48f"}, + {file = "watchfiles-1.0.4-cp310-cp310-win32.whl", hash = "sha256:cc27a65069bcabac4552f34fd2dce923ce3fcde0721a16e4fb1b466d63ec831f"}, + {file = "watchfiles-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:8b1f135238e75d075359cf506b27bf3f4ca12029c47d3e769d8593a2024ce161"}, + {file = "watchfiles-1.0.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2a9f93f8439639dc244c4d2902abe35b0279102bca7bbcf119af964f51d53c19"}, + {file = "watchfiles-1.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eea33ad8c418847dd296e61eb683cae1c63329b6d854aefcd412e12d94ee235"}, + {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31f1a379c9dcbb3f09cf6be1b7e83b67c0e9faabed0471556d9438a4a4e14202"}, + {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab594e75644421ae0a2484554832ca5895f8cab5ab62de30a1a57db460ce06c6"}, + {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc2eb5d14a8e0d5df7b36288979176fbb39672d45184fc4b1c004d7c3ce29317"}, + {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f68d8e9d5a321163ddacebe97091000955a1b74cd43724e346056030b0bacee"}, + {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9ce064e81fe79faa925ff03b9f4c1a98b0bbb4a1b8c1b015afa93030cb21a49"}, + {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b77d5622ac5cc91d21ae9c2b284b5d5c51085a0bdb7b518dba263d0af006132c"}, + {file = "watchfiles-1.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1941b4e39de9b38b868a69b911df5e89dc43767feeda667b40ae032522b9b5f1"}, + {file = "watchfiles-1.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f8c4998506241dedf59613082d1c18b836e26ef2a4caecad0ec41e2a15e4226"}, + {file = "watchfiles-1.0.4-cp311-cp311-win32.whl", hash = "sha256:4ebbeca9360c830766b9f0df3640b791be569d988f4be6c06d6fae41f187f105"}, + {file = "watchfiles-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:05d341c71f3d7098920f8551d4df47f7b57ac5b8dad56558064c3431bdfc0b74"}, + {file = "watchfiles-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:32b026a6ab64245b584acf4931fe21842374da82372d5c039cba6bf99ef722f3"}, + {file = "watchfiles-1.0.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:229e6ec880eca20e0ba2f7e2249c85bae1999d330161f45c78d160832e026ee2"}, + {file = "watchfiles-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5717021b199e8353782dce03bd8a8f64438832b84e2885c4a645f9723bf656d9"}, + {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0799ae68dfa95136dde7c472525700bd48777875a4abb2ee454e3ab18e9fc712"}, + {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43b168bba889886b62edb0397cab5b6490ffb656ee2fcb22dec8bfeb371a9e12"}, + {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb2c46e275fbb9f0c92e7654b231543c7bbfa1df07cdc4b99fa73bedfde5c844"}, + {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:857f5fc3aa027ff5e57047da93f96e908a35fe602d24f5e5d8ce64bf1f2fc733"}, + {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55ccfd27c497b228581e2838d4386301227fc0cb47f5a12923ec2fe4f97b95af"}, + {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c11ea22304d17d4385067588123658e9f23159225a27b983f343fcffc3e796a"}, + {file = "watchfiles-1.0.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:74cb3ca19a740be4caa18f238298b9d472c850f7b2ed89f396c00a4c97e2d9ff"}, + {file = "watchfiles-1.0.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7cce76c138a91e720d1df54014a047e680b652336e1b73b8e3ff3158e05061e"}, + {file = "watchfiles-1.0.4-cp312-cp312-win32.whl", hash = "sha256:b045c800d55bc7e2cadd47f45a97c7b29f70f08a7c2fa13241905010a5493f94"}, + {file = "watchfiles-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:c2acfa49dd0ad0bf2a9c0bb9a985af02e89345a7189be1efc6baa085e0f72d7c"}, + {file = "watchfiles-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:22bb55a7c9e564e763ea06c7acea24fc5d2ee5dfc5dafc5cfbedfe58505e9f90"}, + {file = "watchfiles-1.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9"}, + {file = "watchfiles-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60"}, + {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407"}, + {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d"}, + {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d"}, + {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b"}, + {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590"}, + {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902"}, + {file = "watchfiles-1.0.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1"}, + {file = "watchfiles-1.0.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303"}, + {file = "watchfiles-1.0.4-cp313-cp313-win32.whl", hash = "sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80"}, + {file = "watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc"}, + {file = "watchfiles-1.0.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d3452c1ec703aa1c61e15dfe9d482543e4145e7c45a6b8566978fbb044265a21"}, + {file = "watchfiles-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b75fee5a16826cf5c46fe1c63116e4a156924d668c38b013e6276f2582230f0"}, + {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e997802d78cdb02623b5941830ab06f8860038faf344f0d288d325cc9c5d2ff"}, + {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0611d244ce94d83f5b9aff441ad196c6e21b55f77f3c47608dcf651efe54c4a"}, + {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9745a4210b59e218ce64c91deb599ae8775c8a9da4e95fb2ee6fe745fc87d01a"}, + {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4810ea2ae622add560f4aa50c92fef975e475f7ac4900ce5ff5547b2434642d8"}, + {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d103cd01458f22462dedeb5a3382b7f2c57d07ff033fbc9465919e5e1d0f3"}, + {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbd912a61543a36aef85e34f212e5d2486e7c53ebfdb70d1e0b060cc50dd0bf"}, + {file = "watchfiles-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0bc80d91ddaf95f70258cf78c471246846c1986bcc5fd33ccc4a1a67fcb40f9a"}, + {file = "watchfiles-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab0311bb2ffcd9f74b6c9de2dda1612c13c84b996d032cd74799adb656af4e8b"}, + {file = "watchfiles-1.0.4-cp39-cp39-win32.whl", hash = "sha256:02a526ee5b5a09e8168314c905fc545c9bc46509896ed282aeb5a8ba9bd6ca27"}, + {file = "watchfiles-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:a5ae5706058b27c74bac987d615105da17724172d5aaacc6c362a40599b6de43"}, + {file = "watchfiles-1.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdcc92daeae268de1acf5b7befcd6cfffd9a047098199056c72e4623f531de18"}, + {file = "watchfiles-1.0.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8d3d9203705b5797f0af7e7e5baa17c8588030aaadb7f6a86107b7247303817"}, + {file = "watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdef5a1be32d0b07dcea3318a0be95d42c98ece24177820226b56276e06b63b0"}, + {file = "watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342622287b5604ddf0ed2d085f3a589099c9ae8b7331df3ae9845571586c4f3d"}, + {file = "watchfiles-1.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9fe37a2de80aa785d340f2980276b17ef697ab8db6019b07ee4fd28a8359d2f3"}, + {file = "watchfiles-1.0.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9d1ef56b56ed7e8f312c934436dea93bfa3e7368adfcf3df4c0da6d4de959a1e"}, + {file = "watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b42cac65beae3a362629950c444077d1b44f1790ea2772beaea95451c086bb"}, + {file = "watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e0227b8ed9074c6172cf55d85b5670199c99ab11fd27d2c473aa30aec67ee42"}, + {file = "watchfiles-1.0.4.tar.gz", hash = "sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205"}, ] [package.dependencies] @@ -8798,69 +8734,81 @@ sphinx-gallery = "*" [[package]] name = "xattr" -version = "1.1.0" +version = "1.1.4" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" files = [ - {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef2fa0f85458736178fd3dcfeb09c3cf423f0843313e25391db2cfd1acec8888"}, - {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccab735d0632fe71f7d72e72adf886f45c18b7787430467ce0070207882cfe25"}, - {file = "xattr-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9013f290387f1ac90bccbb1926555ca9aef75651271098d99217284d9e010f7c"}, - {file = "xattr-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcd5dfbcee73c7be057676ecb900cabb46c691aff4397bf48c579ffb30bb963"}, - {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6480589c1dac7785d1f851347a32c4a97305937bf7b488b857fe8b28a25de9e9"}, - {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08f61cbed52dc6f7c181455826a9ff1e375ad86f67dd9d5eb7663574abb32451"}, - {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:918e1f83f2e8a072da2671eac710871ee5af337e9bf8554b5ce7f20cdb113186"}, - {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0f06e0c1e4d06b4e0e49aaa1184b6f0e81c3758c2e8365597918054890763b53"}, - {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a641ac038a9f53d2f696716147ca4dbd6a01998dc9cd4bc628801bc0df7f4d"}, - {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e4ca0956fd11679bb2e0c0d6b9cdc0f25470cc00d8da173bb7656cc9a9cf104"}, - {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6881b120f9a4b36ccd8a28d933bc0f6e1de67218b6ce6e66874e0280fc006844"}, - {file = "xattr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab29d9288aa28e68a6f355ddfc3f0a7342b40c9012798829f3e7bd765e85c2c"}, - {file = "xattr-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c80bbf55339c93770fc294b4b6586b5bf8e85ec00a4c2d585c33dbd84b5006"}, - {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1418705f253b6b6a7224b69773842cac83fcbcd12870354b6e11dd1cd54630f"}, - {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687e7d18611ef8d84a6ecd8f4d1ab6757500c1302f4c2046ce0aa3585e13da3f"}, - {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6ceb9efe0657a982ccb8b8a2efe96b690891779584c901d2f920784e5d20ae3"}, - {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b489b7916f239100956ea0b39c504f3c3a00258ba65677e4c8ba1bd0b5513446"}, - {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0a9c431b0e66516a078125e9a273251d4b8e5ba84fe644b619f2725050d688a0"}, - {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1a5921ea3313cc1c57f2f53b63ea8ca9a91e48f4cc7ebec057d2447ec82c7efe"}, - {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6ad2a7bd5e6cf71d4a862413234a067cf158ca0ae94a40d4b87b98b62808498"}, - {file = "xattr-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0683dae7609f7280b0c89774d00b5957e6ffcb181c6019c46632b389706b77e6"}, - {file = "xattr-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cb15cd94e5ef8a0ef02309f1bf973ba0e13c11e87686e983f371948cfee6af"}, - {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff6223a854229055e803c2ad0c0ea9a6da50c6be30d92c198cf5f9f28819a921"}, - {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d44e8f955218638c9ab222eed21e9bd9ab430d296caf2176fb37abe69a714e5c"}, - {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:caab2c2986c30f92301f12e9c50415d324412e8e6a739a52a603c3e6a54b3610"}, - {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d6eb7d5f281014cd44e2d847a9107491af1bf3087f5afeded75ed3e37ec87239"}, - {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:47a3bdfe034b4fdb70e5941d97037405e3904accc28e10dbef6d1c9061fb6fd7"}, - {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00d2b415cf9d6a24112d019e721aa2a85652f7bbc9f3b9574b2d1cd8668eb491"}, - {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:78b377832dd0ee408f9f121a354082c6346960f7b6b1480483ed0618b1912120"}, - {file = "xattr-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6461a43b585e5f2e049b39bcbfcb6391bfef3c5118231f1b15d10bdb89ef17fe"}, - {file = "xattr-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24d97f0d28f63695e3344ffdabca9fcc30c33e5c8ccc198c7524361a98d526f2"}, - {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad47d89968c9097900607457a0c89160b4771601d813e769f68263755516065"}, - {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc53cab265f6e8449bd683d5ee3bc5a191e6dd940736f3de1a188e6da66b0653"}, - {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cd11e917f5b89f2a0ad639d9875943806c6c9309a3dd02da5a3e8ef92db7bed9"}, - {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9c5a78c7558989492c4cb7242e490ffb03482437bf782967dfff114e44242343"}, - {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cebcf8a303a44fbc439b68321408af7267507c0d8643229dbb107f6c132d389c"}, - {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b0d73150f2f9655b4da01c2369eb33a294b7f9d56eccb089819eafdbeb99f896"}, - {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:793c01deaadac50926c0e1481702133260c7cb5e62116762f6fe1543d07b826f"}, - {file = "xattr-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e189e440bcd04ccaad0474720abee6ee64890823ec0db361fb0a4fb5e843a1bf"}, - {file = "xattr-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afacebbc1fa519f41728f8746a92da891c7755e6745164bd0d5739face318e86"}, - {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b1664edf003153ac8d1911e83a0fc60db1b1b374ee8ac943f215f93754a1102"}, - {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda2684228798e937a7c29b0e1c7ef3d70e2b85390a69b42a1c61b2039ba81de"}, - {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b735ac2625a4fc2c9343b19f806793db6494336338537d2911c8ee4c390dda46"}, - {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fa6a7af7a4ada43f15ccc58b6f9adcdbff4c36ba040013d2681e589e07ae280a"}, - {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1059b2f726e2702c8bbf9bbf369acfc042202a4cc576c2dec6791234ad5e948"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2255f36ebf2cb2dbf772a7437ad870836b7396e60517211834cf66ce678b595"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba4f80b9855cc98513ddf22b7ad8551bc448c70d3147799ea4f6c0b758fb466"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb70c16e7c3ae6ba0ab6c6835c8448c61d8caf43ea63b813af1f4dbe83dd156"}, - {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83652910ef6a368b77b00825ad67815e5c92bfab551a848ca66e9981d14a7519"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a92aff66c43fa3e44cbeab7cbeee66266c91178a0f595e044bf3ce51485743b"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f71b673339aeaae1f6ea9ef8ea6c9643c8cd0df5003b9a0eaa75403e2e06c"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a20de1c47b5cd7b47da61799a3b34e11e5815d716299351f82a88627a43f9a96"}, - {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23705c7079b05761ff2fa778ad17396e7599c8759401abc05b312dfb3bc99f69"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27272afeba8422f2a9d27e1080a9a7b807394e88cce73db9ed8d2dde3afcfb87"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd43978966de3baf4aea367c99ffa102b289d6c2ea5f3d9ce34a203dc2f2ab73"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded771eaf27bb4eb3c64c0d09866460ee8801d81dc21097269cf495b3cac8657"}, - {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca300c0acca4f0cddd2332bb860ef58e1465d376364f0e72a1823fdd58e90d"}, - {file = "xattr-1.1.0.tar.gz", hash = "sha256:fecbf3b05043ed3487a28190dec3e4c4d879b2fcec0e30bafd8ec5d4b6043630"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb85b6249e9f3ea10cbb56df1021d43f4027212f0d004304bc9075dc7f54769"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a848ab125c0fafdc501ccd83b4c9018bba576a037a4ca5960a22f39e295552e"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:467ee77471d26ae5187ee7081b82175b5ca56ead4b71467ec2e6119d1b08beed"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd35f46cb0154f7033f9d5d0960f226857acb0d1e0d71fd7af18ed84663007c"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d956478e9bb98a1efd20ebc6e5703497c1d2d690d5a13c4df4abf59881eed50"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f25dfdcd974b700fb04a40e14a664a80227ee58e02ea062ac241f0d7dc54b4e"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33b63365c1fcbc80a79f601575bac0d6921732e0245b776876f3db3fcfefe22d"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:544542be95c9b49e211f0a463758f200de88ba6d5a94d3c4f42855a484341acd"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac14c9893f3ea046784b7702be30889b200d31adcd2e6781a8a190b6423f9f2d"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bb4bbe37ba95542081890dd34fa5347bef4651e276647adaa802d5d0d7d86452"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3da489ecef798705f9a39ea8cea4ead0d1eeed55f92c345add89740bd930bab6"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:798dd0cbe696635a6f74b06fc430818bf9c3b24314e1502eadf67027ab60c9b0"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2b6361626efad5eb5a6bf8172c6c67339e09397ee8140ec41258737bea9681"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7fa20a0c9ce022d19123b1c5b848d00a68b837251835a7929fe041ee81dcd0"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20eeb08e2c57fc7e71f050b1cfae35cbb46105449853a582bf53fd23c5379e"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:477370e75821bded901487e5e752cffe554d1bd3bd4839b627d4d1ee8c95a093"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a8682091cd34a9f4a93c8aaea4101aae99f1506e24da00a3cc3dd2eca9566f21"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2e079b3b1a274ba2121cf0da38bbe5c8d2fb1cc49ecbceb395ce20eb7d69556d"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ae6579dea05bf9f335a082f711d5924a98da563cac72a2d550f5b940c401c0e9"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd6038ec9df2e67af23c212693751481d5f7e858156924f14340376c48ed9ac7"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:608b2877526674eb15df4150ef4b70b7b292ae00e65aecaae2f192af224be200"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54dad1a6a998c6a23edfd25e99f4d38e9b942d54e518570044edf8c767687ea"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0dab6ff72bb2b508f3850c368f8e53bd706585012676e1f71debba3310acde8"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3c54c6af7cf09432b2c461af257d5f4b1cb2d59eee045f91bacef44421a46d"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e346e05a158d554639fbf7a0db169dc693c2d2260c7acb3239448f1ff4a9d67f"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3ff6d9e2103d0d6e5fcd65b85a2005b66ea81c0720a37036445faadc5bbfa424"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a2ee4563c6414dfec0d1ac610f59d39d5220531ae06373eeb1a06ee37cd193f"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878df1b38cfdadf3184ad8c7b0f516311128d5597b60ac0b3486948953658a83"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c9b8350244a1c5454f93a8d572628ff71d7e2fc2f7480dcf4c4f0e8af3150fe"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a46bf48fb662b8bd745b78bef1074a1e08f41a531168de62b5d7bd331dadb11a"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83fc3c07b583777b1dda6355329f75ca6b7179fe0d1002f1afe0ef96f7e3b5de"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6308b19cff71441513258699f0538394fad5d66e1d324635207a97cb076fd439"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48c00ddc15ddadc9c729cd9504dabf50adb3d9c28f647d4ac9a3df45a046b1a0"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a06136196f26293758e1b244200b73156a0274af9a7349fa201c71c7af3bb9e8"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8fc2631a3c6cfcdc71f7f0f847461839963754e76a2015de71e7e71e3304abc0"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6e1e835f9c938d129dd45e7eb52ebf7d2d6816323dab93ce311bf331f7d2328"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60dea2d369a6484e8b7136224fc2971e10e2c46340d83ab780924afe78c90066"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85c2b778b09d919523f80f244d799a142302582d76da18903dc693207c4020b0"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ee0abba9e1b890d39141714ff43e9666864ca635ea8a5a2194d989e6b17fe862"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e4174ba7f51f46b95ea7918d907c91cd579575d59e6a2f22ca36a0551026737"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2b05e52e99d82d87528c54c2c5c8c5fb0ba435f85ac6545511aeea136e49925"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a3696fad746be37de34eb73c60ea67144162bd08106a5308a90ce9dea9a3287"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3a7149439a26b68904c14fdc4587cde4ac7d80303e9ff0fefcfd893b698c976"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:507b36a126ce900dbfa35d4e2c2db92570c933294cba5d161ecd6a89f7b52f43"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9392b417b54923e031041940d396b1d709df1d3779c6744454e1f1c1f4dad4f5"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e9f00315e6c02943893b77f544776b49c756ac76960bea7cb8d7e1b96aefc284"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8f98775065260140efb348b1ff8d50fd66ddcbf0c685b76eb1e87b380aaffb3"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b471c6a515f434a167ca16c5c15ff34ee42d11956baa749173a8a4e385ff23e7"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee0763a1b7ceb78ba2f78bee5f30d1551dc26daafcce4ac125115fa1def20519"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:099e6e9ce7999b403d36d9cf943105a3d25d8233486b54ec9d1b78623b050433"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e56faef9dde8d969f0d646fb6171883693f88ae39163ecd919ec707fbafa85"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:328156d4e594c9ae63e1072503c168849e601a153ad37f0290743544332d6b6f"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a57a55a27c7864d6916344c9a91776afda6c3b8b2209f8a69b79cdba93fbe128"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c19cdde08b040df1e99d2500bf8a9cff775ab0e6fa162bf8afe6d84aa93ed04"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c72667f19d3a9acf324aed97f58861d398d87e42314731e7c6ab3ac7850c971"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:67ae934d75ea2563fc48a27c5945749575c74a6de19fdd38390917ddcb0e4f24"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1b0c348dd8523554dc535540d2046c0c8a535bb086561d8359f3667967b6ca"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22284255d2a8e8f3da195bd8e8d43ce674dbc7c38d38cb6ecfb37fae7755d31f"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38aac5ef4381c26d3ce147ca98fba5a78b1e5bcd6be6755b4908659f2705c6d"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:803f864af528f6f763a5be1e7b1ccab418e55ae0e4abc8bda961d162f850c991"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:40354ebfb5cecd60a5fbb9833a8a452d147486b0ffec547823658556625d98b5"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2abaf5d06be3361bfa8e0db2ee123ba8e92beab5bceed5e9d7847f2145a32e04"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e638e5ffedc3565242b5fa3296899d35161bad771f88d66277b58f03a1ba9fe"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0597e919d116ec39997804288d77bec3777228368efc0f2294b84a527fc4f9c2"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee9455c501d19f065527afda974418b3ef7c61e85d9519d122cd6eb3cb7a00"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:89ed62ce430f5789e15cfc1ccabc172fd8b349c3a17c52d9e6c64ecedf08c265"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b824f4b9259cd8bb6e83c4873cf8bf080f6e4fa034a02fe778e07aba8d345"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fba66faa0016dfc0af3dd7ac5782b5786a1dfb851f9f3455e266f94c2a05a04"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ec4b0c3e0a7bcd103f3cf31dd40c349940b2d4223ce43d384a3548992138ef1"}, + {file = "xattr-1.1.4.tar.gz", hash = "sha256:b7b02ecb2270da5b7e7deaeea8f8b528c17368401c2b9d5f63e91f545b45d372"}, ] [package.dependencies] @@ -8997,101 +8945,92 @@ type = ["pytest-mypy"] [[package]] name = "zstd" -version = "1.5.5.1" +version = "1.5.6.1" description = "ZSTD Bindings for Python" optional = false python-versions = "*" files = [ - {file = "zstd-1.5.5.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:555779789bc75cd05089c3ba857f45a0a8c4b87d45e5ced02fec77fa8719237a"}, - {file = "zstd-1.5.5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:86496bd4830cdb7b4b05a9ce6ce2baee87d327ff90845da4ee308452bfbbed4e"}, - {file = "zstd-1.5.5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b487c2e67ed42a4e0d47997d209f4456b01b334023083ef61873f79577c84c62"}, - {file = "zstd-1.5.5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:45ccd45a5b681088fca1a863ca9236ded5112b8011f1d5bf69e908f5eb32023a"}, - {file = "zstd-1.5.5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8403fe84207d8b0c7b17bca6c4caad431ac765b1b9b626ad9fae4bb93a64a9d8"}, - {file = "zstd-1.5.5.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:0ab979c6357b8927f0c025ea2f72f25e15d03ce17a8a6c1789e2d5b108bf39ae"}, - {file = "zstd-1.5.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:98cbee6c1b2fe85f02fd475d885f98363c63bc64eebc249d7eb7469a0ff70283"}, - {file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9962714b89641301029f3832bdf07c20f60b9e64e39e8d7b6253451a82b54f5c"}, - {file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f59cc92d71537f8082306f75aa403ddb4a4a1069a39f104525673110e4d23f7"}, - {file = "zstd-1.5.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:569f13d0c926ddafceebce8ac73baddfc2bd9cbbbbc922b6b3073338cc43dae6"}, - {file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba530c44f252016acc6ef906d7d2070c1ad0cfe835c498fdcd37493e4772ac6e"}, - {file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ee3496ed8fff3add6c6e658b207f18d96474c3db0c28ab7a69623380b1a0a8c"}, - {file = "zstd-1.5.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:530d69bea2791cde8afa7fe988f3a37c3ba37015f6a1d5593c0500f089f3090e"}, - {file = "zstd-1.5.5.1-cp310-cp310-win32.whl", hash = "sha256:cf179e51f447b6a7ff47e449fcb98fb5fe15aedcc90401697cf7c93dd6e4434e"}, - {file = "zstd-1.5.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:5f5e6e0805d710d7509c8d175a467eb89c631a4142b1a630ceeb8e3e3138d152"}, - {file = "zstd-1.5.5.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:022f935a8666e08f0fff6204938a84d9fe4fcd8235a205787275933a07a164fb"}, - {file = "zstd-1.5.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3d15a2d18dac8bcafdde52fdf5d40ecae1f73b7de19b171f42339d2e51346d0"}, - {file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b9c67989f50ba63ffa0c50c9eaa037c2d14abacb0813e838ad705135245b4b"}, - {file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97da6a842ba7e4acf8bba7c596057143ee39b3c4a467196c2096d460e44accd6"}, - {file = "zstd-1.5.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dafd492fb8ee4ae04c81ab00f5f137860e7071f611335dd4cdb1c38bd8f11bc"}, - {file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9ee83e0bcbfd776200b026b3b9e86c6c86b8f414749f58d87c85dcf456b27066"}, - {file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ae2fd4bc8ea772a7b5f1acd1cac9e34bb9cd8fcde191f170092fdeea779a3a12"}, - {file = "zstd-1.5.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:edea52a0109f48fd46f4763689d3d356dcafd20ddf6789c559a1bd2e62b40a32"}, - {file = "zstd-1.5.5.1-cp311-cp311-win32.whl", hash = "sha256:88410481209520298ec4430e0d1d57e004c45e0b27c3035674fb182ccd2d8b7b"}, - {file = "zstd-1.5.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:dce18aaefbacf8b133367be86beec670baf68c0420bfcca49be08dbdbf933db6"}, - {file = "zstd-1.5.5.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:634dc632f7cf87e95dabf74dcf682e3507bd5cb9dd1bcdb81f92a6521aab0bd2"}, - {file = "zstd-1.5.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:608414eb75ead573891d97a1e529848b8f31749d21a440e80838548a19d8c0e6"}, - {file = "zstd-1.5.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:384128f7a731e3f45da49976591cec03fc4079e70653df10d9ea43a1d3b49d50"}, - {file = "zstd-1.5.5.1-cp35-cp35m-win32.whl", hash = "sha256:4bce254174ef05cea01021d67e18489d5d08db1168e758b62ecee121572a52a9"}, - {file = "zstd-1.5.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:3f0ff81232b49d7eb4f4d9e6f92443c9d242c139ad98ffedac0e889568f900ce"}, - {file = "zstd-1.5.5.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a871df41b801a260cc849c2c76f300ebb9d286c4b7a1fd6ce45fe0c91340b767"}, - {file = "zstd-1.5.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5a53860dbfbea281eb690ce09cae28967cf1df8e6d7560e4a8bf5b9fcb258147"}, - {file = "zstd-1.5.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:a37cbc0580fdfd66c8b3ec65f9af00a4a34e9781b54dfb89f04d301dc375c90a"}, - {file = "zstd-1.5.5.1-cp36-cp36m-win32.whl", hash = "sha256:5531b683539ae1f7b2ad23dacee8a73e5d7eaa6702ea8df5a24bd3318647dee1"}, - {file = "zstd-1.5.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eeaff418269b41eee8c7971fbba9d32d07d3f6aa26f962a72aff725071096a1b"}, - {file = "zstd-1.5.5.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8bd6a9050de8bbe844447348372ca17d01bc05207619f6a5d448567d111b5cd9"}, - {file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ece3d20ef357370584f304407fbd1e4ff9c231209320e08a889b8e3725d56e"}, - {file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687f9e03dc9f9b8803840425bb23bf6bc700888b4860afcf43c4f238102752d2"}, - {file = "zstd-1.5.5.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a649daac9c8f1b37d29f2b3d0a43f134061659b54877fe4b0da6df2965dc91f"}, - {file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bddc7e3c3ce31c01fe1edaa7c03c0b9e71eadf4ce1609746d32f86d95a0449e6"}, - {file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:12bf8e04add8bb84f9fe9117f3de6d9394eade6a5a82fe4d6bd95914fc6ef423"}, - {file = "zstd-1.5.5.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e6a15fa4d2e65c5902ab2a4e41279ac126cb371ce6c3c75ad5789bb20dd1f54"}, - {file = "zstd-1.5.5.1-cp37-cp37m-win32.whl", hash = "sha256:a1c269243a4321beb948635b544ccbe6390846358ace620fd000ab7099011d9c"}, - {file = "zstd-1.5.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:91366e36773241cb4b049a32f4495d33dd274df1eea5b55396f5f3984a3de22e"}, - {file = "zstd-1.5.5.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:d3ce2cb310690994274d133ea7f269dd4b81799fdbce158690556209723d7d4e"}, - {file = "zstd-1.5.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e0c87bfbfa9d852f79c90bcd7426c3ba46cf3285e6984013636d4fc854ba9230"}, - {file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce6d829d515f272fddb3a87e1a5f32cc0f1a7b0cba24d360c89f4a165b74b"}, - {file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e05f81f346213b23ed1b12d84fc1f72e65eacd8978e1e88facf185c82bd3d053"}, - {file = "zstd-1.5.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43ec66c4c3a76351c672c6ef9f0ff3412fca9ede0a56d18dddaf6418a93faef8"}, - {file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:58e554e91e0d49f4f2b2df390cdd0f64aa9b6fd5f4dcb208c094bfd079b30f3a"}, - {file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:883c6d3b6f5574e1765ca97f4b6a41b69094a41be56175552faebc0e0e43b65e"}, - {file = "zstd-1.5.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d52b6932cab5419c434bccfea3e5640e755369fc9eeb51e3d17e15bf8e8cb103"}, - {file = "zstd-1.5.5.1-cp38-cp38-win32.whl", hash = "sha256:dcaf44270ec88552e969be4dd3359b34aa3065663ccd8168a257c78f150a356c"}, - {file = "zstd-1.5.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:627f12cb7035723c8f3d8d4cefcad6d950ed9cba33fd3eb46bae04ccab479234"}, - {file = "zstd-1.5.5.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:c0dab132c1a5a7cc838a7c3e4e380ad153b9d7bd1fadafabf6cfeb780b916201"}, - {file = "zstd-1.5.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4ab0a5dd9a41d3b083304beee7ada40ee36431acbeb75132032f4fe5cf0490a"}, - {file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6e38f496d287020658c6b4cdb5e815ecc6998889bd0f1f9ab0825f2e3d74ef"}, - {file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0096c8ee0ed4bfe406bc961019f55552109e19771bfd3eb32d2af56ea27085c"}, - {file = "zstd-1.5.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a0f1527728c50b6aa8f04b47a07580f0ae13cfc6c6d9c96bb0bdf5259487559"}, - {file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6a64e420c904063c5c3de53c00ec0993ebc0a48cebbef97dc6c768562c5abab5"}, - {file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03444e357b7632c64480a81ce7095242dab9d7f8aed317326563ef6c663263eb"}, - {file = "zstd-1.5.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:88b9a10f80d2b87bf8cc1a1fc20a815ed92b5eefdc15cbe8062021f0b5a26a10"}, - {file = "zstd-1.5.5.1-cp39-cp39-win32.whl", hash = "sha256:c91cc1606eb8b3a6fed11faaef4c6e55f1133d70cf0db0c829a2cf9c2ac1dfd9"}, - {file = "zstd-1.5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:f462e2ebf26dcbfc2c8dddd6b5c56859683f0b77edb8f268e637f7d390a58f74"}, - {file = "zstd-1.5.5.1-pp27-pypy_73-macosx_10_14_x86_64.whl", hash = "sha256:c63f916732e3e309e49ec95e7a0af5d37ff1321f3df2aac10e507bd2b56fceda"}, - {file = "zstd-1.5.5.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:50d4850d758bb033df50722cc13ed913b2afcd5385250be4f3ffb79a26b319c3"}, - {file = "zstd-1.5.5.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:0412d666515e78a91ada7e2d78e9dd6b25ddda1b41623b145b99653275c7f3ce"}, - {file = "zstd-1.5.5.1-pp36-pypy36_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0ea91f74869a3cdcb2dde08f8f30ee3da72782c5d1737afed9c703232815864e"}, - {file = "zstd-1.5.5.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:477548897dc2b8b595af7bec5f0f55dcba8e9a282335f687cc663b52b171357b"}, - {file = "zstd-1.5.5.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c518938b57a56001ee04dcf79a432152f5bd431416f3b22819ba959bc6054d89"}, - {file = "zstd-1.5.5.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:894a8fe0228d5e24dc286a8d98eb0ce2883f8e2e57f3b7e7619ebdb67967120a"}, - {file = "zstd-1.5.5.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:42ec0a4ae9bedd9909fa4f580f3c800469da1b631faeaa94f204e1b66c767fa2"}, - {file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d56dedaa04ab8ecc23492972b12e0bf8529f64c9bceb28c11f43c2369c9768b3"}, - {file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5b060770d796e4c01f5848b345c3cea8a177ab4e7cd95a1963a355042d429e1"}, - {file = "zstd-1.5.5.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fea04805ef6e1cb93d6e5d6bbc7a03bc75a5c733fd352d5aaa81109986fdf1ef"}, - {file = "zstd-1.5.5.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:405c28a35756e57a434bbd7ed29dc5e6490cd2fc2118cbf78b60eaebd134f5e9"}, - {file = "zstd-1.5.5.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:c42e630443b01a891277426365a51a2aa630b059ce675992c70c1928d30eccb4"}, - {file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1520d23f24f26cdfbcdb4dc86947446b8f694838bfce728d7fc4b3492397357c"}, - {file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4730737f63cf802321743ded6acc85e747e7f5587c5ba2e51a760bf009f7de"}, - {file = "zstd-1.5.5.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9f8c014395e89ad7f67ffe873c0fa1d8e9b4dea8b1801d24e8d9ccd8259858d"}, - {file = "zstd-1.5.5.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5d9ba4f6af0945809bfa3387c6a1208a22937a876521b9ec347e7183d623311b"}, - {file = "zstd-1.5.5.1-pp39-pypy39_pp73-macosx_10_14_x86_64.whl", hash = "sha256:04dfd9f46b0b0b1bc413884fe028b726febcb726d4f66e3cf8afc00c2d9026bf"}, - {file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af52436a2eb5caa925d95461973984cb34d472a963b6be1c0a9f2dfbafad096f"}, - {file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610928b888a2e7ae9d2018ffa814859d47ec4ba75f89a1188ab4eb9232636ee5"}, - {file = "zstd-1.5.5.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee3c9feea99c7f4ff43129a885da056b5aa0cde3f7876bf6397bfb9433f44352"}, - {file = "zstd-1.5.5.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ac9768eeb3c6b530db93de2fec9b363776075dc8a00ee4049612ba5397ca8e"}, - {file = "zstd-1.5.5.1.tar.gz", hash = "sha256:1ef980abf0e1e072b028d2d76ef95b476632651c96225cf30b619c6eef625672"}, + {file = "zstd-1.5.6.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:ca1f2eb3fac59c01bdabcb767427f32d485ead735d599d12bba48b3137d0ce36"}, + {file = "zstd-1.5.6.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4fa797232937c7d9140fc19e83267691fa169756e9a376cd9d1520a76919cc1f"}, + {file = "zstd-1.5.6.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:a9b98a129e74593e11b25d4b42b8f5cfcc33db33bf5b8d4dc17c434d0cf1a9bd"}, + {file = "zstd-1.5.6.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6f76f4eba38cb30d89bad0b3591f4ef3f5f1de3bd526fe33895f1d4187277634"}, + {file = "zstd-1.5.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b75fe9d8af12dd9235f6d932d8601195fd82b818a0c5780ce28cd068cdad885b"}, + {file = "zstd-1.5.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55aaff3a5b3ec1fa9fa000b8bb7926e1c129aa0597096d19be9635998eac0a33"}, + {file = "zstd-1.5.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7e6441005b3c05e32ea62da034ef427ede4f74d59593a98a8d3d067abbb30"}, + {file = "zstd-1.5.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:70e9892f8902bc48b596f393d4d2a3ff2ca7755783f3137069c85e0517b8d8da"}, + {file = "zstd-1.5.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f7203e02d33932a6f37622a94ea72f50310e91e2f21d30e8ecc2ae9e67329b53"}, + {file = "zstd-1.5.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d5f3e3881058262d29ee05a981e11545cc13b722eaa40ce58b2108a14796bd93"}, + {file = "zstd-1.5.6.1-cp310-cp310-win32.whl", hash = "sha256:ad9e48f2ef5dc2db69610a5567acd34adbc2195902a0971021490e925023fd52"}, + {file = "zstd-1.5.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:dbb0ecc30e0eee5e17a6a83028c0003f6270bcd2488f2927d79b19982cbc198c"}, + {file = "zstd-1.5.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2498c1e02398b724249d648e11de196ceca112d1e37270af4ddf359bc29ef10a"}, + {file = "zstd-1.5.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de1f2b7326ca6d3c998c0ea9efc8d92fc716e11a3d02dd977c20be8694ab0cc5"}, + {file = "zstd-1.5.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63ebd8a3efc876afa6b1a930f15853d3e691e5652441487c26cea5ed49e9b239"}, + {file = "zstd-1.5.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:22e6829cbf1994999e281ffe749f110d4544861566e3f0a5e3c93d63f52cbab0"}, + {file = "zstd-1.5.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:472a4d546afaf58bf0e35f388ac330b60b6aaf94713d3b7cf1c6753ff1b4d10c"}, + {file = "zstd-1.5.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d36c1ba71ba155a653f21d67d038fd1a887b343ed65f1660b3911ece5b934d33"}, + {file = "zstd-1.5.6.1-cp311-cp311-win32.whl", hash = "sha256:1e3a20d23b2e8207d6786bdeff01a3c8e9d3da34e46e50a7ddcac71bec5915b9"}, + {file = "zstd-1.5.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:16cfe4ce5987587dcd6a79358dcdedc091a5c1f8ab9defac59024400de34ce77"}, + {file = "zstd-1.5.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebd16e902398161c0de066c65a7ee36eb0a3b91087eafb75e48a120a955de61"}, + {file = "zstd-1.5.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17917107e3a2766629646b21dde257e085408086b95a4a1fd62153e77784fcc2"}, + {file = "zstd-1.5.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5039f112334b43f894e74333ec6b6b9ebcf4926db75136ecfd17f938d4a0d8"}, + {file = "zstd-1.5.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2644cbd2b0cfe4a7492a71bb89c780c27f0a461984e443855624e6fe3d1c29e6"}, + {file = "zstd-1.5.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:136b6ead97209b0309e50751da171a056a95e9b742b4cbfd11d8bab68d379533"}, + {file = "zstd-1.5.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4396d9317ee2e9032466bf006b6254d17ab5a71c6d904fa6b2bc2ab377903cb1"}, + {file = "zstd-1.5.6.1-cp312-cp312-win32.whl", hash = "sha256:af31fc67fe76a1acb1e350b9968a7476a9686a10951c24b3f52bf6173573e722"}, + {file = "zstd-1.5.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:4d61e5145c382aeeaa70dfa19b42e9e8049ab3a24bb2064e9f79cda03bb7ff22"}, + {file = "zstd-1.5.6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fc5981846390755256eaa9e910fabd6460fac380703aa1030fba943cb3a7fe73"}, + {file = "zstd-1.5.6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:acb96a40da4974d5633c2a1577a26928ff98ff3905db3f0c21e9fc409922c4a3"}, + {file = "zstd-1.5.6.1-cp35-cp35m-win32.whl", hash = "sha256:ff4d8a4c1fe37af3c4eddd632303f8fbf891ec422c5e117a70024e3757e2edff"}, + {file = "zstd-1.5.6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:2dc857557e19afef00dd90e4d4cd7451b3add21826003153302e90767ec854d4"}, + {file = "zstd-1.5.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7b1cb1e2dc3cb17a879a7299dcbaf405238256bd9012a35722fa9c1677f6774e"}, + {file = "zstd-1.5.6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:29c5629d5d6f18ce5d52b54b82ce7abc5077e7557279947c050430537db251d2"}, + {file = "zstd-1.5.6.1-cp36-cp36m-win32.whl", hash = "sha256:7956e2efc29e84af71243cd9ece05d580a339fac9525ee531f3c3bdf83c3f073"}, + {file = "zstd-1.5.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6b06146e5b69568dea5506dd870071e2938e260c78db06f40c6184ae411438a3"}, + {file = "zstd-1.5.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:177f19b76efd6e2e7d91c0557638dc49de6c8794c109ccdf0ea4684f63d2fe24"}, + {file = "zstd-1.5.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23aef9526a4ea90cf47030c7a57e4cedd398cf4ba1ec2798b51cafe47aff75fd"}, + {file = "zstd-1.5.6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c533ecfd64285c4f228cd61909e35eb9888da4adc2a7ea213b2b96a8084b96be"}, + {file = "zstd-1.5.6.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:563d36a666107b9f388e766c832630c7b0eb101ead536d0cd5f6582e75cc334a"}, + {file = "zstd-1.5.6.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2128ea4bb903471c1e763e50a1f301bd37e70735eee9979dbef60244605fa5c7"}, + {file = "zstd-1.5.6.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:bd1a162a40ca090f72766ab01ad7748b16f9b14f6bd36c00fb9b1a288db2a985"}, + {file = "zstd-1.5.6.1-cp37-cp37m-win32.whl", hash = "sha256:fcebc4b59406fe04c18349c496a3d5ce5971f8cc6e30a8fb6ff86c8871f4ab3c"}, + {file = "zstd-1.5.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:159b79922e90e387b42986074339c670b224c43d15feb9b98463ba1abd212947"}, + {file = "zstd-1.5.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c327cf199979cd559c713a9cef29a8938bd9d5a17a582b024078b4af1b70fcf2"}, + {file = "zstd-1.5.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1692fd5e120f1b86aeda81492c1eec54c42631bcd73943c68f29c5a13fbc96f1"}, + {file = "zstd-1.5.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b33c4effbdff51a9e1c32e84b6f3b28a7ea2c7278a482f856540584e7ca8016f"}, + {file = "zstd-1.5.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fe034eadceba325671decc0be79c0825cc25b8f8c4c11f34bcc09a793d790d18"}, + {file = "zstd-1.5.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:532df36d563611d44e1d715fb68a082e0d7fbc9f1d83237e99e27725940d03c5"}, + {file = "zstd-1.5.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a2cb819f34bb13456c8468125c290dd2bb227a2b784c4e6ed8bca4a07f50400b"}, + {file = "zstd-1.5.6.1-cp38-cp38-win32.whl", hash = "sha256:0eb553d2979645b373730bc1b46fdaf30d668000785b5943de1f5bbd8206b7c7"}, + {file = "zstd-1.5.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3abdb53c2996d74dfc1a38fc58e55e794ec9557610185519280b6aa9cb90c8"}, + {file = "zstd-1.5.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2523683758345238d544fbda97b0ecc025d440214c4424bd7bd20c0f8904479"}, + {file = "zstd-1.5.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aef69dc30beac397517a7219bdf7873620e8a0de74301e40ad51356c49f724b9"}, + {file = "zstd-1.5.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1573926cdd0ad6620df2bf61f8a8e242205e0fc73efab7ff69fc425ab20aa906"}, + {file = "zstd-1.5.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1239f4ad597c8796cfceddcf1d9062125e0b4ea75dbd369e18f1f13bf687ce63"}, + {file = "zstd-1.5.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b20e203ef2a4b05271420c038e6d7e004920124d33b0aa1f6c6d966b1f9676fa"}, + {file = "zstd-1.5.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3c9efe68de0ed648848324510ea7b100e702bdd866f7105e499b209ccde2ceb0"}, + {file = "zstd-1.5.6.1-cp39-cp39-win32.whl", hash = "sha256:ad6927e917637b679cb9808815800f91c474c3b6c819e7edee898d06d1998615"}, + {file = "zstd-1.5.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ea9a8dfe02f26b1c68c72f063ec2106edb2d71a5279ea5b1a05e952dc44eb668"}, + {file = "zstd-1.5.6.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:255a957f94c618f0af47c3949f27934c953084eac8941a7df455743c9fc2904d"}, + {file = "zstd-1.5.6.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:d7b9fc00a98b16b360d95d1e5f34a5a20f4334ab467ca2f172f09d814580c3cf"}, + {file = "zstd-1.5.6.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:b3b2b4ae40a8404137ba76139b46210ab73faa3f9429e1512ea444d588661b70"}, + {file = "zstd-1.5.6.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:d6409b2816e8c578946d3fa5197b2fc92feb4cc6de28adcf5569f521801f927b"}, + {file = "zstd-1.5.6.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7a9e914b58edad15492eb9dff5f6941cbe8f431d82490f1b4b270a8e51dddbad"}, + {file = "zstd-1.5.6.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68869460a1f6dc6dfc4de5c8da6101e06f21b1ca7b6d4bbabfd87ec1ba3bc0ca"}, + {file = "zstd-1.5.6.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a731b65b7b13dff89a1067c0deab75c12bb1d07b95b89e4c18e91a59a6e290"}, + {file = "zstd-1.5.6.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9011b55afcbc2e93d8e2b1dd15314185f9aa6a32e7f7654b1ccf3273bb9643e4"}, + {file = "zstd-1.5.6.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:51cede4f54f27f62e303328f790a55e2f8f3e14060605d19fb874bb6318a0742"}, + {file = "zstd-1.5.6.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cefbf0a5349f2d772ae41957f5f2a88fe012d374eab801683f644db4edc5a6"}, + {file = "zstd-1.5.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54eb3f22357c0dc69ef5267c6e5df3e496b3beaf5d1857e50caeeea551528bbc"}, + {file = "zstd-1.5.6.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8dbd7e78a1d9f9a751ea93984febdb68ded6c88f9b36448f1bb6ec5f6621aed"}, + {file = "zstd-1.5.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5a672013a0159255f37e56ac799b8f75b3c5e5a4ebeb2c37df7fea818ce0adf8"}, + {file = "zstd-1.5.6.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251dd039bc11da139e26007b04b3a43d8c7e9ea0f399367dbe6634213f96c562"}, + {file = "zstd-1.5.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb485163456835005312499b0210c969b8e680b8ed97387a06e3ac8c573ad81"}, + {file = "zstd-1.5.6.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:972eb2cf749a17f2f489374b8bb69af17b49a62306b35dccaf24767cac3c0194"}, + {file = "zstd-1.5.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:343540559f75e4cbe1ab7145f03f276bbd6b29d98e0efa490e05b8e0f9fb5b80"}, + {file = "zstd-1.5.6.1.tar.gz", hash = "sha256:64a01e79d8d9592cd35f9de2ebc0376e0f94dc8150d6e3ae891a55f190d3490e"}, ] [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "838db84d6f4f4606c1d49f4e370ea0ef0b519566662bbff11b2e905fb7e07e31" +content-hash = "67496a27bcb51e5a9d1491175acf994f8226df57808525f89df8082cec123caa" diff --git a/pyproject.toml b/pyproject.toml index 9d2a1b7cb4..038381913d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ qml = 'qml.app:app' [tool.poetry.dependencies] python = "~3.10.0" typer = "^0.15.1" -poetry = ">1.7" +poetry = ">1.7,<2" poetry-plugin-export = "^1.8.0" dulwich = "<0.22" From fb46d78d3feef83d45f22fb09eb556b7bfd58e79 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 12:44:02 -0500 Subject: [PATCH 08/29] sync --- .../adjoint_diff_benchmarking/demo.py | 3 + .../adjoint_diff_benchmarking/metadata.json | 46 +- demonstrations_v2/ahs_aquila/demo.py | 3 + demonstrations_v2/ahs_aquila/metadata.json | 158 +++--- .../braket-parallel-gradients/demo.py | 7 + .../braket-parallel-gradients/metadata.json | 98 ++-- .../circuits_as_fourier_series/demo.py | 4 + .../circuits_as_fourier_series/metadata.json | 92 +-- demonstrations_v2/covalent_cloud_gpu/demo.py | 3 + .../covalent_cloud_gpu/metadata.json | 110 ++-- demonstrations_v2/ensemble_multi_qpu/demo.py | 3 + .../ensemble_multi_qpu/metadata.json | 62 +-- .../function_fitting_qsp/demo.py | 4 + .../function_fitting_qsp/metadata.json | 74 +-- demonstrations_v2/gbs/demo.py | 5 + demonstrations_v2/gbs/metadata.json | 198 +++---- .../getting_started_with_hybrid_jobs/demo.py | 3 + .../metadata.json | 72 +-- demonstrations_v2/gqe_training/demo.py | 2 + demonstrations_v2/gqe_training/metadata.json | 148 +++-- .../how_to_use_qiskit1_with_pennylane/demo.py | 4 + .../metadata.json | 112 ++-- demonstrations_v2/ibm_pennylane/demo.py | 5 + demonstrations_v2/ibm_pennylane/metadata.json | 92 +-- demonstrations_v2/learning2learn/demo.py | 3 + .../learning2learn/metadata.json | 76 +-- .../ml_classical_shadows/demo.py | 3 + .../ml_classical_shadows/metadata.json | 130 +++-- demonstrations_v2/oqc_pulse/demo.py | 3 + demonstrations_v2/oqc_pulse/metadata.json | 144 ++--- demonstrations_v2/plugins_hybrid/demo.py | 3 + .../plugins_hybrid/metadata.json | 72 +-- demonstrations_v2/pytorch_noise/demo.py | 3 + demonstrations_v2/pytorch_noise/metadata.json | 76 +-- demonstrations_v2/qnspsa/demo.py | 3 + demonstrations_v2/qnspsa/metadata.json | 182 +++--- demonstrations_v2/qonn/demo.py | 3 + demonstrations_v2/qonn/metadata.json | 62 +-- demonstrations_v2/qrack/demo.py | 3 + demonstrations_v2/qrack/metadata.json | 136 ++--- .../qsim_beyond_classical/demo.py | 3 + .../qsim_beyond_classical/metadata.json | 184 +++--- demonstrations_v2/quantum_neural_net/demo.py | 3 + .../quantum_neural_net/metadata.json | 82 +-- .../quantum_volume/metadata.json | 220 ++++---- .../demo.py | 3 + .../metadata.json | 102 ++-- .../demo.py | 2 + .../metadata.json | 102 ++-- .../demo.py | 5 + .../metadata.json | 96 ++-- .../demo.py | 3 + .../metadata.json | 100 ++-- demonstrations_v2/tutorial_QGAN/demo.py | 3 + demonstrations_v2/tutorial_QGAN/metadata.json | 54 +- demonstrations_v2/tutorial_QUBO/demo.py | 3 + demonstrations_v2/tutorial_QUBO/metadata.json | 61 +- .../tutorial_adaptive_circuits/demo.py | 3 + .../tutorial_adaptive_circuits/metadata.json | 188 ++++--- .../tutorial_adjoint_diff/demo.py | 3 + .../tutorial_adjoint_diff/metadata.json | 134 ++--- .../tutorial_adversarial_attacks_QML/demo.py | 2 + .../metadata.json | 164 +++--- demonstrations_v2/tutorial_apply_qsvt/demo.py | 5 + .../tutorial_apply_qsvt/metadata.json | 180 +++--- demonstrations_v2/tutorial_backprop/demo.py | 3 + .../tutorial_backprop/metadata.json | 62 +-- .../tutorial_barren_gadgets/demo.py | 4 + .../tutorial_barren_gadgets/metadata.json | 124 ++--- .../tutorial_barren_plateaus/demo.py | 3 + .../tutorial_barren_plateaus/metadata.json | 120 ++-- .../tutorial_block_encoding/demo.py | 7 + .../tutorial_block_encoding/metadata.json | 132 ++--- demonstrations_v2/tutorial_bluequbit/demo.py | 3 + .../tutorial_bluequbit/metadata.json | 116 ++-- .../tutorial_chemical_reactions/demo.py | 5 + .../tutorial_chemical_reactions/metadata.json | 98 ++-- .../tutorial_circuit_compilation/demo.py | 3 + .../metadata.json | 72 +-- .../demo.py | 2 + .../metadata.json | 174 +++--- .../tutorial_classical_kernels/demo.py | 3 + .../tutorial_classical_kernels/metadata.json | 138 ++--- .../tutorial_classical_shadows/demo.py | 5 + .../tutorial_classical_shadows/metadata.json | 146 ++--- .../tutorial_classically_boosted_vqe/demo.py | 5 + .../metadata.json | 104 ++-- .../demo.py | 2 + .../metadata.json | 190 +++---- .../tutorial_coherent_vqls/demo.py | 3 + .../tutorial_coherent_vqls/metadata.json | 100 ++-- .../tutorial_constant_depth_mps_prep/demo.py | 3 + .../metadata.json | 94 ++-- .../tutorial_contextuality/demo.py | 3 + .../tutorial_contextuality/metadata.json | 162 +++--- .../demo.py | 3 + .../metadata.json | 140 +++-- .../tutorial_diffable-mitigation/demo.py | 4 + .../metadata.json | 100 ++-- .../tutorial_diffable_shadows/demo.py | 3 + .../tutorial_diffable_shadows/metadata.json | 130 ++--- .../tutorial_differentiable_HF/demo.py | 3 + .../tutorial_differentiable_HF/metadata.json | 130 ++--- .../tutorial_doubly_stochastic/demo.py | 3 + .../tutorial_doubly_stochastic/metadata.json | 102 ++-- .../tutorial_eqnn_force_field/demo.py | 4 + .../tutorial_eqnn_force_field/metadata.json | 214 +++---- .../demo.py | 9 +- .../metadata.json | 110 ++-- .../tutorial_error_mitigation/demo.py | 9 +- .../tutorial_error_mitigation/metadata.json | 154 +++--- demonstrations_v2/tutorial_error_prop/demo.py | 2 + .../tutorial_error_prop/metadata.json | 94 ++-- .../demo.py | 5 + .../metadata.json | 94 ++-- demonstrations_v2/tutorial_falqon/demo.py | 5 + .../tutorial_falqon/metadata.json | 104 ++-- .../tutorial_fermionic_operators/demo.py | 3 + .../metadata.json | 108 ++-- .../demo.py | 522 ++++++++++++++++++ .../metadata.json | 65 +++ .../tutorial_gaussian_transformation/demo.py | 3 + .../metadata.json | 82 +-- .../tutorial_general_parshift/demo.py | 3 + .../tutorial_general_parshift/metadata.json | 176 +++--- .../tutorial_geometric_qml/demo.py | 3 + .../tutorial_geometric_qml/metadata.json | 118 ++-- .../tutorial_givens_rotations/demo.py | 3 + .../tutorial_givens_rotations/metadata.json | 128 ++--- .../tutorial_grovers_algorithm/demo.py | 3 + .../tutorial_grovers_algorithm/metadata.json | 76 +-- .../demo.py | 3 + .../metadata.json | 90 +-- .../tutorial_haar_measure/metadata.json | 286 +++++----- .../tutorial_here_comes_the_sun/demo.py | 3 + .../tutorial_here_comes_the_sun/metadata.json | 152 ++--- .../demo.py | 3 + .../metadata.json | 140 ++--- .../tutorial_how_to_collect_mcm_stats/demo.py | 3 + .../metadata.json | 96 ++-- .../demo.py | 3 + .../metadata.json | 96 ++-- .../demo.py | 2 + .../metadata.json | 83 +-- .../demo.py | 24 +- .../metadata.json | 94 ++-- .../tutorial_how_to_use_noise_models/demo.py | 2 + .../metadata.json | 83 +-- .../demo.py | 2 + .../metadata.json | 128 ++--- .../tutorial_how_to_use_registers/demo.py | 4 + .../metadata.json | 82 +-- .../demo.py | 5 + .../metadata.json | 194 +++---- .../demo.py | 3 + .../metadata.json | 81 +-- .../demo.py | 3 + .../metadata.json | 168 +++--- demonstrations_v2/tutorial_intro_qrom/demo.py | 2 + .../tutorial_intro_qrom/metadata.json | 152 ++--- demonstrations_v2/tutorial_intro_qsvt/demo.py | 3 + .../tutorial_intro_qsvt/metadata.json | 136 ++--- .../tutorial_isingmodel_PyTorch/demo.py | 3 + .../tutorial_isingmodel_PyTorch/metadata.json | 130 ++--- .../tutorial_jax_transformations/demo.py | 3 + .../metadata.json | 82 +-- .../tutorial_kak_theorem/demo.py | 2 + .../tutorial_kak_theorem/metadata.json | 346 ++++++------ .../tutorial_kernel_based_training/demo.py | 3 + .../metadata.json | 64 +-- .../tutorial_kernels_module/demo.py | 18 + .../tutorial_kernels_module/metadata.json | 140 ++--- .../tutorial_lcu_blockencoding/demo.py | 5 + .../tutorial_lcu_blockencoding/metadata.json | 118 ++-- .../demo.py | 3 + .../metadata.json | 130 +++-- .../tutorial_learning_few_data/demo.py | 7 + .../tutorial_learning_few_data/metadata.json | 170 +++--- .../demo.py | 3 + .../metadata.json | 94 ++-- .../tutorial_learningshallow/demo.py | 3 + .../tutorial_learningshallow/metadata.json | 168 +++--- demonstrations_v2/tutorial_liealgebra/demo.py | 5 +- .../tutorial_liealgebra/metadata.json | 228 ++++---- demonstrations_v2/tutorial_liesim/demo.py | 3 + .../tutorial_liesim/metadata.json | 326 ++++++----- .../tutorial_liesim_extension/demo.py | 3 + .../tutorial_liesim_extension/metadata.json | 196 ++++--- .../tutorial_local_cost_functions/demo.py | 3 + .../metadata.json | 84 ++- .../tutorial_magic_state_distillation/demo.py | 3 + .../metadata.json | 102 ++-- demonstrations_v2/tutorial_mapping/demo.py | 2 + .../tutorial_mapping/metadata.json | 100 ++-- demonstrations_v2/tutorial_mbqc/demo.py | 5 + demonstrations_v2/tutorial_mbqc/metadata.json | 488 ++++++++-------- .../tutorial_mcm_introduction/demo.py | 3 + .../tutorial_mcm_introduction/metadata.json | 102 ++-- .../tutorial_measurement_optimize/demo.py | 3 + .../metadata.json | 192 +++---- .../tutorial_mitigation_advantage/demo.py | 3 + .../metadata.json | 144 +++-- .../tutorial_mol_geo_opt/demo.py | 3 + .../tutorial_mol_geo_opt/metadata.json | 210 +++---- demonstrations_v2/tutorial_mps/demo.py | 3 + demonstrations_v2/tutorial_mps/metadata.json | 198 ++++--- .../metadata.json | 70 ++- .../tutorial_neutral_atoms/demo.py | 3 + .../tutorial_neutral_atoms/metadata.json | 268 ++++----- .../demo.py | 3 + .../metadata.json | 100 ++-- .../tutorial_noisy_circuits/demo.py | 4 + .../tutorial_noisy_circuits/metadata.json | 92 +-- demonstrations_v2/tutorial_odegen/demo.py | 3 + .../tutorial_odegen/metadata.json | 234 ++++---- .../tutorial_optimal_control/demo.py | 3 + .../tutorial_optimal_control/metadata.json | 188 +++---- demonstrations_v2/tutorial_pasqal/demo.py | 3 + .../tutorial_pasqal/metadata.json | 72 +-- .../tutorial_phase_kickback/demo.py | 3 + .../tutorial_phase_kickback/metadata.json | 74 +-- demonstrations_v2/tutorial_photonics/demo.py | 3 + .../tutorial_photonics/metadata.json | 282 +++++----- .../demo.py | 3 + .../metadata.json | 268 +++++---- .../tutorial_pulse_programming101/demo.py | 3 + .../metadata.json | 152 ++--- demonstrations_v2/tutorial_qaoa_intro/demo.py | 3 + .../tutorial_qaoa_intro/metadata.json | 64 +-- .../tutorial_qaoa_maxcut/demo.py | 3 + .../tutorial_qaoa_maxcut/metadata.json | 68 +-- demonstrations_v2/tutorial_qcbm/demo.py | 2 + demonstrations_v2/tutorial_qcbm/metadata.json | 190 ++++--- .../tutorial_qchem_external/demo.py | 3 + .../tutorial_qchem_external/metadata.json | 101 ++-- demonstrations_v2/tutorial_qft/demo.py | 3 + demonstrations_v2/tutorial_qft/metadata.json | 60 +- .../tutorial_qft_arithmetics/demo.py | 5 + .../tutorial_qft_arithmetics/metadata.json | 84 ++- demonstrations_v2/tutorial_qgrnn/demo.py | 3 + .../tutorial_qgrnn/metadata.json | 72 ++- .../demo.py | 2 + .../metadata.json | 126 ++--- .../tutorial_qnn_module_tf/demo.py | 3 + .../tutorial_qnn_module_tf/metadata.json | 64 +-- .../tutorial_qnn_module_torch/demo.py | 3 + .../tutorial_qnn_module_torch/metadata.json | 64 +-- .../demo.py | 3 + .../metadata.json | 164 +++--- demonstrations_v2/tutorial_qpe/demo.py | 3 + demonstrations_v2/tutorial_qpe/metadata.json | 118 ++-- .../tutorial_qsvt_hardware/demo.py | 3 + .../tutorial_qsvt_hardware/metadata.json | 86 +-- .../tutorial_quantum_analytic_descent/demo.py | 5 + .../metadata.json | 156 +++--- .../tutorial_quantum_chemistry/demo.py | 5 +- .../tutorial_quantum_chemistry/metadata.json | 172 +++--- .../tutorial_quantum_circuit_cutting/demo.py | 7 + .../metadata.json | 143 +++-- .../tutorial_quantum_dropout/demo.py | 2 + .../tutorial_quantum_dropout/metadata.json | 125 ++--- .../tutorial_quantum_gans/demo.py | 3 + .../tutorial_quantum_gans/metadata.json | 102 ++-- .../tutorial_quantum_metrology/demo.py | 3 + .../tutorial_quantum_metrology/metadata.json | 84 ++- .../tutorial_quantum_natural_gradient/demo.py | 3 + .../metadata.json | 150 ++--- .../demo.py | 3 + .../metadata.json | 126 ++--- .../tutorial_quanvolution/demo.py | 3 + .../tutorial_quanvolution/metadata.json | 74 ++- .../tutorial_qubit_rotation/demo.py | 3 + .../tutorial_qubit_rotation/metadata.json | 86 +-- .../tutorial_qubit_tapering/demo.py | 5 + .../tutorial_qubit_tapering/metadata.json | 146 ++--- .../tutorial_qubitization/demo.py | 2 + .../tutorial_qubitization/metadata.json | 98 ++-- .../demo.py | 4 + .../metadata.json | 94 ++-- .../tutorial_resource_estimation/demo.py | 3 + .../metadata.json | 146 ++--- demonstrations_v2/tutorial_rl_pulse/demo.py | 3 + .../tutorial_rl_pulse/metadata.json | 252 +++++---- demonstrations_v2/tutorial_rosalin/demo.py | 3 + .../tutorial_rosalin/metadata.json | 168 +++--- demonstrations_v2/tutorial_rotoselect/demo.py | 3 + .../tutorial_rotoselect/metadata.json | 104 ++-- demonstrations_v2/tutorial_sc_qubits/demo.py | 3 + .../tutorial_sc_qubits/metadata.json | 232 ++++---- .../demo.py | 3 + .../metadata.json | 241 ++++---- demonstrations_v2/tutorial_spsa/demo.py | 4 + demonstrations_v2/tutorial_spsa/metadata.json | 178 +++--- .../tutorial_state_preparation/demo.py | 3 + .../tutorial_state_preparation/metadata.json | 82 +-- .../demo.py | 3 + .../metadata.json | 146 ++--- .../tutorial_teleportation/demo.py | 4 + .../tutorial_teleportation/metadata.json | 146 ++--- .../tutorial_testing_symmetry/demo.py | 3 + .../tutorial_testing_symmetry/metadata.json | 94 ++-- .../tutorial_tn_circuits/demo.py | 9 + .../tutorial_tn_circuits/metadata.json | 122 ++-- demonstrations_v2/tutorial_toric_code/demo.py | 3 + .../tutorial_toric_code/metadata.json | 149 +++-- .../tutorial_trapped_ions/demo.py | 3 + .../tutorial_trapped_ions/metadata.json | 362 ++++++------ .../tutorial_unitary_designs/metadata.json | 319 ++++++----- .../tutorial_univariate_qvr/demo.py | 4 + .../tutorial_univariate_qvr/metadata.json | 172 +++--- .../tutorial_variational_classifier/demo.py | 3 + .../metadata.json | 85 ++- demonstrations_v2/tutorial_vqe/demo.py | 3 + demonstrations_v2/tutorial_vqe/metadata.json | 157 +++--- demonstrations_v2/tutorial_vqe_qng/demo.py | 7 + .../tutorial_vqe_qng/metadata.json | 140 ++--- .../tutorial_vqe_spin_sectors/demo.py | 3 + .../tutorial_vqe_spin_sectors/metadata.json | 110 ++-- demonstrations_v2/tutorial_vqe_vqd/demo.py | 3 + .../tutorial_vqe_vqd/metadata.json | 111 ++-- demonstrations_v2/tutorial_vqls/demo.py | 3 + demonstrations_v2/tutorial_vqls/metadata.json | 88 +-- demonstrations_v2/tutorial_vqt/demo.py | 3 + demonstrations_v2/tutorial_vqt/metadata.json | 84 ++- .../tutorial_zne_catalyst/metadata.json | 166 +++--- .../tutorial_zx_calculus/demo.py | 3 + .../tutorial_zx_calculus/metadata.json | 282 +++++----- demonstrations_v2/vqe_parallel/demo.py | 3 + demonstrations_v2/vqe_parallel/metadata.json | 62 +-- lib/qml/app.py | 31 +- lib/qml/lib/fs.py | 10 + 331 files changed, 12343 insertions(+), 11259 deletions(-) create mode 100644 demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py create mode 100644 demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json diff --git a/demonstrations_v2/adjoint_diff_benchmarking/demo.py b/demonstrations_v2/adjoint_diff_benchmarking/demo.py index be11d3d52e..a53dbcff5f 100644 --- a/demonstrations_v2/adjoint_diff_benchmarking/demo.py +++ b/demonstrations_v2/adjoint_diff_benchmarking/demo.py @@ -142,3 +142,6 @@ def circuit(params): # :align: center # # +# About the author +# ---------------- +# .. include:: ../_static/authors/christina_lee.txt \ No newline at end of file diff --git a/demonstrations_v2/adjoint_diff_benchmarking/metadata.json b/demonstrations_v2/adjoint_diff_benchmarking/metadata.json index f6e61366b7..6ad29f53dc 100644 --- a/demonstrations_v2/adjoint_diff_benchmarking/metadata.json +++ b/demonstrations_v2/adjoint_diff_benchmarking/metadata.json @@ -1,24 +1,24 @@ { - "title": "Adjoint Differentiation \u2013 Supplementary Material", - "authors": [ - { - "username": "christina" - } - ], - "dateOfPublication": "2021-11-23T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adjoint_differentiation.png" - } - ], - "seoDescription": "Benchmarking file for adjoint diff demonstration.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Adjoint Differentiation – Supplementary Material", + "authors": [ + { + "username": "christina" + } + ], + "dateOfPublication": "2021-11-23T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adjoint_differentiation.png" + } + ], + "seoDescription": "Benchmarking file for adjoint diff demonstration.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/ahs_aquila/demo.py b/demonstrations_v2/ahs_aquila/demo.py index 6731faa973..ae4f39f6f7 100644 --- a/demonstrations_v2/ahs_aquila/demo.py +++ b/demonstrations_v2/ahs_aquila/demo.py @@ -816,3 +816,6 @@ def circuit(params): # `Amazon Quantum Computing Blog `__, 2021. ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/lillian_frederiksen.txt diff --git a/demonstrations_v2/ahs_aquila/metadata.json b/demonstrations_v2/ahs_aquila/metadata.json index 498d348c9b..fc530f8d99 100644 --- a/demonstrations_v2/ahs_aquila/metadata.json +++ b/demonstrations_v2/ahs_aquila/metadata.json @@ -1,82 +1,78 @@ { - "title": "Pulse programming on Rydberg atom hardware", - "authors": [ - { - "username": "lillian_frederiksen" - } - ], - "dateOfPublication": "2023-05-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Devices and Performance", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/ahs_aquila/thumbnail_tutorial_pulse_on_hardware.png" - } - ], - "seoDescription": "Perform measurements on neutral atom hardware through PennyLane", - "doi": "", - "references": [ - { - "id": "Semeghini", - "type": "article", - "title": "Probing topological spin liquids on a programmable quantum simulator", - "authors": "G. Semeghini, H. Levine, A. Keesling, S. Ebadi, T.T. Wang, D. Bluvstein, R. Verresen, H. Pichler, M. Kalinowski, R. Samajdar, A. Omran, S. Sachdev, A. Vishwanath, M. Greiner, V. Vuletic, M.D. Lukin", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2104.04119" - }, - { - "id": "Lienhard", - "type": "article", - "title": "Observing the Space- and Time-Dependent Growth of Correlations in Dynamically Tuned Synthetic Ising Models with Antiferromagnetic Interactions", - "authors": "V. Lienhard, S. de L\u00e9s\u00e9leuc, D. Barredo, T. Lahaye, A. Browaeys, M. Schuler, L.-P. Henry, A.M. L\u00e4uchli", - "year": "2018", - "journal": "", - "url": "https://arxiv.org/abs/1711.01185" - }, - { - "id": "BraketDevGuide", - "type": "webpage", - "title": "Hello AHS: Run your first Analog Hamiltonian Simulation", - "authors": "Amazon Web Services: Amazon Braket", - "journal": "", - "url": "https://docs.aws.amazon.com/braket/latest/developerguide/braket-get-started-hello-ahs.html" - }, - { - "id": "Asthana2022", - "type": "article", - "title": "AWS Quantum Technologies Blog: Realizing quantum spin liquid phase on an analog Hamiltonian Rydberg simulator", - "authors": "Alexander Keesling, Eric Kessler, and Peter Komar", - "year": "2021", - "journal": "", - "url": "https://aws.amazon.com/blogs/quantum-computing/realizing-quantum-spin-liquid-phase-on-an-analog-hamiltonian-rydberg-simulator/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pasqal", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_pulse_programming101", - "weight": 1.0 - } - ], - "hardware": [ - { - "id": "aws", - "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/analog_hamiltonian_simulation/06_Analog_Hamiltonian_simulation_with_PennyLane.ipynb", - "logo": "/_static/hardware_logos/aws.png" - } - ] -} \ No newline at end of file + "title": "Pulse programming on Rydberg atom hardware", + "authors": [ + { + "username": "lillian_frederiksen" + } + ], + "dateOfPublication": "2023-05-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": ["Quantum Hardware", "Devices and Performance", "Quantum Computing"], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/ahs_aquila/thumbnail_tutorial_pulse_on_hardware.png" + } + ], + "seoDescription": "Perform measurements on neutral atom hardware through PennyLane", + "doi": "", + "references": [ + { + "id": "Semeghini", + "type": "article", + "title": "Probing topological spin liquids on a programmable quantum simulator", + "authors": "G. Semeghini, H. Levine, A. Keesling, S. Ebadi, T.T. Wang, D. Bluvstein, R. Verresen, H. Pichler, M. Kalinowski, R. Samajdar, A. Omran, S. Sachdev, A. Vishwanath, M. Greiner, V. Vuletic, M.D. Lukin", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2104.04119" + }, + { + "id": "Lienhard", + "type": "article", + "title": "Observing the Space- and Time-Dependent Growth of Correlations in Dynamically Tuned Synthetic Ising Models with Antiferromagnetic Interactions", + "authors": "V. Lienhard, S. de Léséleuc, D. Barredo, T. Lahaye, A. Browaeys, M. Schuler, L.-P. Henry, A.M. Läuchli", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1711.01185" + }, + { + "id": "BraketDevGuide", + "type": "webpage", + "title": "Hello AHS: Run your first Analog Hamiltonian Simulation", + "authors": "Amazon Web Services: Amazon Braket", + "journal": "", + "url": "https://docs.aws.amazon.com/braket/latest/developerguide/braket-get-started-hello-ahs.html" + }, + { + "id": "Asthana2022", + "type": "article", + "title": "AWS Quantum Technologies Blog: Realizing quantum spin liquid phase on an analog Hamiltonian Rydberg simulator", + "authors": "Alexander Keesling, Eric Kessler, and Peter Komar", + "year": "2021", + "journal": "", + "url": "https://aws.amazon.com/blogs/quantum-computing/realizing-quantum-spin-liquid-phase-on-an-analog-hamiltonian-rydberg-simulator/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/analog_hamiltonian_simulation/06_Analog_Hamiltonian_simulation_with_PennyLane.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} diff --git a/demonstrations_v2/braket-parallel-gradients/demo.py b/demonstrations_v2/braket-parallel-gradients/demo.py index d436675e3e..a1d4f8b446 100644 --- a/demonstrations_v2/braket-parallel-gradients/demo.py +++ b/demonstrations_v2/braket-parallel-gradients/demo.py @@ -606,3 +606,10 @@ def cost_function(params, **kwargs): # ############################################################################## +# About the authors +# ----------------- +# .. include:: ../_static/authors/thomas_bromley.txt +# +# .. include:: ../_static/authors/maria_schuld.txt +# +# .. include:: ../_static/authors/matthew_beach.txt diff --git a/demonstrations_v2/braket-parallel-gradients/metadata.json b/demonstrations_v2/braket-parallel-gradients/metadata.json index a7a102bba3..d30f265913 100644 --- a/demonstrations_v2/braket-parallel-gradients/metadata.json +++ b/demonstrations_v2/braket-parallel-gradients/metadata.json @@ -1,50 +1,50 @@ { - "title": "Computing gradients in parallel with Amazon Braket", - "authors": [ - { - "username": "trbromley" - }, - { - "username": "mariaschuld" - }, - { - "username": "mbeach" - } - ], - "dateOfPublication": "2020-12-08T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_computing_gradients_amazon_braket.png" - } - ], - "seoDescription": "Parallelize gradient calculations with Amazon Braket", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qaoa_intro", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "vqe_parallel", - "weight": 1.0 - } - ], - "hardware": [ - { - "id": "aws", - "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/1_Parallelized_optimization_of_quantum_circuits/1_Parallelized_optimization_of_quantum_circuits.ipynb", - "logo": "/_static/hardware_logos/aws.png" - } - ] -} \ No newline at end of file + "title": "Computing gradients in parallel with Amazon Braket", + "authors": [ + { + "username": "trbromley" + }, + { + "username": "mariaschuld" + }, + { + "username": "mbeach" + } + ], + "dateOfPublication": "2020-12-08T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_computing_gradients_amazon_braket.png" + } + ], + "seoDescription": "Parallelize gradient calculations with Amazon Braket", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "vqe_parallel", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/1_Parallelized_optimization_of_quantum_circuits/1_Parallelized_optimization_of_quantum_circuits.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} diff --git a/demonstrations_v2/circuits_as_fourier_series/demo.py b/demonstrations_v2/circuits_as_fourier_series/demo.py index 4f6f60f3aa..bdfa30e199 100644 --- a/demonstrations_v2/circuits_as_fourier_series/demo.py +++ b/demonstrations_v2/circuits_as_fourier_series/demo.py @@ -798,3 +798,7 @@ """ ############################################################################## +# About the author +# ---------------- +# +# .. include:: ../_static/authors/david_wakeham.txt diff --git a/demonstrations_v2/circuits_as_fourier_series/metadata.json b/demonstrations_v2/circuits_as_fourier_series/metadata.json index 111a70eecf..bfb1b18769 100644 --- a/demonstrations_v2/circuits_as_fourier_series/metadata.json +++ b/demonstrations_v2/circuits_as_fourier_series/metadata.json @@ -1,47 +1,47 @@ { - "title": "Circuits as Fourier series", - "authors": [ - { - "username": "hapax" - } - ], - "dateOfPublication": "2023-09-11T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization", - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/circuits_as_fourier_series/thumbnail_circuits_as_fourier_series.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_circuits_as_fourier_series.png" - } - ], - "seoDescription": "Learn with this interactive, code-free introduction to the idea of quantum circuits as Fourier series.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_expressivity_fourier_series", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_general_parshift", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_stochastic_parameter_shift", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Circuits as Fourier series", + "authors": [ + { + "username": "hapax" + } + ], + "dateOfPublication": "2023-09-11T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/circuits_as_fourier_series/thumbnail_circuits_as_fourier_series.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_circuits_as_fourier_series.png" + } + ], + "seoDescription": "Learn with this interactive, code-free introduction to the idea of quantum circuits as Fourier series.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_stochastic_parameter_shift", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/covalent_cloud_gpu/demo.py b/demonstrations_v2/covalent_cloud_gpu/demo.py index 4846c01342..2dbcf02894 100644 --- a/demonstrations_v2/covalent_cloud_gpu/demo.py +++ b/demonstrations_v2/covalent_cloud_gpu/demo.py @@ -367,3 +367,6 @@ def run_qsvm(n_samples, test_size): # ############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/covalent_cloud_gpu/metadata.json b/demonstrations_v2/covalent_cloud_gpu/metadata.json index 3b079b3760..56861d3722 100644 --- a/demonstrations_v2/covalent_cloud_gpu/metadata.json +++ b/demonstrations_v2/covalent_cloud_gpu/metadata.json @@ -1,56 +1,56 @@ { - "title": "Running GPU-accelerated quantum circuit simulations on Covalent Cloud using PennyLane", - "authors": [ - { - "username": "skradha" - }, - { - "username": "aghukasyan" - } - ], - "dateOfPublication": "2024-05-24T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/covalent_cloud_gpu/thumbnail_covalent_cloud_gpu.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_covalent_cloud_gpu.png" - } - ], - "seoDescription": "Explore the process of running GPU-accelerated quantum circuit simulations on Covalent Cloud using PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_kernels_module", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_kernel_based_training", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_univariate_qvr", - "weight": 1.0 - } - ], - "hardware": [ - { - "id": "covalent", - "link": "https://docs.covalent.xyz/docs/cloud/tutorials-cloud/pennylane_tutorial/", - "logo": "/_static/hardware_logos/covalent.png" - } - ] -} \ No newline at end of file + "title": "Running GPU-accelerated quantum circuit simulations on Covalent Cloud using PennyLane", + "authors": [ + { + "username": "skradha" + }, + { + "username": "aghukasyan" + } + ], + "dateOfPublication": "2024-05-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/covalent_cloud_gpu/thumbnail_covalent_cloud_gpu.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_covalent_cloud_gpu.png" + } + ], + "seoDescription": "Explore the process of running GPU-accelerated quantum circuit simulations on Covalent Cloud using PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_univariate_qvr", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "covalent", + "link": "https://docs.covalent.xyz/docs/cloud/tutorials-cloud/pennylane_tutorial/", + "logo": "/_static/hardware_logos/covalent.png" + } + ] +} diff --git a/demonstrations_v2/ensemble_multi_qpu/demo.py b/demonstrations_v2/ensemble_multi_qpu/demo.py index d72b843c17..55b83eebd5 100644 --- a/demonstrations_v2/ensemble_multi_qpu/demo.py +++ b/demonstrations_v2/ensemble_multi_qpu/demo.py @@ -576,3 +576,6 @@ def plot_points_prediction(x, y, p, title): # hydrogen! ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/thomas_bromley.txt \ No newline at end of file diff --git a/demonstrations_v2/ensemble_multi_qpu/metadata.json b/demonstrations_v2/ensemble_multi_qpu/metadata.json index 2765d91fa8..2163c50b80 100644 --- a/demonstrations_v2/ensemble_multi_qpu/metadata.json +++ b/demonstrations_v2/ensemble_multi_qpu/metadata.json @@ -1,32 +1,32 @@ { - "title": "Ensemble classification with Rigetti and Qiskit devices", - "authors": [ - { - "username": "trbromley" - } - ], - "dateOfPublication": "2020-02-14T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_ensemble_classification_rigetti_qiskit.png" - } - ], - "seoDescription": "We demonstrate how two QPUs can be combined in parallel to help solve a machine learning classification problem, using PyTorch and PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Ensemble classification with Rigetti and Qiskit devices", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-02-14T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_ensemble_classification_rigetti_qiskit.png" + } + ], + "seoDescription": "We demonstrate how two QPUs can be combined in parallel to help solve a machine learning classification problem, using PyTorch and PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/function_fitting_qsp/demo.py b/demonstrations_v2/function_fitting_qsp/demo.py index c973108860..6de7b5afb5 100644 --- a/demonstrations_v2/function_fitting_qsp/demo.py +++ b/demonstrations_v2/function_fitting_qsp/demo.py @@ -574,3 +574,7 @@ def step_func(x): # 040203 `__\ *, 2021.* # # +# About the author +# ~~~~~~~~~~~~~~~~ +# .. include:: ../_static/authors/jay_soni.txt +# diff --git a/demonstrations_v2/function_fitting_qsp/metadata.json b/demonstrations_v2/function_fitting_qsp/metadata.json index 1c2e5c2863..192492b725 100644 --- a/demonstrations_v2/function_fitting_qsp/metadata.json +++ b/demonstrations_v2/function_fitting_qsp/metadata.json @@ -1,38 +1,38 @@ { - "title": "Function Fitting using Quantum Signal Processing", - "authors": [ - { - "username": "Jay" - } - ], - "dateOfPublication": "2022-05-24T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_function_fitting_quantum_signal_processing.png" - } - ], - "seoDescription": "Learn how to create polynomial approximations to functions using Quantum Signal Processing (QSP).", - "doi": "", - "references": [ - { - "id": "Martyn2021", - "type": "article", - "title": "A Grand Unification of Quantum Algorithms", - "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang", - "year": "2021", - "journal": "PRX Quantum 2, 040203", - "doi": "10.48550/arXiv.2105.02859" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2105.02859" - ], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Function Fitting using Quantum Signal Processing", + "authors": [ + { + "username": "Jay" + } + ], + "dateOfPublication": "2022-05-24T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_function_fitting_quantum_signal_processing.png" + } + ], + "seoDescription": "Learn how to create polynomial approximations to functions using Quantum Signal Processing (QSP).", + "doi": "", + "references": [ + { + "id": "Martyn2021", + "type": "article", + "title": "A Grand Unification of Quantum Algorithms", + "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang", + "year": "2021", + "journal": "PRX Quantum 2, 040203", + "doi": "10.48550/arXiv.2105.02859" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2105.02859" + ], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/gbs/demo.py b/demonstrations_v2/gbs/demo.py index 1da37f6ed5..b5c2ccbb61 100644 --- a/demonstrations_v2/gbs/demo.py +++ b/demonstrations_v2/gbs/demo.py @@ -476,3 +476,8 @@ def gbs_circuit(): # photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/josh_izaac.txt +# +# .. include:: ../_static/authors/nathan_killoran.txt diff --git a/demonstrations_v2/gbs/metadata.json b/demonstrations_v2/gbs/metadata.json index 39f5110ae9..b26778084c 100644 --- a/demonstrations_v2/gbs/metadata.json +++ b/demonstrations_v2/gbs/metadata.json @@ -1,100 +1,100 @@ { - "title": "Quantum advantage with Gaussian Boson Sampling", - "authors": [ - { - "username": "josh" - }, - { - "username": "co9olguy" - } - ], - "dateOfPublication": "2020-12-04T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_advantage_guassian_boson_sampling.png" - } - ], - "seoDescription": "Using light to perform tasks beyond the reach of classical computers.", - "doi": "", - "references": [ - { - "id": "Arute2019", - "type": "article", - "title": "Quantum supremacy using a programmable superconducting processor", - "authors": "Arute, F., Arya, K., Babbush, R., et al.", - "year": "2019", - "journal": "Nature", - "url": "https://doi.org/10.1038/s41586-019-1666-5" - }, - { - "id": "Zhong2020", - "type": "article", - "title": "Quantum computational advantage using photons", - "authors": "Zhong, H.-S., Wang, H., Deng, Y.-H., et al.", - "year": "2020", - "journal": "Science", - "doi": "10.1126/science.abe8770", - "url": "" - }, - { - "id": "hamilton2017", - "type": "article", - "title": "Gaussian boson sampling", - "authors": "Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, and Igor Jex", - "year": "2017", - "journal": "Physical Review Letters", - "doi": "10.1103/PhysRevLett.119.170501", - "url": "" - }, - { - "id": "aaronson2013", - "type": "article", - "title": "The computational complexity of linear optics", - "authors": "Scott Aaronson and Alex Arkhipov", - "year": "2013", - "journal": "Theory of Computing", - "doi": "10.4086/toc.2013.v009a004", - "url": "" - }, - { - "id": "Bourassa2020", - "type": "article", - "title": "Blueprint for a scalable photonic fault-tolerant quantum computer", - "authors": "Bourassa, J. E., Alexander, R. N., Vasmer, et al.", - "year": "2020", - "journal": "", - "url": "" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_gaussian_transformation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "qsim_beyond_classical", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "qonn", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_photonics", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum advantage with Gaussian Boson Sampling", + "authors": [ + { + "username": "josh" + }, + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-12-04T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_advantage_guassian_boson_sampling.png" + } + ], + "seoDescription": "Using light to perform tasks beyond the reach of classical computers.", + "doi": "", + "references": [ + { + "id": "Arute2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Arute, F., Arya, K., Babbush, R., et al.", + "year": "2019", + "journal": "Nature", + "url": "https://doi.org/10.1038/s41586-019-1666-5" + }, + { + "id": "Zhong2020", + "type": "article", + "title": "Quantum computational advantage using photons", + "authors": "Zhong, H.-S., Wang, H., Deng, Y.-H., et al.", + "year": "2020", + "journal": "Science", + "doi": "10.1126/science.abe8770", + "url": "" + }, + { + "id": "hamilton2017", + "type": "article", + "title": "Gaussian boson sampling", + "authors": "Craig S. Hamilton, Regina Kruse, Linda Sansoni, Sonja Barkhofen, Christine Silberhorn, and Igor Jex", + "year": "2017", + "journal": "Physical Review Letters", + "doi": "10.1103/PhysRevLett.119.170501", + "url": "" + }, + { + "id": "aaronson2013", + "type": "article", + "title": "The computational complexity of linear optics", + "authors": "Scott Aaronson and Alex Arkhipov", + "year": "2013", + "journal": "Theory of Computing", + "doi": "10.4086/toc.2013.v009a004", + "url": "" + }, + { + "id": "Bourassa2020", + "type": "article", + "title": "Blueprint for a scalable photonic fault-tolerant quantum computer", + "authors": "Bourassa, J. E., Alexander, R. N., Vasmer, et al.", + "year": "2020", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_gaussian_transformation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qsim_beyond_classical", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qonn", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py b/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py index 5777735d78..9dc028badc 100644 --- a/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py @@ -402,3 +402,6 @@ def circuit(params): ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/matthew_beach.txt diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json b/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json index faa27af773..3000431cf0 100644 --- a/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json @@ -1,37 +1,37 @@ { - "title": "Getting started with the Amazon Braket Hybrid Jobs", - "authors": [ - { - "username": "mbeach" - } - ], - "dateOfPublication": "2023-10-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/getting_started_with_hybrid_jobs/thumbnail_getting_started_with_hybrid_jobs.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_getting_started_with_hybrid_jobs.png" - } - ], - "seoDescription": "Getting started with the Amazon Braket Hybrid Jobs", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [], - "hardware": [ - { - "id": "aws", - "link": "https://github.com/amazon-braket/amazon-braket-examples/tree/main/examples/hybrid_jobs", - "logo": "/_static/hardware_logos/aws.png" - } - ] -} \ No newline at end of file + "title": "Getting started with the Amazon Braket Hybrid Jobs", + "authors": [ + { + "username": "mbeach" + } + ], + "dateOfPublication": "2023-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/getting_started_with_hybrid_jobs/thumbnail_getting_started_with_hybrid_jobs.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_getting_started_with_hybrid_jobs.png" + } + ], + "seoDescription": "Getting started with the Amazon Braket Hybrid Jobs", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/tree/main/examples/hybrid_jobs", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} diff --git a/demonstrations_v2/gqe_training/demo.py b/demonstrations_v2/gqe_training/demo.py index 6d0b112912..29d9a53530 100644 --- a/demonstrations_v2/gqe_training/demo.py +++ b/demonstrations_v2/gqe_training/demo.py @@ -703,3 +703,5 @@ def generate(self, n_sequences, max_new_tokens, temperature=1.0, device="cpu"): # ###################################################################### +# About the authors +# ----------------- diff --git a/demonstrations_v2/gqe_training/metadata.json b/demonstrations_v2/gqe_training/metadata.json index a20b54000a..8e364cfdcb 100644 --- a/demonstrations_v2/gqe_training/metadata.json +++ b/demonstrations_v2/gqe_training/metadata.json @@ -1,77 +1,73 @@ { - "title": "Generative quantum eigensolver training using PennyLane data", - "authors": [ - { - "username": "Joseph" - }, - { - "username": "zy_n" - } - ], - "dateOfPublication": "2024-09-20T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Quantum Chemistry", - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generative_quantum_eigensolver.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_generative_quantum_eigensolver.png" - } - ], - "seoDescription": "Learn how you can train a small GPT model using the generative quantum eigensolver (GQE) technique and PennyLane data.", - "doi": "", - "references": [ - { - "id": "nakaji2024", - "type": "article", - "title": "The generative quantum eigensolver (GQE) and its application for ground state search", - "authors": "K. Nakaji, L. B. Kristensen, J. A. Campos-Gonzalez-Angulo, M. G. Vakili, H. Huang, M. Bagherimehrab, C. Gorgulla, F. Wong, A. McCaskey, J. S. Kim, T. Nguyen, P. Rao, A. Aspuru-Guzik", - "year": "2024", - "journal": "", - "url": "https://arxiv.org/abs/2401.09253" - }, - { - "id": "radford2019", - "type": "article", - "title": "Language Models are Unsupervised Multitask Learners", - "authors": "A. Radford, J. Wu, R. Child, D. Luan, D. Amodei, I. Sutskever", - "year": "2019", - "journal": "OpenAI blog", - "url": "https://openai.com/index/better-language-models/" - }, - { - "id": "vaswani2017", - "type": "article", - "title": "Attention is All you Need", - "authors": "A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, L. Kaiser, I. Polosukhin", - "year": "2017", - "journal": "Advances in Neural Information Processing Systems", - "url": "https://papers.nips.cc/paper_files/paper/2017/hash/3f5ee243547dee91fbd053c1c4a845aa-Abstract.html" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2401.09253" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe_qng", - "weight": 1.0 - } - ], - "hardware": [] -} \ No newline at end of file + "title": "Generative quantum eigensolver training using PennyLane data", + "authors": [ + { + "username": "Joseph" + }, + { + "username": "zy_n" + } + ], + "dateOfPublication": "2024-09-20T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": ["Quantum Machine Learning", "Quantum Chemistry", "Algorithms"], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generative_quantum_eigensolver.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_generative_quantum_eigensolver.png" + } + ], + "seoDescription": "Learn how you can train a small GPT model using the generative quantum eigensolver (GQE) technique and PennyLane data.", + "doi": "", + "references": [ + { + "id": "nakaji2024", + "type": "article", + "title": "The generative quantum eigensolver (GQE) and its application for ground state search", + "authors": "K. Nakaji, L. B. Kristensen, J. A. Campos-Gonzalez-Angulo, M. G. Vakili, H. Huang, M. Bagherimehrab, C. Gorgulla, F. Wong, A. McCaskey, J. S. Kim, T. Nguyen, P. Rao, A. Aspuru-Guzik", + "year": "2024", + "journal": "", + "url": "https://arxiv.org/abs/2401.09253" + }, + { + "id": "radford2019", + "type": "article", + "title": "Language Models are Unsupervised Multitask Learners", + "authors": "A. Radford, J. Wu, R. Child, D. Luan, D. Amodei, I. Sutskever", + "year": "2019", + "journal": "OpenAI blog", + "url": "https://openai.com/index/better-language-models/" + }, + { + "id": "vaswani2017", + "type": "article", + "title": "Attention is All you Need", + "authors": "A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, L. Kaiser, I. Polosukhin", + "year": "2017", + "journal": "Advances in Neural Information Processing Systems", + "url": "https://papers.nips.cc/paper_files/paper/2017/hash/3f5ee243547dee91fbd053c1c4a845aa-Abstract.html" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2401.09253" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py index 26c699a067..1a6e0ca61a 100644 --- a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py @@ -428,3 +428,7 @@ def cost(phis, theta): # ###################################################################### +# About the author +# ---------------- +# .. include:: ../_static/authors/isaac_de_vlugt.txt +# diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json index 1fa88996ca..3c9d27a9ba 100644 --- a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json @@ -1,57 +1,57 @@ { - "title": "How to use Qiskit 1.0 with PennyLane", - "authors": [ - { - "username": "isaacdevlugt" - } - ], - "dateOfPublication": "2024-07-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_HowToUseQiskit1WithPL.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_HowToUseQiskit1WithPL.png" - } - ], - "seoDescription": "Learn how to get started using Qiskit 1.0 with PennyLane", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_guide_to_pennylane_knowing_qiskit", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qubit_rotation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_diffable-mitigation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_jax_transformations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qnn_module_torch", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to use Qiskit 1.0 with PennyLane", + "authors": [ + { + "username": "isaacdevlugt" + } + ], + "dateOfPublication": "2024-07-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_HowToUseQiskit1WithPL.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_HowToUseQiskit1WithPL.png" + } + ], + "seoDescription": "Learn how to get started using Qiskit 1.0 with PennyLane", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_guide_to_pennylane_knowing_qiskit", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qnn_module_torch", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/ibm_pennylane/demo.py b/demonstrations_v2/ibm_pennylane/demo.py index 85f2a8c93b..eb1101d4bc 100644 --- a/demonstrations_v2/ibm_pennylane/demo.py +++ b/demonstrations_v2/ibm_pennylane/demo.py @@ -360,3 +360,8 @@ def cost_fn_2(theta): # This tutorial has demonstrated how and why to use quantum computing hardware provided by IBM using PennyLane. To read # more about the details and possibilities of the PennyLane-Qiskit plugin, `read the documentation `__. # +# About the authors +# ---------------- +# .. include:: ../_static/authors/kaur_kristjuhan.txt +# .. include:: ../_static/authors/clara_ferreira_cores.txt +# .. include:: ../_static/authors/mark_nicholas_jones.txt diff --git a/demonstrations_v2/ibm_pennylane/metadata.json b/demonstrations_v2/ibm_pennylane/metadata.json index 32b1483537..351c3d6d25 100644 --- a/demonstrations_v2/ibm_pennylane/metadata.json +++ b/demonstrations_v2/ibm_pennylane/metadata.json @@ -1,47 +1,47 @@ { - "title": "Using PennyLane with IBM's quantum devices and Qiskit", - "authors": [ - { - "username": "kkristjuhan" - }, - { - "username": "cfcores" - }, - { - "username": "mnjones" - } - ], - "dateOfPublication": "2023-06-20T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/ibm_pennylane/thumbnail_tutorial_ibm_pennylane.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_ibm_pennylane.png" - } - ], - "seoDescription": "Learn how to use IBM devices with PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "quantum_volume", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Using PennyLane with IBM's quantum devices and Qiskit", + "authors": [ + { + "username": "kkristjuhan" + }, + { + "username": "cfcores" + }, + { + "username": "mnjones" + } + ], + "dateOfPublication": "2023-06-20T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/ibm_pennylane/thumbnail_tutorial_ibm_pennylane.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_ibm_pennylane.png" + } + ], + "seoDescription": "Learn how to use IBM devices with PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/learning2learn/demo.py b/demonstrations_v2/learning2learn/demo.py index eb948e2b54..effa725a2e 100644 --- a/demonstrations_v2/learning2learn/demo.py +++ b/demonstrations_v2/learning2learn/demo.py @@ -1179,3 +1179,6 @@ def call(self, inputs): # input, like ``qaoa_from_graph``. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/stefano_mangini.txt diff --git a/demonstrations_v2/learning2learn/metadata.json b/demonstrations_v2/learning2learn/metadata.json index d81a901fc4..f6b067357f 100644 --- a/demonstrations_v2/learning2learn/metadata.json +++ b/demonstrations_v2/learning2learn/metadata.json @@ -1,39 +1,39 @@ { - "title": "Learning to learn with quantum neural networks", - "authors": [ - { - "username": "smangini" - } - ], - "dateOfPublication": "2021-03-02T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_learning_to_learn_quantum_neural.png" - } - ], - "seoDescription": "Use a classical recurrent neural network to initilize the parameters of a variational quatum algorithm.", - "doi": "", - "references": [], - "basedOnPapers": [ - "10.48550/arXiv.1907.05415" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qaoa_intro", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qaoa_maxcut", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Learning to learn with quantum neural networks", + "authors": [ + { + "username": "smangini" + } + ], + "dateOfPublication": "2021-03-02T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_learning_to_learn_quantum_neural.png" + } + ], + "seoDescription": "Use a classical recurrent neural network to initilize the parameters of a variational quatum algorithm.", + "doi": "", + "references": [], + "basedOnPapers": [ + "10.48550/arXiv.1907.05415" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/ml_classical_shadows/demo.py b/demonstrations_v2/ml_classical_shadows/demo.py index 76d7b7932b..4d290982a4 100644 --- a/demonstrations_v2/ml_classical_shadows/demo.py +++ b/demonstrations_v2/ml_classical_shadows/demo.py @@ -857,3 +857,6 @@ def fit_predict_data(cij, kernel, opt="linear"): # `__ (2018) # # +# About the author +# ---------------- + diff --git a/demonstrations_v2/ml_classical_shadows/metadata.json b/demonstrations_v2/ml_classical_shadows/metadata.json index 528decaa69..3b515b5fc0 100644 --- a/demonstrations_v2/ml_classical_shadows/metadata.json +++ b/demonstrations_v2/ml_classical_shadows/metadata.json @@ -1,67 +1,65 @@ { - "title": "Machine learning for quantum many-body problems", - "authors": [ - { - "username": "whatsis" - } - ], - "dateOfPublication": "2022-05-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_machine_learning_quantum_manybody_problems.png" - }, - { - "type": "large_thumbnail", - "uri": "_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_machine_learning_quantum_manybody_problems.png" - } - ], - "seoDescription": "Learn how to apply machine learning to quantum many-body problems by using classical shadow formalism and ML to predict the ground-state properties of the 2D antiferromagnetic Heisenberg model.", - "doi": "", - "references": [ - { - "id": "preskill", - "type": "article", - "title": "Provably efficient machine learning for quantum many-body problems", - "authors": "H. Y. Huang, R. Kueng, G. Torlai, V. V. Albert, J. Preskill", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2106.12627" - }, - { - "id": "neurtangkernel", - "type": "article", - "title": "Neural tangent kernel: Convergence and generalization in neural networks", - "authors": "A. Jacot, F. Gabriel, and C. Hongler", - "year": "2018", - "journal": "", - "url": "https://proceedings.neurips.cc/paper/2018/file/5a4be1fa34e62bb8a6ec6b91d2462f5a-Paper.pdf" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2106.12627" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_classical_shadows", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_kernel_based_training", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_kernels_module", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Machine learning for quantum many-body problems", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2022-05-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_machine_learning_quantum_manybody_problems.png" + }, + { + "type": "large_thumbnail", + "uri": "_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_machine_learning_quantum_manybody_problems.png" + } + ], + "seoDescription": "Learn how to apply machine learning to quantum many-body problems by using classical shadow formalism and ML to predict the ground-state properties of the 2D antiferromagnetic Heisenberg model.", + "doi": "", + "references": [ + { + "id": "preskill", + "type": "article", + "title": "Provably efficient machine learning for quantum many-body problems", + "authors": "H. Y. Huang, R. Kueng, G. Torlai, V. V. Albert, J. Preskill", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2106.12627" + }, + { + "id": "neurtangkernel", + "type": "article", + "title": "Neural tangent kernel: Convergence and generalization in neural networks", + "authors": "A. Jacot, F. Gabriel, and C. Hongler", + "year": "2018", + "journal": "", + "url": "https://proceedings.neurips.cc/paper/2018/file/5a4be1fa34e62bb8a6ec6b91d2462f5a-Paper.pdf" + } + ], + "basedOnPapers": ["10.48550/arXiv.2106.12627"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/oqc_pulse/demo.py b/demonstrations_v2/oqc_pulse/demo.py index c4cc91718b..5c3bd0ab50 100644 --- a/demonstrations_v2/oqc_pulse/demo.py +++ b/demonstrations_v2/oqc_pulse/demo.py @@ -543,3 +543,6 @@ def qnode_lucy(params, duration=15.0): # `arXiv:2309.16756 `__, 2023. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt diff --git a/demonstrations_v2/oqc_pulse/metadata.json b/demonstrations_v2/oqc_pulse/metadata.json index 2ccea681f7..1c964a11be 100644 --- a/demonstrations_v2/oqc_pulse/metadata.json +++ b/demonstrations_v2/oqc_pulse/metadata.json @@ -1,73 +1,73 @@ { - "title": "Pulse programming on OQC Lucy in PennyLane", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2023-10-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/oqc_pulse/thumbnail_intro_oqc_pulse.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_oqc_pulse.png" - } - ], - "seoDescription": "Learn how to create polynomial approximations to functions using Quantum Signal Processing (QSP).", - "doi": "", - "references": [ - { - "id": "Rahamim", - "type": "article", - "title": "Double-sided coaxial circuit QED with out-of-plane wiring", - "authors": "J. Rahamim, T. Behrle, M. J. Peterer, A. Patterson, P. Spring, T. Tsunoda, R. Manenti, G. Tancredi, P. J. Leek", - "year": "2017", - "journal": "Appl. Phys. Lett. 110, 222602 (2017)", - "doi": "10.1063/1.4984299", - "url": "https://arxiv.org/abs/1703.05828" - }, - { - "id": "Krantz", - "type": "article", - "title": "A Quantum Engineer's Guide to Superconducting Qubits", - "authors": "Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver", - "year": "2017", - "journal": "Applied Physics Reviews 6, 021318 (2019)", - "doi": "10.1063/1.5089550", - "url": "https://arxiv.org/abs/1904.06560" - }, - { - "id": "Kottmann", - "type": "preprint", - "title": "Evaluating analytic gradients of pulse programs on quantum computers", - "authors": "Korbinian Kottmann, Nathan Killoran", - "year": "2023", - "doi": "10.48550/arXiv.2309.16756", - "url": "https://arxiv.org/abs/2309.16756" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2309.16756" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pulse_programming101", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "ahs_aquila", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Pulse programming on OQC Lucy in PennyLane", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-10-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/oqc_pulse/thumbnail_intro_oqc_pulse.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_oqc_pulse.png" + } + ], + "seoDescription": "Learn how to create polynomial approximations to functions using Quantum Signal Processing (QSP).", + "doi": "", + "references": [ + { + "id": "Rahamim", + "type": "article", + "title": "Double-sided coaxial circuit QED with out-of-plane wiring", + "authors": "J. Rahamim, T. Behrle, M. J. Peterer, A. Patterson, P. Spring, T. Tsunoda, R. Manenti, G. Tancredi, P. J. Leek", + "year": "2017", + "journal": "Appl. Phys. Lett. 110, 222602 (2017)", + "doi": "10.1063/1.4984299", + "url": "https://arxiv.org/abs/1703.05828" + }, + { + "id": "Krantz", + "type": "article", + "title": "A Quantum Engineer's Guide to Superconducting Qubits", + "authors": "Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver", + "year": "2017", + "journal": "Applied Physics Reviews 6, 021318 (2019)", + "doi": "10.1063/1.5089550", + "url": "https://arxiv.org/abs/1904.06560" + }, + { + "id": "Kottmann", + "type": "preprint", + "title": "Evaluating analytic gradients of pulse programs on quantum computers", + "authors": "Korbinian Kottmann, Nathan Killoran", + "year": "2023", + "doi": "10.48550/arXiv.2309.16756", + "url": "https://arxiv.org/abs/2309.16756" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2309.16756" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/plugins_hybrid/demo.py b/demonstrations_v2/plugins_hybrid/demo.py index 07455d4915..5e8048497f 100644 --- a/demonstrations_v2/plugins_hybrid/demo.py +++ b/demonstrations_v2/plugins_hybrid/demo.py @@ -447,3 +447,6 @@ def cost(params, phi1=0.5, phi2=0.1): # functions can be combined in many different and interesting ways. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/plugins_hybrid/metadata.json b/demonstrations_v2/plugins_hybrid/metadata.json index ebe347d467..355c197960 100644 --- a/demonstrations_v2/plugins_hybrid/metadata.json +++ b/demonstrations_v2/plugins_hybrid/metadata.json @@ -1,37 +1,37 @@ { - "title": "Plugins and hybrid computation", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_plugins_hybrid_computation.png" - } - ], - "seoDescription": "This tutorial introduces the notion of hybrid computation by combining several PennyLane device backends to train an algorithm containing both photonic and qubit devices.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qubit_rotation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_gaussian_transformation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Plugins and hybrid computation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_plugins_hybrid_computation.png" + } + ], + "seoDescription": "This tutorial introduces the notion of hybrid computation by combining several PennyLane device backends to train an algorithm containing both photonic and qubit devices.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_gaussian_transformation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/pytorch_noise/demo.py b/demonstrations_v2/pytorch_noise/demo.py index 88d8df0d19..1593bc3f90 100644 --- a/demonstrations_v2/pytorch_noise/demo.py +++ b/demonstrations_v2/pytorch_noise/demo.py @@ -249,3 +249,6 @@ def cost(phi, theta, step): # .. note:: For more details on the PyTorch interface, see :doc:`introduction/interfaces/torch`. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/pytorch_noise/metadata.json b/demonstrations_v2/pytorch_noise/metadata.json index 98429cab62..3bb21e9b33 100644 --- a/demonstrations_v2/pytorch_noise/metadata.json +++ b/demonstrations_v2/pytorch_noise/metadata.json @@ -1,39 +1,39 @@ { - "title": "PyTorch and noisy devices", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_PyTorch_noisy_devices.png" - } - ], - "seoDescription": "Extend PyTorch with real quantum computing power, by using it to optimize a noisy quantum hardware device.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_noisy_circuit_optimization", - "weight": 1.0 - } - ], - "hardware": [ - { - "id": "aws", - "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/4_Simulation_of_noisy_quantum_circuits_on_Amazon_Braket_with_PennyLane/4_Simulation_of_noisy_quantum_circuits_on_Amazon_Braket_with_PennyLane.ipynb", - "logo": "/_static/hardware_logos/aws.png" - } - ] -} \ No newline at end of file + "title": "PyTorch and noisy devices", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_PyTorch_noisy_devices.png" + } + ], + "seoDescription": "Extend PyTorch with real quantum computing power, by using it to optimize a noisy quantum hardware device.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/4_Simulation_of_noisy_quantum_circuits_on_Amazon_Braket_with_PennyLane/4_Simulation_of_noisy_quantum_circuits_on_Amazon_Braket_with_PennyLane.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} diff --git a/demonstrations_v2/qnspsa/demo.py b/demonstrations_v2/qnspsa/demo.py index 944d85ff83..d859f176e0 100644 --- a/demonstrations_v2/qnspsa/demo.py +++ b/demonstrations_v2/qnspsa/demo.py @@ -1038,3 +1038,6 @@ def __apply_blocking(self, cost, params_curr, params_next): # 36th IEEE Conference on Decision and Control (Vol. 2, pp. 1417-1424). # IEEE `__. # +# About the author +# ---------------- +# .. include:: ../_static/authors/yiheng_duan.txt diff --git a/demonstrations_v2/qnspsa/metadata.json b/demonstrations_v2/qnspsa/metadata.json index a7e70f05ba..2679e5668d 100644 --- a/demonstrations_v2/qnspsa/metadata.json +++ b/demonstrations_v2/qnspsa/metadata.json @@ -1,92 +1,92 @@ { - "title": "Quantum natural SPSA optimizer", - "authors": [ - { - "username": "yduan" - } - ], - "dateOfPublication": "2022-07-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_natural_SPSA_optimizer.png" - } - ], - "seoDescription": "Introduction to the Quantum natural SPSA optimizer, which reduces the number of quantum measurements in the optimization.", - "doi": "", - "references": [ - { - "id": "Gacon2021", - "type": "article", - "title": "Simultaneous perturbation stochastic approximation of the quantum fisher information", - "authors": "Gacon, J., Zoufal, C., Carleo, G., & Woerner, S.", - "year": "2021", - "journal": "Quantum", - "doi": "10.22331/q-2021-10-20-567", - "url": "https://quantum-journal.org/papers/q-2021-10-20-567/" - }, - { - "id": "SPSA", - "type": "webpage", - "title": "Simultaneous perturbation stochastic approximation", - "year": "2022", - "url": "https://en.wikipedia.org/wiki/Simultaneous_perturbation_stochastic_approximation" - }, - { - "id": "Stokes2020", - "type": "article", - "title": "Quantum natural gradient", - "authors": "Stokes, J., Izaac, J., Killoran, N., & Carleo, G.", - "year": "2020", - "journal": "Quantum", - "doi": "10.22331/q-2020-05-25-269", - "url": "https://quantum-journal.org/papers/q-2020-05-25-269/" - }, - { - "id": "FS", - "type": "webpage", - "title": "Fubini\u2013Study metric", - "year": "2022", - "url": "https://en.wikipedia.org/wiki/Fubini%E2%80%93Study_metric" - }, - { - "id": "Yamamoto2019", - "type": "article", - "title": "On the natural gradient for variational quantum eigensolver", - "authors": "Yamamoto, N.", - "year": "2019", - "journal": "", - "doi": "10.48550/arXiv.1909.05074", - "url": "https://arxiv.org/abs/1909.05074" - }, - { - "id": "Spall1997", - "type": "article", - "title": "Accelerated second-order stochastic optimization using only function measurements", - "authors": "Spall, J. C.", - "year": "1997", - "journal": "In Proceedings of the 36th IEEE Conference on Decision and Control", - "volume": "2", - "pages": "1417-1424", - "doi": "10.1109/CDC.1997.657661", - "url": "https://ieeexplore.ieee.org/document/657661" - } - ], - "basedOnPapers": [ - "10.22331/q-2021-10-20-567" - ], - "referencedByPapers": [], - "relatedContent": [], - "hardware": [ - { - "id": "aws", - "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/6_QNSPSA_optimizer_with_embedded_simulator/qnspsa_with_embedded_simulator.ipynb", - "logo": "/_static/hardware_logos/aws.png" - } - ] -} \ No newline at end of file + "title": "Quantum natural SPSA optimizer", + "authors": [ + { + "username": "yduan" + } + ], + "dateOfPublication": "2022-07-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_natural_SPSA_optimizer.png" + } + ], + "seoDescription": "Introduction to the Quantum natural SPSA optimizer, which reduces the number of quantum measurements in the optimization.", + "doi": "", + "references": [ + { + "id": "Gacon2021", + "type": "article", + "title": "Simultaneous perturbation stochastic approximation of the quantum fisher information", + "authors": "Gacon, J., Zoufal, C., Carleo, G., & Woerner, S.", + "year": "2021", + "journal": "Quantum", + "doi": "10.22331/q-2021-10-20-567", + "url": "https://quantum-journal.org/papers/q-2021-10-20-567/" + }, + { + "id": "SPSA", + "type": "webpage", + "title": "Simultaneous perturbation stochastic approximation", + "year": "2022", + "url": "https://en.wikipedia.org/wiki/Simultaneous_perturbation_stochastic_approximation" + }, + { + "id": "Stokes2020", + "type": "article", + "title": "Quantum natural gradient", + "authors": "Stokes, J., Izaac, J., Killoran, N., & Carleo, G.", + "year": "2020", + "journal": "Quantum", + "doi": "10.22331/q-2020-05-25-269", + "url": "https://quantum-journal.org/papers/q-2020-05-25-269/" + }, + { + "id": "FS", + "type": "webpage", + "title": "Fubini–Study metric", + "year": "2022", + "url": "https://en.wikipedia.org/wiki/Fubini%E2%80%93Study_metric" + }, + { + "id": "Yamamoto2019", + "type": "article", + "title": "On the natural gradient for variational quantum eigensolver", + "authors": "Yamamoto, N.", + "year": "2019", + "journal": "", + "doi": "10.48550/arXiv.1909.05074", + "url": "https://arxiv.org/abs/1909.05074" + }, + { + "id": "Spall1997", + "type": "article", + "title": "Accelerated second-order stochastic optimization using only function measurements", + "authors": "Spall, J. C.", + "year": "1997", + "journal": "In Proceedings of the 36th IEEE Conference on Decision and Control", + "volume": "2", + "pages": "1417-1424", + "doi": "10.1109/CDC.1997.657661", + "url": "https://ieeexplore.ieee.org/document/657661" + } + ], + "basedOnPapers": [ + "10.22331/q-2021-10-20-567" + ], + "referencedByPapers": [], + "relatedContent": [], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/hybrid_jobs/6_QNSPSA_optimizer_with_embedded_simulator/qnspsa_with_embedded_simulator.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ] +} diff --git a/demonstrations_v2/qonn/demo.py b/demonstrations_v2/qonn/demo.py index fffebde61d..7fa2bb0887 100644 --- a/demonstrations_v2/qonn/demo.py +++ b/demonstrations_v2/qonn/demo.py @@ -472,3 +472,6 @@ def cost_wrapper(var, grad=[]): # ──╰BS(-3.48,1.40)──R(0.00)─────Kerr(1.57)─┤ # # +# About the author +# ---------------- +# .. include:: ../_static/authors/theodor_isacsson.txt \ No newline at end of file diff --git a/demonstrations_v2/qonn/metadata.json b/demonstrations_v2/qonn/metadata.json index c0a8c149e6..26ef353aa1 100644 --- a/demonstrations_v2/qonn/metadata.json +++ b/demonstrations_v2/qonn/metadata.json @@ -1,32 +1,32 @@ { - "title": "Optimizing a quantum optical neural network", - "authors": [ - { - "username": "tisacsson" - } - ], - "dateOfPublication": "2020-08-05T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimizing_quantum_optical_neural_network.png" - } - ], - "seoDescription": "Optimizing a quantum optical neural network using PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "quantum_neural_net", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Optimizing a quantum optical neural network", + "authors": [ + { + "username": "tisacsson" + } + ], + "dateOfPublication": "2020-08-05T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimizing_quantum_optical_neural_network.png" + } + ], + "seoDescription": "Optimizing a quantum optical neural network using PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "quantum_neural_net", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/qrack/demo.py b/demonstrations_v2/qrack/demo.py index 0f489b75ee..c5e7968332 100644 --- a/demonstrations_v2/qrack/demo.py +++ b/demonstrations_v2/qrack/demo.py @@ -455,3 +455,6 @@ def circuit(): # `arXiv:2302.04687 `__, 2023. ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/dan_strano.txt diff --git a/demonstrations_v2/qrack/metadata.json b/demonstrations_v2/qrack/metadata.json index a2e87f9832..9afc67ab59 100644 --- a/demonstrations_v2/qrack/metadata.json +++ b/demonstrations_v2/qrack/metadata.json @@ -1,69 +1,69 @@ { - "title": "QJIT compilation with Qrack and Catalyst", - "authors": [ - { - "username": "dstrano" - } - ], - "dateOfPublication": "2024-07-10T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qrack_catalyst_integration.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qrack_catalyst_integration.png" - } - ], - "seoDescription": "Quantum JIT compilation (QJIT) using the Qrack device for PennyLane and Catalyst, with GPU-acceleration and novel optimization.", - "doi": "", - "references": [ - { - "id": "QECReport", - "type": "preprint", - "title": "Exact and approximate simulation of large quantum circuits on a single GPU", - "authors": "Daniel Strano, Benn Bollay, Aryan Blaauw, Nathan Shammah, William J. Zeng, Andrea Mari", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2304.14969", - "url": "https://arxiv.org/abs/2304.14969" - }, - { - "id": "Wille", - "type": "preprint", - "title": "Decision Diagrams for Quantum Computing", - "authors": "Robert Wille, Stefan Hillmich, Lukas Burgholzer", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2302.04687", - "url": "https://arxiv.org/abs/2302.04687" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qft", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_clifford_circuit_simulations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_mcm_introduction", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "QJIT compilation with Qrack and Catalyst", + "authors": [ + { + "username": "dstrano" + } + ], + "dateOfPublication": "2024-07-10T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qrack_catalyst_integration.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qrack_catalyst_integration.png" + } + ], + "seoDescription": "Quantum JIT compilation (QJIT) using the Qrack device for PennyLane and Catalyst, with GPU-acceleration and novel optimization.", + "doi": "", + "references": [ + { + "id": "QECReport", + "type": "preprint", + "title": "Exact and approximate simulation of large quantum circuits on a single GPU", + "authors": "Daniel Strano, Benn Bollay, Aryan Blaauw, Nathan Shammah, William J. Zeng, Andrea Mari", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2304.14969", + "url": "https://arxiv.org/abs/2304.14969" + }, + { + "id": "Wille", + "type": "preprint", + "title": "Decision Diagrams for Quantum Computing", + "authors": "Robert Wille, Stefan Hillmich, Lukas Burgholzer", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2302.04687", + "url": "https://arxiv.org/abs/2302.04687" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_clifford_circuit_simulations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mcm_introduction", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/qsim_beyond_classical/demo.py b/demonstrations_v2/qsim_beyond_classical/demo.py index ea93f3fb99..9f647638cf 100644 --- a/demonstrations_v2/qsim_beyond_classical/demo.py +++ b/demonstrations_v2/qsim_beyond_classical/demo.py @@ -581,3 +581,6 @@ def fidelity_xeb(samples, probs): # `__ # # +# About the author +# ---------------- +# .. include:: ../_static/authors/theodor_isacsson.txt \ No newline at end of file diff --git a/demonstrations_v2/qsim_beyond_classical/metadata.json b/demonstrations_v2/qsim_beyond_classical/metadata.json index d3960e5d20..dfee75dc40 100644 --- a/demonstrations_v2/qsim_beyond_classical/metadata.json +++ b/demonstrations_v2/qsim_beyond_classical/metadata.json @@ -1,94 +1,92 @@ { - "title": "Beyond classical computing with qsim", - "authors": [ - { - "username": "tisacsson" - } - ], - "dateOfPublication": "2020-11-30T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_beyond_classical_computing_QSM.png" - } - ], - "seoDescription": "Use Google's qsim simulator to explore the barriers between quantum and classical computing, and recreate their benchmarks and circuits.", - "doi": "", - "references": [ - { - "id": "Arute2019", - "type": "article", - "title": "Quantum supremacy using a programmable superconducting processor", - "authors": "Arute, F., Arya, K., Babbush, R. et al.", - "year": "2019", - "journal": "Nature", - "doi": "10.1038/s41586-019-1666-5", - "url": "" - }, - { - "id": "Arute2019sup", - "type": "article", - "title": "Supplementary information for \"Quantum supremacy using a programmable superconducting processor\"", - "authors": "Arute, F., Arya, K., Babbush, R. et al.", - "year": "2019", - "journal": "", - "doi": "10.48550/arXiv.1910.11333", - "url": "https://arxiv.org/abs/1910.11333" - }, - { - "id": "Martinis2020", - "type": "article", - "title": "Quantum supremacy using a programmable superconducting processor", - "authors": "Martinis, John M. et al.", - "year": "2020", - "journal": "Dryad", - "doi": "10.5061/dryad.k6t1rj8", - "url": "" - }, - { - "id": "Boixo2018", - "type": "article", - "title": "Characterizing quantum supremacy in near-term devices.", - "authors": "Boixo, S., Isakov, S.V., Smelyanskiy, V.N. et al.", - "year": "2018", - "journal": "Nature Phys", - "doi": "10.1038/s41567-018-0124-x", - "url": "" - }, - { - "id": "Sohaib2019", - "type": "article", - "title": "Unpacking the Quantum Supremacy Benchmark with Python", - "authors": "Sohaib, Alam M. and Zeng, W.", - "year": "2019", - "url": "https://medium.com/@sohaib.alam/unpacking-the-quantum-supremacy-benchmark-with-python-67a46709d" - } - ], - "basedOnPapers": [ - "10.1038/s41586-019-1666-5" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_metrology", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_noisy_circuit_optimization", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "quantum_volume", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Beyond classical computing with qsim", + "authors": [ + { + "username": "tisacsson" + } + ], + "dateOfPublication": "2020-11-30T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_beyond_classical_computing_QSM.png" + } + ], + "seoDescription": "Use Google's qsim simulator to explore the barriers between quantum and classical computing, and recreate their benchmarks and circuits.", + "doi": "", + "references": [ + { + "id": "Arute2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Arute, F., Arya, K., Babbush, R. et al.", + "year": "2019", + "journal": "Nature", + "doi": "10.1038/s41586-019-1666-5", + "url": "" + }, + { + "id": "Arute2019sup", + "type": "article", + "title": "Supplementary information for \"Quantum supremacy using a programmable superconducting processor\"", + "authors": "Arute, F., Arya, K., Babbush, R. et al.", + "year": "2019", + "journal": "", + "doi": "10.48550/arXiv.1910.11333", + "url": "https://arxiv.org/abs/1910.11333" + }, + { + "id": "Martinis2020", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Martinis, John M. et al.", + "year": "2020", + "journal": "Dryad", + "doi": "10.5061/dryad.k6t1rj8", + "url": "" + }, + { + "id": "Boixo2018", + "type": "article", + "title": "Characterizing quantum supremacy in near-term devices.", + "authors": "Boixo, S., Isakov, S.V., Smelyanskiy, V.N. et al.", + "year": "2018", + "journal": "Nature Phys", + "doi": "10.1038/s41567-018-0124-x", + "url": "" + }, + { + "id": "Sohaib2019", + "type": "article", + "title": "Unpacking the Quantum Supremacy Benchmark with Python", + "authors": "Sohaib, Alam M. and Zeng, W.", + "year": "2019", + "url": "https://medium.com/@sohaib.alam/unpacking-the-quantum-supremacy-benchmark-with-python-67a46709d" + } + ], + "basedOnPapers": ["10.1038/s41586-019-1666-5"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_metrology", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/quantum_neural_net/demo.py b/demonstrations_v2/quantum_neural_net/demo.py index 07e39c352e..f8a65360b0 100644 --- a/demonstrations_v2/quantum_neural_net/demo.py +++ b/demonstrations_v2/quantum_neural_net/demo.py @@ -739,3 +739,6 @@ def cost(var, features, labels): # .. image:: ../_static/demonstration_assets/quantum_neural_net/qnn_output_30_0.png ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/maria_schuld.txt \ No newline at end of file diff --git a/demonstrations_v2/quantum_neural_net/metadata.json b/demonstrations_v2/quantum_neural_net/metadata.json index 47ac6cb861..15ea1533f9 100644 --- a/demonstrations_v2/quantum_neural_net/metadata.json +++ b/demonstrations_v2/quantum_neural_net/metadata.json @@ -1,42 +1,42 @@ { - "title": "Function fitting with a photonic quantum neural network", - "authors": [ - { - "username": "mariaschuld" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_function_fitting_photonic_quantum_neural.png" - } - ], - "seoDescription": "Fit to noisy data with a variational quantum circuit.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "qonn", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "pytorch_noise", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_noisy_circuit_optimization", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Function fitting with a photonic quantum neural network", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_function_fitting_photonic_quantum_neural.png" + } + ], + "seoDescription": "Fit to noisy data with a variational quantum circuit.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "qonn", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/quantum_volume/metadata.json b/demonstrations_v2/quantum_volume/metadata.json index fbe4d73114..9e45b3ab88 100644 --- a/demonstrations_v2/quantum_volume/metadata.json +++ b/demonstrations_v2/quantum_volume/metadata.json @@ -1,111 +1,111 @@ { - "title": "Quantum volume", - "authors": [ - { - "username": "glassnotes" - } - ], - "dateOfPublication": "2020-12-15T00:00:00+00:00", - "dateOfLastModification": "2024-10-11T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_volume.png" - } - ], - "seoDescription": "Learn about quantum volume, and how to compute it.", - "doi": "", - "references": [ - { - "id": "top500", - "type": "website", - "title": "Top500", - "url": "https://www.top500.org/" - }, - { - "id": "linpack", - "type": "webpage", - "title": "The Linpack Benchmark", - "url": "https://www.top500.org/project/linpack/" - }, - { - "id": "cross", - "type": "article", - "title": "Validating quantum computers using randomized model circuits", - "authors": "Cross, A. W., Bishop, L. S., Sheldon, S., Nation, P. D., & Gambetta, J. M.", - "year": "2019", - "journal": "Physical Review A", - "doi": "10.1103/physreva.100.032328" - }, - { - "id": "robin", - "type": "article", - "title": "A volumetric framework for quantum computer benchmarks", - "authors": "Blume-Kohout, R., & Young, K. C.", - "year": "2020", - "journal": "Quantum", - "doi": "10.22331/q-2020-11-15-362" - }, - { - "id": "aaronson", - "type": "article", - "title": "Complexity-theoretic foundations of quantum supremacy experiments.", - "authors": "Aaronson, S., & Chen, L.", - "doi": "10.48550/arXiv.1612.05903", - "url": "https://arxiv.org/abs/1612.05903" - }, - { - "id": "cmu", - "type": "other", - "title": "CMU course: Quantum Computation and Quantum Information 2018.", - "authors": "O'Donnell, R.", - "url": "https://www.cs.cmu.edu/~odonnell/quantum18/lecture25.pdf" - }, - { - "id": "qv64", - "type": "article", - "title": "Demonstration of quantum volume 64 on a superconducting quantum computing system.", - "authors": "Jurcevic et al.", - "doi": "10.48550/arXiv.2008.08571", - "url": "https://arxiv.org/abs/2008.08571" - }, - { - "id": "honeywell", - "type": "article", - "title": "Achieving Quantum Volume 128 On The Honeywell Quantum Computer", - "year": "2020", - "url": "https://www.honeywell.com/en-us/newsroom/news/2020/09/achieving-quantum-volume-128-on-the-honeywell-quantum-computer" - }, - { - "id": "ionq", - "type": "article", - "title": "IonQ Unveils World's Most Powerful Quantum Computer", - "year": "2020", - "url": "https://www.prnewswire.com/news-releases/ionq-unveils-worlds-most-powerful-quantum-computer-301143782.html" - }, - { - "id": "sabre", - "type": "article", - "title": "Tackling the qubit mapping problem for nisq-era quantum devices", - "authors": "Li, G., Ding, Y., & Xie, Y.", - "year": "2019", - "proceedings": "In Proceedings of the Twenty-Fourth International Conference on Architectural Support for Programming Languages and Operating Systems", - "pages": "1001\u20131014", - "url": "https://dl.acm.org/doi/10.1145/3297858.3304023" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "qsim_beyond_classical", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum volume", + "authors": [ + { + "username": "glassnotes" + } + ], + "dateOfPublication": "2020-12-15T00:00:00+00:00", + "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_volume.png" + } + ], + "seoDescription": "Learn about quantum volume, and how to compute it.", + "doi": "", + "references": [ + { + "id": "top500", + "type": "website", + "title": "Top500", + "url": "https://www.top500.org/" + }, + { + "id": "linpack", + "type": "webpage", + "title": "The Linpack Benchmark", + "url": "https://www.top500.org/project/linpack/" + }, + { + "id": "cross", + "type": "article", + "title": "Validating quantum computers using randomized model circuits", + "authors": "Cross, A. W., Bishop, L. S., Sheldon, S., Nation, P. D., & Gambetta, J. M.", + "year": "2019", + "journal": "Physical Review A", + "doi": "10.1103/physreva.100.032328" + }, + { + "id": "robin", + "type": "article", + "title": "A volumetric framework for quantum computer benchmarks", + "authors": "Blume-Kohout, R., & Young, K. C.", + "year": "2020", + "journal": "Quantum", + "doi": "10.22331/q-2020-11-15-362" + }, + { + "id": "aaronson", + "type": "article", + "title": "Complexity-theoretic foundations of quantum supremacy experiments.", + "authors": "Aaronson, S., & Chen, L.", + "doi": "10.48550/arXiv.1612.05903", + "url": "https://arxiv.org/abs/1612.05903" + }, + { + "id": "cmu", + "type": "other", + "title": "CMU course: Quantum Computation and Quantum Information 2018.", + "authors": "O'Donnell, R.", + "url": "https://www.cs.cmu.edu/~odonnell/quantum18/lecture25.pdf" + }, + { + "id": "qv64", + "type": "article", + "title": "Demonstration of quantum volume 64 on a superconducting quantum computing system.", + "authors": "Jurcevic et al.", + "doi": "10.48550/arXiv.2008.08571", + "url": "https://arxiv.org/abs/2008.08571" + }, + { + "id": "honeywell", + "type": "article", + "title": "Achieving Quantum Volume 128 On The Honeywell Quantum Computer", + "year": "2020", + "url": "https://www.honeywell.com/en-us/newsroom/news/2020/09/achieving-quantum-volume-128-on-the-honeywell-quantum-computer" + }, + { + "id": "ionq", + "type": "article", + "title": "IonQ Unveils World's Most Powerful Quantum Computer", + "year": "2020", + "url": "https://www.prnewswire.com/news-releases/ionq-unveils-worlds-most-powerful-quantum-computer-301143782.html" + }, + { + "id": "sabre", + "type": "article", + "title": "Tackling the qubit mapping problem for nisq-era quantum devices", + "authors": "Li, G., Ding, Y., & Xie, Y.", + "year": "2019", + "proceedings": "In Proceedings of the Twenty-Fourth International Conference on Architectural Support for Programming Languages and Operating Systems", + "pages": "1001–1014", + "url": "https://dl.acm.org/doi/10.1145/3297858.3304023" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "qsim_beyond_classical", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py index 975e5dd03a..b68a2098a8 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/demo.py @@ -220,3 +220,6 @@ def optimization(params, data, targets): # In this example, JIT compiling the entire optimization loop # is significantly more performant. # +# About the authors +# ----------------- +# diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json index 85e94f3384..159dc0a2a3 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/metadata.json @@ -1,53 +1,51 @@ { - "title": "How to optimize a QML model using JAX and JAXopt", - "authors": [ - { - "username": "josh" - }, - { - "username": "mariaschuld" - } - ], - "dateOfPublication": "2024-01-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Optimization", - "How-to" - ], - "tags": [ - "how to" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png" - } - ], - "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, and JAXopt.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_jax_transformations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to optimize a QML model using JAX and JAXopt", + "authors": [ + { + "username": "josh" + }, + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2024-01-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "How-to" + ], + "tags": ["how to"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_How_to_optimize_QML_model_using_JAX_and_JAXopt_2024-01-16.png" + } + ], + "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, and JAXopt.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py index 19c1b684cd..8dfe1e66de 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/demo.py @@ -237,3 +237,5 @@ def optimization(params, data, targets): # In this example, JIT compiling the entire optimization loop # is significantly more performant. # +# About the authors +# ----------------- diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json index fcc9a7a41d..2148b168eb 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/metadata.json @@ -1,53 +1,51 @@ { - "title": "How to optimize a QML model using JAX and Optax", - "authors": [ - { - "username": "josh" - }, - { - "username": "mariaschuld" - } - ], - "dateOfPublication": "2024-01-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Optimization", - "How-to" - ], - "tags": [ - "how to" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png" - } - ], - "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, and Optax.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_jax_transformations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to optimize a QML model using JAX and Optax", + "authors": [ + { + "username": "josh" + }, + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2024-01-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "How-to" + ], + "tags": ["how to"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_How_to_optimize_QML_model_using_JAX_and_Optax_2024-01-16.png" + } + ], + "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, and Optax.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py index 0e3eb7c779..a5ec3d500b 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py @@ -215,3 +215,8 @@ def optimization_noqjit(params): print(f"Quantum jitting the entire optimization (best of {reps}): {result} sec per loop") ###################################################################### +# About the author +# ---------------- +# +# .. include:: ../_static/authors/josh_izaac.txt +# diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json index d74a06c2e4..4f82ad28e0 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/metadata.json @@ -1,50 +1,48 @@ { - "title": "How to optimize a QML model using Catalyst and quantum just-in-time (QJIT) compilation", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2024-04-26T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Optimization", - "How-to" - ], - "tags": [ - "how to" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how-to-optimize-qjit-optax.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how-to-optimize-qjit-optax.png" - } - ], - "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, Catalyst, and Optax.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_jax_transformations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to optimize a QML model using Catalyst and quantum just-in-time (QJIT) compilation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "How-to" + ], + "tags": ["how to"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how-to-optimize-qjit-optax.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how-to-optimize-qjit-optax.png" + } + ], + "seoDescription": "Learn how to train a quantum machine learning model using PennyLane, JAX, Catalyst, and Optax.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py index 8086734d2e..0e62c88c0e 100644 --- a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py @@ -210,3 +210,6 @@ def circuit(theta, depth, num_qubits): # 4916, URL https://www.sciencedirect.com/science/article/pii/S0003491614001596. ###################################################################### +# About the author +# ---------------- +# .. include:: ../_static/authors/pietropaolo_frisoni.txt diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json index 8d9e0a72e7..7065e33abc 100644 --- a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/metadata.json @@ -1,51 +1,51 @@ { - "title": "How to simulate quantum circuits with tensor networks using DefaultTensor", - "authors": [ - { - "username": "Frisus95" - } - ], - "dateOfPublication": "2024-07-09T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started", - "Quantum Computing", - "Devices and Performance", - "How-to" - ], - "tags": [ - "how to" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_default_tensor.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_default_tensor.png" - } - ], - "seoDescription": "Learn how to simulate quantum circuits with tensor networks using the default.tensor PennyLane device.", - "doi": "", - "references": [ - { - "id": "orus", - "type": "article", - "title": "A practical introduction to tensor networks: Matrix product states and projected entangled pair states", - "authors": "R. Or\u00fas", - "year": "2014", - "journal": "Annals of Physics", - "url": "https://www.sciencedirect.com/science/article/pii/S0003491614001596" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_tn_circuits", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to simulate quantum circuits with tensor networks using DefaultTensor", + "authors": [ + { + "username": "Frisus95" + } + ], + "dateOfPublication": "2024-07-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing", + "Devices and Performance", + "How-to" + ], + "tags": [ + "how to" + ], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_default_tensor.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_default_tensor.png" + } + ], + "seoDescription": "Learn how to simulate quantum circuits with tensor networks using the default.tensor PennyLane device.", + "doi": "", + "references": [ + { + "id": "orus", + "type": "article", + "title": "A practical introduction to tensor networks: Matrix product states and projected entangled pair states", + "authors": "R. Orús", + "year": "2014", + "journal": "Annals of Physics", + "url": "https://www.sciencedirect.com/science/article/pii/S0003491614001596" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_tn_circuits", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_QGAN/demo.py b/demonstrations_v2/tutorial_QGAN/demo.py index 5c629bfce6..c26e53e245 100644 --- a/demonstrations_v2/tutorial_QGAN/demo.py +++ b/demonstrations_v2/tutorial_QGAN/demo.py @@ -287,3 +287,6 @@ def bloch_vector_generator(angles): print(f"Generator Bloch vector: {bloch_vector_generator(gen_weights)}") ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/nathan_killoran.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_QGAN/metadata.json b/demonstrations_v2/tutorial_QGAN/metadata.json index 5f3d04a5f8..2cf2f49c60 100644 --- a/demonstrations_v2/tutorial_QGAN/metadata.json +++ b/demonstrations_v2/tutorial_QGAN/metadata.json @@ -1,28 +1,28 @@ { - "title": "Quantum generative adversarial networks with Cirq + TensorFlow", - "authors": [ - { - "username": "co9olguy" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_generative_adversarial_networks.png" - } - ], - "seoDescription": "This demo constructs and trains a Quantum Generative Adversarial Network (QGAN) using PennyLane, Cirq, and TensorFlow.", - "doi": "", - "references": [], - "basedOnPapers": [ - "10.1103/PhysRevA.98.012324" - ], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Quantum generative adversarial networks with Cirq + TensorFlow", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_generative_adversarial_networks.png" + } + ], + "seoDescription": "This demo constructs and trains a Quantum Generative Adversarial Network (QGAN) using PennyLane, Cirq, and TensorFlow.", + "doi": "", + "references": [], + "basedOnPapers": [ + "10.1103/PhysRevA.98.012324" + ], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_QUBO/demo.py b/demonstrations_v2/tutorial_QUBO/demo.py index 29585889a9..3b4a0cbe30 100644 --- a/demonstrations_v2/tutorial_QUBO/demo.py +++ b/demonstrations_v2/tutorial_QUBO/demo.py @@ -739,3 +739,6 @@ def Knapsack(values, weights, maximum_weight): # ###################################################################### +# About the author +# ---------------- +# .. include:: ../_static/authors/alejandro_montanez.txt diff --git a/demonstrations_v2/tutorial_QUBO/metadata.json b/demonstrations_v2/tutorial_QUBO/metadata.json index 797233c76a..4317d2c3ac 100644 --- a/demonstrations_v2/tutorial_QUBO/metadata.json +++ b/demonstrations_v2/tutorial_QUBO/metadata.json @@ -1,31 +1,32 @@ { - "title": "Quadratic Unconstrained Binary Optimization", - "authors": [ - { - "username": "alejomonbar" - } - ], - "dateOfPublication": "2024-02-29T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/QUBO/thumbnail_QUBO_2024-02-06.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuBO_2024-02-06.png" - } - ], - "seoDescription": "Learn how to solve a QUBO problem in a quantum computer.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [], - "discussionForumUrl": "https://discuss.pennylane.ai/t/quadratic-unconstrained-binary-optimization-qubo-demo/7339" -} \ No newline at end of file + "title": "Quadratic Unconstrained Binary Optimization", + "authors": [ + { + "username": "alejomonbar" + } + ], + "dateOfPublication": "2024-02-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/QUBO/thumbnail_QUBO_2024-02-06.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuBO_2024-02-06.png" + } + ], + "seoDescription": "Learn how to solve a QUBO problem in a quantum computer.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/quadratic-unconstrained-binary-optimization-qubo-demo/7339" +} diff --git a/demonstrations_v2/tutorial_adaptive_circuits/demo.py b/demonstrations_v2/tutorial_adaptive_circuits/demo.py index 918e12a1c7..bd55318b10 100644 --- a/demonstrations_v2/tutorial_adaptive_circuits/demo.py +++ b/demonstrations_v2/tutorial_adaptive_circuits/demo.py @@ -395,3 +395,6 @@ def circuit(params): # `__ # # +# About the author +# ---------------- +# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations_v2/tutorial_adaptive_circuits/metadata.json b/demonstrations_v2/tutorial_adaptive_circuits/metadata.json index 2cc1f32e63..676fc101da 100644 --- a/demonstrations_v2/tutorial_adaptive_circuits/metadata.json +++ b/demonstrations_v2/tutorial_adaptive_circuits/metadata.json @@ -1,96 +1,94 @@ { - "title": "Adaptive circuits for quantum chemistry", - "authors": [ - { - "username": "soran" - } - ], - "dateOfPublication": "2021-09-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/adaptive_circuits/thumbnail_adaptive_circuits.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adaptive_circuits_new.png" - } - ], - "seoDescription": "Learn how to build quantum chemistry circuits adaptively.", - "doi": "", - "references": [ - { - "id": "peruzzo2014", - "type": "article", - "title": "A variational eigenvalue solver on a photonic quantum processor", - "authors": "Alberto Peruzzo, Jarrod McClean, et al.", - "year": "2014", - "journal": "Nature Communications", - "doi": "10.1038/ncomms5213", - "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" - }, - { - "id": "yudong2019", - "type": "article", - "title": "Quantum Chemistry in the Age of Quantum Computing", - "authors": "Yudong Cao, Jonathan Romero, et al.", - "year": "2019", - "journal": "Chem. Rev.", - "doi": "10.1021/acs.chemrev.8b00803", - "url": "https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803" - }, - { - "id": "romero2017", - "type": "article", - "title": "Strategies for quantum computing molecular energies using the unitary coupled cluster ansatz", - "authors": "J. Romero, R. Babbush, et al.", - "doi": "10.48550/arXiv.1701.02691", - "url": "https://arxiv.org/abs/1701.02691" - }, - { - "id": "givenstutorial", - "type": "article", - "title": "Givens rotations for quantum chemistry", - "authors": "Juan Miguel Arrazola", - "year": "2021", - "journal": "", - "url": "https://pennylane.ai/qml/demos/tutorial_givens_rotations.html" - }, - { - "id": "grimsley2019", - "type": "article", - "title": "An adaptive variational algorithm for exact molecular simulations on a quantum computer", - "authors": "Harper R. Grimsley, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", - "year": "2019", - "journal": "Nat. Commun.", - "doi": "10.1038/s41467-019-10988-2", - "url": "https://www.nature.com/articles/s41467-019-10988-2" - } - ], - "basedOnPapers": [ - "10.1038/s41467-019-10988-2" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_givens_rotations", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Adaptive circuits for quantum chemistry", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2021-09-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/adaptive_circuits/thumbnail_adaptive_circuits.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adaptive_circuits_new.png" + } + ], + "seoDescription": "Learn how to build quantum chemistry circuits adaptively.", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean, et al.", + "year": "2014", + "journal": "Nature Communications", + "doi": "10.1038/ncomms5213", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "yudong2019", + "type": "article", + "title": "Quantum Chemistry in the Age of Quantum Computing", + "authors": "Yudong Cao, Jonathan Romero, et al.", + "year": "2019", + "journal": "Chem. Rev.", + "doi": "10.1021/acs.chemrev.8b00803", + "url": "https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803" + }, + { + "id": "romero2017", + "type": "article", + "title": "Strategies for quantum computing molecular energies using the unitary coupled cluster ansatz", + "authors": "J. Romero, R. Babbush, et al.", + "doi": "10.48550/arXiv.1701.02691", + "url": "https://arxiv.org/abs/1701.02691" + }, + { + "id": "givenstutorial", + "type": "article", + "title": "Givens rotations for quantum chemistry", + "authors": "Juan Miguel Arrazola", + "year": "2021", + "journal": "", + "url": "https://pennylane.ai/qml/demos/tutorial_givens_rotations.html" + }, + { + "id": "grimsley2019", + "type": "article", + "title": "An adaptive variational algorithm for exact molecular simulations on a quantum computer", + "authors": "Harper R. Grimsley, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2019", + "journal": "Nat. Commun.", + "doi": "10.1038/s41467-019-10988-2", + "url": "https://www.nature.com/articles/s41467-019-10988-2" + } + ], + "basedOnPapers": ["10.1038/s41467-019-10988-2"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_adjoint_diff/demo.py b/demonstrations_v2/tutorial_adjoint_diff/demo.py index 12d5614213..690479b425 100644 --- a/demonstrations_v2/tutorial_adjoint_diff/demo.py +++ b/demonstrations_v2/tutorial_adjoint_diff/demo.py @@ -453,3 +453,6 @@ def circuit_adjoint(a): # Xiu-Zhe Luo, Jin-Guo Liu, Pan Zhang, and Lei Wang. Yao.jl: `Extensible, efficient framework for quantum # algorithm design `__ , 2019 # +# About the author +# ---------------- +# .. include:: ../_static/authors/christina_lee.txt diff --git a/demonstrations_v2/tutorial_adjoint_diff/metadata.json b/demonstrations_v2/tutorial_adjoint_diff/metadata.json index ddf22df68a..95e9a2c6fc 100644 --- a/demonstrations_v2/tutorial_adjoint_diff/metadata.json +++ b/demonstrations_v2/tutorial_adjoint_diff/metadata.json @@ -1,68 +1,68 @@ { - "title": "Adjoint Differentiation", - "authors": [ - { - "username": "christina" - } - ], - "dateOfPublication": "2021-11-23T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adjoint_differentiation.png" - } - ], - "seoDescription": "Learn how to use the adjoint method to compute gradients of quantum circuits.", - "doi": "", - "references": [ - { - "id": "Jones2020", - "type": "article", - "title": "Efficient calculation of gradients in classical simulations of variational quantum algorithms", - "authors": "Jones and Gacon", - "year": "2020", - "journal": "", - "doi": "10.48550/arXiv.2009.02823", - "url": "https://arxiv.org/abs/2009.02823" - }, - { - "id": "Luo2019", - "type": "article", - "title": "Yao.jl: Extensible, efficient framework for quantum algorithm design", - "authors": "Xiu-Zhe Luo, Jin-Guo Liu, Pan Zhang, and Lei Wang", - "year": "2019", - "journal": "Quantum", - "doi": "10.22331/q-2020-10-11-341", - "url": "https://quantum-journal.org/papers/q-2020-10-11-341/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_backprop", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_natural_gradient", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_general_parshift", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_stochastic_parameter_shift", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Adjoint Differentiation", + "authors": [ + { + "username": "christina" + } + ], + "dateOfPublication": "2021-11-23T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adjoint_differentiation.png" + } + ], + "seoDescription": "Learn how to use the adjoint method to compute gradients of quantum circuits.", + "doi": "", + "references": [ + { + "id": "Jones2020", + "type": "article", + "title": "Efficient calculation of gradients in classical simulations of variational quantum algorithms", + "authors": "Jones and Gacon", + "year": "2020", + "journal": "", + "doi": "10.48550/arXiv.2009.02823", + "url": "https://arxiv.org/abs/2009.02823" + }, + { + "id": "Luo2019", + "type": "article", + "title": "Yao.jl: Extensible, efficient framework for quantum algorithm design", + "authors": "Xiu-Zhe Luo, Jin-Guo Liu, Pan Zhang, and Lei Wang", + "year": "2019", + "journal": "Quantum", + "doi": "10.22331/q-2020-10-11-341", + "url": "https://quantum-journal.org/papers/q-2020-10-11-341/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_stochastic_parameter_shift", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py b/demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py index 5af0f4090f..9b168f6f8a 100644 --- a/demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/demo.py @@ -463,3 +463,5 @@ def PGD(model, feats, labels, epsilon=0.1, alpha=0.01, num_iter=10): # ###################################################################### +# About the authors +# ----------------- diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json index c1041f3d21..79f6a3580b 100644 --- a/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/metadata.json @@ -1,85 +1,79 @@ -{ - "title": "Adversarial attacks and robustness for quantum machine learning", - "authors": [ - { - "username": "mxw" - }, - { - "username": "kil" - } - ], - "dateOfPublication": "2024-09-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Quantum Computing" - ], - "tags": [ - "Quantum machine learning", - "QML", - "Adversarial attacks", - "Quantum security" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" - } - ], - "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", - "doi": "", - "references": [ - { - "id": "Wendlinger2024", - "type": "preprint", - "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", - "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", - "year": "2024", - "doi": "10.48550/arXiv.2404.16154", - "url": "https://arxiv.org/abs/2404.16154" - }, - { - "id": "Goodfellow2014", - "type": "preprint", - "title": "Explaining and harnessing adversarial examples", - "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", - "year": "2014", - "doi": "10.48550/arXiv.1412.6572", - "url": "https://arxiv.org/abs/1412.6572" - }, - { - "id": "Liu2020", - "type": "preprint", - "title": "A rigorous and robust quantum speed-up in supervised machine learning", - "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", - "year": "2020", - "doi": "10.48550/arXiv.2010.02174", - "url": "https://arxiv.org/abs/2010.02174" - }, - { - "id": "Lu2019", - "type": "preprint", - "title": "Quantum Adversarial Machine Learning", - "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", - "year": "2019", - "doi": "10.48550/arXiv.2001.00030", - "url": "https://arxiv.org/abs/2001.00030" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2404.16154" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ], - "hardware": [] -} \ No newline at end of file +{ + "title": "Adversarial attacks and robustness for quantum machine learning", + "authors": [ + { + "username": "mxw" + }, + { + "username": "kil" + + } + ], + "dateOfPublication": "2024-09-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": ["Quantum Machine Learning", "Quantum Computing"], + "tags": ["Quantum machine learning", "QML", "Adversarial attacks", "Quantum security"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_adversarial_attacks_qml.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_adversarial_attacks_qml.png" + } + ], + "seoDescription": "Learn how to construct adversarial attacks on classification networks and how to make quantum machine learning (QML) models more robust.", + "doi": "", + "references": [ + { + "id": "Wendlinger2024", + "type": "preprint", + "title": "A Comparative Analysis of Adversarial Robustness for Quantum and Classical Machine Learning Models", + "authors": "Maximilian Wendlinger, Kilian Tscharke, Pascal Debus", + "year": "2024", + "doi": "10.48550/arXiv.2404.16154", + "url": "https://arxiv.org/abs/2404.16154" + }, + { + "id": "Goodfellow2014", + "type": "preprint", + "title": "Explaining and harnessing adversarial examples", + "authors": "Ian J. Goodfellow, Jonathon Shlens, Christian Szegedy", + "year": "2014", + "doi": "10.48550/arXiv.1412.6572", + "url": "https://arxiv.org/abs/1412.6572" + + }, + { + "id": "Liu2020", + "type": "preprint", + "title": "A rigorous and robust quantum speed-up in supervised machine learning", + "authors": "Yunchao Liu, Srinivasan Arunachalam, Kristan Temme", + "year": "2020", + "doi": "10.48550/arXiv.2010.02174", + "url": "https://arxiv.org/abs/2010.02174" + + }, + { + "id": "Lu2019", + "type": "preprint", + "title": "Quantum Adversarial Machine Learning", + "authors": "Sirui Lu, Lu-Ming Duan, Dong-Ling Deng", + "year": "2019", + "doi": "10.48550/arXiv.2001.00030", + "url": "https://arxiv.org/abs/2001.00030" + + } + ], + "basedOnPapers": ["10.48550/arXiv.2404.16154"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations_v2/tutorial_apply_qsvt/demo.py b/demonstrations_v2/tutorial_apply_qsvt/demo.py index 575cab9280..b441ba0c8f 100644 --- a/demonstrations_v2/tutorial_apply_qsvt/demo.py +++ b/demonstrations_v2/tutorial_apply_qsvt/demo.py @@ -402,3 +402,8 @@ def linear_system_solver_circuit(phi): # `Quantum 3, 190 `__, 2019 ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/jay_soni.txt +# +# .. include:: ../_static/authors/jarrett_smalley.txt diff --git a/demonstrations_v2/tutorial_apply_qsvt/metadata.json b/demonstrations_v2/tutorial_apply_qsvt/metadata.json index 716a9a3eda..41298d7dfe 100644 --- a/demonstrations_v2/tutorial_apply_qsvt/metadata.json +++ b/demonstrations_v2/tutorial_apply_qsvt/metadata.json @@ -1,91 +1,91 @@ { - "title": "QSVT in Practice", - "authors": [ - { - "username": "Jay" - }, - { - "username": "jsmalley" - } - ], - "dateOfPublication": "2023-08-22T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms", - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/apply_qsvt/thumbnail_tutorial_QSVT_for_Matrix_Inversion.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QSVT_for_Matrix_Inversion.png" - } - ], - "seoDescription": "Applying QSVT for matrix inversion", - "doi": "", - "references": [ - { - "type": "article", - "id": "qsvt", - "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", - "authors": "Andr\u00e1s Gily\u00e9n, Yuan Su, Guang Hao Low, and Nathan Wiebe", - "year": "2019", - "publisher": "", - "journal": "", - "doi": "10.1145/3313276.3316366", - "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" - }, - { - "type": "article", - "id": "phaseeval", - "title": "Efficient phase-factor evaluation in quantum signal processing", - "authors": "Dong Y, Meng X, Whaley K and Lin L", - "year": "2021", - "publisher": "", - "journal": "", - "doi": "10.1103/PhysRevA.103.042419", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.042419" - }, - { - "type": "article", - "id": "machineprecision", - "title": "Finding Angles for Quantum Signal Processing with Machine Precision", - "authors": "Chao R, Ding D, Gilyen A, Huang C, and Szegedy M", - "year": "2020", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2003.02831", - "url": "https://arxiv.org/abs/2003.02831" - }, - { - "type": "article", - "id": "productdecomp", - "title": "Product decomposition of periodic functions in quantum signal processing", - "authors": "Haah J", - "year": "2019", - "publisher": "", - "journal": "Quantum", - "doi": "10.48550/arXiv.2003.02831", - "url": "https://quantum-journal.org/papers/q-2019-10-07-190/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_intro_qsvt", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "function_fitting_qsp", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "QSVT in Practice", + "authors": [ + { + "username": "Jay" + }, + { + "username": "jsmalley" + } + ], + "dateOfPublication": "2023-08-22T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms", + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/apply_qsvt/thumbnail_tutorial_QSVT_for_Matrix_Inversion.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QSVT_for_Matrix_Inversion.png" + } + ], + "seoDescription": "Applying QSVT for matrix inversion", + "doi": "", + "references": [ + { + "type": "article", + "id": "qsvt", + "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", + "authors": "András Gilyén, Yuan Su, Guang Hao Low, and Nathan Wiebe", + "year": "2019", + "publisher": "", + "journal": "", + "doi": "10.1145/3313276.3316366", + "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" + }, + { + "type": "article", + "id": "phaseeval", + "title": "Efficient phase-factor evaluation in quantum signal processing", + "authors": "Dong Y, Meng X, Whaley K and Lin L", + "year": "2021", + "publisher": "", + "journal": "", + "doi": "10.1103/PhysRevA.103.042419", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.042419" + }, + { + "type": "article", + "id": "machineprecision", + "title": "Finding Angles for Quantum Signal Processing with Machine Precision", + "authors": "Chao R, Ding D, Gilyen A, Huang C, and Szegedy M", + "year": "2020", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2003.02831", + "url": "https://arxiv.org/abs/2003.02831" + }, + { + "type": "article", + "id": "productdecomp", + "title": "Product decomposition of periodic functions in quantum signal processing", + "authors": "Haah J", + "year": "2019", + "publisher": "", + "journal": "Quantum", + "doi": "10.48550/arXiv.2003.02831", + "url": "https://quantum-journal.org/papers/q-2019-10-07-190/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "function_fitting_qsp", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_backprop/demo.py b/demonstrations_v2/tutorial_backprop/demo.py index 39f9511e69..469cb1d5ca 100644 --- a/demonstrations_v2/tutorial_backprop/demo.py +++ b/demonstrations_v2/tutorial_backprop/demo.py @@ -451,3 +451,6 @@ def circuit(params): # ``default.qubit``, but the parameter-shift rule scales as :math:`\sim 2p.` # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt diff --git a/demonstrations_v2/tutorial_backprop/metadata.json b/demonstrations_v2/tutorial_backprop/metadata.json index 46f85775ee..d29c25bf81 100644 --- a/demonstrations_v2/tutorial_backprop/metadata.json +++ b/demonstrations_v2/tutorial_backprop/metadata.json @@ -1,32 +1,32 @@ { - "title": "Quantum gradients with backpropagation", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2020-08-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_gradients_backpropagation.png" - } - ], - "seoDescription": "Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule\u2014if you are using a simulator.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_natural_gradient", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum gradients with backpropagation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2020-08-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_gradients_backpropagation.png" + } + ], + "seoDescription": "Using backpropagation can speed up training of quantum circuits compared to the parameter-shift rule—if you are using a simulator.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_barren_gadgets/demo.py b/demonstrations_v2/tutorial_barren_gadgets/demo.py index 600186f0ea..4d2787caa4 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/demo.py +++ b/demonstrations_v2/tutorial_barren_gadgets/demo.py @@ -385,3 +385,7 @@ def monitoring_cost(weights): # "Cost function dependent barren plateaus in shallow parametrized quantum circuits." `Nat Commun 12, 1791 # `__, 2021. # +# About the author +# ---------------- +# .. include:: ../_static/authors/simon_cichy.txt +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_barren_gadgets/metadata.json b/demonstrations_v2/tutorial_barren_gadgets/metadata.json index 2d5cb152b6..5de4f55c31 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/metadata.json +++ b/demonstrations_v2/tutorial_barren_gadgets/metadata.json @@ -1,63 +1,63 @@ { - "title": "Perturbative Gadgets for Variational Quantum Algorithms", - "authors": [ - { - "username": "scichy" - } - ], - "dateOfPublication": "2022-12-09T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/barren_gadgets/thumbnail_tutorial_barren_gadgets.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_perturbative_gadget.png" - } - ], - "seoDescription": "Use perturbative gadgets to avoid cost-function-dependent barren plateaus.", - "doi": "", - "references": [ - { - "id": "cichy2022", - "type": "article", - "title": "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms.", - "authors": "Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J.", - "year": "2022", - "journal": "", - "doi": "10.48550/arXiv.2210.03099", - "url": "https://arxiv.org/abs/2210.03099" - }, - { - "id": "cerezo2021", - "type": "article", - "title": "Cost function dependent barren plateaus in shallow parametrized quantum circuits.", - "authors": "Cerezo, M., Sone, A., Volkoff, T. et al.", - "year": "2021", - "journal": "Nat Commun", - "url": "https://doi.org/10.1038/s41467-021-21728-w" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2210.03099" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_barren_plateaus", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_local_cost_functions", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Perturbative Gadgets for Variational Quantum Algorithms", + "authors": [ + { + "username": "scichy" + } + ], + "dateOfPublication": "2022-12-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/barren_gadgets/thumbnail_tutorial_barren_gadgets.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_perturbative_gadget.png" + } + ], + "seoDescription": "Use perturbative gadgets to avoid cost-function-dependent barren plateaus.", + "doi": "", + "references": [ + { + "id": "cichy2022", + "type": "article", + "title": "A perturbative gadget for delaying the onset of barren plateaus in variational quantum algorithms.", + "authors": "Cichy, S., Faehrmann, P.K., Khatri, S., Eisert, J.", + "year": "2022", + "journal": "", + "doi": "10.48550/arXiv.2210.03099", + "url": "https://arxiv.org/abs/2210.03099" + }, + { + "id": "cerezo2021", + "type": "article", + "title": "Cost function dependent barren plateaus in shallow parametrized quantum circuits.", + "authors": "Cerezo, M., Sone, A., Volkoff, T. et al.", + "year": "2021", + "journal": "Nat Commun", + "url": "https://doi.org/10.1038/s41467-021-21728-w" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2210.03099" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_barren_plateaus/demo.py b/demonstrations_v2/tutorial_barren_plateaus/demo.py index c730c388f5..1d501e6714 100644 --- a/demonstrations_v2/tutorial_barren_plateaus/demo.py +++ b/demonstrations_v2/tutorial_barren_plateaus/demo.py @@ -209,3 +209,6 @@ def rand_circuit(params, random_gate_sequence=None, num_qubits=None): # parametrized quantum circuits. arXiv preprint arXiv:1903.05076 (2019). # # +# About the author +# ---------------- +# .. include:: ../_static/authors/shahnawaz_ahmed.txt diff --git a/demonstrations_v2/tutorial_barren_plateaus/metadata.json b/demonstrations_v2/tutorial_barren_plateaus/metadata.json index fb14843951..9226215d4c 100644 --- a/demonstrations_v2/tutorial_barren_plateaus/metadata.json +++ b/demonstrations_v2/tutorial_barren_plateaus/metadata.json @@ -1,62 +1,60 @@ { - "title": "Barren plateaus in quantum neural networks", - "authors": [ - { - "username": "quantshah" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-15T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_barren_plateaus_QNW.png" - } - ], - "seoDescription": "Showing how randomized quantum circuits face the problem of barren plateaus using PennyLane. We will partly reproduce some of the findings in McClean et. al., 2018 with just a few lines of code.", - "doi": "", - "references": [ - { - "id": "Dauphin2014", - "type": "article", - "title": "Identifying and attacking the saddle point problem in high-dimensional non-convex optimization", - "authors": "Dauphin, Yann N., et al.", - "year": "2014", - "journal": "Advances in Neural Information Processing systems", - "url": "" - }, - { - "id": "McClean2018", - "type": "article", - "title": "Barren plateaus in quantum neural network training landscapes", - "authors": "McClean, Jarrod R., et al.", - "year": "2018", - "journal": "Nature communications", - "url": "" - }, - { - "id": "Grant2019", - "type": "article", - "title": "An initialization strategy for addressing barren plateaus in parametrized quantum circuits", - "authors": "Grant, Edward, et al.", - "year": "2019", - "journal": "arXiv preprint", - "url": "" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1803.11173" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_local_cost_functions", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Barren plateaus in quantum neural networks", + "authors": [ + { + "username": "quantshah" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-15T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_barren_plateaus_QNW.png" + } + ], + "seoDescription": "Showing how randomized quantum circuits face the problem of barren plateaus using PennyLane. We will partly reproduce some of the findings in McClean et. al., 2018 with just a few lines of code.", + "doi": "", + "references": [ + { + "id": "Dauphin2014", + "type": "article", + "title": "Identifying and attacking the saddle point problem in high-dimensional non-convex optimization", + "authors": "Dauphin, Yann N., et al.", + "year": "2014", + "journal": "Advances in Neural Information Processing systems", + "url": "" + }, + { + "id": "McClean2018", + "type": "article", + "title": "Barren plateaus in quantum neural network training landscapes", + "authors": "McClean, Jarrod R., et al.", + "year": "2018", + "journal": "Nature communications", + "url": "" + }, + { + "id": "Grant2019", + "type": "article", + "title": "An initialization strategy for addressing barren plateaus in parametrized quantum circuits", + "authors": "Grant, Edward, et al.", + "year": "2019", + "journal": "arXiv preprint", + "url": "" + } + ], + "basedOnPapers": ["10.48550/arXiv.1803.11173"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_block_encoding/demo.py b/demonstrations_v2/tutorial_block_encoding/demo.py index c1c71fd31a..95bae50a0a 100644 --- a/demonstrations_v2/tutorial_block_encoding/demo.py +++ b/demonstrations_v2/tutorial_block_encoding/demo.py @@ -389,3 +389,10 @@ def complete_circuit(thetas): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/jay_soni.txt +# +# .. include:: ../_static/authors/diego_guala.txt +# +# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations_v2/tutorial_block_encoding/metadata.json b/demonstrations_v2/tutorial_block_encoding/metadata.json index c123f5192a..09d93d7dde 100644 --- a/demonstrations_v2/tutorial_block_encoding/metadata.json +++ b/demonstrations_v2/tutorial_block_encoding/metadata.json @@ -1,67 +1,67 @@ { - "title": "Block Encodings", - "authors": [ - { - "username": "Jay" - }, - { - "username": "Diego" - }, - { - "username": "soran" - } - ], - "dateOfPublication": "2023-11-28T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/block_encoding/thumbnail_Block_Encodings_Matrix_Oracle.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Block_Encodings_Matrix_Oracle.png" - } - ], - "seoDescription": "Learn about methods to achieve block-encoding for a given matrix.", - "doi": "", - "references": [ - { - "id": "Daan2023", - "type": "article", - "title": "Explicit Quantum Circuits for Block Encodings of Certain Sparse Matrices.", - "authors": "Daan C., Lin L., Roel V. B, Chao Y.", - "year": "2023", - "journal": "Preprint", - "url": "https://arxiv.org/pdf/2203.10236.pdf" - }, - { - "id": "McClean2018", - "type": "article", - "title": "FABLE: Fast Approximate Quantum Circuits for Block-Encodings", - "authors": "Daan C., Roel V. B.", - "year": "2022", - "journal": "arXiv", - "url": "https://arxiv.org/pdf/2205.00081.pdf" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_lcu_blockencoding", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_intro_qsvt", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Block Encodings", + "authors": [ + { + "username": "Jay" + }, + { + "username": "Diego" + }, + { + "username": "soran" + } + ], + "dateOfPublication": "2023-11-28T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/block_encoding/thumbnail_Block_Encodings_Matrix_Oracle.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Block_Encodings_Matrix_Oracle.png" + } + ], + "seoDescription": "Learn about methods to achieve block-encoding for a given matrix.", + "doi": "", + "references": [ + { + "id": "Daan2023", + "type": "article", + "title": "Explicit Quantum Circuits for Block Encodings of Certain Sparse Matrices.", + "authors": "Daan C., Lin L., Roel V. B, Chao Y.", + "year": "2023", + "journal": "Preprint", + "url": "https://arxiv.org/pdf/2203.10236.pdf" + }, + { + "id": "McClean2018", + "type": "article", + "title": "FABLE: Fast Approximate Quantum Circuits for Block-Encodings", + "authors": "Daan C., Roel V. B.", + "year": "2022", + "journal": "arXiv", + "url": "https://arxiv.org/pdf/2205.00081.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_lcu_blockencoding", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_bluequbit/demo.py b/demonstrations_v2/tutorial_bluequbit/demo.py index a6bfea608a..574356d8ac 100644 --- a/demonstrations_v2/tutorial_bluequbit/demo.py +++ b/demonstrations_v2/tutorial_bluequbit/demo.py @@ -180,3 +180,6 @@ def add_4_6qubit_uniforms(): # Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. ############################################################################## +# About the author +# ---------------- +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_bluequbit/metadata.json b/demonstrations_v2/tutorial_bluequbit/metadata.json index 51bd85b68b..5103d7af75 100644 --- a/demonstrations_v2/tutorial_bluequbit/metadata.json +++ b/demonstrations_v2/tutorial_bluequbit/metadata.json @@ -1,59 +1,59 @@ { - "title": "Using the BlueQubit (CPU) device with PennyLane", - "authors": [ - { - "username": "hayk_tepanyan" - } - ], - "dateOfPublication": "2024-09-24T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_bluequbit-pennylane_device.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_bluequbit-pennylane_device.png" - } - ], - "seoDescription": "Run large-scale quantum simulations with PennyLane and BlueQubit.", - "doi": "", - "references": [ - { - "id": "Draper2000", - "type": "article", - "title": "Addition on a Quantum Computer", - "authors": "Thomas G. Draper", - "year": "2000", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0008033" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qft_arithmetics", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qft", - "weight": 1.0 - } - ], - "hardware": [ - { - "id": "bluequbit", - "link": "https://app.bluequbit.io/", - "logo": "/_static/hardware_logos/bluequbit.png" - } - ] -} \ No newline at end of file + "title": "Using the BlueQubit (CPU) device with PennyLane", + "authors": [ + { + "username": "hayk_tepanyan" + } + ], + "dateOfPublication": "2024-09-24T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_bluequbit-pennylane_device.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_bluequbit-pennylane_device.png" + } + ], + "seoDescription": "Run large-scale quantum simulations with PennyLane and BlueQubit.", + "doi": "", + "references": [ + { + "id": "Draper2000", + "type": "article", + "title": "Addition on a Quantum Computer", + "authors": "Thomas G. Draper", + "year": "2000", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0008033" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qft", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "bluequbit", + "link": "https://app.bluequbit.io/", + "logo": "/_static/hardware_logos/bluequbit.png" + } + ] +} diff --git a/demonstrations_v2/tutorial_chemical_reactions/demo.py b/demonstrations_v2/tutorial_chemical_reactions/demo.py index eef34f1810..541a01d159 100644 --- a/demonstrations_v2/tutorial_chemical_reactions/demo.py +++ b/demonstrations_v2/tutorial_chemical_reactions/demo.py @@ -432,3 +432,8 @@ def circuit(parameters): # -simulations-on-quantum-computers-50a4b4ee5c64>`__ # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/varun_rishi.txt +# +# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_chemical_reactions/metadata.json b/demonstrations_v2/tutorial_chemical_reactions/metadata.json index 0d04e79081..c68aea4c8d 100644 --- a/demonstrations_v2/tutorial_chemical_reactions/metadata.json +++ b/demonstrations_v2/tutorial_chemical_reactions/metadata.json @@ -1,50 +1,50 @@ { - "title": "Modelling chemical reactions on a quantum computer", - "authors": [ - { - "username": "vrishi" - }, - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2021-07-23T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_modelling_chemical_reactions_QC.png" - } - ], - "seoDescription": "Construct potential energy surfaces for chemical reactions", - "doi": "", - "references": [ - { - "id": "motta2020", - "type": "article", - "title": "A Tale of Colliding Electrons: Boosting the Accuracy of Chemical Simulations on Quantum Computers", - "authors": "Mario Motta, Tanvi Gujarati, and Julia Rice", - "year": "2020", - "journal": "", - "url": "https://medium.com/qiskit/a-tale-of-colliding-electrons-boosting-the-accuracy-of-chemical-simulations-on-quantum-computers-50a4b4ee5c64" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Modelling chemical reactions on a quantum computer", + "authors": [ + { + "username": "vrishi" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2021-07-23T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_modelling_chemical_reactions_QC.png" + } + ], + "seoDescription": "Construct potential energy surfaces for chemical reactions", + "doi": "", + "references": [ + { + "id": "motta2020", + "type": "article", + "title": "A Tale of Colliding Electrons: Boosting the Accuracy of Chemical Simulations on Quantum Computers", + "authors": "Mario Motta, Tanvi Gujarati, and Julia Rice", + "year": "2020", + "journal": "", + "url": "https://medium.com/qiskit/a-tale-of-colliding-electrons-boosting-the-accuracy-of-chemical-simulations-on-quantum-computers-50a4b4ee5c64" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_circuit_compilation/demo.py b/demonstrations_v2/tutorial_circuit_compilation/demo.py index 39917ff51b..ab98ab46f1 100644 --- a/demonstrations_v2/tutorial_circuit_compilation/demo.py +++ b/demonstrations_v2/tutorial_circuit_compilation/demo.py @@ -239,3 +239,6 @@ def q_fun(angles): # :mod:`~pennylane.transforms` module. Furthermore, you can even learn how to create your own in # `this blogpost `__. # +# About the author +# ---------------- +# .. include:: ../_static/authors/borja_requena.txt diff --git a/demonstrations_v2/tutorial_circuit_compilation/metadata.json b/demonstrations_v2/tutorial_circuit_compilation/metadata.json index 761bad1daa..a980e66f37 100644 --- a/demonstrations_v2/tutorial_circuit_compilation/metadata.json +++ b/demonstrations_v2/tutorial_circuit_compilation/metadata.json @@ -1,37 +1,37 @@ { - "title": "Compilation of quantum circuits", - "authors": [ - { - "username": "brequena" - } - ], - "dateOfPublication": "2023-06-14T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Getting Started", - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/circuit_compilation/thumbnail_tutorial_circuit_compilation.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_circuit_compilation.png" - } - ], - "seoDescription": "Learn about circuit transformations and quantum circuit compilation with PennyLane", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_circuit_cutting", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Compilation of quantum circuits", + "authors": [ + { + "username": "brequena" + } + ], + "dateOfPublication": "2023-06-14T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/circuit_compilation/thumbnail_tutorial_circuit_compilation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_circuit_compilation.png" + } + ], + "seoDescription": "Learn about circuit transformations and quantum circuit compilation with PennyLane", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_circuit_cutting", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_classical_expval_estimation/demo.py b/demonstrations_v2/tutorial_classical_expval_estimation/demo.py index ecd47004fb..886f40749a 100644 --- a/demonstrations_v2/tutorial_classical_expval_estimation/demo.py +++ b/demonstrations_v2/tutorial_classical_expval_estimation/demo.py @@ -566,3 +566,5 @@ def run_lightning(params, H): # "Achieving quantum supremacy with sparse and noisy commuting quantum computations." # `arXiv:1610.01808 `__, 2016. # +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_classical_expval_estimation/metadata.json b/demonstrations_v2/tutorial_classical_expval_estimation/metadata.json index 799204178c..53e13148c5 100644 --- a/demonstrations_v2/tutorial_classical_expval_estimation/metadata.json +++ b/demonstrations_v2/tutorial_classical_expval_estimation/metadata.json @@ -1,89 +1,87 @@ { - "title": "Classically estimating expectation values from parametrized quantum circuits", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2024-09-10T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classical_expval_estimation.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_classical_expval_estimation.png" - } - ], - "seoDescription": "Estimate expectation values from parametrized quantum circuits efficiently on average with truncated Pauli propagation.", - "doi": "", - "references": [ - { - "id": "gottesman", - "type": "article", - "title": "The Heisenberg Representation of Quantum Computers.", - "authors": "D. Gottesman", - "year": "1998", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/9807006" - }, - { - "id": "angrisani", - "type": "article", - "title": "Classically estimating observables of noiseless quantum circuits.", - "authors": "Armando Angrisani, Alexander Schmidhuber, Manuel S. Rudolph, M. Cerezo, Zo\u00eb Holmes, Hsin-Yuan Huang", - "year": "2024", - "journal": "", - "url": "https://arxiv.org/abs/2409.01706" - }, - { - "id": "aharonov", - "type": "article", - "title": "A polynomial-time classical algorithm for noisy random circuit sampling.", - "authors": "Dorit Aharonov, Xun Gao, Zeph Landau, Yunchao Liu, Umesh Vazirani", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2211.03999" - }, - { - "id": "lowesa", - "type": "article", - "title": "Classical surrogate simulation of quantum systems with LOWESA.", - "authors": "Manuel S. Rudolph, Enrico Fontana, Zo\u00eb Holmes, Lukasz Cincio", - "year": "2023", - "journal": "", - "url": "https://arxiv.org/abs/2308.09109" - }, - { - "id": "begusic", - "type": "article", - "title": "Fast and converged classical simulations of evidence for the utility of quantum computing before fault tolerance.", - "authors": "Tomislav Begu\u0161i\u0107, Johnnie Gray, Garnet Kin-Lic Chan", - "year": "2023", - "journal": "", - "url": "https://arxiv.org/abs/2308.05077" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2409.01706" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_clifford_circuit_simulations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_liesim", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Classically estimating expectation values from parametrized quantum circuits", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-09-10T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classical_expval_estimation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_classical_expval_estimation.png" + } + ], + "seoDescription": "Estimate expectation values from parametrized quantum circuits efficiently on average with truncated Pauli propagation.", + "doi": "", + "references": [ + { + "id": "gottesman", + "type": "article", + "title": "The Heisenberg Representation of Quantum Computers.", + "authors": "D. Gottesman", + "year": "1998", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/9807006" + }, + { + "id": "angrisani", + "type": "article", + "title": "Classically estimating observables of noiseless quantum circuits.", + "authors": "Armando Angrisani, Alexander Schmidhuber, Manuel S. Rudolph, M. Cerezo, Zoë Holmes, Hsin-Yuan Huang", + "year": "2024", + "journal": "", + "url": "https://arxiv.org/abs/2409.01706" + }, + { + "id": "aharonov", + "type": "article", + "title": "A polynomial-time classical algorithm for noisy random circuit sampling.", + "authors": "Dorit Aharonov, Xun Gao, Zeph Landau, Yunchao Liu, Umesh Vazirani", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2211.03999" + }, + { + "id": "lowesa", + "type": "article", + "title": "Classical surrogate simulation of quantum systems with LOWESA.", + "authors": "Manuel S. Rudolph, Enrico Fontana, Zoë Holmes, Lukasz Cincio", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2308.09109" + }, + { + "id": "begusic", + "type": "article", + "title": "Fast and converged classical simulations of evidence for the utility of quantum computing before fault tolerance.", + "authors": "Tomislav Begušić, Johnnie Gray, Garnet Kin-Lic Chan", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2308.05077" + } + ], + "basedOnPapers": ["10.48550/arXiv.2409.01706"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_clifford_circuit_simulations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_classical_kernels/demo.py b/demonstrations_v2/tutorial_classical_kernels/demo.py index 172ce234b6..d256fe79a9 100644 --- a/demonstrations_v2/tutorial_classical_kernels/demo.py +++ b/demonstrations_v2/tutorial_classical_kernels/demo.py @@ -767,3 +767,6 @@ def QK_partial(x): # `arXiv preprint arXiv:2101.11020 `__. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/elies_gil-fuster.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_classical_kernels/metadata.json b/demonstrations_v2/tutorial_classical_kernels/metadata.json index 4109d433a3..ec8b349ffb 100644 --- a/demonstrations_v2/tutorial_classical_kernels/metadata.json +++ b/demonstrations_v2/tutorial_classical_kernels/metadata.json @@ -1,70 +1,70 @@ { - "title": "Approximating a classical kernel with a quantum computer", - "authors": [ - { - "username": "egfuster" - } - ], - "dateOfPublication": "2022-03-01T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_approximate_classical_kernal.png" - } - ], - "seoDescription": "Finding a quantum kernel to approximate the Gaussian kernel.", - "doi": "", - "references": [ - { - "id": "QEK", - "type": "article", - "title": "Training Quantum Embedding Kernels on Near-Term Quantum Computers", - "authors": "Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan HS Derks, Paul K Faehrmann, Johannes Jakob Meyer", - "year": "2021", - "journal": "arXiv preprint", - "url": "https://arxiv.org/abs/2105.02276" - }, - { - "id": "Fourier", - "type": "article", - "title": "The effect of data encoding on the expressive power of variational quantum machine learning models", - "authors": "Maria Schuld, Ryan Sweke, Johannes Jakob Meyer", - "year": "2021", - "journal": "Phys. Rev. A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.032430" - }, - { - "id": "qkernels", - "type": "article", - "title": "Supervised quantum machine learning models are kernel methods", - "authors": "Maria Schuld", - "year": "2021", - "journal": "arXiv preprint", - "url": "https://arxiv.org/abs/2101.11020" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_kernels_module", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_kernel_based_training", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_expressivity_fourier_series", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Approximating a classical kernel with a quantum computer", + "authors": [ + { + "username": "egfuster" + } + ], + "dateOfPublication": "2022-03-01T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_approximate_classical_kernal.png" + } + ], + "seoDescription": "Finding a quantum kernel to approximate the Gaussian kernel.", + "doi": "", + "references": [ + { + "id": "QEK", + "type": "article", + "title": "Training Quantum Embedding Kernels on Near-Term Quantum Computers", + "authors": "Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan HS Derks, Paul K Faehrmann, Johannes Jakob Meyer", + "year": "2021", + "journal": "arXiv preprint", + "url": "https://arxiv.org/abs/2105.02276" + }, + { + "id": "Fourier", + "type": "article", + "title": "The effect of data encoding on the expressive power of variational quantum machine learning models", + "authors": "Maria Schuld, Ryan Sweke, Johannes Jakob Meyer", + "year": "2021", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.032430" + }, + { + "id": "qkernels", + "type": "article", + "title": "Supervised quantum machine learning models are kernel methods", + "authors": "Maria Schuld", + "year": "2021", + "journal": "arXiv preprint", + "url": "https://arxiv.org/abs/2101.11020" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_classical_shadows/demo.py b/demonstrations_v2/tutorial_classical_shadows/demo.py index f09689884a..9a7c055bbd 100644 --- a/demonstrations_v2/tutorial_classical_shadows/demo.py +++ b/demonstrations_v2/tutorial_classical_shadows/demo.py @@ -713,3 +713,8 @@ def circuit_base(params, **kwargs): # Ph.D. thesis, Caltech, eprint quantph/9705052. # # +# About the authors +# ################# +# .. include:: ../_static/authors/roeland_wiersema.txt +# +# .. include:: ../_static/authors/brian_doolittle.txt diff --git a/demonstrations_v2/tutorial_classical_shadows/metadata.json b/demonstrations_v2/tutorial_classical_shadows/metadata.json index d8ac5954ae..158255c4ac 100644 --- a/demonstrations_v2/tutorial_classical_shadows/metadata.json +++ b/demonstrations_v2/tutorial_classical_shadows/metadata.json @@ -1,74 +1,74 @@ { - "title": "Classical shadows", - "authors": [ - { - "username": "therooler" - }, - { - "username": "bdoolittle" - } - ], - "dateOfPublication": "2021-06-14T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classical_shadows.png" - } - ], - "seoDescription": "Learn how to construct classical shadows and use them to estimate observables.", - "doi": "", - "references": [ - { - "id": "Mauro2003", - "type": "article", - "title": "Quantum Tomography", - "authors": "G. Mauro D\u2019Ariano, Matteo G.A. Paris, Massimiliano F. Sacchi", - "year": "2003", - "journal": "Advances in Imaging and Electron Physics", - "url": "https://arxiv.org/pdf/quant-ph/0302028.pdf" - }, - { - "id": "Huang2020", - "type": "article", - "title": "Predicting many properties of a quantum system from very few measurements", - "authors": "Huang, Hsin-Yuan, Richard Kueng, and John Preskill", - "year": "2020", - "journal": "Nature Physics", - "url": "https://arxiv.org/pdf/2002.08953.pdf" - }, - { - "id": "Gottesman1997", - "type": "article", - "title": "Stabilizer Codes and Quantum Error Correction", - "authors": "Gottesman, Daniel", - "year": "1997", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/9705052" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_measurement_optimize", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "quantum_volume", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_metrology", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/classical-shadows-in-calculating-fidelity/3727" -} \ No newline at end of file + "title": "Classical shadows", + "authors": [ + { + "username": "therooler" + }, + { + "username": "bdoolittle" + } + ], + "dateOfPublication": "2021-06-14T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classical_shadows.png" + } + ], + "seoDescription": "Learn how to construct classical shadows and use them to estimate observables.", + "doi": "", + "references": [ + { + "id": "Mauro2003", + "type": "article", + "title": "Quantum Tomography", + "authors": "G. Mauro D’Ariano, Matteo G.A. Paris, Massimiliano F. Sacchi", + "year": "2003", + "journal": "Advances in Imaging and Electron Physics", + "url": "https://arxiv.org/pdf/quant-ph/0302028.pdf" + }, + { + "id": "Huang2020", + "type": "article", + "title": "Predicting many properties of a quantum system from very few measurements", + "authors": "Huang, Hsin-Yuan, Richard Kueng, and John Preskill", + "year": "2020", + "journal": "Nature Physics", + "url": "https://arxiv.org/pdf/2002.08953.pdf" + }, + { + "id": "Gottesman1997", + "type": "article", + "title": "Stabilizer Codes and Quantum Error Correction", + "authors": "Gottesman, Daniel", + "year": "1997", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/9705052" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_measurement_optimize", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_metrology", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/classical-shadows-in-calculating-fidelity/3727" +} diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py b/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py index 60c4d6fa7d..21f4b06529 100644 --- a/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py @@ -557,3 +557,8 @@ def circuit_product_state(state): # M. D. Radin. (2021) "Classically-Boosted Variational Quantum Eigensolver", # `arXiv:2106.04755 [quant-ph] `__ (2021) # +# About the author +# ---------------- +# .. include:: ../_static/authors/joana_fraxanet.txt +# +# .. include:: ../_static/authors/isidor_schoch.txt diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json b/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json index 1cf9202c02..7e3e268228 100644 --- a/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json @@ -1,53 +1,53 @@ { - "title": "Classically-boosted variational quantum eigensolver", - "authors": [ - { - "username": "jfraxanet" - }, - { - "username": "ischoch" - } - ], - "dateOfPublication": "2022-10-31T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classically-boosted_VQE.png" - } - ], - "seoDescription": "Learn how to implement classically-boosted VQE in PennyLane.", - "doi": "", - "references": [ - { - "id": "Radin2021", - "type": "article", - "title": "Classically-Boosted Variational Quantum Eigensolver", - "authors": "M. D. Radin", - "year": "2021", - "journal": "", - "doi": "10.48550/arXiv.2106.04755", - "url": "https://arxiv.org/abs/2106.04755" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2106.04755" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Classically-boosted variational quantum eigensolver", + "authors": [ + { + "username": "jfraxanet" + }, + { + "username": "ischoch" + } + ], + "dateOfPublication": "2022-10-31T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_classically-boosted_VQE.png" + } + ], + "seoDescription": "Learn how to implement classically-boosted VQE in PennyLane.", + "doi": "", + "references": [ + { + "id": "Radin2021", + "type": "article", + "title": "Classically-Boosted Variational Quantum Eigensolver", + "authors": "M. D. Radin", + "year": "2021", + "journal": "", + "doi": "10.48550/arXiv.2106.04755", + "url": "https://arxiv.org/abs/2106.04755" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2106.04755" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py b/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py index 3f05c28efb..60eef7a1e4 100644 --- a/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py +++ b/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py @@ -512,3 +512,5 @@ def original_circuit(x, y): # ###################################################################### +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json b/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json index 107a2f385b..2dc1876ec4 100644 --- a/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json +++ b/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json @@ -1,96 +1,96 @@ { - "title": "Efficient Simulation of Clifford Circuits", - "authors": [ - { - "username": "whatsis" - } - ], - "dateOfPublication": "2024-04-12T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/clifford_simulation/thumbnail_clifford_circuit_simulations.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_clifford_circuit_simulations.png" - } - ], - "seoDescription": "Perform efficient simulations for Clifford circuits.", - "doi": "", - "references": [ - { - "id": "supremecy_exp1", - "type": "article", - "title": "Fast classical simulation of Harvard/QuEra IQP circuits.", - "authors": "D. Maslov, S. Bravyi, F. Tripier, A. Maksymov, and J. Latone", - "year": "2024", - "journal": "", - "url": "https://arxiv.org/abs/2402.03211" - }, - { - "id": "supremecy_exp2", - "type": "article", - "title": "Classical Simulation of Quantum Supremacy Circuits.", - "authors": "C. Huang, F. Zhang, M. Newman, J. Cai, X. Gao, Z. Tian, and et al.", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2005.06787" - }, - { - "id": "mbmqc_2009", - "type": "article", - "title": "Measurement-based quantum computation.", - "authors": "H. J. Briegel, D. E. Browne, W. D\u00fcr, R. Raussendorf, and M. V. den Nest", - "year": "2009", - "journal": "", - "url": "https://arxiv.org/abs/0910.1116" - }, - { - "id": "lowrank_2019", - "type": "article", - "title": "Simulation of quantum circuits by low-rank stabilizer decompositions.", - "authors": "S. Bravyi, D. Browne, P. Calpin, E. Campbell, D. Gosset, and M. Howard", - "year": "2019", - "journal": "Quantum 3, 181", - "url": "https://quantum-journal.org/papers/q-2019-09-02-181/" - }, - { - "id": "aaronson-gottesman2004", - "type": "article", - "title": "ClassiImproved simulation of stabilizer circuits.", - "authors": "S. Aaronson and D. Gottesman", - "year": "2004", - "journal": "Phys. Rev. A 70, 052328", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.052328" - }, - { - "id": "stim", - "type": "article", - "title": "Stim: a fast stabilizer circuit simulator.", - "authors": "C. Gidney", - "year": "2021", - "journal": "Quantum 5, 497", - "url": "https://doi.org/10.22331/q-2021-07-06-497" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_mbqc", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_unitary_designs", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Efficient Simulation of Clifford Circuits", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2024-04-12T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/clifford_simulation/thumbnail_clifford_circuit_simulations.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_clifford_circuit_simulations.png" + } + ], + "seoDescription": "Perform efficient simulations for Clifford circuits.", + "doi": "", + "references": [ + { + "id": "supremecy_exp1", + "type": "article", + "title": "Fast classical simulation of Harvard/QuEra IQP circuits.", + "authors": "D. Maslov, S. Bravyi, F. Tripier, A. Maksymov, and J. Latone", + "year": "2024", + "journal": "", + "url": "https://arxiv.org/abs/2402.03211" + }, + { + "id": "supremecy_exp2", + "type": "article", + "title": "Classical Simulation of Quantum Supremacy Circuits.", + "authors": "C. Huang, F. Zhang, M. Newman, J. Cai, X. Gao, Z. Tian, and et al.", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2005.06787" + }, + { + "id": "mbmqc_2009", + "type": "article", + "title": "Measurement-based quantum computation.", + "authors": "H. J. Briegel, D. E. Browne, W. Dür, R. Raussendorf, and M. V. den Nest", + "year": "2009", + "journal": "", + "url": "https://arxiv.org/abs/0910.1116" + }, + { + "id": "lowrank_2019", + "type": "article", + "title": "Simulation of quantum circuits by low-rank stabilizer decompositions.", + "authors": "S. Bravyi, D. Browne, P. Calpin, E. Campbell, D. Gosset, and M. Howard", + "year": "2019", + "journal": "Quantum 3, 181", + "url": "https://quantum-journal.org/papers/q-2019-09-02-181/" + }, + { + "id": "aaronson-gottesman2004", + "type": "article", + "title": "ClassiImproved simulation of stabilizer circuits.", + "authors": "S. Aaronson and D. Gottesman", + "year": "2004", + "journal": "Phys. Rev. A 70, 052328", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.70.052328" + }, + { + "id": "stim", + "type": "article", + "title": "Stim: a fast stabilizer circuit simulator.", + "authors": "C. Gidney", + "year": "2021", + "journal": "Quantum 5, 497", + "url": "https://doi.org/10.22331/q-2021-07-06-497" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_coherent_vqls/demo.py b/demonstrations_v2/tutorial_coherent_vqls/demo.py index 7cef42afed..2f4d20aa4b 100644 --- a/demonstrations_v2/tutorial_coherent_vqls/demo.py +++ b/demonstrations_v2/tutorial_coherent_vqls/demo.py @@ -556,3 +556,6 @@ def prepare_and_sample(weights): # PhD thesis, University of Waterloo, 2014. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_coherent_vqls/metadata.json b/demonstrations_v2/tutorial_coherent_vqls/metadata.json index 91701d4e9b..8fd3387d94 100644 --- a/demonstrations_v2/tutorial_coherent_vqls/metadata.json +++ b/demonstrations_v2/tutorial_coherent_vqls/metadata.json @@ -1,51 +1,51 @@ { - "title": "Coherent Variational Quantum Linear Solver", - "authors": [ - { - "username": "amari" - } - ], - "dateOfPublication": "2019-11-06T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_coherent_variational_quantum_learner_solver.png" - } - ], - "seoDescription": "This demonstration extends the variational quantum linear solver to solve linear equations defined by a probabilistic coherent operation.", - "doi": "", - "references": [ - { - "id": "BravoPrieto2019", - "type": "article", - "title": "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems", - "authors": "Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1909.05820" - }, - { - "id": "Kothari2014", - "type": "phdthesis", - "title": "Efficient algorithms in quantum query complexity", - "authors": "Robin Kothari", - "year": "2014", - "journal": "", - "url": "" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqls", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Coherent Variational Quantum Linear Solver", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2019-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_coherent_variational_quantum_learner_solver.png" + } + ], + "seoDescription": "This demonstration extends the variational quantum linear solver to solve linear equations defined by a probabilistic coherent operation.", + "doi": "", + "references": [ + { + "id": "BravoPrieto2019", + "type": "article", + "title": "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems", + "authors": "Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.05820" + }, + { + "id": "Kothari2014", + "type": "phdthesis", + "title": "Efficient algorithms in quantum query complexity", + "authors": "Robin Kothari", + "year": "2014", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqls", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py b/demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py index 7bcafa4c27..a69e89d1a6 100644 --- a/demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py +++ b/demonstrations_v2/tutorial_constant_depth_mps_prep/demo.py @@ -846,3 +846,6 @@ def constant_depth_circuit(N, g, q): # "Preparation of Matrix Product States with Log-Depth Quantum Circuits", Physical Review Letters, **132**, 040404, # `open access `__, 2024. # +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json b/demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json index 727931b57e..cb3214370d 100644 --- a/demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json +++ b/demonstrations_v2/tutorial_constant_depth_mps_prep/metadata.json @@ -1,49 +1,47 @@ { - "title": "Constant-depth preparation of matrix product states with dynamic circuits", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2024-10-09T00:00:00+00:00", - "dateOfLastModification": "2024-10-09T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_constant_depth_mps_prep.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_constant_depth_mps_prep.png" - } - ], - "seoDescription": "Prepare a matrix product state (MPS) in constant depth with mid-circuit measurements and feedforward control.", - "doi": "", - "references": [], - "basedOnPapers": [ - "10.48550/arXiv.2404.16083" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_mcm_introduction", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_mps", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_tn_circuits", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Constant-depth preparation of matrix product states with dynamic circuits", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-10-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-09T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_constant_depth_mps_prep.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_constant_depth_mps_prep.png" + } + ], + "seoDescription": "Prepare a matrix product state (MPS) in constant depth with mid-circuit measurements and feedforward control.", + "doi": "", + "references": [], + "basedOnPapers": ["10.48550/arXiv.2404.16083"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_mcm_introduction", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mps", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_tn_circuits", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_contextuality/demo.py b/demonstrations_v2/tutorial_contextuality/demo.py index 53db5fc211..fba179f98b 100644 --- a/demonstrations_v2/tutorial_contextuality/demo.py +++ b/demonstrations_v2/tutorial_contextuality/demo.py @@ -802,3 +802,6 @@ def optimise_model(model, nstep, lr, weights): # `arXiv:2206.11740 `__, 2022. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/joseph_bowles.txt diff --git a/demonstrations_v2/tutorial_contextuality/metadata.json b/demonstrations_v2/tutorial_contextuality/metadata.json index 30808e2b99..146da6192d 100644 --- a/demonstrations_v2/tutorial_contextuality/metadata.json +++ b/demonstrations_v2/tutorial_contextuality/metadata.json @@ -1,82 +1,82 @@ { - "title": "Contextuality and inductive bias in QML", - "authors": [ - { - "username": "josephbowles" - } - ], - "dateOfPublication": "2023-09-06T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/contextuality/thumbnail_tutorial_Contextuality.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Contextuality.png" - } - ], - "seoDescription": "Train a problem-inspired ansatz on a contextuality-inspired dataset.", - "doi": "", - "references": [ - { - "id": "paper", - "type": "article", - "title": "Contextuality and inductive bias in quantum machine learning.", - "authors": "J. Bowles, V. J. Wright, M. Farkas, N. Killoran, M. Schuld", - "year": "2023", - "journal": "", - "url": "https://arxiv.org/abs/2302.01365" - }, - { - "id": "contextuality", - "type": "article", - "title": "Contextuality for preparations, transformations, and unsharp measurements.", - "authors": "R. W. Spekkens", - "year": "2005", - "journal": "Phys. Rev. A 71", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.71.052108" - }, - { - "id": "reptheory", - "type": "article", - "title": "Representation Theory for Geometric Quantum Machine Learning.", - "authors": "M. Ragone, P. Braccia, Q. T. Nguyen, L. Schatzki, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo", - "year": "2023", - "journal": "", - "url": "https://arxiv.org/abs/2210.07980" - }, - { - "id": "equivariant", - "type": "article", - "title": "Theory for Equivariant Quantum Neural Networks.", - "authors": "Q. T. Nguyen, L. Schatzki, P. Braccia, M. Ragone, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2210.08566" - }, - { - "id": "surrogates", - "type": "article", - "title": "Classical surrogates for quantum learning models.", - "authors": "F. J. Schreiber, J. Eiser, J. J. Meyer", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2206.11740" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_geometric_qml", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Contextuality and inductive bias in QML", + "authors": [ + { + "username": "josephbowles" + } + ], + "dateOfPublication": "2023-09-06T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/contextuality/thumbnail_tutorial_Contextuality.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Contextuality.png" + } + ], + "seoDescription": "Train a problem-inspired ansatz on a contextuality-inspired dataset.", + "doi": "", + "references": [ + { + "id": "paper", + "type": "article", + "title": "Contextuality and inductive bias in quantum machine learning.", + "authors": "J. Bowles, V. J. Wright, M. Farkas, N. Killoran, M. Schuld", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2302.01365" + }, + { + "id": "contextuality", + "type": "article", + "title": "Contextuality for preparations, transformations, and unsharp measurements.", + "authors": "R. W. Spekkens", + "year": "2005", + "journal": "Phys. Rev. A 71", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.71.052108" + }, + { + "id": "reptheory", + "type": "article", + "title": "Representation Theory for Geometric Quantum Machine Learning.", + "authors": "M. Ragone, P. Braccia, Q. T. Nguyen, L. Schatzki, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2210.07980" + }, + { + "id": "equivariant", + "type": "article", + "title": "Theory for Equivariant Quantum Neural Networks.", + "authors": "Q. T. Nguyen, L. Schatzki, P. Braccia, M. Ragone, P. J. Coles, F. Sauvage, M. Larocca, M. Cerezo", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.08566" + }, + { + "id": "surrogates", + "type": "article", + "title": "Classical surrogates for quantum learning models.", + "authors": "F. J. Schreiber, J. Eiser, J. J. Meyer", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2206.11740" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py b/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py index c3034b38c7..f94e9b2bff 100644 --- a/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py +++ b/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py @@ -468,3 +468,6 @@ def iterate_minibatches(inputs, targets, batch_size): # 45.1-3 (1989): 503-528. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/shahnawaz_ahmed.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json b/demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json index 9b078937cf..74a9b07dfc 100644 --- a/demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json +++ b/demonstrations_v2/tutorial_data_reuploading_classifier/metadata.json @@ -1,72 +1,70 @@ { - "title": "Data-reuploading classifier", - "authors": [ - { - "username": "quantshah" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-15T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_data-reuploading_classified.png" - } - ], - "seoDescription": "Implement a single-qubit universal quantum classifier using PennyLane.", - "doi": "", - "references": [ - { - "id": "PerezSalinas2019", - "type": "article", - "title": "Data re-uploading for a universal quantum classifier", - "authors": "P\u00e9rez-Salinas, Adri\u00e1n, et al.", - "year": "2019", - "journal": "", - "url": "" - }, - { - "id": "Kingma2014", - "type": "article", - "title": "Adam: A method for stochastic optimization", - "authors": "Kingma, Diederik P., and Ba, J.", - "year": "2014", - "journal": "", - "url": "" - }, - { - "id": "Liu1989", - "type": "article", - "title": "On the limited memory BFGS method for large scale optimization.", - "authors": "Liu, Dong C., and Nocedal, J.", - "year": "1989", - "journal": "Mathematical programming", - "url": "" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1907.02085" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_multiclass_classification", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_expressivity_fourier_series", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Data-reuploading classifier", + "authors": [ + { + "username": "quantshah" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-15T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_data-reuploading_classified.png" + } + ], + "seoDescription": "Implement a single-qubit universal quantum classifier using PennyLane.", + "doi": "", + "references": [ + { + "id": "PerezSalinas2019", + "type": "article", + "title": "Data re-uploading for a universal quantum classifier", + "authors": "Pérez-Salinas, Adrián, et al.", + "year": "2019", + "journal": "", + "url": "" + }, + { + "id": "Kingma2014", + "type": "article", + "title": "Adam: A method for stochastic optimization", + "authors": "Kingma, Diederik P., and Ba, J.", + "year": "2014", + "journal": "", + "url": "" + }, + { + "id": "Liu1989", + "type": "article", + "title": "On the limited memory BFGS method for large scale optimization.", + "authors": "Liu, Dong C., and Nocedal, J.", + "year": "1989", + "journal": "Mathematical programming", + "url": "" + } + ], + "basedOnPapers": ["10.48550/arXiv.1907.02085"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_multiclass_classification", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_diffable-mitigation/demo.py b/demonstrations_v2/tutorial_diffable-mitigation/demo.py index 7c0b4249e2..fa37ae970b 100644 --- a/demonstrations_v2/tutorial_diffable-mitigation/demo.py +++ b/demonstrations_v2/tutorial_diffable-mitigation/demo.py @@ -285,3 +285,7 @@ def VQE_run(cost_fn, max_iter, stepsize=0.1): # `arXiv:2112.05821 `__, 2021. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt + diff --git a/demonstrations_v2/tutorial_diffable-mitigation/metadata.json b/demonstrations_v2/tutorial_diffable-mitigation/metadata.json index eb901817e0..78a66f6e96 100644 --- a/demonstrations_v2/tutorial_diffable-mitigation/metadata.json +++ b/demonstrations_v2/tutorial_diffable-mitigation/metadata.json @@ -1,51 +1,51 @@ { - "title": "Differentiating quantum error mitigation transforms", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2022-08-22T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_differentiating_quantum_error_mitigation_transforms.png" - } - ], - "seoDescription": "Differentiable error mitigation", - "doi": "", - "references": [ - { - "id": "DiffableTransforms", - "type": "article", - "title": "Quantum computing with differentiable quantum transforms", - "authors": "Olivia Di Matteo, Josh Izaac, Tom Bromley, Anthony Hayes, Christina Lee, Maria Schuld, Antal Sz\u00e1va, Chase Roberts, Nathan Killoran", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2202.13414" - }, - { - "id": "VAQEM", - "type": "article", - "title": "VAQEM: A Variational Approach to Quantum Error Mitigation", - "authors": "Gokul Subramanian Ravi, Kaitlin N. Smith, Pranav Gokhale, Andrea Mari, Nathan Earnest, Ali Javadi-Abhari, Frederic T. Chong", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2112.05821" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_error_mitigation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Differentiating quantum error mitigation transforms", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2022-08-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_differentiating_quantum_error_mitigation_transforms.png" + } + ], + "seoDescription": "Differentiable error mitigation", + "doi": "", + "references": [ + { + "id": "DiffableTransforms", + "type": "article", + "title": "Quantum computing with differentiable quantum transforms", + "authors": "Olivia Di Matteo, Josh Izaac, Tom Bromley, Anthony Hayes, Christina Lee, Maria Schuld, Antal Száva, Chase Roberts, Nathan Killoran", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2202.13414" + }, + { + "id": "VAQEM", + "type": "article", + "title": "VAQEM: A Variational Approach to Quantum Error Mitigation", + "authors": "Gokul Subramanian Ravi, Kaitlin N. Smith, Pranav Gokhale, Andrea Mari, Nathan Earnest, Ali Javadi-Abhari, Frederic T. Chong", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2112.05821" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_error_mitigation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_diffable_shadows/demo.py b/demonstrations_v2/tutorial_diffable_shadows/demo.py index 85efe65971..80d071ac18 100644 --- a/demonstrations_v2/tutorial_diffable_shadows/demo.py +++ b/demonstrations_v2/tutorial_diffable_shadows/demo.py @@ -503,3 +503,6 @@ def qnode(): # `arXiv:2201.01471 `__, 2022. ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt diff --git a/demonstrations_v2/tutorial_diffable_shadows/metadata.json b/demonstrations_v2/tutorial_diffable_shadows/metadata.json index 9e35df95c4..87124b0296 100644 --- a/demonstrations_v2/tutorial_diffable_shadows/metadata.json +++ b/demonstrations_v2/tutorial_diffable_shadows/metadata.json @@ -1,66 +1,66 @@ { - "title": "Estimating observables with classical shadows in the Pauli basis", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2022-10-07T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_estimating_observables_classical_shadows_Pauli_Basis.png" - } - ], - "seoDescription": "Classical shadows in the Pauli basis", - "doi": "", - "references": [ - { - "id": "Huang2020", - "type": "article", - "title": "Predicting Many Properties of a Quantum System from Very Few Measurements", - "authors": "Hsin-Yuan Huang, Richard Kueng, John Preskill", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2002.08953" - }, - { - "id": "Huang2021", - "type": "article", - "title": "Efficient estimation of Pauli observables by derandomization", - "authors": "Hsin-Yuan Huang, Richard Kueng, John Preskill", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2103.07510" - }, - { - "id": "Yen", - "type": "article", - "title": "Deterministic improvements of quantum measurements with grouping of compatible operators, non-local transformations, and covariance estimates", - "authors": "Tzu-Ching Yen, Aadithya Ganeshram, Artur F. Izmaylov", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2201.01471" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_classical_shadows", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "ml_classical_shadows", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Estimating observables with classical shadows in the Pauli basis", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2022-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_estimating_observables_classical_shadows_Pauli_Basis.png" + } + ], + "seoDescription": "Classical shadows in the Pauli basis", + "doi": "", + "references": [ + { + "id": "Huang2020", + "type": "article", + "title": "Predicting Many Properties of a Quantum System from Very Few Measurements", + "authors": "Hsin-Yuan Huang, Richard Kueng, John Preskill", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2002.08953" + }, + { + "id": "Huang2021", + "type": "article", + "title": "Efficient estimation of Pauli observables by derandomization", + "authors": "Hsin-Yuan Huang, Richard Kueng, John Preskill", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2103.07510" + }, + { + "id": "Yen", + "type": "article", + "title": "Deterministic improvements of quantum measurements with grouping of compatible operators, non-local transformations, and covariance estimates", + "authors": "Tzu-Ching Yen, Aadithya Ganeshram, Artur F. Izmaylov", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2201.01471" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ml_classical_shadows", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_differentiable_HF/demo.py b/demonstrations_v2/tutorial_differentiable_HF/demo.py index b7fa0b4805..d157c3dd35 100644 --- a/demonstrations_v2/tutorial_differentiable_HF/demo.py +++ b/demonstrations_v2/tutorial_differentiable_HF/demo.py @@ -388,3 +388,6 @@ def circuit(*args): # Structure Theory". Dover Publications, 1996. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/soran_jahangiri.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_differentiable_HF/metadata.json b/demonstrations_v2/tutorial_differentiable_HF/metadata.json index 2602060c4a..2389de40b2 100644 --- a/demonstrations_v2/tutorial_differentiable_HF/metadata.json +++ b/demonstrations_v2/tutorial_differentiable_HF/metadata.json @@ -1,66 +1,66 @@ { - "title": "Differentiable Hartree-Fock", - "authors": [ - { - "username": "soran" - } - ], - "dateOfPublication": "2022-05-09T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_differentiable_Hartree-Fock.png" - } - ], - "seoDescription": "Learn how to use the differentiable Hartree-Fock solver", - "doi": "", - "references": [ - { - "id": "arrazola2021", - "type": "article", - "title": "Differentiable quantum computational chemistry with PennyLane", - "authors": "Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni et al.", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2111.09967" - }, - { - "id": "szabo1996", - "type": "book", - "title": "Modern Quantum Chemistry: Introduction to Advanced Electronic Structure Theory", - "authors": "Attila Szabo, Neil S. Ostlund", - "year": "1996", - "publisher": "Dover Publications", - "url": "" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_givens_rotations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_adaptive_circuits", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Differentiable Hartree-Fock", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2022-05-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_differentiable_Hartree-Fock.png" + } + ], + "seoDescription": "Learn how to use the differentiable Hartree-Fock solver", + "doi": "", + "references": [ + { + "id": "arrazola2021", + "type": "article", + "title": "Differentiable quantum computational chemistry with PennyLane", + "authors": "Juan Miguel Arrazola, Soran Jahangiri, Alain Delgado, Jack Ceroni et al.", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2111.09967" + }, + { + "id": "szabo1996", + "type": "book", + "title": "Modern Quantum Chemistry: Introduction to Advanced Electronic Structure Theory", + "authors": "Attila Szabo, Neil S. Ostlund", + "year": "1996", + "publisher": "Dover Publications", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_adaptive_circuits", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_doubly_stochastic/demo.py b/demonstrations_v2/tutorial_doubly_stochastic/demo.py index 18d6b6df2c..81e8a749d4 100644 --- a/demonstrations_v2/tutorial_doubly_stochastic/demo.py +++ b/demonstrations_v2/tutorial_doubly_stochastic/demo.py @@ -402,3 +402,6 @@ def loss(params, shots=None): # `__, 2019. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_doubly_stochastic/metadata.json b/demonstrations_v2/tutorial_doubly_stochastic/metadata.json index a5d00e7511..85af9a0ffb 100644 --- a/demonstrations_v2/tutorial_doubly_stochastic/metadata.json +++ b/demonstrations_v2/tutorial_doubly_stochastic/metadata.json @@ -1,52 +1,52 @@ { - "title": "Doubly stochastic gradient descent", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2019-10-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_doubly_stochastic_gradient_descent.png" - } - ], - "seoDescription": "Minimize a Hamiltonian via an adaptive shot optimization strategy with doubly stochastic gradient descent.", - "doi": "", - "references": [ - { - "id": "Sweke2019", - "type": "article", - "title": "Stochastic gradient descent for hybrid quantum-classical optimization", - "authors": "Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. F\u00e4hrmann, Barth\u00e9l\u00e9my Meynard-Piganeau, Jens Eisert", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1910.01155" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_backprop", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_natural_gradient", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_rosalin", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Doubly stochastic gradient descent", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_doubly_stochastic_gradient_descent.png" + } + ], + "seoDescription": "Minimize a Hamiltonian via an adaptive shot optimization strategy with doubly stochastic gradient descent.", + "doi": "", + "references": [ + { + "id": "Sweke2019", + "type": "article", + "title": "Stochastic gradient descent for hybrid quantum-classical optimization", + "authors": "Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy Meynard-Piganeau, Jens Eisert", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.01155" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rosalin", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_eqnn_force_field/demo.py b/demonstrations_v2/tutorial_eqnn_force_field/demo.py index 663d529bc3..45373bf0b4 100644 --- a/demonstrations_v2/tutorial_eqnn_force_field/demo.py +++ b/demonstrations_v2/tutorial_eqnn_force_field/demo.py @@ -629,3 +629,7 @@ def inference(loss_data, opt_state): # ###################################################################### +# About the author +# ---------------- +# .. include:: ../_static/authors/isabel_le.txt +# .. include:: ../_static/authors/oriel_kiss.txt diff --git a/demonstrations_v2/tutorial_eqnn_force_field/metadata.json b/demonstrations_v2/tutorial_eqnn_force_field/metadata.json index a08c1ad5f2..99dd1bcc61 100644 --- a/demonstrations_v2/tutorial_eqnn_force_field/metadata.json +++ b/demonstrations_v2/tutorial_eqnn_force_field/metadata.json @@ -1,108 +1,108 @@ { - "title": "Symmetry-invariant quantum machine learning force fields", - "authors": [ - { - "username": "orielkiss" - }, - { - "username": "inmle" - } - ], - "dateOfPublication": "2024-03-12T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/eqnn_force_field/thumbnail_EQNNforcefield_2024-03-07.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_EQNNforceField_2024-03-07.png" - } - ], - "seoDescription": "What is equivariant quantum machine learning for chemistry?", - "doi": "", - "references": [ - { - "id": "Le", - "type": "article", - "title": "Symmetry-invariant quantum machine learning force fields", - "authors": "Isabel Nha Minh Le, Oriel Kiss, Julian Schuhmacher, Ivano Tavernelli and Francesco Tacchino", - "year": "2023", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/abs/2311.11362" - }, - { - "id": "kiss", - "type": "article", - "title": "Quantum neural networks force fields generation", - "authors": "Oriel Kiss, Francesco Tacchino, Sofia Vallecorsa and Ivano Tavernelli", - "year": "2023", - "publisher": "IOP", - "journal": " Mach. Learn.: Sci. Technol.", - "url": "https://iopscience.iop.org/article/10.1088/2632-2153/ac7d3c" - }, - { - "id": "schuld", - "type": "article", - "title": "Effect of data encoding on the expressive power of variational quantum-machine-learning models", - "authors": "Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer", - "year": "2021", - "publisher": "APS", - "journal": "Phys. Rev. A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.032430" - }, - { - "id": "wierichs", - "type": "article", - "title": "Symmetric derivatives of parametrized quantum circuits", - "authors": "David Wierichs, Richard D. P. East, Mart\u00edn Larocca, M. Cerezo and Nathan Killoran", - "year": "2023", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/abs/2312.06752" - }, - { - "id": "meyer", - "type": "article", - "title": "Exploiting Symmetry in Variational Quantum Machine Learning", - "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, and Jens Eisert", - "year": "2023", - "publisher": "APS", - "journal": "Phys. Rev. X Quantum", - "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.4.010328" - } - ], - "basedOnPapers": [ - "https://arxiv.org/abs/2311.11362" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_geometric_qml", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_contextuality", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_equivariant_graph_embedding", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_expressivity_fourier_series", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Symmetry-invariant quantum machine learning force fields", + "authors": [ + { + "username": "orielkiss" + }, + { + "username": "inmle" + } + ], + "dateOfPublication": "2024-03-12T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/eqnn_force_field/thumbnail_EQNNforcefield_2024-03-07.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_EQNNforceField_2024-03-07.png" + } + ], + "seoDescription": "What is equivariant quantum machine learning for chemistry?", + "doi": "", + "references": [ + { + "id": "Le", + "type": "article", + "title": "Symmetry-invariant quantum machine learning force fields", + "authors": "Isabel Nha Minh Le, Oriel Kiss, Julian Schuhmacher, Ivano Tavernelli and Francesco Tacchino", + "year": "2023", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2311.11362" + }, + { + "id": "kiss", + "type": "article", + "title": "Quantum neural networks force fields generation", + "authors": "Oriel Kiss, Francesco Tacchino, Sofia Vallecorsa and Ivano Tavernelli", + "year": "2023", + "publisher": "IOP", + "journal": " Mach. Learn.: Sci. Technol.", + "url": "https://iopscience.iop.org/article/10.1088/2632-2153/ac7d3c" + }, + { + "id": "schuld", + "type": "article", + "title": "Effect of data encoding on the expressive power of variational quantum-machine-learning models", + "authors": "Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer", + "year": "2021", + "publisher": "APS", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.032430" + }, + { + "id": "wierichs", + "type": "article", + "title": "Symmetric derivatives of parametrized quantum circuits", + "authors": "David Wierichs, Richard D. P. East, Martín Larocca, M. Cerezo and Nathan Killoran", + "year": "2023", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2312.06752" + }, + { + "id": "meyer", + "type": "article", + "title": "Exploiting Symmetry in Variational Quantum Machine Learning", + "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, and Jens Eisert", + "year": "2023", + "publisher": "APS", + "journal": "Phys. Rev. X Quantum", + "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.4.010328" + } + ], + "basedOnPapers": [ + "https://arxiv.org/abs/2311.11362" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_contextuality", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_equivariant_graph_embedding", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py b/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py index 9c47aa6550..060ff6953e 100644 --- a/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py @@ -24,7 +24,7 @@ # # For example, the graph in the image above is represented by each of the two equivalent adjacency matrices. # The top matrix can be transformed into the bottom matrix -# by swapping the first row with the third row, then swapping the third column with the third column, then the +# by swapping the first row with the third row, then swapping the third column with the first column, then the # new first row with the second, and finally the first colum with the second. # # But the number of such permutations grows factorially with the number of nodes in the graph, which @@ -333,3 +333,10 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): # Theory for Equivariant Quantum Neural Networks. # `arXiv:2210.08566 `__ # +# About the author +# ------------------------- +# .. include:: ../_static/authors/maria_schuld.txt + + + + diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json b/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json index 1386ffa50e..22cbcd9936 100644 --- a/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json @@ -1,57 +1,55 @@ { - "title": "An equivariant graph embedding", - "authors": [ - { - "username": "mariaschuld" - } - ], - "dateOfPublication": "2023-07-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/equivariant_graph_embedding/thumbnail_tutorial_equivariant_graph_embedding.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_equivariant_graph_embedding.png" - } - ], - "seoDescription": "Find out more about how to embedd graphs into quantum states.", - "doi": "", - "references": [ - { - "id": "Skolik2022", - "type": "article", - "title": "Equivariant quantum circuits for learning on weighted graphs", - "authors": "Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2205.06109" - }, - { - "id": "Nguyen", - "type": "article", - "title": "Theory for Equivariant Quantum Neural Networks.", - "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Fr\u00e9d\u00e9ric Sauvage, Mart\u00edn Larocca and Marco Cerezo", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2210.08566" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2205.06109" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_geometric_qml", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "An equivariant graph embedding", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2023-07-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/equivariant_graph_embedding/thumbnail_tutorial_equivariant_graph_embedding.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_equivariant_graph_embedding.png" + } + ], + "seoDescription": "Find out more about how to embedd graphs into quantum states.", + "doi": "", + "references": [ + { + "id": "Skolik2022", + "type": "article", + "title": "Equivariant quantum circuits for learning on weighted graphs", + "authors": "Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2205.06109" + }, + { + "id": "Nguyen", + "type": "article", + "title": "Theory for Equivariant Quantum Neural Networks.", + "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Frédéric Sauvage, Martín Larocca and Marco Cerezo", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.08566" + } + ], + "basedOnPapers": ["10.48550/arXiv.2205.06109"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_error_mitigation/demo.py b/demonstrations_v2/tutorial_error_mitigation/demo.py index e690d5a7e9..e75f6f452f 100644 --- a/demonstrations_v2/tutorial_error_mitigation/demo.py +++ b/demonstrations_v2/tutorial_error_mitigation/demo.py @@ -435,10 +435,10 @@ def executor(circuits, dev=dev_noisy): # hardware. Suppose we want to simulate the ``ibmq_lima`` hardware device available on IBMQ. We # can load a noise model that represents this device using: -from qiskit_ibm_runtime.fake_provider import FakeLimaV2 +from qiskit_ibm_runtime.fake_provider import FakeLima from qiskit_aer.noise import NoiseModel -backend = FakeLimaV2() +backend = FakeLima() noise_model = NoiseModel.from_backend(backend) ############################################################################## @@ -593,3 +593,8 @@ def executor(circuit): # IEEE International Conference on Quantum Computing and Engineering (2020). # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/thomas_bromley.txt +# +# .. include:: ../_static/authors/andrea_mari.txt diff --git a/demonstrations_v2/tutorial_error_mitigation/metadata.json b/demonstrations_v2/tutorial_error_mitigation/metadata.json index 953a5011c9..0dfc8c3ad3 100644 --- a/demonstrations_v2/tutorial_error_mitigation/metadata.json +++ b/demonstrations_v2/tutorial_error_mitigation/metadata.json @@ -1,78 +1,78 @@ { - "title": "Error mitigation with Mitiq and PennyLane", - "authors": [ - { - "username": "trbromley" - }, - { - "username": "amari" - } - ], - "dateOfPublication": "2021-11-29T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_error_mitigation_Mitiq_PL.png" - } - ], - "seoDescription": "Learn how to mitigate quantum circuits using Mitiq and PennyLane.", - "doi": "", - "references": [ - { - "id": "proctor2020measuring", - "type": "article", - "title": "Measuring the Capabilities of Quantum Computers", - "authors": "T. Proctor, K. Rudinger, K. Young, E. Nielsen, R. Blume-Kohout", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2008.11294" - }, - { - "id": "temme2017error", - "type": "article", - "title": "Error Mitigation for Short-Depth Quantum Circuits", - "authors": "K. Temme, S. Bravyi, J. M. Gambetta", - "year": "2017", - "journal": "Phys. Rev. Lett.", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.180509" - }, - { - "id": "li2017efficient", - "type": "article", - "title": "Efficient Variational Quantum Simulator Incorporating Active Error Minimization", - "authors": "Y. Li, S. C. Benjamin", - "year": "2017", - "journal": "Phys. Rev. X", - "url": "https://journals.aps.org/prx/abstract/10.1103/PhysRevX.7.021050" - }, - { - "id": "giurgica2020digital", - "type": "article", - "title": "Digital zero noise extrapolation for quantum error mitigation", - "authors": "T. Giurgica-Tiron, Y. Hindy, R. LaRose, A. Mari, W. J. Zeng", - "year": "2020", - "journal": "IEEE International Conference on Quantum Computing and Engineering", - "url": "https://ieeexplore.ieee.org/document/9259940" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_chemical_reactions", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_noisy_circuits", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Error mitigation with Mitiq and PennyLane", + "authors": [ + { + "username": "trbromley" + }, + { + "username": "amari" + } + ], + "dateOfPublication": "2021-11-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_error_mitigation_Mitiq_PL.png" + } + ], + "seoDescription": "Learn how to mitigate quantum circuits using Mitiq and PennyLane.", + "doi": "", + "references": [ + { + "id": "proctor2020measuring", + "type": "article", + "title": "Measuring the Capabilities of Quantum Computers", + "authors": "T. Proctor, K. Rudinger, K. Young, E. Nielsen, R. Blume-Kohout", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2008.11294" + }, + { + "id": "temme2017error", + "type": "article", + "title": "Error Mitigation for Short-Depth Quantum Circuits", + "authors": "K. Temme, S. Bravyi, J. M. Gambetta", + "year": "2017", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.180509" + }, + { + "id": "li2017efficient", + "type": "article", + "title": "Efficient Variational Quantum Simulator Incorporating Active Error Minimization", + "authors": "Y. Li, S. C. Benjamin", + "year": "2017", + "journal": "Phys. Rev. X", + "url": "https://journals.aps.org/prx/abstract/10.1103/PhysRevX.7.021050" + }, + { + "id": "giurgica2020digital", + "type": "article", + "title": "Digital zero noise extrapolation for quantum error mitigation", + "authors": "T. Giurgica-Tiron, Y. Hindy, R. LaRose, A. Mari, W. J. Zeng", + "year": "2020", + "journal": "IEEE International Conference on Quantum Computing and Engineering", + "url": "https://ieeexplore.ieee.org/document/9259940" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_chemical_reactions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_error_prop/demo.py b/demonstrations_v2/tutorial_error_prop/demo.py index fa2e3efd6a..cfd5c77883 100644 --- a/demonstrations_v2/tutorial_error_prop/demo.py +++ b/demonstrations_v2/tutorial_error_prop/demo.py @@ -243,3 +243,5 @@ def circ(H, t, phi1, phi2): # `__ # # +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_error_prop/metadata.json b/demonstrations_v2/tutorial_error_prop/metadata.json index d017eba970..e77f3ee4b3 100644 --- a/demonstrations_v2/tutorial_error_prop/metadata.json +++ b/demonstrations_v2/tutorial_error_prop/metadata.json @@ -1,48 +1,48 @@ { - "title": "How to track algorithmic error using PennyLane", - "authors": [ - { - "username": "Jay" - } - ], - "dateOfPublication": "2024-05-03T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/error_prop/thumbnail_error-prop_2024-05-01.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_error-prop_2024-05-01.png" - } - ], - "seoDescription": "Learn how to propagate error through quantum circuits.", - "doi": "", - "references": [ - { - "id": "TrotterError", - "type": "article", - "title": "Theory of Trotter Error with Commutator Scaling", - "authors": "Andrew M. Childs, Yuan Su, Minh C. Tran, Nathan Wiebe, and Shuchen Zhu", - "year": "2021", - "journal": "Phys. Rev. X", - "url": "https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.011020" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_lcu_blockencoding", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to track algorithmic error using PennyLane", + "authors": [ + { + "username": "Jay" + } + ], + "dateOfPublication": "2024-05-03T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/error_prop/thumbnail_error-prop_2024-05-01.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_error-prop_2024-05-01.png" + } + ], + "seoDescription": "Learn how to propagate error through quantum circuits.", + "doi": "", + "references": [ + { + "id": "TrotterError", + "type": "article", + "title": "Theory of Trotter Error with Commutator Scaling", + "authors": "Andrew M. Childs, Yuan Su, Minh C. Tran, Nathan Wiebe, and Shuchen Zhu", + "year": "2021", + "journal": "Phys. Rev. X", + "url": "https://journals.aps.org/prx/abstract/10.1103/PhysRevX.11.011020" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_lcu_blockencoding", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py b/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py index 22711d54dc..66be8b783e 100644 --- a/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py +++ b/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py @@ -856,3 +856,8 @@ def random_weights(): # `arXiv:2008.08605 `__ (2020). # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/maria_schuld.txt +# +# .. include:: ../_static/authors/johannes_jakob_meyer.txt diff --git a/demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json b/demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json index 5c3d45f9b3..e263e267e4 100644 --- a/demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json +++ b/demonstrations_v2/tutorial_expressivity_fourier_series/metadata.json @@ -1,48 +1,48 @@ { - "title": "Quantum models as Fourier series", - "authors": [ - { - "username": "mariaschuld" - }, - { - "username": "jjmeyer" - } - ], - "dateOfPublication": "2020-08-24T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_models_as_fourier_series.png" - } - ], - "seoDescription": "The class of functions a quantum model can learn is characterized by the structure of its corresponding Fourier series.", - "doi": "", - "references": [ - { - "id": "schuld2020", - "type": "article", - "title": "The effect of data encoding on the expressive power of variational quantum machine learning models.", - "authors": "Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer", - "year": "2020", - "journal": "", - "doi": "10.1103/PhysRevA.103.032430", - "url": "https://arxiv.org/abs/2008.08605" - } - ], - "basedOnPapers": [ - "10.1103/PhysRevA.103.032430" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_data_reuploading_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum models as Fourier series", + "authors": [ + { + "username": "mariaschuld" + }, + { + "username": "jjmeyer" + } + ], + "dateOfPublication": "2020-08-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_models_as_fourier_series.png" + } + ], + "seoDescription": "The class of functions a quantum model can learn is characterized by the structure of its corresponding Fourier series.", + "doi": "", + "references": [ + { + "id": "schuld2020", + "type": "article", + "title": "The effect of data encoding on the expressive power of variational quantum machine learning models.", + "authors": "Maria Schuld, Ryan Sweke, and Johannes Jakob Meyer", + "year": "2020", + "journal": "", + "doi": "10.1103/PhysRevA.103.032430", + "url": "https://arxiv.org/abs/2008.08605" + } + ], + "basedOnPapers": [ + "10.1103/PhysRevA.103.032430" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_falqon/demo.py b/demonstrations_v2/tutorial_falqon/demo.py index 833d5cbb07..98a2e1bbdc 100644 --- a/demonstrations_v2/tutorial_falqon/demo.py +++ b/demonstrations_v2/tutorial_falqon/demo.py @@ -473,3 +473,8 @@ def prob_circuit(params): # Magann, A. B., Rudinger, K. M., Grace, M. D., & Sarovar, M. (2021). Feedback-based quantum optimization. arXiv preprint `arXiv:2103.08619 `__. # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/david_wakeham.txt +# +# .. include:: ../_static/authors/jack_ceroni.txt diff --git a/demonstrations_v2/tutorial_falqon/metadata.json b/demonstrations_v2/tutorial_falqon/metadata.json index 64d17b0991..1df5f4ec09 100644 --- a/demonstrations_v2/tutorial_falqon/metadata.json +++ b/demonstrations_v2/tutorial_falqon/metadata.json @@ -1,53 +1,53 @@ { - "title": "Feedback-Based Quantum Optimization (FALQON)", - "authors": [ - { - "username": "hapax" - }, - { - "username": "jceroni" - } - ], - "dateOfPublication": "2021-05-21T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_FALQON.png" - } - ], - "seoDescription": "Solve combinatorial optimization problems without a classical optimizer", - "doi": "", - "references": [ - { - "id": "Magann2021", - "type": "article", - "title": "Feedback-based quantum optimization", - "authors": "Magann, A. B., Rudinger, K. M., Grace, M. D., & Sarovar, M.", - "year": "2021", - "journal": "", - "doi": "10.1103/PhysRevLett.129.250502", - "url": "https://arxiv.org/abs/2103.08619" - } - ], - "basedOnPapers": [ - "10.1103/PhysRevLett.129.250502" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qaoa_intro", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qaoa_maxcut", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Feedback-Based Quantum Optimization (FALQON)", + "authors": [ + { + "username": "hapax" + }, + { + "username": "jceroni" + } + ], + "dateOfPublication": "2021-05-21T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_FALQON.png" + } + ], + "seoDescription": "Solve combinatorial optimization problems without a classical optimizer", + "doi": "", + "references": [ + { + "id": "Magann2021", + "type": "article", + "title": "Feedback-based quantum optimization", + "authors": "Magann, A. B., Rudinger, K. M., Grace, M. D., & Sarovar, M.", + "year": "2021", + "journal": "", + "doi": "10.1103/PhysRevLett.129.250502", + "url": "https://arxiv.org/abs/2103.08619" + } + ], + "basedOnPapers": [ + "10.1103/PhysRevLett.129.250502" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_fermionic_operators/demo.py b/demonstrations_v2/tutorial_fermionic_operators/demo.py index 41857120ae..b41b286cd1 100644 --- a/demonstrations_v2/tutorial_fermionic_operators/demo.py +++ b/demonstrations_v2/tutorial_fermionic_operators/demo.py @@ -225,3 +225,6 @@ # Peter R. Surjan, "Second Quantized Approach to Quantum Chemistry". Springer-Verlag, 1989. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations_v2/tutorial_fermionic_operators/metadata.json b/demonstrations_v2/tutorial_fermionic_operators/metadata.json index 7c566a8536..6ac634f0ab 100644 --- a/demonstrations_v2/tutorial_fermionic_operators/metadata.json +++ b/demonstrations_v2/tutorial_fermionic_operators/metadata.json @@ -1,55 +1,55 @@ { - "title": "Fermionic operators", - "authors": [ - { - "username": "soran" - } - ], - "dateOfPublication": "2023-06-27T00:00:00+00:00", - "dateOfLastModification": "2024-10-30T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/fermionic_operators/thumbnail_tutorial_fermionic_operators.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_fermionic_operators.png" - }, - { - "type": "hero_image", - "uri": "/_static/hero_illustrations/fermionic_ops_hero.png" - } - ], - "seoDescription": "Construct Hamiltonians and other observables using fermionic creation and annihilation operators.", - "doi": "", - "references": [ - { - "id": "surjan", - "type": "book", - "title": "Second Quantized Approach to Quantum Chemistry", - "authors": "Peter R. Surjan", - "year": "1989", - "publisher": "Springer-Verlag", - "url": "" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Fermionic operators", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2023-06-27T00:00:00+00:00", + "dateOfLastModification": "2024-10-30T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/fermionic_operators/thumbnail_tutorial_fermionic_operators.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_fermionic_operators.png" + }, + { + "type": "hero_image", + "uri": "/_static/hero_illustrations/fermionic_ops_hero.png" + } + ], + "seoDescription": "Construct Hamiltonians and other observables using fermionic creation and annihilation operators.", + "doi": "", + "references": [ + { + "id": "surjan", + "type": "book", + "title": "Second Quantized Approach to Quantum Chemistry", + "authors": "Peter R. Surjan", + "year": "1989", + "publisher": "Springer-Verlag", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py new file mode 100644 index 0000000000..03cc87ea24 --- /dev/null +++ b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py @@ -0,0 +1,522 @@ +r"""Fixed depth Hamiltonian simulation via Cartan decomposition +=============================================================== + +We introduce the powerful Lie-theoretic decomposition technique for Hamiltonians, :math:`H = K h_0 K^\dagger,` +that lets you perform time-evolution by arbitrary times with fixed depth, :math:`e^{-i t H} = K e^{-i t h_0} K^\dagger.` +In particular, we follow the approach in [#Kökcü]_ that directly provides us with a (fixed-depth) circuit +decomposition of the unitaries :math:`K` and :math:`e^{-i t h_0}.` + +Sounds too good to be true? There are of course caveats as this fast-forwards the Hamiltonian. +In this algebraic setting, the crucial property is the size of the relevant :doc:`dynamical Lie algebra ` (DLA). +Most systems yield an exponentially large DLA, making them hard to manage in practice (i.e., fast-forwarding is not possible). +Yet, this is still an extremely powerful mathematical result integral for quantum compilation, +circuit optimization, and Hamiltonian simulation. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.png + :align: center + :width: 70% + :target: javascript:void(0) + +Introduction +------------ + +The :doc:`KAK theorem ` is an important result from Lie theory that states that any Lie group element :math:`U` can be decomposed +as :math:`U = K_1 A K_2,` where :math:`K_{1, 2}` and :math:`A` are elements of two special sub-groups +:math:`\mathcal{K}` and :math:`\mathcal{A},` respectively. In special cases, the decomposition simplifies to :math:`U = K A K^\dagger.` + +You can think of this KAK decomposition as a generalization of +the singular value decomposition to Lie groups. For that, recall that the singular value decomposition states that any +matrix :math:`M \in \mathbb{C}^{m \times n}` can be decomposed as :math:`M = U \Lambda V^\dagger,` where :math:`\Lambda` +are the diagonal singular values and :math:`U \in \mathbb{C}^{m \times \mu}` and :math:`V^\dagger \in \mathbb{C}^{\mu \times n}` +are left- and right-unitary with :math:`\mu = \min(m, n).` + +In the case of the KAK decomposition, :math:`\mathcal{A}` is an Abelian subgroup in which all its elements are commuting, +just as is the case for diagonal matrices. + +We can use this general result from Lie theory as a powerful circuit decomposition technique. + +.. note:: We recommend a basic understanding of Lie algebras, see e.g. our :doc:`introduction to (dynamical) Lie algebras for quantum practitioners `. + Otherwise, this demo should be self-contained, though for the mathematically inclined, we further recommend our :doc:`demo on the KAK theorem ` + that dives into the mathematical depths of the theorem and provides more background info. + +Goal: Fast-forwarding time evolutions using the KAK decomposition +----------------------------------------------------------------- + +Unitary gates in quantum computing are described by the special unitary Lie group :math:`SU(2^n),` so we can use the KAK +theorem to decompose quantum gates into :math:`U = K_1 A K_2.` While the mathematical statement is rather straightforward, +actually finding this decomposition is not. We are going to follow the recipe prescribed in +`Fixed Depth Hamiltonian Simulation via Cartan Decomposition `__ [#Kökcü]_, +which tackles this decomposition on the level of the associated Lie algebra via Cartan decomposition. + +In particular, we are going to consider the problem of time-evolving a Hermitian operator :math:`H` that generates the time-evolution unitary :math:`U = e^{-i t H}.` +We are going to perform a special case of KAK decomposition, a "KhK decomposition" if you will, on the algebraic level in terms of + +.. math:: H = K h_0 K^\dagger. + +This then induces the KAK decomposition on the group level as + +.. math:: e^{-i t H} = K e^{-i t h_0} K^\dagger. + +Let us walk through an explicit example, doing theory and code side-by-side. + +For that, we are going to use the generators of the `Heisenberg model `__ Hamiltonian for :math:`n=4` qubits on a one-dimensional chain, + +.. math:: \{X_i X_{i+1}, Y_i Y_{i+1}, Z_i Z_{i+1}\}_{i=0}^{2}. + +The foundation of a KAK decomposition is a Cartan decomposition of the associated Lie algebra :math:`\mathfrak{g}.` +Now, let us first import some libraries that we are going to use later and construct :math:`\mathfrak{g}.` + + +""" +from datetime import datetime +import matplotlib.pyplot as plt + +import numpy as np +import pennylane as qml +from pennylane import X, Y, Z + +import jax +import jax.numpy as jnp +import optax +jax.config.update("jax_enable_x64", True) + +n_wires = 4 +gens = [X(i) @ X(i+1) for i in range(n_wires-1)] +gens += [Y(i) @ Y(i+1) for i in range(n_wires-1)] +gens += [Z(i) @ Z(i+1) for i in range(n_wires-1)] + +H = qml.sum(*gens) + +g = qml.lie_closure(gens) +g = [op.pauli_rep for op in g] + +############################################################################## +# +# Cartan decomposition +# -------------------- +# +# A Cartan decomposition is a bipartition :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}` into a vertical subspace +# :math:`\mathfrak{k}` and an orthogonal horizontal subspace :math:`\mathfrak{m}.` In practice, it can be induced by an +# involution function :math:`\Theta` that fulfills :math:`\Theta(\Theta(g)) = g \ \forall g \in \mathfrak{g}.` Different +# involutions lead to different types of Cartan decompositions, which have been fully classified by Cartan +# (see `the classification of symmetric spaces by Cartan `__). +# +# .. admonition:: Notation +# :class: note +# +# Note that :math:`\mathfrak{k}` is the small letter k in +# `Fraktur `__ and a +# common — not our — choice for the vertical subspace in a Cartan decomposition. +# +# One common choice of involution is the so-called even-odd involution for Pauli words, +# :math:`P = P_1 \otimes P_2 .. \otimes P_n,` where :math:`P_j \in \{I, X, Y, Z\}.` +# It essentially counts whether the number of non-identity Pauli operators in the Pauli word is even or odd. + +def even_odd_involution(op): + """Generalization of EvenOdd to sums of Paulis""" + [pw] = op.pauli_rep + return len(pw) % 2 + +even_odd_involution(X(0)), even_odd_involution(X(0) @ Y(3)) + +############################################################################## +# +# The vertical and horizontal subspaces are the two eigenspaces of the involution, corresponding to the :math:`\pm 1` eigenvalues. +# In particular, we have :math:`\Theta(\mathfrak{k}) = \mathfrak{k}` and :math:`\Theta(\mathfrak{m}) = - \mathfrak{m}.` +# So in order to perform the Cartan decomposition :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m},` we simply +# sort the operators by whether or not they yield a plus or minus sign from the involution function. +# This is possible because the operators and involution nicely align with the eigenspace decomposition. + +def cartan_decomposition(g, involution): + """Cartan Decomposition g = k + m + + Args: + g (List[PauliSentence]): the (dynamical) Lie algebra to decompose + involution (callable): Involution function :math:`\Theta(\cdot)` to act on PauliSentence ops, should return ``0/1`` or ``True/False``. + + Returns: + k (List[PauliSentence]): the vertical subspace :math:`\Theta(x) = x` + m (List[PauliSentence]): the horizontal subspace :math:`\Theta(x) = -x` """ + m = [] + k = [] + + for op in g: + if involution(op): # vertical space when involution returns True + k.append(op) + else: # horizontal space when involution returns False + m.append(op) + return k, m + +k, m = cartan_decomposition(g, even_odd_involution) +len(g), len(k), len(m) + + +############################################################################## +# We have successfully decomposed the 60-dimensional Lie algebra +# into a 24-dimensional vertical subspace and a 36-dimensional subspace. +# +# Note that not every bipartition of a Lie algebra constitutes a Cartan decomposition. +# For that, the subspaces need to fulfill the following three commutation relations. +# +# .. math:: +# \begin{align} +# [\mathfrak{k}, \mathfrak{k}] \subseteq \mathfrak{k} & \text{ (subalgebra)}\\ +# [\mathfrak{k}, \mathfrak{m}] \subseteq \mathfrak{m} & \text{ (reductive property)}\\ +# [\mathfrak{m}, \mathfrak{m}] \subseteq \mathfrak{k} & \text{ (symmetric property)} +# \end{align} +# +# In particular, :math:`\mathfrak{k}` is closed under commutation and is therefore a subalgebra, whereas :math:`\mathfrak{m}` is not. +# This also has the consequence that the associated Lie group :math:`\mathcal{K} := e^{i \mathfrak{k}}` is a subgroup +# of the associated Lie group :math:`\mathcal{G} := e^{i \mathfrak{g}}.` +# +# We mentioned earlier that we are aiming to do a special case of KAK decomposition where the second unitary :math:`K_2 = K_1^\dagger.` +# This is possible whenever the operator that we want to decompose is in the horizontal subspace :math:`\mathfrak{m}`, i.e. we want :math:`\Theta(H) = -H`. +# The chosen :math:`H` and :math:`\Theta` fulfill this property, as can be easily verified. +# + +for op in H.operands: + assert not even_odd_involution(op) + +############################################################################## +# +# Cartan subalgebra +# ----------------- +# +# With this we have identified the first subgroup, :math:`\mathcal{K},` of the KAK decomposition. The other subgroup +# is induced by the so-called (horizontal) Cartan subalgebra :math:`\mathfrak{h}.` This is a maximal Abelian subalgebra of :math:`\mathfrak{m}` and it is not unique. +# For the case of Pauli words, we can simply pick any element in :math:`\mathfrak{m}` and collect all other operators in :math:`\mathfrak{m}` +# that commute with it. +# +# We then obtain a further split of the vector space :math:`\mathfrak{m} = \tilde{\mathfrak{m}} \oplus \mathfrak{h},` +# where :math:`\tilde{\mathfrak{m}}` is just the remainder of :math:`\mathfrak{m}.` + +def _commutes_with_all(candidate, ops): + r"""Check if ``candidate`` commutes with all ``ops``""" + for op in ops: + com = candidate.commutator(op) + com.simplify() + + if not len(com) == 0: + return False + return True + +def cartan_subalgebra(m, which=0): + """Compute the Cartan subalgebra from the horizontal subspace :math:`\mathfrak{m}` + of the Cartan decomposition + + This implementation is specific for cases of bases of m with pure Pauli words as + detailed in Appendix C in `2104.00728 `__. + + Args: + m (List[PauliSentence]): the horizontal subspace :math:`\Theta(x) = -x + which (int): Choice for the initial element of m from which to construct + the maximal Abelian subalgebra + + Returns: + mtilde (List): remaining elements of :math:`\mathfrak{m}` + s.t. :math:`\mathfrak{m} = \tilde{\mathfrak{m}} \oplus \mathfrak{h}`. + h (List): Cartan subalgebra :math:`\mathfrak{h}`. + + """ + + h = [m[which]] # first candidate + mtilde = m.copy() + + for m_i in m: + if _commutes_with_all(m_i, h): + if m_i not in h: + h.append(m_i) + + for h_i in h: + mtilde.remove(h_i) + + return mtilde, h + +mtilde, h = cartan_subalgebra(m) +len(g), len(k), len(mtilde), len(h) + +############################################################################## +# We now have the Cartan decomposition :math:`\mathfrak{g} = \mathfrak{k} \oplus \tilde{\mathfrak{m}} \oplus \mathfrak{h}` +# and with that all the necessary ingredients for the KAK decomposition. +# +# Variational KhK decomposition +# ----------------------------- +# +# The KAK theorem is not constructive in the sense that it proves that there exists such a decomposition, but there is no general way of obtaining +# it. In particular, there are no linear algebra subroutines implemented in ``numpy`` or ``scipy`` that just compute it for us. +# Here, we follow the construction of [#Kökcü]_ for the special case of :math:`H` being in the horizontal space and the decomposition +# simplifying to :math:`H = K^\dagger h K`. +# +# The authors propose to find a local extremum of the cost function +# +# .. math:: f(\theta) = \langle K(\theta) v K(\theta)^\dagger, H\rangle +# +# where :math:`\langle \cdot, \cdot \rangle` is some inner product (in our case, the trace inner product :math:`\langle A, B \rangle = \text{tr}(A^\dagger B)).` +# This construction uses the operator :math:`v = \sum_j \pi^j h_j \in \mathfrak{h}` +# that is such that :math:`e^{i t v}` is dense in :math:`e^{i \mathcal{h}}.` +# The latter means that for any :math:`e^{i \mathcal{h}}` there is a :math:`t \in \mathbb{R}` such that :math:`e^{i t v}` approximates it. +# Let us construct it. To numerically avoid very large vectors, we take :math:`\pi (\text{mod} 2),` which preserves the dense property of :math:`v`. + +gammas = [np.pi**i % 2 for i in range(1, len(h)+1)] + +v = qml.dot(gammas, h) +v_m = qml.matrix(v, wire_order=range(n_wires)) +v_m = jnp.array(v_m) + + +############################################################################## +# +# This procedure has the advantage that we can use an already decomposed ansatz +# +# .. math:: K(\theta) = \prod_j e^{-i \theta_j k_j} +# +# for the vertical unitary. +# +# Now we just have to define the cost function and find an extremum. +# In this case, we are going to use gradient descent to minimize the cost function to a minimum. +# We are going to use ``jax`` and ``optax`` and write some boilerplate for the optimization procedure. +# +# .. note:: +# You can check our demos on parameter optimization in JAX with +# :doc:`Optax ` or +# :doc:`JAXOpt `. +# + +def run_opt( + value_and_grad, + theta, + n_epochs=500, + lr=0.1, +): + """Boilerplate JAX optimization""" + value_and_grad = jax.jit(jax.value_and_grad(loss)) + optimizer = optax.lbfgs(learning_rate=lr, memory_size=100) + opt_state = optimizer.init(theta) + + energy = [] + gradients = [] + thetas = [] + + @jax.jit + def step(opt_state, theta): + val, grad_circuit = value_and_grad(theta) + updates, opt_state = optimizer.update( + grad_circuit, opt_state, theta, value=val, grad=grad_circuit, value_fn=loss + ) + theta = optax.apply_updates(theta, updates) + + return opt_state, theta, val + + t0 = datetime.now() + ## Optimization loop + for _ in range(n_epochs): + opt_state, theta, val = step(opt_state, theta) + + energy.append(val) + thetas.append(theta) + + t1 = datetime.now() + print(f"final loss: {val}; min loss: {np.min(energy)}; after {t1 - t0}") + + return thetas, energy, gradients + + +############################################################################## +# We can now implement the cost function and find a minimum via gradient descent. + +H_m = qml.matrix(H, wire_order=range(n_wires)) +H_m = jnp.array(H_m) + +def K(theta, k): + for th, k_j in zip(theta, k): + qml.exp(-1j * th * k_j.operation()) + +@jax.jit +def loss(theta): + K_m = qml.matrix(K, wire_order=range(n_wires))(theta, k) + A = K_m @ v_m @ K_m.conj().T + return jnp.trace(A.conj().T @ H_m).real + +theta0 = jnp.ones(len(k), dtype=float) + +thetas, energy, _ = run_opt(loss, theta0, n_epochs=600, lr=0.05) +plt.plot(energy - np.min(energy)) +plt.xlabel("epochs") +plt.ylabel("cost") +plt.yscale("log") +plt.show() + + +############################################################################## +# This gives us the optimal values of the parameters :math:`\theta_\text{opt}` of :math:`K(\theta_\text{opt}) =: K_c.` + +theta_opt = thetas[-1] +Kc_m = qml.matrix(K, wire_order=range(n_wires))(theta_opt, k) + +############################################################################## +# The special element :math:`h_0` from the Cartan subalgebra :math:`\mathfrak{h}` is given by +# rotating the Hamiltonian by the critical :math:`K_c,` +# +# .. math:: h_0 = K_c^\dagger H K_c. + +h_0_m = Kc_m.conj().T @ H_m @ Kc_m +h_0 = qml.pauli_decompose(h_0_m) + +print(len(h_0)) + +# assure that h_0 is in \mathfrak{h} +h_vspace = qml.pauli.PauliVSpace(h) +not h_vspace.is_independent(h_0.pauli_rep) + +############################################################################## +# +# The fact that :math:`K_c^\dagger H K_c \in \mathfrak{h}` is crucial for this decomposition to be valid and meaningful. +# Otherwise, :math:`h_0` could be anything and we arrive back at the original problem of decomposing :math:`e^{-i t h_0}`. +# Here we know that :math:`h_0` is composed of elements of an Abelian Lie algebra :math:`\mathfrak{h}`, such that we can +# trivially decompose its unitary as +# +# .. math:: e^{-i t h_0} = e^{-i t \sum_{j=1}^{|\mathfrak{h}|} c_j h_j} = \prod_{j=1}^{|\mathfrak{h}|} e^{-i t c_j h_j}. +# +# Overall, this gives us the KhK decomposition of :math:`H,` +# +# .. math:: H = K_c h_0 K_c^\dagger. +# +# This trivially reproduces the original Hamiltonian. +# + +H_re = Kc_m @ h_0_m @ Kc_m.conj().T +np.allclose(H_re, H_m) + +############################################################################## +# We can now check if the Hamiltonian evolution is reproduced correctly. +# + +t = 1. +U_exact = qml.exp(-1j * t * H) +U_exact_m = qml.matrix(U_exact, wire_order=range(n_wires)) + +def U_kak(theta_opt, t): + qml.adjoint(K)(theta_opt, k) + qml.exp(-1j * t * h_0) + K(theta_opt, k) + +U_kak_m = qml.matrix(U_kak, wire_order=range(n_wires))(theta_opt, t) + +def trace_distance(A, B): + return 1 - np.abs(np.trace(A.conj().T @ B))/len(A) + +trace_distance(U_exact_m, U_kak_m) + + + + + +############################################################################## +# Indeed we find that the KAK decomposition that we found reproduces the unitary evolution operator. +# Note that this is valid for arbitrary :math:`t,` such that the Hamiltonian simulation operator has a fixed depth. +# +# .. admonition:: Choice of algebra representation +# :class: note +# +# In the case of Pauli words, we could make use of the well-known commutation relations of Pauli operators and treat the whole calculation semi-analytically +# (as is done in [#Kökcü]_). +# Here, we have chosen the full :math:`2^n` Hilbert space representation of the involved operators as this is actually faster for the chosen size :math:`n=4`. +# +# More generally, we can use the adjoint identity introdcued in our :doc:`g-sim demo ` and perform all computations in the +# adjoint representation of the algebra. In that case, the dimension of the algebra is the determining factor. This is useful when a low-dimensional Lie algebra +# is embedded in a high dimensional Hilbert space. For example, the operators :math:`S^x_\text{total} = \sum_{j=1}^n X_j,` +# :math:`S^y_\text{total} = \sum_{j=1}^n Y_j` and :math:`S^z_\text{total} = \sum_{j=1}^n Z_j` make up :math:`\mathfrak{su}(2)` of dimension :math:`3`, but are embedded +# in a :math:`2^n`-dimensional Hilbert space. +# + +############################################################################## +# Time evolutions +# --------------- +# +# We compute multiple time evolutions for different times and compare Suzuki—Trotter products with the KAK decomposition circuit. +# + +ts = jnp.linspace(1., 5., 10) + +Us_exact = jax.vmap(lambda t: qml.matrix(qml.exp(-1j * t * H), wire_order=range(n_wires)))(ts) + +def Us_kak(t): + return Kc_m @ jax.scipy.linalg.expm(-1j * t * h_0_m) @ Kc_m.conj().T + +Us_kak = jax.vmap(Us_kak)(ts) +Us_trotter5 = jax.vmap(lambda t: qml.matrix(qml.TrotterProduct(H, time=-t, n=5, order=4), wire_order=range(n_wires)))(ts) +Us_trotter50 = jax.vmap(lambda t: qml.matrix(qml.TrotterProduct(H, time=-t, n=50, order=4), wire_order=range(n_wires)))(ts) + +def compute_res(Us): + # vectorized trace inner product + res = jnp.abs(jnp.einsum("bij,bji->b", Us_exact.conj(), Us)) + res /= 2**n_wires + return 1 - res + +res_kak = compute_res(Us_kak) +res_trotter5 = compute_res(Us_trotter5) +res_trotter50 = compute_res(Us_trotter50) + +plt.plot(ts, res_kak+1e-15, label="KAK") # displace by machine precision to see it still in plot +plt.plot(ts, res_trotter5, "x--", label="5 Trotter steps") +plt.plot(ts, res_trotter50, ".-", label="50 Trotter steps") +plt.ylabel("empirical error") +plt.xlabel("t") +plt.yscale("log") +plt.legend() +plt.show() + + +############################################################################## +# We see the expected behavior of Suzuki—Trotter product formulas getting worse with an increase in time +# while the KAK error is constant zero. +# +# The KAK decomposition is particularly well-suited for smaller systems as the circuit depth is equal to the +# dimension of the subspaces, in particular :math:`2 |\mathfrak{k}| + |\mathfrak{h}|.` Note, however, +# that these dimensions typically scale exponentially in the system size. +# + + +############################################################################## +# +# Conclusion +# ---------- +# +# The KAK theorem is a very general mathematical result with far-reaching consequences. +# While there is no canonical way of obtaining an actual decomposition in practice, we followed +# the approach of [#Kökcü]_ which uses a specifically designed loss function and variational +# optimization to find the decomposition. +# This approach has the advantage that the resulting decomposition is itself already decomposed in terms of rotation gates in the original Lie algebra, +# as opposed to other methods such as [#Chu]_ that find :math:`K` as a whole. +# We provided a flexible pipeline that lets users find KAK decompositions in PennyLane for systems with small +# DLA (:doc:`Dynamical Lie Algebras `) and specifically decomposed the Heisenberg model Hamiltonian with :math:`n=4` qubits that has a DLA of dimension :math:`60` (:math:`\left(\mathfrak{s u}(2^{n-2})\right)^{\oplus 4}`). +# +# As most DLAs scale exponentially in the number of qubits, KAK decompositions are limited to small system sizes +# or special cases of systems with small DLAs. +# This is in line with the notion that fast-forwarding is generally not possible and limited to special systems. +# In particular, a KAK decomposition is ultimately always a fast-forwarding of a Hamiltonian. + + + +############################################################################## +# +# References +# ---------- +# +# .. [#Kökcü] +# +# Efekan Kökcü, Thomas Steckmann, Yan Wang, J. K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper +# "Fixed Depth Hamiltonian Simulation via Cartan Decomposition" +# `arXiv:2104.00728 `__, 2021. +# +# .. [#Chu] +# +# Moody T. Chu +# "Lax dynamics for Cartan decomposition with applications to Hamiltonian simulation" +# `doi:10.1093/imanum/drad018 `__, `preprint PDF `__ 2024. +# +# + +############################################################################## +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json new file mode 100644 index 0000000000..b66ab6437f --- /dev/null +++ b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json @@ -0,0 +1,65 @@ +{ + "title": "Fixed depth Hamiltonian simulation via Cartan decomposition", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-12-19T00:00:00+00:00", + "dateOfLastModification": "2024-12-19T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_fixed_depth_hamiltonian_simulation_via_cartan_decomposition.png" + } + ], + "seoDescription": "Using Cartan decomposition and the KAK theorem to decompose unitary gates following the approach in the paper Fixed Depth Hamiltonian Simulation via Cartan Decomposition (arXiv:2104.00728) with a code implementation in PennyLane.", + "doi": "", + "references": [ + { + "id": "Kökcü", + "type": "preprint", + "title": "Fixed Depth Hamiltonian Simulation via Cartan Decomposition", + "authors": "Efekan Kökcü, Thomas Steckmann, Yan Wang, J. K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper", + "year": "2021", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2104.00728", + "url": "https://arxiv.org/abs/2104.00728" + }, + { + "id": "Chu", + "type": "preprint", + "title": "Lax dynamics for Cartan decomposition with applications to Hamiltonian simulation", + "authors": "Moody T. Chu", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.1093/imanum/drad018", + "url": "https://mtchu.math.ncsu.edu/Research/Papers/Cartan_02.pdf" + } + ], + "basedOnPapers": ["10.48550/arXiv.2104.00728"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kak_theorem", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_gaussian_transformation/demo.py b/demonstrations_v2/tutorial_gaussian_transformation/demo.py index 48ba4fb6b0..03e2f4f611 100644 --- a/demonstrations_v2/tutorial_gaussian_transformation/demo.py +++ b/demonstrations_v2/tutorial_gaussian_transformation/demo.py @@ -167,3 +167,6 @@ def cost(params): # hybrid qubit-CV-classical computation using PennyLane. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_gaussian_transformation/metadata.json b/demonstrations_v2/tutorial_gaussian_transformation/metadata.json index 2047aa555e..771442bc90 100644 --- a/demonstrations_v2/tutorial_gaussian_transformation/metadata.json +++ b/demonstrations_v2/tutorial_gaussian_transformation/metadata.json @@ -1,42 +1,42 @@ { - "title": "Gaussian transformation", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_gaussian_transformation.png" - } - ], - "seoDescription": "Use quantum machine learning techniques to tune a beamsplitter.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "plugins_hybrid", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "quantum_neural_net", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "qonn", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Gaussian transformation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_gaussian_transformation.png" + } + ], + "seoDescription": "Use quantum machine learning techniques to tune a beamsplitter.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "plugins_hybrid", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_neural_net", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qonn", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_general_parshift/demo.py b/demonstrations_v2/tutorial_general_parshift/demo.py index 8fddd2e8db..62a267ca37 100644 --- a/demonstrations_v2/tutorial_general_parshift/demo.py +++ b/demonstrations_v2/tutorial_general_parshift/demo.py @@ -886,3 +886,6 @@ def finite_diff_second(fun): # .. _Rotosolve_code: https://pennylane.readthedocs.io/en/stable/code/api/pennylane.RotosolveOptimizer.html ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/david_wierichs.txt diff --git a/demonstrations_v2/tutorial_general_parshift/metadata.json b/demonstrations_v2/tutorial_general_parshift/metadata.json index d411e7cf26..18d408e1ff 100644 --- a/demonstrations_v2/tutorial_general_parshift/metadata.json +++ b/demonstrations_v2/tutorial_general_parshift/metadata.json @@ -1,90 +1,88 @@ { - "title": "Generalized parameter-shift rules", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2021-08-23T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generalized_parametershift_rules.png" - } - ], - "seoDescription": "Reconstruct quantum functions and compute their derivatives.", - "doi": "", - "references": [ - { - "id": "GenPar", - "type": "article", - "title": "General parameter-shift rules for quantum gradients", - "authors": "David Wierichs, Josh Izaac, Cody Wang, Cedric Yen-Yu Lin", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2107.12390" - }, - { - "id": "CalcPQC", - "type": "article", - "title": "Calculus on parameterized quantum circuits", - "authors": "Javier Gil Vidal, Dirk Oliver Theis", - "year": "2018", - "journal": "", - "url": "https://arxiv.org/abs/1812.06323" - }, - { - "id": "Rotosolve", - "type": "article", - "title": "Structure optimization for parameterized quantum circuits", - "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Benedetti", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1905.09692" - }, - { - "id": "AlgeShift", - "type": "article", - "title": "Analytic gradients in variational quantum algorithms: Algebraic extensions of the parameter-shift rule to general unitary transformations", - "authors": "Artur F. Izmaylov, Robert A. Lang, Tzu-Ching Yen", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2107.08131" - }, - { - "id": "GenDiffRules", - "type": "article", - "title": "Generalized quantum circuit differentiation rules", - "authors": "Oleksandr Kyriienko, Vincent E. Elfving", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2108.01218" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2107.12390" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_expressivity_fourier_series", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_rotoselect", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_analytic_descent", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Generalized parameter-shift rules", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2021-08-23T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generalized_parametershift_rules.png" + } + ], + "seoDescription": "Reconstruct quantum functions and compute their derivatives.", + "doi": "", + "references": [ + { + "id": "GenPar", + "type": "article", + "title": "General parameter-shift rules for quantum gradients", + "authors": "David Wierichs, Josh Izaac, Cody Wang, Cedric Yen-Yu Lin", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2107.12390" + }, + { + "id": "CalcPQC", + "type": "article", + "title": "Calculus on parameterized quantum circuits", + "authors": "Javier Gil Vidal, Dirk Oliver Theis", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1812.06323" + }, + { + "id": "Rotosolve", + "type": "article", + "title": "Structure optimization for parameterized quantum circuits", + "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Benedetti", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1905.09692" + }, + { + "id": "AlgeShift", + "type": "article", + "title": "Analytic gradients in variational quantum algorithms: Algebraic extensions of the parameter-shift rule to general unitary transformations", + "authors": "Artur F. Izmaylov, Robert A. Lang, Tzu-Ching Yen", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2107.08131" + }, + { + "id": "GenDiffRules", + "type": "article", + "title": "Generalized quantum circuit differentiation rules", + "authors": "Oleksandr Kyriienko, Vincent E. Elfving", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2108.01218" + } + ], + "basedOnPapers": ["10.48550/arXiv.2107.12390"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rotoselect", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_analytic_descent", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_geometric_qml/demo.py b/demonstrations_v2/tutorial_geometric_qml/demo.py index 65becb2126..eb43a2c4bc 100644 --- a/demonstrations_v2/tutorial_geometric_qml/demo.py +++ b/demonstrations_v2/tutorial_geometric_qml/demo.py @@ -896,3 +896,6 @@ def opt_func(): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/richard_east.txt diff --git a/demonstrations_v2/tutorial_geometric_qml/metadata.json b/demonstrations_v2/tutorial_geometric_qml/metadata.json index 63142ced4c..2f2e2a6687 100644 --- a/demonstrations_v2/tutorial_geometric_qml/metadata.json +++ b/demonstrations_v2/tutorial_geometric_qml/metadata.json @@ -1,60 +1,60 @@ { - "title": "Introduction to Geometric Quantum Machine Learning", - "authors": [ - { - "username": "reast" - } - ], - "dateOfPublication": "2022-10-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_intro_to_geometric_QML.png" - } - ], - "seoDescription": "Using the natural symmetries in a quantum learning problem can improve learning", - "doi": "", - "references": [ - { - "id": "Bronstein2021", - "type": "article", - "title": "Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges", - "authors": "Michael M. Bronstein, Joan Bruna, Taco Cohen, Petar Veli\u010dkovi\u0107", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2104.13478" - }, - { - "id": "Nguyen2022", - "type": "article", - "title": "Theory for Equivariant Quantum Neural Networks", - "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Fr\u00e9d\u00e9ric Sauvage, Mart\u00edn Larocca, and M. Cerezo", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2210.08566" - }, - { - "id": "Meyer2022", - "type": "article", - "title": "Exploiting symmetry in variational quantum machine learning", - "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2205.06217" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_equivariant_graph_embedding", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Introduction to Geometric Quantum Machine Learning", + "authors": [ + { + "username": "reast" + } + ], + "dateOfPublication": "2022-10-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_intro_to_geometric_QML.png" + } + ], + "seoDescription": "Using the natural symmetries in a quantum learning problem can improve learning", + "doi": "", + "references": [ + { + "id": "Bronstein2021", + "type": "article", + "title": "Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges", + "authors": "Michael M. Bronstein, Joan Bruna, Taco Cohen, Petar Veličković", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2104.13478" + }, + { + "id": "Nguyen2022", + "type": "article", + "title": "Theory for Equivariant Quantum Neural Networks", + "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Frédéric Sauvage, Martín Larocca, and M. Cerezo", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.08566" + }, + { + "id": "Meyer2022", + "type": "article", + "title": "Exploiting symmetry in variational quantum machine learning", + "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2205.06217" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_equivariant_graph_embedding", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_givens_rotations/demo.py b/demonstrations_v2/tutorial_givens_rotations/demo.py index 4da5630b53..ddbd9ab254 100644 --- a/demonstrations_v2/tutorial_givens_rotations/demo.py +++ b/demonstrations_v2/tutorial_givens_rotations/demo.py @@ -493,3 +493,6 @@ def state_preparation(params): # quantum circuits for quantum chemistry", arXiv:2106.13839, (2021) # # +# About the author +# ---------------- +# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_givens_rotations/metadata.json b/demonstrations_v2/tutorial_givens_rotations/metadata.json index 026702d6ef..7ce03efc41 100644 --- a/demonstrations_v2/tutorial_givens_rotations/metadata.json +++ b/demonstrations_v2/tutorial_givens_rotations/metadata.json @@ -1,65 +1,65 @@ { - "title": "Givens rotations for quantum chemistry", - "authors": [ - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2021-06-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_Givens_rotations_for_QChem.png" - } - ], - "seoDescription": "Discover the building blocks of quantum circuits for quantum chemistry", - "doi": "", - "references": [ - { - "id": "anselmetti", - "type": "article", - "title": "Local, Expressive, Quantum-Number-Preserving VQE Ansatze for Fermionic Systems", - "authors": "G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish", - "year": "2021", - "journal": "", - "url": "" - }, - { - "id": "barkoutsos", - "type": "article", - "title": "Quantum algorithms for electronic structure calculations: Particle-hole Hamiltonian and optimized wave-function expansions", - "authors": "P. KL. Barkoutsos, Panagiotis, et al.", - "year": "2018", - "journal": "Physical Review A", - "url": "" - }, - { - "id": "arrazola", - "type": "article", - "title": "Universal quantum circuits for quantum chemistry", - "authors": "J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran", - "year": "2021", - "journal": "", - "url": "" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Givens rotations for quantum chemistry", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2021-06-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_Givens_rotations_for_QChem.png" + } + ], + "seoDescription": "Discover the building blocks of quantum circuits for quantum chemistry", + "doi": "", + "references": [ + { + "id": "anselmetti", + "type": "article", + "title": "Local, Expressive, Quantum-Number-Preserving VQE Ansatze for Fermionic Systems", + "authors": "G-L. R. Anselmetti, D. Wierichs, C. Gogolin, Christian, and R. M. Parrish", + "year": "2021", + "journal": "", + "url": "" + }, + { + "id": "barkoutsos", + "type": "article", + "title": "Quantum algorithms for electronic structure calculations: Particle-hole Hamiltonian and optimized wave-function expansions", + "authors": "P. KL. Barkoutsos, Panagiotis, et al.", + "year": "2018", + "journal": "Physical Review A", + "url": "" + }, + { + "id": "arrazola", + "type": "article", + "title": "Universal quantum circuits for quantum chemistry", + "authors": "J. M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran", + "year": "2021", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_grovers_algorithm/demo.py b/demonstrations_v2/tutorial_grovers_algorithm/demo.py index 4370816294..8230cc98fd 100644 --- a/demonstrations_v2/tutorial_grovers_algorithm/demo.py +++ b/demonstrations_v2/tutorial_grovers_algorithm/demo.py @@ -390,3 +390,6 @@ def circuit(): # M. A. Nielsen, and I. L. Chuang (2000) "Quantum Computation and Quantum Information", # Cambridge University Press. # +# About the author +# ---------------- +# .. include:: ../_static/authors/ludmila_botelho.txt diff --git a/demonstrations_v2/tutorial_grovers_algorithm/metadata.json b/demonstrations_v2/tutorial_grovers_algorithm/metadata.json index 84744be7f6..4a17a4b597 100644 --- a/demonstrations_v2/tutorial_grovers_algorithm/metadata.json +++ b/demonstrations_v2/tutorial_grovers_algorithm/metadata.json @@ -1,39 +1,39 @@ { - "title": "Grover's Algorithm", - "authors": [ - { - "username": "lbotelho" - } - ], - "dateOfPublication": "2023-07-03T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Getting Started", - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/grovers_algorithm/thumbnail_tutorial_grovers_algorithm.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_grovers_algorithm.png" - } - ], - "seoDescription": "Learn how to find an entry in a list using Grover's Algorithm", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qft_arithmetics", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/understanding-grover/3340" -} \ No newline at end of file + "title": "Grover's Algorithm", + "authors": [ + { + "username": "lbotelho" + } + ], + "dateOfPublication": "2023-07-03T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/grovers_algorithm/thumbnail_tutorial_grovers_algorithm.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_grovers_algorithm.png" + } + ], + "seoDescription": "Learn how to find an entry in a list using Grover's Algorithm", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/understanding-grover/3340" +} diff --git a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py index 0d667619e2..e82eef9302 100644 --- a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py +++ b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/demo.py @@ -251,3 +251,6 @@ def pl_circuit(): # ###################################################################### +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json index 538a4d3b49..e2609e360c 100644 --- a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json +++ b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/metadata.json @@ -1,46 +1,46 @@ { - "title": "Your guide to PennyLane if you know Qiskit", - "authors": [ - { - "username": "isaacdevlugt" - } - ], - "dateOfPublication": "2024-07-22T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_guide_to_pennylane_knowing_qiskit.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_guide_to_pennylane_knowing_qiskit.png" - } - ], - "seoDescription": "This is your ultimate guide to PennyLane if you already know Qiskit.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "how_to_use_qiskit1_with_pennylane", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_diffable-mitigation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qubit_rotation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Your guide to PennyLane if you know Qiskit", + "authors": [ + { + "username": "isaacdevlugt" + } + ], + "dateOfPublication": "2024-07-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_guide_to_pennylane_knowing_qiskit.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_guide_to_pennylane_knowing_qiskit.png" + } + ], + "seoDescription": "This is your ultimate guide to PennyLane if you already know Qiskit.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "how_to_use_qiskit1_with_pennylane", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_haar_measure/metadata.json b/demonstrations_v2/tutorial_haar_measure/metadata.json index 9485f9df57..eea684494c 100644 --- a/demonstrations_v2/tutorial_haar_measure/metadata.json +++ b/demonstrations_v2/tutorial_haar_measure/metadata.json @@ -1,144 +1,144 @@ { - "title": "Understanding the Haar measure", - "authors": [ - { - "username": "glassnotes" - } - ], - "dateOfPublication": "2021-03-22T00:00:00+00:00", - "dateOfLastModification": "2024-10-11T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_understanding_Haar_measure.png" - } - ], - "seoDescription": "Learn all about the Haar measure and how to randomly sample quantum states.", - "doi": "", - "references": [ - { - "id": "NandC2000", - "type": "book", - "title": "Quantum Computation and Quantum Information", - "authors": "M. A. Nielsen, and I. L. Chuang", - "year": "2000", - "publisher": "Cambridge University Press", - "journal": "", - "url": "" - }, - { - "id": "deGuise2018", - "type": "article", - "title": "Simple factorization of unitary transformations", - "authors": "H. de Guise, O. Di Matteo, and L. L. S\u00e1nchez-Soto", - "year": "2018", - "journal": "Phys. Rev. A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.97.022328" - }, - { - "id": "Clements2016", - "type": "article", - "title": "Optimal design for universal multiport interferometers", - "authors": "W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and I. A. Walmsley", - "year": "2016", - "journal": "Optica", - "url": "https://www.osapublishing.org/optica/fulltext.cfm?uri=optica-3-12-1460&id=355743" - }, - { - "id": "Reck1994", - "type": "article", - "title": "Experimental realization of any discrete unitary operator", - "authors": "M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani", - "year": "1994", - "journal": "Phys. Rev. Lett.", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58" - }, - { - "id": "Mezzadri2006", - "type": "article", - "title": "How to generate random matrices from the classical compact groups", - "authors": "F. Mezzadri", - "year": "2006", - "journal": "", - "url": "https://arxiv.org/abs/math-ph/0609050" - }, - { - "id": "Meckes2014", - "type": "book", - "title": "The Random Matrix Theory of the Classical Compact Groups", - "authors": "E. Meckes", - "year": "2019", - "publisher": "Cambridge University Press", - "url": "https://case.edu/artsci/math/esmeckes/Haar_book.pdf" - }, - { - "id": "Gerken2013", - "type": "article", - "title": "Measure concentration: Levy's Lemma", - "authors": "M. Gerken", - "year": "2013", - "url": "http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.679.2560" - }, - { - "id": "Hayden2006", - "type": "article", - "title": "Aspects of generic entanglement", - "authors": "P. Hayden, D. W. Leung, and A. Winter", - "year": "2006", - "journal": "Comm. Math. Phys.", - "volume": "265", - "number": "1", - "pages": "95-117", - "url": "https://link.springer.com/article/10.1007%2Fs00220-006-1535-6" - }, - { - "id": "McClean2018", - "type": "article", - "title": "Barren plateaus in quantum neural network training landscapes", - "authors": "J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven", - "year": "2018", - "journal": "Nature Communications", - "doi": "10.1038/s41467-018-07090-4", - "url": "" - }, - { - "id": "Holmes2021", - "type": "article", - "title": "Connecting ansatz expressibility to gradient magnitudes and barren plateaus", - "authors": "Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2101.02138" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_unitary_designs", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "quantum_volume", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "qsim_beyond_classical", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_barren_plateaus", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/understanding-the-haar-measure-demo/7334" -} \ No newline at end of file + "title": "Understanding the Haar measure", + "authors": [ + { + "username": "glassnotes" + } + ], + "dateOfPublication": "2021-03-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_understanding_Haar_measure.png" + } + ], + "seoDescription": "Learn all about the Haar measure and how to randomly sample quantum states.", + "doi": "", + "references": [ + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "journal": "", + "url": "" + }, + { + "id": "deGuise2018", + "type": "article", + "title": "Simple factorization of unitary transformations", + "authors": "H. de Guise, O. Di Matteo, and L. L. Sánchez-Soto", + "year": "2018", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.97.022328" + }, + { + "id": "Clements2016", + "type": "article", + "title": "Optimal design for universal multiport interferometers", + "authors": "W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer, and I. A. Walmsley", + "year": "2016", + "journal": "Optica", + "url": "https://www.osapublishing.org/optica/fulltext.cfm?uri=optica-3-12-1460&id=355743" + }, + { + "id": "Reck1994", + "type": "article", + "title": "Experimental realization of any discrete unitary operator", + "authors": "M. Reck, A. Zeilinger, H. J. Bernstein, and P. Bertani", + "year": "1994", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58" + }, + { + "id": "Mezzadri2006", + "type": "article", + "title": "How to generate random matrices from the classical compact groups", + "authors": "F. Mezzadri", + "year": "2006", + "journal": "", + "url": "https://arxiv.org/abs/math-ph/0609050" + }, + { + "id": "Meckes2014", + "type": "book", + "title": "The Random Matrix Theory of the Classical Compact Groups", + "authors": "E. Meckes", + "year": "2019", + "publisher": "Cambridge University Press", + "url": "https://case.edu/artsci/math/esmeckes/Haar_book.pdf" + }, + { + "id": "Gerken2013", + "type": "article", + "title": "Measure concentration: Levy's Lemma", + "authors": "M. Gerken", + "year": "2013", + "url": "http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.679.2560" + }, + { + "id": "Hayden2006", + "type": "article", + "title": "Aspects of generic entanglement", + "authors": "P. Hayden, D. W. Leung, and A. Winter", + "year": "2006", + "journal": "Comm. Math. Phys.", + "volume": "265", + "number": "1", + "pages": "95-117", + "url": "https://link.springer.com/article/10.1007%2Fs00220-006-1535-6" + }, + { + "id": "McClean2018", + "type": "article", + "title": "Barren plateaus in quantum neural network training landscapes", + "authors": "J. R. McClean, S. Boixo, V. N. Smelyanskiy, R. Babbush, and H. Neven", + "year": "2018", + "journal": "Nature Communications", + "doi": "10.1038/s41467-018-07090-4", + "url": "" + }, + { + "id": "Holmes2021", + "type": "article", + "title": "Connecting ansatz expressibility to gradient magnitudes and barren plateaus", + "authors": "Z. Holmes, K. Sharma, M. Cerezo, and P. J. Coles", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2101.02138" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "quantum_volume", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qsim_beyond_classical", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/understanding-the-haar-measure-demo/7334" +} diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py index d573cc676f..ef0d1510f4 100644 --- a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py +++ b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py @@ -571,3 +571,6 @@ def circuit(params, operation=None): # General Quantum Evolution with the Stochastic Parameter Shift Rule." # `Quantum 5, 386 `__ (2021). # +# About the author +# ---------------- +# .. include:: ../_static/authors/david_wierichs.txt diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/metadata.json b/demonstrations_v2/tutorial_here_comes_the_sun/metadata.json index 971e6c8615..dd53507e96 100644 --- a/demonstrations_v2/tutorial_here_comes_the_sun/metadata.json +++ b/demonstrations_v2/tutorial_here_comes_the_sun/metadata.json @@ -1,77 +1,77 @@ { - "title": "Here comes the SU(N): multivariate quantum gates and gradients", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2023-04-03T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/here_comes_the_sun/thumbnail_tutorial_here_comes_the_sun.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_here_comes_the_sun.png" - } - ], - "seoDescription": "Learn about multivariate quantum gates for optimization", - "doi": "", - "references": [ - { - "id": "vatan", - "type": "article", - "title": "Optimal Quantum Circuits for General Two-Qubit Gates", - "authors": "Farrokh Vatan and Colin Williams", - "year": "2003", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0308006" - }, - { - "id": "wiersema", - "type": "article", - "title": "Here comes the SU(N): multivariate quantum gates and gradients", - "authors": "R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran", - "year": "2023", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/abs/2303.11355" - }, - { - "id": "banchi", - "type": "article", - "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule.", - "authors": "Leonardo Banchi and Gavin E. Crooks", - "year": "2021", - "publisher": "", - "journal": "Quantum", - "url": "https://quantum-journal.org/papers/q-2021-01-25-386/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_general_parshift", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_unitary_designs", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Here comes the SU(N): multivariate quantum gates and gradients", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2023-04-03T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/here_comes_the_sun/thumbnail_tutorial_here_comes_the_sun.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_here_comes_the_sun.png" + } + ], + "seoDescription": "Learn about multivariate quantum gates for optimization", + "doi": "", + "references": [ + { + "id": "vatan", + "type": "article", + "title": "Optimal Quantum Circuits for General Two-Qubit Gates", + "authors": "Farrokh Vatan and Colin Williams", + "year": "2003", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0308006" + }, + { + "id": "wiersema", + "type": "article", + "title": "Here comes the SU(N): multivariate quantum gates and gradients", + "authors": "R. Wiersema, D. Lewis, D. Wierichs, J. F. Carrasquilla, and N. Killoran", + "year": "2023", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2303.11355" + }, + { + "id": "banchi", + "type": "article", + "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule.", + "authors": "Leonardo Banchi and Gavin E. Crooks", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-01-25-386/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py index 668029bc10..bef674beb2 100644 --- a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py +++ b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/demo.py @@ -415,3 +415,6 @@ def plot(lattice, figsize=None, showlabel=True): # "Refraction and band isotropy in 2D square-like Archimedean photonic crystal lattices", # Opt. Express 16, 4048, 2008. # +# About the authors +# ----------------- +# diff --git a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json index 7389b9ce35..79292d0d01 100644 --- a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json +++ b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/metadata.json @@ -1,71 +1,71 @@ { - "title": "How to build spin Hamiltonians", - "authors": [ - { - "username": "ddhawan" - }, - { - "username": "soran" - } - ], - "dateOfPublication": "2024-12-05T00:00:00+00:00", - "dateOfLastModification": "2024-12-05T00:00:00+00:00", - "categories": [ - "Getting Started", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_build_spin_hamiltonians.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_build_spin_hamiltonians.png" - } - ], - "seoDescription": "Learn how to build spin Hamiltonians with PennyLane.", - "doi": "", - "references": [ - { - "id": "ashcroft", - "type": "book", - "title": "Solid State Physics", - "authors": "N. W. Ashcroft, D. N. Mermin", - "year": "1976", - "publisher": "New York: Saunders College Publishing", - "url": "https://en.wikipedia.org/wiki/Ashcroft_and_Mermin" - }, - { - "id": "jovanovic", - "type": "article", - "title": "Refraction and band isotropy in 2D square-like Archimedean photonic crystal lattices", - "authors": "D. Jovanovic, R. Gajic, K. Hingerl", - "journal": "Opt. Express", - "volume": "16", - "pages": "4048--4058", - "year": "2008", - "url": "https://opg.optica.org/oe/fulltext.cfm?uri=oe-16-6-4048&id=154856" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_fermionic_operators", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_isingmodel_PyTorch", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to build spin Hamiltonians", + "authors": [ + { + "username": "ddhawan" + }, + { + "username": "soran" + } + ], + "dateOfPublication": "2024-12-05T00:00:00+00:00", + "dateOfLastModification": "2024-12-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_build_spin_hamiltonians.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_build_spin_hamiltonians.png" + } + ], + "seoDescription": "Learn how to build spin Hamiltonians with PennyLane.", + "doi": "", + "references": [ + { + "id": "ashcroft", + "type": "book", + "title": "Solid State Physics", + "authors": "N. W. Ashcroft, D. N. Mermin", + "year": "1976", + "publisher": "New York: Saunders College Publishing", + "url": "https://en.wikipedia.org/wiki/Ashcroft_and_Mermin" + }, + { + "id": "jovanovic", + "type": "article", + "title": "Refraction and band isotropy in 2D square-like Archimedean photonic crystal lattices", + "authors": "D. Jovanovic, R. Gajic, K. Hingerl", + "journal": "Opt. Express", + "volume": "16", + "pages": "4048--4058", + "year": "2008", + "url": "https://opg.optica.org/oe/fulltext.cfm?uri=oe-16-6-4048&id=154856" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_fermionic_operators", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_isingmodel_PyTorch", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py index 8eed5c7337..a828f1b95e 100644 --- a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py +++ b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/demo.py @@ -225,3 +225,6 @@ def comparing_function(first_mcms, second_mcms): # `measurements quickstart page `_ # and the documentation of :func:`~.pennylane.measure`. # +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json index 7022601b2d..de376b48f1 100644 --- a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json +++ b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/metadata.json @@ -1,50 +1,48 @@ { - "title": "How to collect statistics of mid-circuit measurements", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2024-04-26T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Getting Started", - "Quantum Computing", - "How-to" - ], - "tags": [ - "how to" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_collect_mcm_stats.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_collect_mcm_stats.png" - } - ], - "seoDescription": "Learn how to collect statistics about measurements performed during a quantum circuit.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_teleportation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_mbqc", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_how_to_create_dynamic_mcm_circuits", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to collect statistics of mid-circuit measurements", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing", + "How-to" + ], + "tags": ["how to"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_collect_mcm_stats.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_collect_mcm_stats.png" + } + ], + "seoDescription": "Learn how to collect statistics about measurements performed during a quantum circuit.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_teleportation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_create_dynamic_mcm_circuits", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py index 82acd759d5..3a19d01cba 100644 --- a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py +++ b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/demo.py @@ -250,3 +250,6 @@ def create_selected_half_filled_state(x, selection): # # And this is how to create dynamic circuits in PennyLane with mid-circuit measurements! # +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json index eb097bbd12..e74e16e5af 100644 --- a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json +++ b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/metadata.json @@ -1,50 +1,48 @@ { - "title": "How to create dynamic circuits with mid-circuit measurements", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2024-05-03T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Getting Started", - "Quantum Computing", - "How-to" - ], - "tags": [ - "how to" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_create_dynamic_mcm_circuits.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_create_dynamic_mcm_circuits.png" - } - ], - "seoDescription": "Learn how to create dynamic circuits with control flow based on mid-circuit measurements.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_teleportation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_mbqc", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_how_to_collect_mcm_stats", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to create dynamic circuits with mid-circuit measurements", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-05-03T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing", + "How-to" + ], + "tags": ["how to"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_create_dynamic_mcm_circuits.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_create_dynamic_mcm_circuits.png" + } + ], + "seoDescription": "Learn how to create dynamic circuits with control flow based on mid-circuit measurements.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_teleportation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_collect_mcm_stats", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py index 3d9b47e3da..7568184d0e 100644 --- a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py +++ b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/demo.py @@ -215,3 +215,5 @@ def GHZcircuit(): # ###################################################################### +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json index 4b054e79fe..78b1bd040a 100644 --- a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json +++ b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/metadata.json @@ -1,42 +1,43 @@ { - "title": "How to import noise models from Qiskit", - "authors": [ - { - "username": "whatsis" - } - ], - "dateOfPublication": "2024-11-25T00:00:00+00:00", - "dateOfLastModification": "2024-11-25T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_import_qiskit_noise_models.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_import_qiskit_noise_models.png" - } - ], - "seoDescription": "Learn how noise models can be imported into PennyLane from Qiskit.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_how_to_use_noise_models", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_noisy_circuits", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to import noise models from Qiskit", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2024-11-25T00:00:00+00:00", + "dateOfLastModification": "2024-11-25T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_import_qiskit_noise_models.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_import_qiskit_noise_models.png" + } + ], + "seoDescription": "Learn how noise models can be imported into PennyLane from Qiskit.", + "doi": "", + "references": [ + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_how_to_use_noise_models", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py index 2f9f279910..779a85668d 100644 --- a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py +++ b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py @@ -141,8 +141,11 @@ def cost(params): # not JAX compatible. # # Instead, we can use `Optax `__, a library designed for -# optimization using JAX, as well as the :func:`~.catalyst.grad` function, which allows us to -# differentiate through quantum just-in-time compiled workflows. +# optimization using JAX, as well as the :func:`~.catalyst.value_and_grad` function, which allows us to +# differentiate through quantum just-in-time compiled workflows while also returning the cost value. +# Here we use :func:`~.catalyst.value_and_grad` as we want to be able to print out and track our +# cost function during execution, but if this is not required the :func:`~.catalyst.grad` function +# can be used instead. # import catalyst @@ -153,23 +156,17 @@ def cost(params): @qml.qjit def update_step(i, params, opt_state): """Perform a single gradient update step""" - grads = catalyst.grad(cost)(params) + energy, grads = catalyst.value_and_grad(cost)(params) updates, opt_state = opt.update(grads, opt_state) params = optax.apply_updates(params, updates) + catalyst.debug.print("Step = {i}, Energy = {energy:.8f} Ha", i=i, energy=energy) return (params, opt_state) -loss_history = [] - opt_state = opt.init(init_params) params = init_params for i in range(10): params, opt_state = update_step(i, params, opt_state) - loss_val = cost(params) - - print(f"--- Step: {i}, Energy: {loss_val:.8f}") - - loss_history.append(loss_val) ###################################################################### # Step 4: QJIT-compile the optimization @@ -195,3 +192,10 @@ def optimization(params): # ###################################################################### +# About the authors +# ----------------- +# +# .. include:: ../_static/authors/ali_asadi.txt +# +# .. include:: ../_static/authors/josh_izaac.txt +# diff --git a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json index d91caf09a6..2447d6fcf9 100644 --- a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json +++ b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/metadata.json @@ -1,49 +1,47 @@ { - "title": "How to quantum just-in-time compile VQE with Catalyst", - "authors": [ - { - "username": "maliasadi" - }, - { - "username": "josh" - } - ], - "dateOfPublication": "2024-04-26T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Optimization", - "Quantum Chemistry", - "How-to" - ], - "tags": [ - "how to" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how-to-vqe-qjit.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how-to-vqe-qjit.png" - } - ], - "seoDescription": "Learn how to find the ground state of a molecule with VQE using PennyLane, Catalyst, and just-in-time compilation.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to quantum just-in-time compile VQE with Catalyst", + "authors": [ + { + "username": "maliasadi" + }, + { + "username": "josh" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-11-12T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Optimization", + "Quantum Chemistry", + "How-to" + ], + "tags": ["how to"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how-to-vqe-qjit.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how-to-vqe-qjit.png" + } + ], + "seoDescription": "Learn how to find the ground state of a molecule with VQE using PennyLane, Catalyst, and just-in-time compilation.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_use_noise_models/demo.py b/demonstrations_v2/tutorial_how_to_use_noise_models/demo.py index 94e7478404..1f321addd3 100644 --- a/demonstrations_v2/tutorial_how_to_use_noise_models/demo.py +++ b/demonstrations_v2/tutorial_how_to_use_noise_models/demo.py @@ -232,3 +232,5 @@ def circuit(theta, phi): # ###################################################################### +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json b/demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json index b59906c472..f79c640020 100644 --- a/demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json +++ b/demonstrations_v2/tutorial_how_to_use_noise_models/metadata.json @@ -1,42 +1,43 @@ { - "title": "How to use noise models in PennyLane", - "authors": [ - { - "username": "whatsis" - } - ], - "dateOfPublication": "2024-10-01T00:00:00+00:00", - "dateOfLastModification": "2024-11-22T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_noise_models.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_noise_models.png" - } - ], - "seoDescription": "Learn how noise models can be built and inserted into a quantum circuit in PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_noisy_circuits", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_how_to_import_qiskit_noise_models", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to use noise models in PennyLane", + "authors": [ + { + "username": "whatsis" + } + ], + "dateOfPublication": "2024-10-01T00:00:00+00:00", + "dateOfLastModification": "2024-11-22T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_noise_models.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_noise_models.png" + } + ], + "seoDescription": "Learn how noise models can be built and inserted into a quantum circuit in PennyLane.", + "doi": "", + "references": [ + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_import_qiskit_noise_models", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py index c330573d79..a9c8d0a57c 100644 --- a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py +++ b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/demo.py @@ -356,3 +356,5 @@ def circuit_with_Poly(x,y): # ###################################################################### +# About the authors +# ----------------- \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json index d1ea6e5285..eb9ea02294 100644 --- a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json +++ b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/metadata.json @@ -1,65 +1,65 @@ { - "title": "How to use quantum arithmetic operators", - "authors": [ - { - "username": "gmlejarza" - }, - { - "username": "KetPuntoG" - } - ], - "dateOfPublication": "2024-11-05T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_quantum_arithmetic_operators.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_quantum_arithmetic_operators.png" - } - ], - "seoDescription": "Learn how to use the quantum arithmetic operators of PennyLane.", - "doi": "", - "references": [ - { - "id": "shor_exp", - "type": "article", - "title": "Shor's Factoring Algorithm and Modular Exponentiation Operators", - "authors": "Robert L Singleton Jr", - "year": "2023", - "publisher": "", - "url": "https://arxiv.org/abs/2306.09122/" - }, - { - "id": "sanders", - "type": "article", - "title": "Black-box quantum state preparation without arithmetic", - "authors": "Yuval R. Sanders, Guang Hao Low, Artur Scherer, Dominic W. Berry", - "year": "2018", - "publisher": "", - "url": "https://arxiv.org/abs/1807.03206/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qft_arithmetics", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_how_to_use_registers", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to use quantum arithmetic operators", + "authors": [ + { + "username": "gmlejarza" + }, + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-11-05T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_quantum_arithmetic_operators.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_quantum_arithmetic_operators.png" + } + ], + "seoDescription": "Learn how to use the quantum arithmetic operators of PennyLane.", + "doi": "", + "references": [ + { + "id": "shor_exp", + "type": "article", + "title": "Shor's Factoring Algorithm and Modular Exponentiation Operators", + "authors": "Robert L Singleton Jr", + "year": "2023", + "publisher": "", + "url": "https://arxiv.org/abs/2306.09122/" + }, + { + "id": "sanders", + "type": "article", + "title": "Black-box quantum state preparation without arithmetic", + "authors": "Yuval R. Sanders, Guang Hao Low, Artur Scherer, Dominic W. Berry", + "year": "2018", + "publisher": "", + "url": "https://arxiv.org/abs/1807.03206/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_use_registers", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_how_to_use_registers/demo.py b/demonstrations_v2/tutorial_how_to_use_registers/demo.py index 4f8d3c13c9..981d123dec 100644 --- a/demonstrations_v2/tutorial_how_to_use_registers/demo.py +++ b/demonstrations_v2/tutorial_how_to_use_registers/demo.py @@ -180,3 +180,7 @@ def circuit(): # algorithms are described as acting upon registers and sub-registers; using wire registers # can greatly streamline the implementation from theory to code. # +# About the author +# ---------------- +# +# .. include:: ../_static/authors/austin_huang.txt diff --git a/demonstrations_v2/tutorial_how_to_use_registers/metadata.json b/demonstrations_v2/tutorial_how_to_use_registers/metadata.json index 0eaa840bab..fdb53666a8 100644 --- a/demonstrations_v2/tutorial_how_to_use_registers/metadata.json +++ b/demonstrations_v2/tutorial_how_to_use_registers/metadata.json @@ -1,42 +1,42 @@ { - "title": "How to use wire registers", - "authors": [ - { - "username": "austingmhuang" - } - ], - "dateOfPublication": "2024-09-05T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_registers.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_registers.png" - } - ], - "seoDescription": "Learn how wire registers can be built and used in PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qpe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qft_arithmetics", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to use wire registers", + "authors": [ + { + "username": "austingmhuang" + } + ], + "dateOfPublication": "2024-09-05T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_use_registers.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_use_registers.png" + } + ], + "seoDescription": "Learn how wire registers can be built and used in PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qpe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py b/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py index 6739c22c76..b17ea81f06 100644 --- a/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py +++ b/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py @@ -652,3 +652,8 @@ def groundstate_expval_variational(a, z_init) -> float: # "The analytic domain in the implicit function theorem." # `JIPAM. J. Inequal. Pure Appl. Math 4.1 `__, (2003). # +# About the authors +# ----------------- +# .. include:: ../_static/authors/shahnawaz_ahmed.txt +# +# .. include:: ../_static/authors/juan_felipe_carrasquilla_alvarez.txt diff --git a/demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json b/demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json index 59f2b6fbc6..3a8b762d87 100644 --- a/demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json +++ b/demonstrations_v2/tutorial_implicit_diff_susceptibility/metadata.json @@ -1,98 +1,98 @@ { - "title": "Implicit differentiation of variational quantum algorithms", - "authors": [ - { - "username": "quantshah" - }, - { - "username": "jfcalvarez" - } - ], - "dateOfPublication": "2022-11-28T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_implicit_differentiation_variational_quantum_algo.png" - } - ], - "seoDescription": "Implicitly differentiating the the solution of a VQA in PennyLane.", - "doi": "", - "references": [ - { - "id": "Paradis2004", - "type": "article", - "title": "Fermat and the Quadrature of the Folium of Descartes", - "authors": "Jaume Parad\u00eds, Josep Pla & Pelegr\u00ed Viader", - "year": "2004", - "journal": "The American Mathematical Monthly", - "doi": "10.1080/00029890.2004.11920067", - "url": "" - }, - { - "id": "Ahmed2022", - "type": "article", - "title": "Implicit differentiation of variational quantum algorithms", - "authors": "Shahnawaz Ahmed, Nathan Killoran, Juan Felipe Carrasquilla \u00c1lvarez", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2211.13765" - }, - { - "id": "Blondel2021", - "type": "article", - "title": "Efficient and modular implicit differentiation", - "authors": "Mathieu Blondel, Quentin Berthet, Marco Cuturi, Roy Frostig, Stephan Hoyer, Felipe Llinares-L\u00f3pez, Fabian Pedregosa, Jean-Philippe Vert ", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2105.15183" - }, - { - "id": "implicitlayers", - "type": "webpage", - "title": "Deep Implicit Layers - Neural ODEs, Deep Equilibirum Models, and Beyond", - "authors": "Zico Kolter, David Duvenaud, Matt Johnson", - "year": "2021", - "journal": "", - "url": "http://implicit-layers-tutorial.org" - }, - { - "id": "Matteo2021", - "type": "article", - "title": "Quantum computing fidelity susceptibility using automatic differentiation", - "authors": "Olivia Di Matteo, R. M. Woloshyn", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2207.06526" - }, - { - "id": "Chang2003", - "type": "article", - "title": "The analytic domain in the implicit function theorem.", - "authors": "Chang, Hung-Chieh, Wei He, and Nagabhushana Prabhu", - "year": "2003", - "journal": "J. Inequal. Pure Appl. Math", - "url": "http://emis.icm.edu.pl/journals/JIPAM/v4n1/061_02_www.pdf" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2211.13765" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_backprop", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_jax_transformations", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Implicit differentiation of variational quantum algorithms", + "authors": [ + { + "username": "quantshah" + }, + { + "username": "jfcalvarez" + } + ], + "dateOfPublication": "2022-11-28T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_implicit_differentiation_variational_quantum_algo.png" + } + ], + "seoDescription": "Implicitly differentiating the the solution of a VQA in PennyLane.", + "doi": "", + "references": [ + { + "id": "Paradis2004", + "type": "article", + "title": "Fermat and the Quadrature of the Folium of Descartes", + "authors": "Jaume Paradís, Josep Pla & Pelegrí Viader", + "year": "2004", + "journal": "The American Mathematical Monthly", + "doi": "10.1080/00029890.2004.11920067", + "url": "" + }, + { + "id": "Ahmed2022", + "type": "article", + "title": "Implicit differentiation of variational quantum algorithms", + "authors": "Shahnawaz Ahmed, Nathan Killoran, Juan Felipe Carrasquilla Álvarez", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2211.13765" + }, + { + "id": "Blondel2021", + "type": "article", + "title": "Efficient and modular implicit differentiation", + "authors": "Mathieu Blondel, Quentin Berthet, Marco Cuturi, Roy Frostig, Stephan Hoyer, Felipe Llinares-López, Fabian Pedregosa, Jean-Philippe Vert ", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2105.15183" + }, + { + "id": "implicitlayers", + "type": "webpage", + "title": "Deep Implicit Layers - Neural ODEs, Deep Equilibirum Models, and Beyond", + "authors": "Zico Kolter, David Duvenaud, Matt Johnson", + "year": "2021", + "journal": "", + "url": "http://implicit-layers-tutorial.org" + }, + { + "id": "Matteo2021", + "type": "article", + "title": "Quantum computing fidelity susceptibility using automatic differentiation", + "authors": "Olivia Di Matteo, R. M. Woloshyn", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2207.06526" + }, + { + "id": "Chang2003", + "type": "article", + "title": "The analytic domain in the implicit function theorem.", + "authors": "Chang, Hung-Chieh, Wei He, and Nagabhushana Prabhu", + "year": "2003", + "journal": "J. Inequal. Pure Appl. Math", + "url": "http://emis.icm.edu.pl/journals/JIPAM/v4n1/061_02_www.pdf" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2211.13765" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_initial_state_preparation/demo.py b/demonstrations_v2/tutorial_initial_state_preparation/demo.py index 62aa1b2ad9..589546603a 100644 --- a/demonstrations_v2/tutorial_initial_state_preparation/demo.py +++ b/demonstrations_v2/tutorial_initial_state_preparation/demo.py @@ -337,3 +337,6 @@ def circuit_VQE(theta, initial_state): # used in our example, the CISD state was sufficient: however, in more correlated # molecules, DMRG and SHCI initial states typically provide the best speed-ups. # +# About the author +# ---------------- +# .. include:: ../_static/authors/stepan_fomichev.txt diff --git a/demonstrations_v2/tutorial_initial_state_preparation/metadata.json b/demonstrations_v2/tutorial_initial_state_preparation/metadata.json index 147738ea35..9ee85aaabf 100644 --- a/demonstrations_v2/tutorial_initial_state_preparation/metadata.json +++ b/demonstrations_v2/tutorial_initial_state_preparation/metadata.json @@ -1,41 +1,42 @@ { - "title": "Initial State Preparation for Quantum Chemistry", - "authors": [ - { - "username": "chiffa" - } - ], - "dateOfPublication": "2023-10-20T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/initial_state_preparation/thumbnail_initial_state_preparation.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_initial_state_preparation.png" - } - ], - "seoDescription": "Prepare initial states for quantum algorithms from output of traditional quantum chemistry methods.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Initial State Preparation for Quantum Chemistry", + "authors": [ + { + "username": "chiffa" + } + ], + "dateOfPublication": "2023-10-20T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/initial_state_preparation/thumbnail_initial_state_preparation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_initial_state_preparation.png" + } + ], + "seoDescription": "Prepare initial states for quantum algorithms from output of traditional quantum chemistry methods.", + "doi": "", + "references": [ + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py b/demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py index 0937cf048d..abee17800b 100644 --- a/demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py +++ b/demonstrations_v2/tutorial_intro_amplitude_amplification/demo.py @@ -373,3 +373,6 @@ def circuit(iters): # "Simulating Hamiltonian dynamics with a truncated Taylor series", # `arXiv:1412.4687 `__, 2014 # +# About the author +# ---------------- + diff --git a/demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json b/demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json index f49538ef9d..a9b38146ff 100644 --- a/demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json +++ b/demonstrations_v2/tutorial_intro_amplitude_amplification/metadata.json @@ -1,85 +1,85 @@ { - "title": "Intro to Amplitude Amplification", - "authors": [ - { - "username": "KetPuntoG" - }, - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2024-05-07T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/intro_amplitude_amplification/thumbnail_AmplitudeAmplification_2024-04-29.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_AmplitudeAmplification_2024-04-29.png" - } - ], - "seoDescription": "Learn Amplitude Amplification from scratch and how to use fixed-point quantum search", - "doi": "", - "references": [ - { - "id": "ampamp", - "type": "article", - "title": "Quantum Amplitude Amplification and Estimation", - "authors": "Gilles Brassard, Peter Hoyer, Michele Mosca, Alain Tapp", - "year": "2000", - "journal": "arXiv", - "url": "https://arxiv.org/abs/quant-ph/0005055" - }, - { - "id": "fixedpoint", - "type": "article", - "title": "Fixed-point quantum search with an optimal number of queries", - "authors": "Theodore J. Yoder, Guang Hao Low, Isaac L. Chuang", - "year": "2014", - "publisher": "", - "journal": "arXiv", - "url": "https://arxiv.org/abs/1409.3305" - }, - { - "id": "unification", - "type": "article", - "title": "A Grand Unification of Quantum Algorithms", - "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang", - "year": "2021", - "publisher": "PRX Quantum", - "journal": "arXiv", - "url": "https://arxiv.org/abs/2105.02859" - }, - { - "id": "oblivious", - "type": "article", - "title": "Simulating Hamiltonian dynamics with a truncated Taylor series", - "authors": "Dominic W. Berry, et al.", - "year": "2014", - "publisher": "", - "journal": "arXiv", - "url": "https://arxiv.org/pdf/1412.4687.pdf" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_grovers_algorithm", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qft_arithmetics", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Intro to Amplitude Amplification", + "authors": [ + { + "username": "KetPuntoG" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2024-05-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/intro_amplitude_amplification/thumbnail_AmplitudeAmplification_2024-04-29.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_AmplitudeAmplification_2024-04-29.png" + } + ], + "seoDescription": "Learn Amplitude Amplification from scratch and how to use fixed-point quantum search", + "doi": "", + "references": [ + { + "id": "ampamp", + "type": "article", + "title": "Quantum Amplitude Amplification and Estimation", + "authors": "Gilles Brassard, Peter Hoyer, Michele Mosca, Alain Tapp", + "year": "2000", + "journal": "arXiv", + "url": "https://arxiv.org/abs/quant-ph/0005055" + }, + { + "id": "fixedpoint", + "type": "article", + "title": "Fixed-point quantum search with an optimal number of queries", + "authors": "Theodore J. Yoder, Guang Hao Low, Isaac L. Chuang", + "year": "2014", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/abs/1409.3305" + }, + { + "id": "unification", + "type": "article", + "title": "A Grand Unification of Quantum Algorithms", + "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang", + "year": "2021", + "publisher": "PRX Quantum", + "journal": "arXiv", + "url": "https://arxiv.org/abs/2105.02859" + }, + { + "id": "oblivious", + "type": "article", + "title": "Simulating Hamiltonian dynamics with a truncated Taylor series", + "authors": "Dominic W. Berry, et al.", + "year": "2014", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/pdf/1412.4687.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_grovers_algorithm", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qft_arithmetics", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_intro_qrom/demo.py b/demonstrations_v2/tutorial_intro_qrom/demo.py index 007109cd6f..efceb77449 100644 --- a/demonstrations_v2/tutorial_intro_qrom/demo.py +++ b/demonstrations_v2/tutorial_intro_qrom/demo.py @@ -350,3 +350,5 @@ def circuit(index): # "Creating superpositions that correspond to efficiently integrable probability distributions", # `arXiv:quant-ph/0208112 `__, 2002 # +# About the authors +# ----------------- diff --git a/demonstrations_v2/tutorial_intro_qrom/metadata.json b/demonstrations_v2/tutorial_intro_qrom/metadata.json index 8a0b69e144..9fe232c5f7 100644 --- a/demonstrations_v2/tutorial_intro_qrom/metadata.json +++ b/demonstrations_v2/tutorial_intro_qrom/metadata.json @@ -1,81 +1,81 @@ { - "title": "Intro to quantum read-only memory (QROM)", - "authors": [ - { - "username": "KetPuntoG" + "title": "Intro to quantum read-only memory (QROM)", + "authors": [ + { + "username": "KetPuntoG" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2024-09-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qrom.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qrom.png" + } + ], + "seoDescription": "Learn how to enter data into a quantum computer with a quantum read-only memory (QROM) approach.", + "doi": "", + "references": [ + { + "id": "unary", + "type": "article", + "title": "Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity", + "authors": "Ryan Babbush, Craig Gidney, Dominic W. Berry, Nathan Wiebe, Jarrod McClean, Alexandru Paler, Austin Fowler, Hartmut Neven", + "year": "2018", + "publisher": "", + "journal": "Physical Review X", + "url": "http://dx.doi.org/10.1103/PhysRevX.8.041015" }, - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2024-09-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qrom.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qrom.png" - } - ], - "seoDescription": "Learn how to enter data into a quantum computer with a quantum read-only memory (QROM) approach.", - "doi": "", - "references": [ - { - "id": "unary", - "type": "article", - "title": "Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity", - "authors": "Ryan Babbush, Craig Gidney, Dominic W. Berry, Nathan Wiebe, Jarrod McClean, Alexandru Paler, Austin Fowler, Hartmut Neven", - "year": "2018", - "publisher": "", - "journal": "Physical Review X", - "url": "http://dx.doi.org/10.1103/PhysRevX.8.041015" + { + "id": "selectSwap", + "type": "article", + "title": "Trading T-gates for dirty qubits in state preparation and unitary synthesis", + "authors": "Guang Hao Low, Vadym Kliuchnikov, Luke Schaeffer", + "year": "2018", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/abs/1812.00954" }, - { - "id": "selectSwap", - "type": "article", - "title": "Trading T-gates for dirty qubits in state preparation and unitary synthesis", - "authors": "Guang Hao Low, Vadym Kliuchnikov, Luke Schaeffer", - "year": "2018", - "publisher": "", - "journal": "arXiv", - "url": "https://arxiv.org/abs/1812.00954" + { + "id": "cleanQROM", + "type": "article", + "title": "Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization", + "authors": "Dominic W. Berry, Craig Gidney, Mario Motta, Jarrod R. McClean, Ryan Babbush", + "year": "2019", + "publisher": "", + "journal": "Quantum", + "url": "http://dx.doi.org/10.22331/q-2019-12-02-208" }, - { - "id": "cleanQROM", - "type": "article", - "title": "Qubitization of Arbitrary Basis Quantum Chemistry Leveraging Sparsity and Low Rank Factorization", - "authors": "Dominic W. Berry, Craig Gidney, Mario Motta, Jarrod R. McClean, Ryan Babbush", - "year": "2019", - "publisher": "", - "journal": "Quantum", - "url": "http://dx.doi.org/10.22331/q-2019-12-02-208" - }, - { - "id": "StatePrep", - "type": "article", - "title": "Creating superpositions that correspond to efficiently integrable probability distributions", - "authors": "Lov Grover, Terry Rudolph", - "year": "2002", - "publisher": "", - "journal": "arXiv", - "url": "https://arxiv.org/abs/quant-ph/0208112" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_initial_state_preparation", - "weight": 1.0 + { + "id": "StatePrep", + "type": "article", + "title": "Creating superpositions that correspond to efficiently integrable probability distributions", + "authors": "Lov Grover, Terry Rudolph", + "year": "2002", + "publisher": "", + "journal": "arXiv", + "url": "https://arxiv.org/abs/quant-ph/0208112" } - ] -} \ No newline at end of file + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_initial_state_preparation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_intro_qsvt/demo.py b/demonstrations_v2/tutorial_intro_qsvt/demo.py index 0989db9829..b54bc0b88d 100644 --- a/demonstrations_v2/tutorial_intro_qsvt/demo.py +++ b/demonstrations_v2/tutorial_intro_qsvt/demo.py @@ -312,3 +312,6 @@ def qsvt_output(a): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/juan_miguel_arrazola.txt diff --git a/demonstrations_v2/tutorial_intro_qsvt/metadata.json b/demonstrations_v2/tutorial_intro_qsvt/metadata.json index 21feae8866..2a2d3d128e 100644 --- a/demonstrations_v2/tutorial_intro_qsvt/metadata.json +++ b/demonstrations_v2/tutorial_intro_qsvt/metadata.json @@ -1,69 +1,69 @@ { - "title": "Intro to QSVT", - "authors": [ - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2023-05-23T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/intro_qsvt/thumbnail_intro_qsvt.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_intro_qsvt.png" - } - ], - "seoDescription": "Master the basics of the quantum singular value transformation", - "doi": "", - "references": [ - { - "id": "qsvt", - "type": "article", - "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", - "authors": "Andr\u00e1s Gily\u00e9n, Yuan Su, Guang Hao Low, Nathan Wiebe", - "year": "2019", - "publisher": "", - "journal": "", - "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" - }, - { - "id": "lintong", - "type": "article", - "title": "Near-optimal ground state preparation", - "authors": "Lin, Lin, and Yu Tong", - "year": "2020", - "publisher": "", - "journal": "Quantum", - "url": "https://quantum-journal.org/papers/q-2020-12-14-372/" - }, - { - "id": "unification", - "type": "article", - "title": "Grand Unification of Quantum Algorithms", - "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, and Isaac L. Chuang", - "year": "2021", - "publisher": "", - "journal": "Quantum", - "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.040203" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "function_fitting_qsp", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/qsvt-algorithm-computing-the-angles-for-projector-controlled-phase-gate/3203" -} \ No newline at end of file + "title": "Intro to QSVT", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2023-05-23T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/intro_qsvt/thumbnail_intro_qsvt.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_intro_qsvt.png" + } + ], + "seoDescription": "Master the basics of the quantum singular value transformation", + "doi": "", + "references": [ + { + "id": "qsvt", + "type": "article", + "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", + "authors": "András Gilyén, Yuan Su, Guang Hao Low, Nathan Wiebe", + "year": "2019", + "publisher": "", + "journal": "", + "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" + }, + { + "id": "lintong", + "type": "article", + "title": "Near-optimal ground state preparation", + "authors": "Lin, Lin, and Yu Tong", + "year": "2020", + "publisher": "", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2020-12-14-372/" + }, + { + "id": "unification", + "type": "article", + "title": "Grand Unification of Quantum Algorithms", + "authors": "John M. Martyn, Zane M. Rossi, Andrew K. Tan, and Isaac L. Chuang", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.040203" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "function_fitting_qsp", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/qsvt-algorithm-computing-the-angles-for-projector-controlled-phase-gate/3203" +} diff --git a/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py b/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py index ce9eeb3d5a..d5519cbbbc 100644 --- a/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py +++ b/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py @@ -249,3 +249,6 @@ def closure(): # `Journal of Combinatorial Optimization `__, 2014. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/aroosa_ijaz.txt diff --git a/demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json b/demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json index 2c3c42db3a..b173d60ebc 100644 --- a/demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json +++ b/demonstrations_v2/tutorial_isingmodel_PyTorch/metadata.json @@ -1,66 +1,66 @@ { - "title": "3-qubit Ising model in PyTorch", - "authors": [ - { - "username": "aijaz" - } - ], - "dateOfPublication": "2019-10-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_3qubit_Ising_model_PyTorch.png" - } - ], - "seoDescription": "This demonstration uses the PyTorch interface of PennyLane to optimize a 3-qubit Ising model.", - "doi": "", - "references": [ - { - "id": "Schuld2018", - "type": "article", - "title": "Supervised Learning with Quantum Computers.", - "authors": "Maria Schuld and Francesco Petruccione", - "year": "2018", - "journal": "", - "publisher": "Springer", - "url": "" - }, - { - "id": "Lucas2014", - "type": "article", - "title": "Ising formulations of many NP problems.", - "authors": "Andrew Lucas", - "year": "2014", - "journal": "", - "url": "https://arxiv.org/pdf/1302.5843" - }, - { - "id": "Kochenberger2014", - "type": "article", - "title": "The Unconstrained Binary Quadratic Programming Problem: A Survey.", - "authors": "Gary Kochenberger et al.", - "year": "2014", - "journal": "Journal of Combinatorial Optimization", - "url": "https://link.springer.com/article/10.1007/s10878-014-9734-0" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_state_preparation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "pytorch_noise", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "3-qubit Ising model in PyTorch", + "authors": [ + { + "username": "aijaz" + } + ], + "dateOfPublication": "2019-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_3qubit_Ising_model_PyTorch.png" + } + ], + "seoDescription": "This demonstration uses the PyTorch interface of PennyLane to optimize a 3-qubit Ising model.", + "doi": "", + "references": [ + { + "id": "Schuld2018", + "type": "article", + "title": "Supervised Learning with Quantum Computers.", + "authors": "Maria Schuld and Francesco Petruccione", + "year": "2018", + "journal": "", + "publisher": "Springer", + "url": "" + }, + { + "id": "Lucas2014", + "type": "article", + "title": "Ising formulations of many NP problems.", + "authors": "Andrew Lucas", + "year": "2014", + "journal": "", + "url": "https://arxiv.org/pdf/1302.5843" + }, + { + "id": "Kochenberger2014", + "type": "article", + "title": "The Unconstrained Binary Quadratic Programming Problem: A Survey.", + "authors": "Gary Kochenberger et al.", + "year": "2014", + "journal": "Journal of Combinatorial Optimization", + "url": "https://link.springer.com/article/10.1007/s10878-014-9734-0" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_state_preparation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_jax_transformations/demo.py b/demonstrations_v2/tutorial_jax_transformations/demo.py index 846e9a8587..71e931252b 100644 --- a/demonstrations_v2/tutorial_jax_transformations/demo.py +++ b/demonstrations_v2/tutorial_jax_transformations/demo.py @@ -306,3 +306,6 @@ def my_circuit(): # The future looks bright for this field, and we're excited to see what you build! # # +# About the author +# ---------------- +# .. include:: ../_static/authors/chase_roberts.txt diff --git a/demonstrations_v2/tutorial_jax_transformations/metadata.json b/demonstrations_v2/tutorial_jax_transformations/metadata.json index 0a217da7b4..94c26675f3 100644 --- a/demonstrations_v2/tutorial_jax_transformations/metadata.json +++ b/demonstrations_v2/tutorial_jax_transformations/metadata.json @@ -1,42 +1,42 @@ { - "title": "Using JAX with PennyLane", - "authors": [ - { - "username": "croberts" - } - ], - "dateOfPublication": "2021-04-12T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_jax_with_pl.png" - } - ], - "seoDescription": "Learn how to use JAX with PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qubit_rotation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqt", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Using JAX with PennyLane", + "authors": [ + { + "username": "croberts" + } + ], + "dateOfPublication": "2021-04-12T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_jax_with_pl.png" + } + ], + "seoDescription": "Learn how to use JAX with PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqt", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_kak_theorem/demo.py b/demonstrations_v2/tutorial_kak_theorem/demo.py index b491b59817..14d804f1d6 100644 --- a/demonstrations_v2/tutorial_kak_theorem/demo.py +++ b/demonstrations_v2/tutorial_kak_theorem/demo.py @@ -984,3 +984,5 @@ def su4_gate(params): # "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits" # `Nat. Commun. **15** `__, 2024. # +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_kak_theorem/metadata.json b/demonstrations_v2/tutorial_kak_theorem/metadata.json index 12c1b6d0eb..c9e865884d 100644 --- a/demonstrations_v2/tutorial_kak_theorem/metadata.json +++ b/demonstrations_v2/tutorial_kak_theorem/metadata.json @@ -1,174 +1,174 @@ { - "title": "The KAK theorem", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2024-11-25T00:00:00+00:00", - "dateOfLastModification": "2024-11-25T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_kak_theorem.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_kak_theorem.png" - } - ], - "seoDescription": "Learn about the KAK theorem and how it powers circuit decompositions.", - "doi": "", - "references": [ - { - "id": "hall", - "type": "book", - "title": "Lie Groups, Lie Algebras, and Representations. An Elementary Introduction", - "authors": "Brian C. Hall", - "year": "2015", - "publisher": "Springer", - "journal": "Graduate Texts in Mathematics", - "doi": "10.1007/978-3-319-13467-3", - "url": "https://link.springer.com/book/10.1007/978-3-319-13467-3" - }, - { - "id": "tu", - "type": "book", - "title": "An Introduction to Manifolds", - "authors": "Loring W. Tu", - "year": "2011", - "publisher": "Springer", - "journal": "Universitext", - "doi": "10.1007/978-1-4419-7400-6", - "url": "https://link.springer.com/book/10.1007/978-1-4419-7400-6" - }, - { - "id": "arvanitogeorgos", - "type": "book", - "title": "An Introduction to Lie Groups and the Geometry of Homogeneous Spaces", - "authors": "Andreas Arvanitogeorgos", - "year": "2003", - "publisher": "American Mathematical Society", - "journal": "Student Mathematical Library", - "url": "https://bookstore.ams.org/stml-22" - }, - { - "id": "helgason", - "type": "book", - "title": "Differential geometry, Lie groups, and symmetric spaces", - "authors": "Sigurdur Helgason", - "year": "2001", - "publisher": "American Mathematical Society", - "journal": "Graduate Studies in Mathematics", - "doi": "10.1090/gsm/034", - "url": "https://bookstore.ams.org/gsm-34" - }, - { - "id": "goh", - "type": "preprint", - "title": "lie-algebraic classical simulations for variational quantum computing", - "authors": "matthew l. goh, martin larocca, lukasz cincio, m. cerezo, fr\u00e9d\u00e9ric sauvage", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arxiv.2308.01432", - "url": "https://arxiv.org/abs/2308.01432" - }, - { - "id": "somma", - "type": "preprint", - "title": "quantum computation, complexity, and many-body physics", - "authors": "rolando d. somma", - "year": "2005", - "publisher": "", - "journal": "", - "doi": "10.48550/arxiv.quant-ph/0512209", - "url": "https://arxiv.org/abs/quant-ph/0512209" - }, - { - "id": "kokcu_fdhs", - "type": "article", - "title": "Fixed Depth Hamiltonian Simulation via Cartan Decomposition", - "authors": "Efekan K\u00f6kc\u00fc, Thomas Steckmann, Yan Wang, J.\u2009K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper", - "year": "2022", - "publisher": "American Physical Society", - "journal": "", - "doi": "10.1103/PhysRevLett.129.070501", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.129.070501" - }, - { - "id": "kokcu_comp", - "type": "article", - "title": "Algebraic Compression of Quantum Circuits for Hamiltonian Evolution", - "authors": "Efekan K\u00f6kc\u00fc, Daan Camps, Lindsay Bassman, James K. Freericks, Wibe A. de Jong, Roel Van Beeumen, Alexander F. Kemper", - "year": "2022", - "publisher": "American Physical Society", - "journal": "", - "doi": "10.1103/PhysRevA.105.032420", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.105.032420" - }, - { - "id": "gu", - "type": "article", - "title": "Fast-forwarding quantum evolution", - "authors": "Shouzhen Gu, Rolando D. Somma, Burak \u015eahino\u011flu", - "year": "2021", - "publisher": "", - "journal": "Quantum", - "doi": "10.22331/q-2021-11-15-577", - "url": "https://quantum-journal.org/papers/q-2021-11-15-577/#" - }, - { - "id": "dirr", - "type": "article", - "title": "Lie Theory for Quantum Control", - "authors": "G. Dirr, U. Helmke", - "year": "2008", - "publisher": "Gesellschaft f\u00fcr Angewandte Mathematik und Mechanik", - "journal": "Surveys for Applied Mathematics and Mechanics", - "doi": "10.1002/gamm.200890003", - "url": "https://onlinelibrary.wiley.com/doi/abs/10.1002/gamm.200890003" - }, - { - "id": "fontana", - "type": "article", - "title": "the adjoint is all you need: characterizing barren plateaus in quantum ans\u00e4tze", - "authors": "enrico fontana, dylan herman, shouvanik chakrabarti, niraj kumar, romina yalovetzky, jamie heredge, shree hari sureshbabu, marco pistoia", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arxiv.2309.07902", - "url": "https://arxiv.org/abs/2309.07902" - }, - { - "id": "ragone", - "type": "preprint", - "title": "a unified theory of barren plateaus for deep parametrized quantum circuits", - "authors": "michael ragone, bojko n. bakalov, fr\u00e9d\u00e9ric sauvage, alexander f. kemper, carlos ortiz marrero, martin larocca, m. cerezo", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arxiv.2309.09342", - "url": "https://arxiv.org/abs/2309.09342" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_liealgebra", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_liesim", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "The KAK theorem", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-11-25T00:00:00+00:00", + "dateOfLastModification": "2024-11-25T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_kak_theorem.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_kak_theorem.png" + } + ], + "seoDescription": "Learn about the KAK theorem and how it powers circuit decompositions.", + "doi": "", + "references": [ + { + "id": "hall", + "type": "book", + "title": "Lie Groups, Lie Algebras, and Representations. An Elementary Introduction", + "authors": "Brian C. Hall", + "year": "2015", + "publisher": "Springer", + "journal": "Graduate Texts in Mathematics", + "doi": "10.1007/978-3-319-13467-3", + "url": "https://link.springer.com/book/10.1007/978-3-319-13467-3" + }, + { + "id": "tu", + "type": "book", + "title": "An Introduction to Manifolds", + "authors": "Loring W. Tu", + "year": "2011", + "publisher": "Springer", + "journal": "Universitext", + "doi": "10.1007/978-1-4419-7400-6", + "url": "https://link.springer.com/book/10.1007/978-1-4419-7400-6" + }, + { + "id": "arvanitogeorgos", + "type": "book", + "title": "An Introduction to Lie Groups and the Geometry of Homogeneous Spaces", + "authors": "Andreas Arvanitogeorgos", + "year": "2003", + "publisher": "American Mathematical Society", + "journal": "Student Mathematical Library", + "url": "https://bookstore.ams.org/stml-22" + }, + { + "id": "helgason", + "type": "book", + "title": "Differential geometry, Lie groups, and symmetric spaces", + "authors": "Sigurdur Helgason", + "year": "2001", + "publisher": "American Mathematical Society", + "journal": "Graduate Studies in Mathematics", + "doi": "10.1090/gsm/034", + "url": "https://bookstore.ams.org/gsm-34" + }, + { + "id": "goh", + "type": "preprint", + "title": "lie-algebraic classical simulations for variational quantum computing", + "authors": "matthew l. goh, martin larocca, lukasz cincio, m. cerezo, frédéric sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "somma", + "type": "preprint", + "title": "quantum computation, complexity, and many-body physics", + "authors": "rolando d. somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "kokcu_fdhs", + "type": "article", + "title": "Fixed Depth Hamiltonian Simulation via Cartan Decomposition", + "authors": "Efekan Kökcü, Thomas Steckmann, Yan Wang, J. K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper", + "year": "2022", + "publisher": "American Physical Society", + "journal": "", + "doi": "10.1103/PhysRevLett.129.070501", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.129.070501" + }, + { + "id": "kokcu_comp", + "type": "article", + "title": "Algebraic Compression of Quantum Circuits for Hamiltonian Evolution", + "authors": "Efekan Kökcü, Daan Camps, Lindsay Bassman, James K. Freericks, Wibe A. de Jong, Roel Van Beeumen, Alexander F. Kemper", + "year": "2022", + "publisher": "American Physical Society", + "journal": "", + "doi": "10.1103/PhysRevA.105.032420", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.105.032420" + }, + { + "id": "gu", + "type": "article", + "title": "Fast-forwarding quantum evolution", + "authors": "Shouzhen Gu, Rolando D. Somma, Burak Şahinoğlu", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "doi": "10.22331/q-2021-11-15-577", + "url": "https://quantum-journal.org/papers/q-2021-11-15-577/#" + }, + { + "id": "dirr", + "type": "article", + "title": "Lie Theory for Quantum Control", + "authors": "G. Dirr, U. Helmke", + "year": "2008", + "publisher": "Gesellschaft für Angewandte Mathematik und Mechanik", + "journal": "Surveys for Applied Mathematics and Mechanics", + "doi": "10.1002/gamm.200890003", + "url": "https://onlinelibrary.wiley.com/doi/abs/10.1002/gamm.200890003" + }, + { + "id": "fontana", + "type": "article", + "title": "the adjoint is all you need: characterizing barren plateaus in quantum ansätze", + "authors": "enrico fontana, dylan herman, shouvanik chakrabarti, niraj kumar, romina yalovetzky, jamie heredge, shree hari sureshbabu, marco pistoia", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2309.07902", + "url": "https://arxiv.org/abs/2309.07902" + }, + { + "id": "ragone", + "type": "preprint", + "title": "a unified theory of barren plateaus for deep parametrized quantum circuits", + "authors": "michael ragone, bojko n. bakalov, frédéric sauvage, alexander f. kemper, carlos ortiz marrero, martin larocca, m. cerezo", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2309.09342", + "url": "https://arxiv.org/abs/2309.09342" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_kernel_based_training/demo.py b/demonstrations_v2/tutorial_kernel_based_training/demo.py index 66a5e8bb59..b24b4bcd1b 100644 --- a/demonstrations_v2/tutorial_kernel_based_training/demo.py +++ b/demonstrations_v2/tutorial_kernel_based_training/demo.py @@ -685,3 +685,6 @@ def model_evals_nn(n_data, n_params, n_steps, split, batch_size): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/maria_schuld.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_kernel_based_training/metadata.json b/demonstrations_v2/tutorial_kernel_based_training/metadata.json index aeb9c5d2ed..b0f2561717 100644 --- a/demonstrations_v2/tutorial_kernel_based_training/metadata.json +++ b/demonstrations_v2/tutorial_kernel_based_training/metadata.json @@ -1,33 +1,33 @@ { - "title": "Kernel-based training of quantum models with scikit-learn", - "authors": [ - { - "username": "mariaschuld" - } - ], - "dateOfPublication": "2021-02-03T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_kernal-based_training_of_quantum_models.png" - } - ], - "seoDescription": "Train a quantum machine learning model based on the idea of quantum kernels.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/kernel-based-training-demonstration/1017" -} \ No newline at end of file + "title": "Kernel-based training of quantum models with scikit-learn", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2021-02-03T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_kernal-based_training_of_quantum_models.png" + } + ], + "seoDescription": "Train a quantum machine learning model based on the idea of quantum kernels.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/kernel-based-training-demonstration/1017" +} diff --git a/demonstrations_v2/tutorial_kernels_module/demo.py b/demonstrations_v2/tutorial_kernels_module/demo.py index fc3a5e67fa..8f8ea23d12 100644 --- a/demonstrations_v2/tutorial_kernels_module/demo.py +++ b/demonstrations_v2/tutorial_kernels_module/demo.py @@ -628,3 +628,21 @@ def target_alignment( # `Artificial Intelligence Review 43.2: 179-192 `__, 2015. # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/peter-jan_derks.txt +# +# +# .. include:: ../_static/authors/paul_k_faehrmann.txt +# +# +# .. include:: ../_static/authors/elies_gil-fuster.txt +# +# +# .. include:: ../_static/authors/tom_hubregtsen.txt +# +# +# .. include:: ../_static/authors/johannes_jakob_meyer.txt +# +# +# .. include:: ../_static/authors/david_wierichs.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_kernels_module/metadata.json b/demonstrations_v2/tutorial_kernels_module/metadata.json index 3983a5324e..c019e51f41 100644 --- a/demonstrations_v2/tutorial_kernels_module/metadata.json +++ b/demonstrations_v2/tutorial_kernels_module/metadata.json @@ -1,71 +1,71 @@ { - "title": "Training and evaluating quantum kernels", - "authors": [ - { - "username": "pjderks" - }, - { - "username": "pkfaehrmann" - }, - { - "username": "egfuster" - }, - { - "username": "thubregtsen" - }, - { - "username": "jjmeyer" - }, - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2021-06-24T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_training_and_evaluating_quantum_kernels.png" - } - ], - "seoDescription": "Kernels and alignment training with Pennylane.", - "doi": "", - "references": [ - { - "id": "Training_QEKs", - "type": "article", - "title": "Training Quantum Embedding Kernels on Near-Term Quantum Computers.", - "authors": "Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan H. S. Derks, Paul K. Faehrmann, and Johannes Jakob Meyer", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2105.02276" - }, - { - "id": "Alignment", - "type": "article", - "title": "An overview of kernel alignment and its applications.", - "authors": "Wang, Tinghua, Dongyan Zhao, and Shengfeng Tian", - "year": "2015", - "journal": "Artificial Intelligence Review", - "url": "https://link.springer.com/article/10.1007/s10462-012-9369-4" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_kernel_based_training", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_data_reuploading_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Training and evaluating quantum kernels", + "authors": [ + { + "username": "pjderks" + }, + { + "username": "pkfaehrmann" + }, + { + "username": "egfuster" + }, + { + "username": "thubregtsen" + }, + { + "username": "jjmeyer" + }, + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2021-06-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_training_and_evaluating_quantum_kernels.png" + } + ], + "seoDescription": "Kernels and alignment training with Pennylane.", + "doi": "", + "references": [ + { + "id": "Training_QEKs", + "type": "article", + "title": "Training Quantum Embedding Kernels on Near-Term Quantum Computers.", + "authors": "Thomas Hubregtsen, David Wierichs, Elies Gil-Fuster, Peter-Jan H. S. Derks, Paul K. Faehrmann, and Johannes Jakob Meyer", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2105.02276" + }, + { + "id": "Alignment", + "type": "article", + "title": "An overview of kernel alignment and its applications.", + "authors": "Wang, Tinghua, Dongyan Zhao, and Shengfeng Tian", + "year": "2015", + "journal": "Artificial Intelligence Review", + "url": "https://link.springer.com/article/10.1007/s10462-012-9369-4" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernel_based_training", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_lcu_blockencoding/demo.py b/demonstrations_v2/tutorial_lcu_blockencoding/demo.py index 81c0f15fcd..a0af2bc4bf 100644 --- a/demonstrations_v2/tutorial_lcu_blockencoding/demo.py +++ b/demonstrations_v2/tutorial_lcu_blockencoding/demo.py @@ -275,3 +275,8 @@ def lcu_circuit(): # block_encode ############################################################################## +# About the authors +# ----------------- +# .. include:: ../_static/authors/juan_miguel_arrazola.txt +# .. include:: ../_static/authors/jay_soni.txt +# .. include:: ../_static/authors/diego_guala.txt diff --git a/demonstrations_v2/tutorial_lcu_blockencoding/metadata.json b/demonstrations_v2/tutorial_lcu_blockencoding/metadata.json index b3cc50ad14..a6f33b3e8e 100644 --- a/demonstrations_v2/tutorial_lcu_blockencoding/metadata.json +++ b/demonstrations_v2/tutorial_lcu_blockencoding/metadata.json @@ -1,60 +1,60 @@ { - "title": "Linear combination of unitaries and block encodings", - "authors": [ - { - "username": "ixfoduap" - }, - { - "username": "Diego" - }, - { - "username": "Jay" - } - ], - "dateOfPublication": "2023-10-25T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/lcu_blockencoding/thumbnail_lcu_blockencoding.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_lcu_blockencoding.png" - } - ], - "seoDescription": "Master the basics of LCUs and their applications", - "doi": "", - "references": [ - { - "id": "qsvt", - "type": "article", - "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", - "authors": "Andr\u00e1s Gily\u00e9n, Yuan Su, Guang Hao Low, Nathan Wiebe", - "year": "2019", - "publisher": "", - "journal": "", - "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_intro_qsvt", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_apply_qsvt", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/lcu-demo-comment/7316" -} \ No newline at end of file + "title": "Linear combination of unitaries and block encodings", + "authors": [ + { + "username": "ixfoduap" + }, + { + "username": "Diego" + }, + { + "username": "Jay" + } + ], + "dateOfPublication": "2023-10-25T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/lcu_blockencoding/thumbnail_lcu_blockencoding.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_lcu_blockencoding.png" + } + ], + "seoDescription": "Master the basics of LCUs and their applications", + "doi": "", + "references": [ + { + "id": "qsvt", + "type": "article", + "title": "Quantum singular value transformation and beyond: exponential improvements for quantum matrix arithmetics", + "authors": "András Gilyén, Yuan Su, Guang Hao Low, Nathan Wiebe", + "year": "2019", + "publisher": "", + "journal": "", + "url": "https://dl.acm.org/doi/abs/10.1145/3313276.3316366" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_apply_qsvt", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/lcu-demo-comment/7316" +} diff --git a/demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py b/demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py index b7d456a4ba..bd485b96f1 100644 --- a/demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py +++ b/demonstrations_v2/tutorial_learning_dynamics_incoherently/demo.py @@ -430,3 +430,6 @@ def model_circuit(params, random_state): # ############################################################################## +# About the author +# ------------------ +# diff --git a/demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json b/demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json index 43f2329a80..7fb9cfde72 100644 --- a/demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json +++ b/demonstrations_v2/tutorial_learning_dynamics_incoherently/metadata.json @@ -1,67 +1,65 @@ { - "title": "Learning dynamics incoherently: Variational learning using classical shadows", - "authors": [ - { - "username": "Diego" - } - ], - "dateOfPublication": "2024-08-15T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_learning_dynamics_incoherently.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_learning_dynamics_incoherently.png" - } - ], - "seoDescription": "Learn how to reproduce an unknown quantum process with classical shadow measurements", - "doi": "", - "references": [ - { - "id": "jerbi2023power", - "type": "article", - "title": "The power and limitations of learning quantum dynamics incoherently", - "authors": "Sofiene Jerbi, Joe Gibbs, Manuel S. Rudolph, Matthias C. Caro, Patrick J. Coles, Hsin-Yuan Huang, and Zo\u00eb Holmes", - "year": "2023", - "url": "https://arxiv.org/abs/2303.12834" - }, - { - "id": "Huang2022Quantum", - "type": "article", - "title": "Quantum advantage in learning from experiments", - "journal": "Science", - "authors": "Hsin-Yuan Huang, Michael Broughton, Jordan Cotler, Sitan Chen, Jerry Li, Masoud Mohseni, Hartmut Neven, Ryan Babbush, Richard Kueng, John Preskill, and Jarrod R. McClean", - "year": "2022", - "url": "http://dx.doi.org/10.1126/science.abn7293" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2303.12834" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_haar_measure", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_classical_shadows", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Learning dynamics incoherently: Variational learning using classical shadows", + "authors": [ + { + "username": "Diego" + } + ], + "dateOfPublication": "2024-08-15T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_learning_dynamics_incoherently.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_learning_dynamics_incoherently.png" + } + ], + "seoDescription": "Learn how to reproduce an unknown quantum process with classical shadow measurements", + "doi": "", + "references": [ + { + "id": "jerbi2023power", + "type": "article", + "title": "The power and limitations of learning quantum dynamics incoherently", + "authors": "Sofiene Jerbi, Joe Gibbs, Manuel S. Rudolph, Matthias C. Caro, Patrick J. Coles, Hsin-Yuan Huang, and Zoë Holmes", + "year": "2023", + "url": "https://arxiv.org/abs/2303.12834" + }, + { + "id": "Huang2022Quantum", + "type": "article", + "title": "Quantum advantage in learning from experiments", + "journal": "Science", + "authors": "Hsin-Yuan Huang, Michael Broughton, Jordan Cotler, Sitan Chen, Jerry Li, Masoud Mohseni, Hartmut Neven, Ryan Babbush, Richard Kueng, John Preskill, and Jarrod R. McClean", + "year": "2022", + "url": "http://dx.doi.org/10.1126/science.abn7293" + } + ], + "basedOnPapers": ["10.48550/arXiv.2303.12834"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_haar_measure", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_learning_few_data/demo.py b/demonstrations_v2/tutorial_learning_few_data/demo.py index 7733739d43..cb42c92ab7 100644 --- a/demonstrations_v2/tutorial_learning_few_data/demo.py +++ b/demonstrations_v2/tutorial_learning_few_data/demo.py @@ -596,3 +596,10 @@ def run_iterations(n_train): # `Journal of Open Source Software `__, 2019. # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt +# +# .. include:: ../_static/authors/luis_mantilla_calderon.txt +# +# .. include:: ../_static/authors/maurice_weber.txt diff --git a/demonstrations_v2/tutorial_learning_few_data/metadata.json b/demonstrations_v2/tutorial_learning_few_data/metadata.json index 0afe124f43..c8ccd61ed1 100644 --- a/demonstrations_v2/tutorial_learning_few_data/metadata.json +++ b/demonstrations_v2/tutorial_learning_few_data/metadata.json @@ -1,87 +1,85 @@ { - "title": "Generalization in QML from few training data", - "authors": [ - { - "username": "Qottmann" - }, - { - "username": "lmcalderon" - }, - { - "username": "mauriceweber" - } - ], - "dateOfPublication": "2022-08-29T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generalization_QML.png" - } - ], - "seoDescription": "Generalization of quantum machine learning models.", - "doi": "", - "references": [ - { - "id": "CaroGeneralization", - "type": "article", - "title": "Generalization in quantum machine learning from few training data", - "authors": "Matthias C. Caro, Hsin-Yuan Huang, M. Cerezo, Kunal Sharma, Andrew Sornborger, Lukasz Cincio, Patrick J. Coles", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2111.05292" - }, - { - "id": "DLBook", - "type": "book", - "title": "Deep Learning", - "authors": "Ian Goodfellow, Yoshua Bengio and Aaron Courville", - "year": "2016", - "journal": "", - "url": "http://www.deeplearningbook.org" - }, - { - "id": "NamkoongVariance", - "type": "article", - "title": "Variance-based regularization with convex objectives.", - "authors": "Hongseok Namkoong and John C. Duchi", - "year": "2017", - "journal": "Advances in Neural Information Processing Systems", - "url": "https://proceedings.neurips.cc/paper/2017/file/5a142a55461d5fef016acfb927fee0bd-Paper.pdf" - }, - { - "id": "CongQuantumCNN", - "type": "article", - "title": "Quantum Convolutional Neural Networks", - "authors": "Iris Cong, Soonwon Choi, Mikhail D. Lukin", - "year": "2018", - "journal": "", - "url": "https://arxiv.org/abs/1810.03787" - }, - { - "id": "LeNailNNSVG", - "type": "article", - "title": "NN-SVG: Publication-Ready Neural Network Architecture Schematics", - "authors": "Alexander LeNail", - "year": "2019", - "journal": "Journal of Open Source Software", - "doi": "10.21105/joss.00747", - "url": "" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2111.05292" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_local_cost_functions", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Generalization in QML from few training data", + "authors": [ + { + "username": "Qottmann" + }, + { + "username": "lmcalderon" + }, + { + "username": "mauriceweber" + } + ], + "dateOfPublication": "2022-08-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_generalization_QML.png" + } + ], + "seoDescription": "Generalization of quantum machine learning models.", + "doi": "", + "references": [ + { + "id": "CaroGeneralization", + "type": "article", + "title": "Generalization in quantum machine learning from few training data", + "authors": "Matthias C. Caro, Hsin-Yuan Huang, M. Cerezo, Kunal Sharma, Andrew Sornborger, Lukasz Cincio, Patrick J. Coles", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2111.05292" + }, + { + "id": "DLBook", + "type": "book", + "title": "Deep Learning", + "authors": "Ian Goodfellow, Yoshua Bengio and Aaron Courville", + "year": "2016", + "journal": "", + "url": "http://www.deeplearningbook.org" + }, + { + "id": "NamkoongVariance", + "type": "article", + "title": "Variance-based regularization with convex objectives.", + "authors": "Hongseok Namkoong and John C. Duchi", + "year": "2017", + "journal": "Advances in Neural Information Processing Systems", + "url": "https://proceedings.neurips.cc/paper/2017/file/5a142a55461d5fef016acfb927fee0bd-Paper.pdf" + }, + { + "id": "CongQuantumCNN", + "type": "article", + "title": "Quantum Convolutional Neural Networks", + "authors": "Iris Cong, Soonwon Choi, Mikhail D. Lukin", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1810.03787" + }, + { + "id": "LeNailNNSVG", + "type": "article", + "title": "NN-SVG: Publication-Ready Neural Network Architecture Schematics", + "authors": "Alexander LeNail", + "year": "2019", + "journal": "Journal of Open Source Software", + "doi": "10.21105/joss.00747", + "url": "" + } + ], + "basedOnPapers": ["10.48550/arXiv.2111.05292"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_learning_from_experiments/demo.py b/demonstrations_v2/tutorial_learning_from_experiments/demo.py index e719804eb3..47ac16b729 100644 --- a/demonstrations_v2/tutorial_learning_from_experiments/demo.py +++ b/demonstrations_v2/tutorial_learning_from_experiments/demo.py @@ -537,3 +537,6 @@ def enhanced_circuit(ts=False): # `arxiv:2111.05881 `__ (2021) # # +# About the author +# ---------------- +# .. include:: ../_static/authors/joseph_bowles.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_learning_from_experiments/metadata.json b/demonstrations_v2/tutorial_learning_from_experiments/metadata.json index 5d83de727e..098d5255a1 100644 --- a/demonstrations_v2/tutorial_learning_from_experiments/metadata.json +++ b/demonstrations_v2/tutorial_learning_from_experiments/metadata.json @@ -1,48 +1,48 @@ { - "title": "Quantum advantage in learning from experiments", - "authors": [ - { - "username": "josephbowles" - } - ], - "dateOfPublication": "2022-04-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_advantage_learning_from_experiments.png" - } - ], - "seoDescription": "Learn how quantum memory can boost quantum machine learning algorithms", - "doi": "", - "references": [ - { - "id": "Huang2021", - "type": "article", - "title": "Quantum advantage in learning from experiments", - "authors": "Hsin-Yuan Huang et al.", - "year": "2021", - "journal": "", - "doi": "10.1126/science.abn7293", - "url": "https://arxiv.org/pdf/2112.00778.pdf" - }, - { - "id": "Chen2021", - "type": "article", - "title": "Exponential separations between learning with and without quantum memory", - "authors": "Sitan Chen, Jordan Cotler, Hsin-Yuan Huang, Jerry Li", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2111.05881" - } - ], - "basedOnPapers": [ - "10.1126/science.abn7293" - ], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Quantum advantage in learning from experiments", + "authors": [ + { + "username": "josephbowles" + } + ], + "dateOfPublication": "2022-04-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_advantage_learning_from_experiments.png" + } + ], + "seoDescription": "Learn how quantum memory can boost quantum machine learning algorithms", + "doi": "", + "references": [ + { + "id": "Huang2021", + "type": "article", + "title": "Quantum advantage in learning from experiments", + "authors": "Hsin-Yuan Huang et al.", + "year": "2021", + "journal": "", + "doi": "10.1126/science.abn7293", + "url": "https://arxiv.org/pdf/2112.00778.pdf" + }, + { + "id": "Chen2021", + "type": "article", + "title": "Exponential separations between learning with and without quantum memory", + "authors": "Sitan Chen, Jordan Cotler, Hsin-Yuan Huang, Jerry Li", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2111.05881" + } + ], + "basedOnPapers": [ + "10.1126/science.abn7293" + ], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_learningshallow/demo.py b/demonstrations_v2/tutorial_learningshallow/demo.py index 38cd6d014a..9f762f7714 100644 --- a/demonstrations_v2/tutorial_learningshallow/demo.py +++ b/demonstrations_v2/tutorial_learningshallow/demo.py @@ -463,3 +463,6 @@ def sewing_test(): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt diff --git a/demonstrations_v2/tutorial_learningshallow/metadata.json b/demonstrations_v2/tutorial_learningshallow/metadata.json index 5d97f2e6c4..a7718c5ea3 100644 --- a/demonstrations_v2/tutorial_learningshallow/metadata.json +++ b/demonstrations_v2/tutorial_learningshallow/metadata.json @@ -1,85 +1,85 @@ { - "title": "Learning shallow quantum circuits with local inversions and circuit sewing", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2024-01-24T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Optimization", - "Quantum Computing", - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/learningshallow/thumbnail_learningshallow.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_learningshallow.png" - } - ], - "seoDescription": "Learn how to do circuit sewing with local inversions, which play a crucial role in learning shallow quantum circuits", - "doi": "", - "references": [ - { - "id": "Huang", - "type": "preprint", - "title": "Learning shallow quantum circuits", - "authors": "Hsin-Yuan Huang, Yunchao Liu, Michael Broughton, Isaac Kim, Anurag Anshu, Zeph Landau, Jarrod R. McClean", - "year": "2024", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2401.10095", - "url": "https://arxiv.org/abs/2401.10095" - }, - { - "id": "Bravyi", - "type": "preprint", - "title": "Sergey Bravyi, David Gosset, Robert Koenig", - "authors": "Quantum advantage with shallow circuits", - "year": "2017", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.1704.00690", - "url": "https://arxiv.org/abs/1704.00690" - }, - { - "id": "Anschuetz", - "type": "preprint", - "title": "Beyond Barren Plateaus: Quantum Variational Algorithms Are Swamped With Traps", - "authors": "Eric R. Anschuetz, Bobak T. Kiani", - "year": "2022", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2205.05786", - "url": "https://arxiv.org/abs/2205.05786" - }, - { - "id": "Shende", - "type": "article", - "title": "Synthesis of Quantum Logic Circuits", - "authors": "Vivek V. Shende, Stephen S. Bullock, Igor L. Markov", - "year": "2004", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0406176", - "url": "https://arxiv.org/abs/quant-ph/0406176" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2401.10095" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Learning shallow quantum circuits with local inversions and circuit sewing", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-01-24T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Computing", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/learningshallow/thumbnail_learningshallow.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_learningshallow.png" + } + ], + "seoDescription": "Learn how to do circuit sewing with local inversions, which play a crucial role in learning shallow quantum circuits", + "doi": "", + "references": [ + { + "id": "Huang", + "type": "preprint", + "title": "Learning shallow quantum circuits", + "authors": "Hsin-Yuan Huang, Yunchao Liu, Michael Broughton, Isaac Kim, Anurag Anshu, Zeph Landau, Jarrod R. McClean", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2401.10095", + "url": "https://arxiv.org/abs/2401.10095" + }, + { + "id": "Bravyi", + "type": "preprint", + "title": "Sergey Bravyi, David Gosset, Robert Koenig", + "authors": "Quantum advantage with shallow circuits", + "year": "2017", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1704.00690", + "url": "https://arxiv.org/abs/1704.00690" + }, + { + "id": "Anschuetz", + "type": "preprint", + "title": "Beyond Barren Plateaus: Quantum Variational Algorithms Are Swamped With Traps", + "authors": "Eric R. Anschuetz, Bobak T. Kiani", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2205.05786", + "url": "https://arxiv.org/abs/2205.05786" + }, + { + "id": "Shende", + "type": "article", + "title": "Synthesis of Quantum Logic Circuits", + "authors": "Vivek V. Shende, Stephen S. Bullock, Igor L. Markov", + "year": "2004", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0406176", + "url": "https://arxiv.org/abs/quant-ph/0406176" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.2401.10095" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_liealgebra/demo.py b/demonstrations_v2/tutorial_liealgebra/demo.py index b0615d8e1d..59e6e2e65b 100644 --- a/demonstrations_v2/tutorial_liealgebra/demo.py +++ b/demonstrations_v2/tutorial_liealgebra/demo.py @@ -1,4 +1,4 @@ -r"""Introducing (Dynamical) Lie Algebras for quantum practitioners +r"""Introducing (dynamical) Lie algebras for quantum practitioners ================================================================== Are you a quantum practitioner that has so far successfully avoided learning about Lie groups and Lie algebras, @@ -456,3 +456,6 @@ def IsingGenerators(n, bc="open"): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt diff --git a/demonstrations_v2/tutorial_liealgebra/metadata.json b/demonstrations_v2/tutorial_liealgebra/metadata.json index 379aac25a0..94e1a00810 100644 --- a/demonstrations_v2/tutorial_liealgebra/metadata.json +++ b/demonstrations_v2/tutorial_liealgebra/metadata.json @@ -1,115 +1,115 @@ { - "title": "Introducing (Dynamical) Lie Algebras for quantum practitioners", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2024-02-27T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/liealgebra/thumbnail_liealgebra.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_liealgebra.png" - } - ], - "seoDescription": "A gentle introduction to Lie theory covering the basics of Lie algebras and Lie groups in the context of quantum computing.", - "doi": "", - "references": [ - { - "id": "Wiersema", - "type": "preprint", - "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", - "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.05690", - "url": "https://arxiv.org/abs/2309.05690" - }, - { - "id": "Meyer", - "type": "article", - "title": "Exploiting symmetry in variational quantum machine learning", - "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert", - "year": "2022", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2205.06217", - "url": "https://arxiv.org/abs/2205.06217" - }, - { - "id": "Nguyen", - "type": "preprint", - "title": "Theory for Equivariant Quantum Neural Networks", - "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Frederic Sauvage, Martin Larocca, M. Cerezo", - "year": "2022", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2210.08566", - "url": "https://arxiv.org/abs/2210.08566" - }, - { - "id": "Fontana", - "type": "article", - "title": "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ans\u00e4tze", - "authors": "Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.07902", - "url": "https://arxiv.org/abs/2309.07902" - }, - { - "id": "Ragone", - "type": "preprint", - "title": "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits", - "authors": "Michael Ragone, Bojko N. Bakalov, Fr\u00e9d\u00e9ric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.09342", - "url": "https://arxiv.org/abs/2309.09342" - }, - { - "id": "Goh", - "type": "preprint", - "title": "Lie-algebraic classical simulations for variational quantum computing", - "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2308.01432", - "url": "https://arxiv.org/abs/2308.01432" - }, - { - "id": "Somma", - "type": "preprint", - "title": "Quantum Computation, Complexity, and Many-Body Physics", - "authors": "Rolando D. Somma", - "year": "2005", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0512209", - "url": "https://arxiv.org/abs/quant-ph/0512209" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_geometric_qml", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Introducing (dynamical) Lie algebras for quantum practitioners", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-02-27T00:00:00+00:00", + "dateOfLastModification": "2024-12-18T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/liealgebra/thumbnail_liealgebra.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_liealgebra.png" + } + ], + "seoDescription": "A gentle introduction to Lie theory covering the basics of Lie algebras and Lie groups in the context of quantum computing.", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Meyer", + "type": "article", + "title": "Exploiting symmetry in variational quantum machine learning", + "authors": "Johannes Jakob Meyer, Marian Mularski, Elies Gil-Fuster, Antonio Anna Mele, Francesco Arzani, Alissa Wilms, Jens Eisert", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2205.06217", + "url": "https://arxiv.org/abs/2205.06217" + }, + { + "id": "Nguyen", + "type": "preprint", + "title": "Theory for Equivariant Quantum Neural Networks", + "authors": "Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, Patrick J. Coles, Frederic Sauvage, Martin Larocca, M. Cerezo", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2210.08566", + "url": "https://arxiv.org/abs/2210.08566" + }, + { + "id": "Fontana", + "type": "article", + "title": "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ansätze", + "authors": "Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.07902", + "url": "https://arxiv.org/abs/2309.07902" + }, + { + "id": "Ragone", + "type": "preprint", + "title": "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits", + "authors": "Michael Ragone, Bojko N. Bakalov, Frédéric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.09342", + "url": "https://arxiv.org/abs/2309.09342" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_liesim/demo.py b/demonstrations_v2/tutorial_liesim/demo.py index 2fee5f3463..14f91b9a05 100644 --- a/demonstrations_v2/tutorial_liesim/demo.py +++ b/demonstrations_v2/tutorial_liesim/demo.py @@ -481,3 +481,6 @@ def forward(theta): # ############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_liesim/metadata.json b/demonstrations_v2/tutorial_liesim/metadata.json index decf240cce..f3d75d959d 100644 --- a/demonstrations_v2/tutorial_liesim/metadata.json +++ b/demonstrations_v2/tutorial_liesim/metadata.json @@ -1,165 +1,163 @@ { - "title": "g-sim: Lie-algebraic classical simulations for variational quantum computing", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2024-06-07T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/liesim/thumbnail_gsim.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_gsim.png" - } - ], - "seoDescription": "A differentiable implementation of g-sim in PennyLane", - "doi": "", - "references": [ - { - "id": "Wiersema", - "type": "preprint", - "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", - "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.05690", - "url": "https://arxiv.org/abs/2309.05690" - }, - { - "id": "Fontana", - "type": "article", - "title": "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ans\u00e4tze", - "authors": "Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.07902", - "url": "https://arxiv.org/abs/2309.07902" - }, - { - "id": "Ragone", - "type": "preprint", - "title": "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits", - "authors": "Michael Ragone, Bojko N. Bakalov, Fr\u00e9d\u00e9ric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.09342", - "url": "https://arxiv.org/abs/2309.09342" - }, - { - "id": "Goh", - "type": "preprint", - "title": "Lie-algebraic classical simulations for variational quantum computing", - "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2308.01432", - "url": "https://arxiv.org/abs/2308.01432" - }, - { - "id": "Somma", - "type": "preprint", - "title": "Quantum Computation, Complexity, and Many-Body Physics", - "authors": "Rolando D. Somma", - "year": "2005", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0512209", - "url": "https://arxiv.org/abs/quant-ph/0512209" - }, - { - "id": "Somma2", - "type": "preprint", - "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", - "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", - "year": "2006", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0601030", - "url": "https://arxiv.org/abs/quant-ph/0601030" - }, - { - "id": "Galitski", - "type": "preprint", - "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", - "authors": "Victor Galitski", - "year": "2010", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.1012.2873", - "url": "https://arxiv.org/abs/1012.2873" - }, - { - "id": "Cerezo", - "type": "preprint", - "title": "Does provable absence of barren plateaus imply classical simulability? Or, why we need to rethink variational quantum computing", - "authors": "M. Cerezo, Martin Larocca, Diego Garc\u00eda-Mart\u00edn, N. L. Diaz, Paolo Braccia, Enrico Fontana, Manuel S. Rudolph, Pablo Bermejo, Aroosa Ijaz, Supanut Thanasilp, Eric R. Anschuetz, Zo\u00eb Holmes", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2312.09121", - "url": "https://arxiv.org/abs/2312.09121" - }, - { - "id": "Mazzola", - "type": "preprint", - "title": "Quantum computing for chemistry and physics applications from a Monte Carlo perspective", - "authors": "Guglielmo Mazzola", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2308.07964", - "url": "https://arxiv.org/abs/2308.07964" - }, - { - "id": "Park", - "type": "preprint", - "title": "Hardware-efficient ansatz without barren plateaus in any depth", - "authors": "Chae-Yeun Park, Minhyeok Kang, Joonsuk Huh", - "year": "2024", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2403.04844", - "url": "https://arxiv.org/abs/2403.04844" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2308.01432" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_liealgebra", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_barren_plateaus", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_liesim_extension", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "g-sim: Lie-algebraic classical simulations for variational quantum computing", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-06-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/liesim/thumbnail_gsim.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_gsim.png" + } + ], + "seoDescription": "A differentiable implementation of g-sim in PennyLane", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Fontana", + "type": "article", + "title": "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ansätze", + "authors": "Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.07902", + "url": "https://arxiv.org/abs/2309.07902" + }, + { + "id": "Ragone", + "type": "preprint", + "title": "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits", + "authors": "Michael Ragone, Bojko N. Bakalov, Frédéric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.09342", + "url": "https://arxiv.org/abs/2309.09342" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "Somma2", + "type": "preprint", + "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", + "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", + "year": "2006", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0601030", + "url": "https://arxiv.org/abs/quant-ph/0601030" + }, + { + "id": "Galitski", + "type": "preprint", + "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", + "authors": "Victor Galitski", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1012.2873", + "url": "https://arxiv.org/abs/1012.2873" + }, + { + "id": "Cerezo", + "type": "preprint", + "title": "Does provable absence of barren plateaus imply classical simulability? Or, why we need to rethink variational quantum computing", + "authors": "M. Cerezo, Martin Larocca, Diego García-Martín, N. L. Diaz, Paolo Braccia, Enrico Fontana, Manuel S. Rudolph, Pablo Bermejo, Aroosa Ijaz, Supanut Thanasilp, Eric R. Anschuetz, Zoë Holmes", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2312.09121", + "url": "https://arxiv.org/abs/2312.09121" + }, + { + "id": "Mazzola", + "type": "preprint", + "title": "Quantum computing for chemistry and physics applications from a Monte Carlo perspective", + "authors": "Guglielmo Mazzola", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.07964", + "url": "https://arxiv.org/abs/2308.07964" + }, + { + "id": "Park", + "type": "preprint", + "title": "Hardware-efficient ansatz without barren plateaus in any depth", + "authors": "Chae-Yeun Park, Minhyeok Kang, Joonsuk Huh", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2403.04844", + "url": "https://arxiv.org/abs/2403.04844" + } + ], + "basedOnPapers": ["10.48550/arXiv.2308.01432"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim_extension", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_liesim_extension/demo.py b/demonstrations_v2/tutorial_liesim_extension/demo.py index b7f2942bef..b0f3226e98 100644 --- a/demonstrations_v2/tutorial_liesim_extension/demo.py +++ b/demonstrations_v2/tutorial_liesim_extension/demo.py @@ -534,3 +534,6 @@ def Moment_step(ops, dla): # ############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_liesim_extension/metadata.json b/demonstrations_v2/tutorial_liesim_extension/metadata.json index cb14378ab7..c91f1393be 100644 --- a/demonstrations_v2/tutorial_liesim_extension/metadata.json +++ b/demonstrations_v2/tutorial_liesim_extension/metadata.json @@ -1,100 +1,98 @@ { - "title": "(g + P)-sim: Extending g-sim by non-DLA observables and gates", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2024-06-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/liesim_extension/thumbnail_liesim_extension.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_liesim_extension.png" - } - ], - "seoDescription": "(g + P)-sim: Extending g-sim by non-DLA observables and gates", - "doi": "", - "references": [ - { - "id": "Wiersema", - "type": "preprint", - "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", - "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.05690", - "url": "https://arxiv.org/abs/2309.05690" - }, - { - "id": "Goh", - "type": "preprint", - "title": "Lie-algebraic classical simulations for variational quantum computing", - "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2308.01432", - "url": "https://arxiv.org/abs/2308.01432" - }, - { - "id": "Somma", - "type": "preprint", - "title": "Quantum Computation, Complexity, and Many-Body Physics", - "authors": "Rolando D. Somma", - "year": "2005", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0512209", - "url": "https://arxiv.org/abs/quant-ph/0512209" - }, - { - "id": "Somma2", - "type": "preprint", - "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", - "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", - "year": "2006", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0601030", - "url": "https://arxiv.org/abs/quant-ph/0601030" - }, - { - "id": "Galitski", - "type": "preprint", - "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", - "authors": "Victor Galitski", - "year": "2010", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.1012.2873", - "url": "https://arxiv.org/abs/1012.2873" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2308.01432" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_liealgebra", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_liesim", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "(g + P)-sim: Extending g-sim by non-DLA observables and gates", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-06-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/liesim_extension/thumbnail_liesim_extension.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_liesim_extension.png" + } + ], + "seoDescription": "(g + P)-sim: Extending g-sim by non-DLA observables and gates", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "Somma2", + "type": "preprint", + "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", + "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", + "year": "2006", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0601030", + "url": "https://arxiv.org/abs/quant-ph/0601030" + }, + { + "id": "Galitski", + "type": "preprint", + "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", + "authors": "Victor Galitski", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1012.2873", + "url": "https://arxiv.org/abs/1012.2873" + } + ], + "basedOnPapers": ["10.48550/arXiv.2308.01432"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_local_cost_functions/demo.py b/demonstrations_v2/tutorial_local_cost_functions/demo.py index 5d5d8db870..f981a9f485 100644 --- a/demonstrations_v2/tutorial_local_cost_functions/demo.py +++ b/demonstrations_v2/tutorial_local_cost_functions/demo.py @@ -532,3 +532,6 @@ def cost_tunable(rotations): # `arXiv:2001.00550 `__ # # +# About the author +# ---------------- +# .. include:: ../_static/authors/thomas_storwick.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_local_cost_functions/metadata.json b/demonstrations_v2/tutorial_local_cost_functions/metadata.json index 39db0e05a9..8525b307cd 100644 --- a/demonstrations_v2/tutorial_local_cost_functions/metadata.json +++ b/demonstrations_v2/tutorial_local_cost_functions/metadata.json @@ -1,44 +1,42 @@ { - "title": "Alleviating barren plateaus with local cost functions", - "authors": [ - { - "username": "tstorwick" - } - ], - "dateOfPublication": "2020-09-09T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_alleviating_barren_plateaus.png" - } - ], - "seoDescription": "Local cost functions are cost formulations for variational quantum circuits that are more robust to barren plateaus.", - "doi": "", - "references": [ - { - "id": "Cerezo2020", - "type": "article", - "title": "Cost-Function-Dependent Barren Plateaus in Shallow Quantum Neural Networks", - "authors": "Cerezo, M., Sone, A., Volkoff, T., Cincio, L., and Coles, P.", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2001.00550" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2001.00550" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_barren_plateaus", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Alleviating barren plateaus with local cost functions", + "authors": [ + { + "username": "tstorwick" + } + ], + "dateOfPublication": "2020-09-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_alleviating_barren_plateaus.png" + } + ], + "seoDescription": "Local cost functions are cost formulations for variational quantum circuits that are more robust to barren plateaus.", + "doi": "", + "references": [ + { + "id": "Cerezo2020", + "type": "article", + "title": "Cost-Function-Dependent Barren Plateaus in Shallow Quantum Neural Networks", + "authors": "Cerezo, M., Sone, A., Volkoff, T., Cincio, L., and Coles, P.", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2001.00550" + } + ], + "basedOnPapers": ["10.48550/arXiv.2001.00550"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_barren_plateaus", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_magic_state_distillation/demo.py b/demonstrations_v2/tutorial_magic_state_distillation/demo.py index 8532c500a8..eb1bfd6990 100644 --- a/demonstrations_v2/tutorial_magic_state_distillation/demo.py +++ b/demonstrations_v2/tutorial_magic_state_distillation/demo.py @@ -333,3 +333,6 @@ def faulty_T0_state(random_key, r): # `arXiv preprint arXiv:1205.6715 (2012) `__. ###################################################################### +# About the author +# ---------------- +# .. include:: ../_static/authors/david_ittah.txt diff --git a/demonstrations_v2/tutorial_magic_state_distillation/metadata.json b/demonstrations_v2/tutorial_magic_state_distillation/metadata.json index 4ff025ae03..99f65e1d10 100644 --- a/demonstrations_v2/tutorial_magic_state_distillation/metadata.json +++ b/demonstrations_v2/tutorial_magic_state_distillation/metadata.json @@ -1,52 +1,52 @@ { - "title": "Magic state distillation", - "authors": [ - { - "username": "david" - } - ], - "dateOfPublication": "2024-04-26T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_magic-state-distillation.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_magic-state-distillation.png" - } - ], - "seoDescription": "Use PennyLane, Catalyst, and QJIT to improve the fidelity of magic T-states.", - "doi": "", - "references": [ - { - "id": "bravyi2005", - "type": "article", - "title": "Universal quantum computation with ideal Clifford gates and noisy ancillas.", - "authors": "Sergey Bravyi, Alexei Kitaev", - "year": "2005", - "journal": "Physical Review A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.71.022316" - }, - { - "id": "tomas2012", - "type": "article", - "title": "The robustness of magic state distillation against errors in Clifford gates.", - "authors": "Tomas Jochym-O'Connor", - "year": "2012", - "journal": "arXiv", - "url": "https://arxiv.org/abs/1205.6715" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1205.6715" - ], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Magic state distillation", + "authors": [ + { + "username": "david" + } + ], + "dateOfPublication": "2024-04-26T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_magic-state-distillation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_magic-state-distillation.png" + } + ], + "seoDescription": "Use PennyLane, Catalyst, and QJIT to improve the fidelity of magic T-states.", + "doi": "", + "references": [ + { + "id": "bravyi2005", + "type": "article", + "title": "Universal quantum computation with ideal Clifford gates and noisy ancillas.", + "authors": "Sergey Bravyi, Alexei Kitaev", + "year": "2005", + "journal": "Physical Review A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.71.022316" + }, + { + "id": "tomas2012", + "type": "article", + "title": "The robustness of magic state distillation against errors in Clifford gates.", + "authors": "Tomas Jochym-O'Connor", + "year": "2012", + "journal": "arXiv", + "url": "https://arxiv.org/abs/1205.6715" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1205.6715" + ], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_mapping/demo.py b/demonstrations_v2/tutorial_mapping/demo.py index be1ce23b5d..e1f1911189 100644 --- a/demonstrations_v2/tutorial_mapping/demo.py +++ b/demonstrations_v2/tutorial_mapping/demo.py @@ -341,3 +341,5 @@ def circuit(params): # `Physical Review A 102.6 (2020). # `__ # +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_mapping/metadata.json b/demonstrations_v2/tutorial_mapping/metadata.json index 8ac1d86029..f7be98076a 100644 --- a/demonstrations_v2/tutorial_mapping/metadata.json +++ b/demonstrations_v2/tutorial_mapping/metadata.json @@ -1,51 +1,51 @@ { - "title": "Mapping fermionic Hamiltonians to qubit Hamiltonians", - "authors": [ - { - "username": "ddhawan" - } - ], - "dateOfPublication": "2024-05-06T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing", - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/mapping/thumbnail_mapping_2024-06-20.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mapping_2024-06-20.png" - } - ], - "seoDescription": "Learn how to map fermionic operators to qubit operators", - "doi": "", - "references": [ - { - "id": "Tranter", - "type": "article", - "title": "The Bravyi\u2013Kitaev Transformation: Properties and Applications", - "authors": "A. Tranter, S. Sofia et al.", - "year": "2015", - "publisher": "International Journal of Quantum Chemistry", - "url": "https://onlinelibrary.wiley.com/doi/10.1002/qua.24969" - }, - { - "id": "Yordanov", - "type": "article", - "title": "Efficient quantum circuits for quantum computational chemistry", - "authors": "Y. S. Yordanov et al.", - "year": "2020", - "publisher": "Physical Review A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.102.062612" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Mapping fermionic Hamiltonians to qubit Hamiltonians", + "authors": [ + { + "username": "ddhawan" + } + ], + "dateOfPublication": "2024-05-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing", + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/mapping/thumbnail_mapping_2024-06-20.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mapping_2024-06-20.png" + } + ], + "seoDescription": "Learn how to map fermionic operators to qubit operators", + "doi": "", + "references": [ + { + "id": "Tranter", + "type": "article", + "title": "The Bravyi–Kitaev Transformation: Properties and Applications", + "authors": "A. Tranter, S. Sofia et al.", + "year": "2015", + "publisher": "International Journal of Quantum Chemistry", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/qua.24969" + }, + { + "id": "Yordanov", + "type": "article", + "title": "Efficient quantum circuits for quantum computational chemistry", + "authors": "Y. S. Yordanov et al.", + "year": "2020", + "publisher": "Physical Review A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.102.062612" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_mbqc/demo.py b/demonstrations_v2/tutorial_mbqc/demo.py index f21d800114..bcbe5d524f 100644 --- a/demonstrations_v2/tutorial_mbqc/demo.py +++ b/demonstrations_v2/tutorial_mbqc/demo.py @@ -807,3 +807,8 @@ def CNOT_MBQC(input_state): ############################################################################## # +# About the authors +# ---------------- +# +# .. include:: ../_static/authors/joost_bus.txt +# .. include:: ../_static/authors/radoica_draskic.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_mbqc/metadata.json b/demonstrations_v2/tutorial_mbqc/metadata.json index 5e3d5b118c..79b26f370d 100644 --- a/demonstrations_v2/tutorial_mbqc/metadata.json +++ b/demonstrations_v2/tutorial_mbqc/metadata.json @@ -1,245 +1,245 @@ { - "title": "Measurement-based quantum computation", - "authors": [ - { - "username": "jbus" - }, - { - "username": "rdraskic" - } - ], - "dateOfPublication": "2022-12-05T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_measurement-based_QC.png" - } - ], - "seoDescription": "Learn about measurement-based quantum computation", - "doi": "", - "references": [ - { - "id": "OneWay2001", - "type": "article", - "title": "A One-Way Quantum Computer", - "authors": "Robert Raussendorf and Hans J. Briegel", - "year": "2001", - "journal": "Phys. Rev. Lett.", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.5188" - }, - { - "id": "MBQCRealization", - "type": "article", - "title": "Realizations of Measurement Based Quantum Computing", - "authors": "Swapnil Nitin Shah", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/pdf/2112.11601.pdf" - }, - { - "id": "XanaduBlueprint", - "type": "article", - "title": "Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer", - "authors": "J. Eli Bourassa, Rafael N. Alexander, Michael Vasmer, Ashlesha Patil, Ilan Tzitrin, Takaya Matsuura, Daiqin Su, Ben Q. Baragiola, Saikat Guha, Guillaume Dauphinais, Krishna K. Sabapathy, Nicolas C. Menicucci, and Ish Dhand", - "year": "2021", - "journal": "Quantum", - "url": "https://quantum-journal.org/papers/q-2021-02-04-392/" - }, - { - "id": "XanaduPassiveArchitecture", - "type": "article", - "title": "Fault-Tolerant Quantum Computation with Static Linear Optics", - "authors": "Ilan Tzitrin, Takaya Matsuura, Rafael N. Alexander, Guillaume Dauphinais, J. Eli Bourassa, Krishna K. Sabapathy, Nicolas C. Menicucci, and Ish Dhand", - "year": "2021", - "journal": "PRX Quantum", - "volume": "2", - "number": "4", - "doi": "10.1103/PRXQuantum.2.040353", - "url": "" - }, - { - "id": "ShorQEC1995", - "type": "article", - "title": "Scheme for reducing decoherence in quantum computer memory", - "authors": "Peter W. Shor", - "year": "1995", - "journal": "Physical Review A", - "volume": "52", - "issue": "4", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.52.R2493" - }, - { - "id": "LatticeSurgeryRaussendorf2018", - "type": "article", - "title": "Lattice Surgery on the Raussendorf Lattice", - "authors": "Daniel Herr, Alexandru Paler, Simon J. Devitt and Franco Nori", - "year": "2018", - "journal": "IOP Publishing", - "url": "https://arxiv.org/abs/1711.04921" - }, - { - "id": "FowlerSurfaceCode", - "type": "article", - "title": "Surface codes: Towards practical large-scale quantum computation", - "authors": "Austin G. Fowler, Matteo Mariantoni, John M. Martinis, Andrew N. Cleland", - "year": "2012", - "journal": "", - "url": "https://arxiv.org/abs/1208.0928" - }, - { - "id": "GoogleQEC2022", - "type": "article", - "title": "Suppressing quantum errors by scaling a surface code logical qubit", - "authors": "Google Quantum AI", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/pdf/2207.06431.pdf" - }, - { - "id": "CV-MBQC", - "type": "article", - "title": "Universal Quantum Computation with Continuous-Variable Cluster States", - "authors": "Nicolas C. Menicucci, Peter van Loock, Mile Gu, Christian Weedbrook, Timothy C. Ralph, and Michael A. Nielsen", - "year": "2006", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0605198" - }, - { - "id": "DiVincenzo", - "type": "article", - "title": "The Physical Implementation of Quantum Computation", - "authors": "David P. DiVincenzo", - "year": "2000", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0002077" - }, - { - "id": "Furusawa1998", - "type": "article", - "title": "Unconditional Quantum Teleportation", - "authors": "A. Furusawa, J. L. S\u00f8rensen, S. L. Braunstein, C. A. Fuchs,H. J. Kimble, E. S. Polzik", - "year": "1998", - "journal": "Science", - "volume": "282", - "issue": "5389", - "url": "https://www.science.org/doi/10.1126/science.282.5389.706" - }, - { - "id": "Nielsen1998", - "type": "article", - "title": "Complete quantum teleportation using nuclear magnetic resonance", - "authors": "M. A. Nielsen, E. Knill & R. Laflamme", - "year": "1998", - "journal": "Nature", - "volume": "396", - "url": "https://www.nature.com/articles/23891" - }, - { - "id": "Hermans2022", - "type": "article", - "title": "Qubit teleportation between non-neighbouring nodes in a quantum network", - "authors": "S. L. N. Hermans, M. Pompili, H. K. C. Beukers, S. Baier, J. Borregaard & R. Hanson", - "year": "2022", - "journal": "Nature", - "url": "https://www.nature.com/articles/s41586-022-04697-y" - }, - { - "id": "Riebe2004", - "type": "article", - "title": "Deterministic quantum teleportation with atoms", - "authors": "M. Riebe, H. H\u00e4ffner, C. F. Roos, W. H\u00e4nsel, J. Benhelm, G. P. T. Lancaster, T. W. K\u00f6rber, C. Becher, F. Schmidt-Kaler, D. F. V. James & R. Blatt", - "year": "2002", - "journal": "Nature", - "url": "https://www.nature.com/articles/nature02570" - }, - { - "id": "FoliatedQuantumCodes", - "type": "article", - "title": "Foliated Quantum Codes", - "authors": "A. Bolt, G. Duclos-Cianci, D. Poulin, T. M. Stace", - "year": "2016", - "journal": "", - "url": "https://arxiv.org/abs/1607.02579" - }, - { - "id": "MagicStates", - "type": "article", - "title": "Universal quantum computation with ideal Clifford gates and noisy ancillas", - "authors": "Sergey Bravyi and Alexei Kitaev", - "year": "2004", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0403025" - }, - { - "id": "QuantumTeleportation", - "type": "article", - "title": "Multi-party entanglement in graph states", - "authors": "M. Hein, J. Eisert and H.J. Briegel", - "year": "2003", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0307130" - }, - { - "id": "OpticalQuantumComputing", - "type": "article", - "title": "Optical quantum computing", - "authors": "Jeremy L. O'Brien", - "year": "2007", - "journal": "Science", - "volume": "318", - "issue": "5856", - "url": "https://www.science.org/doi/10.1126/science.1142892" - }, - { - "id": "FowlerPolyestimate", - "type": "article", - "title": "Polyestimate: instantaneous open source surface code analysis", - "authors": "Austin G. Fowler", - "year": "2013", - "journal": "", - "url": "https://arxiv.org/abs/1307.0689" - }, - { - "id": "EntanglementGraphStates", - "type": "article", - "title": "Entanglement in Graph States and its Applications", - "authors": "M. Hein, W. D\u00fcr, J. Eisert, R. Raussendorf, M. Van den Nest, H.J. Briegel", - "year": "2006", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0602096" - }, - { - "id": "PersistentEntanglement", - "type": "article", - "title": "Persistent Entanglement in Arrays of Interacting Particles", - "authors": "Hans J. Briegel and Robert Raussendorf", - "year": "2001", - "journal": "Phys. Rev. Lett.", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.910" - }, - { - "id": "UniversalFTMBQC", - "type": "article", - "title": "Universal fault-tolerant measurement-based quantum computation", - "authors": "Benjamin J. Brown, Sam Roberts", - "year": "2018", - "journal": "", - "url": "https://arxiv.org/abs/1811.11780" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_toric_code", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Measurement-based quantum computation", + "authors": [ + { + "username": "jbus" + }, + { + "username": "rdraskic" + } + ], + "dateOfPublication": "2022-12-05T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_measurement-based_QC.png" + } + ], + "seoDescription": "Learn about measurement-based quantum computation", + "doi": "", + "references": [ + { + "id": "OneWay2001", + "type": "article", + "title": "A One-Way Quantum Computer", + "authors": "Robert Raussendorf and Hans J. Briegel", + "year": "2001", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.5188" + }, + { + "id": "MBQCRealization", + "type": "article", + "title": "Realizations of Measurement Based Quantum Computing", + "authors": "Swapnil Nitin Shah", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/pdf/2112.11601.pdf" + }, + { + "id": "XanaduBlueprint", + "type": "article", + "title": "Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer", + "authors": "J. Eli Bourassa, Rafael N. Alexander, Michael Vasmer, Ashlesha Patil, Ilan Tzitrin, Takaya Matsuura, Daiqin Su, Ben Q. Baragiola, Saikat Guha, Guillaume Dauphinais, Krishna K. Sabapathy, Nicolas C. Menicucci, and Ish Dhand", + "year": "2021", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-02-04-392/" + }, + { + "id": "XanaduPassiveArchitecture", + "type": "article", + "title": "Fault-Tolerant Quantum Computation with Static Linear Optics", + "authors": "Ilan Tzitrin, Takaya Matsuura, Rafael N. Alexander, Guillaume Dauphinais, J. Eli Bourassa, Krishna K. Sabapathy, Nicolas C. Menicucci, and Ish Dhand", + "year": "2021", + "journal": "PRX Quantum", + "volume": "2", + "number": "4", + "doi": "10.1103/PRXQuantum.2.040353", + "url": "" + }, + { + "id": "ShorQEC1995", + "type": "article", + "title": "Scheme for reducing decoherence in quantum computer memory", + "authors": "Peter W. Shor", + "year": "1995", + "journal": "Physical Review A", + "volume": "52", + "issue": "4", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.52.R2493" + }, + { + "id": "LatticeSurgeryRaussendorf2018", + "type": "article", + "title": "Lattice Surgery on the Raussendorf Lattice", + "authors": "Daniel Herr, Alexandru Paler, Simon J. Devitt and Franco Nori", + "year": "2018", + "journal": "IOP Publishing", + "url": "https://arxiv.org/abs/1711.04921" + }, + { + "id": "FowlerSurfaceCode", + "type": "article", + "title": "Surface codes: Towards practical large-scale quantum computation", + "authors": "Austin G. Fowler, Matteo Mariantoni, John M. Martinis, Andrew N. Cleland", + "year": "2012", + "journal": "", + "url": "https://arxiv.org/abs/1208.0928" + }, + { + "id": "GoogleQEC2022", + "type": "article", + "title": "Suppressing quantum errors by scaling a surface code logical qubit", + "authors": "Google Quantum AI", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/pdf/2207.06431.pdf" + }, + { + "id": "CV-MBQC", + "type": "article", + "title": "Universal Quantum Computation with Continuous-Variable Cluster States", + "authors": "Nicolas C. Menicucci, Peter van Loock, Mile Gu, Christian Weedbrook, Timothy C. Ralph, and Michael A. Nielsen", + "year": "2006", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0605198" + }, + { + "id": "DiVincenzo", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "David P. DiVincenzo", + "year": "2000", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0002077" + }, + { + "id": "Furusawa1998", + "type": "article", + "title": "Unconditional Quantum Teleportation", + "authors": "A. Furusawa, J. L. Sørensen, S. L. Braunstein, C. A. Fuchs,H. J. Kimble, E. S. Polzik", + "year": "1998", + "journal": "Science", + "volume": "282", + "issue": "5389", + "url": "https://www.science.org/doi/10.1126/science.282.5389.706" + }, + { + "id": "Nielsen1998", + "type": "article", + "title": "Complete quantum teleportation using nuclear magnetic resonance", + "authors": "M. A. Nielsen, E. Knill & R. Laflamme", + "year": "1998", + "journal": "Nature", + "volume": "396", + "url": "https://www.nature.com/articles/23891" + }, + { + "id": "Hermans2022", + "type": "article", + "title": "Qubit teleportation between non-neighbouring nodes in a quantum network", + "authors": "S. L. N. Hermans, M. Pompili, H. K. C. Beukers, S. Baier, J. Borregaard & R. Hanson", + "year": "2022", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-022-04697-y" + }, + { + "id": "Riebe2004", + "type": "article", + "title": "Deterministic quantum teleportation with atoms", + "authors": "M. Riebe, H. Häffner, C. F. Roos, W. Hänsel, J. Benhelm, G. P. T. Lancaster, T. W. Körber, C. Becher, F. Schmidt-Kaler, D. F. V. James & R. Blatt", + "year": "2002", + "journal": "Nature", + "url": "https://www.nature.com/articles/nature02570" + }, + { + "id": "FoliatedQuantumCodes", + "type": "article", + "title": "Foliated Quantum Codes", + "authors": "A. Bolt, G. Duclos-Cianci, D. Poulin, T. M. Stace", + "year": "2016", + "journal": "", + "url": "https://arxiv.org/abs/1607.02579" + }, + { + "id": "MagicStates", + "type": "article", + "title": "Universal quantum computation with ideal Clifford gates and noisy ancillas", + "authors": "Sergey Bravyi and Alexei Kitaev", + "year": "2004", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0403025" + }, + { + "id": "QuantumTeleportation", + "type": "article", + "title": "Multi-party entanglement in graph states", + "authors": "M. Hein, J. Eisert and H.J. Briegel", + "year": "2003", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0307130" + }, + { + "id": "OpticalQuantumComputing", + "type": "article", + "title": "Optical quantum computing", + "authors": "Jeremy L. O'Brien", + "year": "2007", + "journal": "Science", + "volume": "318", + "issue": "5856", + "url": "https://www.science.org/doi/10.1126/science.1142892" + }, + { + "id": "FowlerPolyestimate", + "type": "article", + "title": "Polyestimate: instantaneous open source surface code analysis", + "authors": "Austin G. Fowler", + "year": "2013", + "journal": "", + "url": "https://arxiv.org/abs/1307.0689" + }, + { + "id": "EntanglementGraphStates", + "type": "article", + "title": "Entanglement in Graph States and its Applications", + "authors": "M. Hein, W. Dür, J. Eisert, R. Raussendorf, M. Van den Nest, H.J. Briegel", + "year": "2006", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0602096" + }, + { + "id": "PersistentEntanglement", + "type": "article", + "title": "Persistent Entanglement in Arrays of Interacting Particles", + "authors": "Hans J. Briegel and Robert Raussendorf", + "year": "2001", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.86.910" + }, + { + "id": "UniversalFTMBQC", + "type": "article", + "title": "Universal fault-tolerant measurement-based quantum computation", + "authors": "Benjamin J. Brown, Sam Roberts", + "year": "2018", + "journal": "", + "url": "https://arxiv.org/abs/1811.11780" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_toric_code", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_mcm_introduction/demo.py b/demonstrations_v2/tutorial_mcm_introduction/demo.py index e0232a62eb..39c887fd35 100644 --- a/demonstrations_v2/tutorial_mcm_introduction/demo.py +++ b/demonstrations_v2/tutorial_mcm_introduction/demo.py @@ -467,3 +467,6 @@ def test_t_gadget(init_state): # `arXiv quant-ph/0002039 `__, 2000 # # +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_mcm_introduction/metadata.json b/demonstrations_v2/tutorial_mcm_introduction/metadata.json index a651290d70..2bf7ea6baf 100644 --- a/demonstrations_v2/tutorial_mcm_introduction/metadata.json +++ b/demonstrations_v2/tutorial_mcm_introduction/metadata.json @@ -1,52 +1,52 @@ { - "title": "Introduction to mid-circuit measurements", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2024-05-10T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Getting Started", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mcm_introduction.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mcm_introduction.png" - } - ], - "seoDescription": "Learn the basics of mid-circuit measurements and how to use them in PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_how_to_create_dynamic_mcm_circuits", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_how_to_collect_mcm_stats", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_mbqc", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_teleportation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Introduction to mid-circuit measurements", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-05-10T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mcm_introduction.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mcm_introduction.png" + } + ], + "seoDescription": "Learn the basics of mid-circuit measurements and how to use them in PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_how_to_create_dynamic_mcm_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_collect_mcm_stats", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_teleportation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_measurement_optimize/demo.py b/demonstrations_v2/tutorial_measurement_optimize/demo.py index 31d3913e46..860a8700e6 100644 --- a/demonstrations_v2/tutorial_measurement_optimize/demo.py +++ b/demonstrations_v2/tutorial_measurement_optimize/demo.py @@ -834,3 +834,6 @@ def cost_fn(weights): # 152.12 (2020): 124114. `__ # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_measurement_optimize/metadata.json b/demonstrations_v2/tutorial_measurement_optimize/metadata.json index 96fa55f33b..597ab1dfff 100644 --- a/demonstrations_v2/tutorial_measurement_optimize/metadata.json +++ b/demonstrations_v2/tutorial_measurement_optimize/metadata.json @@ -1,97 +1,97 @@ { - "title": "Measurement optimization", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2021-01-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_measurement_optimization.png" - } - ], - "seoDescription": "Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function.", - "doi": "", - "references": [ - { - "id": "peruzzo2014", - "type": "article", - "title": "A variational eigenvalue solver on a photonic quantum processor", - "authors": "Alberto Peruzzo, Jarrod McClean, et al.", - "year": "2014", - "journal": "Nature Communications", - "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" - }, - { - "id": "yen2020", - "type": "article", - "title": "Measuring all compatible operators in one series of single-qubit measurements using unitary transformations.", - "authors": "Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov", - "year": "2020", - "journal": "Journal of Chemical Theory and Computation", - "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.0c00008" - }, - { - "id": "izmaylov2019", - "type": "article", - "title": "Unitary partitioning approach to the measurement problem in the variational quantum eigensolver method.", - "authors": "Artur F. Izmaylov et al.", - "year": "2019", - "journal": "Journal of Chemical Theory and Computation", - "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.9b00791" - }, - { - "id": "huggins2019", - "type": "article", - "title": "Efficient and noise resilient measurements for quantum chemistry on near-term quantum computers.", - "authors": "William J. Huggins et al.", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1907.13117" - }, - { - "id": "gokhale2020", - "type": "article", - "title": "Minimizing state preparations in variational quantum eigensolver by partitioning into commuting families.", - "authors": "Pranav Gokhale et al.", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1907.13623" - }, - { - "id": "verteletskyi2020", - "type": "article", - "title": "Measurement optimization in the variational quantum eigensolver using a minimum clique cover.", - "authors": "Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov", - "year": "2020", - "journal": "The Journal of Chemical Physics", - "url": "https://aip.scitation.org/doi/10.1063/1.5141458" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qaoa_intro", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Measurement optimization", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2021-01-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_measurement_optimization.png" + } + ], + "seoDescription": "Optimize and reduce the number of measurements required to evaluate a variational algorithm cost function.", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean, et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "yen2020", + "type": "article", + "title": "Measuring all compatible operators in one series of single-qubit measurements using unitary transformations.", + "authors": "Tzu-Ching Yen, Vladyslav Verteletskyi, and Artur F. Izmaylov", + "year": "2020", + "journal": "Journal of Chemical Theory and Computation", + "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.0c00008" + }, + { + "id": "izmaylov2019", + "type": "article", + "title": "Unitary partitioning approach to the measurement problem in the variational quantum eigensolver method.", + "authors": "Artur F. Izmaylov et al.", + "year": "2019", + "journal": "Journal of Chemical Theory and Computation", + "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.9b00791" + }, + { + "id": "huggins2019", + "type": "article", + "title": "Efficient and noise resilient measurements for quantum chemistry on near-term quantum computers.", + "authors": "William J. Huggins et al.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1907.13117" + }, + { + "id": "gokhale2020", + "type": "article", + "title": "Minimizing state preparations in variational quantum eigensolver by partitioning into commuting families.", + "authors": "Pranav Gokhale et al.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1907.13623" + }, + { + "id": "verteletskyi2020", + "type": "article", + "title": "Measurement optimization in the variational quantum eigensolver using a minimum clique cover.", + "authors": "Vladyslav Verteletskyi, Tzu-Ching Yen, and Artur F. Izmaylov", + "year": "2020", + "journal": "The Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/10.1063/1.5141458" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_mitigation_advantage/demo.py b/demonstrations_v2/tutorial_mitigation_advantage/demo.py index 6bd899605c..efffb21c68 100644 --- a/demonstrations_v2/tutorial_mitigation_advantage/demo.py +++ b/demonstrations_v2/tutorial_mitigation_advantage/demo.py @@ -273,3 +273,6 @@ def time_evolution(theta_h, n_layers = 10, obs = qml.PauliZ(4)): # `arXiv:2201.09866 `__, 2022. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt diff --git a/demonstrations_v2/tutorial_mitigation_advantage/metadata.json b/demonstrations_v2/tutorial_mitigation_advantage/metadata.json index 08cb7d2a2d..612599db9b 100644 --- a/demonstrations_v2/tutorial_mitigation_advantage/metadata.json +++ b/demonstrations_v2/tutorial_mitigation_advantage/metadata.json @@ -1,74 +1,72 @@ { - "title": "Is quantum computing useful before fault tolerance?", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2023-06-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/mitigation_advantage/thumbnail_tutorial_mitigation_advantage.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mitigation_advantage.png" - } - ], - "seoDescription": "Using IBM's 127-qubit quantum computer to simulate deep quantum circuits using Zero Noise Extrapolation", - "doi": "", - "references": [ - { - "id": "ibm", - "type": "article", - "title": "Evidence for the utility of quantum computing before fault tolerance", - "authors": "Youngseok Kim, Andrew Eddins, Sajant Anand, Ken Xuan Wei, Ewout van den Berg, Sami Rosenblatt, Hasan Nayfeh, Yantao Wu, Michael Zaletel, Kristan Temme & Abhinav Kandala", - "year": "2023", - "publisher": "Nature", - "doi": "10.1038/s41586-023-06096-3", - "url": "https://www.nature.com/articles/s41586-023-06096-3" - }, - { - "id": "PEC", - "type": "article", - "title": "Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors", - "authors": "Ewout van den Berg, Zlatko K. Minev, Abhinav Kandala, Kristan Temme", - "year": "2022", - "publisher": "", - "url": "https://arxiv.org/abs/2201.09866" - } - ], - "basedOnPapers": [ - "10.1038/s41586-023-06096-3" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_noisy_circuits", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "gbs", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_error_mitigation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_diffable-mitigation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Is quantum computing useful before fault tolerance?", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-06-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/mitigation_advantage/thumbnail_tutorial_mitigation_advantage.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mitigation_advantage.png" + } + ], + "seoDescription": "Using IBM's 127-qubit quantum computer to simulate deep quantum circuits using Zero Noise Extrapolation", + "doi": "", + "references": [ + { + "id": "ibm", + "type": "article", + "title": "Evidence for the utility of quantum computing before fault tolerance", + "authors": "Youngseok Kim, Andrew Eddins, Sajant Anand, Ken Xuan Wei, Ewout van den Berg, Sami Rosenblatt, Hasan Nayfeh, Yantao Wu, Michael Zaletel, Kristan Temme & Abhinav Kandala", + "year": "2023", + "publisher": "Nature", + "doi": "10.1038/s41586-023-06096-3", + "url": "https://www.nature.com/articles/s41586-023-06096-3" + }, + { + "id": "PEC", + "type": "article", + "title": "Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors", + "authors": "Ewout van den Berg, Zlatko K. Minev, Abhinav Kandala, Kristan Temme", + "year": "2022", + "publisher": "", + "url": "https://arxiv.org/abs/2201.09866" + } + ], + "basedOnPapers": ["10.1038/s41586-023-06096-3"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "gbs", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_error_mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_mol_geo_opt/demo.py b/demonstrations_v2/tutorial_mol_geo_opt/demo.py index 681962e167..50f092ddb6 100644 --- a/demonstrations_v2/tutorial_mol_geo_opt/demo.py +++ b/demonstrations_v2/tutorial_mol_geo_opt/demo.py @@ -465,3 +465,6 @@ def grad_x(params, x): # `Advances in Chemical Sciences (1987) # `__ # +# About the author +# ---------------- +# .. include:: ../_static/authors/alain_delgado.txt diff --git a/demonstrations_v2/tutorial_mol_geo_opt/metadata.json b/demonstrations_v2/tutorial_mol_geo_opt/metadata.json index 084f7fd332..c876927c3d 100644 --- a/demonstrations_v2/tutorial_mol_geo_opt/metadata.json +++ b/demonstrations_v2/tutorial_mol_geo_opt/metadata.json @@ -1,106 +1,106 @@ { - "title": "Optimization of molecular geometries", - "authors": [ - { - "username": "adgran" - } - ], - "dateOfPublication": "2021-06-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimization_of_molecular_geometrics.png" - } - ], - "seoDescription": "Find the equilibrium geometry of a molecule", - "doi": "", - "references": [ - { - "id": "jensenbook", - "type": "book", - "title": "Introduction to computational chemistry", - "authors": "F. Jensen", - "year": "2016", - "publisher": "John Wiley & Sons", - "url": "" - }, - { - "id": "seeley2012", - "type": "article", - "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", - "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", - "year": "2012", - "journal": "Journal of Chemical Physics", - "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" - }, - { - "id": "kohanoff2006", - "type": "book", - "title": "Electronic structure calculations for solids and molecules: theory and computational methods", - "authors": "Jorge Kohanoff", - "year": "2006", - "publisher": "Cambridge University Press", - "url": "" - }, - { - "id": "qchemcircuits", - "type": "article", - "title": "Universal quantum circuits for quantum chemistry", - "authors": "J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran", - "year": "2021", - "journal": "", - "url": "" - }, - { - "id": "ref_gamess", - "type": "article", - "title": "General atomic and molecular electronic structure system", - "authors": "M.W. Schmidt, K.K. Baldridge, J.A. Boatz, S.T. Elbert, M.S. Gordon, J.H. Jensen, S. Koseki, N. Matsunaga, K.A. Nguyen, S.Su, et al.", - "year": "1993", - "journal": "Journal of Computational Chemistry", - "url": "https://onlinelibrary.wiley.com/doi/10.1002/jcc.540141112" - }, - { - "id": "geo_opt_paper", - "type": "article", - "title": "Variational quantum algorithm for molecular geometry optimization", - "authors": "A. Delgado, J.M. Arrazola, S. Jahangiri, Z. Niu, J. Izaac, C. Roberts, N. Killoran", - "year": "2021", - "journal": "", - "url": "" - }, - { - "id": "pulay", - "type": "article", - "title": "Analytical derivative methods in quantum chemistry", - "authors": "P. Pulay", - "year": "1987", - "journal": "Advances in Chemical Sciences", - "url": "https://onlinelibrary.wiley.com/doi/10.1002/9780470142943.ch4" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_givens_rotations", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Optimization of molecular geometries", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2021-06-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimization_of_molecular_geometrics.png" + } + ], + "seoDescription": "Find the equilibrium geometry of a molecule", + "doi": "", + "references": [ + { + "id": "jensenbook", + "type": "book", + "title": "Introduction to computational chemistry", + "authors": "F. Jensen", + "year": "2016", + "publisher": "John Wiley & Sons", + "url": "" + }, + { + "id": "seeley2012", + "type": "article", + "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", + "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", + "year": "2012", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" + }, + { + "id": "kohanoff2006", + "type": "book", + "title": "Electronic structure calculations for solids and molecules: theory and computational methods", + "authors": "Jorge Kohanoff", + "year": "2006", + "publisher": "Cambridge University Press", + "url": "" + }, + { + "id": "qchemcircuits", + "type": "article", + "title": "Universal quantum circuits for quantum chemistry", + "authors": "J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran", + "year": "2021", + "journal": "", + "url": "" + }, + { + "id": "ref_gamess", + "type": "article", + "title": "General atomic and molecular electronic structure system", + "authors": "M.W. Schmidt, K.K. Baldridge, J.A. Boatz, S.T. Elbert, M.S. Gordon, J.H. Jensen, S. Koseki, N. Matsunaga, K.A. Nguyen, S.Su, et al.", + "year": "1993", + "journal": "Journal of Computational Chemistry", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/jcc.540141112" + }, + { + "id": "geo_opt_paper", + "type": "article", + "title": "Variational quantum algorithm for molecular geometry optimization", + "authors": "A. Delgado, J.M. Arrazola, S. Jahangiri, Z. Niu, J. Izaac, C. Roberts, N. Killoran", + "year": "2021", + "journal": "", + "url": "" + }, + { + "id": "pulay", + "type": "article", + "title": "Analytical derivative methods in quantum chemistry", + "authors": "P. Pulay", + "year": "1987", + "journal": "Advances in Chemical Sciences", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/9780470142943.ch4" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_mps/demo.py b/demonstrations_v2/tutorial_mps/demo.py index 25d1ff46af..da3e1cad19 100644 --- a/demonstrations_v2/tutorial_mps/demo.py +++ b/demonstrations_v2/tutorial_mps/demo.py @@ -693,3 +693,6 @@ def circuit(): ############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_mps/metadata.json b/demonstrations_v2/tutorial_mps/metadata.json index 0e251e4372..3d2b16346b 100644 --- a/demonstrations_v2/tutorial_mps/metadata.json +++ b/demonstrations_v2/tutorial_mps/metadata.json @@ -1,101 +1,99 @@ { - "title": "Introducing matrix product states for quantum practitioners", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2024-09-29T00:00:00+00:00", - "dateOfLastModification": "2024-11-20T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms", - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mps_simulation.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mps_simulation.png" - } - ], - "seoDescription": "What is a matrix product state (MPS)? Here is all you need to know about MPS from a quantum computation and quantum simulation perspective.", - "doi": "", - "references": [ - { - "id": "Baiardi", - "type": "preprint", - "title": "The Density Matrix Renormalization Group in Chemistry and Molecular Physics: Recent Developments and New Challenges", - "authors": "Alberto Baiardi, Markus Reiher", - "year": "2019", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.1910.00137", - "url": "https://arxiv.org/abs/1910.00137" - }, - { - "id": "Patra", - "type": "preprint", - "title": "Efficient tensor network simulation of IBM's largest quantum processors", - "authors": "Siddhartha Patra, Saeed S. Jahromi, Sukhbinder Singh, Roman Orus", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.15642", - "url": "https://arxiv.org/abs/2309.15642" - }, - { - "id": "Jiang", - "type": "preprint", - "title": "Accurate determination of tensor network state of quantum lattice models in two dimensions", - "authors": "H. C. Jiang, Z. Y. Weng, T. Xiang", - "year": "2008", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.0806.3719", - "url": "https://arxiv.org/abs/0806.3719" - }, - { - "id": "Vidal", - "type": "preprint", - "title": "Efficient classical simulation of slightly entangled quantum computations", - "authors": "Guifre Vidal", - "year": "2003", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0301063", - "url": "https://arxiv.org/abs/quant-ph/0301063" - }, - { - "id": "Schollwoeck", - "type": "preprint", - "title": "The density-matrix renormalization group in the age of Matrix Product States", - "authors": "Ulrich Schollwoeck", - "year": "2010", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.1008.3477", - "url": "https://arxiv.org/abs/1008.3477" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1008.3477" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_How_to_simulate_quantum_circuits_with_tensor_networks", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_diffable-mitigation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Introducing matrix product states for quantum practitioners", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-09-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-20T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_mps_simulation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_mps_simulation.png" + } + ], + "seoDescription": "What is a matrix product state (MPS)? Here is all you need to know about MPS from a quantum computation and quantum simulation perspective.", + "doi": "", + "references": [ + { + "id": "Baiardi", + "type": "preprint", + "title": "The Density Matrix Renormalization Group in Chemistry and Molecular Physics: Recent Developments and New Challenges", + "authors": "Alberto Baiardi, Markus Reiher", + "year": "2019", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1910.00137", + "url": "https://arxiv.org/abs/1910.00137" + }, + { + "id": "Patra", + "type": "preprint", + "title": "Efficient tensor network simulation of IBM's largest quantum processors", + "authors": "Siddhartha Patra, Saeed S. Jahromi, Sukhbinder Singh, Roman Orus", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.15642", + "url": "https://arxiv.org/abs/2309.15642" + }, + { + "id": "Jiang", + "type": "preprint", + "title": "Accurate determination of tensor network state of quantum lattice models in two dimensions", + "authors": "H. C. Jiang, Z. Y. Weng, T. Xiang", + "year": "2008", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.0806.3719", + "url": "https://arxiv.org/abs/0806.3719" + }, + { + "id": "Vidal", + "type": "preprint", + "title": "Efficient classical simulation of slightly entangled quantum computations", + "authors": "Guifre Vidal", + "year": "2003", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0301063", + "url": "https://arxiv.org/abs/quant-ph/0301063" + }, + { + "id": "Schollwoeck", + "type": "preprint", + "title": "The density-matrix renormalization group in the age of Matrix Product States", + "authors": "Ulrich Schollwoeck", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1008.3477", + "url": "https://arxiv.org/abs/1008.3477" + } + ], + "basedOnPapers": ["10.48550/arXiv.1008.3477"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_simulate_quantum_circuits_with_tensor_networks", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_multiclass_classification/metadata.json b/demonstrations_v2/tutorial_multiclass_classification/metadata.json index 7a39583b3a..c1e75624de 100644 --- a/demonstrations_v2/tutorial_multiclass_classification/metadata.json +++ b/demonstrations_v2/tutorial_multiclass_classification/metadata.json @@ -1,37 +1,35 @@ { - "title": "Multiclass margin classifier", - "authors": [ - { - "username": "shossein" - } - ], - "dateOfPublication": "2020-04-09T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_multiclass_margin_classifier.png" - } - ], - "seoDescription": "Using PyTorch to implement a multiclass quantum variational classifier on MNIST data.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_data_reuploading_classifier", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Multiclass margin classifier", + "authors": [ + { + "username": "shossein" + } + ], + "dateOfPublication": "2020-04-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": ["Algorithms"], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_multiclass_margin_classifier.png" + } + ], + "seoDescription": "Using PyTorch to implement a multiclass quantum variational classifier on MNIST data.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_neutral_atoms/demo.py b/demonstrations_v2/tutorial_neutral_atoms/demo.py index 99cd6e16f4..3d385adf34 100644 --- a/demonstrations_v2/tutorial_neutral_atoms/demo.py +++ b/demonstrations_v2/tutorial_neutral_atoms/demo.py @@ -818,3 +818,6 @@ def neutral_atom_CZ(distance, coupling): # K. Wintersperger et al. (2023) "Neutral Atom Quantum Computing Hardware: Performance and End-User Perspective", # (`arXiv `__) # +# About the author +# ---------------- +# .. include:: ../_static/authors/alvaro_ballon.txt diff --git a/demonstrations_v2/tutorial_neutral_atoms/metadata.json b/demonstrations_v2/tutorial_neutral_atoms/metadata.json index f825dba837..b443ba3fa8 100644 --- a/demonstrations_v2/tutorial_neutral_atoms/metadata.json +++ b/demonstrations_v2/tutorial_neutral_atoms/metadata.json @@ -1,135 +1,135 @@ { - "title": "Neutral-atom quantum computers", - "authors": [ - { - "username": "alvaro" - } - ], - "dateOfPublication": "2023-05-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/neutral_atoms/thumbnail_tutorial_neutral_atoms.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_neutral_atoms.png" - } - ], - "seoDescription": "Learn how neutral atom quantum devices work using code.", - "doi": "", - "references": [ - { - "id": "Aquila2022", - "type": "webpage", - "title": "Aquila, our 256-qubit quantum processor.", - "authors": "Quera", - "year": "2023", - "journal": "", - "url": "https://www.quera.com/aquila" - }, - { - "id": "DiVincenzo2000", - "type": "article", - "title": "The Physical Implementation of Quantum Computation.", - "authors": "DiVincenzo", - "year": "2000", - "journal": "Fortschritte der Physik 48", - "url": "https://arxiv.org/abs/quant-ph/0002077" - }, - { - "id": "Tweezers1985", - "type": "article", - "title": "Observation of a single-beam gradient force optical trap for dielectric particles.", - "authors": "A. Ashkin, J. M. Dziedzic, J. E. Bjorkholm, and Steven Chu.", - "year": "1986", - "journal": "", - "url": "" - }, - { - "id": "AtomComputing", - "type": "webpage", - "title": "Quantum Computing Technology.", - "authors": "Atom Computing", - "year": "2023", - "journal": "", - "url": "https://atom-computing.com/quantum-computing-technology" - }, - { - "id": "Neutral2020", - "type": "article", - "title": "Quantum computing with neutral atoms.", - "authors": "L. Henriet, et al.", - "year": "2020", - "journal": "Quantum volume", - "url": "https://arxiv.org/abs/2006.12326" - }, - { - "id": "Pulser2022", - "type": "article", - "title": "Pulser: An open-source package for the design of pulse sequences in programmable neutral-atom arrays.", - "authors": "H. Silverio et al.", - "year": "2022", - "journal": "Quantum volume", - "url": "https://arxiv.org/abs/2104.15044" - }, - { - "id": "Morgado2011", - "type": "article", - "title": "Quantum simulation and computing with Rydberg-interacting qubits", - "authors": "M. Morgado and S. Whitlock.", - "year": "2021", - "journal": "AVS Quantum Sci.", - "url": "https://arxiv.org/abs/2011.03031" - }, - { - "id": "NeutralHardware2023", - "type": "article", - "title": "Neutral Atom Quantum Computing Hardware: Performance and End-User Perspective.", - "authors": "K. Wintersperger et al.", - "year": "2023", - "journal": "", - "url": "https://arxiv.org/abs/2304.14360" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pasqal", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "ahs_aquila", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_pulse_programming101", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_trapped_ions", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_sc_qubits", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_photonics", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Neutral-atom quantum computers", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2023-05-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/neutral_atoms/thumbnail_tutorial_neutral_atoms.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_neutral_atoms.png" + } + ], + "seoDescription": "Learn how neutral atom quantum devices work using code.", + "doi": "", + "references": [ + { + "id": "Aquila2022", + "type": "webpage", + "title": "Aquila, our 256-qubit quantum processor.", + "authors": "Quera", + "year": "2023", + "journal": "", + "url": "https://www.quera.com/aquila" + }, + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation.", + "authors": "DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik 48", + "url": "https://arxiv.org/abs/quant-ph/0002077" + }, + { + "id": "Tweezers1985", + "type": "article", + "title": "Observation of a single-beam gradient force optical trap for dielectric particles.", + "authors": "A. Ashkin, J. M. Dziedzic, J. E. Bjorkholm, and Steven Chu.", + "year": "1986", + "journal": "", + "url": "" + }, + { + "id": "AtomComputing", + "type": "webpage", + "title": "Quantum Computing Technology.", + "authors": "Atom Computing", + "year": "2023", + "journal": "", + "url": "https://atom-computing.com/quantum-computing-technology" + }, + { + "id": "Neutral2020", + "type": "article", + "title": "Quantum computing with neutral atoms.", + "authors": "L. Henriet, et al.", + "year": "2020", + "journal": "Quantum volume", + "url": "https://arxiv.org/abs/2006.12326" + }, + { + "id": "Pulser2022", + "type": "article", + "title": "Pulser: An open-source package for the design of pulse sequences in programmable neutral-atom arrays.", + "authors": "H. Silverio et al.", + "year": "2022", + "journal": "Quantum volume", + "url": "https://arxiv.org/abs/2104.15044" + }, + { + "id": "Morgado2011", + "type": "article", + "title": "Quantum simulation and computing with Rydberg-interacting qubits", + "authors": "M. Morgado and S. Whitlock.", + "year": "2021", + "journal": "AVS Quantum Sci.", + "url": "https://arxiv.org/abs/2011.03031" + }, + { + "id": "NeutralHardware2023", + "type": "article", + "title": "Neutral Atom Quantum Computing Hardware: Performance and End-User Perspective.", + "authors": "K. Wintersperger et al.", + "year": "2023", + "journal": "", + "url": "https://arxiv.org/abs/2304.14360" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_trapped_ions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_sc_qubits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py b/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py index 112259d9cb..0598b75d54 100644 --- a/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py +++ b/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py @@ -541,3 +541,6 @@ def param_shift(theta1): # `__, 2020. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/nathan_killoran.txt diff --git a/demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json b/demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json index cfa07bf03f..158d1e9594 100644 --- a/demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json +++ b/demonstrations_v2/tutorial_noisy_circuit_optimization/metadata.json @@ -1,51 +1,51 @@ { - "title": "Optimizing noisy circuits with Cirq", - "authors": [ - { - "username": "co9olguy" - } - ], - "dateOfPublication": "2020-06-01T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimizing_noisy_circuits_Cirq.png" - } - ], - "seoDescription": "Learn how noise can affect the optimization and training of quantum computations.", - "doi": "", - "references": [ - { - "id": "arute2019", - "type": "article", - "title": "Quantum supremacy using a programmable superconducting processor.", - "authors": "Frank Arute et al.", - "year": "2019", - "journal": "Nature", - "url": "" - }, - { - "id": "meyer2020", - "type": "article", - "title": "A variational toolbox for quantum multi-parameter estimation.", - "authors": "Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2006.06303" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "pytorch_noise", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Optimizing noisy circuits with Cirq", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-06-01T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimizing_noisy_circuits_Cirq.png" + } + ], + "seoDescription": "Learn how noise can affect the optimization and training of quantum computations.", + "doi": "", + "references": [ + { + "id": "arute2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor.", + "authors": "Frank Arute et al.", + "year": "2019", + "journal": "Nature", + "url": "" + }, + { + "id": "meyer2020", + "type": "article", + "title": "A variational toolbox for quantum multi-parameter estimation.", + "authors": "Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2006.06303" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_noisy_circuits/demo.py b/demonstrations_v2/tutorial_noisy_circuits/demo.py index 71fb8ab91c..a0985a3e92 100644 --- a/demonstrations_v2/tutorial_noisy_circuits/demo.py +++ b/demonstrations_v2/tutorial_noisy_circuits/demo.py @@ -295,3 +295,7 @@ def cost(x, target): # multi-parameter estimation." `arXiv:2006.06303 (2020) `__. # # +# About the author +# ---------------- +# +# .. include:: ../_static/authors/juan_miguel_arrazola.txt diff --git a/demonstrations_v2/tutorial_noisy_circuits/metadata.json b/demonstrations_v2/tutorial_noisy_circuits/metadata.json index 65f0ff746e..92522adae3 100644 --- a/demonstrations_v2/tutorial_noisy_circuits/metadata.json +++ b/demonstrations_v2/tutorial_noisy_circuits/metadata.json @@ -1,47 +1,47 @@ { - "title": "Noisy circuits", - "authors": [ - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2021-02-22T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_noisy_circuits.png" - } - ], - "seoDescription": "Learn how to simulate noisy quantum circuits", - "doi": "", - "references": [ - { - "id": "johannes", - "type": "article", - "title": "A variational toolbox for quantum multi-parameter estimation.", - "authors": "Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2006.06303" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_noisy_circuit_optimization", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "pytorch_noise", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Noisy circuits", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2021-02-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_noisy_circuits.png" + } + ], + "seoDescription": "Learn how to simulate noisy quantum circuits", + "doi": "", + "references": [ + { + "id": "johannes", + "type": "article", + "title": "A variational toolbox for quantum multi-parameter estimation.", + "authors": "Johannes Jakob Meyer, Johannes Borregaard, and Jens Eisert", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2006.06303" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_odegen/demo.py b/demonstrations_v2/tutorial_odegen/demo.py index 13640a2d52..2802df3536 100644 --- a/demonstrations_v2/tutorial_odegen/demo.py +++ b/demonstrations_v2/tutorial_odegen/demo.py @@ -367,3 +367,6 @@ def partial_step(grad_circuit, opt_state, theta): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt diff --git a/demonstrations_v2/tutorial_odegen/metadata.json b/demonstrations_v2/tutorial_odegen/metadata.json index 50546e7299..ab2eb2a794 100644 --- a/demonstrations_v2/tutorial_odegen/metadata.json +++ b/demonstrations_v2/tutorial_odegen/metadata.json @@ -1,118 +1,118 @@ { - "title": "Evaluating analytic gradients of pulse programs on quantum computers", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2023-12-12T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Optimization", - "Quantum Computing", - "Quantum Hardware" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/odegen/thumbnail_odegen.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_odegen.png" - } - ], - "seoDescription": "Learn how to compute analytic gradients of pulse programs on quantum hardware", - "doi": "", - "references": [ - { - "id": "Kottmann", - "type": "preprint", - "title": "Evaluating analytic gradients of pulse programs on quantum computers", - "authors": "Korbinian Kottmann, Nathan Killoran", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.16756", - "url": "https://arxiv.org/abs/2309.16756" - }, - { - "id": "Krantz", - "type": "article", - "title": "Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver", - "authors": "A Quantum Engineer's Guide to Superconducting Qubits", - "year": "2019", - "publisher": "", - "journal": "J. Magn. Reson. 172, 296-305", - "doi": "10.1063/1.5089550", - "url": "https://arxiv.org/abs/1904.06560" - }, - { - "id": "Mitei", - "type": "preprint", - "title": "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE", - "authors": "Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", - "year": "2020", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2008.04302", - "url": "https://arxiv.org/abs/2008.04302" - }, - { - "id": "Banchi", - "type": "article", - "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule", - "authors": "Leonardo Banchi, Gavin E. Crooks", - "year": "2020", - "publisher": "", - "journal": "Quantum", - "doi": "10.22331/q-2021-01-25-386", - "url": "https://arxiv.org/abs/2005.10299", - "preprint": "https://arxiv.org/abs/2005.10299" - }, - { - "id": "Leng", - "type": "preprint", - "title": "Differentiable Analog Quantum Computing for Optimization and Control", - "authors": "Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu", - "year": "2022", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2210.15812", - "url": "https://arxiv.org/abs/2210.15812", - "preprint": "https://arxiv.org/abs/2210.15812" - }, - { - "id": "Patterson", - "type": "preprint", - "title": "Calibration of the cross-resonance two-qubit gate between directly-coupled transmons", - "authors": "A. D. Patterson, J. Rahamim, T. Tsunoda, P. Spring, S. Jebari, K. Ratter, M. Mergenthaler, G. Tancredi, B. Vlastakis, M. Esposito, P. J. Leek", - "year": "2019", - "publisher": "", - "journal": "Phys. Rev. Applied 12, 064013", - "doi": "10.1103/PhysRevApplied.12.064013", - "url": "https://doi.org/10.1103/PhysRevApplied.12.064013", - "preprint": "https://arxiv.org/abs/1905.05670" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pulse_programming101", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "oqc_pulse", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "ahs_aquila", - "weight": 0.5 - } - ] -} \ No newline at end of file + "title": "Evaluating analytic gradients of pulse programs on quantum computers", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-12-12T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Computing", + "Quantum Hardware" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/odegen/thumbnail_odegen.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_odegen.png" + } + ], + "seoDescription": "Learn how to compute analytic gradients of pulse programs on quantum hardware", + "doi": "", + "references": [ + { + "id": "Kottmann", + "type": "preprint", + "title": "Evaluating analytic gradients of pulse programs on quantum computers", + "authors": "Korbinian Kottmann, Nathan Killoran", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.16756", + "url": "https://arxiv.org/abs/2309.16756" + }, + { + "id": "Krantz", + "type": "article", + "title": "Philip Krantz, Morten Kjaergaard, Fei Yan, Terry P. Orlando, Simon Gustavsson, William D. Oliver", + "authors": "A Quantum Engineer's Guide to Superconducting Qubits", + "year": "2019", + "publisher": "", + "journal": "J. Magn. Reson. 172, 296-305", + "doi": "10.1063/1.5089550", + "url": "https://arxiv.org/abs/1904.06560" + }, + { + "id": "Mitei", + "type": "preprint", + "title": "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE", + "authors": "Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2020", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2008.04302", + "url": "https://arxiv.org/abs/2008.04302" + }, + { + "id": "Banchi", + "type": "article", + "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule", + "authors": "Leonardo Banchi, Gavin E. Crooks", + "year": "2020", + "publisher": "", + "journal": "Quantum", + "doi": "10.22331/q-2021-01-25-386", + "url": "https://arxiv.org/abs/2005.10299", + "preprint": "https://arxiv.org/abs/2005.10299" + }, + { + "id": "Leng", + "type": "preprint", + "title": "Differentiable Analog Quantum Computing for Optimization and Control", + "authors": "Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2210.15812", + "url": "https://arxiv.org/abs/2210.15812", + "preprint": "https://arxiv.org/abs/2210.15812" + }, + { + "id": "Patterson", + "type": "preprint", + "title": "Calibration of the cross-resonance two-qubit gate between directly-coupled transmons", + "authors": "A. D. Patterson, J. Rahamim, T. Tsunoda, P. Spring, S. Jebari, K. Ratter, M. Mergenthaler, G. Tancredi, B. Vlastakis, M. Esposito, P. J. Leek", + "year": "2019", + "publisher": "", + "journal": "Phys. Rev. Applied 12, 064013", + "doi": "10.1103/PhysRevApplied.12.064013", + "url": "https://doi.org/10.1103/PhysRevApplied.12.064013", + "preprint": "https://arxiv.org/abs/1905.05670" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "oqc_pulse", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 0.5 + } + ] +} diff --git a/demonstrations_v2/tutorial_optimal_control/demo.py b/demonstrations_v2/tutorial_optimal_control/demo.py index 1bcd01d8b8..2d1ac0d8bb 100644 --- a/demonstrations_v2/tutorial_optimal_control/demo.py +++ b/demonstrations_v2/tutorial_optimal_control/demo.py @@ -841,3 +841,6 @@ def node(params): # `arxiv:1003.3750 `__, 2011 # # +# About the author +# ---------------- +# .. include:: ../_static/authors/david_wierichs.txt diff --git a/demonstrations_v2/tutorial_optimal_control/metadata.json b/demonstrations_v2/tutorial_optimal_control/metadata.json index 463d39ff84..2e80b76d12 100644 --- a/demonstrations_v2/tutorial_optimal_control/metadata.json +++ b/demonstrations_v2/tutorial_optimal_control/metadata.json @@ -1,95 +1,95 @@ { - "title": "Optimal control for gate compilation", - "authors": [ - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2023-08-08T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization", - "Quantum Computing", - "Quantum Hardware" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/optimal_control/thumbnail_tutorial_optimal_control.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_optimal_control.png" - } - ], - "seoDescription": "Learn how to optimize pulse programs to obtain digital gates", - "doi": "", - "references": [ - { - "id": "KingmaBa14", - "type": "preprint", - "title": "Adam: A method for Stochastic Optimization", - "authors": "D. Kingma and J. Ba", - "year": "2014", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2303.11355", - "url": "https://arxiv.org/abs/2303.11355" - }, - { - "id": "KhanejaReiss05", - "type": "article", - "title": "Optimal Control of Coupled Spin Dynamics: Design of NMR Pulse Sequences by Gradient Ascent Algorithms", - "authors": "N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbr\u00fcggen and S.J. Glaser", - "year": "2005", - "publisher": "", - "journal": "J. Magn. Reson. 172, 296-305", - "doi": "10.1016/j.jmr.2004.11.004", - "url": "https://www.ch.nat.tum.de/fileadmin/w00bzu/ocnmr/pdf/94_GRAPE_JMR_05_.pdf" - }, - { - "id": "CanevaMurphy09", - "type": "article", - "title": "Optimal Control at the Quantum Speed Limit", - "authors": "T. Caneva, M. Murphy, T. Calarco, R. Fazio, S. Montangero, V. Giovannetti and G. Santoro", - "year": "2009", - "publisher": "", - "journal": "Phys. Rev. Lett. 103, 240501", - "doi": "10.1103/PhysRevLett.103.240501", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.103.240501", - "preprint": "https://arxiv.org/abs/0902.4193" - }, - { - "id": "DoriaCalarco11", - "type": "article", - "title": "Optimal Control Technique for Many-Body Quantum Dynamics", - "authors": "P. Doria, T. Calarco and S. Montangero", - "year": "2011", - "publisher": "", - "journal": "Phys. Rev. Lett. 106, 190501", - "doi": "10.1103/PhysRevLett.106.190501", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.106.190501", - "preprint": "https://arxiv.org/abs/1003.3750" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pulse_programming101", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_neutral_atoms", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "ahs_aquila", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Optimal control for gate compilation", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2023-08-08T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization", + "Quantum Computing", + "Quantum Hardware" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/optimal_control/thumbnail_tutorial_optimal_control.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_optimal_control.png" + } + ], + "seoDescription": "Learn how to optimize pulse programs to obtain digital gates", + "doi": "", + "references": [ + { + "id": "KingmaBa14", + "type": "preprint", + "title": "Adam: A method for Stochastic Optimization", + "authors": "D. Kingma and J. Ba", + "year": "2014", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2303.11355", + "url": "https://arxiv.org/abs/2303.11355" + }, + { + "id": "KhanejaReiss05", + "type": "article", + "title": "Optimal Control of Coupled Spin Dynamics: Design of NMR Pulse Sequences by Gradient Ascent Algorithms", + "authors": "N. Khaneja, T. Reiss, C. Kehlet, T. Schulte-Herbrüggen and S.J. Glaser", + "year": "2005", + "publisher": "", + "journal": "J. Magn. Reson. 172, 296-305", + "doi": "10.1016/j.jmr.2004.11.004", + "url": "https://www.ch.nat.tum.de/fileadmin/w00bzu/ocnmr/pdf/94_GRAPE_JMR_05_.pdf" + }, + { + "id": "CanevaMurphy09", + "type": "article", + "title": "Optimal Control at the Quantum Speed Limit", + "authors": "T. Caneva, M. Murphy, T. Calarco, R. Fazio, S. Montangero, V. Giovannetti and G. Santoro", + "year": "2009", + "publisher": "", + "journal": "Phys. Rev. Lett. 103, 240501", + "doi": "10.1103/PhysRevLett.103.240501", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.103.240501", + "preprint": "https://arxiv.org/abs/0902.4193" + }, + { + "id": "DoriaCalarco11", + "type": "article", + "title": "Optimal Control Technique for Many-Body Quantum Dynamics", + "authors": "P. Doria, T. Calarco and S. Montangero", + "year": "2011", + "publisher": "", + "journal": "Phys. Rev. Lett. 106, 190501", + "doi": "10.1103/PhysRevLett.106.190501", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.106.190501", + "preprint": "https://arxiv.org/abs/1003.3750" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_neutral_atoms", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ahs_aquila", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_pasqal/demo.py b/demonstrations_v2/tutorial_pasqal/demo.py index aa86406724..0f35239b27 100644 --- a/demonstrations_v2/tutorial_pasqal/demo.py +++ b/demonstrations_v2/tutorial_pasqal/demo.py @@ -357,3 +357,6 @@ def cost(): # `__, 2017. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/nathan_killoran.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_pasqal/metadata.json b/demonstrations_v2/tutorial_pasqal/metadata.json index 5f80463445..f5decd0ac3 100644 --- a/demonstrations_v2/tutorial_pasqal/metadata.json +++ b/demonstrations_v2/tutorial_pasqal/metadata.json @@ -1,37 +1,37 @@ { - "title": "Quantum computation with neutral atoms", - "authors": [ - { - "username": "co9olguy" - } - ], - "dateOfPublication": "2020-10-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QC_neutral_atoms.png" - } - ], - "seoDescription": "Neutral atom quantum devices allow you to place qubits within interesting three-dimensional configurations.", - "doi": "", - "references": [ - { - "id": "barredo2017", - "type": "article", - "title": "Synthetic three-dimensional atomic structures assembled atom by atom.", - "authors": "Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys", - "year": "2017", - "journal": "", - "url": "https://arxiv.org/abs/1712.02727" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Quantum computation with neutral atoms", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-10-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QC_neutral_atoms.png" + } + ], + "seoDescription": "Neutral atom quantum devices allow you to place qubits within interesting three-dimensional configurations.", + "doi": "", + "references": [ + { + "id": "barredo2017", + "type": "article", + "title": "Synthetic three-dimensional atomic structures assembled atom by atom.", + "authors": "Daniel Barredo, Vincent Lienhard, Sylvain de Leseleuc, Thierry Lahaye, and Antoine Browaeys", + "year": "2017", + "journal": "", + "url": "https://arxiv.org/abs/1712.02727" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_phase_kickback/demo.py b/demonstrations_v2/tutorial_phase_kickback/demo.py index ec0781ff61..4b530854ba 100644 --- a/demonstrations_v2/tutorial_phase_kickback/demo.py +++ b/demonstrations_v2/tutorial_phase_kickback/demo.py @@ -181,3 +181,6 @@ def check_key(lock, key): # incredible quantum lock-picking skills! # # +# About the author +# ---------------- +# .. include:: ../_static/authors/danial_motlagh.txt diff --git a/demonstrations_v2/tutorial_phase_kickback/metadata.json b/demonstrations_v2/tutorial_phase_kickback/metadata.json index b179cfb4ee..063bb9e26a 100644 --- a/demonstrations_v2/tutorial_phase_kickback/metadata.json +++ b/demonstrations_v2/tutorial_phase_kickback/metadata.json @@ -1,38 +1,38 @@ { - "title": "Building a quantum lock using phase kickback", - "authors": [ - { - "username": "Dan" - } - ], - "dateOfPublication": "2023-08-01T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started", - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/phase_kickback/thumbnail_tutorial_phase_kickback.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_phase_kickback.png" - } - ], - "seoDescription": "Use phase kickback to create an unbreakable quantum lock", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qubit_rotation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Building a quantum lock using phase kickback", + "authors": [ + { + "username": "Dan" + } + ], + "dateOfPublication": "2023-08-01T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/phase_kickback/thumbnail_tutorial_phase_kickback.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_phase_kickback.png" + } + ], + "seoDescription": "Use phase kickback to create an unbreakable quantum lock", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_photonics/demo.py b/demonstrations_v2/tutorial_photonics/demo.py index 1a0cd13aea..a379a489aa 100644 --- a/demonstrations_v2/tutorial_photonics/demo.py +++ b/demonstrations_v2/tutorial_photonics/demo.py @@ -961,3 +961,6 @@ def measurement2_1(a, theta, alpha, phi): # `__. # (`arXiv `__) # +# About the author +# ~~~~~~~~~~~~~~~~ +# .. include:: ../_static/authors/alvaro_ballon.txt diff --git a/demonstrations_v2/tutorial_photonics/metadata.json b/demonstrations_v2/tutorial_photonics/metadata.json index 97be9e976b..56d9a92c12 100644 --- a/demonstrations_v2/tutorial_photonics/metadata.json +++ b/demonstrations_v2/tutorial_photonics/metadata.json @@ -1,142 +1,142 @@ { - "title": "Photonic quantum computers", - "authors": [ - { - "username": "alvaro" - } - ], - "dateOfPublication": "2022-05-31T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_photonic_QC.png" - } - ], - "seoDescription": "Learn how photonic quantum computers work through code", - "doi": "", - "references": [ - { - "id": "DiVincenzo2000", - "type": "article", - "title": "The Physical Implementation of Quantum Computation", - "authors": "D. DiVincenzo", - "year": "2000", - "journal": "Fortschritte der Physik", - "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" - }, - { - "id": "Weedbrook2012", - "type": "article", - "title": "Gaussian Quantum Information", - "authors": "C. Weedbrook, et al.", - "year": "2012", - "journal": "Rev. Mod. Phys.", - "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.84.621" - }, - { - "id": "Sabouri2021", - "type": "article", - "title": "Thermo Optical Phase Shifter With Low Thermal Crosstalk for SOI Strip Waveguide", - "authors": "S. Sabouri, et al.", - "year": "2021", - "journal": "IEEE Photonics Journal", - "volume": "13", - "number": "2", - "url": "https://ieeexplore.ieee.org/document/9345963" - }, - { - "id": "Paris1996", - "type": "article", - "title": "Displacement operator by beam splitter", - "authors": "M. Paris", - "year": "1996", - "journal": "Physics Letters A", - "url": "https://www.sciencedirect.com/science/article/abs/pii/0375960196003398?via%3Dihub" - }, - { - "id": "Braunstein2005", - "type": "article", - "title": "Quantum information with continuous variables", - "authors": "S. Braunstein, P. van Loock", - "year": "2005", - "journal": "Rev. Mod. Phys.", - "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.77.513" - }, - { - "id": "Hamilton2017", - "type": "article", - "title": "Gaussian Boson Sampling", - "authors": "C. Hamilton, et al.", - "year": "2017", - "journal": "Phys. Rev. Lett.", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.170501" - }, - { - "id": "Zhong2020", - "type": "article", - "title": "Quantum computational advantage using photons", - "authors": "H.S. Zhong, et al.", - "year": "2020", - "journal": "Science", - "url": "https://www.science.org/doi/10.1126/science.abe8770" - }, - { - "id": "Madsen2020", - "type": "article", - "title": "Quantum computational advantage with a programmable photonic processor", - "authors": "L. Madsen, et al.", - "year": "2022", - "journal": "Nature", - "url": "https://www.nature.com/articles/s41586-022-04725-x" - }, - { - "id": "Tzitrin2020", - "type": "article", - "title": "Progress towards practical qubit computation using approximate Gottesman-Kitaev-Preskill codes", - "authors": "I. Tzitrin, et al.", - "year": "2020", - "journal": "Phys. Rev. A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.64.012310" - }, - { - "id": "Bourassa2021", - "type": "article", - "title": "Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer", - "authors": "E. Bourassa, et al.", - "year": "2021", - "journal": "Quantum", - "url": "https://quantum-journal.org/papers/q-2021-02-04-392/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pasqal", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_trapped_ions", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_sc_qubits", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "gbs", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/photonic-quantum-computers-demo/7335" -} \ No newline at end of file + "title": "Photonic quantum computers", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2022-05-31T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_photonic_QC.png" + } + ], + "seoDescription": "Learn how photonic quantum computers work through code", + "doi": "", + "references": [ + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "D. DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" + }, + { + "id": "Weedbrook2012", + "type": "article", + "title": "Gaussian Quantum Information", + "authors": "C. Weedbrook, et al.", + "year": "2012", + "journal": "Rev. Mod. Phys.", + "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.84.621" + }, + { + "id": "Sabouri2021", + "type": "article", + "title": "Thermo Optical Phase Shifter With Low Thermal Crosstalk for SOI Strip Waveguide", + "authors": "S. Sabouri, et al.", + "year": "2021", + "journal": "IEEE Photonics Journal", + "volume": "13", + "number": "2", + "url": "https://ieeexplore.ieee.org/document/9345963" + }, + { + "id": "Paris1996", + "type": "article", + "title": "Displacement operator by beam splitter", + "authors": "M. Paris", + "year": "1996", + "journal": "Physics Letters A", + "url": "https://www.sciencedirect.com/science/article/abs/pii/0375960196003398?via%3Dihub" + }, + { + "id": "Braunstein2005", + "type": "article", + "title": "Quantum information with continuous variables", + "authors": "S. Braunstein, P. van Loock", + "year": "2005", + "journal": "Rev. Mod. Phys.", + "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.77.513" + }, + { + "id": "Hamilton2017", + "type": "article", + "title": "Gaussian Boson Sampling", + "authors": "C. Hamilton, et al.", + "year": "2017", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.170501" + }, + { + "id": "Zhong2020", + "type": "article", + "title": "Quantum computational advantage using photons", + "authors": "H.S. Zhong, et al.", + "year": "2020", + "journal": "Science", + "url": "https://www.science.org/doi/10.1126/science.abe8770" + }, + { + "id": "Madsen2020", + "type": "article", + "title": "Quantum computational advantage with a programmable photonic processor", + "authors": "L. Madsen, et al.", + "year": "2022", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-022-04725-x" + }, + { + "id": "Tzitrin2020", + "type": "article", + "title": "Progress towards practical qubit computation using approximate Gottesman-Kitaev-Preskill codes", + "authors": "I. Tzitrin, et al.", + "year": "2020", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.64.012310" + }, + { + "id": "Bourassa2021", + "type": "article", + "title": "Blueprint for a Scalable Photonic Fault-Tolerant Quantum Computer", + "authors": "E. Bourassa, et al.", + "year": "2021", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-02-04-392/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_trapped_ions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_sc_qubits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "gbs", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/photonic-quantum-computers-demo/7335" +} diff --git a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py index 18e329177d..0d72abcda0 100644 --- a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py +++ b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/demo.py @@ -813,3 +813,6 @@ def circuit(features, params): # ############################################################################## +# About the authors +# --------------------- +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json index 99d9d748f8..ef4b4e6a61 100644 --- a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json +++ b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/metadata.json @@ -1,136 +1,134 @@ { - "title": "Post Variational Quantum Neural Networks", - "authors": [ - { - "username": "elainazhu" - }, - { - "username": "georgepwhuang" - } - ], - "dateOfPublication": "2024-10-07T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_post-variational_quantum_neural_networks.png" - }, - { - "type": "large_thumbnail", - "uri": "_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_post-variational_quantum_neural_networks.png" - } - ], - "seoDescription": "Learn about post-variational quantum neural networks", - "doi": "", - "references": [ - { - "id": "cerezo2021variational", - "type": "article", - "title": "Variational quantum algorithms", - "authors": "Marco Cerezo and Andrew Arrasmith and Ryan Babbush and Simon C. Benjamin and Suguru Endo and Keisuke Fujii and Jarrod R. McClean and Kosuke Mitarai and Xiao Yuan and Lukasz Cincio and Patrick J. Coles", - "year": "2021", - "publisher": "Nature Portfolio", - "journal": "Nat. Rev. Phys.", - "doi": "10.1038/s42254-021-00348-9", - "url": "https://doi.org/10.1038/s42254-021-00348-9" - }, - { - "id": "mcclean2018barren", - "type": "article", - "title": "Barren plateaus in quantum neural network training landscapes", - "authors": "Jarrod R. McClean and Sergio Boixo and Vadim N. Smelyanskiy and Ryan Babbush and Hartmut Neven", - "year": "2018", - "publisher": "Nature Portfolio", - "journal": "Nat. Commun.", - "doi": "10.1038/s41467-018-07090-4", - "url": "https://doi.org/10.1038/s41467-018-07090-4" - }, - { - "id": "du2020expressive", - "type": "article", - "title": "Expressive power of parametrized quantum circuits", - "authors": "Du, Yuxuan and Hsieh, Min-Hsiu and Liu, Tongliang and Tao, Dacheng", - "year": "2020", - "publisher": "American Physical Society", - "journal": "Phys. Rev. Res.", - "doi": "10.1103/PhysRevResearch.2.033125", - "url": "https://doi.org/10.1103/PhysRevResearch.2.033125" - }, - { - "id": "schuld2019evaluating", - "type": "article", - "title": "Evaluating analytic gradients on quantum hardware", - "authors": "Schuld, Maria and Bergholm, Ville and Gogolin, Christian and Izaac, Josh and Killoran, Nathan", - "year": "2019", - "publisher": "American Physical Society", - "journal": "Phys. Rev. A", - "doi": "10.1103/PhysRevA.99.032331", - "url": "https://doi.org/10.1103/PhysRevA.99.032331" - }, - { - "id": "huang2024postvariational", - "type": "article", - "title": "Post-variational quantum neural networks", - "authors": "Po-Wei Huang and Patrick Rebentrost", - "year": "2024", - "doi": "10.48550/arXiv.2307.10560", - "url": "https://arxiv.org/abs/2307.10560" - }, - { - "id": "huang2020predicting", - "type": "article", - "title": "Predicting many properties of a quantum system from very few measurements", - "authors": "Hsin-Yuan Huang and Richard Kueng and John Preskill", - "year": "2024", - "doi": "10.1038/s41567-020-0932-7", - "url": "https://doi.org/10.1038/s41567-020-0932-7" - } - ], - "basedOnPapers": [ - "https://arxiv.org/abs/2307.10560" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_local_cost_functions", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_jax_transformations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_classical_shadows", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_diffable_shadows", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "ensemble_multi_qpu", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Post Variational Quantum Neural Networks", + "authors": [ + { + "username": "elainazhu" + }, + { + "username": "georgepwhuang" + } + ], + "dateOfPublication": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_post-variational_quantum_neural_networks.png" + }, + { + "type": "large_thumbnail", + "uri": "_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_post-variational_quantum_neural_networks.png" + } + ], + "seoDescription": "Learn about post-variational quantum neural networks", + "doi": "", + "references": [ + { + "id": "cerezo2021variational", + "type": "article", + "title": "Variational quantum algorithms", + "authors": "Marco Cerezo and Andrew Arrasmith and Ryan Babbush and Simon C. Benjamin and Suguru Endo and Keisuke Fujii and Jarrod R. McClean and Kosuke Mitarai and Xiao Yuan and Lukasz Cincio and Patrick J. Coles", + "year": "2021", + "publisher": "Nature Portfolio", + "journal": "Nat. Rev. Phys.", + "doi": "10.1038/s42254-021-00348-9", + "url": "https://doi.org/10.1038/s42254-021-00348-9" + }, + { + "id": "mcclean2018barren", + "type": "article", + "title": "Barren plateaus in quantum neural network training landscapes", + "authors": "Jarrod R. McClean and Sergio Boixo and Vadim N. Smelyanskiy and Ryan Babbush and Hartmut Neven", + "year": "2018", + "publisher": "Nature Portfolio", + "journal": "Nat. Commun.", + "doi": "10.1038/s41467-018-07090-4", + "url": "https://doi.org/10.1038/s41467-018-07090-4" + }, + { + "id": "du2020expressive", + "type": "article", + "title": "Expressive power of parametrized quantum circuits", + "authors": "Du, Yuxuan and Hsieh, Min-Hsiu and Liu, Tongliang and Tao, Dacheng", + "year": "2020", + "publisher": "American Physical Society", + "journal": "Phys. Rev. Res.", + "doi": "10.1103/PhysRevResearch.2.033125", + "url": "https://doi.org/10.1103/PhysRevResearch.2.033125" + }, + { + "id": "schuld2019evaluating", + "type": "article", + "title": "Evaluating analytic gradients on quantum hardware", + "authors": "Schuld, Maria and Bergholm, Ville and Gogolin, Christian and Izaac, Josh and Killoran, Nathan", + "year": "2019", + "publisher": "American Physical Society", + "journal": "Phys. Rev. A", + "doi": "10.1103/PhysRevA.99.032331", + "url": "https://doi.org/10.1103/PhysRevA.99.032331" + }, + { + "id": "huang2024postvariational", + "type": "article", + "title": "Post-variational quantum neural networks", + "authors": "Po-Wei Huang and Patrick Rebentrost", + "year": "2024", + "doi": "10.48550/arXiv.2307.10560", + "url": "https://arxiv.org/abs/2307.10560" + }, + { + "id": "huang2020predicting", + "type": "article", + "title": "Predicting many properties of a quantum system from very few measurements", + "authors": "Hsin-Yuan Huang and Richard Kueng and John Preskill", + "year": "2024", + "doi": "10.1038/s41567-020-0932-7", + "url": "https://doi.org/10.1038/s41567-020-0932-7" + } + ], + "basedOnPapers": ["https://arxiv.org/abs/2307.10560"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_local_cost_functions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_jax_transformations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_classical_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable_shadows", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ensemble_multi_qpu", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_pulse_programming101/demo.py b/demonstrations_v2/tutorial_pulse_programming101/demo.py index 54bfe10d58..7b6a39c942 100644 --- a/demonstrations_v2/tutorial_pulse_programming101/demo.py +++ b/demonstrations_v2/tutorial_pulse_programming101/demo.py @@ -482,3 +482,6 @@ def qnode(theta, t=duration): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/korbinian_kottmann.txt diff --git a/demonstrations_v2/tutorial_pulse_programming101/metadata.json b/demonstrations_v2/tutorial_pulse_programming101/metadata.json index 3be2908e82..7801031d1f 100644 --- a/demonstrations_v2/tutorial_pulse_programming101/metadata.json +++ b/demonstrations_v2/tutorial_pulse_programming101/metadata.json @@ -1,77 +1,77 @@ { - "title": "Differentiable pulse programming with qubits in PennyLane", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2023-03-08T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/pulse_programming101/thumbnail_tutorial_pulse_programming.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_pulse_programming.png" - } - ], - "seoDescription": "Simulating differentialble pulse programs in PennyLane with qubits", - "doi": "", - "references": [ - { - "id": "Mitei", - "type": "article", - "title": "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE", - "authors": "Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", - "year": "2020", - "publisher": "", - "url": "https://arxiv.org/abs/2008.04302" - }, - { - "id": "Sheldon2016", - "type": "article", - "title": "Procedure for systematically tuning up crosstalk in the cross resonance gate", - "authors": "Sarah Sheldon, Easwar Magesan, Jerry M. Chow, Jay M. Gambetta", - "year": "2016", - "publisher": "", - "url": "https://arxiv.org/abs/1603.04821" - }, - { - "id": "Leng2022", - "type": "article", - "title": "Differentiable Analog Quantum Computing for Optimization and Control", - "authors": "Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu", - "year": "2022", - "publisher": "", - "url": "https://arxiv.org/abs/2210.15812" - }, - { - "id": "Asthana2022", - "type": "article", - "title": "Minimizing state preparation times in pulse-level variational molecular simulations", - "authors": "Ayush Asthana, Chenxu Liu, Oinam Romesh Meitei, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", - "year": "2022", - "publisher": "", - "url": "https://arxiv.org/abs/2203.06818" - }, - { - "id": "Russell2016", - "type": "article", - "title": "Quantum Control Landscapes Are Almost Always Trap Free", - "authors": "Benjamin Russell, Herschel Rabitz, Rebing Wu", - "year": "2016", - "publisher": "", - "url": "https://arxiv.org/abs/1608.06198" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Differentiable pulse programming with qubits in PennyLane", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2023-03-08T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/pulse_programming101/thumbnail_tutorial_pulse_programming.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_pulse_programming.png" + } + ], + "seoDescription": "Simulating differentialble pulse programs in PennyLane with qubits", + "doi": "", + "references": [ + { + "id": "Mitei", + "type": "article", + "title": "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE", + "authors": "Oinam Romesh Meitei, Bryan T. Gard, George S. Barron, David P. Pappas, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2020", + "publisher": "", + "url": "https://arxiv.org/abs/2008.04302" + }, + { + "id": "Sheldon2016", + "type": "article", + "title": "Procedure for systematically tuning up crosstalk in the cross resonance gate", + "authors": "Sarah Sheldon, Easwar Magesan, Jerry M. Chow, Jay M. Gambetta", + "year": "2016", + "publisher": "", + "url": "https://arxiv.org/abs/1603.04821" + }, + { + "id": "Leng2022", + "type": "article", + "title": "Differentiable Analog Quantum Computing for Optimization and Control", + "authors": "Jiaqi Leng, Yuxiang Peng, Yi-Ling Qiao, Ming Lin, Xiaodi Wu", + "year": "2022", + "publisher": "", + "url": "https://arxiv.org/abs/2210.15812" + }, + { + "id": "Asthana2022", + "type": "article", + "title": "Minimizing state preparation times in pulse-level variational molecular simulations", + "authors": "Ayush Asthana, Chenxu Liu, Oinam Romesh Meitei, Sophia E. Economou, Edwin Barnes, Nicholas J. Mayhall", + "year": "2022", + "publisher": "", + "url": "https://arxiv.org/abs/2203.06818" + }, + { + "id": "Russell2016", + "type": "article", + "title": "Quantum Control Landscapes Are Almost Always Trap Free", + "authors": "Benjamin Russell, Herschel Rabitz, Rebing Wu", + "year": "2016", + "publisher": "", + "url": "https://arxiv.org/abs/1608.06198" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_qaoa_intro/demo.py b/demonstrations_v2/tutorial_qaoa_intro/demo.py index 1224ea5f14..36dc01a337 100644 --- a/demonstrations_v2/tutorial_qaoa_intro/demo.py +++ b/demonstrations_v2/tutorial_qaoa_intro/demo.py @@ -511,3 +511,6 @@ def probability_circuit(gamma, alpha): # :width: 90% # # +# About the author +# ---------------- +# .. include:: ../_static/authors/jack_ceroni.txt diff --git a/demonstrations_v2/tutorial_qaoa_intro/metadata.json b/demonstrations_v2/tutorial_qaoa_intro/metadata.json index fa8e69f0cc..89b69fa6d9 100644 --- a/demonstrations_v2/tutorial_qaoa_intro/metadata.json +++ b/demonstrations_v2/tutorial_qaoa_intro/metadata.json @@ -1,33 +1,33 @@ { - "title": "Intro to QAOA", - "authors": [ - { - "username": "jceroni" - } - ], - "dateOfPublication": "2020-11-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_intro_to_QAOA.png" - } - ], - "seoDescription": "Learn how to implement QAOA with PennyLane", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qaoa_maxcut", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/qaoa-and-optimization/5188" -} \ No newline at end of file + "title": "Intro to QAOA", + "authors": [ + { + "username": "jceroni" + } + ], + "dateOfPublication": "2020-11-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_intro_to_QAOA.png" + } + ], + "seoDescription": "Learn how to implement QAOA with PennyLane", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/qaoa-and-optimization/5188" +} diff --git a/demonstrations_v2/tutorial_qaoa_maxcut/demo.py b/demonstrations_v2/tutorial_qaoa_maxcut/demo.py index ab2d32087b..69f7eab8c0 100644 --- a/demonstrations_v2/tutorial_qaoa_maxcut/demo.py +++ b/demonstrations_v2/tutorial_qaoa_maxcut/demo.py @@ -294,3 +294,6 @@ def qaoa_maxcut(n_layers=1): plt.show() ############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qaoa_maxcut/metadata.json b/demonstrations_v2/tutorial_qaoa_maxcut/metadata.json index 1758c95f27..9d8d00e6de 100644 --- a/demonstrations_v2/tutorial_qaoa_maxcut/metadata.json +++ b/demonstrations_v2/tutorial_qaoa_maxcut/metadata.json @@ -1,35 +1,35 @@ { - "title": "QAOA for MaxCut", - "authors": [ - { - "username": "alowe" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-11-20T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QAOA_for_MaxCut.png" - } - ], - "seoDescription": "Implementing the quantum approximate optimization algorithm using PennyLane to solve the MaxCut problem.", - "doi": "", - "references": [], - "basedOnPapers": [ - "10.48550/arXiv.1411.4028" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qaoa_intro", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/qaoa-and-optimization/5188" -} \ No newline at end of file + "title": "QAOA for MaxCut", + "authors": [ + { + "username": "alowe" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-11-20T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QAOA_for_MaxCut.png" + } + ], + "seoDescription": "Implementing the quantum approximate optimization algorithm using PennyLane to solve the MaxCut problem.", + "doi": "", + "references": [], + "basedOnPapers": [ + "10.48550/arXiv.1411.4028" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/qaoa-and-optimization/5188" +} diff --git a/demonstrations_v2/tutorial_qcbm/demo.py b/demonstrations_v2/tutorial_qcbm/demo.py index c164010005..524e1ab866 100644 --- a/demonstrations_v2/tutorial_qcbm/demo.py +++ b/demonstrations_v2/tutorial_qcbm/demo.py @@ -574,3 +574,5 @@ def circuit(weights): # ###################################################################### +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_qcbm/metadata.json b/demonstrations_v2/tutorial_qcbm/metadata.json index 32d8c24d84..ca14426ecb 100644 --- a/demonstrations_v2/tutorial_qcbm/metadata.json +++ b/demonstrations_v2/tutorial_qcbm/metadata.json @@ -1,98 +1,94 @@ { - "title": "Quantum Circuit Born Machines", - "authors": [ - { - "username": "grdahale" - } - ], - "dateOfPublication": "2024-05-22T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/qcbm/thumbnail_QuantumCircuitBornMachines_2024-05-13.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuantumCircuitBornMachines_2024-05-13.png" - } - ], - "seoDescription": "Learn how to use the Quantum Circuit Born Machines (QCBMs).", - "doi": "", - "references": [ - { - "id": "Liu", - "type": "article", - "title": "Differentiable learning of quantum circuit Born machines", - "authors": "Liu, Jin-Guo and Wang, Lei", - "year": "2018", - "publisher": "American Physical Society", - "journal": "Phys. Rev. A", - "doi": "10.1103/PhysRevA.98.062324", - "url": "https://link.aps.org/doi/10.1103/PhysRevA.98.062324" - }, - { - "id": "Ben", - "type": "article", - "title": "A generative modeling approach for benchmarking and training shallow quantum circuits", - "authors": "Benedetti, Marcello and Garcia-Pintos, Delfina and Nam, Yunseong and Perdomo-Ortiz, Alejandro", - "year": "2018", - "journal": "npj Quantum Information", - "doi": "10.1038/s41534-019-0157-8" - }, - { - "id": "Du", - "type": "article", - "title": "Expressive power of parametrized quantum circuits", - "authors": "Du, Yuxuan and Hsieh, Min-Hsiu and Liu, Tongliang and Tao, Dacheng", - "year": "2020", - "publisher": "American Physical Society", - "journal": "Phys. Rev. Res.", - "doi": "10.1103/PhysRevResearch.2.033125", - "url": "https://link.aps.org/doi/10.1103/PhysRevResearch.2.033125" - }, - { - "id": "Ack", - "type": "article", - "title": "A learning algorithm for boltzmann machines", - "authors": "David H. Ackley and Geoffrey E. Hinton and Terrence J. Sejnowski", - "year": "1985", - "journal": "Cognitive Science", - "doi": "10.1016/S0364-0213(85)80012-4", - "url": "https://www.sciencedirect.com/science/article/pii/S0364021385800124" - }, - { - "id": "Gret", - "type": "article", - "title": "A Kernel Method for the Two-Sample-Problem", - "authors": "Gretton, Arthur and Borgwardt, Karsten and Rasch, Malte and Sch\u00f6lkopf, Bernhard and Smola, Alexander", - "year": "2006", - "journal": "Advances in neural information processing systems" - }, - { - "id": "Kull", - "type": "article", - "title": "On Information and Sufficiency", - "authors": "Solomon Kullback and R. A. Leibler", - "year": "1951", - "journal": "Annals of Mathematical Statistics", - "url": "https://api.semanticscholar.org/CorpusID:120349231" - } - ], - "basedOnPapers": [ - "10.1103/PhysRevA.98.062324" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_kernels_module", - "weight": 1.0 - } - ], - "hardware": [] -} \ No newline at end of file + "title": "Quantum Circuit Born Machines", + "authors": [ + { + "username": "grdahale" + } + ], + "dateOfPublication": "2024-05-22T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": ["Quantum Machine Learning"], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qcbm/thumbnail_QuantumCircuitBornMachines_2024-05-13.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuantumCircuitBornMachines_2024-05-13.png" + } + ], + "seoDescription": "Learn how to use the Quantum Circuit Born Machines (QCBMs).", + "doi": "", + "references": [ + { + "id": "Liu", + "type": "article", + "title": "Differentiable learning of quantum circuit Born machines", + "authors": "Liu, Jin-Guo and Wang, Lei", + "year": "2018", + "publisher": "American Physical Society", + "journal": "Phys. Rev. A", + "doi": "10.1103/PhysRevA.98.062324", + "url": "https://link.aps.org/doi/10.1103/PhysRevA.98.062324" + }, + { + "id": "Ben", + "type": "article", + "title": "A generative modeling approach for benchmarking and training shallow quantum circuits", + "authors": "Benedetti, Marcello and Garcia-Pintos, Delfina and Nam, Yunseong and Perdomo-Ortiz, Alejandro", + "year": "2018", + "journal": "npj Quantum Information", + "doi": "10.1038/s41534-019-0157-8" + }, + { + "id": "Du", + "type": "article", + "title": "Expressive power of parametrized quantum circuits", + "authors": "Du, Yuxuan and Hsieh, Min-Hsiu and Liu, Tongliang and Tao, Dacheng", + "year": "2020", + "publisher": "American Physical Society", + "journal": "Phys. Rev. Res.", + "doi": "10.1103/PhysRevResearch.2.033125", + "url": "https://link.aps.org/doi/10.1103/PhysRevResearch.2.033125" + }, + { + "id": "Ack", + "type": "article", + "title": "A learning algorithm for boltzmann machines", + "authors": "David H. Ackley and Geoffrey E. Hinton and Terrence J. Sejnowski", + "year": "1985", + "journal": "Cognitive Science", + "doi": "10.1016/S0364-0213(85)80012-4", + "url": "https://www.sciencedirect.com/science/article/pii/S0364021385800124" + }, + { + "id": "Gret", + "type": "article", + "title": "A Kernel Method for the Two-Sample-Problem", + "authors": "Gretton, Arthur and Borgwardt, Karsten and Rasch, Malte and Schölkopf, Bernhard and Smola, Alexander", + "year": "2006", + "journal": "Advances in neural information processing systems" + }, + { + "id": "Kull", + "type": "article", + "title": "On Information and Sufficiency", + "authors": "Solomon Kullback and R. A. Leibler", + "year": "1951", + "journal": "Annals of Mathematical Statistics", + "url": "https://api.semanticscholar.org/CorpusID:120349231" + } + ], + "basedOnPapers": ["10.1103/PhysRevA.98.062324"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_kernels_module", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations_v2/tutorial_qchem_external/demo.py b/demonstrations_v2/tutorial_qchem_external/demo.py index 598a4b9ba0..77dff37479 100644 --- a/demonstrations_v2/tutorial_qchem_external/demo.py +++ b/demonstrations_v2/tutorial_qchem_external/demo.py @@ -225,3 +225,6 @@ # 4. Finally, we can convert PySCF wave functions to PennyLane state vectors using the # :func:`~.pennylane.qchem.import_state` function. # +# About the author +# ---------------- +# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations_v2/tutorial_qchem_external/metadata.json b/demonstrations_v2/tutorial_qchem_external/metadata.json index e6aae2983b..eb5d12152d 100644 --- a/demonstrations_v2/tutorial_qchem_external/metadata.json +++ b/demonstrations_v2/tutorial_qchem_external/metadata.json @@ -1,52 +1,51 @@ { - "title": "Using PennyLane with PySCF and OpenFermion", - "authors": [ - { - "username": "soran" - } - ], - "dateOfPublication": "2023-01-03T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry", - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/external_libs/thumbnail_tutorial_external_libs.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_external_libs.png" - } - ], - "seoDescription": "Learn how to integrate external quantum chemistry libraries with PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_givens_rotations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_adaptive_circuits", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Using PennyLane with PySCF and OpenFermion", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2023-01-03T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry", "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/external_libs/thumbnail_tutorial_external_libs.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_external_libs.png" + } + ], + "seoDescription": "Learn how to integrate external quantum chemistry libraries with PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_adaptive_circuits", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qft/demo.py b/demonstrations_v2/tutorial_qft/demo.py index 5b779ab2a7..f682bcdbd7 100644 --- a/demonstrations_v2/tutorial_qft/demo.py +++ b/demonstrations_v2/tutorial_qft/demo.py @@ -177,3 +177,6 @@ def circuit(): # formulation, its implementation with basic quantum gates, and its application demonstrating its usefulness in algorithms like quantum phase estimation. # It is a technique that deserves to be mastered! # +# About the authors +# ----------------- +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qft/metadata.json b/demonstrations_v2/tutorial_qft/metadata.json index 82ef40f977..d99c9dd532 100644 --- a/demonstrations_v2/tutorial_qft/metadata.json +++ b/demonstrations_v2/tutorial_qft/metadata.json @@ -1,31 +1,31 @@ { - "title": "Intro to Quantum Fourier Transform", - "authors": [ - { - "username": "KetPuntoG" - } - ], - "dateOfPublication": "2024-04-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/qft/thumbnail_QFT.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QFT_2024-04-04.png" - } - ], - "seoDescription": "Master the basics of the quantum fourier transform", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Intro to Quantum Fourier Transform", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-04-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qft/thumbnail_QFT.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QFT_2024-04-04.png" + } + ], + "seoDescription": "Master the basics of the quantum fourier transform", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_qft_arithmetics/demo.py b/demonstrations_v2/tutorial_qft_arithmetics/demo.py index 74ba2dd3a5..bd4655afd3 100644 --- a/demonstrations_v2/tutorial_qft_arithmetics/demo.py +++ b/demonstrations_v2/tutorial_qft_arithmetics/demo.py @@ -428,3 +428,8 @@ def factorization(n, wires_m, wires_k, wires_solution): # Thomas G. Draper, "Addition on a Quantum Computer". `arXiv:quant-ph/0008033 `__. # # +# About the author +# ---------------- +# +# .. include:: ../_static/authors/guillermo_alonso.txt +# diff --git a/demonstrations_v2/tutorial_qft_arithmetics/metadata.json b/demonstrations_v2/tutorial_qft_arithmetics/metadata.json index fc8450ff02..b772780baa 100644 --- a/demonstrations_v2/tutorial_qft_arithmetics/metadata.json +++ b/demonstrations_v2/tutorial_qft_arithmetics/metadata.json @@ -1,44 +1,42 @@ { - "title": "Basic arithmetic with the quantum Fourier transform (QFT)", - "authors": [ - { - "username": "KetPuntoG" - } - ], - "dateOfPublication": "2022-11-07T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started", - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_basic_arithmetic_QFT.png" - } - ], - "seoDescription": "Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic", - "doi": "", - "references": [ - { - "id": "Draper2000", - "type": "article", - "title": "Addition on a Quantum Computer", - "authors": "Thomas G. Draper", - "year": "2000", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0008033" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qubit_rotation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Basic arithmetic with the quantum Fourier transform (QFT)", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2022-11-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", "Algorithms", "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_basic_arithmetic_QFT.png" + } + ], + "seoDescription": "Learn how to use the quantum Fourier transform (QFT) to do basic arithmetic", + "doi": "", + "references": [ + { + "id": "Draper2000", + "type": "article", + "title": "Addition on a Quantum Computer", + "authors": "Thomas G. Draper", + "year": "2000", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0008033" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qgrnn/demo.py b/demonstrations_v2/tutorial_qgrnn/demo.py index 6005ee5054..913fc9aea4 100644 --- a/demonstrations_v2/tutorial_qgrnn/demo.py +++ b/demonstrations_v2/tutorial_qgrnn/demo.py @@ -673,3 +673,6 @@ def cost_function(weight_params, bias_params): # `arXiv:1909.12264 `__. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/jack_ceroni.txt diff --git a/demonstrations_v2/tutorial_qgrnn/metadata.json b/demonstrations_v2/tutorial_qgrnn/metadata.json index 7fa78032c8..9e0c81ffb2 100644 --- a/demonstrations_v2/tutorial_qgrnn/metadata.json +++ b/demonstrations_v2/tutorial_qgrnn/metadata.json @@ -1,38 +1,36 @@ { - "title": "The Quantum Graph Recurrent Neural Network", - "authors": [ - { - "username": "jceroni" - } - ], - "dateOfPublication": "2020-07-27T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_graph_recurrent_neural.png" - } - ], - "seoDescription": "Using a quantum graph recurrent neural network to learn quantum dynamics.", - "doi": "", - "references": [ - { - "id": "Verdon2019", - "type": "article", - "title": "Quantum Graph Neural Networks", - "authors": "Verdon, G., McCourt, T., Luzhnica, E., Singh, V., Leichenauer, S., & Hidary, J.", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1909.12264" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1909.12264" - ], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "The Quantum Graph Recurrent Neural Network", + "authors": [ + { + "username": "jceroni" + } + ], + "dateOfPublication": "2020-07-27T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_graph_recurrent_neural.png" + } + ], + "seoDescription": "Using a quantum graph recurrent neural network to learn quantum dynamics.", + "doi": "", + "references": [ + { + "id": "Verdon2019", + "type": "article", + "title": "Quantum Graph Neural Networks", + "authors": "Verdon, G., McCourt, T., Luzhnica, E., Singh, V., Leichenauer, S., & Hidary, J.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.12264" + } + ], + "basedOnPapers": ["10.48550/arXiv.1909.12264"], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py index 1b9090a5de..006459d40b 100644 --- a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py +++ b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/demo.py @@ -399,3 +399,5 @@ def std_err(x): # specific size and topology of your PennyLane circuit. ###################################################################### +# About the author +# ---------------- \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json index 8aef341082..f9d297ca80 100644 --- a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json +++ b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/metadata.json @@ -1,64 +1,64 @@ { - "title": "How to quantum just-in-time (QJIT) compile Grover's algorithm with Catalyst", - "authors": [ - { - "username": "joeycarter" - } - ], - "dateOfPublication": "2024-11-07T00:00:00+00:00", - "dateOfLastModification": "2024-11-08T00:00:00+00:00", - "categories": [ - "Algorithms", - "Devices and Performance", - "How-to", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qjit_compile_grovers_algorithm_with_catalyst.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qjit_compile_grovers_algorithm_with_catalyst.png" - } - ], - "seoDescription": "See how to use Catalyst to just-in-time (QJIT) compile a PennyLane circuit implementing Grover's algorithm.", - "doi": "", - "references": [ - { - "id": "Grover1996", - "type": "article", - "title": "A fast quantum mechanical algorithm for database search", - "authors": "L. K. Grover", - "year": "1996", - "journal": "Proceedings of the twenty-eighth annual ACM symposium on Theory of Computing", - "doi": "10.1145/237814.237866" - }, - { - "id": "NandC2000", - "type": "book", - "title": "Quantum Computation and Quantum Information", - "authors": "M. A. Nielsen, and I. L. Chuang", - "year": "2000", - "publisher": "Cambridge University Press", - "journal": "", - "url": "" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_grovers_algorithm", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to quantum just-in-time (QJIT) compile Grover's algorithm with Catalyst", + "authors": [ + { + "username": "joeycarter" + } + ], + "dateOfPublication": "2024-11-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-08T00:00:00+00:00", + "categories": [ + "Algorithms", + "Devices and Performance", + "How-to", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qjit_compile_grovers_algorithm_with_catalyst.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qjit_compile_grovers_algorithm_with_catalyst.png" + } + ], + "seoDescription": "See how to use Catalyst to just-in-time (QJIT) compile a PennyLane circuit implementing Grover's algorithm.", + "doi": "", + "references": [ + { + "id": "Grover1996", + "type": "article", + "title": "A fast quantum mechanical algorithm for database search", + "authors": "L. K. Grover", + "year": "1996", + "journal": "Proceedings of the twenty-eighth annual ACM symposium on Theory of Computing", + "doi": "10.1145/237814.237866" + }, + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_grovers_algorithm", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qnn_module_tf/demo.py b/demonstrations_v2/tutorial_qnn_module_tf/demo.py index dc4cad811a..a78ddae642 100644 --- a/demonstrations_v2/tutorial_qnn_module_tf/demo.py +++ b/demonstrations_v2/tutorial_qnn_module_tf/demo.py @@ -230,3 +230,6 @@ def qnode(inputs, weights): # perform on realistic datasets? ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/thomas_bromley.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qnn_module_tf/metadata.json b/demonstrations_v2/tutorial_qnn_module_tf/metadata.json index 1ea07b10f3..36a7aab856 100644 --- a/demonstrations_v2/tutorial_qnn_module_tf/metadata.json +++ b/demonstrations_v2/tutorial_qnn_module_tf/metadata.json @@ -1,33 +1,33 @@ { - "title": "Turning quantum nodes into Keras Layers", - "authors": [ - { - "username": "trbromley" - } - ], - "dateOfPublication": "2020-11-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance", - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_nodes_into_keras.png" - } - ], - "seoDescription": "Learn how to create hybrid ML models in PennyLane using Keras", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qnn_module_torch", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Turning quantum nodes into Keras Layers", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-11-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_nodes_into_keras.png" + } + ], + "seoDescription": "Learn how to create hybrid ML models in PennyLane using Keras", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qnn_module_torch", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qnn_module_torch/demo.py b/demonstrations_v2/tutorial_qnn_module_torch/demo.py index 4c897559fd..4a7eee615f 100644 --- a/demonstrations_v2/tutorial_qnn_module_torch/demo.py +++ b/demonstrations_v2/tutorial_qnn_module_torch/demo.py @@ -289,3 +289,6 @@ def forward(self, x): # perform on realistic datasets? ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/thomas_bromley.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qnn_module_torch/metadata.json b/demonstrations_v2/tutorial_qnn_module_torch/metadata.json index daca515919..bfd569d229 100644 --- a/demonstrations_v2/tutorial_qnn_module_torch/metadata.json +++ b/demonstrations_v2/tutorial_qnn_module_torch/metadata.json @@ -1,33 +1,33 @@ { - "title": "Turning quantum nodes into Torch Layers", - "authors": [ - { - "username": "trbromley" - } - ], - "dateOfPublication": "2020-11-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance", - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_nodes_into_touch.png" - } - ], - "seoDescription": "Learn how to create hybrid ML models in PennyLane using Torch", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qnn_module_tf", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Turning quantum nodes into Torch Layers", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-11-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance", + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_nodes_into_touch.png" + } + ], + "seoDescription": "Learn how to create hybrid ML models in PennyLane using Torch", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qnn_module_tf", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py b/demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py index 61b0ea9006..0ec837aa13 100644 --- a/demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py +++ b/demonstrations_v2/tutorial_qnn_multivariate_regression/demo.py @@ -287,3 +287,6 @@ def evaluate(params, data): # "How to optimize a QML model using JAX and Optax", # `Pennylane: How to optimize a QML model using JAX and Optax `__, 2024 # +# About the authors +# ----------------- + diff --git a/demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json b/demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json index cb296d316d..2087296497 100644 --- a/demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json +++ b/demonstrations_v2/tutorial_qnn_multivariate_regression/metadata.json @@ -1,83 +1,83 @@ { - "title": "Multidimensional regression with a variational quantum circuit", - "authors": [ - { - "username": "gmlejarza" - }, - { - "username": "quantumpenguin" - } - ], - "dateOfPublication": "2024-10-01T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Quantum Machine Learning", - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qnn_multivariate_regression.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qnn_multivariate_regression.png" - } - ], - "seoDescription": "Learn to use a quantum neural network to fit a multivariate function.", - "doi": "", - "references": [ - { - "id": "schuld", - "type": "article", - "title": "The effect of data encoding on the expressive power of variational quantum machine learning models", - "authors": "Maria Schuld, Ryan Sweke, Johannes Jakob Meyer", - "year": "2021", - "publisher": "", - "url": "https://arxiv.org/pdf/2008.08605" - }, - { - "id": "qibo", - "type": "article", - "title": "Tutorial: Quantum Fourier Iterative Amplitude Estimation", - "authors": "Jorge J. Martinez de Lejarza", - "year": "2023", - "publisher": "", - "url": "hhttps://qibo.science/qibo/stable/code-examples/tutorials/qfiae/qfiae_demo.html" - }, - { - "id": "demoschuld", - "type": "other", - "title": "Tutorial: Quantum models as Fourier series", - "authors": "Johannes Jakob Meyer, Maria Schuld", - "year": "2021", - "publisher": "", - "url": "https://pennylane.ai/qml/demos/tutorial_expressivity_fourier_series/" - }, - { - "id": "demojax", - "type": "other", - "title": "How to optimize a QML model using JAX and Optax", - "authors": "Josh Izaac, Maria Schuld", - "year": "2024", - "publisher": "", - "url": "ttps://pennylane.ai/qml/demos/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_expressivity_fourier_series", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Multidimensional regression with a variational quantum circuit", + "authors": [ + { + "username": "gmlejarza" + }, + { + "username": "quantumpenguin" + } + ], + "dateOfPublication": "2024-10-01T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Quantum Machine Learning", + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qnn_multivariate_regression.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qnn_multivariate_regression.png" + } + ], + "seoDescription": "Learn to use a quantum neural network to fit a multivariate function.", + "doi": "", + "references": [ + { + "id": "schuld", + "type": "article", + "title": "The effect of data encoding on the expressive power of variational quantum machine learning models", + "authors": "Maria Schuld, Ryan Sweke, Johannes Jakob Meyer", + "year": "2021", + "publisher": "", + "url": "https://arxiv.org/pdf/2008.08605" + }, + { + "id": "qibo", + "type": "article", + "title": "Tutorial: Quantum Fourier Iterative Amplitude Estimation", + "authors": "Jorge J. Martinez de Lejarza", + "year": "2023", + "publisher": "", + "url": "hhttps://qibo.science/qibo/stable/code-examples/tutorials/qfiae/qfiae_demo.html" + }, + { + "id": "demoschuld", + "type": "other", + "title": "Tutorial: Quantum models as Fourier series", + "authors": "Johannes Jakob Meyer, Maria Schuld", + "year": "2021", + "publisher": "", + "url": "https://pennylane.ai/qml/demos/tutorial_expressivity_fourier_series/" + }, + { + "id": "demojax", + "type": "other", + "title": "How to optimize a QML model using JAX and Optax", + "authors": "Josh Izaac, Maria Schuld", + "year": "2024", + "publisher": "", + "url": "ttps://pennylane.ai/qml/demos/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_How_to_optimize_QML_model_using_JAX_and_Optax", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_expressivity_fourier_series", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qpe/demo.py b/demonstrations_v2/tutorial_qpe/demo.py index 3f917b4883..1a5563ec70 100644 --- a/demonstrations_v2/tutorial_qpe/demo.py +++ b/demonstrations_v2/tutorial_qpe/demo.py @@ -290,3 +290,6 @@ def circuit_qpe(estimation_wires): # `Arxiv `__, 2023 # # +# About the authors +# ----------------- +# diff --git a/demonstrations_v2/tutorial_qpe/metadata.json b/demonstrations_v2/tutorial_qpe/metadata.json index 85d506579f..5e385acff9 100644 --- a/demonstrations_v2/tutorial_qpe/metadata.json +++ b/demonstrations_v2/tutorial_qpe/metadata.json @@ -1,60 +1,60 @@ { - "title": "Intro to Quantum Phase Estimation", - "authors": [ - { - "username": "ixfoduap" - }, - { - "username": "KetPuntoG" - } - ], - "dateOfPublication": "2024-01-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/qpe/thumbnail_Quantum_Phase_Estimation_2023-11-27.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Quantum_Phase_Estimation_2023-11-27.png" - } - ], - "seoDescription": "Master the basics of the quantum phase estimation", - "doi": "", - "references": [ - { - "id": "qpe", - "type": "article", - "title": "Quantum measurements and the Abelian Stabilizer Problem", - "authors": "A.Yu.Kitaev.", - "year": "1995", - "publisher": "", - "url": "https://arxiv.org/abs/quant-ph/9511026" - }, - { - "id": "initial_state", - "type": "article", - "title": "Initial state preparation for quantum chemistry on quantum computers", - "authors": "Stepan Fomichev et al.", - "year": "2023", - "publisher": "", - "url": "https://arxiv.org/pdf/2310.18410.pdf/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_phase_kickback", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/introduction-to-quantum-phase-estimation-demo/7337" -} \ No newline at end of file + "title": "Intro to Quantum Phase Estimation", + "authors": [ + { + "username": "ixfoduap" + }, + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-01-30T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qpe/thumbnail_Quantum_Phase_Estimation_2023-11-27.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_Quantum_Phase_Estimation_2023-11-27.png" + } + ], + "seoDescription": "Master the basics of the quantum phase estimation", + "doi": "", + "references": [ + { + "id": "qpe", + "type": "article", + "title": "Quantum measurements and the Abelian Stabilizer Problem", + "authors": "A.Yu.Kitaev.", + "year": "1995", + "publisher": "", + "url": "https://arxiv.org/abs/quant-ph/9511026" + }, + { + "id": "initial_state", + "type": "article", + "title": "Initial state preparation for quantum chemistry on quantum computers", + "authors": "Stepan Fomichev et al.", + "year": "2023", + "publisher": "", + "url": "https://arxiv.org/pdf/2310.18410.pdf/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_phase_kickback", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/introduction-to-quantum-phase-estimation-demo/7337" +} diff --git a/demonstrations_v2/tutorial_qsvt_hardware/demo.py b/demonstrations_v2/tutorial_qsvt_hardware/demo.py index 824fa6f776..9314e3f87f 100644 --- a/demonstrations_v2/tutorial_qsvt_hardware/demo.py +++ b/demonstrations_v2/tutorial_qsvt_hardware/demo.py @@ -162,3 +162,6 @@ def circuit(): # "A Grand Unification of Quantum Algorithms". # `arXiv preprint arXiv:2105.02859 `__. # +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_qsvt_hardware/metadata.json b/demonstrations_v2/tutorial_qsvt_hardware/metadata.json index b51d363302..acc4370c21 100644 --- a/demonstrations_v2/tutorial_qsvt_hardware/metadata.json +++ b/demonstrations_v2/tutorial_qsvt_hardware/metadata.json @@ -1,43 +1,45 @@ { - "title": "How to implement QSVT on hardware", - "authors": [ - { - "username": "KetPuntoG" - } - ], - "dateOfPublication": "2024-09-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qsvt_hardware.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qsvt_hardware.png" - } - ], - "seoDescription": "Learn how to implement QSVT on hardware.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_intro_qsvt", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_apply_qsvt", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "How to implement QSVT on hardware", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-09-18T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qsvt_hardware.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qsvt_hardware.png" + } + ], + "seoDescription": "Learn how to implement QSVT on hardware.", + "doi": "", + "references": [ + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_qsvt", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_apply_qsvt", + "weight": 1.0 + } + + ] +} diff --git a/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py b/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py index 9db0a91576..538272d28d 100644 --- a/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py +++ b/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py @@ -707,3 +707,8 @@ def plot_cost_and_model(f, model, params, shift_radius=5 * np.pi / 8, num_points # `arXiv preprint arXiv:2008.06517 `__. # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/elies_gil-fuster.txt +# +# .. include:: ../_static/authors/david_wierichs.txt diff --git a/demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json b/demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json index 7c72957f8b..51a204e2f7 100644 --- a/demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json +++ b/demonstrations_v2/tutorial_quantum_analytic_descent/metadata.json @@ -1,80 +1,78 @@ { - "title": "Quantum analytic descent", - "authors": [ - { - "username": "egfuster" - }, - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2021-06-30T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_analytic_descent.png" - } - ], - "seoDescription": "Implement the Quantum analytic descent algorithm for VQE.", - "doi": "", - "references": [ - { - "id": "QAD", - "type": "article", - "title": "Quantum Analytic Descent", - "authors": "Balint Koczor, Simon Benjamin", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2008.13774" - }, - { - "id": "Rotosolve", - "type": "article", - "title": "Structure optimization for parameterized quantum circuits", - "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Benedetti", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1905.09692" - }, - { - "id": "higher_order_diff", - "type": "article", - "title": "Estimating the gradient and higher-order derivatives on quantum hardware", - "authors": "Andrea Mari, Thomas R. Bromley, Nathan Killoran", - "year": "2021", - "journal": "Phys. Rev. A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.012405" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2008.13774" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_natural_gradient", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_rotoselect", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_stochastic_parameter_shift", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum analytic descent", + "authors": [ + { + "username": "egfuster" + }, + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2021-06-30T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_analytic_descent.png" + } + ], + "seoDescription": "Implement the Quantum analytic descent algorithm for VQE.", + "doi": "", + "references": [ + { + "id": "QAD", + "type": "article", + "title": "Quantum Analytic Descent", + "authors": "Balint Koczor, Simon Benjamin", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2008.13774" + }, + { + "id": "Rotosolve", + "type": "article", + "title": "Structure optimization for parameterized quantum circuits", + "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Benedetti", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1905.09692" + }, + { + "id": "higher_order_diff", + "type": "article", + "title": "Estimating the gradient and higher-order derivatives on quantum hardware", + "authors": "Andrea Mari, Thomas R. Bromley, Nathan Killoran", + "year": "2021", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.012405" + } + ], + "basedOnPapers": ["10.48550/arXiv.2008.13774"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rotoselect", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_stochastic_parameter_shift", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_quantum_chemistry/demo.py b/demonstrations_v2/tutorial_quantum_chemistry/demo.py index 9fad88c657..1872263cac 100644 --- a/demonstrations_v2/tutorial_quantum_chemistry/demo.py +++ b/demonstrations_v2/tutorial_quantum_chemistry/demo.py @@ -78,7 +78,7 @@ from pennylane import qchem -symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") +symbols, coordinates = qchem.read_structure("h2o.xyz") ############################################################################## # The xyz format is supported. @@ -326,3 +326,6 @@ # `Journal of Chemical Theory and Computation 14, 2017 (2018). # `_ # +# About the author +# ---------------- +# .. include:: ../_static/authors/alain_delgado.txt diff --git a/demonstrations_v2/tutorial_quantum_chemistry/metadata.json b/demonstrations_v2/tutorial_quantum_chemistry/metadata.json index 7ba317278e..3d481d3440 100644 --- a/demonstrations_v2/tutorial_quantum_chemistry/metadata.json +++ b/demonstrations_v2/tutorial_quantum_chemistry/metadata.json @@ -1,87 +1,87 @@ { - "title": "Building molecular Hamiltonians", - "authors": [ - { - "username": "adgran" - } - ], - "dateOfPublication": "2020-08-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_building_molecular_Hamiltonians.png" - } - ], - "seoDescription": "Learn how to build electronic Hamiltonians of molecules.", - "doi": "", - "references": [ - { - "id": "yudong2019", - "type": "article", - "title": "Quantum Chemistry in the Age of Quantum Computing", - "authors": "Yudong Cao, Jonathan Romero, et al.", - "year": "2019", - "journal": "Chem. Rev.", - "url": "https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803" - }, - { - "id": "BornOpp1927", - "type": "article", - "title": "Quantum Theory of the Molecules", - "authors": "M. Born, J.R. Oppenheimer", - "year": "1927", - "journal": "Annalen der Physik", - "url": "https://onlinelibrary.wiley.com/doi/abs/10.1002/andp.19273892002" - }, - { - "id": "pople1977", - "type": "article", - "title": "Self\u2010consistent molecular orbital methods. XVIII. Constraints and stability in Hartree\u2013Fock theory", - "authors": "Rolf Seeger, John Pople", - "year": "1977", - "journal": "Journal of Chemical Physics", - "url": "https://aip.scitation.org/doi/abs/10.1063/1.434318" - }, - { - "id": "ref_integrals", - "type": "article", - "title": "Fundamentals of Molecular Integrals Evaluation", - "authors": "J.T. Fermann, E.F. Valeev", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2007.12057" - }, - { - "id": "seeley2012", - "type": "article", - "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", - "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", - "year": "2012", - "journal": "Journal of Chemical Physics", - "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" - }, - { - "id": "truhlar2018", - "type": "article", - "title": "Automatic Selection of an Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT", - "authors": "J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar", - "year": "2018", - "journal": "Journal of Chemical Theory and Computation", - "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.8b00032" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Building molecular Hamiltonians", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2020-08-02T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_building_molecular_Hamiltonians.png" + } + ], + "seoDescription": "Learn how to build electronic Hamiltonians of molecules.", + "doi": "", + "references": [ + { + "id": "yudong2019", + "type": "article", + "title": "Quantum Chemistry in the Age of Quantum Computing", + "authors": "Yudong Cao, Jonathan Romero, et al.", + "year": "2019", + "journal": "Chem. Rev.", + "url": "https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00803" + }, + { + "id": "BornOpp1927", + "type": "article", + "title": "Quantum Theory of the Molecules", + "authors": "M. Born, J.R. Oppenheimer", + "year": "1927", + "journal": "Annalen der Physik", + "url": "https://onlinelibrary.wiley.com/doi/abs/10.1002/andp.19273892002" + }, + { + "id": "pople1977", + "type": "article", + "title": "Self‐consistent molecular orbital methods. XVIII. Constraints and stability in Hartree–Fock theory", + "authors": "Rolf Seeger, John Pople", + "year": "1977", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.434318" + }, + { + "id": "ref_integrals", + "type": "article", + "title": "Fundamentals of Molecular Integrals Evaluation", + "authors": "J.T. Fermann, E.F. Valeev", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2007.12057" + }, + { + "id": "seeley2012", + "type": "article", + "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", + "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", + "year": "2012", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" + }, + { + "id": "truhlar2018", + "type": "article", + "title": "Automatic Selection of an Active Space for Calculating Electronic Excitation Spectra by MS-CASPT2 or MC-PDFT", + "authors": "J.J. Bao, S.S. Dong, L. Gagliardi, D.G. Truhlar", + "year": "2018", + "journal": "Journal of Chemical Theory and Computation", + "url": "https://pubs.acs.org/doi/abs/10.1021/acs.jctc.8b00032" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py b/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py index 8f7546b166..45083d90fe 100644 --- a/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py @@ -887,3 +887,10 @@ def make_kraus_ops(num_wires: int): # (`arXiv `__) # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/gideon_uchehara.txt +# +# .. include:: ../_static/authors/matija_medvidovic.txt +# +# .. include:: ../_static/authors/anuj_apte.txt diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json b/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json index 9b7b66a7d1..ff928c43f3 100644 --- a/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json @@ -1,73 +1,72 @@ { - "title": "Quantum Circuit Cutting", - "authors": [ - { - "username": "gideonuchehara" - }, - { - "username": "Matematija" - }, - { - "username": "aapte" - } - ], - "dateOfPublication": "2022-09-02T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_circuit_cutting.png" - } - ], - "seoDescription": "We dive into two algorithms for splitting a large quantum circuit into smaller ones.", - "doi": "", - "references": [ - { - "id": "Peng2019", - "type": "article", - "title": "Simulating Large Quantum Circuits on a Small Quantum Computer", - "authors": "T. Peng, A. Harrow, M. Ozols, and X. Wu", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1904.00102" - }, - { - "id": "Lowe2022", - "type": "article", - "title": "Fast quantum circuit cutting with randomized measurements", - "authors": "A. Lowe et al.", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2207.14734" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qaoa_intro", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qaoa_maxcut", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_haar_measure", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_unitary_designs", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum Circuit Cutting", + "authors": [ + { + "username": "gideonuchehara" + }, + { + "username": "Matematija" + }, + { + "username": "aapte" + } + ], + "dateOfPublication": "2022-09-02T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_circuit_cutting.png" + } + ], + "seoDescription": "We dive into two algorithms for splitting a large quantum circuit into smaller ones.", + "doi": "", + "references": [ + { + "id": "Peng2019", + "type": "article", + "title": "Simulating Large Quantum Circuits on a Small Quantum Computer", + "authors": "T. Peng, A. Harrow, M. Ozols, and X. Wu", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1904.00102" + }, + { + "id": "Lowe2022", + "type": "article", + "title": "Fast quantum circuit cutting with randomized measurements", + "authors": "A. Lowe et al.", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2207.14734" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qaoa_maxcut", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_haar_measure", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_unitary_designs", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_quantum_dropout/demo.py b/demonstrations_v2/tutorial_quantum_dropout/demo.py index 4469c56943..cebca15758 100644 --- a/demonstrations_v2/tutorial_quantum_dropout/demo.py +++ b/demonstrations_v2/tutorial_quantum_dropout/demo.py @@ -698,3 +698,5 @@ def optimizer_update(opt_state, params, x, y, keep_rot): # *Theory of overparametrization in quantum neural networks*. # `Nat. Comp. Science, 3, 542–551. `__. # +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_quantum_dropout/metadata.json b/demonstrations_v2/tutorial_quantum_dropout/metadata.json index d74f9d9c8a..ebbac313ff 100644 --- a/demonstrations_v2/tutorial_quantum_dropout/metadata.json +++ b/demonstrations_v2/tutorial_quantum_dropout/metadata.json @@ -1,68 +1,59 @@ { - "title": "Dropout in Quantum Neural Networks", - "authors": [ - { - "username": "fscala" - } - ], - "dateOfPublication": "2024-03-12T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [ - "QNN", - "overfitting", - "dropout", - "regularization" - ], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/quantum_dropout/thumbnail_QuantumDropout_2024-03-07.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuantumDropout_2024-03-07.png" - } - ], - "seoDescription": "Learn to avoid overfitting employing quantum dropout", - "doi": "", - "references": [ - { - "id": "dropout", - "type": "article", - "title": "A General Approach to Dropout in Quantum Neural Networks", - "authors": "Scala, F., Ceschini, A., Panella, M., & Gerace, D.", - "year": "2023", - "journal": "Advanced Quantum Technologies", - "url": "https://onlinelibrary.wiley.com/doi/full/10.1002/qute.202300220", - "doi": "10.1002/qute.202300220" - }, - { - "id": "Kiani2020", - "type": "article", - "title": "Learning Unitaries by Gradient Descent", - "authors": "Kiani,B. T., Lloyd, S., & Maity, R.", - "year": "2020", - "journal": "arXiv", - "url": "https://arxiv.org/abs/2001.11897" - }, - { - "id": "Larocca2023", - "type": "article", - "title": "Theory of overparametrization in quantum neural networks", - "authors": "Larocca, M., Ju, N., Garc\u00eda-Mart\u00edn, D., Coles, P. J., & Cerezo, M.", - "year": "2023", - "journal": "Nature Computational Science", - "url": "http://dx.doi.org/10.1038/s43588-023-00467-6", - "doi": "10.1038/s43588-023-00467-6" - } - ], - "basedOnPapers": [ - "10.1002/qute.202300220" - ], - "referencedByPapers": [], - "relatedContent": [], - "hardware": [] -} \ No newline at end of file + "title": "Dropout in Quantum Neural Networks", + "authors": [ + { + "username": "fscala" + } + ], + "dateOfPublication": "2024-03-12T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": ["Quantum Machine Learning"], + "tags": ["QNN", "overfitting", "dropout", "regularization"], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/quantum_dropout/thumbnail_QuantumDropout_2024-03-07.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_QuantumDropout_2024-03-07.png" + } + ], + "seoDescription": "Learn to avoid overfitting employing quantum dropout", + "doi": "", + "references": [ + { + "id": "dropout", + "type": "article", + "title": "A General Approach to Dropout in Quantum Neural Networks", + "authors": "Scala, F., Ceschini, A., Panella, M., & Gerace, D.", + "year": "2023", + "journal": "Advanced Quantum Technologies", + "url": "https://onlinelibrary.wiley.com/doi/full/10.1002/qute.202300220", + "doi": "10.1002/qute.202300220" + }, + { + "id": "Kiani2020", + "type": "article", + "title": "Learning Unitaries by Gradient Descent", + "authors": "Kiani,B. T., Lloyd, S., & Maity, R.", + "year": "2020", + "journal": "arXiv", + "url": "https://arxiv.org/abs/2001.11897" + }, + { + "id": "Larocca2023", + "type": "article", + "title": "Theory of overparametrization in quantum neural networks", + "authors": "Larocca, M., Ju, N., García-Martín, D., Coles, P. J., & Cerezo, M.", + "year": "2023", + "journal": "Nature Computational Science", + "url": "http://dx.doi.org/10.1038/s43588-023-00467-6", + "doi": "10.1038/s43588-023-00467-6" + } + ], + "basedOnPapers": ["10.1002/qute.202300220"], + "referencedByPapers": [], + "relatedContent": [], + "hardware": [] +} diff --git a/demonstrations_v2/tutorial_quantum_gans/demo.py b/demonstrations_v2/tutorial_quantum_gans/demo.py index f24a785ecf..6222a82b27 100644 --- a/demonstrations_v2/tutorial_quantum_gans/demo.py +++ b/demonstrations_v2/tutorial_quantum_gans/demo.py @@ -597,3 +597,6 @@ def forward(self, x): # `arXiv:2010.06201 `__ (2020). # # +# About the author +# ---------------- +# .. include:: ../_static/authors/james_ellis.txt diff --git a/demonstrations_v2/tutorial_quantum_gans/metadata.json b/demonstrations_v2/tutorial_quantum_gans/metadata.json index 88b84b3c82..8e82c5680e 100644 --- a/demonstrations_v2/tutorial_quantum_gans/metadata.json +++ b/demonstrations_v2/tutorial_quantum_gans/metadata.json @@ -1,52 +1,52 @@ { - "title": "Quantum GANs", - "authors": [ - { - "username": "jellis" - } - ], - "dateOfPublication": "2022-02-01T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_GANs.png" - } - ], - "seoDescription": "Explore quantum GANs to generate hand-written digits of zero", - "doi": "", - "references": [ - { - "id": "goodfellow2014", - "type": "article", - "title": "Generative Adversarial Networks", - "authors": "Ian J. Goodfellow et al.", - "year": "2014", - "journal": "", - "url": "https://arxiv.org/abs/1406.2661" - }, - { - "id": "huang2020", - "type": "article", - "title": "Experimental Quantum Generative Adversarial Networks for Image Generation", - "authors": "He-Liang Huang et al.", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2010.06201" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_QGAN", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/questions-on-qgan-tutorial/2607" -} \ No newline at end of file + "title": "Quantum GANs", + "authors": [ + { + "username": "jellis" + } + ], + "dateOfPublication": "2022-02-01T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_GANs.png" + } + ], + "seoDescription": "Explore quantum GANs to generate hand-written digits of zero", + "doi": "", + "references": [ + { + "id": "goodfellow2014", + "type": "article", + "title": "Generative Adversarial Networks", + "authors": "Ian J. Goodfellow et al.", + "year": "2014", + "journal": "", + "url": "https://arxiv.org/abs/1406.2661" + }, + { + "id": "huang2020", + "type": "article", + "title": "Experimental Quantum Generative Adversarial Networks for Image Generation", + "authors": "He-Liang Huang et al.", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2010.06201" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_QGAN", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/questions-on-qgan-tutorial/2607" +} diff --git a/demonstrations_v2/tutorial_quantum_metrology/demo.py b/demonstrations_v2/tutorial_quantum_metrology/demo.py index 9dbe56c758..6b4f2acb85 100644 --- a/demonstrations_v2/tutorial_quantum_metrology/demo.py +++ b/demonstrations_v2/tutorial_quantum_metrology/demo.py @@ -357,3 +357,6 @@ def opt_cost(weights, phi=phi, gamma=gamma, J=J, W=W): # `__, 2020. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/johannes_jakob_meyer.txt diff --git a/demonstrations_v2/tutorial_quantum_metrology/metadata.json b/demonstrations_v2/tutorial_quantum_metrology/metadata.json index 106cc1fd24..3c99c1559b 100644 --- a/demonstrations_v2/tutorial_quantum_metrology/metadata.json +++ b/demonstrations_v2/tutorial_quantum_metrology/metadata.json @@ -1,44 +1,42 @@ { - "title": "Variationally optimizing measurement protocols", - "authors": [ - { - "username": "jjmeyer" - } - ], - "dateOfPublication": "2020-06-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_optimizing_measurement_protocols.png" - } - ], - "seoDescription": "Using a variational quantum algorithm to optimize a quantum sensing protocol.", - "doi": "", - "references": [ - { - "id": "meyer2020", - "type": "article", - "title": "A variational toolbox for quantum multi-parameter estimation.", - "authors": "Johannes Jakob Meyer, Johannes Borregaard, Jens Eisert", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2006.06303" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2006.06303" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_noisy_circuit_optimization", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Variationally optimizing measurement protocols", + "authors": [ + { + "username": "jjmeyer" + } + ], + "dateOfPublication": "2020-06-18T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_optimizing_measurement_protocols.png" + } + ], + "seoDescription": "Using a variational quantum algorithm to optimize a quantum sensing protocol.", + "doi": "", + "references": [ + { + "id": "meyer2020", + "type": "article", + "title": "A variational toolbox for quantum multi-parameter estimation.", + "authors": "Johannes Jakob Meyer, Johannes Borregaard, Jens Eisert", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2006.06303" + } + ], + "basedOnPapers": ["10.48550/arXiv.2006.06303"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_noisy_circuit_optimization", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py index 026193898c..32c2071a18 100644 --- a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py @@ -494,3 +494,6 @@ def layer1_off_diag_double(params): # `arXiv:1909.05074 `__, 2019. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json b/demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json index b14f164128..5f0439e02f 100644 --- a/demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/metadata.json @@ -1,76 +1,76 @@ { - "title": "Quantum natural gradient", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_natural_gradient.png" - } - ], - "seoDescription": "The quantum natural gradient method can achieve faster convergence for quantum machine learning problems by taking into account the intrinsic geometry of qubits.", - "doi": "", - "references": [ - { - "id": "Amari1998", - "type": "article", - "title": "Natural gradient works efficiently in learning.", - "authors": "Shun-Ichi Amari", - "year": "1998", - "journal": "Neural computation", - "url": "https://www.mitpressjournals.org/doi/abs/10.1162/089976698300017746" - }, - { - "id": "Stokes2019", - "type": "article", - "title": "Quantum Natural Gradient.", - "authors": "James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1909.02108" - }, - { - "id": "Harrow2019", - "type": "article", - "title": "Low-depth gradient measurements can improve convergence in variational hybrid quantum-classical algorithms.", - "authors": "Aram Harrow and John Napp", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1901.05374" - }, - { - "id": "Yamamoto2019", - "type": "article", - "title": "On the natural gradient for variational quantum eigensolver.", - "authors": "Naoki Yamamoto", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1909.05074" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1909.02108" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_backprop", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe_qng", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum natural gradient", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_natural_gradient.png" + } + ], + "seoDescription": "The quantum natural gradient method can achieve faster convergence for quantum machine learning problems by taking into account the intrinsic geometry of qubits.", + "doi": "", + "references": [ + { + "id": "Amari1998", + "type": "article", + "title": "Natural gradient works efficiently in learning.", + "authors": "Shun-Ichi Amari", + "year": "1998", + "journal": "Neural computation", + "url": "https://www.mitpressjournals.org/doi/abs/10.1162/089976698300017746" + }, + { + "id": "Stokes2019", + "type": "article", + "title": "Quantum Natural Gradient.", + "authors": "James Stokes, Josh Izaac, Nathan Killoran, Giuseppe Carleo", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.02108" + }, + { + "id": "Harrow2019", + "type": "article", + "title": "Low-depth gradient measurements can improve convergence in variational hybrid quantum-classical algorithms.", + "authors": "Aram Harrow and John Napp", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1901.05374" + }, + { + "id": "Yamamoto2019", + "type": "article", + "title": "On the natural gradient for variational quantum eigensolver.", + "authors": "Naoki Yamamoto", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.05074" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1909.02108" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py index e6dbda2076..23dab8e3ce 100644 --- a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py @@ -607,3 +607,6 @@ def visualize_model(model, num_images=6, fig_name="Predictions"): # *PennyLane: Automatic differentiation of hybrid quantum-classical computations*. arXiv:1811.04968 (2018). # # +# About the author +# ---------------- +# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json b/demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json index 358928cb86..471fd0f39d 100644 --- a/demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/metadata.json @@ -1,64 +1,64 @@ { - "title": "Quantum transfer learning", - "authors": [ - { - "username": "amari" - } - ], - "dateOfPublication": "2019-12-19T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tutorial_quantum_transfer_learning.png" - } - ], - "seoDescription": "Combine PyTorch and PennyLane to train a hybrid quantum-classical image classifier using transfer learning.", - "doi": "", - "references": [ - { - "id": "Mari2019", - "type": "article", - "title": "Transfer learning in hybrid classical-quantum neural networks", - "authors": "Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran", - "year": "2019", - "journal": "", - "url": "" - }, - { - "id": "Raina2007", - "type": "article", - "title": "Self-taught learning: transfer learning from unlabeled data", - "authors": "Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng", - "year": "2007", - "journal": "Proceedings of the 24th International Conference on Machine Learning", - "url": "" - }, - { - "id": "He2016", - "type": "article", - "title": "Deep residual learning for image recognition", - "authors": "Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun", - "year": "2016", - "journal": "Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition", - "url": "" - }, - { - "id": "Bergholm2018", - "type": "article", - "title": "PennyLane: Automatic differentiation of hybrid quantum-classical computations", - "authors": "Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran", - "year": "2018", - "journal": "", - "url": "" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [], - "discussionForumUrl": "https://discuss.pennylane.ai/t/can-quantum-transfer-learning-be-used-for-multi-classification/3963" -} \ No newline at end of file + "title": "Quantum transfer learning", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2019-12-19T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tutorial_quantum_transfer_learning.png" + } + ], + "seoDescription": "Combine PyTorch and PennyLane to train a hybrid quantum-classical image classifier using transfer learning.", + "doi": "", + "references": [ + { + "id": "Mari2019", + "type": "article", + "title": "Transfer learning in hybrid classical-quantum neural networks", + "authors": "Andrea Mari, Thomas R. Bromley, Josh Izaac, Maria Schuld, and Nathan Killoran", + "year": "2019", + "journal": "", + "url": "" + }, + { + "id": "Raina2007", + "type": "article", + "title": "Self-taught learning: transfer learning from unlabeled data", + "authors": "Rajat Raina, Alexis Battle, Honglak Lee, Benjamin Packer, and Andrew Y Ng", + "year": "2007", + "journal": "Proceedings of the 24th International Conference on Machine Learning", + "url": "" + }, + { + "id": "He2016", + "type": "article", + "title": "Deep residual learning for image recognition", + "authors": "Kaiming He, Xiangyu Zhang, Shaoqing ren and Jian Sun", + "year": "2016", + "journal": "Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition", + "url": "" + }, + { + "id": "Bergholm2018", + "type": "article", + "title": "PennyLane: Automatic differentiation of hybrid quantum-classical computations", + "authors": "Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, Carsten Blank, Keri McKiernan, and Nathan Killoran", + "year": "2018", + "journal": "", + "url": "" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [], + "discussionForumUrl": "https://discuss.pennylane.ai/t/can-quantum-transfer-learning-be-used-for-multi-classification/3963" +} diff --git a/demonstrations_v2/tutorial_quanvolution/demo.py b/demonstrations_v2/tutorial_quanvolution/demo.py index e4aab89bfa..0b455fce13 100644 --- a/demonstrations_v2/tutorial_quanvolution/demo.py +++ b/demonstrations_v2/tutorial_quanvolution/demo.py @@ -369,3 +369,6 @@ def MyModel(): # `arXiv:1904.04767 `__, 2019. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quanvolution/metadata.json b/demonstrations_v2/tutorial_quanvolution/metadata.json index 9445748cc6..bdded4e6ca 100644 --- a/demonstrations_v2/tutorial_quanvolution/metadata.json +++ b/demonstrations_v2/tutorial_quanvolution/metadata.json @@ -1,39 +1,37 @@ { - "title": "Quanvolutional Neural Networks", - "authors": [ - { - "username": "amari" - } - ], - "dateOfPublication": "2020-03-24T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quanvolutional_neural_networks.png" - } - ], - "seoDescription": "Train a quantum convolutional neural network to classify MNIST images.", - "doi": "", - "references": [ - { - "id": "Henderson2019", - "type": "article", - "title": "Quanvolutional Neural Networks: Powering Image Recognition with Quantum Circuits.", - "authors": "Maxwell Henderson, Samriddhi Shakya, Shashindra Pradhan, Tristan Cook", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1904.04767" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1904.04767" - ], - "referencedByPapers": [], - "relatedContent": [], - "discussionForumUrl": "https://discuss.pennylane.ai/t/quanvolutional-neural-networks/7296" -} \ No newline at end of file + "title": "Quanvolutional Neural Networks", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2020-03-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quanvolutional_neural_networks.png" + } + ], + "seoDescription": "Train a quantum convolutional neural network to classify MNIST images.", + "doi": "", + "references": [ + { + "id": "Henderson2019", + "type": "article", + "title": "Quanvolutional Neural Networks: Powering Image Recognition with Quantum Circuits.", + "authors": "Maxwell Henderson, Samriddhi Shakya, Shashindra Pradhan, Tristan Cook", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1904.04767" + } + ], + "basedOnPapers": ["10.48550/arXiv.1904.04767"], + "referencedByPapers": [], + "relatedContent": [], + "discussionForumUrl": "https://discuss.pennylane.ai/t/quanvolutional-neural-networks/7296" +} diff --git a/demonstrations_v2/tutorial_qubit_rotation/demo.py b/demonstrations_v2/tutorial_qubit_rotation/demo.py index a34e037a37..231af2f71d 100644 --- a/demonstrations_v2/tutorial_qubit_rotation/demo.py +++ b/demonstrations_v2/tutorial_qubit_rotation/demo.py @@ -368,3 +368,6 @@ def cost(x): # continuous-variable (CV) quantum nodes. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qubit_rotation/metadata.json b/demonstrations_v2/tutorial_qubit_rotation/metadata.json index b9989deda2..4c20e39c43 100644 --- a/demonstrations_v2/tutorial_qubit_rotation/metadata.json +++ b/demonstrations_v2/tutorial_qubit_rotation/metadata.json @@ -1,44 +1,44 @@ { - "title": "Basic tutorial: qubit rotation", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_basic_cubic_rotation.png" - } - ], - "seoDescription": "To see how PennyLane allows the easy construction and optimization of quantum functions, let's consider the 'hello world' of QML: qubit rotation.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "plugins_hybrid", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_gaussian_transformation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_state_preparation", - "weight": 1.0 - } - ], - "hardware": [], - "discussionForumUrl": "https://discuss.pennylane.ai/t/basic-tutorial-qubit-rotation/7338" -} \ No newline at end of file + "title": "Basic tutorial: qubit rotation", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_basic_cubic_rotation.png" + } + ], + "seoDescription": "To see how PennyLane allows the easy construction and optimization of quantum functions, let's consider the 'hello world' of QML: qubit rotation.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "plugins_hybrid", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_gaussian_transformation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_state_preparation", + "weight": 1.0 + } + ], + "hardware": [], + "discussionForumUrl": "https://discuss.pennylane.ai/t/basic-tutorial-qubit-rotation/7338" +} diff --git a/demonstrations_v2/tutorial_qubit_tapering/demo.py b/demonstrations_v2/tutorial_qubit_tapering/demo.py index 4ef36de092..f2af09bfa4 100644 --- a/demonstrations_v2/tutorial_qubit_tapering/demo.py +++ b/demonstrations_v2/tutorial_qubit_tapering/demo.py @@ -307,3 +307,8 @@ def tapered_circuit(params): # # # +# About the author +# ---------------- +# .. include:: ../_static/authors/utkarsh_azad.txt +# +# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations_v2/tutorial_qubit_tapering/metadata.json b/demonstrations_v2/tutorial_qubit_tapering/metadata.json index 652b6bd730..d65cec6324 100644 --- a/demonstrations_v2/tutorial_qubit_tapering/metadata.json +++ b/demonstrations_v2/tutorial_qubit_tapering/metadata.json @@ -1,74 +1,74 @@ { - "title": "Qubit tapering", - "authors": [ - { - "username": "whatsis" - }, - { - "username": "soran" - } - ], - "dateOfPublication": "2022-05-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_Qubit_tapering.png" - } - ], - "seoDescription": "Learn how to taper off qubits", - "doi": "", - "references": [ - { - "id": "bravyi2017", - "type": "article", - "title": "Tapering off qubits to simulate fermionic Hamiltonians", - "authors": "Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme", - "year": "2017", - "journal": "", - "url": "https://arxiv.org/abs/1701.08213" - }, - { - "id": "setia2019", - "type": "article", - "title": "Reducing qubit requirements for quantum simulation using molecular point group symmetries", - "authors": "Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1910.14644" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_givens_rotations", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_adaptive_circuits", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_differentiable_HF", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Qubit tapering", + "authors": [ + { + "username": "whatsis" + }, + { + "username": "soran" + } + ], + "dateOfPublication": "2022-05-16T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_Qubit_tapering.png" + } + ], + "seoDescription": "Learn how to taper off qubits", + "doi": "", + "references": [ + { + "id": "bravyi2017", + "type": "article", + "title": "Tapering off qubits to simulate fermionic Hamiltonians", + "authors": "Sergey Bravyi, Jay M. Gambetta, Antonio Mezzacapo, Kristan Temme", + "year": "2017", + "journal": "", + "url": "https://arxiv.org/abs/1701.08213" + }, + { + "id": "setia2019", + "type": "article", + "title": "Reducing qubit requirements for quantum simulation using molecular point group symmetries", + "authors": "Kanav Setia, Richard Chen, Julia E. Rice, Antonio Mezzacapo, Marco Pistoia, James Whitfield", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.14644" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_adaptive_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_differentiable_HF", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qubitization/demo.py b/demonstrations_v2/tutorial_qubitization/demo.py index f4382e3460..354d291ed9 100644 --- a/demonstrations_v2/tutorial_qubitization/demo.py +++ b/demonstrations_v2/tutorial_qubitization/demo.py @@ -165,3 +165,5 @@ def circuit(): # # In this demo, we explored the concept of qubitization and one of its applications. To achieve this, we combined several key concepts, including `block encoding `_, `quantum phase estimation `_, and `amplitude amplification `_. This algorithm serves as a foundation for more advanced techniques like `quantum singular value transformation `_. We encourage you to continue studying these methods and apply them in your research. # +# About the authors +# ------------------ diff --git a/demonstrations_v2/tutorial_qubitization/metadata.json b/demonstrations_v2/tutorial_qubitization/metadata.json index 8a08ee9039..6ec14b591f 100644 --- a/demonstrations_v2/tutorial_qubitization/metadata.json +++ b/demonstrations_v2/tutorial_qubitization/metadata.json @@ -1,50 +1,50 @@ { - "title": "Intro to qubitization", - "authors": [ - { - "username": "KetPuntoG" - }, - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2024-09-09T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qubitization.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qubitization.png" - } - ], - "seoDescription": "Learn how to use, visualize and apply qubitization, a block-encoding technique in quantum computing.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_intro_amplitude_amplification", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_qpe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_lcu_blockencoding", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Intro to qubitization", + "authors": [ + { + "username": "KetPuntoG" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2024-09-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_qubitization.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_qubitization.png" + } + ], + "seoDescription": "Learn how to use, visualize and apply qubitization, a block-encoding technique in quantum computing.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_intro_amplitude_amplification", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_qpe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_lcu_blockencoding", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py index 9762791c52..9be0a9266f 100644 --- a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py @@ -403,3 +403,7 @@ def circuit(): # .. [#toffoli_qutrits] # Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble, `"Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits". # `__ +# About the author +# ---------------- +# .. include:: ../_static/authors/guillermo_alonso.txt + diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json index 8973cad2de..2dbf11c48a 100644 --- a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/metadata.json @@ -1,48 +1,48 @@ { - "title": "Qutrits and quantum algorithms", - "authors": [ - { - "username": "KetPuntoG" - } - ], - "dateOfPublication": "2023-05-09T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/qutrits_bernstein_vazirani/thumbnail_tutorial_qutrits_bernstein_vazirani.png" - } - ], - "seoDescription": "Learn how to interpret the Bernstein\u2013Vazirani algorithm with qutrits", - "doi": "", - "references": [ - { - "id": "bv", - "type": "article", - "title": "Quantum Complexity Theory", - "authors": "Ethan Bernstein, Umesh Vazirani", - "year": "1997", - "journal": "SIAM Journal on Computing", - "volume": "26", - "doi": "10.1137/S0097539796300921", - "url": "https://epubs.siam.org/doi/10.1137/S0097539796300921" - }, - { - "id": "toffoli_qutrits", - "type": "article", - "title": "Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits", - "authors": "Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/pdf/2109.00558.pdf" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Qutrits and quantum algorithms", + "authors": [ + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2023-05-09T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/qutrits_bernstein_vazirani/thumbnail_tutorial_qutrits_bernstein_vazirani.png" + } + ], + "seoDescription": "Learn how to interpret the Bernstein–Vazirani algorithm with qutrits", + "doi": "", + "references": [ + { + "id": "bv", + "type": "article", + "title": "Quantum Complexity Theory", + "authors": "Ethan Bernstein, Umesh Vazirani", + "year": "1997", + "journal": "SIAM Journal on Computing", + "volume": "26", + "doi": "10.1137/S0097539796300921", + "url": "https://epubs.siam.org/doi/10.1137/S0097539796300921" + }, + { + "id": "toffoli_qutrits", + "type": "article", + "title": "Implementing a Ternary Decomposition of the Toffoli Gate on Fixed-FrequencyTransmon Qutrits", + "authors": "Alexey Galda, Michael Cubeddu, Naoki Kanazawa, Prineha Narang, Nathan Earnest-Noble", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/pdf/2109.00558.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + ] +} diff --git a/demonstrations_v2/tutorial_resource_estimation/demo.py b/demonstrations_v2/tutorial_resource_estimation/demo.py index 10f554ef6d..c171a76578 100644 --- a/demonstrations_v2/tutorial_resource_estimation/demo.py +++ b/demonstrations_v2/tutorial_resource_estimation/demo.py @@ -313,3 +313,6 @@ # "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer". # `Phys. Rev. A 106, 032428 (2022) # `__ +# About the author +# ---------------- +# .. include:: ../_static/authors/soran_jahangiri.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_resource_estimation/metadata.json b/demonstrations_v2/tutorial_resource_estimation/metadata.json index fe2b5e20e3..9bc9fc0efe 100644 --- a/demonstrations_v2/tutorial_resource_estimation/metadata.json +++ b/demonstrations_v2/tutorial_resource_estimation/metadata.json @@ -1,74 +1,74 @@ { - "title": "Resource estimation for quantum chemistry", - "authors": [ - { - "username": "soran" - } - ], - "dateOfPublication": "2022-11-21T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_resource_estimation_QChem.png" - } - ], - "seoDescription": "Learn how to estimate the number of qubits and gates needed to implement quantum algorithms", - "doi": "", - "references": [ - { - "id": "vonburg2021", - "type": "article", - "title": "Quantum computing enhanced computational catalysis", - "authors": "Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, et al.", - "year": "2021", - "journal": "Phys. Rev. Research", - "url": "https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.3.033055" - }, - { - "id": "lee2021", - "type": "article", - "title": "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction", - "authors": "Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, et al.", - "year": "2021", - "journal": "PRX Quantum", - "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.030305" - }, - { - "id": "zini2023", - "type": "article", - "title": "Quantum simulation of battery materials using ionic pseudopotentials", - "authors": "Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, et al.", - "year": "2023", - "journal": "Quantum", - "url": "https://quantum-journal.org/papers/q-2023-07-10-1049" - }, - { - "id": "delgado2022", - "type": "article", - "title": "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer", - "authors": "Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, et al.", - "year": "2022", - "journal": "Phys. Rev. A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.106.032428" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Resource estimation for quantum chemistry", + "authors": [ + { + "username": "soran" + } + ], + "dateOfPublication": "2022-11-21T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_resource_estimation_QChem.png" + } + ], + "seoDescription": "Learn how to estimate the number of qubits and gates needed to implement quantum algorithms", + "doi": "", + "references": [ + { + "id": "vonburg2021", + "type": "article", + "title": "Quantum computing enhanced computational catalysis", + "authors": "Vera von Burg, Guang Hao Low, Thomas Haner, Damian S. Steiger, et al.", + "year": "2021", + "journal": "Phys. Rev. Research", + "url": "https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.3.033055" + }, + { + "id": "lee2021", + "type": "article", + "title": "Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction", + "authors": "Joonho Lee, Dominic W. Berry, Craig Gidney, William J. Huggins, et al.", + "year": "2021", + "journal": "PRX Quantum", + "url": "https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.030305" + }, + { + "id": "zini2023", + "type": "article", + "title": "Quantum simulation of battery materials using ionic pseudopotentials", + "authors": "Modjtaba Shokrian Zini, Alain Delgado, Roberto dos Reis, et al.", + "year": "2023", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2023-07-10-1049" + }, + { + "id": "delgado2022", + "type": "article", + "title": "Simulating key properties of lithium-ion batteries with a fault-tolerant quantum computer", + "authors": "Alain Delgado, Pablo A. M. Casares, Roberto dos Reis, Modjtaba Shokrian Zini, et al.", + "year": "2022", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.106.032428" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_rl_pulse/demo.py b/demonstrations_v2/tutorial_rl_pulse/demo.py index ab9e9fd98d..7fbc9eab20 100644 --- a/demonstrations_v2/tutorial_rl_pulse/demo.py +++ b/demonstrations_v2/tutorial_rl_pulse/demo.py @@ -1047,3 +1047,6 @@ def evolve_states(state, params, t): # ###################################################################### +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_rl_pulse/metadata.json b/demonstrations_v2/tutorial_rl_pulse/metadata.json index a257d0c8bd..4b0baf9076 100644 --- a/demonstrations_v2/tutorial_rl_pulse/metadata.json +++ b/demonstrations_v2/tutorial_rl_pulse/metadata.json @@ -1,128 +1,126 @@ { - "title": "Gate calibration with reinforcement learning", - "authors": [ - { - "username": "brequena" - } - ], - "dateOfPublication": "2024-04-09T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Getting Started", - "Optimization", - "Quantum Hardware" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/rl_pulse/thumbnail_RLpulse.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_RLpulse.png" - } - ], - "seoDescription": "Learn how to calibrate quantum computers with reinforcement learning", - "doi": "", - "references": [ - { - "id": "BaumPRXQ21", - "type": "article", - "title": "Experimental Deep Reinforcement Learning for Error-Robust Gate-Set Design on a Superconducting Quantum Computer", - "authors": "Baum, Yuval and Amico, Mirko and Howell, Sean and Hush, Michael and Liuzzi, Maggie and Mundada, Pranav and Merkh, Thomas and Carvalho, Andre R.R. and Biercuk, Michael J.", - "year": "2021", - "publisher": "American Physical Society", - "journal": "PRX Quantum", - "doi": "10.1103/PRXQuantum.2.040324", - "url": "https://link.aps.org/doi/10.1103/PRXQuantum.2.040324" - }, - { - "id": "Williams1992", - "type": "article", - "title": "Simple statistical gradient-following algorithms for connectionist reinforcement learning", - "authors": "Ronald J. Williams", - "year": "1992", - "publisher": "Springer", - "journal": "Machine Learning", - "doi": "10.1007/BF00992696", - "url": "https://link.springer.com/article/10.1007/BF00992696" - }, - { - "id": "Dawid22", - "type": "preprint", - "title": "Modern applications of machine learning in quantum sciences", - "authors": "Anna Dawid and Julian Arnold and Borja Requena and Alexander Gresch and Marcin P\u0142odzie\u0144 and Kaelan Donatella and Kim A. Nicoli and Paolo Stornati and Rouven Koch and Miriam B\u00fcttner and Robert Oku\u0142a and Gorka Mu\u00f1oz-Gil and Rodrigo A. Vargas-Hern\u00e1ndez and Alba Cervera-Lierta and Juan Carrasquilla and Vedran Dunjko and Marylou Gabri\u00e9 and Patrick Huembeli and Evert van Nieuwenburg and Filippo Vicentini and Lei Wang and Sebastian J. Wetzel and Giuseppe Carleo and Eli\u0161ka Greplov\u00e1 and Roman Krems and Florian Marquardt and Micha\u0142 Tomza and Maciej Lewenstein and Alexandre Dauphin", - "year": "2022", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2204.04198", - "url": "https://arxiv.org/abs/2204.04198" - }, - { - "id": "kingma14", - "type": "preprint", - "title": "Adam: A method for Stochastic Optimization", - "authors": "D. Kingma and J. Ba", - "year": "2014", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2303.11355", - "url": "https://arxiv.org/abs/2303.11355" - }, - { - "id": "SheldonPRA16", - "type": "article", - "title": "Procedure for systematically tuning up cross-talk in the cross-resonance gate", - "authors": "Sheldon, Sarah and Magesan, Easwar and Chow, Jerry M. and Gambetta, Jay M.", - "year": "2016", - "publisher": "American Physical Society", - "journal": "Phys. Rev. A", - "doi": "10.1103/PhysRevA.93.060302", - "url": "https://link.aps.org/doi/10.1103/PhysRevA.93.060302" - }, - { - "id": "SuttonBarto18", - "type": "book", - "title": "Reinforcement learning: An introduction", - "authors": "Sutton, Richard S and Barto, Andrew G", - "year": "2018", - "publisher": "MIT Press", - "journal": "", - "doi": "10.5555/980651.980663", - "url": "https://mitpress.mit.edu/9780262039246/reinforcement-learning/" - }, - { - "id": "KrantzAPR19", - "type": "article", - "title": "A quantum engineer's guide to superconducting qubits", - "authors": "Krantz, Philip and Kjaergaard, Morten and Yan, Fei and Orlando, Terry P and Gustavsson, Simon and Oliver, William D", - "year": "2019", - "publisher": "AIP Publishing", - "journal": "Applied physics reviews", - "doi": "10.1063/1.5089550", - "url": "https://pubs.aip.org/aip/apr/article/6/2/021318/570326/A-quantum-engineer-s-guide-to-superconducting" - } - ], - "basedOnPapers": [ - "10.1103/PRXQuantum.2.040324" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "oqc_pulse", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_pulse_programming101", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_optimal_control", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Gate calibration with reinforcement learning", + "authors": [ + { + "username": "brequena" + } + ], + "dateOfPublication": "2024-04-09T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Getting Started", + "Optimization", + "Quantum Hardware" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/rl_pulse/thumbnail_RLpulse.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_RLpulse.png" + } + ], + "seoDescription": "Learn how to calibrate quantum computers with reinforcement learning", + "doi": "", + "references": [ + { + "id": "BaumPRXQ21", + "type": "article", + "title": "Experimental Deep Reinforcement Learning for Error-Robust Gate-Set Design on a Superconducting Quantum Computer", + "authors": "Baum, Yuval and Amico, Mirko and Howell, Sean and Hush, Michael and Liuzzi, Maggie and Mundada, Pranav and Merkh, Thomas and Carvalho, Andre R.R. and Biercuk, Michael J.", + "year": "2021", + "publisher": "American Physical Society", + "journal": "PRX Quantum", + "doi": "10.1103/PRXQuantum.2.040324", + "url": "https://link.aps.org/doi/10.1103/PRXQuantum.2.040324" + }, + { + "id": "Williams1992", + "type": "article", + "title": "Simple statistical gradient-following algorithms for connectionist reinforcement learning", + "authors": "Ronald J. Williams", + "year": "1992", + "publisher": "Springer", + "journal": "Machine Learning", + "doi": "10.1007/BF00992696", + "url": "https://link.springer.com/article/10.1007/BF00992696" + }, + { + "id": "Dawid22", + "type": "preprint", + "title": "Modern applications of machine learning in quantum sciences", + "authors": "Anna Dawid and Julian Arnold and Borja Requena and Alexander Gresch and Marcin Płodzień and Kaelan Donatella and Kim A. Nicoli and Paolo Stornati and Rouven Koch and Miriam Büttner and Robert Okuła and Gorka Muñoz-Gil and Rodrigo A. Vargas-Hernández and Alba Cervera-Lierta and Juan Carrasquilla and Vedran Dunjko and Marylou Gabrié and Patrick Huembeli and Evert van Nieuwenburg and Filippo Vicentini and Lei Wang and Sebastian J. Wetzel and Giuseppe Carleo and Eliška Greplová and Roman Krems and Florian Marquardt and Michał Tomza and Maciej Lewenstein and Alexandre Dauphin", + "year": "2022", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2204.04198", + "url": "https://arxiv.org/abs/2204.04198" + }, + { + "id": "kingma14", + "type": "preprint", + "title": "Adam: A method for Stochastic Optimization", + "authors": "D. Kingma and J. Ba", + "year": "2014", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2303.11355", + "url": "https://arxiv.org/abs/2303.11355" + }, + { + "id": "SheldonPRA16", + "type": "article", + "title": "Procedure for systematically tuning up cross-talk in the cross-resonance gate", + "authors": "Sheldon, Sarah and Magesan, Easwar and Chow, Jerry M. and Gambetta, Jay M.", + "year": "2016", + "publisher": "American Physical Society", + "journal": "Phys. Rev. A", + "doi": "10.1103/PhysRevA.93.060302", + "url": "https://link.aps.org/doi/10.1103/PhysRevA.93.060302" + }, + { + "id": "SuttonBarto18", + "type": "book", + "title": "Reinforcement learning: An introduction", + "authors": "Sutton, Richard S and Barto, Andrew G", + "year": "2018", + "publisher": "MIT Press", + "journal": "", + "doi": "10.5555/980651.980663", + "url": "https://mitpress.mit.edu/9780262039246/reinforcement-learning/" + }, + { + "id": "KrantzAPR19", + "type": "article", + "title": "A quantum engineer's guide to superconducting qubits", + "authors": "Krantz, Philip and Kjaergaard, Morten and Yan, Fei and Orlando, Terry P and Gustavsson, Simon and Oliver, William D", + "year": "2019", + "publisher": "AIP Publishing", + "journal": "Applied physics reviews", + "doi": "10.1063/1.5089550", + "url": "https://pubs.aip.org/aip/apr/article/6/2/021318/570326/A-quantum-engineer-s-guide-to-superconducting" + } + ], + "basedOnPapers": ["10.1103/PRXQuantum.2.040324"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "oqc_pulse", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_pulse_programming101", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_optimal_control", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_rosalin/demo.py b/demonstrations_v2/tutorial_rosalin/demo.py index 8a3a8cbc4f..117b1ed410 100644 --- a/demonstrations_v2/tutorial_rosalin/demo.py +++ b/demonstrations_v2/tutorial_rosalin/demo.py @@ -661,3 +661,6 @@ def cost(weights): # `__ (2020). # # +# About the author +# ---------------- +# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_rosalin/metadata.json b/demonstrations_v2/tutorial_rosalin/metadata.json index 3eeee3973e..c2e840380e 100644 --- a/demonstrations_v2/tutorial_rosalin/metadata.json +++ b/demonstrations_v2/tutorial_rosalin/metadata.json @@ -1,86 +1,84 @@ { - "title": "Frugal shot optimization with Rosalin", - "authors": [ - { - "username": "josh" - } - ], - "dateOfPublication": "2020-05-19T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_frugal_shot_optimization_Rosalin.png" - } - ], - "seoDescription": "The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the number of times a quantum computer is accessed.", - "doi": "", - "references": [ - { - "id": "arrasmith2020", - "type": "article", - "title": "Operator Sampling for Shot-frugal Optimization in Variational Algorithms.", - "authors": "Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2004.06252" - }, - { - "id": "stokes2019", - "type": "article", - "title": "Quantum Natural Gradient.", - "authors": "James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1909.02108" - }, - { - "id": "sweke2019", - "type": "article", - "title": "Stochastic gradient descent for hybrid quantum-classical optimization.", - "authors": "Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. F\u00e4hrmann, Barth\u00e9l\u00e9my Meynard-Piganeau, and Jens Eisert", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1910.01155" - }, - { - "id": "kubler2020", - "type": "article", - "title": "An Adaptive Optimizer for Measurement-Frugal Variational Algorithms.", - "authors": "Jonas M. K\u00fcbler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles", - "year": "2020", - "journal": "Quantum", - "url": "https://quantum-journal.org/papers/q-2020-05-11-263/" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2004.06252" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_natural_gradient", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_doubly_stochastic", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_rotoselect", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Frugal shot optimization with Rosalin", + "authors": [ + { + "username": "josh" + } + ], + "dateOfPublication": "2020-05-19T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_frugal_shot_optimization_Rosalin.png" + } + ], + "seoDescription": "The Rosalin optimizer uses a measurement-frugal optimization strategy to minimize the number of times a quantum computer is accessed.", + "doi": "", + "references": [ + { + "id": "arrasmith2020", + "type": "article", + "title": "Operator Sampling for Shot-frugal Optimization in Variational Algorithms.", + "authors": "Andrew Arrasmith, Lukasz Cincio, Rolando D. Somma, and Patrick J. Coles", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2004.06252" + }, + { + "id": "stokes2019", + "type": "article", + "title": "Quantum Natural Gradient.", + "authors": "James Stokes, Josh Izaac, Nathan Killoran, and Giuseppe Carleo", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.02108" + }, + { + "id": "sweke2019", + "type": "article", + "title": "Stochastic gradient descent for hybrid quantum-classical optimization.", + "authors": "Ryan Sweke, Frederik Wilde, Johannes Jakob Meyer, Maria Schuld, Paul K. Fährmann, Barthélémy Meynard-Piganeau, and Jens Eisert", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.01155" + }, + { + "id": "kubler2020", + "type": "article", + "title": "An Adaptive Optimizer for Measurement-Frugal Variational Algorithms.", + "authors": "Jonas M. Kübler, Andrew Arrasmith, Lukasz Cincio, and Patrick J. Coles", + "year": "2020", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2020-05-11-263/" + } + ], + "basedOnPapers": ["10.48550/arXiv.2004.06252"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_doubly_stochastic", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rotoselect", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_rotoselect/demo.py b/demonstrations_v2/tutorial_rotoselect/demo.py index aeccba97f2..430103293d 100644 --- a/demonstrations_v2/tutorial_rotoselect/demo.py +++ b/demonstrations_v2/tutorial_rotoselect/demo.py @@ -465,3 +465,6 @@ def rotoselect_cycle(cost, params, generators): # `arxiv:1905.09692 `__, 2019. ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/angus_lowe.txt diff --git a/demonstrations_v2/tutorial_rotoselect/metadata.json b/demonstrations_v2/tutorial_rotoselect/metadata.json index 79244eef57..47be4f2716 100644 --- a/demonstrations_v2/tutorial_rotoselect/metadata.json +++ b/demonstrations_v2/tutorial_rotoselect/metadata.json @@ -1,54 +1,52 @@ { - "title": "Quantum circuit structure learning", - "authors": [ - { - "username": "alowe" - } - ], - "dateOfPublication": "2019-10-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_circuit_structure_learning.png" - } - ], - "seoDescription": "Applying the Rotoselect optimization algorithm to find the minimum in a variational quantum algorithm.", - "doi": "", - "references": [ - { - "id": "Ostaszewski2019", - "type": "article", - "title": "Quantum circuit structure learning.", - "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Bendetti", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1905.09692" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1905.09692" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe_qng", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_rosalin", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum circuit structure learning", + "authors": [ + { + "username": "alowe" + } + ], + "dateOfPublication": "2019-10-16T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_quantum_circuit_structure_learning.png" + } + ], + "seoDescription": "Applying the Rotoselect optimization algorithm to find the minimum in a variational quantum algorithm.", + "doi": "", + "references": [ + { + "id": "Ostaszewski2019", + "type": "article", + "title": "Quantum circuit structure learning.", + "authors": "Mateusz Ostaszewski, Edward Grant, Marcello Bendetti", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1905.09692" + } + ], + "basedOnPapers": ["10.48550/arXiv.1905.09692"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_rosalin", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_sc_qubits/demo.py b/demonstrations_v2/tutorial_sc_qubits/demo.py index eb02c2d1a2..9de73a5a3d 100644 --- a/demonstrations_v2/tutorial_sc_qubits/demo.py +++ b/demonstrations_v2/tutorial_sc_qubits/demo.py @@ -874,3 +874,6 @@ def H_evolve(state, phi, time): # `__. # IBM Research Blog. Retrieved 2022-03-15 # +# About the author +# ~~~~~~~~~~~~~~~~ +# .. include:: ../_static/authors/alvaro_ballon.txt diff --git a/demonstrations_v2/tutorial_sc_qubits/metadata.json b/demonstrations_v2/tutorial_sc_qubits/metadata.json index cfdf626252..43da743cf9 100644 --- a/demonstrations_v2/tutorial_sc_qubits/metadata.json +++ b/demonstrations_v2/tutorial_sc_qubits/metadata.json @@ -1,117 +1,117 @@ { - "title": "Quantum computing with superconducting qubits", - "authors": [ - { - "username": "alvaro" - } - ], - "dateOfPublication": "2022-03-22T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QC_superconducting_qubits.png" - } - ], - "seoDescription": "Learn about quantum computers based on superconducting qubits, developed by companies such as IBM and Google.", - "doi": "", - "references": [ - { - "id": "Google2019", - "type": "article", - "title": "Quantum supremacy using a programmable superconducting processor", - "authors": "Arute, F., Arya, K., Babbush, R. et al.", - "year": "2019", - "journal": "Nature", - "url": "https://www.nature.com/articles/s41586-019-1666-5" - }, - { - "id": "IBM2021", - "type": "article", - "title": "IBM Unveils Breakthrough 127-Qubit Quantum Processor", - "authors": "", - "year": "2021", - "journal": "", - "url": "https://newsroom.ibm.com/2021-11-16-IBM-Unveils-Breakthrough-127-Qubit-Quantum-Processor" - }, - { - "id": "DiVincenzo2000", - "type": "article", - "title": "The Physical Implementation of Quantum Computation", - "authors": "D. DiVincenzo", - "year": "2000", - "journal": "Fortschritte der Physik", - "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" - }, - { - "id": "Bergou2021", - "type": "book", - "title": "Quantum Information Processing", - "authors": "Bergou, J., Hillery, M., and Saffman, M.", - "year": "2021", - "publisher": "Springer", - "url": "" - }, - { - "id": "Blais2021", - "type": "article", - "title": "Circuit quantum electrodynamics", - "authors": "Blais, A., Grimsmo, A., Girvin, S., and Walraff, A.", - "year": "2021", - "journal": "Rev. Mod. Phys.", - "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.93.025005" - }, - { - "id": "Schuch2003", - "type": "article", - "title": "Natural two-qubit gate for quantum computation using the XY interaction", - "authors": "Schuch, N., Siewert, J.", - "year": "2003", - "journal": "Phys. Rev. A", - "url": "https://doi.org/10.1103/PhysRevA.67.032301" - }, - { - "id": "Rigetti2003", - "type": "article", - "title": "Fully microwave-tunable universal gates in superconducting qubits with linear couplings and fixed transition frequencies", - "authors": "Rigetti, C., Devoret, M.", - "year": "2009", - "journal": "Phys. Rev. B", - "url": "https://journals.aps.org/prb/abstract/10.1103/PhysRevB.81.134507" - }, - { - "id": "IBMHex2021", - "type": "article", - "title": "The IBM Quantum heavy hex lattice", - "authors": "", - "year": "2021", - "journal": "", - "url": "https://research.ibm.com/blog/heavy-hex-lattice" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pasqal", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_trapped_ions", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_photonics", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/quantum-computing-with-superconducting-qubits-demo/7336" -} \ No newline at end of file + "title": "Quantum computing with superconducting qubits", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2022-03-22T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_QC_superconducting_qubits.png" + } + ], + "seoDescription": "Learn about quantum computers based on superconducting qubits, developed by companies such as IBM and Google.", + "doi": "", + "references": [ + { + "id": "Google2019", + "type": "article", + "title": "Quantum supremacy using a programmable superconducting processor", + "authors": "Arute, F., Arya, K., Babbush, R. et al.", + "year": "2019", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-019-1666-5" + }, + { + "id": "IBM2021", + "type": "article", + "title": "IBM Unveils Breakthrough 127-Qubit Quantum Processor", + "authors": "", + "year": "2021", + "journal": "", + "url": "https://newsroom.ibm.com/2021-11-16-IBM-Unveils-Breakthrough-127-Qubit-Quantum-Processor" + }, + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "D. DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" + }, + { + "id": "Bergou2021", + "type": "book", + "title": "Quantum Information Processing", + "authors": "Bergou, J., Hillery, M., and Saffman, M.", + "year": "2021", + "publisher": "Springer", + "url": "" + }, + { + "id": "Blais2021", + "type": "article", + "title": "Circuit quantum electrodynamics", + "authors": "Blais, A., Grimsmo, A., Girvin, S., and Walraff, A.", + "year": "2021", + "journal": "Rev. Mod. Phys.", + "url": "https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.93.025005" + }, + { + "id": "Schuch2003", + "type": "article", + "title": "Natural two-qubit gate for quantum computation using the XY interaction", + "authors": "Schuch, N., Siewert, J.", + "year": "2003", + "journal": "Phys. Rev. A", + "url": "https://doi.org/10.1103/PhysRevA.67.032301" + }, + { + "id": "Rigetti2003", + "type": "article", + "title": "Fully microwave-tunable universal gates in superconducting qubits with linear couplings and fixed transition frequencies", + "authors": "Rigetti, C., Devoret, M.", + "year": "2009", + "journal": "Phys. Rev. B", + "url": "https://journals.aps.org/prb/abstract/10.1103/PhysRevB.81.134507" + }, + { + "id": "IBMHex2021", + "type": "article", + "title": "The IBM Quantum heavy hex lattice", + "authors": "", + "year": "2021", + "journal": "", + "url": "https://research.ibm.com/blog/heavy-hex-lattice" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_trapped_ions", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/quantum-computing-with-superconducting-qubits-demo/7336" +} diff --git a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py index 84ac1fda71..97a034bf07 100644 --- a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py +++ b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/demo.py @@ -361,3 +361,6 @@ def shadow_evolve(H_S_qubit, O_0, t): ############################################################################## +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json index 8575f38484..e4ede62383 100644 --- a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json +++ b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/metadata.json @@ -1,122 +1,121 @@ { - "title": "Shadow Hamiltonian simulation", - "authors": [ - { - "username": "Qottmann" - } - ], - "dateOfPublication": "2024-08-06T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing", - "Algorithms" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_shadow_hamiltonian_simulation.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_shadow_hamiltonian_simulation.png" - } - ], - "seoDescription": "A user-friendly intro to the contents of shadow Hamiltonian simulation, a technique introduced by by Somma et al (arXiv:2407.21775), with a code implementation.", - "doi": "", - "references": [ - { - "id": "Wiersema", - "type": "preprint", - "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", - "authors": "Roeland Wiersema, Efekan K\u00f6kc\u00fc, Alexander F. Kemper, Bojko N. Bakalov", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2309.05690", - "url": "https://arxiv.org/abs/2309.05690" - }, - { - "id": "Goh", - "type": "preprint", - "title": "Lie-algebraic classical simulations for variational quantum computing", - "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Fr\u00e9d\u00e9ric Sauvage", - "year": "2023", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2308.01432", - "url": "https://arxiv.org/abs/2308.01432" - }, - { - "id": "Somma", - "type": "preprint", - "title": "Quantum Computation, Complexity, and Many-Body Physics", - "authors": "Rolando D. Somma", - "year": "2005", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0512209", - "url": "https://arxiv.org/abs/quant-ph/0512209" - }, - { - "id": "Somma2", - "type": "preprint", - "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", - "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", - "year": "2006", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.quant-ph/0601030", - "url": "https://arxiv.org/abs/quant-ph/0601030" - }, - { - "id": "Galitski", - "type": "preprint", - "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", - "authors": "Victor Galitski", - "year": "2010", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.1012.2873", - "url": "https://arxiv.org/abs/1012.2873" - }, - { - "id": "Aguilar", - "type": "preprint", - "title": "Full classification of Pauli Lie algebras", - "authors": "Gerard Aguilar, Simon Cichy, Jens Eisert, Lennart Bittel", - "year": "2024", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2408.00081", - "url": "https://arxiv.org/abs/2408.00081" - }, - { - "id": "SommaShadow", - "type": "preprint", - "title": "Shadow Hamiltonian Simulation", - "authors": "olando D. Somma, Robbie King, Robin Kothari, Thomas O'Brien, Ryan Babbush", - "year": "2024", - "publisher": "", - "journal": "", - "doi": "10.48550/arXiv.2407.21775", - "url": "https://arxiv.org/abs/2407.21775" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2407.21775" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_liealgebra", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_liesim", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Shadow Hamiltonian simulation", + "authors": [ + { + "username": "Qottmann" + } + ], + "dateOfPublication": "2024-08-06T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_shadow_hamiltonian_simulation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_shadow_hamiltonian_simulation.png" + } + ], + "seoDescription": "A user-friendly intro to the contents of shadow Hamiltonian simulation, a technique introduced by by Somma et al (arXiv:2407.21775), with a code implementation.", + "doi": "", + "references": [ + { + "id": "Wiersema", + "type": "preprint", + "title": "Classification of dynamical Lie algebras for translation-invariant 2-local spin systems in one dimension", + "authors": "Roeland Wiersema, Efekan Kökcü, Alexander F. Kemper, Bojko N. Bakalov", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2309.05690", + "url": "https://arxiv.org/abs/2309.05690" + }, + { + "id": "Goh", + "type": "preprint", + "title": "Lie-algebraic classical simulations for variational quantum computing", + "authors": "Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "Somma", + "type": "preprint", + "title": "Quantum Computation, Complexity, and Many-Body Physics", + "authors": "Rolando D. Somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "Somma2", + "type": "preprint", + "title": "Efficient solvability of Hamiltonians and limits on the power of some quantum computational models", + "authors": "Rolando Somma, Howard Barnum, Gerardo Ortiz, Emanuel Knill", + "year": "2006", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.quant-ph/0601030", + "url": "https://arxiv.org/abs/quant-ph/0601030" + }, + { + "id": "Galitski", + "type": "preprint", + "title": "Quantum-to-Classical Correspondence and Hubbard-Stratonovich Dynamical Systems, a Lie-Algebraic Approach", + "authors": "Victor Galitski", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.1012.2873", + "url": "https://arxiv.org/abs/1012.2873" + }, + { + "id": "Aguilar", + "type": "preprint", + "title": "Full classification of Pauli Lie algebras", + "authors": "Gerard Aguilar, Simon Cichy, Jens Eisert, Lennart Bittel", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2408.00081", + "url": "https://arxiv.org/abs/2408.00081" + } + , + { + "id": "SommaShadow", + "type": "preprint", + "title": "Shadow Hamiltonian Simulation", + "authors": "olando D. Somma, Robbie King, Robin Kothari, Thomas O'Brien, Ryan Babbush", + "year": "2024", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.2407.21775", + "url": "https://arxiv.org/abs/2407.21775" + } + ], + "basedOnPapers": ["10.48550/arXiv.2407.21775"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_spsa/demo.py b/demonstrations_v2/tutorial_spsa/demo.py index 9f7bd07bf3..7344e643ea 100644 --- a/demonstrations_v2/tutorial_spsa/demo.py +++ b/demonstrations_v2/tutorial_spsa/demo.py @@ -552,3 +552,7 @@ def circuit(param): # `Quantum, 5, 567 `__, Oct 2021 ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/antal_szava.txt +# .. include:: ../_static/authors/david_wierichs.txt diff --git a/demonstrations_v2/tutorial_spsa/metadata.json b/demonstrations_v2/tutorial_spsa/metadata.json index 252077a34a..b1753f818d 100644 --- a/demonstrations_v2/tutorial_spsa/metadata.json +++ b/demonstrations_v2/tutorial_spsa/metadata.json @@ -1,90 +1,90 @@ { - "title": "Optimization using SPSA", - "authors": [ - { - "username": "aszava" - }, - { - "username": "dwierichs" - } - ], - "dateOfPublication": "2023-03-19T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimization_SPSA.png" - } - ], - "seoDescription": "Use the simultaneous perturbation stochastic approximation algorithm to optimize variational circuits in PennyLane.", - "doi": "", - "references": [ - { - "id": "spall_overview", - "type": "article", - "title": "An Overview of the Simultaneous Perturbation Method for Efficient Optimization", - "authors": "James C. Spall", - "year": "1998", - "journal": "", - "url": "https://www.jhuapl.edu/SPSA/PDF-SPSA/Spall_An_Overview.PDF" - }, - { - "id": "spall_implementation", - "type": "article", - "title": "Implementation of the simultaneous perturbation algorithm for stochastic optimization", - "authors": "J. C. Spall", - "year": "1998", - "journal": "IEEE Transactions on Aerospace and Electronic Systems", - "volume": "34", - "number": "3", - "pages": "817-823", - "doi": "10.1109/7.705889", - "url": "" - }, - { - "id": "spall_hessian", - "type": "article", - "title": "Adaptive stochastic approximation by the simultaneous perturbation method", - "authors": "J. C. Spall", - "year": "2020", - "journal": "IEEE Transactions on Automatic Control", - "volume": "45", - "number": "10", - "pages": "1839-1853", - "doi": "10.1109/TAC.2000.880982", - "url": "" - }, - { - "id": "qnspsa", - "type": "article", - "title": "Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information", - "authors": "J. Gacon, C. Zoufal, G. Carleo, and S. Woerner", - "year": "2021", - "journal": "Quantum", - "url": "https://quantum-journal.org/papers/q-2021-10-20-567/" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe_qng", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "qnspsa", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Optimization using SPSA", + "authors": [ + { + "username": "aszava" + }, + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2023-03-19T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_optimization_SPSA.png" + } + ], + "seoDescription": "Use the simultaneous perturbation stochastic approximation algorithm to optimize variational circuits in PennyLane.", + "doi": "", + "references": [ + { + "id": "spall_overview", + "type": "article", + "title": "An Overview of the Simultaneous Perturbation Method for Efficient Optimization", + "authors": "James C. Spall", + "year": "1998", + "journal": "", + "url": "https://www.jhuapl.edu/SPSA/PDF-SPSA/Spall_An_Overview.PDF" + }, + { + "id": "spall_implementation", + "type": "article", + "title": "Implementation of the simultaneous perturbation algorithm for stochastic optimization", + "authors": "J. C. Spall", + "year": "1998", + "journal": "IEEE Transactions on Aerospace and Electronic Systems", + "volume": "34", + "number": "3", + "pages": "817-823", + "doi": "10.1109/7.705889", + "url": "" + }, + { + "id": "spall_hessian", + "type": "article", + "title": "Adaptive stochastic approximation by the simultaneous perturbation method", + "authors": "J. C. Spall", + "year": "2020", + "journal": "IEEE Transactions on Automatic Control", + "volume": "45", + "number": "10", + "pages": "1839-1853", + "doi": "10.1109/TAC.2000.880982", + "url": "" + }, + { + "id": "qnspsa", + "type": "article", + "title": "Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information", + "authors": "J. Gacon, C. Zoufal, G. Carleo, and S. Woerner", + "year": "2021", + "journal": "Quantum", + "url": "https://quantum-journal.org/papers/q-2021-10-20-567/" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qnspsa", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_state_preparation/demo.py b/demonstrations_v2/tutorial_state_preparation/demo.py index 3e07e43190..2d54848218 100644 --- a/demonstrations_v2/tutorial_state_preparation/demo.py +++ b/demonstrations_v2/tutorial_state_preparation/demo.py @@ -196,3 +196,6 @@ def cost_fn(params): print("Output Bloch vector = ", output_bloch_v) ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_state_preparation/metadata.json b/demonstrations_v2/tutorial_state_preparation/metadata.json index 30f43a8192..a519c29a43 100644 --- a/demonstrations_v2/tutorial_state_preparation/metadata.json +++ b/demonstrations_v2/tutorial_state_preparation/metadata.json @@ -1,42 +1,42 @@ { - "title": "Training a quantum circuit with PyTorch", - "authors": [ - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Devices and Performance" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tarining_QC_with_PyTorch.png" - } - ], - "seoDescription": "Build and optimize a circuit to prepare arbitrary single-qubit states, including mixed states, with PyTorch and PennyLane.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qubit_rotation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "pytorch_noise", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_isingmodel_PyTorch", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Training a quantum circuit with PyTorch", + "authors": [ + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Devices and Performance" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tarining_QC_with_PyTorch.png" + } + ], + "seoDescription": "Build and optimize a circuit to prepare arbitrary single-qubit states, including mixed states, with PyTorch and PennyLane.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qubit_rotation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "pytorch_noise", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_isingmodel_PyTorch", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py b/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py index 23007029a1..e1b45f06bd 100644 --- a/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py +++ b/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py @@ -414,3 +414,6 @@ def spsr_circuit(gate_pars, s=None, sign=+1): # `arXiv:1811.11184 `__ (2019). # # +# About the author +# ---------------- +# .. include:: ../_static/authors/nathan_killoran.txt diff --git a/demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json b/demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json index 20ee733061..a952a7939f 100644 --- a/demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json +++ b/demonstrations_v2/tutorial_stochastic_parameter_shift/metadata.json @@ -1,74 +1,74 @@ { - "title": "The stochastic parameter-shift rule", - "authors": [ - { - "username": "co9olguy" - } - ], - "dateOfPublication": "2020-05-25T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_stochastic_parametershift_rule.png" - } - ], - "seoDescription": "Differentiate any qubit gate with the stochastic parameter-shift rule.", - "doi": "", - "references": [ - { - "id": "banchi2020", - "type": "article", - "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule.", - "authors": "Leonardo Banchi and Gavin E. Crooks", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/2005.10299" - }, - { - "id": "li2016", - "type": "article", - "title": "Hybrid Quantum-Classical Approach to Quantum Optimal Control.", - "authors": "Jun Li, Xiaodong Yang, Xinhua Peng, and Chang-Pu Sun", - "year": "2016", - "journal": "", - "url": "https://arxiv.org/abs/1608.00677" - }, - { - "id": "mitarai2018", - "type": "article", - "title": "Quantum Circuit Learning", - "authors": "Kosuke Mitarai, Makoto Negoro, Masahiro Kitagawa, and Keisuke Fujii", - "year": "2020", - "journal": "", - "url": "https://arxiv.org/abs/1803.00745" - }, - { - "id": "schuld2018", - "type": "article", - "title": "Evaluating analytic gradients on quantum hardware.", - "authors": "Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, and Nathan Killoran", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1811.11184" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_backprop", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_general_parshift", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "The stochastic parameter-shift rule", + "authors": [ + { + "username": "co9olguy" + } + ], + "dateOfPublication": "2020-05-25T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_stochastic_parametershift_rule.png" + } + ], + "seoDescription": "Differentiate any qubit gate with the stochastic parameter-shift rule.", + "doi": "", + "references": [ + { + "id": "banchi2020", + "type": "article", + "title": "Measuring Analytic Gradients of General Quantum Evolution with the Stochastic Parameter Shift Rule.", + "authors": "Leonardo Banchi and Gavin E. Crooks", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/2005.10299" + }, + { + "id": "li2016", + "type": "article", + "title": "Hybrid Quantum-Classical Approach to Quantum Optimal Control.", + "authors": "Jun Li, Xiaodong Yang, Xinhua Peng, and Chang-Pu Sun", + "year": "2016", + "journal": "", + "url": "https://arxiv.org/abs/1608.00677" + }, + { + "id": "mitarai2018", + "type": "article", + "title": "Quantum Circuit Learning", + "authors": "Kosuke Mitarai, Makoto Negoro, Masahiro Kitagawa, and Keisuke Fujii", + "year": "2020", + "journal": "", + "url": "https://arxiv.org/abs/1803.00745" + }, + { + "id": "schuld2018", + "type": "article", + "title": "Evaluating analytic gradients on quantum hardware.", + "authors": "Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, and Nathan Killoran", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1811.11184" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_general_parshift", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_teleportation/demo.py b/demonstrations_v2/tutorial_teleportation/demo.py index ac3e5f7953..3c1d7fad2a 100644 --- a/demonstrations_v2/tutorial_teleportation/demo.py +++ b/demonstrations_v2/tutorial_teleportation/demo.py @@ -434,3 +434,7 @@ def teleport_state(state): ############################################################################## # +# About the author +# ---------------- +# +# .. include:: ../_static/authors/matthew_silverman.txt diff --git a/demonstrations_v2/tutorial_teleportation/metadata.json b/demonstrations_v2/tutorial_teleportation/metadata.json index e8d825c761..e96483a6f7 100644 --- a/demonstrations_v2/tutorial_teleportation/metadata.json +++ b/demonstrations_v2/tutorial_teleportation/metadata.json @@ -1,74 +1,74 @@ { - "title": "Quantum Teleportation", - "authors": [ - { - "username": "timmysilv" - } - ], - "dateOfPublication": "2023-10-20T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Getting Started", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/teleportation/thumbnail_teleportation.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_teleportation.png" - } - ], - "seoDescription": "Transmit arbitrary quantum states using quantum teleportation", - "doi": "", - "references": [ - { - "id": "Teleportation1993", - "type": "article", - "title": "Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels", - "authors": "C. H. Bennett, G. Brassard, C. Cr\u00e9peau, R. Jozsa, A. Peres, W. K. Wootters", - "year": "1993", - "publisher": "", - "journal": "Phys. Rev. Lett.", - "doi": "10.1103/PhysRevLett.70.1895", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.70.1895" - }, - { - "id": "NandC2000", - "type": "book", - "title": "Quantum Computation and Quantum Information", - "authors": "M. A. Nielsen, and I. L. Chuang", - "year": "2000", - "publisher": "Cambridge University Press", - "journal": "", - "url": "" - }, - { - "id": "Codebook", - "type": "webpage", - "title": "Xanadu Quantum Codebook", - "authors": "C. Albornoz, G. Alonso, M. Andrenkov, P. Angara, A. Asadi, A. Ballon, S. Bapat, I. De Vlugt, O. Di Matteo, P. Finlay, A. Fumagalli, A. Gardhouse, N. Girard, A. Hayes, J. Izaac, R. Janik, T. Kalajdzievski, N. Killoran, J. Soni, D. Wakeham", - "year": "2021", - "publisher": "", - "journal": "", - "url": "https://codebook.xanadu.ai/I.15" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_zx_calculus", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_mbqc", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Quantum Teleportation", + "authors": [ + { + "username": "timmysilv" + } + ], + "dateOfPublication": "2023-10-20T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/teleportation/thumbnail_teleportation.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_teleportation.png" + } + ], + "seoDescription": "Transmit arbitrary quantum states using quantum teleportation", + "doi": "", + "references": [ + { + "id": "Teleportation1993", + "type": "article", + "title": "Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels", + "authors": "C. H. Bennett, G. Brassard, C. Crépeau, R. Jozsa, A. Peres, W. K. Wootters", + "year": "1993", + "publisher": "", + "journal": "Phys. Rev. Lett.", + "doi": "10.1103/PhysRevLett.70.1895", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.70.1895" + }, + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "journal": "", + "url": "" + }, + { + "id": "Codebook", + "type": "webpage", + "title": "Xanadu Quantum Codebook", + "authors": "C. Albornoz, G. Alonso, M. Andrenkov, P. Angara, A. Asadi, A. Ballon, S. Bapat, I. De Vlugt, O. Di Matteo, P. Finlay, A. Fumagalli, A. Gardhouse, N. Girard, A. Hayes, J. Izaac, R. Janik, T. Kalajdzievski, N. Killoran, J. Soni, D. Wakeham", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://codebook.xanadu.ai/I.15" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_zx_calculus", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_testing_symmetry/demo.py b/demonstrations_v2/tutorial_testing_symmetry/demo.py index 1f9849788c..d3bffa531e 100644 --- a/demonstrations_v2/tutorial_testing_symmetry/demo.py +++ b/demonstrations_v2/tutorial_testing_symmetry/demo.py @@ -463,3 +463,6 @@ def asymm(hamiltonian, time): # ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/david_wakeham.txt diff --git a/demonstrations_v2/tutorial_testing_symmetry/metadata.json b/demonstrations_v2/tutorial_testing_symmetry/metadata.json index d4657b48b2..2a7ca1c3df 100644 --- a/demonstrations_v2/tutorial_testing_symmetry/metadata.json +++ b/demonstrations_v2/tutorial_testing_symmetry/metadata.json @@ -1,49 +1,47 @@ { - "title": "Testing for symmetry with quantum computers", - "authors": [ - { - "username": "hapax" - } - ], - "dateOfPublication": "2023-01-24T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/testing_symmetry/thumbnail_tutorial_testing_symmetry.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_testing_symmetry.png" - } - ], - "seoDescription": "Test if a system possesses discrete symmetries", - "doi": "", - "references": [ - { - "id": "LaBorde2022", - "type": "article", - "title": "Quantum Algorithms for Testing Hamiltonian Symmetry", - "authors": "LaBorde, M. L and Wilde, M.M.", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/pdf/2203.10017.pdf" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2203.10017" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_geometric_qml", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Testing for symmetry with quantum computers", + "authors": [ + { + "username": "hapax" + } + ], + "dateOfPublication": "2023-01-24T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/testing_symmetry/thumbnail_tutorial_testing_symmetry.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_testing_symmetry.png" + } + ], + "seoDescription": "Test if a system possesses discrete symmetries", + "doi": "", + "references": [ + { + "id": "LaBorde2022", + "type": "article", + "title": "Quantum Algorithms for Testing Hamiltonian Symmetry", + "authors": "LaBorde, M. L and Wilde, M.M.", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/pdf/2203.10017.pdf" + } + ], + "basedOnPapers": ["10.48550/arXiv.2203.10017"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_geometric_qml", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_tn_circuits/demo.py b/demonstrations_v2/tutorial_tn_circuits/demo.py index f1d412c8a5..e317690f13 100644 --- a/demonstrations_v2/tutorial_tn_circuits/demo.py +++ b/demonstrations_v2/tutorial_tn_circuits/demo.py @@ -416,3 +416,12 @@ def costfunc(params): # 4916, URL https://www.sciencedirect.com/science/article/pii/S0003491614001596. # # +# About the authors +# ^^^^^^^^^^^^^^^^^ +# .. include:: ../_static/authors/diego_guala.txt +# +# .. include:: ../_static/authors/esther_cruz-rico.txt +# +# .. include:: ../_static/authors/shaoming_zhang.txt +# +# .. include:: ../_static/authors/juan_miguel_arrazola.txt diff --git a/demonstrations_v2/tutorial_tn_circuits/metadata.json b/demonstrations_v2/tutorial_tn_circuits/metadata.json index 7ddf0ce33d..9663375543 100644 --- a/demonstrations_v2/tutorial_tn_circuits/metadata.json +++ b/demonstrations_v2/tutorial_tn_circuits/metadata.json @@ -1,63 +1,61 @@ { - "title": "Tensor-network quantum circuits", - "authors": [ - { - "username": "Diego" - }, - { - "username": "ecrico" - }, - { - "username": "szhang" - }, - { - "username": "ixfoduap" - } - ], - "dateOfPublication": "2022-03-29T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tensor_network_quantum_circuits.png" - } - ], - "seoDescription": "This demonstration explains how to simulate tensor-network quantum circuits.", - "doi": "", - "references": [ - { - "id": "huggins", - "type": "article", - "title": "Towards quantum machine learning with tensor networks", - "authors": "W. Huggins, P. Patil, B. Mitchell, K. B. Whaley, and E. M. Stoudenmire", - "year": "2019", - "journal": "Quantum Science and Technology", - "url": "http://dx.doi.org/10.1088/2058-9565/aaea94" - }, - { - "id": "orus", - "type": "article", - "title": "A practical introduction to tensor networks: Matrix product states and projected entangled pair states", - "authors": "R. Or\u00fas", - "year": "2014", - "journal": "Annals of Physics", - "url": "https://www.sciencedirect.com/science/article/pii/S0003491614001596" - } - ], - "basedOnPapers": [ - "10.1088/2058-9565/aaea94" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_variational_classifier", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/tensor-network-quantum-circuits-demo/7340" -} \ No newline at end of file + "title": "Tensor-network quantum circuits", + "authors": [ + { + "username": "Diego" + }, + { + "username": "ecrico" + }, + { + "username": "szhang" + }, + { + "username": "ixfoduap" + } + ], + "dateOfPublication": "2022-03-29T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tensor_network_quantum_circuits.png" + } + ], + "seoDescription": "This demonstration explains how to simulate tensor-network quantum circuits.", + "doi": "", + "references": [ + { + "id": "huggins", + "type": "article", + "title": "Towards quantum machine learning with tensor networks", + "authors": "W. Huggins, P. Patil, B. Mitchell, K. B. Whaley, and E. M. Stoudenmire", + "year": "2019", + "journal": "Quantum Science and Technology", + "url": "http://dx.doi.org/10.1088/2058-9565/aaea94" + }, + { + "id": "orus", + "type": "article", + "title": "A practical introduction to tensor networks: Matrix product states and projected entangled pair states", + "authors": "R. Orús", + "year": "2014", + "journal": "Annals of Physics", + "url": "https://www.sciencedirect.com/science/article/pii/S0003491614001596" + } + ], + "basedOnPapers": ["10.1088/2058-9565/aaea94"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_variational_classifier", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/tensor-network-quantum-circuits-demo/7340" +} diff --git a/demonstrations_v2/tutorial_toric_code/demo.py b/demonstrations_v2/tutorial_toric_code/demo.py index f36824b2c1..6ac3f0f783 100644 --- a/demonstrations_v2/tutorial_toric_code/demo.py +++ b/demonstrations_v2/tutorial_toric_code/demo.py @@ -1012,3 +1012,6 @@ def hadamard_test(x_prep, z_prep, x_loop, z_loop): # `Reviews in Mathematical Physics 32.02 (2020): 2030002. `__ # (`arXiv `__) # +# About the author +# ---------------- +# .. include:: ../_static/authors/christina_lee.txt diff --git a/demonstrations_v2/tutorial_toric_code/metadata.json b/demonstrations_v2/tutorial_toric_code/metadata.json index 439fc723c9..a97638ad5b 100644 --- a/demonstrations_v2/tutorial_toric_code/metadata.json +++ b/demonstrations_v2/tutorial_toric_code/metadata.json @@ -1,76 +1,75 @@ { - "title": "Modeling the toric code on a quantum computer", - "authors": [ - { - "username": "christina" - } - ], - "dateOfPublication": "2022-08-08T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_modeling_toric_code_on_QC.png" - } - ], - "seoDescription": "Investigation of the toric code degenerate ground state and anyon excitations", - "doi": "", - "references": [ - { - "id": "Kitaev2003", - "type": "article", - "title": "Fault-tolerant quantum computation by anyons.", - "authors": "Kitaev, A. Yu", - "year": "2003", - "journal": "Annals of Physics", - "doi": "10.1016/S0003-4916(02)00018-0", - "url": "https://arxiv.org/pdf/quant-ph/9707021" - }, - { - "id": "surface_codes", - "type": "article", - "title": "Surface codes: Towards practical large-scale quantum computation.", - "authors": "Fowler, Austin G., et al.", - "year": "2012", - "journal": "Physical Review A", - "doi": "10.1103/PhysRevA.86.032324", - "url": "https://arxiv.org/pdf/1208.0928" - }, - { - "id": "google_paper", - "type": "article", - "title": "Realizing topologically ordered states on a quantum processor.", - "authors": "Satzinger, K. J., et al.", - "year": "2021", - "journal": "Science", - "url": "https://www.science.org/doi/abs/10.1126/science.abi8378" - }, - { - "id": "savary_balents", - "type": "article", - "title": "Quantum spin liquids: a review.", - "authors": "Savary, Lucile, and Leon Balents", - "year": "2016", - "journal": "Reports on Progress in Physics", - "url": "https://iopscience.iop.org/article/10.1088/0034-4885/80/1/016502/meta" - }, - { - "id": "Resende", - "type": "article", - "title": "A pedagogical overview on 2D and 3D Toric Codes and the origin of their topological orders.", - "authors": "Araujo de Resende, M. F.", - "year": "2020", - "journal": "Reviews in Mathematical Physics", - "doi": "10.1142/S0129055X20300022", - "url": "https://arxiv.org/pdf/1712.01258.pdf" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [] -} \ No newline at end of file + "title": "Modeling the toric code on a quantum computer", + "authors": [ + { + "username": "christina" + } + ], + "dateOfPublication": "2022-08-08T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Algorithms", "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_modeling_toric_code_on_QC.png" + } + ], + "seoDescription": "Investigation of the toric code degenerate ground state and anyon excitations", + "doi": "", + "references": [ + { + "id": "Kitaev2003", + "type": "article", + "title": "Fault-tolerant quantum computation by anyons.", + "authors": "Kitaev, A. Yu", + "year": "2003", + "journal": "Annals of Physics", + "doi": "10.1016/S0003-4916(02)00018-0", + "url": "https://arxiv.org/pdf/quant-ph/9707021" + }, + { + "id": "surface_codes", + "type": "article", + "title": "Surface codes: Towards practical large-scale quantum computation.", + "authors": "Fowler, Austin G., et al.", + "year": "2012", + "journal": "Physical Review A", + "doi": "10.1103/PhysRevA.86.032324", + "url": "https://arxiv.org/pdf/1208.0928" + }, + { + "id": "google_paper", + "type": "article", + "title": "Realizing topologically ordered states on a quantum processor.", + "authors": "Satzinger, K. J., et al.", + "year": "2021", + "journal": "Science", + "url": "https://www.science.org/doi/abs/10.1126/science.abi8378" + }, + { + "id": "savary_balents", + "type": "article", + "title": "Quantum spin liquids: a review.", + "authors": "Savary, Lucile, and Leon Balents", + "year": "2016", + "journal": "Reports on Progress in Physics", + "url": "https://iopscience.iop.org/article/10.1088/0034-4885/80/1/016502/meta" + }, + { + "id": "Resende", + "type": "article", + "title": "A pedagogical overview on 2D and 3D Toric Codes and the origin of their topological orders.", + "authors": "Araujo de Resende, M. F.", + "year": "2020", + "journal": "Reviews in Mathematical Physics", + "doi": "10.1142/S0129055X20300022", + "url": "https://arxiv.org/pdf/1712.01258.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} diff --git a/demonstrations_v2/tutorial_trapped_ions/demo.py b/demonstrations_v2/tutorial_trapped_ions/demo.py index 309d305447..507d970bdc 100644 --- a/demonstrations_v2/tutorial_trapped_ions/demo.py +++ b/demonstrations_v2/tutorial_trapped_ions/demo.py @@ -1100,3 +1100,6 @@ def cnot_gate(basis_state): # `__. # (`arXiv `__) # +# About the author +# ~~~~~~~~~~~~~~~~ +# .. include:: ../_static/authors/alvaro_ballon.txt diff --git a/demonstrations_v2/tutorial_trapped_ions/metadata.json b/demonstrations_v2/tutorial_trapped_ions/metadata.json index 3fced30416..567d9488d8 100644 --- a/demonstrations_v2/tutorial_trapped_ions/metadata.json +++ b/demonstrations_v2/tutorial_trapped_ions/metadata.json @@ -1,182 +1,182 @@ { - "title": "Trapped ion quantum computers", - "authors": [ - { - "username": "alvaro" - } - ], - "dateOfPublication": "2021-11-10T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Hardware", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tapped_ion_QC.png" - } - ], - "seoDescription": "Learn all about trapped ion quantum computers, developed by companies such as IonQ and Honeywell.", - "doi": "", - "references": [ - { - "id": "DiVincenzo2000", - "type": "article", - "title": "The Physical Implementation of Quantum Computation", - "authors": "D. DiVincenzo", - "year": "2000", - "journal": "Fortschritte der Physik", - "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" - }, - { - "id": "Paul1953", - "type": "article", - "title": "Ein neues Massenspektrometer ohne Magnetfeld", - "authors": "W. Paul, H. Steinwedel", - "year": "1953", - "journal": "RZeitschrift f\u00fcr Naturforschung", - "url": "" - }, - { - "id": "CiracZoller", - "type": "article", - "title": "Quantum Computations with Cold Trapped Ions", - "authors": "J. Cirac, P. Zoller", - "year": "1995", - "journal": "Physical Review Letters", - "url": "" - }, - { - "id": "Malinowski", - "type": "article", - "title": "Unitary and Dissipative Trapped-\u200bIon Entanglement Using Integrated Optics", - "authors": "M. Malinowski", - "year": "2021", - "journal": "", - "url": "https://ethz.ch/content/dam/ethz/special-interest/phys/quantum-electronics/tiqi-dam/documents/phd_theses/Thesis-Maciej-Malinowski" - }, - { - "id": "NandC2000", - "type": "book", - "title": "Quantum Computation and Quantum Information", - "authors": "M. A. Nielsen, and I. L. Chuang", - "year": "2000", - "publisher": "Cambridge University Press", - "url": "" - }, - { - "id": "Hughes2020", - "type": "article", - "title": "Benchmarking a High-Fidelity Mixed-Species Entangling Gate", - "authors": "A. Hughes, V. Schafer, K. Thirumalai, et al.", - "year": "2020", - "journal": "Phys. Rev. Lett.", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.125.080504" - }, - { - "id": "Bergou2021", - "type": "book", - "title": "Quantum Information Processing", - "authors": "J. Bergou, M. Hillery, and M. Saffman", - "year": "2021", - "publisher": "Springer", - "url": "" - }, - { - "id": "Molmer1999", - "type": "article", - "title": "Multi-particle entanglement of hot trapped ions", - "authors": "A. S\u00f8rensen, K. M\u00f8lmer", - "year": "1999", - "journal": "Physical Review Letters", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.82.1835" - }, - { - "id": "Brown2019", - "type": "article", - "title": "Handling leakage with subsystem codes", - "authors": "M. Brown, M. Newman, and K. Brown", - "year": "2019", - "journal": "New J. Phys.", - "url": "https://iopscience.iop.org/article/10.1088/1367-2630/ab3372" - }, - { - "id": "Monroe2014", - "type": "article", - "title": "Large scale modular quantum computer architecture with atomic memory and photonic interconnects", - "authors": "C. Monroe, R. Ruassendorf, A Ruthven, et al.", - "year": "2019", - "journal": "Phys. Rev. A", - "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.89.022317" - }, - { - "id": "QCCD2002", - "type": "article", - "title": "Architecture for a large-scale ion-trap quantum computer", - "authors": "D. Kielpinski, C. Monroe, and D. Wineland", - "year": "2002", - "journal": "Nature", - "url": "https://www.nature.com/articles/nature00784" - }, - { - "id": "Amini2010", - "type": "article", - "title": "Toward scalable ion traps for quantum information processing", - "authors": "J. Amini, H. Uys, J. Wesenberg, et al.", - "year": "2010", - "journal": "New J. Phys", - "url": "https://iopscience.iop.org/article/10.1088/1367-2630/12/3/033031/meta" - }, - { - "id": "Pino2021", - "type": "article", - "title": "Demonstration of the trapped-ion quantum CCD computer architecture", - "authors": "J. Pino, J. Dreiling, J, C, Figgatt, et al.", - "year": "2021", - "journal": "Nature", - "url": "https://www.nature.com/articles/s41586-021-03318-4" - }, - { - "id": "Blumel2021", - "type": "article", - "title": "Efficient Stabilized Two-Qubit Gates on a Trapped-Ion Quantum Computer", - "authors": "R. Blumel, N. Grzesiak, N. Nguyen, et al.", - "year": "2021", - "journal": "Phys. Rev. Lett.", - "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.126.220503" - }, - { - "id": "Niffenegger2020", - "type": "article", - "title": "Integrated multi-wavelength control of an ion qubit", - "authors": "R. Niffenegger, J. Stuart, C.Sorace-Agaskar, et al.", - "year": "2020", - "journal": "Nature", - "volume": "586", - "pages": "538\u2013542", - "url": "https://www.nature.com/articles/s41586-020-2811-x" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_pasqal", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_sc_qubits", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_photonics", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/trapped-ion-quantum-computers-demo/7341" -} \ No newline at end of file + "title": "Trapped ion quantum computers", + "authors": [ + { + "username": "alvaro" + } + ], + "dateOfPublication": "2021-11-10T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Hardware", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tapped_ion_QC.png" + } + ], + "seoDescription": "Learn all about trapped ion quantum computers, developed by companies such as IonQ and Honeywell.", + "doi": "", + "references": [ + { + "id": "DiVincenzo2000", + "type": "article", + "title": "The Physical Implementation of Quantum Computation", + "authors": "D. DiVincenzo", + "year": "2000", + "journal": "Fortschritte der Physik", + "url": "https://onlinelibrary.wiley.com/doi/10.1002/1521-3978(200009)48:9/11%3C771::AID-PROP771%3E3.0.CO;2-E" + }, + { + "id": "Paul1953", + "type": "article", + "title": "Ein neues Massenspektrometer ohne Magnetfeld", + "authors": "W. Paul, H. Steinwedel", + "year": "1953", + "journal": "RZeitschrift für Naturforschung", + "url": "" + }, + { + "id": "CiracZoller", + "type": "article", + "title": "Quantum Computations with Cold Trapped Ions", + "authors": "J. Cirac, P. Zoller", + "year": "1995", + "journal": "Physical Review Letters", + "url": "" + }, + { + "id": "Malinowski", + "type": "article", + "title": "Unitary and Dissipative Trapped-​Ion Entanglement Using Integrated Optics", + "authors": "M. Malinowski", + "year": "2021", + "journal": "", + "url": "https://ethz.ch/content/dam/ethz/special-interest/phys/quantum-electronics/tiqi-dam/documents/phd_theses/Thesis-Maciej-Malinowski" + }, + { + "id": "NandC2000", + "type": "book", + "title": "Quantum Computation and Quantum Information", + "authors": "M. A. Nielsen, and I. L. Chuang", + "year": "2000", + "publisher": "Cambridge University Press", + "url": "" + }, + { + "id": "Hughes2020", + "type": "article", + "title": "Benchmarking a High-Fidelity Mixed-Species Entangling Gate", + "authors": "A. Hughes, V. Schafer, K. Thirumalai, et al.", + "year": "2020", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.125.080504" + }, + { + "id": "Bergou2021", + "type": "book", + "title": "Quantum Information Processing", + "authors": "J. Bergou, M. Hillery, and M. Saffman", + "year": "2021", + "publisher": "Springer", + "url": "" + }, + { + "id": "Molmer1999", + "type": "article", + "title": "Multi-particle entanglement of hot trapped ions", + "authors": "A. Sørensen, K. Mølmer", + "year": "1999", + "journal": "Physical Review Letters", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.82.1835" + }, + { + "id": "Brown2019", + "type": "article", + "title": "Handling leakage with subsystem codes", + "authors": "M. Brown, M. Newman, and K. Brown", + "year": "2019", + "journal": "New J. Phys.", + "url": "https://iopscience.iop.org/article/10.1088/1367-2630/ab3372" + }, + { + "id": "Monroe2014", + "type": "article", + "title": "Large scale modular quantum computer architecture with atomic memory and photonic interconnects", + "authors": "C. Monroe, R. Ruassendorf, A Ruthven, et al.", + "year": "2019", + "journal": "Phys. Rev. A", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.89.022317" + }, + { + "id": "QCCD2002", + "type": "article", + "title": "Architecture for a large-scale ion-trap quantum computer", + "authors": "D. Kielpinski, C. Monroe, and D. Wineland", + "year": "2002", + "journal": "Nature", + "url": "https://www.nature.com/articles/nature00784" + }, + { + "id": "Amini2010", + "type": "article", + "title": "Toward scalable ion traps for quantum information processing", + "authors": "J. Amini, H. Uys, J. Wesenberg, et al.", + "year": "2010", + "journal": "New J. Phys", + "url": "https://iopscience.iop.org/article/10.1088/1367-2630/12/3/033031/meta" + }, + { + "id": "Pino2021", + "type": "article", + "title": "Demonstration of the trapped-ion quantum CCD computer architecture", + "authors": "J. Pino, J. Dreiling, J, C, Figgatt, et al.", + "year": "2021", + "journal": "Nature", + "url": "https://www.nature.com/articles/s41586-021-03318-4" + }, + { + "id": "Blumel2021", + "type": "article", + "title": "Efficient Stabilized Two-Qubit Gates on a Trapped-Ion Quantum Computer", + "authors": "R. Blumel, N. Grzesiak, N. Nguyen, et al.", + "year": "2021", + "journal": "Phys. Rev. Lett.", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.126.220503" + }, + { + "id": "Niffenegger2020", + "type": "article", + "title": "Integrated multi-wavelength control of an ion qubit", + "authors": "R. Niffenegger, J. Stuart, C.Sorace-Agaskar, et al.", + "year": "2020", + "journal": "Nature", + "volume": "586", + "pages": "538–542", + "url": "https://www.nature.com/articles/s41586-020-2811-x" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_pasqal", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_sc_qubits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_photonics", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/trapped-ion-quantum-computers-demo/7341" +} diff --git a/demonstrations_v2/tutorial_unitary_designs/metadata.json b/demonstrations_v2/tutorial_unitary_designs/metadata.json index 557e4a1091..927e0b2694 100644 --- a/demonstrations_v2/tutorial_unitary_designs/metadata.json +++ b/demonstrations_v2/tutorial_unitary_designs/metadata.json @@ -1,161 +1,160 @@ { - "title": "Unitary designs", - "authors": [ - { - "username": "glassnotes" - } - ], - "dateOfPublication": "2021-09-07T00:00:00+00:00", - "dateOfLastModification": "2024-10-11T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_unitary_designs.png" - } - ], - "seoDescription": "Learn about designs and their uses in quantum computing.", - "doi": "", - "references": [ - { - "id": "Handbook", - "type": "book", - "title": "Handbook of Combinatorial Designs, Second Edition", - "authors": "C. J. Colbourn and J. H. Dinitz", - "year": "2006", - "publisher": "Chapman & Hall/CRC", - "url": "" - }, - { - "id": "Delsarte", - "type": "article", - "title": "Spherical Codes and Designs", - "authors": "P. Delsarte, J.M. Goethals, J.J. Seidel", - "year": "1977", - "journal": "Geometriae Dedicata", - "url": "" - }, - { - "id": "sph4design", - "type": "article", - "title": "New spherical 4-designs", - "authors": "R. H. Hardin and N. J. A. Sloane", - "year": "1992", - "journal": "Discrete Mathematics", - "url": "https://www.sciencedirect.com/science/article/pii/0012365X9290552Q" - }, - { - "id": "Ambainis", - "type": "article", - "title": "Quantum t-designs: t-wise independence in the quantum world.", - "authors": "A. Ambainis and J. Emerson", - "year": "2007", - "journal": "Twenty-Second Annual IEEE Conference on Computational Complexity", - "url": "https://arxiv.org/abs/quant-ph/0701126" - }, - { - "id": "Klappenecker", - "type": "article", - "title": "Mutually unbiased bases, spherical designs, and frames.", - "authors": "A. Klappenecker and M. Roetteler", - "year": "2005", - "journal": "Proceedings of SPIE", - "volume": "5914", - "url": "" - }, - { - "id": "Dankert", - "type": "article", - "title": "Exact and Approximate Unitary 2-Designs: Constructions and Applications.", - "authors": "C. Dankert, R. Cleve, J. Emerson, and E. Levine", - "year": "2009", - "journal": "Phys. Rev. A", - "url": "https://arxiv.org/abs/quant-ph/0606161" - }, - { - "id": "DankertThesis", - "type": "article", - "title": "Efficient Simulation of Random Quantum States and Operators.", - "authors": "C. Dankert", - "year": "2005", - "journal": "", - "url": "https://arxiv.org/abs/quant-ph/0512217" - }, - { - "id": "Gross", - "type": "article", - "title": "Evenly distributed unitaries: on the structure of unitary designs", - "authors": "D. Gross, K. Audenaert, and J. Eisert", - "year": "2007", - "journal": "J. Math. Phys.", - "url": "https://arxiv.org/abs/quant-ph/0611002" - }, - { - "id": "Roy", - "type": "article", - "title": "Unitary designs and codes", - "authors": "A. Roy and A. J. Scott", - "year": "2009", - "journal": "Des. Codes Cryptogr.", - "url": "https://arxiv.org/abs/0809.3813" - }, - { - "id": "Bannai", - "type": "article", - "title": "On the explicit constructions of certain unitary t-designs.", - "authors": "E. Bannai, M. Nakahara, D. Zhao, and Y. Zhu", - "year": "2019", - "journal": "J. Phys. A: Math. Theor.", - "url": "https://arxiv.org/abs/1906.04583" - }, - { - "id": "Nakata", - "type": "article", - "title": "Quantum circuits for exact unitary t-designs and applications to higher-order randomized benchmarking.", - "authors": "Y. Nakata et al.", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2102.12617" - }, - { - "id": "PQC", - "type": "article", - "title": "Private Quantum Channels", - "authors": "A. Ambainis, M. Mosca, A. Tapp, and R. de Wolf", - "year": "2000", - "journal": "Proc. 41st FOCS", - "url": "https://homepages.cwi.nl/~rdewolf/publ/qc/AMTW00.pdf" - }, - { - "id": "Seberry", - "type": "article", - "title": "Hadamard matrices, sequences, and block designs.", - "authors": "J. Seberry and M. Yamada", - "year": "1992", - "publisher": "John Wiley and Sons", - "url": "http://mathscinet.ru/files/YamadaSeberry1992.pdf" - }, - { - "id": "Gaeta", - "type": "article", - "title": "Discrete phase-space approach to mutually orthogonal Latin squares", - "authors": "M. Gaeta, O. Di Matteo, A. B. Klimov, and H. de Guise", - "year": "2014", - "journal": "J. Phys. A: Math. Theor.", - "url": "https://arxiv.org/abs/1408.6742" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_haar_measure", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Unitary designs", + "authors": [ + { + "username": "glassnotes" + } + ], + "dateOfPublication": "2021-09-07T00:00:00+00:00", + "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_unitary_designs.png" + } + ], + "seoDescription": "Learn about designs and their uses in quantum computing.", + "doi": "", + "references": [ + { + "id": "Handbook", + "type": "book", + "title": "Handbook of Combinatorial Designs, Second Edition", + "authors": "C. J. Colbourn and J. H. Dinitz", + "year": "2006", + "publisher": "Chapman & Hall/CRC", + "url": "" + }, + { + "id": "Delsarte", + "type": "article", + "title": "Spherical Codes and Designs", + "authors": "P. Delsarte, J.M. Goethals, J.J. Seidel", + "year": "1977", + "journal": "Geometriae Dedicata", + "url": "" + }, + { + "id": "sph4design", + "type": "article", + "title": "New spherical 4-designs", + "authors": "R. H. Hardin and N. J. A. Sloane", + "year": "1992", + "journal": "Discrete Mathematics", + "url": "https://www.sciencedirect.com/science/article/pii/0012365X9290552Q" + }, + { + "id": "Ambainis", + "type": "article", + "title": "Quantum t-designs: t-wise independence in the quantum world.", + "authors": "A. Ambainis and J. Emerson", + "year": "2007", + "journal": "Twenty-Second Annual IEEE Conference on Computational Complexity", + "url": "https://arxiv.org/abs/quant-ph/0701126" + }, + { + "id": "Klappenecker", + "type": "article", + "title": "Mutually unbiased bases, spherical designs, and frames.", + "authors": "A. Klappenecker and M. Roetteler", + "year": "2005", + "journal": "Proceedings of SPIE", + "volume": "5914", + "url": "" + }, + { + "id": "Dankert", + "type": "article", + "title": "Exact and Approximate Unitary 2-Designs: Constructions and Applications.", + "authors": "C. Dankert, R. Cleve, J. Emerson, and E. Levine", + "year": "2009", + "journal": "Phys. Rev. A", + "url": "https://arxiv.org/abs/quant-ph/0606161" + }, + { + "id": "DankertThesis", + "type": "article", + "title": "Efficient Simulation of Random Quantum States and Operators.", + "authors": "C. Dankert", + "year": "2005", + "journal": "", + "url": "https://arxiv.org/abs/quant-ph/0512217" + }, + { + "id": "Gross", + "type": "article", + "title": "Evenly distributed unitaries: on the structure of unitary designs", + "authors": "D. Gross, K. Audenaert, and J. Eisert", + "year": "2007", + "journal": "J. Math. Phys.", + "url": "https://arxiv.org/abs/quant-ph/0611002" + }, + { + "id": "Roy", + "type": "article", + "title": "Unitary designs and codes", + "authors": "A. Roy and A. J. Scott", + "year": "2009", + "journal": "Des. Codes Cryptogr.", + "url": "https://arxiv.org/abs/0809.3813" + }, + { + "id": "Bannai", + "type": "article", + "title": "On the explicit constructions of certain unitary t-designs.", + "authors": "E. Bannai, M. Nakahara, D. Zhao, and Y. Zhu", + "year": "2019", + "journal": "J. Phys. A: Math. Theor.", + "url": "https://arxiv.org/abs/1906.04583" + }, + { + "id": "Nakata", + "type": "article", + "title": "Quantum circuits for exact unitary t-designs and applications to higher-order randomized benchmarking.", + "authors": "Y. Nakata et al.", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2102.12617" + }, + { + "id": "PQC", + "type": "article", + "title": "Private Quantum Channels", + "authors": "A. Ambainis, M. Mosca, A. Tapp, and R. de Wolf", + "year": "2000", + "journal": "Proc. 41st FOCS", + "url": "https://homepages.cwi.nl/~rdewolf/publ/qc/AMTW00.pdf" + }, + { + "id": "Seberry", + "type": "article", + "title": "Hadamard matrices, sequences, and block designs.", + "authors": "J. Seberry and M. Yamada", + "year": "1992", + "publisher": "John Wiley and Sons", + "url": "http://mathscinet.ru/files/YamadaSeberry1992.pdf" + }, + { + "id": "Gaeta", + "type": "article", + "title": "Discrete phase-space approach to mutually orthogonal Latin squares", + "authors": "M. Gaeta, O. Di Matteo, A. B. Klimov, and H. de Guise", + "year": "2014", + "journal": "J. Phys. A: Math. Theor.", + "url": "https://arxiv.org/abs/1408.6742" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_haar_measure", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_univariate_qvr/demo.py b/demonstrations_v2/tutorial_univariate_qvr/demo.py index 117a1a2d56..1507de82cd 100644 --- a/demonstrations_v2/tutorial_univariate_qvr/demo.py +++ b/demonstrations_v2/tutorial_univariate_qvr/demo.py @@ -1264,3 +1264,7 @@ def testing_workflow( # (2020) # # +# About the authors +# ---------------- +# .. include:: ../_static/authors/jack_stephen_baker.txt +# .. include:: ../_static/authors/santosh_kumar_radha.txt diff --git a/demonstrations_v2/tutorial_univariate_qvr/metadata.json b/demonstrations_v2/tutorial_univariate_qvr/metadata.json index de42b0694c..e8c361d140 100644 --- a/demonstrations_v2/tutorial_univariate_qvr/metadata.json +++ b/demonstrations_v2/tutorial_univariate_qvr/metadata.json @@ -1,88 +1,86 @@ { - "title": "Quantum detection of time series anomalies", - "authors": [ - { - "username": "jsbaker" - }, - { - "username": "skradha" - } - ], - "dateOfPublication": "2023-02-07T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/univariate_qvr/thumbnail_tutorial_univariate_qvr.jpg" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_univariate_qvr.png" - } - ], - "seoDescription": "Learn how to quantumly detect anomalous behaviour in time series data with the help of Covalent.", - "doi": "", - "references": [ - { - "id": "Baker2022", - "type": "article", - "title": "Quantum Variational Rewinding for Time Series Anomaly Detection.", - "authors": "Baker, Jack S. et al.", - "year": "2022", - "journal": "", - "url": "https://arxiv.org/abs/2210.16438" - }, - { - "id": "Stone1932", - "type": "article", - "title": "On one-parameter unitary groups in Hilbert space.", - "authors": "Stone, Marshall H.", - "year": "1932", - "journal": "Annals of Mathematics", - "doi": "10.2307/1968538", - "url": "https://doi.org/10.2307/1968538" - }, - { - "id": "Welch2014", - "type": "article", - "title": "Efficient quantum circuits for diagonal unitaries without ancillas", - "authors": "Welch, Jonathan et al.", - "year": "2014", - "journal": "New Journal of Physics", - "doi": "10.1088/1367-2630/16/3/033040", - "url": "https://doi.org/10.1088/1367-2630/16/3/033040" - }, - { - "id": "C\u00eerstoiu2020", - "type": "article", - "title": "Variational fast forwarding for quantum simulation beyond the coherence time", - "authors": "C\u00eerstoiu, Cristina et al.", - "year": "2020", - "journal": "npj Quantum Information", - "doi": "10.1038/s41534-020-00302-0", - "url": "https://doi.org/10.1038/s41534-020-00302-0" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.2210.16438" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_qaoa_intro", - "weight": 1.0 - } - ], - "hardware": [ - { - "id": "covalent", - "link": "https://github.com/AgnostiqHQ/QuantumVariationalRewinding", - "logo": "/_static/hardware_logos/covalent.png" - } - ] -} \ No newline at end of file + "title": "Quantum detection of time series anomalies", + "authors": [ + { + "username": "jsbaker" + }, + { + "username": "skradha" + } + ], + "dateOfPublication": "2023-02-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/univariate_qvr/thumbnail_tutorial_univariate_qvr.jpg" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_univariate_qvr.png" + } + ], + "seoDescription": "Learn how to quantumly detect anomalous behaviour in time series data with the help of Covalent.", + "doi": "", + "references": [ + { + "id": "Baker2022", + "type": "article", + "title": "Quantum Variational Rewinding for Time Series Anomaly Detection.", + "authors": "Baker, Jack S. et al.", + "year": "2022", + "journal": "", + "url": "https://arxiv.org/abs/2210.16438" + }, + { + "id": "Stone1932", + "type": "article", + "title": "On one-parameter unitary groups in Hilbert space.", + "authors": "Stone, Marshall H.", + "year": "1932", + "journal": "Annals of Mathematics", + "doi": "10.2307/1968538", + "url": "https://doi.org/10.2307/1968538" + }, + { + "id": "Welch2014", + "type": "article", + "title": "Efficient quantum circuits for diagonal unitaries without ancillas", + "authors": "Welch, Jonathan et al.", + "year": "2014", + "journal": "New Journal of Physics", + "doi": "10.1088/1367-2630/16/3/033040", + "url": "https://doi.org/10.1088/1367-2630/16/3/033040" + }, + { + "id": "Cîrstoiu2020", + "type": "article", + "title": "Variational fast forwarding for quantum simulation beyond the coherence time", + "authors": "Cîrstoiu, Cristina et al.", + "year": "2020", + "journal": "npj Quantum Information", + "doi": "10.1038/s41534-020-00302-0", + "url": "https://doi.org/10.1038/s41534-020-00302-0" + } + ], + "basedOnPapers": ["10.48550/arXiv.2210.16438"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_qaoa_intro", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "covalent", + "link": "https://github.com/AgnostiqHQ/QuantumVariationalRewinding", + "logo": "/_static/hardware_logos/covalent.png" + } + ] +} diff --git a/demonstrations_v2/tutorial_variational_classifier/demo.py b/demonstrations_v2/tutorial_variational_classifier/demo.py index 28396efb1a..5af2347883 100644 --- a/demonstrations_v2/tutorial_variational_classifier/demo.py +++ b/demonstrations_v2/tutorial_variational_classifier/demo.py @@ -552,3 +552,6 @@ def cost(weights, bias, X, Y): # the two different classes, which allows it to classify even the unseen validation data with # perfect accuracy. # +# About the author +# ---------------- +# .. include:: ../_static/authors/maria_schuld.txt diff --git a/demonstrations_v2/tutorial_variational_classifier/metadata.json b/demonstrations_v2/tutorial_variational_classifier/metadata.json index 1afa350ef0..3941e9212a 100644 --- a/demonstrations_v2/tutorial_variational_classifier/metadata.json +++ b/demonstrations_v2/tutorial_variational_classifier/metadata.json @@ -1,44 +1,43 @@ { - "title": "Variational classifier", - "authors": [ - { - "username": "mariaschuld" - } - ], - "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Machine Learning", - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variational_classifier.png" - } - ], - "seoDescription": "Use PennyLane to implement quantum circuits that can be trained from labelled data to classify new data samples.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_data_reuploading_classifier", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_multiclass_classification", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "ensemble_multi_qpu", - "weight": 1.0 - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/variational-classifier-demo-question-on-padding/3367" -} \ No newline at end of file + "title": "Variational classifier", + "authors": [ + { + "username": "mariaschuld" + } + ], + "dateOfPublication": "2019-10-11T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Machine Learning", "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variational_classifier.png" + } + ], + "seoDescription": "Use PennyLane to implement quantum circuits that can be trained from labelled data to classify new data samples.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_data_reuploading_classifier", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_multiclass_classification", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "ensemble_multi_qpu", + "weight": 1.0 + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/variational-classifier-demo-question-on-padding/3367" +} diff --git a/demonstrations_v2/tutorial_vqe/demo.py b/demonstrations_v2/tutorial_vqe/demo.py index 5a26167517..5795b87464 100644 --- a/demonstrations_v2/tutorial_vqe/demo.py +++ b/demonstrations_v2/tutorial_vqe/demo.py @@ -290,3 +290,6 @@ def cost_fn(param): # quantum computation of electronic structure". `Journal of Chemical Physics 137, 224109 (2012). # `__ # +# About the author +# ---------------- +# .. include:: ../_static/authors/alain_delgado.txt diff --git a/demonstrations_v2/tutorial_vqe/metadata.json b/demonstrations_v2/tutorial_vqe/metadata.json index f0d3ef7279..8cc82fc5dd 100644 --- a/demonstrations_v2/tutorial_vqe/metadata.json +++ b/demonstrations_v2/tutorial_vqe/metadata.json @@ -1,80 +1,79 @@ { - "title": "A brief overview of VQE", - "authors": [ - { - "username": "adgran" - } - ], - "dateOfPublication": "2020-02-08T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry", - "Getting Started" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_brief_overview_of_VQE.png" - } - ], - "seoDescription": "Find the ground state of a Hamiltonian using the variational quantum eigensolver algorithm.", - "doi": "", - "references": [ - { - "id": "peruzzo2014", - "type": "article", - "title": "A variational eigenvalue solver on a photonic quantum processor", - "authors": "Alberto Peruzzo, Jarrod McClean et al.", - "year": "2014", - "journal": "Nature Communications", - "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" - }, - { - "id": "seeley2012", - "type": "article", - "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", - "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", - "year": "2012", - "journal": "Journal of Chemical Physics", - "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_quantum_chemistry", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "vqe_parallel", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe_qng", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqe_spin_sectors", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_vqt", - "weight": 1.0 - } - ], - "hardware": [ - { - "id": "aws", - "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/3_Hydrogen_Molecule_geometry_with_VQE/3_Hydrogen_Molecule_geometry_with_VQE.ipynb", - "logo": "/_static/hardware_logos/aws.png" - } - ], - "discussionForumUrl": "https://discuss.pennylane.ai/t/a-brief-overview-of-vqe-demo/7333" -} \ No newline at end of file + "title": "A brief overview of VQE", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2020-02-08T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry", "Getting Started" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_brief_overview_of_VQE.png" + } + ], + "seoDescription": "Find the ground state of a Hamiltonian using the variational quantum eigensolver algorithm.", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "seeley2012", + "type": "article", + "title": "The Bravyi-Kitaev transformation for quantum computation of electronic structure", + "authors": "Jacob T. Seeley, Martin J. Richard, Peter J. Love", + "year": "2012", + "journal": "Journal of Chemical Physics", + "url": "https://aip.scitation.org/doi/abs/10.1063/1.4768229" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_quantum_chemistry", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "vqe_parallel", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_qng", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqe_spin_sectors", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_vqt", + "weight": 1.0 + } + ], + "hardware": [ + { + "id": "aws", + "link": "https://github.com/amazon-braket/amazon-braket-examples/blob/main/examples/pennylane/3_Hydrogen_Molecule_geometry_with_VQE/3_Hydrogen_Molecule_geometry_with_VQE.ipynb", + "logo": "/_static/hardware_logos/aws.png" + } + ], + "discussionForumUrl": "https://discuss.pennylane.ai/t/a-brief-overview-of-vqe-demo/7333" +} diff --git a/demonstrations_v2/tutorial_vqe_qng/demo.py b/demonstrations_v2/tutorial_vqe_qng/demo.py index 81c1f919cb..b3de324702 100644 --- a/demonstrations_v2/tutorial_vqe_qng/demo.py +++ b/demonstrations_v2/tutorial_vqe_qng/demo.py @@ -473,3 +473,10 @@ def cost(params): # `__ # # +# About the authors +# ----------------- +# .. include:: ../_static/authors/maggie_li.txt +# +# .. include:: ../_static/authors/lana_bozanic.txt +# +# .. include:: ../_static/authors/sukin_sim.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqe_qng/metadata.json b/demonstrations_v2/tutorial_vqe_qng/metadata.json index 37d6c550eb..b39f80c350 100644 --- a/demonstrations_v2/tutorial_vqe_qng/metadata.json +++ b/demonstrations_v2/tutorial_vqe_qng/metadata.json @@ -1,71 +1,71 @@ { - "title": "Accelerating VQEs with quantum natural gradient", - "authors": [ - { - "username": "mli" - }, - { - "username": "lbozanic" - }, - { - "username": "ssim" - } - ], - "dateOfPublication": "2020-11-06T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_accelerating_VQEs.png" - } - ], - "seoDescription": "Accelerating variational quantum eigensolvers using quantum natural gradients in PennyLane.", - "doi": "", - "references": [ - { - "id": "stokes2019", - "type": "article", - "title": "Quantum Natural Gradient", - "authors": "Stokes, James, et al.", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1909.02108" - }, - { - "id": "yamamoto2019", - "type": "article", - "title": "On the natural gradient for variational quantum eigensolver", - "authors": "Yamamoto, Naoki", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1909.05074" - }, - { - "id": "peruzzo2014", - "type": "article", - "title": "A variational eigenvalue solver on a photonic quantum processor", - "authors": "Alberto Peruzzo, Jarrod McClean et al.", - "year": "2014", - "journal": "Nature Communications", - "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_quantum_natural_gradient", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Accelerating VQEs with quantum natural gradient", + "authors": [ + { + "username": "mli" + }, + { + "username": "lbozanic" + }, + { + "username": "ssim" + } + ], + "dateOfPublication": "2020-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_accelerating_VQEs.png" + } + ], + "seoDescription": "Accelerating variational quantum eigensolvers using quantum natural gradients in PennyLane.", + "doi": "", + "references": [ + { + "id": "stokes2019", + "type": "article", + "title": "Quantum Natural Gradient", + "authors": "Stokes, James, et al.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.02108" + }, + { + "id": "yamamoto2019", + "type": "article", + "title": "On the natural gradient for variational quantum eigensolver", + "authors": "Yamamoto, Naoki", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1909.05074" + }, + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "Alberto Peruzzo, Jarrod McClean et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_quantum_natural_gradient", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py b/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py index dda3d7747d..a65b93352c 100644 --- a/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py +++ b/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py @@ -374,3 +374,6 @@ def S2_exp_value(params): # "Universal quantum circuits for quantum chemistry". `arXiv:2106.13839, (2021) # `__ # +# About the author +# ---------------- +# .. include:: ../_static/authors/alain_delgado.txt diff --git a/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json b/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json index 72502a258c..e8afdd4075 100644 --- a/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json +++ b/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json @@ -1,56 +1,56 @@ { - "title": "VQE in different spin sectors", - "authors": [ - { - "username": "adgran" - } - ], - "dateOfPublication": "2020-10-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_VQE_different_spin_sectors.png" - } - ], - "seoDescription": "Find the lowest-energy states of a Hamiltonian in different spin sectors", - "doi": "", - "references": [ - { - "id": "peruzzo2014", - "type": "article", - "title": "A variational eigenvalue solver on a photonic quantum processor", - "authors": "A. Peruzzo, J. McClean et al.", - "year": "2014", - "journal": "Nature Communications", - "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" - }, - { - "id": "qchemcircuits", - "type": "article", - "title": "Universal quantum circuits for quantum chemistry", - "authors": "J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran.", - "year": "2021", - "journal": "", - "url": "https://arxiv.org/abs/2106.13839" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "vqe_parallel", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "VQE in different spin sectors", + "authors": [ + { + "username": "adgran" + } + ], + "dateOfPublication": "2020-10-13T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_VQE_different_spin_sectors.png" + } + ], + "seoDescription": "Find the lowest-energy states of a Hamiltonian in different spin sectors", + "doi": "", + "references": [ + { + "id": "peruzzo2014", + "type": "article", + "title": "A variational eigenvalue solver on a photonic quantum processor", + "authors": "A. Peruzzo, J. McClean et al.", + "year": "2014", + "journal": "Nature Communications", + "url": "https://www.nature.com/articles/ncomms5213?origin=ppub" + }, + { + "id": "qchemcircuits", + "type": "article", + "title": "Universal quantum circuits for quantum chemistry", + "authors": "J.M. Arrazola, O. Di Matteo, N. Quesada, S. Jahangiri, A. Delgado, N. Killoran.", + "year": "2021", + "journal": "", + "url": "https://arxiv.org/abs/2106.13839" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "vqe_parallel", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_vqe_vqd/demo.py b/demonstrations_v2/tutorial_vqe_vqd/demo.py index f560c260fe..cb618a7887 100644 --- a/demonstrations_v2/tutorial_vqe_vqd/demo.py +++ b/demonstrations_v2/tutorial_vqe_vqd/demo.py @@ -223,3 +223,6 @@ def loss_f(theta, beta): # "Variational Quantum Computation of Excited States" # `Quantum 3, 156 (2019) `__. # +# About the authors +# ----------------- +# diff --git a/demonstrations_v2/tutorial_vqe_vqd/metadata.json b/demonstrations_v2/tutorial_vqe_vqd/metadata.json index 41b72b5703..cfd303ebac 100644 --- a/demonstrations_v2/tutorial_vqe_vqd/metadata.json +++ b/demonstrations_v2/tutorial_vqe_vqd/metadata.json @@ -1,56 +1,57 @@ { - "title": "How to implement VQD with PennyLane", - "authors": [ - { - "username": "mchau" - }, - { - "username": "KetPuntoG" - } - ], - "dateOfPublication": "2024-08-26T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry", - "How-to" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_vqd_pennylane.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_vqd_pennylane.png" - } - ], - "seoDescription": "Learn how to use variational quantum deflation (VQD) to find the first excited state energy of the hydrogen molecule.", - "doi": "", - "references": [ - { - "id": "Higgott2019variationalquantum", - "type": "article", - "title": "Variational Quantum Computation of Excited States", - "authors": "Higgott, Oscar and Wang, Daochen and Brierley, Stephen", - "year": "2019", - "journal": "Quantum", - "url": "https://doi.org/10.22331/q-2019-07-01-156" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_givens_rotations", - "weight": 1.0 - } - ], - "hardware": [] -} \ No newline at end of file + "title": "How to implement VQD with PennyLane", + "authors": [ + { + "username": "mchau" + }, + { + "username": "KetPuntoG" + } + ], + "dateOfPublication": "2024-08-26T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry", + "How-to" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_how_to_vqd_pennylane.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_how_to_vqd_pennylane.png" + } + ], + "seoDescription": "Learn how to use variational quantum deflation (VQD) to find the first excited state energy of the hydrogen molecule.", + "doi": "", + "references": [ + { + + "id": "Higgott2019variationalquantum", + "type": "article", + "title": "Variational Quantum Computation of Excited States", + "authors": "Higgott, Oscar and Wang, Daochen and Brierley, Stephen", + "year": "2019", + "journal": "Quantum", + "url": "https://doi.org/10.22331/q-2019-07-01-156" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_givens_rotations", + "weight": 1.0 + } + ], + "hardware": [] +} diff --git a/demonstrations_v2/tutorial_vqls/demo.py b/demonstrations_v2/tutorial_vqls/demo.py index 2bd1bd0a4e..d6682fd0f6 100644 --- a/demonstrations_v2/tutorial_vqls/demo.py +++ b/demonstrations_v2/tutorial_vqls/demo.py @@ -528,3 +528,6 @@ def prepare_and_sample(weights): # `arXiv:1909.05820 `__, 2019. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqls/metadata.json b/demonstrations_v2/tutorial_vqls/metadata.json index feb4e8a6cd..6d111010be 100644 --- a/demonstrations_v2/tutorial_vqls/metadata.json +++ b/demonstrations_v2/tutorial_vqls/metadata.json @@ -1,45 +1,45 @@ { - "title": "Variational Quantum Linear Solver", - "authors": [ - { - "username": "amari" - } - ], - "dateOfPublication": "2019-11-04T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_quantum_linear_solver.png" - } - ], - "seoDescription": "Implementing the variational quantum linear solver to solve a system of linear equation with a quantum device.", - "doi": "", - "references": [ - { - "id": "BravoPrieto2019", - "type": "article", - "title": "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems.", - "authors": "Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles", - "year": "2019", - "journal": "", - "doi": "10.48550/arXiv.1909.05820", - "url": "https://arxiv.org/abs/1909.05820" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1909.05820" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_coherent_vqls", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Variational Quantum Linear Solver", + "authors": [ + { + "username": "amari" + } + ], + "dateOfPublication": "2019-11-04T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_quantum_linear_solver.png" + } + ], + "seoDescription": "Implementing the variational quantum linear solver to solve a system of linear equation with a quantum device.", + "doi": "", + "references": [ + { + "id": "BravoPrieto2019", + "type": "article", + "title": "Variational Quantum Linear Solver: A Hybrid Algorithm for Linear Systems.", + "authors": "Carlos Bravo-Prieto, Ryan LaRose, Marco Cerezo, Yigit Subasi, Lukasz Cincio, Patrick J. Coles", + "year": "2019", + "journal": "", + "doi": "10.48550/arXiv.1909.05820", + "url": "https://arxiv.org/abs/1909.05820" + } + ], + "basedOnPapers": [ + "10.48550/arXiv.1909.05820" + ], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_coherent_vqls", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_vqt/demo.py b/demonstrations_v2/tutorial_vqt/demo.py index cc5dabc7d8..4ffce13164 100644 --- a/demonstrations_v2/tutorial_vqt/demo.py +++ b/demonstrations_v2/tutorial_vqt/demo.py @@ -570,3 +570,6 @@ def trace_distance(one, two): # `arXiv:1910.02071 `__. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/jack_ceroni.txt diff --git a/demonstrations_v2/tutorial_vqt/metadata.json b/demonstrations_v2/tutorial_vqt/metadata.json index cf0c972795..652134a98f 100644 --- a/demonstrations_v2/tutorial_vqt/metadata.json +++ b/demonstrations_v2/tutorial_vqt/metadata.json @@ -1,44 +1,42 @@ { - "title": "Variational Quantum Thermalizer", - "authors": [ - { - "username": "jceroni" - } - ], - "dateOfPublication": "2020-07-07T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", - "categories": [ - "Optimization" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_quantum_thermalizer.png" - } - ], - "seoDescription": "Using the Variational Quantum Thermalizer to prepare the thermal state of a Heisenberg model Hamiltonian.", - "doi": "", - "references": [ - { - "id": "Verdon2019", - "type": "article", - "title": "Quantum Hamiltonian-Based Models and the Variational Quantum Thermalizer Algorithm", - "authors": "Verdon, G., Marks, J., Nanda, S., Leichenauer, S., & Hidary, J.", - "year": "2019", - "journal": "", - "url": "https://arxiv.org/abs/1910.02071" - } - ], - "basedOnPapers": [ - "10.48550/arXiv.1910.02071" - ], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Variational Quantum Thermalizer", + "authors": [ + { + "username": "jceroni" + } + ], + "dateOfPublication": "2020-07-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "categories": [ + "Optimization" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_variationally_quantum_thermalizer.png" + } + ], + "seoDescription": "Using the Variational Quantum Thermalizer to prepare the thermal state of a Heisenberg model Hamiltonian.", + "doi": "", + "references": [ + { + "id": "Verdon2019", + "type": "article", + "title": "Quantum Hamiltonian-Based Models and the Variational Quantum Thermalizer Algorithm", + "authors": "Verdon, G., Marks, J., Nanda, S., Leichenauer, S., & Hidary, J.", + "year": "2019", + "journal": "", + "url": "https://arxiv.org/abs/1910.02071" + } + ], + "basedOnPapers": ["10.48550/arXiv.1910.02071"], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_zne_catalyst/metadata.json b/demonstrations_v2/tutorial_zne_catalyst/metadata.json index 40afd668ce..f9cc710683 100644 --- a/demonstrations_v2/tutorial_zne_catalyst/metadata.json +++ b/demonstrations_v2/tutorial_zne_catalyst/metadata.json @@ -1,84 +1,84 @@ { - "title": "Digital zero-noise extrapolation with Catalyst", - "authors": [ - { - "username": "cosenal" - }, - { - "username": "natestemen" - } - ], - "dateOfPublication": "2024-11-15T00:00:00+00:00", - "dateOfLastModification": "2024-11-25T09:00:00+00:00", - "categories": [ - "Algorithms", - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_zne_catalyst.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_zne_catalyst.png" - } - ], - "seoDescription": "Learn to use error mitigation and the digital zero-noise extrapolation (ZNE) technique with Catalyst, the PennyLane framework for quantum JIT compilation.", - "doi": "", - "references": [ - { - "id": "dzne-2020", - "type": "article", - "title": "Digital zero noise extrapolation for quantum error mitigation", - "authors": "Tudor Giurgica-Tiron, Yousef Hindy, Ryan LaRose, Andrea Mari, and William J. Zeng", - "year": "2020", - "publisher": "IEEE", - "journal": "2020 IEEE International Conference on Quantum Computing and Engineering (QCE)", - "doi": "10.1109/QCE49297.2020.00045", - "url": "https://arxiv.org/abs/2005.10921v2" - }, - { - "id": "zne-2017", - "type": "article", - "title": "Error Mitigation for Short-Depth Quantum Circuits", - "authors": "K. Temme, S. Bravyi, J. M. Gambetta", - "year": "2017", - "publisher": "", - "journal": "Phys. Rev. Lett. 119, 180509", - "doi": "10.1103/PhysRevLett.119.180509", - "url": "https://arxiv.org/abs/1612.02058" - }, - { - "id": "mitiq-2022", - "type": "article", - "title": "Mitiq: A software package for error mitigation on noisy quantum computers", - "authors": "Ryan LaRose and Andrea Mari and Sarah Kaiser and Peter J. Karalekas and Andre A. Alves and Piotr Czarnik and Mohamed El Mandouh and Max H. Gordon and Yousef Hindy and Aaron Robertson and Purva Thakre and Misty Wahl and Danny Samuel and Rahul Mistri and Maxime Tremblay and Nick Gardner and Nathaniel T. Stemen and Nathan Shammah and William J. Zeng", - "year": "2022", - "publisher": "Verein zur Forderung des Open Access Publizierens in den Quantenwissenschaften", - "journal": "Quantum", - "doi": "10.22331/q-2022-08-11-774", - "url": "https://doi.org/10.22331/q-2022-08-11-774" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_error_mitigation", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "qrack", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_diffable-mitigation", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Digital zero-noise extrapolation with Catalyst", + "authors": [ + { + "username": "cosenal" + }, + { + "username": "natestemen" + } + ], + "dateOfPublication": "2024-11-15T00:00:00+00:00", + "dateOfLastModification": "2024-11-25T09:00:00+00:00", + "categories": [ + "Algorithms", + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_zne_catalyst.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_zne_catalyst.png" + } + ], + "seoDescription": "Learn to use error mitigation and the digital zero-noise extrapolation (ZNE) technique with Catalyst, the PennyLane framework for quantum JIT compilation.", + "doi": "", + "references": [ + { + "id": "dzne-2020", + "type": "article", + "title": "Digital zero noise extrapolation for quantum error mitigation", + "authors": "Tudor Giurgica-Tiron, Yousef Hindy, Ryan LaRose, Andrea Mari, and William J. Zeng", + "year": "2020", + "publisher": "IEEE", + "journal": "2020 IEEE International Conference on Quantum Computing and Engineering (QCE)", + "doi": "10.1109/QCE49297.2020.00045", + "url": "https://arxiv.org/abs/2005.10921v2" + }, + { + "id": "zne-2017", + "type": "article", + "title": "Error Mitigation for Short-Depth Quantum Circuits", + "authors": "K. Temme, S. Bravyi, J. M. Gambetta", + "year": "2017", + "publisher": "", + "journal": "Phys. Rev. Lett. 119, 180509", + "doi": "10.1103/PhysRevLett.119.180509", + "url": "https://arxiv.org/abs/1612.02058" + }, + { + "id": "mitiq-2022", + "type": "article", + "title": "Mitiq: A software package for error mitigation on noisy quantum computers", + "authors": "Ryan LaRose and Andrea Mari and Sarah Kaiser and Peter J. Karalekas and Andre A. Alves and Piotr Czarnik and Mohamed El Mandouh and Max H. Gordon and Yousef Hindy and Aaron Robertson and Purva Thakre and Misty Wahl and Danny Samuel and Rahul Mistri and Maxime Tremblay and Nick Gardner and Nathaniel T. Stemen and Nathan Shammah and William J. Zeng", + "year": "2022", + "publisher": "Verein zur Forderung des Open Access Publizierens in den Quantenwissenschaften", + "journal": "Quantum", + "doi": "10.22331/q-2022-08-11-774", + "url": "https://doi.org/10.22331/q-2022-08-11-774" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_error_mitigation", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "qrack", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_diffable-mitigation", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_zx_calculus/demo.py b/demonstrations_v2/tutorial_zx_calculus/demo.py index 08beeb3fd7..b8faff5014 100644 --- a/demonstrations_v2/tutorial_zx_calculus/demo.py +++ b/demonstrations_v2/tutorial_zx_calculus/demo.py @@ -865,3 +865,6 @@ def mod_5_4(): # `ArXiv `__. # # +# About the author +# ---------------- +# .. include:: ../_static/authors/romain_moyard.txt diff --git a/demonstrations_v2/tutorial_zx_calculus/metadata.json b/demonstrations_v2/tutorial_zx_calculus/metadata.json index 18795bd74a..f646f23511 100644 --- a/demonstrations_v2/tutorial_zx_calculus/metadata.json +++ b/demonstrations_v2/tutorial_zx_calculus/metadata.json @@ -1,142 +1,142 @@ { - "title": "Introduction to the ZX-calculus", - "authors": [ - { - "username": "rmoyard" - } - ], - "dateOfPublication": "2023-06-06T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Computing" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demonstration_assets/zx_calculus/thumbnail_tutorial_zx_calculus.png" - }, - { - "type": "large_thumbnail", - "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_zx_calculus.png" - } - ], - "seoDescription": "Introduction to the ZX-calculus and use cases.", - "doi": "", - "references": [ - { - "id": "Coecke", - "type": "article", - "title": "Interacting Quantum Observables: Categorical Algebra and Diagrammatics.", - "authors": "Bob Coecke and Ross Duncan.", - "year": "2008", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/pdf/0906.4725.pdf" - }, - { - "id": "PyZX", - "type": "webpage", - "title": "PyZX GitHub.", - "authors": "John van de Wetering.", - "year": "2018", - "publisher": "", - "journal": "", - "url": "https://github.com/Quantomatic/pyzx" - }, - { - "id": "Backens2018", - "type": "article", - "title": "ZH: A Complete Graphical Calculus for Quantum Computations Involving Classical Non-linearity", - "authors": "Miriam Backens and Aleks Kissinger", - "year": "2018", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/pdf/1805.02175.pdf" - }, - { - "id": "East2021", - "type": "article", - "title": "AKLT-states as ZX-diagrams: diagrammatic reasoning for quantum states.", - "authors": "Richard D. P. East, John van de Wetering, Nicholas Chancellor and Adolfo G. Grushin.", - "year": "2021", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/pdf/2012.01219.pdf" - }, - { - "id": "JvdW2020", - "type": "article", - "title": "ZX-calculus for the working quantum computer scientist.", - "authors": "John van de Wetering.", - "year": "2020", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/abs/2012.13966" - }, - { - "id": "Zhao2021", - "type": "article", - "title": "Analyzing the barren plateau phenomenon in training quantum neural networks with the ZX-calculus.", - "authors": "Chen Zhao and Xiao-Shan Gao.", - "year": "2021", - "publisher": "", - "journal": "", - "url": "https://quantum-journal.org/papers/q-2021-06-04-466/pdf/" - }, - { - "id": "Wang2022", - "type": "article", - "title": "Differentiating and Integrating ZX Diagrams with Applications to Quantum Machine Learning.", - "authors": "Quanlong Wang, Richie Yeung, and Mark Koch.", - "year": "2022", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/pdf/2201.13250.pdf" - }, - { - "id": "Duncan2020", - "type": "article", - "title": "Graph-theoretic Simplification of Quantum Circuits with the ZX-calculus.", - "authors": "Ross Duncan, Aleks Kissinger, Simon Perdrix, and John van de Wetering.", - "year": "2020", - "publisher": "", - "journal": "", - "url": "https://quantum-journal.org/papers/q-2020-06-04-279/pdf/" - }, - { - "id": "Kissinger2021", - "type": "article", - "title": "Reducing T-count with the ZX-calculus.", - "authors": "Aleks Kissinger and John van de Wetering.", - "year": "2021", - "publisher": "", - "journal": "", - "url": "https://quantum-journal.org/papers/q-2020-06-04-279/pdf/" - }, - { - "id": "Beaudrap2021", - "type": "article", - "title": "Circuit Extraction for ZX-diagrams can be #P-hard.", - "authors": "Niel de Beaudrap, Aleks Kissinger and John van de Wetering.", - "year": "2021", - "publisher": "", - "journal": "", - "url": "https://arxiv.org/pdf/2202.09194.pdf" - } - ], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_backprop", - "weight": 1.0 - }, - { - "type": "demonstration", - "id": "tutorial_mbqc", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "Introduction to the ZX-calculus", + "authors": [ + { + "username": "rmoyard" + } + ], + "dateOfPublication": "2023-06-06T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Computing" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demonstration_assets/zx_calculus/thumbnail_tutorial_zx_calculus.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_zx_calculus.png" + } + ], + "seoDescription": "Introduction to the ZX-calculus and use cases.", + "doi": "", + "references": [ + { + "id": "Coecke", + "type": "article", + "title": "Interacting Quantum Observables: Categorical Algebra and Diagrammatics.", + "authors": "Bob Coecke and Ross Duncan.", + "year": "2008", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/0906.4725.pdf" + }, + { + "id": "PyZX", + "type": "webpage", + "title": "PyZX GitHub.", + "authors": "John van de Wetering.", + "year": "2018", + "publisher": "", + "journal": "", + "url": "https://github.com/Quantomatic/pyzx" + }, + { + "id": "Backens2018", + "type": "article", + "title": "ZH: A Complete Graphical Calculus for Quantum Computations Involving Classical Non-linearity", + "authors": "Miriam Backens and Aleks Kissinger", + "year": "2018", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/1805.02175.pdf" + }, + { + "id": "East2021", + "type": "article", + "title": "AKLT-states as ZX-diagrams: diagrammatic reasoning for quantum states.", + "authors": "Richard D. P. East, John van de Wetering, Nicholas Chancellor and Adolfo G. Grushin.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/2012.01219.pdf" + }, + { + "id": "JvdW2020", + "type": "article", + "title": "ZX-calculus for the working quantum computer scientist.", + "authors": "John van de Wetering.", + "year": "2020", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/abs/2012.13966" + }, + { + "id": "Zhao2021", + "type": "article", + "title": "Analyzing the barren plateau phenomenon in training quantum neural networks with the ZX-calculus.", + "authors": "Chen Zhao and Xiao-Shan Gao.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://quantum-journal.org/papers/q-2021-06-04-466/pdf/" + }, + { + "id": "Wang2022", + "type": "article", + "title": "Differentiating and Integrating ZX Diagrams with Applications to Quantum Machine Learning.", + "authors": "Quanlong Wang, Richie Yeung, and Mark Koch.", + "year": "2022", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/2201.13250.pdf" + }, + { + "id": "Duncan2020", + "type": "article", + "title": "Graph-theoretic Simplification of Quantum Circuits with the ZX-calculus.", + "authors": "Ross Duncan, Aleks Kissinger, Simon Perdrix, and John van de Wetering.", + "year": "2020", + "publisher": "", + "journal": "", + "url": "https://quantum-journal.org/papers/q-2020-06-04-279/pdf/" + }, + { + "id": "Kissinger2021", + "type": "article", + "title": "Reducing T-count with the ZX-calculus.", + "authors": "Aleks Kissinger and John van de Wetering.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://quantum-journal.org/papers/q-2020-06-04-279/pdf/" + }, + { + "id": "Beaudrap2021", + "type": "article", + "title": "Circuit Extraction for ZX-diagrams can be #P-hard.", + "authors": "Niel de Beaudrap, Aleks Kissinger and John van de Wetering.", + "year": "2021", + "publisher": "", + "journal": "", + "url": "https://arxiv.org/pdf/2202.09194.pdf" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_backprop", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mbqc", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/vqe_parallel/demo.py b/demonstrations_v2/vqe_parallel/demo.py index 351c9dc13c..89a64e91f7 100644 --- a/demonstrations_v2/vqe_parallel/demo.py +++ b/demonstrations_v2/vqe_parallel/demo.py @@ -385,3 +385,6 @@ def compute_energy_parallel_optimized(H, devs, param): # ``shots=1024``). ############################################################################## +# About the author +# ---------------- +# .. include:: ../_static/authors/thomas_bromley.txt diff --git a/demonstrations_v2/vqe_parallel/metadata.json b/demonstrations_v2/vqe_parallel/metadata.json index 032b3bd621..d27d1dd51b 100644 --- a/demonstrations_v2/vqe_parallel/metadata.json +++ b/demonstrations_v2/vqe_parallel/metadata.json @@ -1,32 +1,32 @@ { - "title": "VQE with parallel QPUs with Rigetti", - "authors": [ - { - "username": "trbromley" - } - ], - "dateOfPublication": "2020-02-14T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", - "categories": [ - "Quantum Chemistry" - ], - "tags": [], - "previewImages": [ - { - "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_VQE_parallel_QPUs_Rigetti.png" - } - ], - "seoDescription": "Using parallel QPUs to speed up the calculation of the potential energy surface of molecular Hamiltonian.", - "doi": "", - "references": [], - "basedOnPapers": [], - "referencedByPapers": [], - "relatedContent": [ - { - "type": "demonstration", - "id": "tutorial_vqe", - "weight": 1.0 - } - ] -} \ No newline at end of file + "title": "VQE with parallel QPUs with Rigetti", + "authors": [ + { + "username": "trbromley" + } + ], + "dateOfPublication": "2020-02-14T00:00:00+00:00", + "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "categories": [ + "Quantum Chemistry" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_VQE_parallel_QPUs_Rigetti.png" + } + ], + "seoDescription": "Using parallel QPUs to speed up the calculation of the potential energy surface of molecular Hamiltonian.", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_vqe", + "weight": 1.0 + } + ] +} diff --git a/lib/qml/app.py b/lib/qml/app.py index 2897549355..2ceac2a4cb 100644 --- a/lib/qml/app.py +++ b/lib/qml/app.py @@ -1,6 +1,6 @@ import typer from qml.context import Context -from qml.lib import demo +from qml.lib import demo, fs import shutil import logging from typing import Annotated @@ -48,3 +48,32 @@ def build( target=target, execute=execute, ) + + +@app.command() +def sync_v2(): + """Copy new and changed demos from /demonstrations to /demonstrations_v2.""" + ctx = Context() + for v1_demo in (ctx.repo_root / "demonstrations").glob("*.py"): + demo_name = v1_demo.stem + v1_metadata = v1_demo.with_suffix(".metadata.json") + + v2_demo_dir = ctx.demos_dir / demo_name + v2_demo = v2_demo_dir / "demo.py" + v2_metadata = v2_demo_dir / "metadata.json" + + if not v2_demo_dir.exists(): + v2_demo_dir.mkdir() + shutil.copy2(v1_demo, v2_demo) + shutil.copy2(v1_metadata, v2_metadata) + with open(v2_demo / "requirements.in", "w"): + pass + + print( + f"Copied new demo {v1_demo} to {v2_demo_dir}. Please updated the requirements.in file." + ) + else: + for src, dest in [(v1_demo, v2_demo), (v1_metadata, v2_metadata)]: + if not dest.exists() or fs.file_sha(src) != fs.file_sha(dest): + shutil.copy2(src, dest) + print(f"Updated {dest} from {src}") diff --git a/lib/qml/lib/fs.py b/lib/qml/lib/fs.py index c1957cb475..135023f4ba 100644 --- a/lib/qml/lib/fs.py +++ b/lib/qml/lib/fs.py @@ -1,5 +1,6 @@ from pathlib import Path import shutil +import hashlib def copy_any(src: Path, dest: Path, exist_ok: bool = False): @@ -8,3 +9,12 @@ def copy_any(src: Path, dest: Path, exist_ok: bool = False): shutil.copytree(src, dest, dirs_exist_ok=exist_ok) else: shutil.copy2(src, dest) + + +def file_sha(path: Path) -> bytes: + """Return the SHA256 hash of file at ``path``.""" + with open(path, "rb") as f: + m = hashlib.sha256() + m.update(f.read()) + + return m.digest() From 6b19a633b95a8391d44686592b91efe402863410 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 12:45:43 -0500 Subject: [PATCH 09/29] requirements --- .../requirements.in | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in diff --git a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in new file mode 100644 index 0000000000..05bf133e2e --- /dev/null +++ b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in @@ -0,0 +1,5 @@ +optax +jax +jaxlib +numpy +pennylane From bea7530a650dea8e58e034b95996bf8b9af66c6f Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 12:54:22 -0500 Subject: [PATCH 10/29] fix tutorial quantum chemistry paths --- demonstrations/{ => quantum_chemistry}/h2o.xyz | 0 demonstrations/tutorial_quantum_chemistry.py | 2 +- demonstrations_v2/tutorial_quantum_chemistry/demo.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename demonstrations/{ => quantum_chemistry}/h2o.xyz (100%) diff --git a/demonstrations/h2o.xyz b/demonstrations/quantum_chemistry/h2o.xyz similarity index 100% rename from demonstrations/h2o.xyz rename to demonstrations/quantum_chemistry/h2o.xyz diff --git a/demonstrations/tutorial_quantum_chemistry.py b/demonstrations/tutorial_quantum_chemistry.py index 1872263cac..68db18429c 100644 --- a/demonstrations/tutorial_quantum_chemistry.py +++ b/demonstrations/tutorial_quantum_chemistry.py @@ -78,7 +78,7 @@ from pennylane import qchem -symbols, coordinates = qchem.read_structure("h2o.xyz") +symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") ############################################################################## # The xyz format is supported. diff --git a/demonstrations_v2/tutorial_quantum_chemistry/demo.py b/demonstrations_v2/tutorial_quantum_chemistry/demo.py index 1872263cac..68db18429c 100644 --- a/demonstrations_v2/tutorial_quantum_chemistry/demo.py +++ b/demonstrations_v2/tutorial_quantum_chemistry/demo.py @@ -78,7 +78,7 @@ from pennylane import qchem -symbols, coordinates = qchem.read_structure("h2o.xyz") +symbols, coordinates = qchem.read_structure("quantum_chemistry/h2o.xyz") ############################################################################## # The xyz format is supported. From c51c68efe2ed2515d6e6c098a8e945a338b1c006 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 13:00:04 -0500 Subject: [PATCH 11/29] docs --- lib/qml/lib/virtual_env.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/qml/lib/virtual_env.py b/lib/qml/lib/virtual_env.py index 30a915b7cc..37cb0be793 100644 --- a/lib/qml/lib/virtual_env.py +++ b/lib/qml/lib/virtual_env.py @@ -4,17 +4,27 @@ class Virtualenv: + """Interface to a Python virtual environment.""" + def __init__(self, path: Path): + """ + Get python virtual env, creating it if it does not exist. + Args: + path: Path to virtual env directory. Will be initialized + if it does not exist. + """ self.path = path.resolve() if not self.python.exists(): - self.init() + self._init() @property def python(self) -> Path: + """Path to the python executable in this virtual env.""" return self.path / "bin" / "python" - def init(self): + def _init(self): + """Initialize a virtual environment.""" self.path.parent.mkdir(exist_ok=True) subprocess.run([sys.executable, "-m", "venv", self.path]).check_returncode() From e768e21db8ade03b4971b95452f8fe3f9f0b94ef Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 13:03:29 -0500 Subject: [PATCH 12/29] docstrings --- lib/qml/lib/cmds.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/qml/lib/cmds.py b/lib/qml/lib/cmds.py index 8ffb0ace95..f7a89846d2 100644 --- a/lib/qml/lib/cmds.py +++ b/lib/qml/lib/cmds.py @@ -14,6 +14,7 @@ def poetry_export( """Executes `poetry export` with the given Python interpreter. Args: + python: Path to Python executable output: Path to output file format: Format, either 'constraints.txt' or 'requirements.txt' groups: If provided, only include dependencies from the groups @@ -51,7 +52,18 @@ def pip_install( quiet: bool = True, ): """Executes `pip install` with the given python - interpreter and args.""" + interpreter and args. + + Args: + python: Path to python executable + args: Command line args passed to pip install + requirements: Path to a requirements file + constraints: Path to a constriants file + quiet: Whether to suppress output to stdout + + Raises: + CalledProcessError: The command does not complete successfully + """ cmd = [str(python), "-m", "pip", "install"] if requirements: cmd.extend(("--requirement", str(requirements))) From ceea64f6db76e41e7674aad16326c77d5ca74164 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Thu, 16 Jan 2025 13:11:41 -0500 Subject: [PATCH 13/29] update lockfile --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3da7a9c7da..d50f2a68ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -9033,4 +9033,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "67496a27bcb51e5a9d1491175acf994f8226df57808525f89df8082cec123caa" +content-hash = "6fb2a040e257cab53f6796991e9d56c8549a1b1511d2cb50fb976ca82de0b087" diff --git a/pyproject.toml b/pyproject.toml index 038381913d..3807275137 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ qml = 'qml.app:app' [tool.poetry.dependencies] python = "~3.10.0" typer = "^0.15.1" -poetry = ">1.7,<2" +poetry = "^1.8.5" poetry-plugin-export = "^1.8.0" dulwich = "<0.22" From 39d96d3ab27ab906f118902e00beaf0beafefea5 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 11:05:51 -0500 Subject: [PATCH 14/29] fmt --- lib/qml/app/__init__.py | 3 +++ lib/qml/{ => app}/app.py | 11 ++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 lib/qml/app/__init__.py rename lib/qml/{ => app}/app.py (92%) diff --git a/lib/qml/app/__init__.py b/lib/qml/app/__init__.py new file mode 100644 index 0000000000..34f275ed3c --- /dev/null +++ b/lib/qml/app/__init__.py @@ -0,0 +1,3 @@ +from .app import app + +__all__ = ["app"] diff --git a/lib/qml/app.py b/lib/qml/app/app.py similarity index 92% rename from lib/qml/app.py rename to lib/qml/app/app.py index 2ceac2a4cb..49a4c5cbda 100644 --- a/lib/qml/app.py +++ b/lib/qml/app/app.py @@ -3,7 +3,8 @@ from qml.lib import demo, fs import shutil import logging -from typing import Annotated +from typing import Annotated, Optional +import typing logging.basicConfig(level=logging.INFO) @@ -19,14 +20,14 @@ def help(): @app.command() def build( demo_names: Annotated[ - list[str], + Optional[list[str]], typer.Argument( help="Names of demos to build. If not provided, build all demos." ), ] = None, - target: Annotated[ + format: Annotated[ demo.BuildTarget, typer.Option(help="Format to build demos") - ] = "html", + ] = typing.cast(demo.BuildTarget, "html"), execute: Annotated[ bool, typer.Option(help="Whether to execute demos and generate output cells") ] = False, @@ -45,7 +46,7 @@ def build( build_dir=ctx.build_dir, venv_path=ctx.build_venv_path, demos=demos, - target=target, + target=format, execute=execute, ) From 8441b41af8d8b6c61e31d3ab6a83808af653dc40 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 12:49:52 -0500 Subject: [PATCH 15/29] add core dependencies --- .../adjoint_diff_benchmarking/requirements.in | 2 - demonstrations_v2/ahs_aquila/requirements.in | 3 -- .../braket-parallel-gradients/requirements.in | 2 - .../requirements.in | 0 .../covalent_cloud_gpu/requirements.in | 2 - .../ensemble_multi_qpu/requirements.in | 3 -- .../function_fitting_qsp/requirements.in | 3 -- demonstrations_v2/gbs/requirements.in | 2 - .../requirements.in | 2 - .../gqe_training/requirements.in | 2 - .../requirements.in | 1 - .../ibm_pennylane/requirements.in | 2 - .../learning2learn/requirements.in | 3 -- .../ml_classical_shadows/requirements.in | 3 -- demonstrations_v2/oqc_pulse/requirements.in | 3 -- .../plugins_hybrid/requirements.in | 1 - .../pytorch_noise/requirements.in | 1 - demonstrations_v2/qnspsa/requirements.in | 1 - demonstrations_v2/qonn/requirements.in | 1 - demonstrations_v2/qrack/requirements.in | 2 - .../qsim_beyond_classical/requirements.in | 2 - .../quantum_neural_net/requirements.in | 2 - .../quantum_volume/requirements.in | 3 -- .../requirements.in | 1 - .../requirements.in | 1 - .../requirements.in | 1 - .../requirements.in | 2 - .../tutorial_QGAN/requirements.in | 2 - .../tutorial_QUBO/requirements.in | 3 -- .../requirements.in | 1 - .../tutorial_adjoint_diff/requirements.in | 2 - .../requirements.in | 2 - .../tutorial_apply_qsvt/requirements.in | 2 - .../tutorial_backprop/requirements.in | 2 - .../tutorial_barren_gadgets/requirements.in | 2 - .../tutorial_barren_plateaus/requirements.in | 2 - .../tutorial_block_encoding/requirements.in | 3 -- .../tutorial_bluequbit/requirements.in | 3 -- .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 1 - .../requirements.in | 3 -- .../tutorial_coherent_vqls/requirements.in | 2 - .../requirements.in | 3 -- .../tutorial_contextuality/requirements.in | 3 -- .../requirements.in | 2 - .../requirements.in | 2 - .../tutorial_diffable_shadows/requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 3 -- .../tutorial_eqnn_force_field/requirements.in | 3 -- .../requirements.in | 3 -- .../tutorial_error_mitigation/requirements.in | 2 - .../tutorial_error_prop/requirements.in | 1 - .../requirements.in | 2 - .../tutorial_falqon/requirements.in | 2 - .../requirements.in | 1 - .../requirements.in | 2 - .../requirements.in | 1 - .../tutorial_general_parshift/requirements.in | 3 -- .../tutorial_geometric_qml/requirements.in | 3 -- .../tutorial_givens_rotations/requirements.in | 3 -- .../requirements.in | 3 -- .../requirements.in | 2 - .../tutorial_haar_measure/requirements.in | 3 -- .../requirements.in | 3 -- .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 1 - .../requirements.in | 3 -- .../requirements.in | 1 - .../requirements.in | 2 - .../requirements.in | 3 -- .../requirements.in | 2 - .../requirements.in | 3 -- .../tutorial_intro_qrom/requirements.in | 3 -- .../tutorial_intro_qsvt/requirements.in | 3 -- .../requirements.in | 2 - .../requirements.in | 1 - .../tutorial_kak_theorem/requirements.in | 2 - .../requirements.in | 3 -- .../tutorial_kernels_module/requirements.in | 2 - .../requirements.in | 3 -- .../requirements.in | 3 -- .../requirements.in | 3 -- .../requirements.in | 3 -- .../tutorial_learningshallow/requirements.in | 4 -- .../tutorial_liealgebra/requirements.in | 2 - .../tutorial_liesim/requirements.in | 4 -- .../tutorial_liesim_extension/requirements.in | 3 -- .../requirements.in | 2 - .../requirements.in | 2 - .../tutorial_mapping/requirements.in | 1 - .../tutorial_mbqc/requirements.in | 2 - .../tutorial_mcm_introduction/requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../tutorial_mol_geo_opt/requirements.in | 2 - .../tutorial_mps/requirements.in | 3 -- .../requirements.in | 3 -- .../tutorial_neutral_atoms/requirements.in | 2 - .../requirements.in | 2 - .../tutorial_noisy_circuits/requirements.in | 1 - .../tutorial_odegen/requirements.in | 3 -- .../tutorial_optimal_control/requirements.in | 2 - .../tutorial_pasqal/requirements.in | 3 -- .../tutorial_phase_kickback/requirements.in | 2 - .../tutorial_photonics/requirements.in | 3 -- .../requirements.in | 2 - .../requirements.in | 4 -- .../tutorial_qaoa_intro/requirements.in | 2 - .../tutorial_qaoa_maxcut/requirements.in | 2 - .../tutorial_qcbm/requirements.in | 3 -- .../tutorial_qchem_external/requirements.in | 1 - .../tutorial_qft/requirements.in | 3 -- .../tutorial_qft_arithmetics/requirements.in | 3 -- .../tutorial_qgrnn/requirements.in | 2 - .../requirements.in | 3 -- .../tutorial_qnn_module_tf/requirements.in | 3 -- .../tutorial_qnn_module_torch/requirements.in | 3 -- .../requirements.in | 2 - .../tutorial_qpe/requirements.in | 3 -- .../tutorial_qsvt_hardware/requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../tutorial_quantum_dropout/requirements.in | 3 -- .../tutorial_quantum_gans/requirements.in | 3 -- .../requirements.in | 1 - .../requirements.in | 3 -- .../requirements.in | 2 - .../tutorial_quanvolution/requirements.in | 2 - .../tutorial_qubit_rotation/requirements.in | 1 - .../tutorial_qubit_tapering/requirements.in | 1 - .../tutorial_qubitization/requirements.in | 3 -- .../requirements.in | 1 - .../requirements.in | 2 - .../tutorial_rl_pulse/requirements.in | 2 - .../tutorial_rosalin/requirements.in | 2 - .../tutorial_rotoselect/requirements.in | 2 - .../tutorial_sc_qubits/requirements.in | 3 -- .../requirements.in | 2 - .../tutorial_spsa/requirements.in | 2 - .../requirements.in | 2 - .../requirements.in | 2 - .../tutorial_teleportation/requirements.in | 2 - .../tutorial_testing_symmetry/requirements.in | 1 - .../tutorial_tn_circuits/requirements.in | 3 -- .../tutorial_toric_code/requirements.in | 3 -- .../tutorial_trapped_ions/requirements.in | 3 -- .../tutorial_unitary_designs/requirements.in | 2 - .../tutorial_univariate_qvr/requirements.in | 2 - .../requirements.in | 2 - .../tutorial_vqe/requirements.in | 2 - .../tutorial_vqe_qng/requirements.in | 2 - .../tutorial_vqe_spin_sectors/requirements.in | 1 - .../tutorial_vqe_vqd/requirements.in | 2 - .../tutorial_vqls/requirements.in | 2 - .../tutorial_vqt/requirements.in | 3 -- .../tutorial_zne_catalyst/requirements.in | 2 - .../tutorial_zx_calculus/requirements.in | 2 - .../vqe_parallel/requirements.in | 2 - lib/qml/lib/demo.py | 41 +++++++++++++------ 168 files changed, 29 insertions(+), 379 deletions(-) delete mode 100644 demonstrations_v2/circuits_as_fourier_series/requirements.in delete mode 100644 demonstrations_v2/plugins_hybrid/requirements.in delete mode 100644 demonstrations_v2/quantum_neural_net/requirements.in delete mode 100644 demonstrations_v2/tutorial_adaptive_circuits/requirements.in delete mode 100644 demonstrations_v2/tutorial_apply_qsvt/requirements.in delete mode 100644 demonstrations_v2/tutorial_barren_gadgets/requirements.in delete mode 100644 demonstrations_v2/tutorial_barren_plateaus/requirements.in delete mode 100644 demonstrations_v2/tutorial_block_encoding/requirements.in delete mode 100644 demonstrations_v2/tutorial_chemical_reactions/requirements.in delete mode 100644 demonstrations_v2/tutorial_circuit_compilation/requirements.in delete mode 100644 demonstrations_v2/tutorial_classical_expval_estimation/requirements.in delete mode 100644 demonstrations_v2/tutorial_classical_kernels/requirements.in delete mode 100644 demonstrations_v2/tutorial_classical_shadows/requirements.in delete mode 100644 demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in delete mode 100644 demonstrations_v2/tutorial_coherent_vqls/requirements.in delete mode 100644 demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in delete mode 100644 demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in delete mode 100644 demonstrations_v2/tutorial_diffable-mitigation/requirements.in delete mode 100644 demonstrations_v2/tutorial_diffable_shadows/requirements.in delete mode 100644 demonstrations_v2/tutorial_doubly_stochastic/requirements.in delete mode 100644 demonstrations_v2/tutorial_error_prop/requirements.in delete mode 100644 demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in delete mode 100644 demonstrations_v2/tutorial_fermionic_operators/requirements.in delete mode 100644 demonstrations_v2/tutorial_grovers_algorithm/requirements.in delete mode 100644 demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in delete mode 100644 demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in delete mode 100644 demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in delete mode 100644 demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in delete mode 100644 demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in delete mode 100644 demonstrations_v2/tutorial_how_to_use_registers/requirements.in delete mode 100644 demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in delete mode 100644 demonstrations_v2/tutorial_intro_qrom/requirements.in delete mode 100644 demonstrations_v2/tutorial_intro_qsvt/requirements.in delete mode 100644 demonstrations_v2/tutorial_kak_theorem/requirements.in delete mode 100644 demonstrations_v2/tutorial_lcu_blockencoding/requirements.in delete mode 100644 demonstrations_v2/tutorial_liealgebra/requirements.in delete mode 100644 demonstrations_v2/tutorial_local_cost_functions/requirements.in delete mode 100644 demonstrations_v2/tutorial_mapping/requirements.in delete mode 100644 demonstrations_v2/tutorial_mcm_introduction/requirements.in delete mode 100644 demonstrations_v2/tutorial_mol_geo_opt/requirements.in delete mode 100644 demonstrations_v2/tutorial_mps/requirements.in delete mode 100644 demonstrations_v2/tutorial_phase_kickback/requirements.in delete mode 100644 demonstrations_v2/tutorial_qaoa_maxcut/requirements.in delete mode 100644 demonstrations_v2/tutorial_qft_arithmetics/requirements.in delete mode 100644 demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in delete mode 100644 demonstrations_v2/tutorial_qpe/requirements.in delete mode 100644 demonstrations_v2/tutorial_qsvt_hardware/requirements.in delete mode 100644 demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in delete mode 100644 demonstrations_v2/tutorial_quantum_chemistry/requirements.in delete mode 100644 demonstrations_v2/tutorial_qubit_tapering/requirements.in delete mode 100644 demonstrations_v2/tutorial_qubitization/requirements.in delete mode 100644 demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in delete mode 100644 demonstrations_v2/tutorial_resource_estimation/requirements.in delete mode 100644 demonstrations_v2/tutorial_rotoselect/requirements.in delete mode 100644 demonstrations_v2/tutorial_sc_qubits/requirements.in delete mode 100644 demonstrations_v2/tutorial_teleportation/requirements.in delete mode 100644 demonstrations_v2/tutorial_testing_symmetry/requirements.in delete mode 100644 demonstrations_v2/tutorial_tn_circuits/requirements.in delete mode 100644 demonstrations_v2/tutorial_toric_code/requirements.in delete mode 100644 demonstrations_v2/tutorial_variational_classifier/requirements.in delete mode 100644 demonstrations_v2/tutorial_vqe_qng/requirements.in delete mode 100644 demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in delete mode 100644 demonstrations_v2/tutorial_vqls/requirements.in diff --git a/demonstrations_v2/adjoint_diff_benchmarking/requirements.in b/demonstrations_v2/adjoint_diff_benchmarking/requirements.in index a7e20f9b86..a7dd74d3dd 100644 --- a/demonstrations_v2/adjoint_diff_benchmarking/requirements.in +++ b/demonstrations_v2/adjoint_diff_benchmarking/requirements.in @@ -1,4 +1,2 @@ jax jaxlib -matplotlib -pennylane diff --git a/demonstrations_v2/ahs_aquila/requirements.in b/demonstrations_v2/ahs_aquila/requirements.in index bb242ed39b..a7dd74d3dd 100644 --- a/demonstrations_v2/ahs_aquila/requirements.in +++ b/demonstrations_v2/ahs_aquila/requirements.in @@ -1,5 +1,2 @@ jax jaxlib -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/braket-parallel-gradients/requirements.in b/demonstrations_v2/braket-parallel-gradients/requirements.in index c516a74f3e..d53043c816 100644 --- a/demonstrations_v2/braket-parallel-gradients/requirements.in +++ b/demonstrations_v2/braket-parallel-gradients/requirements.in @@ -1,5 +1,3 @@ amazon-braket-sdk -matplotlib networkx pandas -pennylane diff --git a/demonstrations_v2/circuits_as_fourier_series/requirements.in b/demonstrations_v2/circuits_as_fourier_series/requirements.in deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/demonstrations_v2/covalent_cloud_gpu/requirements.in b/demonstrations_v2/covalent_cloud_gpu/requirements.in index 5e2d3500be..25fa5e54bb 100644 --- a/demonstrations_v2/covalent_cloud_gpu/requirements.in +++ b/demonstrations_v2/covalent_cloud_gpu/requirements.in @@ -1,5 +1,3 @@ covalent==0.227.0rc0 covalent_cloud -matplotlib -pennylane scikit-learn diff --git a/demonstrations_v2/ensemble_multi_qpu/requirements.in b/demonstrations_v2/ensemble_multi_qpu/requirements.in index d65986eab4..a2f2c3fbba 100644 --- a/demonstrations_v2/ensemble_multi_qpu/requirements.in +++ b/demonstrations_v2/ensemble_multi_qpu/requirements.in @@ -1,6 +1,3 @@ dask -matplotlib -numpy -pennylane scikit-learn torch diff --git a/demonstrations_v2/function_fitting_qsp/requirements.in b/demonstrations_v2/function_fitting_qsp/requirements.in index d8d1b51485..12c6d5d5ea 100644 --- a/demonstrations_v2/function_fitting_qsp/requirements.in +++ b/demonstrations_v2/function_fitting_qsp/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane torch diff --git a/demonstrations_v2/gbs/requirements.in b/demonstrations_v2/gbs/requirements.in index 3df1b86098..c2b184966b 100644 --- a/demonstrations_v2/gbs/requirements.in +++ b/demonstrations_v2/gbs/requirements.in @@ -1,4 +1,2 @@ -numpy -pennylane scipy thewalrus diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in b/demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in index baffb20148..4502436d57 100644 --- a/demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/requirements.in @@ -1,4 +1,2 @@ amazon-braket-sdk -matplotlib pandas -pennylane diff --git a/demonstrations_v2/gqe_training/requirements.in b/demonstrations_v2/gqe_training/requirements.in index 5cef66d488..5b18b37717 100644 --- a/demonstrations_v2/gqe_training/requirements.in +++ b/demonstrations_v2/gqe_training/requirements.in @@ -1,6 +1,4 @@ holoviews hvplot -numpy pandas -pennylane torch diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in index 3666a853e5..849ed68fec 100644 --- a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/requirements.in @@ -1,2 +1 @@ -pennylane qiskit diff --git a/demonstrations_v2/ibm_pennylane/requirements.in b/demonstrations_v2/ibm_pennylane/requirements.in index 7d0a480805..83af2eb995 100644 --- a/demonstrations_v2/ibm_pennylane/requirements.in +++ b/demonstrations_v2/ibm_pennylane/requirements.in @@ -1,5 +1,3 @@ -matplotlib -pennylane pennylane_qiskit qiskit_aer qiskit_ibm_runtime diff --git a/demonstrations_v2/learning2learn/requirements.in b/demonstrations_v2/learning2learn/requirements.in index 0139ee4a32..2d5b84e244 100644 --- a/demonstrations_v2/learning2learn/requirements.in +++ b/demonstrations_v2/learning2learn/requirements.in @@ -1,5 +1,2 @@ -matplotlib networkx -numpy -pennylane tensorflow diff --git a/demonstrations_v2/ml_classical_shadows/requirements.in b/demonstrations_v2/ml_classical_shadows/requirements.in index c303d5693a..035d3232d0 100644 --- a/demonstrations_v2/ml_classical_shadows/requirements.in +++ b/demonstrations_v2/ml_classical_shadows/requirements.in @@ -1,7 +1,4 @@ -matplotlib networkx neural_tangents -numpy -pennylane scipy scikit-learn diff --git a/demonstrations_v2/oqc_pulse/requirements.in b/demonstrations_v2/oqc_pulse/requirements.in index 76ab11da75..d1b403bda2 100644 --- a/demonstrations_v2/oqc_pulse/requirements.in +++ b/demonstrations_v2/oqc_pulse/requirements.in @@ -1,6 +1,3 @@ jax jaxlib -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/plugins_hybrid/requirements.in b/demonstrations_v2/plugins_hybrid/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/plugins_hybrid/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/pytorch_noise/requirements.in b/demonstrations_v2/pytorch_noise/requirements.in index eb64412b26..12c6d5d5ea 100644 --- a/demonstrations_v2/pytorch_noise/requirements.in +++ b/demonstrations_v2/pytorch_noise/requirements.in @@ -1,2 +1 @@ -pennylane torch diff --git a/demonstrations_v2/qnspsa/requirements.in b/demonstrations_v2/qnspsa/requirements.in index 5be38fb006..cec2ef79a7 100644 --- a/demonstrations_v2/qnspsa/requirements.in +++ b/demonstrations_v2/qnspsa/requirements.in @@ -1,4 +1,3 @@ amazon-braket-sdk networkx -pennylane scipy diff --git a/demonstrations_v2/qonn/requirements.in b/demonstrations_v2/qonn/requirements.in index 56e807ad3a..694625bc49 100644 --- a/demonstrations_v2/qonn/requirements.in +++ b/demonstrations_v2/qonn/requirements.in @@ -1,2 +1 @@ nlopt -pennylane diff --git a/demonstrations_v2/qrack/requirements.in b/demonstrations_v2/qrack/requirements.in index d08f6bd335..35ad0e1eed 100644 --- a/demonstrations_v2/qrack/requirements.in +++ b/demonstrations_v2/qrack/requirements.in @@ -1,3 +1 @@ pennylane-catalyst -matplotlib -pennylane diff --git a/demonstrations_v2/qsim_beyond_classical/requirements.in b/demonstrations_v2/qsim_beyond_classical/requirements.in index 4bf640990a..a5f9877bd4 100644 --- a/demonstrations_v2/qsim_beyond_classical/requirements.in +++ b/demonstrations_v2/qsim_beyond_classical/requirements.in @@ -1,4 +1,2 @@ cirq -numpy -pennylane pennylane_cirq diff --git a/demonstrations_v2/quantum_neural_net/requirements.in b/demonstrations_v2/quantum_neural_net/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/quantum_neural_net/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/quantum_volume/requirements.in b/demonstrations_v2/quantum_volume/requirements.in index 1fae978d3a..a4b2b7ffcb 100644 --- a/demonstrations_v2/quantum_volume/requirements.in +++ b/demonstrations_v2/quantum_volume/requirements.in @@ -1,6 +1,3 @@ -matplotlib -numpy -pennylane qiskit_ibm_runtime rustworkx strawberryfields diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in index 375a0e28cc..a789351986 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_JAXopt/requirements.in @@ -1,4 +1,3 @@ jax jaxlib jaxopt -pennylane diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in index 7a30722112..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_and_Optax/requirements.in @@ -1,4 +1,3 @@ jax jaxlib optax -pennylane diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in index fc745d6f41..8abf851606 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/requirements.in @@ -2,4 +2,3 @@ pennylane-catalyst jax jaxlib optax -pennylane diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in index 8a849c9ca7..c420b7df84 100644 --- a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in @@ -1,3 +1 @@ -numpy -pennylane quimb diff --git a/demonstrations_v2/tutorial_QGAN/requirements.in b/demonstrations_v2/tutorial_QGAN/requirements.in index 911f2c73ac..0f57144081 100644 --- a/demonstrations_v2/tutorial_QGAN/requirements.in +++ b/demonstrations_v2/tutorial_QGAN/requirements.in @@ -1,3 +1 @@ -numpy -pennylane tensorflow diff --git a/demonstrations_v2/tutorial_QUBO/requirements.in b/demonstrations_v2/tutorial_QUBO/requirements.in index 152f7b0907..8056917e84 100644 --- a/demonstrations_v2/tutorial_QUBO/requirements.in +++ b/demonstrations_v2/tutorial_QUBO/requirements.in @@ -1,8 +1,5 @@ dimod docplex dwave-ocean-sdk==7.0.0 -matplotlib -numpy openqaoa-core pandas -pennylane diff --git a/demonstrations_v2/tutorial_adaptive_circuits/requirements.in b/demonstrations_v2/tutorial_adaptive_circuits/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_adaptive_circuits/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_adjoint_diff/requirements.in b/demonstrations_v2/tutorial_adjoint_diff/requirements.in index 277e023bab..a7dd74d3dd 100644 --- a/demonstrations_v2/tutorial_adjoint_diff/requirements.in +++ b/demonstrations_v2/tutorial_adjoint_diff/requirements.in @@ -1,4 +1,2 @@ jax jaxlib -pennylane -matplotlib diff --git a/demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in b/demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in index 929357a2b9..12c6d5d5ea 100644 --- a/demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in +++ b/demonstrations_v2/tutorial_adversarial_attacks_QML/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane torch diff --git a/demonstrations_v2/tutorial_apply_qsvt/requirements.in b/demonstrations_v2/tutorial_apply_qsvt/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_apply_qsvt/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_backprop/requirements.in b/demonstrations_v2/tutorial_backprop/requirements.in index a7e20f9b86..a7dd74d3dd 100644 --- a/demonstrations_v2/tutorial_backprop/requirements.in +++ b/demonstrations_v2/tutorial_backprop/requirements.in @@ -1,4 +1,2 @@ jax jaxlib -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_barren_gadgets/requirements.in b/demonstrations_v2/tutorial_barren_gadgets/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_barren_gadgets/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_barren_plateaus/requirements.in b/demonstrations_v2/tutorial_barren_plateaus/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_barren_plateaus/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_block_encoding/requirements.in b/demonstrations_v2/tutorial_block_encoding/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_block_encoding/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_bluequbit/requirements.in b/demonstrations_v2/tutorial_bluequbit/requirements.in index 1972a95adc..5f4fca9dc8 100644 --- a/demonstrations_v2/tutorial_bluequbit/requirements.in +++ b/demonstrations_v2/tutorial_bluequbit/requirements.in @@ -1,4 +1 @@ bluequbit -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_chemical_reactions/requirements.in b/demonstrations_v2/tutorial_chemical_reactions/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_chemical_reactions/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_circuit_compilation/requirements.in b/demonstrations_v2/tutorial_circuit_compilation/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_circuit_compilation/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_classical_expval_estimation/requirements.in b/demonstrations_v2/tutorial_classical_expval_estimation/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_classical_expval_estimation/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_classical_kernels/requirements.in b/demonstrations_v2/tutorial_classical_kernels/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_classical_kernels/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_classical_shadows/requirements.in b/demonstrations_v2/tutorial_classical_shadows/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_classical_shadows/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in b/demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in index fc12179ec4..9a635b910d 100644 --- a/demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/requirements.in @@ -1,2 +1 @@ -pennylane scipy diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in b/demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_clifford_circuit_simulations/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_coherent_vqls/requirements.in b/demonstrations_v2/tutorial_coherent_vqls/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_coherent_vqls/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in b/demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_constant_depth_mps_prep/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_contextuality/requirements.in b/demonstrations_v2/tutorial_contextuality/requirements.in index 2612555b82..a0a64308f3 100644 --- a/demonstrations_v2/tutorial_contextuality/requirements.in +++ b/demonstrations_v2/tutorial_contextuality/requirements.in @@ -1,7 +1,4 @@ jax jaxlib -matplotlib -numpy optax -pennylane tqdm diff --git a/demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in b/demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_data_reuploading_classifier/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_diffable-mitigation/requirements.in b/demonstrations_v2/tutorial_diffable-mitigation/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_diffable-mitigation/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_diffable_shadows/requirements.in b/demonstrations_v2/tutorial_diffable_shadows/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_diffable_shadows/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_differentiable_HF/requirements.in b/demonstrations_v2/tutorial_differentiable_HF/requirements.in index 8aa5764d33..d0238be155 100644 --- a/demonstrations_v2/tutorial_differentiable_HF/requirements.in +++ b/demonstrations_v2/tutorial_differentiable_HF/requirements.in @@ -1,3 +1 @@ autograd -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_doubly_stochastic/requirements.in b/demonstrations_v2/tutorial_doubly_stochastic/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_doubly_stochastic/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_eqnn_force_field/requirements.in b/demonstrations_v2/tutorial_eqnn_force_field/requirements.in index 8090cd5743..45adf972f2 100644 --- a/demonstrations_v2/tutorial_eqnn_force_field/requirements.in +++ b/demonstrations_v2/tutorial_eqnn_force_field/requirements.in @@ -1,7 +1,4 @@ jax jaxlib -matplotlib -numpy -pennylane scipy scikit-learn diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in b/demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in index a9a3103589..4d07dfe2f8 100644 --- a/demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/requirements.in @@ -1,4 +1 @@ -matplotlib networkx -numpy -pennylane diff --git a/demonstrations_v2/tutorial_error_mitigation/requirements.in b/demonstrations_v2/tutorial_error_mitigation/requirements.in index 9b8e92a025..79d711c560 100644 --- a/demonstrations_v2/tutorial_error_mitigation/requirements.in +++ b/demonstrations_v2/tutorial_error_mitigation/requirements.in @@ -1,6 +1,4 @@ -matplotlib mitiq -pennylane qiskit qiskit_aer qiskit_ibm_runtime diff --git a/demonstrations_v2/tutorial_error_prop/requirements.in b/demonstrations_v2/tutorial_error_prop/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_error_prop/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in b/demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_expressivity_fourier_series/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_falqon/requirements.in b/demonstrations_v2/tutorial_falqon/requirements.in index f84259cda4..4d07dfe2f8 100644 --- a/demonstrations_v2/tutorial_falqon/requirements.in +++ b/demonstrations_v2/tutorial_falqon/requirements.in @@ -1,3 +1 @@ -matplotlib networkx -pennylane diff --git a/demonstrations_v2/tutorial_fermionic_operators/requirements.in b/demonstrations_v2/tutorial_fermionic_operators/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_fermionic_operators/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in index 05bf133e2e..3bfde23c32 100644 --- a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in +++ b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/requirements.in @@ -1,5 +1,3 @@ optax jax jaxlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_gaussian_transformation/requirements.in b/demonstrations_v2/tutorial_gaussian_transformation/requirements.in index 375a0e28cc..a789351986 100644 --- a/demonstrations_v2/tutorial_gaussian_transformation/requirements.in +++ b/demonstrations_v2/tutorial_gaussian_transformation/requirements.in @@ -1,4 +1,3 @@ jax jaxlib jaxopt -pennylane diff --git a/demonstrations_v2/tutorial_general_parshift/requirements.in b/demonstrations_v2/tutorial_general_parshift/requirements.in index 76ab11da75..d1b403bda2 100644 --- a/demonstrations_v2/tutorial_general_parshift/requirements.in +++ b/demonstrations_v2/tutorial_general_parshift/requirements.in @@ -1,6 +1,3 @@ jax jaxlib -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_geometric_qml/requirements.in b/demonstrations_v2/tutorial_geometric_qml/requirements.in index d8d1b51485..12c6d5d5ea 100644 --- a/demonstrations_v2/tutorial_geometric_qml/requirements.in +++ b/demonstrations_v2/tutorial_geometric_qml/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane torch diff --git a/demonstrations_v2/tutorial_givens_rotations/requirements.in b/demonstrations_v2/tutorial_givens_rotations/requirements.in index a5f5319ab0..a7dd74d3dd 100644 --- a/demonstrations_v2/tutorial_givens_rotations/requirements.in +++ b/demonstrations_v2/tutorial_givens_rotations/requirements.in @@ -1,5 +1,2 @@ -numpy -pennylane jax jaxlib -matplotlib diff --git a/demonstrations_v2/tutorial_grovers_algorithm/requirements.in b/demonstrations_v2/tutorial_grovers_algorithm/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_grovers_algorithm/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in index 1d1ec52c06..410fc82e8f 100644 --- a/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in +++ b/demonstrations_v2/tutorial_guide_to_pennylane_knowing_qiskit/requirements.in @@ -1,4 +1,2 @@ -matplotlib -pennylane pennylane-qiskit qiskit diff --git a/demonstrations_v2/tutorial_haar_measure/requirements.in b/demonstrations_v2/tutorial_haar_measure/requirements.in index 8132c3977e..9a635b910d 100644 --- a/demonstrations_v2/tutorial_haar_measure/requirements.in +++ b/demonstrations_v2/tutorial_haar_measure/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/requirements.in b/demonstrations_v2/tutorial_here_comes_the_sun/requirements.in index bb242ed39b..a7dd74d3dd 100644 --- a/demonstrations_v2/tutorial_here_comes_the_sun/requirements.in +++ b/demonstrations_v2/tutorial_here_comes_the_sun/requirements.in @@ -1,5 +1,2 @@ jax jaxlib -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in b/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_how_to_build_spin_hamiltonians/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in b/demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_how_to_collect_mcm_stats/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in b/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_how_to_create_dynamic_mcm_circuits/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in index 5e86ccff7a..13f72fdcaf 100644 --- a/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in +++ b/demonstrations_v2/tutorial_how_to_import_qiskit_noise_models/requirements.in @@ -1,4 +1,2 @@ -numpy -pennylane qiskit qiskit_aer diff --git a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in index fc745d6f41..8abf851606 100644 --- a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in +++ b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/requirements.in @@ -2,4 +2,3 @@ pennylane-catalyst jax jaxlib optax -pennylane diff --git a/demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in b/demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_how_to_use_noise_models/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in b/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_how_to_use_quantum_arithmetic_operators/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_how_to_use_registers/requirements.in b/demonstrations_v2/tutorial_how_to_use_registers/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_how_to_use_registers/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in b/demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in index 075509d294..a789351986 100644 --- a/demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in +++ b/demonstrations_v2/tutorial_implicit_diff_susceptibility/requirements.in @@ -1,6 +1,3 @@ jax jaxlib jaxopt -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_initial_state_preparation/requirements.in b/demonstrations_v2/tutorial_initial_state_preparation/requirements.in index 1dc2016a8b..8a06484a30 100644 --- a/demonstrations_v2/tutorial_initial_state_preparation/requirements.in +++ b/demonstrations_v2/tutorial_initial_state_preparation/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane pyscf diff --git a/demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in b/demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_intro_amplitude_amplification/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_intro_qrom/requirements.in b/demonstrations_v2/tutorial_intro_qrom/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_intro_qrom/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_intro_qsvt/requirements.in b/demonstrations_v2/tutorial_intro_qsvt/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_intro_qsvt/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in b/demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in index 929357a2b9..12c6d5d5ea 100644 --- a/demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in +++ b/demonstrations_v2/tutorial_isingmodel_PyTorch/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane torch diff --git a/demonstrations_v2/tutorial_jax_transformations/requirements.in b/demonstrations_v2/tutorial_jax_transformations/requirements.in index d630fe270f..a7dd74d3dd 100644 --- a/demonstrations_v2/tutorial_jax_transformations/requirements.in +++ b/demonstrations_v2/tutorial_jax_transformations/requirements.in @@ -1,3 +1,2 @@ jax jaxlib -pennylane diff --git a/demonstrations_v2/tutorial_kak_theorem/requirements.in b/demonstrations_v2/tutorial_kak_theorem/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_kak_theorem/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_kernel_based_training/requirements.in b/demonstrations_v2/tutorial_kernel_based_training/requirements.in index 375ecbc231..04383c96ca 100644 --- a/demonstrations_v2/tutorial_kernel_based_training/requirements.in +++ b/demonstrations_v2/tutorial_kernel_based_training/requirements.in @@ -1,5 +1,2 @@ -matplotlib -numpy -pennylane scikit-learn torch diff --git a/demonstrations_v2/tutorial_kernels_module/requirements.in b/demonstrations_v2/tutorial_kernels_module/requirements.in index 166e26691f..d5e06028d8 100644 --- a/demonstrations_v2/tutorial_kernels_module/requirements.in +++ b/demonstrations_v2/tutorial_kernels_module/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane scikit-learn diff --git a/demonstrations_v2/tutorial_lcu_blockencoding/requirements.in b/demonstrations_v2/tutorial_lcu_blockencoding/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_lcu_blockencoding/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in b/demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in index 8132c3977e..9a635b910d 100644 --- a/demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in +++ b/demonstrations_v2/tutorial_learning_dynamics_incoherently/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_learning_few_data/requirements.in b/demonstrations_v2/tutorial_learning_few_data/requirements.in index 75f3252d06..773918cee0 100644 --- a/demonstrations_v2/tutorial_learning_few_data/requirements.in +++ b/demonstrations_v2/tutorial_learning_few_data/requirements.in @@ -1,9 +1,6 @@ jax jaxlib -matplotlib -numpy optax pandas -pennylane seaborn scikit-learn diff --git a/demonstrations_v2/tutorial_learning_from_experiments/requirements.in b/demonstrations_v2/tutorial_learning_from_experiments/requirements.in index c18ba892e9..d5e06028d8 100644 --- a/demonstrations_v2/tutorial_learning_from_experiments/requirements.in +++ b/demonstrations_v2/tutorial_learning_from_experiments/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scikit-learn diff --git a/demonstrations_v2/tutorial_learningshallow/requirements.in b/demonstrations_v2/tutorial_learningshallow/requirements.in index ed2cc33de0..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_learningshallow/requirements.in +++ b/demonstrations_v2/tutorial_learningshallow/requirements.in @@ -1,7 +1,3 @@ -datetime jax jaxlib -matplotlib -numpy optax -pennylane diff --git a/demonstrations_v2/tutorial_liealgebra/requirements.in b/demonstrations_v2/tutorial_liealgebra/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_liealgebra/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_liesim/requirements.in b/demonstrations_v2/tutorial_liesim/requirements.in index ed2cc33de0..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_liesim/requirements.in +++ b/demonstrations_v2/tutorial_liesim/requirements.in @@ -1,7 +1,3 @@ -datetime jax jaxlib -matplotlib -numpy optax -pennylane diff --git a/demonstrations_v2/tutorial_liesim_extension/requirements.in b/demonstrations_v2/tutorial_liesim_extension/requirements.in index 8132c3977e..9a635b910d 100644 --- a/demonstrations_v2/tutorial_liesim_extension/requirements.in +++ b/demonstrations_v2/tutorial_liesim_extension/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_local_cost_functions/requirements.in b/demonstrations_v2/tutorial_local_cost_functions/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_local_cost_functions/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_magic_state_distillation/requirements.in b/demonstrations_v2/tutorial_magic_state_distillation/requirements.in index 21024b5740..bff34c1d5f 100644 --- a/demonstrations_v2/tutorial_magic_state_distillation/requirements.in +++ b/demonstrations_v2/tutorial_magic_state_distillation/requirements.in @@ -1,5 +1,3 @@ pennylane-catalyst jax jaxlib -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_mapping/requirements.in b/demonstrations_v2/tutorial_mapping/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_mapping/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_mbqc/requirements.in b/demonstrations_v2/tutorial_mbqc/requirements.in index 373f510e32..956d3afda3 100644 --- a/demonstrations_v2/tutorial_mbqc/requirements.in +++ b/demonstrations_v2/tutorial_mbqc/requirements.in @@ -1,4 +1,2 @@ flamingpy -matplotlib networkx -pennylane diff --git a/demonstrations_v2/tutorial_mcm_introduction/requirements.in b/demonstrations_v2/tutorial_mcm_introduction/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_mcm_introduction/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_measurement_optimize/requirements.in b/demonstrations_v2/tutorial_measurement_optimize/requirements.in index f84259cda4..4d07dfe2f8 100644 --- a/demonstrations_v2/tutorial_measurement_optimize/requirements.in +++ b/demonstrations_v2/tutorial_measurement_optimize/requirements.in @@ -1,3 +1 @@ -matplotlib networkx -pennylane diff --git a/demonstrations_v2/tutorial_mitigation_advantage/requirements.in b/demonstrations_v2/tutorial_mitigation_advantage/requirements.in index a7e20f9b86..a7dd74d3dd 100644 --- a/demonstrations_v2/tutorial_mitigation_advantage/requirements.in +++ b/demonstrations_v2/tutorial_mitigation_advantage/requirements.in @@ -1,4 +1,2 @@ jax jaxlib -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_mol_geo_opt/requirements.in b/demonstrations_v2/tutorial_mol_geo_opt/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_mol_geo_opt/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_mps/requirements.in b/demonstrations_v2/tutorial_mps/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_mps/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_multiclass_classification/requirements.in b/demonstrations_v2/tutorial_multiclass_classification/requirements.in index d8d1b51485..12c6d5d5ea 100644 --- a/demonstrations_v2/tutorial_multiclass_classification/requirements.in +++ b/demonstrations_v2/tutorial_multiclass_classification/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane torch diff --git a/demonstrations_v2/tutorial_neutral_atoms/requirements.in b/demonstrations_v2/tutorial_neutral_atoms/requirements.in index a7e20f9b86..a7dd74d3dd 100644 --- a/demonstrations_v2/tutorial_neutral_atoms/requirements.in +++ b/demonstrations_v2/tutorial_neutral_atoms/requirements.in @@ -1,4 +1,2 @@ jax jaxlib -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in b/demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in index d190727410..0b918fb667 100644 --- a/demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in +++ b/demonstrations_v2/tutorial_noisy_circuit_optimization/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane pennylane_cirq diff --git a/demonstrations_v2/tutorial_noisy_circuits/requirements.in b/demonstrations_v2/tutorial_noisy_circuits/requirements.in index 375a0e28cc..a789351986 100644 --- a/demonstrations_v2/tutorial_noisy_circuits/requirements.in +++ b/demonstrations_v2/tutorial_noisy_circuits/requirements.in @@ -1,4 +1,3 @@ jax jaxlib jaxopt -pennylane diff --git a/demonstrations_v2/tutorial_odegen/requirements.in b/demonstrations_v2/tutorial_odegen/requirements.in index 543f5dbfb8..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_odegen/requirements.in +++ b/demonstrations_v2/tutorial_odegen/requirements.in @@ -1,6 +1,3 @@ jax jaxlib -matplotlib -numpy optax -pennylane diff --git a/demonstrations_v2/tutorial_optimal_control/requirements.in b/demonstrations_v2/tutorial_optimal_control/requirements.in index 0aae374bda..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_optimal_control/requirements.in +++ b/demonstrations_v2/tutorial_optimal_control/requirements.in @@ -1,5 +1,3 @@ jax jaxlib -matplotlib optax -pennylane diff --git a/demonstrations_v2/tutorial_pasqal/requirements.in b/demonstrations_v2/tutorial_pasqal/requirements.in index a0bd965555..92a98d5801 100644 --- a/demonstrations_v2/tutorial_pasqal/requirements.in +++ b/demonstrations_v2/tutorial_pasqal/requirements.in @@ -1,5 +1,2 @@ cirq_pasqal -matplotlib -numpy -pennylane tensorflow diff --git a/demonstrations_v2/tutorial_phase_kickback/requirements.in b/demonstrations_v2/tutorial_phase_kickback/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_phase_kickback/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_photonics/requirements.in b/demonstrations_v2/tutorial_photonics/requirements.in index 8132c3977e..9a635b910d 100644 --- a/demonstrations_v2/tutorial_photonics/requirements.in +++ b/demonstrations_v2/tutorial_photonics/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in index 2315fdb489..dafe458526 100644 --- a/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in +++ b/demonstrations_v2/tutorial_post-variational_quantum_neural_networks/requirements.in @@ -1,6 +1,4 @@ jax jaxlib -matplotlib optax -pennylane scikit-learn diff --git a/demonstrations_v2/tutorial_pulse_programming101/requirements.in b/demonstrations_v2/tutorial_pulse_programming101/requirements.in index ed2cc33de0..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_pulse_programming101/requirements.in +++ b/demonstrations_v2/tutorial_pulse_programming101/requirements.in @@ -1,7 +1,3 @@ -datetime jax jaxlib -matplotlib -numpy optax -pennylane diff --git a/demonstrations_v2/tutorial_qaoa_intro/requirements.in b/demonstrations_v2/tutorial_qaoa_intro/requirements.in index c38ea5aa94..531ab16f06 100644 --- a/demonstrations_v2/tutorial_qaoa_intro/requirements.in +++ b/demonstrations_v2/tutorial_qaoa_intro/requirements.in @@ -1,5 +1,3 @@ -matplotlib networkx -pennylane pennylane-qulacs qulacs diff --git a/demonstrations_v2/tutorial_qaoa_maxcut/requirements.in b/demonstrations_v2/tutorial_qaoa_maxcut/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_qaoa_maxcut/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_qcbm/requirements.in b/demonstrations_v2/tutorial_qcbm/requirements.in index 543f5dbfb8..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_qcbm/requirements.in +++ b/demonstrations_v2/tutorial_qcbm/requirements.in @@ -1,6 +1,3 @@ jax jaxlib -matplotlib -numpy optax -pennylane diff --git a/demonstrations_v2/tutorial_qchem_external/requirements.in b/demonstrations_v2/tutorial_qchem_external/requirements.in index 55116fe210..58186cb893 100644 --- a/demonstrations_v2/tutorial_qchem_external/requirements.in +++ b/demonstrations_v2/tutorial_qchem_external/requirements.in @@ -1,3 +1,2 @@ openfermion -pennylane pyscf diff --git a/demonstrations_v2/tutorial_qft/requirements.in b/demonstrations_v2/tutorial_qft/requirements.in index 8132c3977e..9a635b910d 100644 --- a/demonstrations_v2/tutorial_qft/requirements.in +++ b/demonstrations_v2/tutorial_qft/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_qft_arithmetics/requirements.in b/demonstrations_v2/tutorial_qft_arithmetics/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_qft_arithmetics/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_qgrnn/requirements.in b/demonstrations_v2/tutorial_qgrnn/requirements.in index a56451577c..32c40881df 100644 --- a/demonstrations_v2/tutorial_qgrnn/requirements.in +++ b/demonstrations_v2/tutorial_qgrnn/requirements.in @@ -1,4 +1,2 @@ -matplotlib networkx -pennylane scipy diff --git a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in b/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_qjit_compile_grovers_algorithm_with_catalyst/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_qnn_module_tf/requirements.in b/demonstrations_v2/tutorial_qnn_module_tf/requirements.in index 9f9b8dab3a..480638e1f8 100644 --- a/demonstrations_v2/tutorial_qnn_module_tf/requirements.in +++ b/demonstrations_v2/tutorial_qnn_module_tf/requirements.in @@ -1,5 +1,2 @@ -matplotlib -numpy -pennylane scikit-learn tensorflow diff --git a/demonstrations_v2/tutorial_qnn_module_torch/requirements.in b/demonstrations_v2/tutorial_qnn_module_torch/requirements.in index 375ecbc231..04383c96ca 100644 --- a/demonstrations_v2/tutorial_qnn_module_torch/requirements.in +++ b/demonstrations_v2/tutorial_qnn_module_torch/requirements.in @@ -1,5 +1,2 @@ -matplotlib -numpy -pennylane scikit-learn torch diff --git a/demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in b/demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in index 2315fdb489..dafe458526 100644 --- a/demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in +++ b/demonstrations_v2/tutorial_qnn_multivariate_regression/requirements.in @@ -1,6 +1,4 @@ jax jaxlib -matplotlib optax -pennylane scikit-learn diff --git a/demonstrations_v2/tutorial_qpe/requirements.in b/demonstrations_v2/tutorial_qpe/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_qpe/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_qsvt_hardware/requirements.in b/demonstrations_v2/tutorial_qsvt_hardware/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_qsvt_hardware/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in b/demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_quantum_analytic_descent/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_quantum_chemistry/requirements.in b/demonstrations_v2/tutorial_quantum_chemistry/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_quantum_chemistry/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in b/demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in index f84259cda4..4d07dfe2f8 100644 --- a/demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/requirements.in @@ -1,3 +1 @@ -matplotlib networkx -pennylane diff --git a/demonstrations_v2/tutorial_quantum_dropout/requirements.in b/demonstrations_v2/tutorial_quantum_dropout/requirements.in index 4dffc525cf..dafe458526 100644 --- a/demonstrations_v2/tutorial_quantum_dropout/requirements.in +++ b/demonstrations_v2/tutorial_quantum_dropout/requirements.in @@ -1,7 +1,4 @@ jax jaxlib -matplotlib -numpy optax -pennylane scikit-learn diff --git a/demonstrations_v2/tutorial_quantum_gans/requirements.in b/demonstrations_v2/tutorial_quantum_gans/requirements.in index f2ca2d2c1e..caad7aa85b 100644 --- a/demonstrations_v2/tutorial_quantum_gans/requirements.in +++ b/demonstrations_v2/tutorial_quantum_gans/requirements.in @@ -1,6 +1,3 @@ -matplotlib -numpy pandas -pennylane torch torchvision diff --git a/demonstrations_v2/tutorial_quantum_metrology/requirements.in b/demonstrations_v2/tutorial_quantum_metrology/requirements.in index 77f4df8aac..3de8e6abf2 100644 --- a/demonstrations_v2/tutorial_quantum_metrology/requirements.in +++ b/demonstrations_v2/tutorial_quantum_metrology/requirements.in @@ -1,3 +1,2 @@ -pennylane pennylane_cirq sympy diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in b/demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in index 8132c3977e..9a635b910d 100644 --- a/demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in b/demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in index 88255b3813..ac988bdf84 100644 --- a/demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/requirements.in @@ -1,4 +1,2 @@ -matplotlib -pennylane torch torchvision diff --git a/demonstrations_v2/tutorial_quanvolution/requirements.in b/demonstrations_v2/tutorial_quanvolution/requirements.in index b85bbff928..0f57144081 100644 --- a/demonstrations_v2/tutorial_quanvolution/requirements.in +++ b/demonstrations_v2/tutorial_quanvolution/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane tensorflow diff --git a/demonstrations_v2/tutorial_qubit_rotation/requirements.in b/demonstrations_v2/tutorial_qubit_rotation/requirements.in index 375a0e28cc..a789351986 100644 --- a/demonstrations_v2/tutorial_qubit_rotation/requirements.in +++ b/demonstrations_v2/tutorial_qubit_rotation/requirements.in @@ -1,4 +1,3 @@ jax jaxlib jaxopt -pennylane diff --git a/demonstrations_v2/tutorial_qubit_tapering/requirements.in b/demonstrations_v2/tutorial_qubit_tapering/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_qubit_tapering/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_qubitization/requirements.in b/demonstrations_v2/tutorial_qubitization/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_qubitization/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_resource_estimation/requirements.in b/demonstrations_v2/tutorial_resource_estimation/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_resource_estimation/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_rl_pulse/requirements.in b/demonstrations_v2/tutorial_rl_pulse/requirements.in index 67a803e170..6fb80fb580 100644 --- a/demonstrations_v2/tutorial_rl_pulse/requirements.in +++ b/demonstrations_v2/tutorial_rl_pulse/requirements.in @@ -1,7 +1,5 @@ flax jax jaxlib -matplotlib optax -pennylane qutip diff --git a/demonstrations_v2/tutorial_rosalin/requirements.in b/demonstrations_v2/tutorial_rosalin/requirements.in index a9c09d8048..9a635b910d 100644 --- a/demonstrations_v2/tutorial_rosalin/requirements.in +++ b/demonstrations_v2/tutorial_rosalin/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane scipy diff --git a/demonstrations_v2/tutorial_rotoselect/requirements.in b/demonstrations_v2/tutorial_rotoselect/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_rotoselect/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_sc_qubits/requirements.in b/demonstrations_v2/tutorial_sc_qubits/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_sc_qubits/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in index 17c51f9a9f..9a635b910d 100644 --- a/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in +++ b/demonstrations_v2/tutorial_shadow_hamiltonian_simulation/requirements.in @@ -1,3 +1 @@ -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_spsa/requirements.in b/demonstrations_v2/tutorial_spsa/requirements.in index f3b5d9cab2..fe8bbd159d 100644 --- a/demonstrations_v2/tutorial_spsa/requirements.in +++ b/demonstrations_v2/tutorial_spsa/requirements.in @@ -1,4 +1,2 @@ -matplotlib -pennylane qiskit_aer qiskit_ibm_runtime diff --git a/demonstrations_v2/tutorial_state_preparation/requirements.in b/demonstrations_v2/tutorial_state_preparation/requirements.in index d3389b90bd..12c6d5d5ea 100644 --- a/demonstrations_v2/tutorial_state_preparation/requirements.in +++ b/demonstrations_v2/tutorial_state_preparation/requirements.in @@ -1,3 +1 @@ -numpy -pennylane torch diff --git a/demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in b/demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in index a9c09d8048..9a635b910d 100644 --- a/demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in +++ b/demonstrations_v2/tutorial_stochastic_parameter_shift/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane scipy diff --git a/demonstrations_v2/tutorial_teleportation/requirements.in b/demonstrations_v2/tutorial_teleportation/requirements.in deleted file mode 100644 index 002324f217..0000000000 --- a/demonstrations_v2/tutorial_teleportation/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pennylane diff --git a/demonstrations_v2/tutorial_testing_symmetry/requirements.in b/demonstrations_v2/tutorial_testing_symmetry/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_testing_symmetry/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_tn_circuits/requirements.in b/demonstrations_v2/tutorial_tn_circuits/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_tn_circuits/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_toric_code/requirements.in b/demonstrations_v2/tutorial_toric_code/requirements.in deleted file mode 100644 index 3090ac2cf2..0000000000 --- a/demonstrations_v2/tutorial_toric_code/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -pennylane diff --git a/demonstrations_v2/tutorial_trapped_ions/requirements.in b/demonstrations_v2/tutorial_trapped_ions/requirements.in index 8132c3977e..9a635b910d 100644 --- a/demonstrations_v2/tutorial_trapped_ions/requirements.in +++ b/demonstrations_v2/tutorial_trapped_ions/requirements.in @@ -1,4 +1 @@ -matplotlib -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_unitary_designs/requirements.in b/demonstrations_v2/tutorial_unitary_designs/requirements.in index 17c51f9a9f..9a635b910d 100644 --- a/demonstrations_v2/tutorial_unitary_designs/requirements.in +++ b/demonstrations_v2/tutorial_unitary_designs/requirements.in @@ -1,3 +1 @@ -numpy -pennylane scipy diff --git a/demonstrations_v2/tutorial_univariate_qvr/requirements.in b/demonstrations_v2/tutorial_univariate_qvr/requirements.in index 6e3976cc87..bad2d99a2e 100644 --- a/demonstrations_v2/tutorial_univariate_qvr/requirements.in +++ b/demonstrations_v2/tutorial_univariate_qvr/requirements.in @@ -1,4 +1,2 @@ covalent==0.227.0rc0 -matplotlib -pennylane torch diff --git a/demonstrations_v2/tutorial_variational_classifier/requirements.in b/demonstrations_v2/tutorial_variational_classifier/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_variational_classifier/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_vqe/requirements.in b/demonstrations_v2/tutorial_vqe/requirements.in index 0aae374bda..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_vqe/requirements.in +++ b/demonstrations_v2/tutorial_vqe/requirements.in @@ -1,5 +1,3 @@ jax jaxlib -matplotlib optax -pennylane diff --git a/demonstrations_v2/tutorial_vqe_qng/requirements.in b/demonstrations_v2/tutorial_vqe_qng/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_vqe_qng/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in b/demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in deleted file mode 100644 index 18b28256af..0000000000 --- a/demonstrations_v2/tutorial_vqe_spin_sectors/requirements.in +++ /dev/null @@ -1 +0,0 @@ -pennylane diff --git a/demonstrations_v2/tutorial_vqe_vqd/requirements.in b/demonstrations_v2/tutorial_vqe_vqd/requirements.in index 492121497d..52f0b6758a 100644 --- a/demonstrations_v2/tutorial_vqe_vqd/requirements.in +++ b/demonstrations_v2/tutorial_vqe_vqd/requirements.in @@ -1,5 +1,3 @@ jax jaxlib -numpy optax -pennylane diff --git a/demonstrations_v2/tutorial_vqls/requirements.in b/demonstrations_v2/tutorial_vqls/requirements.in deleted file mode 100644 index 5e98280e02..0000000000 --- a/demonstrations_v2/tutorial_vqls/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -pennylane diff --git a/demonstrations_v2/tutorial_vqt/requirements.in b/demonstrations_v2/tutorial_vqt/requirements.in index b57a1d96ba..a215aec472 100644 --- a/demonstrations_v2/tutorial_vqt/requirements.in +++ b/demonstrations_v2/tutorial_vqt/requirements.in @@ -1,6 +1,3 @@ -matplotlib networkx -numpy -pennylane scipy seaborn diff --git a/demonstrations_v2/tutorial_zne_catalyst/requirements.in b/demonstrations_v2/tutorial_zne_catalyst/requirements.in index b16382ce3f..76f185a3c3 100644 --- a/demonstrations_v2/tutorial_zne_catalyst/requirements.in +++ b/demonstrations_v2/tutorial_zne_catalyst/requirements.in @@ -1,4 +1,2 @@ pennylane-catalyst pennylane-qrack -numpy -pennylane diff --git a/demonstrations_v2/tutorial_zx_calculus/requirements.in b/demonstrations_v2/tutorial_zx_calculus/requirements.in index 48bae8219a..511a3ba917 100644 --- a/demonstrations_v2/tutorial_zx_calculus/requirements.in +++ b/demonstrations_v2/tutorial_zx_calculus/requirements.in @@ -1,3 +1 @@ -matplotlib -pennylane pyzx diff --git a/demonstrations_v2/vqe_parallel/requirements.in b/demonstrations_v2/vqe_parallel/requirements.in index f0de54fb32..b2034ba3b6 100644 --- a/demonstrations_v2/vqe_parallel/requirements.in +++ b/demonstrations_v2/vqe_parallel/requirements.in @@ -1,3 +1 @@ dask -matplotlib -pennylane diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index 62c38fc425..51ce336ba3 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -10,6 +10,7 @@ import subprocess from enum import Enum import re +import functools logger = getLogger("qml") @@ -25,6 +26,16 @@ class BuildTarget(Enum): class Demo: """Represents a demo and its metadata.""" + CORE_DEPENDENCIES = frozenset( + ( + "matplotlib", + "numpy", + "pennylane", + ) + ) + """Dependencies installed for every demo that do not + need to be specified by `requirements.in`.""" + name: str path: Path @@ -40,10 +51,13 @@ def metadata_file(self) -> Path: return self.path / "metadata.json" @property - def requirements_file(self) -> Path: + def requirements_file(self) -> Path | None: """Path to a requirements file containing unversioned dependencies for this demo.""" - return self.path / "requirements.in" + if (path := self.path / "requirements.in").exists(): + return path + + return None @property def resources(self) -> Sequence[Path]: @@ -59,11 +73,15 @@ def executable(self) -> bool: """Whether this demo can be exeucted.""" return self.name.startswith("tutorial_") - def requirements(self): + @functools.cached_property + def requirements(self) -> frozenset[str]: """Return a list of this demo's unversioned requirements.""" - with open(self.requirements_file, "r") as f: - return f.read().splitlines() + if path := self.requirements_file: + with open(path, "r") as f: + return frozenset(f.read().splitlines()) + + return frozenset() def find(search_dir: Path, *names: str) -> Iterator[Demo]: @@ -92,6 +110,7 @@ def build( demos: Sequence[Demo], target: BuildTarget, execute: bool, + quiet: bool = False, ) -> None: """Build the provided demos using 'sphinx-build', optionally executing them to generate plots and cell outputs. @@ -126,7 +145,7 @@ def build( _install_build_dependencies(build_venv, build_dir) if execute: - _install_execution_dependencies(build_venv, build_dir, demos) + _install_execution_dependencies(build_venv, build_dir, demos, quiet=quiet) cmd = [ str(build_venv.path / "bin" / "sphinx-build"), @@ -156,7 +175,7 @@ def _install_build_dependencies(venv: Virtualenv, build_dir: Path): def _install_execution_dependencies( - venv: Virtualenv, build_dir: Path, demos: Sequence[Demo] + venv: Virtualenv, build_dir: Path, demos: Sequence[Demo], quiet: bool ): """Install dependencies for executing provided demos into `venv`.""" @@ -170,16 +189,14 @@ def _install_execution_dependencies( if sys.platform == "darwin": _fix_pytorch_constraint_macos(constraints_file) - requirements: set[str] = set() + requirements: set[str] = set(Demo.CORE_DEPENDENCIES) for demo in demos: if demo.executable: - requirements.update(demo.requirements()) + requirements.update(demo.requirements) logger.info("Installing execution dependencies") cmds.pip_install( - venv.python, - *requirements, - constraints=constraints_file, + venv.python, *requirements, constraints=constraints_file, quiet=quiet ) From c54066c800aa2ea9034ef55e8408f68168abd85d Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 12:53:43 -0500 Subject: [PATCH 16/29] fmt --- lib/qml/lib/demo.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index 51ce336ba3..0fbcdf32bf 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -103,6 +103,13 @@ def find(search_dir: Path, *names: str) -> Iterator[Demo]: yield Demo(name=name, path=demo_dir.resolve()) +def search(search_dir: Path, pattern: str) -> Iterator[str]: + """Yield demo names in `search_dir` matching `pattern`.""" + for path in search_dir.glob(pattern=pattern): + if (path / "demo.py").exists(): + yield path.name + + def build( sphinx_dir: Path, build_dir: Path, From 1fc423ed26692a4c6a0e446cbfc1eed03ed37fb9 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 12:58:32 -0500 Subject: [PATCH 17/29] sync --- .../adjoint_diff_benchmarking/demo.py | 2 +- demonstrations_v2/ahs_aquila/demo.py | 2 +- .../braket-parallel-gradients/demo.py | 4 - .../circuits_as_fourier_series/demo.py | 1 - demonstrations_v2/ensemble_multi_qpu/demo.py | 16 +- .../ensemble_multi_qpu/metadata.json | 2 +- .../function_fitting_qsp/demo.py | 2 - demonstrations_v2/gbs/demo.py | 6 +- .../getting_started_with_hybrid_jobs/demo.py | 6 +- .../metadata.json | 2 +- .../how_to_use_qiskit1_with_pennylane/demo.py | 11 +- .../metadata.json | 2 +- demonstrations_v2/ibm_pennylane/demo.py | 27 +- demonstrations_v2/ibm_pennylane/metadata.json | 2 +- demonstrations_v2/learning2learn/demo.py | 5 +- .../learning2learn/metadata.json | 2 +- .../ml_classical_shadows/demo.py | 5 +- .../ml_classical_shadows/metadata.json | 2 +- demonstrations_v2/oqc_pulse/demo.py | 6 +- demonstrations_v2/oqc_pulse/metadata.json | 2 +- demonstrations_v2/plugins_hybrid/demo.py | 2 +- demonstrations_v2/pytorch_noise/demo.py | 6 +- demonstrations_v2/pytorch_noise/metadata.json | 2 +- demonstrations_v2/qnspsa/demo.py | 65 +- demonstrations_v2/qnspsa/metadata.json | 2 +- demonstrations_v2/qonn/demo.py | 2 +- demonstrations_v2/qrack/demo.py | 9 +- demonstrations_v2/qrack/metadata.json | 2 +- .../qsim_beyond_classical/demo.py | 2 +- demonstrations_v2/quantum_neural_net/demo.py | 2 +- demonstrations_v2/quantum_volume/demo.py | 55 +- .../quantum_volume/metadata.json | 2 +- .../demo.py | 2 - .../demo.py | 2 +- demonstrations_v2/tutorial_QGAN/demo.py | 2 +- demonstrations_v2/tutorial_QUBO/demo.py | 2 +- .../tutorial_adaptive_circuits/demo.py | 68 +- .../tutorial_adaptive_circuits/metadata.json | 2 +- .../tutorial_adjoint_diff/demo.py | 2 +- demonstrations_v2/tutorial_apply_qsvt/demo.py | 41 +- .../tutorial_apply_qsvt/metadata.json | 2 +- demonstrations_v2/tutorial_backprop/demo.py | 2 +- .../tutorial_barren_gadgets/demo.py | 1 - .../tutorial_barren_plateaus/demo.py | 2 +- .../tutorial_block_encoding/demo.py | 4 - demonstrations_v2/tutorial_bluequbit/demo.py | 4 - .../tutorial_bluequbit/metadata.json | 2 +- .../tutorial_chemical_reactions/demo.py | 87 +- .../tutorial_chemical_reactions/metadata.json | 2 +- .../tutorial_circuit_compilation/demo.py | 2 +- .../tutorial_classical_kernels/demo.py | 2 +- .../tutorial_classical_shadows/demo.py | 6 +- .../tutorial_classically_boosted_vqe/demo.py | 60 +- .../metadata.json | 2 +- .../demo.py | 3 +- .../metadata.json | 2 +- .../tutorial_coherent_vqls/demo.py | 2 +- .../tutorial_contextuality/demo.py | 2 +- .../demo.py | 2 +- .../tutorial_diffable-mitigation/demo.py | 2 +- .../tutorial_diffable_shadows/demo.py | 2 +- .../tutorial_differentiable_HF/demo.py | 96 +- .../tutorial_differentiable_HF/metadata.json | 2 +- .../tutorial_doubly_stochastic/demo.py | 2 +- .../tutorial_eqnn_force_field/demo.py | 7 +- .../tutorial_eqnn_force_field/metadata.json | 2 +- .../demo.py | 210 ++-- .../metadata.json | 2 +- .../tutorial_error_mitigation/demo.py | 10 +- .../tutorial_error_mitigation/metadata.json | 2 +- .../demo.py | 6 +- demonstrations_v2/tutorial_falqon/demo.py | 18 +- .../tutorial_falqon/metadata.json | 2 +- .../tutorial_fermionic_operators/demo.py | 7 +- .../metadata.json | 2 +- .../demo.py | 81 +- .../metadata.json | 4 +- .../tutorial_gaussian_transformation/demo.py | 2 +- .../tutorial_general_parshift/demo.py | 2 +- .../tutorial_geometric_qml/demo.py | 2 +- .../tutorial_givens_rotations/demo.py | 54 +- .../tutorial_givens_rotations/metadata.json | 2 +- .../tutorial_grovers_algorithm/demo.py | 2 +- .../tutorial_here_comes_the_sun/demo.py | 2 +- .../demo.py | 10 +- .../tutorial_how_to_use_registers/demo.py | 1 - .../demo.py | 6 +- .../demo.py | 56 +- .../metadata.json | 2 +- demonstrations_v2/tutorial_intro_qrom/demo.py | 4 +- .../tutorial_intro_qrom/metadata.json | 2 +- demonstrations_v2/tutorial_intro_qsvt/demo.py | 29 +- .../tutorial_intro_qsvt/metadata.json | 2 +- .../tutorial_isingmodel_PyTorch/demo.py | 2 +- .../tutorial_jax_transformations/demo.py | 2 +- .../tutorial_kak_decomposition/demo.py | 988 ++++++++++++++++++ .../tutorial_kak_decomposition/metadata.json | 174 +++ .../tutorial_kernel_based_training/demo.py | 2 +- .../tutorial_kernels_module/demo.py | 21 +- .../tutorial_lcu_blockencoding/demo.py | 8 +- .../tutorial_learning_few_data/demo.py | 8 +- .../demo.py | 2 +- .../tutorial_learningshallow/demo.py | 2 +- demonstrations_v2/tutorial_liealgebra/demo.py | 2 +- .../tutorial_liesim_extension/demo.py | 6 +- .../tutorial_liesim_extension/metadata.json | 2 +- .../tutorial_local_cost_functions/demo.py | 2 +- .../tutorial_magic_state_distillation/demo.py | 2 +- demonstrations_v2/tutorial_mapping/demo.py | 11 +- .../tutorial_mapping/metadata.json | 2 +- demonstrations_v2/tutorial_mbqc/demo.py | 7 +- .../tutorial_measurement_optimize/demo.py | 63 +- .../metadata.json | 2 +- .../tutorial_mitigation_advantage/demo.py | 2 +- .../tutorial_mol_geo_opt/demo.py | 77 +- .../tutorial_mol_geo_opt/metadata.json | 2 +- .../tutorial_neutral_atoms/demo.py | 2 +- .../demo.py | 2 +- .../tutorial_noisy_circuits/demo.py | 1 - demonstrations_v2/tutorial_odegen/demo.py | 2 +- .../tutorial_optimal_control/demo.py | 2 +- demonstrations_v2/tutorial_pasqal/demo.py | 2 +- .../tutorial_phase_kickback/demo.py | 2 +- demonstrations_v2/tutorial_photonics/demo.py | 4 +- .../tutorial_pulse_programming101/demo.py | 2 +- demonstrations_v2/tutorial_qaoa_intro/demo.py | 5 +- .../tutorial_qaoa_intro/metadata.json | 2 +- .../tutorial_qchem_external/demo.py | 6 +- .../tutorial_qchem_external/metadata.json | 2 +- demonstrations_v2/tutorial_qft/demo.py | 2 +- demonstrations_v2/tutorial_qft/metadata.json | 2 +- .../tutorial_qft_arithmetics/demo.py | 2 - demonstrations_v2/tutorial_qgrnn/demo.py | 13 +- .../tutorial_qgrnn/metadata.json | 2 +- .../tutorial_qnn_module_tf/demo.py | 2 +- .../tutorial_qnn_module_torch/demo.py | 2 +- .../tutorial_qsvt_hardware/demo.py | 75 +- .../tutorial_qsvt_hardware/metadata.json | 2 +- .../tutorial_quantum_analytic_descent/demo.py | 6 +- .../tutorial_quantum_chemistry/demo.py | 2 +- .../tutorial_quantum_circuit_cutting/demo.py | 12 +- .../metadata.json | 2 +- .../tutorial_quantum_gans/demo.py | 2 +- .../tutorial_quantum_metrology/demo.py | 2 +- .../tutorial_quantum_natural_gradient/demo.py | 2 +- .../demo.py | 2 +- .../tutorial_quanvolution/demo.py | 2 +- .../tutorial_qubit_rotation/demo.py | 2 +- .../tutorial_qubit_tapering/demo.py | 56 +- .../tutorial_qubit_tapering/metadata.json | 2 +- .../demo.py | 2 +- .../tutorial_resource_estimation/demo.py | 9 +- .../metadata.json | 2 +- demonstrations_v2/tutorial_rosalin/demo.py | 2 +- demonstrations_v2/tutorial_rotoselect/demo.py | 2 +- demonstrations_v2/tutorial_sc_qubits/demo.py | 4 +- demonstrations_v2/tutorial_spsa/demo.py | 3 +- .../tutorial_state_preparation/demo.py | 2 +- .../demo.py | 2 +- .../tutorial_teleportation/demo.py | 1 - .../tutorial_tensor_network_basics/demo.py | 583 +++++++++++ .../metadata.json | 172 +++ .../requirements.in | 0 .../tutorial_testing_symmetry/demo.py | 2 +- .../tutorial_tn_circuits/demo.py | 10 +- demonstrations_v2/tutorial_toric_code/demo.py | 2 +- .../tutorial_trapped_ions/demo.py | 4 +- .../tutorial_univariate_qvr/demo.py | 5 +- .../tutorial_variational_classifier/demo.py | 2 +- demonstrations_v2/tutorial_vqe/demo.py | 2 +- demonstrations_v2/tutorial_vqe_qng/demo.py | 10 +- .../tutorial_vqe_spin_sectors/demo.py | 84 +- .../tutorial_vqe_spin_sectors/metadata.json | 2 +- demonstrations_v2/tutorial_vqe_vqd/demo.py | 2 +- .../tutorial_vqe_vqd/metadata.json | 2 +- demonstrations_v2/tutorial_vqls/demo.py | 2 +- demonstrations_v2/tutorial_vqt/demo.py | 2 +- .../tutorial_zx_calculus/demo.py | 2 +- demonstrations_v2/vqe_parallel/demo.py | 37 +- demonstrations_v2/vqe_parallel/metadata.json | 2 +- lib/qml/app/app.py | 2 +- 181 files changed, 2878 insertions(+), 900 deletions(-) create mode 100644 demonstrations_v2/tutorial_kak_decomposition/demo.py create mode 100644 demonstrations_v2/tutorial_kak_decomposition/metadata.json create mode 100644 demonstrations_v2/tutorial_tensor_network_basics/demo.py create mode 100644 demonstrations_v2/tutorial_tensor_network_basics/metadata.json create mode 100644 demonstrations_v2/tutorial_tensor_network_basics/requirements.in diff --git a/demonstrations_v2/adjoint_diff_benchmarking/demo.py b/demonstrations_v2/adjoint_diff_benchmarking/demo.py index a53dbcff5f..04a557f40c 100644 --- a/demonstrations_v2/adjoint_diff_benchmarking/demo.py +++ b/demonstrations_v2/adjoint_diff_benchmarking/demo.py @@ -144,4 +144,4 @@ def circuit(params): # # About the author # ---------------- -# .. include:: ../_static/authors/christina_lee.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/ahs_aquila/demo.py b/demonstrations_v2/ahs_aquila/demo.py index ae4f39f6f7..c579b2f385 100644 --- a/demonstrations_v2/ahs_aquila/demo.py +++ b/demonstrations_v2/ahs_aquila/demo.py @@ -818,4 +818,4 @@ def circuit(params): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/lillian_frederiksen.txt +# diff --git a/demonstrations_v2/braket-parallel-gradients/demo.py b/demonstrations_v2/braket-parallel-gradients/demo.py index a1d4f8b446..59a549b8b4 100644 --- a/demonstrations_v2/braket-parallel-gradients/demo.py +++ b/demonstrations_v2/braket-parallel-gradients/demo.py @@ -608,8 +608,4 @@ def cost_function(params, **kwargs): ############################################################################## # About the authors # ----------------- -# .. include:: ../_static/authors/thomas_bromley.txt # -# .. include:: ../_static/authors/maria_schuld.txt -# -# .. include:: ../_static/authors/matthew_beach.txt diff --git a/demonstrations_v2/circuits_as_fourier_series/demo.py b/demonstrations_v2/circuits_as_fourier_series/demo.py index bdfa30e199..63c67daf27 100644 --- a/demonstrations_v2/circuits_as_fourier_series/demo.py +++ b/demonstrations_v2/circuits_as_fourier_series/demo.py @@ -801,4 +801,3 @@ # About the author # ---------------- # -# .. include:: ../_static/authors/david_wakeham.txt diff --git a/demonstrations_v2/ensemble_multi_qpu/demo.py b/demonstrations_v2/ensemble_multi_qpu/demo.py index 55b83eebd5..eaf0bc3ea3 100644 --- a/demonstrations_v2/ensemble_multi_qpu/demo.py +++ b/demonstrations_v2/ensemble_multi_qpu/demo.py @@ -18,10 +18,10 @@ classification problem. .. warning:: - This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin. - It is compatible with ``qiskit==0.46`` and ``pennylane-qiskit==0.35.1``. Older versions of - Qiskit and the Pennylane-Qiskit plugin should not be installed in environments with an - existing installation of Qiskit 1.0 or above. + This demo does not work with the latest version of Qiskit or the Pennylane-Qiskit plugin and + is compatible with Python versions 3.10 and lower. It is compatible with ``qiskit==0.46`` and + ``pennylane-qiskit==0.35.1``. Older versions of Qiskit and the Pennylane-Qiskit plugin should + not be installed in environments with an existing installation of Qiskit 1.0 or above. We use the ``rigetti.qvm`` device to simulate one QPU and the ``qiskit.aer`` device to simulate another. Each QPU makes an independent prediction, and an ensemble model is @@ -198,9 +198,9 @@ def plot_points(x_train, y_train, x_test, y_test): ############################################################################## # .. note:: # If you have access to Rigetti hardware, you can swap out ``rigetti.qvm`` for ``rigetti.qpu`` -# and specify the hardware device to run on. Users with access to the IBM Q Experience can -# swap ``qiskit.aer`` for ``qiskit.ibmq`` and specify their chosen backend (see `here -# `__). +# and specify the hardware device to run on. Users with access to the IBM hardware can +# swap ``qiskit.aer`` for ``qiskit.remote`` and specify their chosen backend (see `here +# `__). # # # The circuits for both QPUs are shown in the figure below: @@ -578,4 +578,4 @@ def plot_points_prediction(x, y, p, title): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/thomas_bromley.txt \ No newline at end of file +# diff --git a/demonstrations_v2/ensemble_multi_qpu/metadata.json b/demonstrations_v2/ensemble_multi_qpu/metadata.json index 2163c50b80..2e1918c1d5 100644 --- a/demonstrations_v2/ensemble_multi_qpu/metadata.json +++ b/demonstrations_v2/ensemble_multi_qpu/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-02-14T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations_v2/function_fitting_qsp/demo.py b/demonstrations_v2/function_fitting_qsp/demo.py index 6de7b5afb5..0544e49042 100644 --- a/demonstrations_v2/function_fitting_qsp/demo.py +++ b/demonstrations_v2/function_fitting_qsp/demo.py @@ -576,5 +576,3 @@ def step_func(x): # # About the author # ~~~~~~~~~~~~~~~~ -# .. include:: ../_static/authors/jay_soni.txt -# diff --git a/demonstrations_v2/gbs/demo.py b/demonstrations_v2/gbs/demo.py index b5c2ccbb61..e1ecc9c2ba 100644 --- a/demonstrations_v2/gbs/demo.py +++ b/demonstrations_v2/gbs/demo.py @@ -476,8 +476,6 @@ def gbs_circuit(): # photonic fault-tolerant quantum computer. arXiv preprint arXiv:2010.02905. # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/josh_izaac.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/nathan_killoran.txt diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py b/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py index 9dc028badc..1d97626328 100644 --- a/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/demo.py @@ -304,6 +304,10 @@ def circuit(params): # .. note:: # AWS devices must be declared within the body of the decorated function. # +# .. warning:: +# The Rigetti device used in this demo, AspenM3, has been retired. For an updated list of available hardware through +# Amazon Braket, please consult the `supported regions and devices documentation `__. The general steps outlined below still hold regardless of the choice of device, though. +# from braket.devices import Devices @@ -404,4 +408,4 @@ def circuit(params): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/matthew_beach.txt +# diff --git a/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json b/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json index 3000431cf0..b7b67ddfe9 100644 --- a/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json +++ b/demonstrations_v2/getting_started_with_hybrid_jobs/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-10-16T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py index 1a6e0ca61a..8bc5ab1805 100644 --- a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/demo.py @@ -242,7 +242,7 @@ def qiskit_GHZ_circuit(n): # pl_circuit = qml.QNode(pl_qfunc, device=qml.device("lightning.qubit", wires=n)) -pl_circuit() +print(pl_circuit()) ###################################################################### # .. rst-class:: sphx-glr-script-out @@ -263,7 +263,7 @@ def qiskit_GHZ_circuit(n): pl_qfunc = qml.from_qiskit(qc, measurements=measurements) pl_circuit = qml.QNode(pl_qfunc, device=qml.device("default.qubit", wires=n)) -pl_circuit(shots=5) +print(pl_circuit(shots=5)) ###################################################################### # .. rst-class:: sphx-glr-script-out @@ -304,6 +304,7 @@ def qiskit_GHZ_circuit(n): # from qiskit.circuit import ParameterVector, Parameter +from matplotlib import pyplot as plt n = 3 @@ -316,6 +317,8 @@ def qiskit_GHZ_circuit(n): qc.ry(angle2, [2]) qc.draw("mpl") +plt.show() + ###################################################################### # .. rst-class:: image-no-text-wrap @@ -349,7 +352,8 @@ def differentiable_circuit(phis, theta): theta = np.array([0.19]) print(differentiable_circuit(phis, theta)) -print(qml.draw_mpl(differentiable_circuit)(phis, theta)) +qml.draw_mpl(differentiable_circuit, style="pennylane")(phis, theta) +plt.show() ###################################################################### # .. rst-class:: sphx-glr-script-out @@ -430,5 +434,4 @@ def cost(phis, theta): ###################################################################### # About the author # ---------------- -# .. include:: ../_static/authors/isaac_de_vlugt.txt # diff --git a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json index 3c9d27a9ba..c5a0361540 100644 --- a/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json +++ b/demonstrations_v2/how_to_use_qiskit1_with_pennylane/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-07-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-08T00:00:00+00:00", "categories": [ "Quantum Computing", "How-to" diff --git a/demonstrations_v2/ibm_pennylane/demo.py b/demonstrations_v2/ibm_pennylane/demo.py index eb1101d4bc..0d318931b5 100644 --- a/demonstrations_v2/ibm_pennylane/demo.py +++ b/demonstrations_v2/ibm_pennylane/demo.py @@ -124,6 +124,16 @@ # # First, we set up our problem as usual, and then retrieve a program ID from IBM, which gives us a # place to upload our job: +# +# .. warning:: +# +# By default, this demo uses the online simulator (`ibmq_qasm_simulator`), which is free at the +# time of writing. Please note that IBM Quantum's policies may change, and simulators could become paid services. +# Always verify current pricing and access policies on the IBM Quantum platform. +# +# This demo can also run on quantum hardware by updating the backend variable accordingly. Be aware, that access +# to IBM Quantum hardware is not free and may result in substantial costs. Ensure you are aware of these costs +# and comfortable with them before proceeding. from pennylane import numpy as pnp from qiskit_ibm_runtime import QiskitRuntimeService @@ -135,13 +145,16 @@ H = dataset.hamiltonian qubits = 4 +# Initialize QiskitRuntimeService service = QiskitRuntimeService() -# Gets a 127 qubit device from IBM -backend = service.least_busy(n_qubits=127, simulator=False, operational=True) + +# Use the `ibmq_qasm_simulator` available on IBM Cloud +backend = service.backend("ibmq_qasm_simulator") try: - # Although we only need 4 qubits, our device has 127 qubits, therefore we initialize with wires=127 - dev = qml.device("qiskit.remote", wires=127, backend=backend) + # Our device supports a maximum of 31 qubits + NUM_QUBITS_SUPPORTED = 31 + dev = qml.device("qiskit.remote", wires=NUM_QUBITS_SUPPORTED, backend=backend) except Exception as e: print(e) @@ -360,8 +373,6 @@ def cost_fn_2(theta): # This tutorial has demonstrated how and why to use quantum computing hardware provided by IBM using PennyLane. To read # more about the details and possibilities of the PennyLane-Qiskit plugin, `read the documentation `__. # -# About the authors +# About the author # ---------------- -# .. include:: ../_static/authors/kaur_kristjuhan.txt -# .. include:: ../_static/authors/clara_ferreira_cores.txt -# .. include:: ../_static/authors/mark_nicholas_jones.txt +# diff --git a/demonstrations_v2/ibm_pennylane/metadata.json b/demonstrations_v2/ibm_pennylane/metadata.json index 351c3d6d25..95825f1de7 100644 --- a/demonstrations_v2/ibm_pennylane/metadata.json +++ b/demonstrations_v2/ibm_pennylane/metadata.json @@ -12,7 +12,7 @@ } ], "dateOfPublication": "2023-06-20T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations_v2/learning2learn/demo.py b/demonstrations_v2/learning2learn/demo.py index effa725a2e..57df7dd4b2 100644 --- a/demonstrations_v2/learning2learn/demo.py +++ b/demonstrations_v2/learning2learn/demo.py @@ -13,6 +13,9 @@ *Author: Stefano Mangini — Posted: 02 March 2021. Last updated: 15 September 2021.* +.. warning:: + This demo is only compatible with TensorFlow version ``2.9`` or below. + Otherwise, the output of some cells and plots may differ. In this demo we recreate the architecture proposed in *Learning to learn with quantum neural networks via @@ -1181,4 +1184,4 @@ def call(self, inputs): # # About the author # ---------------- -# .. include:: ../_static/authors/stefano_mangini.txt +# diff --git a/demonstrations_v2/learning2learn/metadata.json b/demonstrations_v2/learning2learn/metadata.json index f6b067357f..c77a2eb0f2 100644 --- a/demonstrations_v2/learning2learn/metadata.json +++ b/demonstrations_v2/learning2learn/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-03-02T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-07T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations_v2/ml_classical_shadows/demo.py b/demonstrations_v2/ml_classical_shadows/demo.py index 4d290982a4..66bca15783 100644 --- a/demonstrations_v2/ml_classical_shadows/demo.py +++ b/demonstrations_v2/ml_classical_shadows/demo.py @@ -410,8 +410,8 @@ def estimate_shadow_obs(shadow, observable, k=10): target_obs = np.array([map_name_to_int[observable.name]]) target_locs = np.array([observable.wires[0]]) else: - target_obs = np.array([map_name_to_int[o.name] for o in observable.obs]) - target_locs = np.array([o.wires[0] for o in observable.obs]) + target_obs = np.array([map_name_to_int[o.name] for o in observable.operands]) + target_locs = np.array([o.wires[0] for o in observable.operands]) # perform median of means to return the result means = [] @@ -859,4 +859,3 @@ def fit_predict_data(cij, kernel, opt="linear"): # # About the author # ---------------- - diff --git a/demonstrations_v2/ml_classical_shadows/metadata.json b/demonstrations_v2/ml_classical_shadows/metadata.json index 3b515b5fc0..5bcf2a0116 100644 --- a/demonstrations_v2/ml_classical_shadows/metadata.json +++ b/demonstrations_v2/ml_classical_shadows/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-05-02T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations_v2/oqc_pulse/demo.py b/demonstrations_v2/oqc_pulse/demo.py index 5c3bd0ab50..e204e2124f 100644 --- a/demonstrations_v2/oqc_pulse/demo.py +++ b/demonstrations_v2/oqc_pulse/demo.py @@ -11,6 +11,10 @@ *Author: Korbinian Kottmann — Posted: 30 October 2023.* +.. warning:: + The OQC Lucy device is no longer available on Amazon Braket. As there is no alternative at this time, this + demo is now intended for educational purposes only. + Pulse-level access to quantum computers offers many interesting new avenues in quantum optimal control, variational quantum algorithms and device-aware algorithm design. We now have the possibility to run hardware-level circuits combined with standard gates on a @@ -545,4 +549,4 @@ def qnode_lucy(params, duration=15.0): # # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/oqc_pulse/metadata.json b/demonstrations_v2/oqc_pulse/metadata.json index 1c964a11be..bd26d757a4 100644 --- a/demonstrations_v2/oqc_pulse/metadata.json +++ b/demonstrations_v2/oqc_pulse/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-10-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Quantum Computing" ], diff --git a/demonstrations_v2/plugins_hybrid/demo.py b/demonstrations_v2/plugins_hybrid/demo.py index 5e8048497f..2267192a82 100644 --- a/demonstrations_v2/plugins_hybrid/demo.py +++ b/demonstrations_v2/plugins_hybrid/demo.py @@ -449,4 +449,4 @@ def cost(params, phi1=0.5, phi2=0.1): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/pytorch_noise/demo.py b/demonstrations_v2/pytorch_noise/demo.py index 1593bc3f90..3a3cd81669 100644 --- a/demonstrations_v2/pytorch_noise/demo.py +++ b/demonstrations_v2/pytorch_noise/demo.py @@ -15,6 +15,10 @@ *Author: Josh Izaac — Posted: 11 October 2019. Last updated: 9 November 2022.* +.. warning:: + This demo is only compatible with PennyLane v0.40 or below and Braket v1.31.0. To use Rigetti hardware with newer versions of PennyLane please use the `PennyLane-Braket plugin `__ instead. + + Let's revisit the original :ref:`qubit rotation ` tutorial, but instead of using the default NumPy/autograd QNode interface, we'll use the :doc:`introduction/interfaces/torch`. We'll also replace the ``default.qubit`` device with a noisy ``rigetti.qvm`` @@ -251,4 +255,4 @@ def cost(phi, theta, step): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# diff --git a/demonstrations_v2/pytorch_noise/metadata.json b/demonstrations_v2/pytorch_noise/metadata.json index 3bb21e9b33..97f6fc7129 100644 --- a/demonstrations_v2/pytorch_noise/metadata.json +++ b/demonstrations_v2/pytorch_noise/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2019-10-11T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations_v2/qnspsa/demo.py b/demonstrations_v2/qnspsa/demo.py index d859f176e0..8d35e96225 100644 --- a/demonstrations_v2/qnspsa/demo.py +++ b/demonstrations_v2/qnspsa/demo.py @@ -356,26 +356,12 @@ def get_grad(params_curr): # 0 (minimum overlap) and 1 (perfect overlap). # -from copy import copy - - -def get_operations(qnode, params): - qnode.construct([params], {}) - return qnode.tape.operations - - def get_overlap_tape(qnode, params1, params2): - op_forward = get_operations(qnode, params1) - op_inv = get_operations(qnode, params2) - - with qml.tape.QuantumTape() as tape: - for op in op_forward: - qml.apply(op) - for op in reversed(op_inv): - qml.adjoint(copy(op)) - qml.probs(wires=qnode.tape.wires.labels) - return tape + tape_forward = qml.workflow.construct_tape(qnode)(*params1) + tape_inv = qml.workflow.construct_tape(qnode)(*params2) + ops = tape_forward.operations + list(qml.adjoint(op) for op in reversed(tape_inv.operations)) + return qml.tape.QuantumTape(ops, [qml.probs(wires=tape_forward.wires)]) def get_state_overlap(tape): return qml.execute([tape], dev, None)[0][0] @@ -833,10 +819,8 @@ def __get_spsa_grad_tapes(self, cost, params): # used to estimate the gradient per optimization step. The sampled # direction is of the shape of the input parameter. direction = self.__get_perturbation_direction(params) - cost.construct([params + self.finite_diff_step * direction], {}) - tape_forward = cost.tape.copy(copy_operations=True) - cost.construct([params - self.finite_diff_step * direction], {}) - tape_backward = cost.tape.copy(copy_operations=True) + tape_forward = qml.workflow.construct_tape(cost)(*[params + self.finite_diff_step * direction]) + tape_backward = qml.workflow.construct_tape(cost)(*[params - self.finite_diff_step * direction]) return [tape_forward, tape_backward], direction def __update_tensor(self, tensor_raw): @@ -864,21 +848,7 @@ def __get_tensor_tapes(self, cost, params): return tapes, dir_vecs def __get_overlap_tape(self, cost, params1, params2): - op_forward = self.__get_operations(cost, params1) - op_inv = self.__get_operations(cost, params2) - - with qml.tape.QuantumTape() as tape: - for op in op_forward: - qml.apply(op) - for op in reversed(op_inv): - qml.adjoint(copy(op)) - qml.probs(wires=cost.tape.wires.labels) - return tape - - def __get_operations(self, cost, params): - # Given a QNode, returns the list of operations before the measurement. - cost.construct([params], {}) - return cost.tape.operations + return get_overlap_tape(cost, params1, params2) def __get_tensor_moving_avg(self, metric_tensor): # For numerical stability: averaging on the Fubini-Study metric tensor. @@ -893,10 +863,8 @@ def __regularize_tensor(self, metric_tensor): def __apply_blocking(self, cost, params_curr, params_next): # For numerical stability: apply the blocking condition on the parameter update. - cost.construct([params_curr], {}) - tape_loss_curr = cost.tape.copy(copy_operations=True) - cost.construct([params_next], {}) - tape_loss_next = cost.tape.copy(copy_operations=True) + tape_loss_curr = qml.workflow.construct_tape(cost)(*[params_curr]) + tape_loss_next = qml.workflow.construct_tape(cost)(*[params_next]) loss_curr, loss_next = qml.execute([tape_loss_curr, tape_loss_next], cost.device, None) # self.k has been updated earlier. @@ -945,11 +913,14 @@ def __apply_blocking(self, cost, params_curr, params_next): # The optimizer performs reasonably well: the loss drops over optimization # steps and converges finally. We then reproduce the benchmarking test # between the gradient descent, quantum natural gradient descent, SPSA and -# QN-SPSA in Fig. 1(b) of reference [#Gacon2021]_ with the following job. You -# can find a more detailed version of the example in this -# `notebook `__, -# with its dependencies in the `source_scripts` -# `folder `__. +# QN-SPSA in Fig. 1(b) of reference [#Gacon2021]_ with the following job. +# +# .. warning:: +# The code required to plot the results of the example below is not provided, but a similar +# example can be found in this +# `notebook `__, +# with all its dependencies included in the `source_scripts` +# `folder `__. # # .. note:: # In order for the remainder of this demo to work, you will need to have done 3 things: @@ -1040,4 +1011,4 @@ def __apply_blocking(self, cost, params_curr, params_next): # # About the author # ---------------- -# .. include:: ../_static/authors/yiheng_duan.txt +# diff --git a/demonstrations_v2/qnspsa/metadata.json b/demonstrations_v2/qnspsa/metadata.json index 2679e5668d..c854871045 100644 --- a/demonstrations_v2/qnspsa/metadata.json +++ b/demonstrations_v2/qnspsa/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-07-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-09T00:00:00+00:00", "categories": [ "Optimization" ], diff --git a/demonstrations_v2/qonn/demo.py b/demonstrations_v2/qonn/demo.py index 7fa2bb0887..90eaad7f6a 100644 --- a/demonstrations_v2/qonn/demo.py +++ b/demonstrations_v2/qonn/demo.py @@ -474,4 +474,4 @@ def cost_wrapper(var, grad=[]): # # About the author # ---------------- -# .. include:: ../_static/authors/theodor_isacsson.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/qrack/demo.py b/demonstrations_v2/qrack/demo.py index c5e7968332..e2dbbe60e6 100644 --- a/demonstrations_v2/qrack/demo.py +++ b/demonstrations_v2/qrack/demo.py @@ -276,6 +276,11 @@ def circuit(): # # Comparing performance # --------------------- +# .. note:: +# These results were obtained using Ubuntu 22.04 LTS, Linux kernel version 5.19.0-35-generic, +# using one Intel(R) Core(TM) i9-10980HK CPU @ 2.40GHz, one NVIDIA GeForce RTX 3080 Laptop GPU, +# and 32 GB of SK hynix 3200 MT/s DDR4 in 2x16 GB row configuration. +# Results may differ if run on other system configurations. # We've already seen that the Qrack device back end can do some tasks that most other simulators, or # basically any other simulator, simply can't do, like 60-qubit-wide special cases of the QFT or GHZ state # preparation with a Clifford or universal (QBDD) simulation algorithm, for example. However, @@ -438,7 +443,7 @@ def circuit(): # choice of devices or device initialization, to handle a mixture of wide and narrow qubit registers in your subroutines. ############################################################################## -# +# # References # ---------- # @@ -457,4 +462,4 @@ def circuit(): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/dan_strano.txt +# diff --git a/demonstrations_v2/qrack/metadata.json b/demonstrations_v2/qrack/metadata.json index 9afc67ab59..d53d40ed5c 100644 --- a/demonstrations_v2/qrack/metadata.json +++ b/demonstrations_v2/qrack/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-07-10T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations_v2/qsim_beyond_classical/demo.py b/demonstrations_v2/qsim_beyond_classical/demo.py index 9f647638cf..b564beff2e 100644 --- a/demonstrations_v2/qsim_beyond_classical/demo.py +++ b/demonstrations_v2/qsim_beyond_classical/demo.py @@ -583,4 +583,4 @@ def fidelity_xeb(samples, probs): # # About the author # ---------------- -# .. include:: ../_static/authors/theodor_isacsson.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/quantum_neural_net/demo.py b/demonstrations_v2/quantum_neural_net/demo.py index f8a65360b0..020890e5a8 100644 --- a/demonstrations_v2/quantum_neural_net/demo.py +++ b/demonstrations_v2/quantum_neural_net/demo.py @@ -741,4 +741,4 @@ def cost(var, features, labels): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/maria_schuld.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/quantum_volume/demo.py b/demonstrations_v2/quantum_volume/demo.py index 7dc9e43f48..08d3783992 100644 --- a/demonstrations_v2/quantum_volume/demo.py +++ b/demonstrations_v2/quantum_volume/demo.py @@ -343,19 +343,17 @@ def permute_qubits(num_qubits): ############################################################################## # -# Next, we need to apply SU(4) gates to pairs of qubits. PennyLane doesn't have -# built-in functionality to generate these random matrices, however its cousin -# `Strawberry Fields `_ does! We will use the -# ``random_interferometer`` method, which can generate unitary matrices uniformly +# Next, we need to apply SU(4) gates to pairs of qubits. +# We can use scipy to generate unitary matrices uniformly # at random. This function actually generates elements of U(4), but they are # essentially equivalent up to a global phase. -from strawberryfields.utils import random_interferometer +from scipy.stats import unitary_group def apply_random_su4_layer(num_qubits): for qubit_idx in range(0, num_qubits, 2): if qubit_idx < num_qubits - 1: - rand_haar_su4 = random_interferometer(N=4) + rand_haar_su4 = unitary_group.rvs(4) qml.QubitUnitary(rand_haar_su4, wires=[qubit_idx, qubit_idx + 1]) @@ -386,9 +384,10 @@ def qv_circuit_layer(num_qubits): m = 3 # number of qubits with qml.tape.QuantumTape() as tape: - qml.layer(qv_circuit_layer, m, num_qubits=m) + for _ in range(m): + qv_circuit_layer(m) -expanded_tape = tape.expand(stop_at=lambda op: isinstance(op, qml.QubitUnitary)) +(expanded_tape, ), _ = qml.transforms.decompose(tape, gate_set={qml.QubitUnitary, qml.SWAP}) print(qml.drawer.tape_text(expanded_tape, wire_order=dev_ideal.wires, show_all_wires=True, show_matrices=True)) @@ -487,12 +486,11 @@ def heavy_output_set(m, probs): # # Adds a measurement of the first m qubits to the previous circuit -with tape: - qml.probs(wires=range(m)) +tape = tape.copy(measurements=[qml.probs(wires=range(m))]) # Run the circuit, compute heavy outputs, and print results -output_probs = qml.execute([tape], dev_ideal, None) # returns a list of result ! -output_probs = output_probs[0].reshape(2 ** m, ) +[output_probs] = qml.execute([tape], dev_ideal) # returns a list of result ! +output_probs = output_probs.reshape(2 ** m, ) heavy_outputs, prob_heavy_output = heavy_output_set(m, output_probs) print("State\tProbability") @@ -583,10 +581,6 @@ def heavy_output_set(m, probs): # Lima noise model. Again, we won't be running on Lima directly --- # we'll set up a local device to simulate its behaviour. # - -dev_noisy = qml.device("qiskit.remote", wires=5, shots=1000, backend=FakeLimaV2()) - -############################################################################## # # As a final point, since we are allowed to do as much optimization as we like, # let's put the compiler to work. The compiler will perform a number of @@ -594,14 +588,15 @@ def heavy_output_set(m, probs): # qubit placement and routing techniques [#sabre]_ in order to fit the circuits # on the hardware graph in the best way possible. -dev_noisy.set_transpile_args( - **{ + +transpile_args = { "optimization_level": 3, "coupling_map": FakeLimaV2().coupling_map, "layout_method": "sabre", "routing_method": "sabre", } -) + +dev_noisy = qml.device("qiskit.remote", wires=5, shots=1000, backend=FakeLimaV2(), **transpile_args) ############################################################################## @@ -625,28 +620,28 @@ def heavy_output_set(m, probs): for trial in range(num_trials): # Simulate the circuit analytically - with qml.tape.QuantumTape() as tape: - qml.layer(qv_circuit_layer, m, num_qubits=m) + with qml.tape.QuantumTape() as tape_probs: + for _ in range(m): + qv_circuit_layer(m) qml.probs(wires=range(m)) + + # when using qml.execute, shots must be on the tape + tape_counts = tape_probs.copy(measurements=[qml.counts()], shots=1000) - output_probs = qml.execute([tape], dev_ideal, None) + output_probs = qml.execute([tape_probs], dev_ideal) output_probs = output_probs[0].reshape(2 ** m, ) heavy_outputs, prob_heavy_output = heavy_output_set(m, output_probs) # Execute circuit on the noisy device - qml.execute([tape], dev_noisy, None) - - # Get the output bit strings; flip ordering of qubits to match PennyLane - counts = dev_noisy._current_job.result().get_counts() - reordered_counts = {x[::-1]: counts[x] for x in counts.keys()} + [counts] = qml.execute([tape_counts], dev_noisy) device_heavy_outputs = np.sum( [ - reordered_counts[x] if x[:m] in heavy_outputs else 0 - for x in reordered_counts.keys() + counts[x] if x[:m] in heavy_outputs else 0 + for x in counts.keys() ] ) - fraction_device_heavy_output = device_heavy_outputs / dev_noisy.shots + fraction_device_heavy_output = device_heavy_outputs / dev_noisy.shots.total_shots probs_ideal[m - min_m, trial] = prob_heavy_output probs_noisy[m - min_m, trial] = fraction_device_heavy_output diff --git a/demonstrations_v2/quantum_volume/metadata.json b/demonstrations_v2/quantum_volume/metadata.json index 9e45b3ab88..4a626d10ed 100644 --- a/demonstrations_v2/quantum_volume/metadata.json +++ b/demonstrations_v2/quantum_volume/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-12-15T00:00:00+00:00", - "dateOfLastModification": "2024-10-11T00:00:00+00:00", + "dateOfLastModification": "2025-01-08T00:00:00+00:00", "categories": [ "Quantum Hardware", "Quantum Computing" diff --git a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py index a5ec3d500b..d70b30d015 100644 --- a/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py +++ b/demonstrations_v2/tutorial_How_to_optimize_QML_model_using_JAX_catalyst_and_Optax/demo.py @@ -218,5 +218,3 @@ def optimization_noqjit(params): # About the author # ---------------- # -# .. include:: ../_static/authors/josh_izaac.txt -# diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py index 0e62c88c0e..69ac442a3b 100644 --- a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/demo.py @@ -212,4 +212,4 @@ def circuit(theta, depth, num_qubits): ###################################################################### # About the author # ---------------- -# .. include:: ../_static/authors/pietropaolo_frisoni.txt +# diff --git a/demonstrations_v2/tutorial_QGAN/demo.py b/demonstrations_v2/tutorial_QGAN/demo.py index c26e53e245..bc51ac55e2 100644 --- a/demonstrations_v2/tutorial_QGAN/demo.py +++ b/demonstrations_v2/tutorial_QGAN/demo.py @@ -289,4 +289,4 @@ def bloch_vector_generator(angles): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/nathan_killoran.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_QUBO/demo.py b/demonstrations_v2/tutorial_QUBO/demo.py index 3b4a0cbe30..6b6ed10da8 100644 --- a/demonstrations_v2/tutorial_QUBO/demo.py +++ b/demonstrations_v2/tutorial_QUBO/demo.py @@ -741,4 +741,4 @@ def Knapsack(values, weights, maximum_weight): ###################################################################### # About the author # ---------------- -# .. include:: ../_static/authors/alejandro_montanez.txt +# diff --git a/demonstrations_v2/tutorial_adaptive_circuits/demo.py b/demonstrations_v2/tutorial_adaptive_circuits/demo.py index bd55318b10..79d3041fb6 100644 --- a/demonstrations_v2/tutorial_adaptive_circuits/demo.py +++ b/demonstrations_v2/tutorial_adaptive_circuits/demo.py @@ -61,10 +61,15 @@ """ import pennylane as qml -from pennylane import qchem -from pennylane import numpy as np +import jax +import numpy as np import time +from pennylane import qchem +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) + symbols = ["Li", "H"] geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.969280527]]) molecule = qchem.Molecule(symbols, geometry) @@ -172,8 +177,17 @@ def circuit(): # # We create a circuit that applies a selected group of gates to the reference Hartree-Fock state. + +# Re-define H using Jax Arrays +molecule = qchem.Molecule(symbols, jnp.array(geometry)) +H, qubits = qchem.molecular_hamiltonian( + molecule, + active_electrons=2, + active_orbitals=5 +) + def circuit_1(params, excitations): - qml.BasisState(hf_state, wires=range(qubits)) + qml.BasisState(jnp.array(hf_state), wires=range(qubits)) for i, excitation in enumerate(excitations): if len(excitation) == 4: @@ -189,10 +203,10 @@ def circuit_1(params, excitations): # with respect to the Hartree-Fock state. -dev = qml.device("default.qubit", wires=qubits) -cost_fn = qml.QNode(circuit_1, dev, interface="autograd") +dev = qml.device("lightning.qubit", wires=qubits) +cost_fn = qml.QNode(circuit_1, dev, interface="jax") -circuit_gradient = qml.grad(cost_fn, argnum=0) +circuit_gradient = jax.grad(cost_fn, argnums=0) params = [0.0] * len(doubles) grads = circuit_gradient(params, excitations=doubles) @@ -214,12 +228,17 @@ def circuit_1(params, excitations): # the updated parameters for the selected gates. We also need to define an optimizer. Note that the # optimization is not very costly as we only have six gates in our circuit. -opt = qml.GradientDescentOptimizer(stepsize=0.5) +import optax + +params_doubles = jnp.zeros(len(doubles_select)) -params_doubles = np.zeros(len(doubles_select), requires_grad=True) +opt = optax.sgd(learning_rate=0.5) # sgd stands for StochasticGradientDescent +opt_state = opt.init(params_doubles) -for n in range(20): - params_doubles = opt.step(cost_fn, params_doubles, excitations=doubles_select) +for n in range(10): + gradient = jax.grad(cost_fn, argnums=0)(params_doubles, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params_doubles = optax.apply_updates(params_doubles, updates) ############################################################################## # Now, we keep the selected gates in the circuit and compute the gradients with respect to all of @@ -248,8 +267,8 @@ def circuit_2(params, excitations, gates_select, params_select): ############################################################################## # We now compute the gradients for the single excitation gates. -cost_fn = qml.QNode(circuit_2, dev, interface="autograd") -circuit_gradient = qml.grad(cost_fn, argnum=0) +cost_fn = qml.QNode(circuit_2, dev, interface="jax") +circuit_gradient = jax.grad(cost_fn, argnums=0) params = [0.0] * len(singles) grads = circuit_gradient( @@ -280,15 +299,19 @@ def circuit_2(params, excitations, gates_select, params_select): # We perform a final circuit optimization to get the ground-state energy. The resulting energy # should match the exact energy of the ground electronic state of LiH which is -7.8825378193 Ha. -cost_fn = qml.QNode(circuit_1, dev, interface="autograd") +cost_fn = qml.QNode(circuit_1, dev, interface="jax") -params = np.zeros(len(doubles_select + singles_select), requires_grad=True) +params = jnp.zeros(len(doubles_select + singles_select)) gates_select = doubles_select + singles_select +opt_state = opt.init(params) -for n in range(20): +for n in range(10): t1 = time.time() - params, energy = opt.step_and_cost(cost_fn, params, excitations=gates_select) + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) t2 = time.time() print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) @@ -327,9 +350,9 @@ def circuit_2(params, excitations, gates_select, params_select): excitations = doubles_select + singles_select -params = np.zeros(len(excitations), requires_grad=True) +params = jnp.zeros(len(excitations)) -@qml.qnode(dev, diff_method="parameter-shift", interface="autograd") +@qml.qnode(dev, diff_method="parameter-shift", interface="jax") def circuit(params): qml.BasisState(hf_state, wires=range(qubits)) @@ -342,9 +365,12 @@ def circuit(params): return qml.expval(qml.SparseHamiltonian(H_sparse, wires=range(qubits))) -for n in range(20): +for n in range(10): t1 = time.time() - params, energy = opt.step_and_cost(circuit, params) + gradient = jax.grad(cost_fn, argnums=0)(params, excitations=doubles_select) + updates, opt_state = opt.update(gradient, opt_state) + params = optax.apply_updates(params, updates) + energy = cost_fn(params, doubles_select) t2 = time.time() print("n = {:}, E = {:.8f} H, t = {:.2f} s".format(n, energy, t2 - t1)) @@ -397,4 +423,4 @@ def circuit(params): # # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt +# diff --git a/demonstrations_v2/tutorial_adaptive_circuits/metadata.json b/demonstrations_v2/tutorial_adaptive_circuits/metadata.json index 676fc101da..c65158915f 100644 --- a/demonstrations_v2/tutorial_adaptive_circuits/metadata.json +++ b/demonstrations_v2/tutorial_adaptive_circuits/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-09-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_adjoint_diff/demo.py b/demonstrations_v2/tutorial_adjoint_diff/demo.py index 690479b425..bcb7fca3f4 100644 --- a/demonstrations_v2/tutorial_adjoint_diff/demo.py +++ b/demonstrations_v2/tutorial_adjoint_diff/demo.py @@ -455,4 +455,4 @@ def circuit_adjoint(a): # # About the author # ---------------- -# .. include:: ../_static/authors/christina_lee.txt +# diff --git a/demonstrations_v2/tutorial_apply_qsvt/demo.py b/demonstrations_v2/tutorial_apply_qsvt/demo.py index b441ba0c8f..2bac55382a 100644 --- a/demonstrations_v2/tutorial_apply_qsvt/demo.py +++ b/demonstrations_v2/tutorial_apply_qsvt/demo.py @@ -32,7 +32,7 @@ ------------- For a refresher on the basics of QSVT check out our :doc:`Intro to QSVT ` tutorial. Let's recall how to apply QSVT in a circuit. This requires two pieces of information -as input: the matrix to be transformed and a set of phase angles which determine the polynomial +as input: the block encoding of the matrix to be transformed and a set of projectors which determine the polynomial transformation. For now, we use placeholder values for the phase angles; we'll later describe how to obtain them. The code below shows how to construct a basic QSVT circuit on two qubits: """ @@ -40,30 +40,33 @@ import pennylane as qml from pennylane import numpy as np -dev = qml.device("default.qubit", wires=[0, 1]) +dev = qml.device("lightning.qubit", wires=[0, 1]) A = np.array([[0.1, 0.2], [0.3, 0.4]]) phase_angles = np.array([0.0, 1.0, 2.0, 3.0]) +block_encoding = qml.BlockEncode(A, wires=[0, 1]) +projectors = [qml.PCPhase(angle, dim=2, wires=[0, 1]) for angle in phase_angles] + @qml.qnode(dev) -def my_circuit(phase_angles): - qml.qsvt(A, phase_angles, wires=[0, 1]) +def my_circuit(): + qml.QSVT(block_encoding, projectors) return qml.state() ############################################################################### # We can now execute the circuit and visualize it. -my_circuit(phase_angles) -print(qml.draw(my_circuit)(phase_angles)) +my_circuit() +print(qml.draw(my_circuit, level = "top")()) ############################################################################### # We can inspect details by drawing the expanded circuit. The :class:`~.pennylane.QSVT` # operation is composed of repeated applications of the :class:`~.pennylane.BlockEncode` and # :class:`~.pennylane.PCPhase` (:math:`\Pi_{\phi}`) operations. -print(my_circuit.tape.expand().draw()) +print(qml.draw(my_circuit)()) ############################################################################### # Now let's look at an application of QSVT --- solving a linear system of equations. @@ -120,6 +123,7 @@ def my_circuit(phase_angles): kappa = 4 s = 0.10145775 phi_pyqsp = [-2.287, 2.776, -1.163, 0.408, -0.16, -0.387, 0.385, -0.726, 0.456, 0.062, -0.468, 0.393, 0.028, -0.567, 0.76, -0.432, -0.011, 0.323, -0.573, 0.82, -1.096, 1.407, -1.735, 2.046, -2.321, 2.569, -2.819, -0.011, 2.71, -2.382, 2.574, 0.028, -2.749, 2.673, 0.062, -2.685, 2.416, 0.385, -0.387, -0.16, 0.408, -1.163, -0.365, 2.426] +phi_qsvt = qml.transform_angles(phi_pyqsp, "QSP", "QSVT") # convert pyqsp angles to be compatible with QSVT ############################################################################### # .. note:: @@ -144,9 +148,11 @@ def my_circuit(phase_angles): qsvt_y_vals = [] for x in x_vals: - poly_x = qml.matrix(qml.qsvt, wire_order=[0])( - x, phi_pyqsp, wires=[0], convention="Wx" # specify angles using convention=`Wx` - ) + + block_encoding = qml.BlockEncode(x, wires=[0]) + projectors = [qml.PCPhase(angle, dim=1, wires=[0]) for angle in phi_qsvt] + + poly_x = qml.matrix(qml.QSVT, wire_order=[0])(block_encoding, projectors) qsvt_y_vals.append(np.real(poly_x[0, 0])) ############################################################################### @@ -184,15 +190,20 @@ def my_circuit(phase_angles): # We apply each QSVT operation, even or odd, conditioned on the ancilla. Finally, the ancilla # qubit is reset. - def sum_even_odd_circ(x, phi, ancilla_wire, wires): - phi1, phi2 = phi[: len(phi) // 2], phi[len(phi) // 2:] + phi1, phi2 = phi[: len(phi) // 2], phi[len(phi) // 2 :] + block_encode = qml.BlockEncode(x, wires=wires) qml.Hadamard(wires=ancilla_wire) # equal superposition # apply even and odd polynomial approx - qml.ctrl(qml.qsvt, control=(ancilla_wire,), control_values=(0,))(x, phi1, wires=wires) - qml.ctrl(qml.qsvt, control=(ancilla_wire,), control_values=(1,))(x, phi2, wires=wires) + + dim = x.shape[0] if x.ndim > 0 else 1 + projectors_even = [qml.PCPhase(angle, dim= dim, wires=wires) for angle in phi1] + qml.ctrl(qml.QSVT, control=(ancilla_wire,), control_values=(0,))(block_encode, projectors_even) + + projectors_odd = [qml.PCPhase(angle, dim= dim, wires=wires) for angle in phi2] + qml.ctrl(qml.QSVT, control=(ancilla_wire,), control_values=(0,))(block_encode, projectors_odd) qml.Hadamard(wires=ancilla_wire) # un-prepare superposition @@ -404,6 +415,4 @@ def linear_system_solver_circuit(phi): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/jay_soni.txt # -# .. include:: ../_static/authors/jarrett_smalley.txt diff --git a/demonstrations_v2/tutorial_apply_qsvt/metadata.json b/demonstrations_v2/tutorial_apply_qsvt/metadata.json index 41298d7dfe..ac0d7e56c5 100644 --- a/demonstrations_v2/tutorial_apply_qsvt/metadata.json +++ b/demonstrations_v2/tutorial_apply_qsvt/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2023-08-22T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-05T00:00:00+00:00", "categories": [ "Quantum Computing", "Algorithms", diff --git a/demonstrations_v2/tutorial_backprop/demo.py b/demonstrations_v2/tutorial_backprop/demo.py index 469cb1d5ca..c55bbf280b 100644 --- a/demonstrations_v2/tutorial_backprop/demo.py +++ b/demonstrations_v2/tutorial_backprop/demo.py @@ -453,4 +453,4 @@ def circuit(params): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt +# diff --git a/demonstrations_v2/tutorial_barren_gadgets/demo.py b/demonstrations_v2/tutorial_barren_gadgets/demo.py index 4d2787caa4..0eacb1fff7 100644 --- a/demonstrations_v2/tutorial_barren_gadgets/demo.py +++ b/demonstrations_v2/tutorial_barren_gadgets/demo.py @@ -387,5 +387,4 @@ def monitoring_cost(weights): # # About the author # ---------------- -# .. include:: ../_static/authors/simon_cichy.txt # \ No newline at end of file diff --git a/demonstrations_v2/tutorial_barren_plateaus/demo.py b/demonstrations_v2/tutorial_barren_plateaus/demo.py index 1d501e6714..eb2bd24f65 100644 --- a/demonstrations_v2/tutorial_barren_plateaus/demo.py +++ b/demonstrations_v2/tutorial_barren_plateaus/demo.py @@ -211,4 +211,4 @@ def rand_circuit(params, random_gate_sequence=None, num_qubits=None): # # About the author # ---------------- -# .. include:: ../_static/authors/shahnawaz_ahmed.txt +# diff --git a/demonstrations_v2/tutorial_block_encoding/demo.py b/demonstrations_v2/tutorial_block_encoding/demo.py index 95bae50a0a..22494eaf9e 100644 --- a/demonstrations_v2/tutorial_block_encoding/demo.py +++ b/demonstrations_v2/tutorial_block_encoding/demo.py @@ -391,8 +391,4 @@ def complete_circuit(thetas): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/jay_soni.txt # -# .. include:: ../_static/authors/diego_guala.txt -# -# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations_v2/tutorial_bluequbit/demo.py b/demonstrations_v2/tutorial_bluequbit/demo.py index 574356d8ac..95f74963e4 100644 --- a/demonstrations_v2/tutorial_bluequbit/demo.py +++ b/demonstrations_v2/tutorial_bluequbit/demo.py @@ -41,10 +41,6 @@ import matplotlib.pyplot as plt import numpy as np -# This filter will suppress deprecation warnings for viewability -import warnings -warnings.filterwarnings("ignore", "QubitDevice", qml.PennyLaneDeprecationWarning) - def bell_pair(): qml.Hadamard(0) diff --git a/demonstrations_v2/tutorial_bluequbit/metadata.json b/demonstrations_v2/tutorial_bluequbit/metadata.json index 5103d7af75..d30aba3426 100644 --- a/demonstrations_v2/tutorial_bluequbit/metadata.json +++ b/demonstrations_v2/tutorial_bluequbit/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-09-24T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-12T00:00:00+00:00", "categories": [ "Devices and Performance", "Quantum Computing" diff --git a/demonstrations_v2/tutorial_chemical_reactions/demo.py b/demonstrations_v2/tutorial_chemical_reactions/demo.py index 541a01d159..a8d7f4a899 100644 --- a/demonstrations_v2/tutorial_chemical_reactions/demo.py +++ b/demonstrations_v2/tutorial_chemical_reactions/demo.py @@ -106,7 +106,7 @@ # the equilibrium bond length, and the point where the bond is broken, which occurs when the atoms # are far away from each other. -from pennylane import numpy as np +from jax import numpy as jnp # atomic symbols defining the molecule symbols = ['H', 'H'] @@ -115,7 +115,7 @@ energies = [] # set up a loop to change bond length -r_range = np.arange(0.5, 5.0, 0.25) +r_range = jnp.arange(0.5, 5.0, 0.25) # keeps track of points in the potential energy surface pes_point = 0 @@ -124,9 +124,11 @@ # We build the Hamiltonian using the :func:`~.pennylane.qchem.molecular_hamiltonian` # function, and use standard Pennylane techniques to optimize the circuit. +import optax + for r in r_range: # Change only the z coordinate of one atom - coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r]]) + coordinates = jnp.array([[0.0, 0.0, 0.0], [0.0, 0.0, r]]) # Construct the Molecule object molecule = qchem.Molecule(symbols, coordinates) @@ -135,13 +137,13 @@ H, qubits = qchem.molecular_hamiltonian(molecule, method='openfermion') # define the device, optimizer and circuit - dev = qml.device("default.qubit", wires=qubits) - opt = qml.GradientDescentOptimizer(stepsize=0.4) + dev = qml.device("lightning.qubit", wires=qubits) + opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent - @qml.qnode(dev, interface='autograd') + @qml.qnode(dev, interface='jax') def circuit(parameters): # Prepare the HF state: |1100> - qml.BasisState(hf, wires=range(qubits)) + qml.BasisState.compute_decomposition(hf, wires=range(qubits)) qml.DoubleExcitation(parameters[0], wires=[0, 1, 2, 3]) qml.SingleExcitation(parameters[1], wires=[0, 2]) qml.SingleExcitation(parameters[2], wires=[1, 3]) @@ -149,19 +151,33 @@ def circuit(parameters): return qml.expval(H) # we are interested in minimizing this expectation value # initialize the gate parameters - params = np.zeros(3, requires_grad=True) + init_params = jnp.zeros(3) # initialize with converged parameters from previous point if pes_point > 0: - params = params_old + init_params = params_old prev_energy = 0.0 - for n in range(50): - # perform optimization step - params, energy = opt.step_and_cost(circuit, params) + @qml.qjit + def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(circuit)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + + loss_history = [] + + opt_state = opt.init(init_params) + params = init_params - if np.abs(energy - prev_energy) < 1e-6: + for i in range(50): + params, opt_state = update_step(i, params, opt_state) + energy = circuit(params) + + if jnp.abs(energy - prev_energy) < 1e-6: break + prev_energy = energy # store the converged parameters @@ -280,35 +296,52 @@ def circuit(parameters): # loop to change reaction coordinate -r_range = np.arange(1.0, 3.0, 0.1) +r_range = jnp.arange(1.0, 3.0, 0.1) for r in r_range: - coordinates = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, r], [0.0, 0.0, 4.0]]) + coordinates = jnp.array([[0.0, 0.0, 0.0], [0.0, 0.0, r], [0.0, 0.0, 4.0]]) # We now specify the multiplicity molecule = qchem.Molecule(symbols, coordinates, mult=multiplicity) H, qubits = qchem.molecular_hamiltonian(molecule, method='openfermion') - dev = qml.device("default.qubit", wires=qubits) - opt = qml.GradientDescentOptimizer(stepsize=1.5) + dev = qml.device("lightning.qubit", wires=qubits) + opt = optax.sgd(learning_rate=1.5) # sgd stands for StochasticGradientDescent - @qml.qnode(dev, interface='autograd') + @qml.qjit + @qml.qnode(dev, interface='jax') def circuit(parameters): AllSinglesDoubles(parameters, range(qubits), hf, singles, doubles) return qml.expval(H) # we are interested in minimizing this expectation value - params = np.zeros(len(singles) + len(doubles), requires_grad=True) + init_params = jnp.zeros(len(singles) + len(doubles)) + # initialize with converged parameters from previous point if pes_point > 0: - params = params_old + init_params = params_old prev_energy = 0.0 + @qml.qjit + def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(circuit)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + + loss_history = [] + + opt_state = opt.init(init_params) + params = init_params - for n in range(60): - params, energy = opt.step_and_cost(circuit, params) - if np.abs(energy - prev_energy) < 1e-6: + for i in range(60): + params, opt_state = update_step(i, params, opt_state) + energy = circuit(params) + + if jnp.abs(energy - prev_energy) < 1e-6: break + prev_energy = energy # store the converged parameters @@ -393,7 +426,7 @@ def circuit(parameters): # Temperature T = 300 -ratio = np.exp(activation_energy / (2 * k_B * T)) +ratio = jnp.exp(activation_energy / (2 * k_B * T)) print(f"Ratio of reaction rates is {ratio:.0f}") @@ -432,8 +465,6 @@ def circuit(parameters): # -simulations-on-quantum-computers-50a4b4ee5c64>`__ # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/varun_rishi.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file diff --git a/demonstrations_v2/tutorial_chemical_reactions/metadata.json b/demonstrations_v2/tutorial_chemical_reactions/metadata.json index c68aea4c8d..7c69271a89 100644 --- a/demonstrations_v2/tutorial_chemical_reactions/metadata.json +++ b/demonstrations_v2/tutorial_chemical_reactions/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2021-07-23T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_circuit_compilation/demo.py b/demonstrations_v2/tutorial_circuit_compilation/demo.py index ab98ab46f1..abad3f4d54 100644 --- a/demonstrations_v2/tutorial_circuit_compilation/demo.py +++ b/demonstrations_v2/tutorial_circuit_compilation/demo.py @@ -241,4 +241,4 @@ def q_fun(angles): # # About the author # ---------------- -# .. include:: ../_static/authors/borja_requena.txt +# diff --git a/demonstrations_v2/tutorial_classical_kernels/demo.py b/demonstrations_v2/tutorial_classical_kernels/demo.py index d256fe79a9..ad03b79bd5 100644 --- a/demonstrations_v2/tutorial_classical_kernels/demo.py +++ b/demonstrations_v2/tutorial_classical_kernels/demo.py @@ -769,4 +769,4 @@ def QK_partial(x): # # About the author # ---------------- -# .. include:: ../_static/authors/elies_gil-fuster.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_classical_shadows/demo.py b/demonstrations_v2/tutorial_classical_shadows/demo.py index 9a7c055bbd..9df50bf189 100644 --- a/demonstrations_v2/tutorial_classical_shadows/demo.py +++ b/demonstrations_v2/tutorial_classical_shadows/demo.py @@ -713,8 +713,6 @@ def circuit_base(params, **kwargs): # Ph.D. thesis, Caltech, eprint quantph/9705052. # # -# About the authors -# ################# -# .. include:: ../_static/authors/roeland_wiersema.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/brian_doolittle.txt diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py b/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py index 21f4b06529..dd65f9a332 100644 --- a/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/demo.py @@ -104,10 +104,11 @@ import pennylane as qml from pennylane import qchem -from pennylane import numpy as np +import numpy as np +from jax import numpy as jnp symbols = ["H", "H"] -coordinates = np.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) +coordinates = jnp.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]]) basis_set = "sto-3g" electrons = 2 @@ -139,7 +140,9 @@ def circuit_VQE(theta, wires): - qml.AllSinglesDoubles(weights=theta, wires=wires, hf_state=hf, singles=singles, doubles=doubles) + qml.AllSinglesDoubles( + weights=theta, wires=wires, hf_state=hf, singles=singles, doubles=doubles + ) ###################################################################### @@ -147,11 +150,17 @@ def circuit_VQE(theta, wires): # define a circuit for the cost function. # +import optax +import jax + +jax.config.update("jax_enable_x64", True) # use double-precision numbers + dev = qml.device("lightning.qubit", wires=qubits) -@qml.qnode(dev, interface="autograd") -def cost_fn(theta): +@qml.qjit +@qml.qnode(dev, interface="jax") +def cost(theta): circuit_VQE(theta, range(qubits)) return qml.expval(H) @@ -163,20 +172,35 @@ def cost_fn(theta): stepsize = 0.4 max_iterations = 30 -opt = qml.GradientDescentOptimizer(stepsize=stepsize) -theta = np.zeros(num_theta, requires_grad=True) +opt = optax.sgd(learning_rate=stepsize) # sgd stands for StochasticGradientDescent +init_params = jnp.zeros(num_theta) ###################################################################### # Finally, we run the algorithm. # -for n in range(max_iterations): - theta, prev_energy = opt.step_and_cost(cost_fn, theta) - samples = cost_fn(theta) -energy_VQE = cost_fn(theta) -theta_opt = theta +@qml.qjit +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(cost)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + + +loss_history = [] + +opt_state = opt.init(init_params) +params = init_params + +for i in range(30): + params, opt_state = update_step(i, params, opt_state) + energy = cost(params) + +energy_VQE = cost(params) +theta_opt = params print("VQE energy: %.4f" % (energy_VQE)) print("Optimal parameters:", theta_opt) @@ -411,20 +435,22 @@ def cost_fn(theta): # :math:`U_i \vert 0^n \rangle = \vert \phi_i \rangle.` In this case, this # is just a mapping of a classical basis state into the circuit consisting # of :math:`X` gates and can be easily implemented using PennyLane’s -# function ``qml.BasisState(i, n))``. +# function ``qml.BasisState(i, n)``. # wires = range(qubits + 1) dev = qml.device("lightning.qubit", wires=wires) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def hadamard_test(Uq, Ucl, component="real"): if component == "imag": qml.RX(math.pi / 2, wires=wires[1:]) qml.Hadamard(wires=[0]) - qml.ControlledQubitUnitary(Uq.conjugate().T @ Ucl, control_wires=[0], wires=wires[1:]) + qml.ControlledQubitUnitary( + Uq.conjugate().T @ Ucl, control_wires=[0], wires=wires[1:] + ) qml.Hadamard(wires=[0]) return qml.probs(wires=[0]) @@ -445,7 +471,7 @@ def circuit_product_state(state): H12 = 0 relevant_basis_states = np.array( - [[1, 1, 0, 0], [0, 1, 1, 0], [1, 0, 0, 1], [0, 0, 1, 1]], requires_grad=True + [[1, 1, 0, 0], [0, 1, 1, 0], [1, 0, 0, 1], [0, 0, 1, 1]] ) for j, basis_state in enumerate(relevant_basis_states): Ucl = qml.matrix(circuit_product_state, wire_order=wire_order)(basis_state) @@ -559,6 +585,4 @@ def circuit_product_state(state): # # About the author # ---------------- -# .. include:: ../_static/authors/joana_fraxanet.txt # -# .. include:: ../_static/authors/isidor_schoch.txt diff --git a/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json b/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json index 7e3e268228..797d86cf63 100644 --- a/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json +++ b/demonstrations_v2/tutorial_classically_boosted_vqe/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2022-10-31T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py b/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py index 60eef7a1e4..acc2402425 100644 --- a/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py +++ b/demonstrations_v2/tutorial_clifford_circuit_simulations/demo.py @@ -381,7 +381,8 @@ def state_at_each_step(tape): # Let's examine the remaining operations to confirm this. # -circuit_ops = circuit.tape.operations +tape = qml.workflow.construct_tape(circuit)() +circuit_ops = tape.operations print("Circ. Ops: ", circuit_ops) for step in range(1, len(circuit_ops)): diff --git a/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json b/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json index 2dc1876ec4..f46c5c790d 100644 --- a/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json +++ b/demonstrations_v2/tutorial_clifford_circuit_simulations/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-04-12T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-14T00:00:00+00:00", "categories": [ "Devices and Performance" ], diff --git a/demonstrations_v2/tutorial_coherent_vqls/demo.py b/demonstrations_v2/tutorial_coherent_vqls/demo.py index 2f4d20aa4b..6a408e152b 100644 --- a/demonstrations_v2/tutorial_coherent_vqls/demo.py +++ b/demonstrations_v2/tutorial_coherent_vqls/demo.py @@ -558,4 +558,4 @@ def prepare_and_sample(weights): # # About the author # ---------------- -# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_contextuality/demo.py b/demonstrations_v2/tutorial_contextuality/demo.py index fba179f98b..fac3c192b6 100644 --- a/demonstrations_v2/tutorial_contextuality/demo.py +++ b/demonstrations_v2/tutorial_contextuality/demo.py @@ -804,4 +804,4 @@ def optimise_model(model, nstep, lr, weights): # # About the author # ---------------- -# .. include:: ../_static/authors/joseph_bowles.txt +# diff --git a/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py b/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py index f94e9b2bff..5241a4b093 100644 --- a/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py +++ b/demonstrations_v2/tutorial_data_reuploading_classifier/demo.py @@ -470,4 +470,4 @@ def iterate_minibatches(inputs, targets, batch_size): # # About the author # ---------------- -# .. include:: ../_static/authors/shahnawaz_ahmed.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_diffable-mitigation/demo.py b/demonstrations_v2/tutorial_diffable-mitigation/demo.py index fa37ae970b..d9601b4573 100644 --- a/demonstrations_v2/tutorial_diffable-mitigation/demo.py +++ b/demonstrations_v2/tutorial_diffable-mitigation/demo.py @@ -287,5 +287,5 @@ def VQE_run(cost_fn, max_iter, stepsize=0.1): # # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/tutorial_diffable_shadows/demo.py b/demonstrations_v2/tutorial_diffable_shadows/demo.py index 80d071ac18..d5f7b2b0bb 100644 --- a/demonstrations_v2/tutorial_diffable_shadows/demo.py +++ b/demonstrations_v2/tutorial_diffable_shadows/demo.py @@ -505,4 +505,4 @@ def qnode(): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/tutorial_differentiable_HF/demo.py b/demonstrations_v2/tutorial_differentiable_HF/demo.py index d157c3dd35..e06dc3fa4d 100644 --- a/demonstrations_v2/tutorial_differentiable_HF/demo.py +++ b/demonstrations_v2/tutorial_differentiable_HF/demo.py @@ -101,20 +101,21 @@ For the hydrogen molecule we have """ -from autograd import grad import pennylane as qml -from pennylane import numpy as np +import numpy as np +import jax +import jax.numpy as jnp import matplotlib.pyplot as plt np.set_printoptions(precision=5) +jax.config.update("jax_enable_x64", True) symbols = ["H", "H"] # optimized geometry at the Hartree-Fock level -geometry = np.array([[-0.672943567415407, 0.0, 0.0], - [ 0.672943567415407, 0.0, 0.0]], requires_grad=True) +geometry = jnp.array([[-0.672943567415407, 0.0, 0.0], + [ 0.672943567415407, 0.0, 0.0]]) ############################################################################## -# The use of ``requires_grad=True`` specifies that the nuclear coordinates are differentiable -# parameters. We can now compute the Hartree-Fock energy and its gradient with respect to the +# We can now compute the Hartree-Fock energy and its gradient with respect to the # nuclear coordinates. To do that, we create a molecule object that stores all the molecular # parameters needed to perform a Hartree-Fock calculation. @@ -124,12 +125,12 @@ # The Hartree-Fock energy can now be computed with the # :func:`~.pennylane.qchem.hf_energy` function which is a function transform -qml.qchem.hf_energy(mol)(geometry) +qml.qchem.hf_energy(mol)() ############################################################################## # We now compute the gradient of the energy with respect to the nuclear coordinates -grad(qml.qchem.hf_energy(mol))(geometry) +jax.grad(qml.qchem.hf_energy(mol), argnums=0)(geometry, mol.coeff, mol.alpha) ############################################################################## # The obtained gradients are equal or very close to zero because the geometry we used here has been @@ -181,13 +182,13 @@ # are those of the hydrogen atoms by default and are therefore treated as differentiable parameters # by PennyLane. -qml.qchem.overlap_integral(S1, S2)([geometry[0], geometry[1]]) +qml.qchem.overlap_integral(S1, S2)() ############################################################################## # You can verify that the overlap integral between two identical atomic orbitals is equal to one. # We can now compute the gradient of the overlap integral with respect to the orbital centres -grad(qml.qchem.overlap_integral(S1, S2))([geometry[0], geometry[1]]) +jax.grad(qml.qchem.overlap_integral(S1, S2))(geometry, mol.coeff, mol.alpha) ############################################################################## # Can you explain why some of the computed gradients are zero? @@ -228,13 +229,13 @@ # plane. n = 30 # number of grid points along each axis - +qml.qchem.hf_energy(mol)() mol.mo_coefficients = mol.mo_coefficients.T mo = mol.molecular_orbital(0) x, y = np.meshgrid(np.linspace(-2, 2, n), np.linspace(-2, 2, n)) val = np.vectorize(mo)(x, y, 0) -val = np.array([val[i][j]._value for i in range(n) for j in range(n)]).reshape(n, n) +val = np.array([val[i][j] for i in range(n) for j in range(n)]).reshape(n, n) fig, ax = plt.subplots() co = ax.contour(x, y, val, 10, cmap='summer_r', zorder=0) @@ -252,7 +253,7 @@ # over molecular orbitals that can be used to construct the molecular Hamiltonian with the # :func:`~.pennylane.qchem.molecular_hamiltonian` function. -hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol, args=[mol.coordinates]) +hamiltonian, qubits = qml.qchem.molecular_hamiltonian(mol) print(hamiltonian) ############################################################################## @@ -265,11 +266,12 @@ # hydrogen atoms. dev = qml.device("default.qubit", wires=4) -def energy(mol): - @qml.qnode(dev, interface="autograd") +def energy(): + @qml.qnode(dev, interface="jax") def circuit(*args): qml.BasisState(np.array([1, 1, 0, 0]), wires=range(4)) - qml.DoubleExcitation(*args[0][0], wires=[0, 1, 2, 3]) + qml.DoubleExcitation(*args[0], wires=[0, 1, 2, 3]) + mol = qml.qchem.Molecule(symbols, geometry, alpha=args[3], coeff=args[2]) H = qml.qchem.molecular_hamiltonian(mol, args=args[1:])[0] return qml.expval(H) return circuit @@ -282,23 +284,24 @@ def circuit(*args): # coordinate gradients are simply the forces on the atomic nuclei. # initial value of the circuit parameter -circuit_param = [np.array([0.0], requires_grad=True)] +circuit_param = jnp.array([0.0]) -for n in range(36): +geometry = jnp.array([[0.0, 0.02, -0.672943567415407], + [0.1, 0.0, 0.672943567415407]]) - args = [circuit_param, geometry] +for n in range(36): mol = qml.qchem.Molecule(symbols, geometry) - + args = [circuit_param, geometry, mol.coeff, mol.alpha] # gradient for circuit parameters - g_param = grad(energy(mol), argnum = 0)(*args) + g_param = jax.grad(energy(), argnums = 0)(*args) circuit_param = circuit_param - 0.25 * g_param[0] # gradient for nuclear coordinates - forces = -grad(energy(mol), argnum = 1)(*args) - geometry = geometry + 0.5 * forces + forces = jax.grad(energy(), argnums = 1)(*args) + geometry = geometry - 0.5 * forces if n % 5 == 0: - print(f'n: {n}, E: {energy(mol)(*args):.8f}, Force-max: {abs(forces).max():.8f}') + print(f'n: {n}, E: {energy()(*args):.8f}, Force-max: {abs(forces).max():.8f}') ############################################################################## # After 35 steps of optimization, the forces on the atomic nuclei and the gradient of the @@ -313,44 +316,41 @@ def circuit(*args): # coordinates and the basis set parameters are all differentiable parameters that can be optimized # simultaneously. +symbols = ["H", "H"] # initial values of the nuclear coordinates -geometry = np.array([[0.0, 0.0, -0.672943567415407], - [0.0, 0.0, 0.672943567415407]], requires_grad=True) - -# initial values of the basis set exponents -alpha = np.array([[3.42525091, 0.62391373, 0.1688554], - [3.42525091, 0.62391373, 0.1688554]], requires_grad=True) +geometry = jnp.array([[0.0, 0.0, -0.672943567415407], + [0.0, 0.0, 0.672943567415407]]) # initial values of the basis set contraction coefficients -coeff = np.array([[0.1543289673, 0.5353281423, 0.4446345422], - [0.1543289673, 0.5353281423, 0.4446345422]], requires_grad=True) +coeff = jnp.array([[0.1543289673, 0.5353281423, 0.4446345422], + [0.1543289673, 0.5353281423, 0.4446345422]]) + +# initial values of the basis set exponents +alpha = jnp.array([[3.42525091, 0.62391373, 0.1688554], + [3.42525091, 0.62391373, 0.1688554]]) # initial value of the circuit parameter -circuit_param = [np.array([0.0], requires_grad=True)] +circuit_param = jnp.array([0.0]) -for n in range(36): +mol = qml.qchem.Molecule(symbols, geometry, coeff=coeff, alpha=alpha) +args = [circuit_param, geometry, coeff, alpha] - args = [circuit_param, geometry, alpha, coeff] +for n in range(36): + args = [circuit_param, geometry, coeff, alpha] mol = qml.qchem.Molecule(symbols, geometry, alpha=alpha, coeff=coeff) # gradient for circuit parameters - g_param = grad(energy(mol), argnum=0)(*args) + g_param = jax.grad(energy(), argnums=[0, 1, 2, 3])(*args)[0] circuit_param = circuit_param - 0.25 * g_param[0] # gradient for nuclear coordinates - forces = -grad(energy(mol), argnum=1)(*args) - geometry = geometry + 0.5 * forces - - # gradient for basis set exponents - g_alpha = grad(energy(mol), argnum=2)(*args) - alpha = alpha - 0.25 * g_alpha - - # gradient for basis set contraction coefficients - g_coeff = grad(energy(mol), argnum=3)(*args) - coeff = coeff - 0.25 * g_coeff + value, gradients = jax.value_and_grad(energy(), argnums=[1, 2, 3])(*args) + geometry = geometry - 0.5 * gradients[0] + alpha = alpha - 0.25 * gradients[2] + coeff = coeff - 0.25 * gradients[1] if n % 5 == 0: - print(f'n: {n}, E: {energy(mol)(*args):.8f}, Force-max: {abs(forces).max():.8f}') + print(f'n: {n}, E: {value:.8f}, Force-max: {abs(gradients[0]).max():.8f}') ############################################################################## # You can also print the gradients of the circuit and basis set parameters and confirm that they are @@ -390,4 +390,4 @@ def circuit(*args): # # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt \ No newline at end of file +# diff --git a/demonstrations_v2/tutorial_differentiable_HF/metadata.json b/demonstrations_v2/tutorial_differentiable_HF/metadata.json index 2389de40b2..8f76d217f4 100644 --- a/demonstrations_v2/tutorial_differentiable_HF/metadata.json +++ b/demonstrations_v2/tutorial_differentiable_HF/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-05-09T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_doubly_stochastic/demo.py b/demonstrations_v2/tutorial_doubly_stochastic/demo.py index 81e8a749d4..b75b365834 100644 --- a/demonstrations_v2/tutorial_doubly_stochastic/demo.py +++ b/demonstrations_v2/tutorial_doubly_stochastic/demo.py @@ -404,4 +404,4 @@ def loss(params, shots=None): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_eqnn_force_field/demo.py b/demonstrations_v2/tutorial_eqnn_force_field/demo.py index 45373bf0b4..159702e5d9 100644 --- a/demonstrations_v2/tutorial_eqnn_force_field/demo.py +++ b/demonstrations_v2/tutorial_eqnn_force_field/demo.py @@ -134,7 +134,7 @@ import jax -jax.config.update("jax_platform_name", "cpu") +jax.config.update('jax_platform_name', 'cpu') jax.config.update("jax_enable_x64", True) from jax import numpy as jnp @@ -155,7 +155,7 @@ [ np.kron(X, X), np.kron(Y, Y), - np.kron(Z, Z), + np.kron(Z, Z) ] # Vector of tensor products of Pauli matrices ) ) @@ -631,5 +631,4 @@ def inference(loss_data, opt_state): ###################################################################### # About the author # ---------------- -# .. include:: ../_static/authors/isabel_le.txt -# .. include:: ../_static/authors/oriel_kiss.txt +# diff --git a/demonstrations_v2/tutorial_eqnn_force_field/metadata.json b/demonstrations_v2/tutorial_eqnn_force_field/metadata.json index 99dd1bcc61..b2d5af1d64 100644 --- a/demonstrations_v2/tutorial_eqnn_force_field/metadata.json +++ b/demonstrations_v2/tutorial_eqnn_force_field/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2024-03-12T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Machine Learning", "Quantum Chemistry" diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py b/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py index 060ff6953e..720a1d8dbd 100644 --- a/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/demo.py @@ -12,57 +12,57 @@ ###################################################################### # A notorious problem when data comes in the form of graphs -- think of molecules or social media -# networks -- is that the numerical representation of a graph in a computer is not unique. +# networks -- is that the numerical representation of a graph in a computer is not unique. # For example, if we describe a graph via an `adjacency matrix `_ whose -# entries contain the edge weights as off-diagonals and node weights on the diagonal, -# any simultaneous permutation of rows and columns of this matrix refer to the same graph. -# +# entries contain the edge weights as off-diagonals and node weights on the diagonal, +# any simultaneous permutation of rows and columns of this matrix refer to the same graph. +# # .. figure:: ../_static/demonstration_assets/equivariant_graph_embedding/adjacency-matrices.png # :width: 60% # :align: center # :alt: adjacency-matrices # -# For example, the graph in the image above is represented by each of the two equivalent adjacency matrices. -# The top matrix can be transformed into the bottom matrix -# by swapping the first row with the third row, then swapping the third column with the first column, then the +# For example, the graph in the image above is represented by each of the two equivalent adjacency matrices. +# The top matrix can be transformed into the bottom matrix +# by swapping the first row with the third row, then swapping the third column with the first column, then the # new first row with the second, and finally the first colum with the second. # -# But the number of such permutations grows factorially with the number of nodes in the graph, which +# But the number of such permutations grows factorially with the number of nodes in the graph, which # is even worse than an exponential growth! -# +# # If we want computers to learn from graph data, we usually want our models to "know" that all these -# permuted adjacency matrices refer to the same object, so we do not waste resources on learning -# this property. In mathematical terms, this means that the model should be in- or -# equivariant (more about this distinction below) with respect to permutations. -# This is the basic motivation of `Geometric Deep Learning `_, -# ideas of which have found their way into quantum machine learning. -# -# This tutorial shows how to implement an example of a trainable permutation equivariant graph embedding -# as proposed in `Skolik et al. (2022) `_. The embedding -# maps the adjacency matrix of an undirected graph with edge and node weights to a quantum state, such that -# permutations of an adjacency matrix get mapped to the same states *if only we also +# permuted adjacency matrices refer to the same object, so we do not waste resources on learning +# this property. In mathematical terms, this means that the model should be in- or +# equivariant (more about this distinction below) with respect to permutations. +# This is the basic motivation of `Geometric Deep Learning `_, +# ideas of which have found their way into quantum machine learning. +# +# This tutorial shows how to implement an example of a trainable permutation equivariant graph embedding +# as proposed in `Skolik et al. (2022) `_. The embedding +# maps the adjacency matrix of an undirected graph with edge and node weights to a quantum state, such that +# permutations of an adjacency matrix get mapped to the same states *if only we also # permute the qubit registers in the same fashion*. -# -# .. note:: -# The tutorial is meant for beginners and does not contain the mathematical details of the +# +# .. note:: +# The tutorial is meant for beginners and does not contain the mathematical details of the # rich theory of equivariance. Have a look -# `at this demo `_ if you want to know more. -# -# +# `at this demo `_ if you want to know more. +# +# # Permuted adjacency matrices describe the same graph # --------------------------------------------------- -# -# Let us first verify that permuted adjacency matrices really describe one and the same graph. +# +# Let us first verify that permuted adjacency matrices really describe one and the same graph. # We also gain some useful data generation functions for later. # -# First we create random adjacency matrices. -# The entry :math:`a_{ij}` of this matrix corresponds to the weight of the edge between nodes -# :math:`i` and :math:`j` in the graph. We assume that graphs have no self-loops; instead, -# the diagonal elements of the adjacency matrix are interpreted as node weights (or -# "node attributes"). -# -# Taking the example of a Twitter user retweet network, the nodes would be users, -# edge weights indicate how often two users retweet each other and node attributes +# First we create random adjacency matrices. +# The entry :math:`a_{ij}` of this matrix corresponds to the weight of the edge between nodes +# :math:`i` and :math:`j` in the graph. We assume that graphs have no self-loops; instead, +# the diagonal elements of the adjacency matrix are interpreted as node weights (or +# "node attributes"). +# +# Taking the example of a Twitter user retweet network, the nodes would be users, +# edge weights indicate how often two users retweet each other and node attributes # could indicate the follower count of a user. # @@ -104,7 +104,7 @@ def permute(A, permutation): print(A_perm) ###################################################################### -# If we create `networkx` graphs from both adjacency matrices and plot them, +# If we create `networkx` graphs from both adjacency matrices and plot them, # we see that they are identical as claimed. # @@ -115,7 +115,7 @@ def permute(A, permutation): np.fill_diagonal(A, np.zeros(len(A))) G1 = nx.Graph(A) -pos1=nx.spring_layout(G1) +pos1 = nx.spring_layout(G1, seed=1) nx.draw(G1, pos1, labels=node_labels, ax=ax1, node_size = 800, node_color = "#ACE3FF") edge_labels = nx.get_edge_attributes(G1,'weight') nx.draw_networkx_edge_labels(G1,pos1,edge_labels=edge_labels, ax=ax1) @@ -125,7 +125,7 @@ def permute(A, permutation): np.fill_diagonal(A_perm, np.zeros(len(A))) G2 = nx.Graph(A_perm) -pos2=nx.spring_layout(G2) +pos2 = nx.spring_layout(G2, seed=1) nx.draw(G2, pos2, labels=node_labels, ax=ax2, node_size = 800, node_color = "#ACE3FF") edge_labels = nx.get_edge_attributes(G2,'weight') nx.draw_networkx_edge_labels(G2,pos2,edge_labels=edge_labels, ax=ax2) @@ -136,73 +136,73 @@ def permute(A, permutation): plt.show() ###################################################################### -# .. note:: -# -# The issue of non-unique numerical representations of graphs ultimately stems -# from the fact that the nodes in a graph +# .. note:: +# +# The issue of non-unique numerical representations of graphs ultimately stems +# from the fact that the nodes in a graph # do not have an intrinsic order, and by labelling them in a numerical data structure like a matrix # we therefore impose an arbitrary order. # # Permutation equivariant embeddings # ---------------------------------- -# -# When we design a machine learning model that takes graph data, the first step is to encode -# the adjacency matrix into a quantum state using an embedding or +# +# When we design a machine learning model that takes graph data, the first step is to encode +# the adjacency matrix into a quantum state using an embedding or # `quantum feature map `_ # :math:`\phi:` -# -# .. math:: -# +# +# .. math:: +# # A \rightarrow |\phi(A)\rangle . -# -# We may want the resulting quantum state to be the same for all adjacency matrices describing -# the same graph. In mathematical terms, this means that :math:`\phi` is an *invariant* embedding with respect to +# +# We may want the resulting quantum state to be the same for all adjacency matrices describing +# the same graph. In mathematical terms, this means that :math:`\phi` is an *invariant* embedding with respect to # simultaneous row and column permutations :math:`\pi(A)` of the adjacency matrix: -# -# .. math:: -# +# +# .. math:: +# # |\phi(A) \rangle = |\phi(\pi(A))\rangle \;\; \text{ for all } \pi . -# -# However, invariance is often too strong a constraint. Think for example of an encoding that -# associates each node in the graph with a qubit. We might want permutations of the adjacency +# +# However, invariance is often too strong a constraint. Think for example of an encoding that +# associates each node in the graph with a qubit. We might want permutations of the adjacency # matrix to lead to the same state *up to an equivalent permutation of the qubits* :math:`P_{\pi},` # where # -# .. math:: -# +# .. math:: +# # P_{\pi} |q_1,...,q_n \rangle = |q_{\textit{perm}_{\pi}(1)}, ... q_{\textit{perm}_{\pi}(n)} \rangle . -# -# The function :math:`\text{perm}_{\pi}` maps each index to the permuted index according to :math:`\pi.` -# # -# .. note:: +# The function :math:`\text{perm}_{\pi}` maps each index to the permuted index according to :math:`\pi.` +# +# +# .. note:: # # The operator :math:`P_{\pi}` is implemented by PennyLane's :class:`~pennylane.Permute.` -# +# # This results in an *equivariant* embedding with respect to permutations of the adjacency matrix: -# -# .. math:: -# -# |\phi(A) \rangle = P_{\pi}|\phi(\pi(A))\rangle \;\; \text{ for all } \pi . -# -# +# +# .. math:: +# +# |\phi(A) \rangle = P_{\pi}|\phi(\pi(A))\rangle \;\; \text{ for all } \pi . +# +# # This is exactly what the following quantum embedding is aiming to do! The mathematical details -# behind these concepts use group theory and are beautiful, but can be a bit daunting. +# behind these concepts use group theory and are beautiful, but can be a bit daunting. # Have a look at `this paper `_ if you want to learn more. -# +# # # Implementation in PennyLane # --------------------------- -# -# Let's get our hands dirty with an example. As mentioned, we will implement the permutation-equivariant +# +# Let's get our hands dirty with an example. As mentioned, we will implement the permutation-equivariant # embedding suggested in `Skolik et al. (2022) `_ which has this structure: -# +# # .. figure:: ../_static/demonstration_assets/equivariant_graph_embedding/circuit.png # :width: 70% # :align: center # :alt: Equivariant embedding -# -# The image can be found in `Skolik et al. (2022) `_ and shows one layer of the circuit. +# +# The image can be found in `Skolik et al. (2022) `_ and shows one layer of the circuit. # The :math:`\epsilon` are our edge weights while :math:`\alpha` describe the node weights, and the :math:`\beta,` :math:`\gamma` are variational parameters. # # In PennyLane this looks as follows: @@ -222,23 +222,23 @@ def perm_equivariant_embedding(A, betas, gammas): """ n_nodes = len(A) n_layers = len(betas) # infer the number of layers from the parameters - + # initialise in the plus state for i in range(n_nodes): qml.Hadamard(i) - + for l in range(n_layers): for i in range(n_nodes): for j in range(i): - # factor of 2 due to definition of gate + # factor of 2 due to definition of gate qml.IsingZZ(2*gammas[l]*A[i,j], wires=[i,j]) for i in range(n_nodes): qml.RX(A[i,i]*betas[l], wires=i) ###################################################################### -# We can use this ansatz in a circuit. +# We can use this ansatz in a circuit. n_qubits = 5 n_layers = 2 @@ -265,9 +265,9 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): ###################################################################### # Validating the equivariance # --------------------------- -# +# # Let's now check if the circuit is really equivariant! -# +# # This is the expectation value we get using the original adjacency matrix as an input: # @@ -286,15 +286,15 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): ###################################################################### -# Why are the two values different? Well, we constructed an *equivariant* ansatz, -# not an *invariant* one! Remember, an *invariant* ansatz means that embedding a permutation of -# the adjacency matrix leads to the same state as an embedding of the original matrix. -# An *equivariant* ansatz embeds the permuted adjacency matrix into a state where the qubits -# are permuted as well. -# -# As a result, the final state before measurement is only the same if we -# permute the qubits in the same manner that we permute the input adjacency matrix. We could insert a -# permutation operator ``qml.Permute(perm)`` to achieve this, or we simply permute the wires +# Why are the two values different? Well, we constructed an *equivariant* ansatz, +# not an *invariant* one! Remember, an *invariant* ansatz means that embedding a permutation of +# the adjacency matrix leads to the same state as an embedding of the original matrix. +# An *equivariant* ansatz embeds the permuted adjacency matrix into a state where the qubits +# are permuted as well. +# +# As a result, the final state before measurement is only the same if we +# permute the qubits in the same manner that we permute the input adjacency matrix. We could insert a +# permutation operator ``qml.Permute(perm)`` to achieve this, or we simply permute the wires # of the observables! # @@ -309,23 +309,23 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): ###################################################################### # Et voilà! -# -# +# +# # Conclusion # ---------- -# -# Equivariant graph embeddings can be combined with other equivariant parts of a quantum machine learning pipeline -# (like measurements and the cost function). `Skolik et al. (2022) `_, -# for example, use such a pipeline as part of a reinforcement learning scheme that finds heuristic solutions for the -# traveling salesman problem. Their simulations compare a fully equivariant model to circuits that break -# permutation equivariance and show that it performs better, confirming that if we know +# +# Equivariant graph embeddings can be combined with other equivariant parts of a quantum machine learning pipeline +# (like measurements and the cost function). `Skolik et al. (2022) `_, +# for example, use such a pipeline as part of a reinforcement learning scheme that finds heuristic solutions for the +# traveling salesman problem. Their simulations compare a fully equivariant model to circuits that break +# permutation equivariance and show that it performs better, confirming that if we know # about structure in our data, we should try to use this knowledge in machine learning. # # References # ---------- # -# 1. Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko (2022). -# Equivariant quantum circuits for learning on weighted graphs. +# 1. Andrea Skolik, Michele Cattelan, Sheir Yarkoni,Thomas Baeck and Vedran Dunjko (2022). +# Equivariant quantum circuits for learning on weighted graphs. # `arXiv:2205.06109 `__ # # 2. Quynh T. Nguyen, Louis Schatzki, Paolo Braccia, Michael Ragone, @@ -333,9 +333,9 @@ def eqc(adjacency_matrix, observable, trainable_betas, trainable_gammas): # Theory for Equivariant Quantum Neural Networks. # `arXiv:2210.08566 `__ # -# About the author -# ------------------------- -# .. include:: ../_static/authors/maria_schuld.txt +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json b/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json index 22cbcd9936..e299020b98 100644 --- a/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json +++ b/demonstrations_v2/tutorial_equivariant_graph_embedding/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-07-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations_v2/tutorial_error_mitigation/demo.py b/demonstrations_v2/tutorial_error_mitigation/demo.py index e75f6f452f..023a9cf202 100644 --- a/demonstrations_v2/tutorial_error_mitigation/demo.py +++ b/demonstrations_v2/tutorial_error_mitigation/demo.py @@ -273,7 +273,7 @@ def executor(circuits, dev=dev_noisy): ) circuits_with_meas.append(circuit_with_meas) - return qml.execute(circuits_with_meas, dev, gradient_fn=None) + return qml.execute(circuits_with_meas, dev, diff_method=None) ############################################################################## @@ -540,7 +540,7 @@ def executor(circuit): circuits, postproc = qml.transforms.split_non_commuting( circuit_with_meas, grouping_strategy=None ) - circuits_executed = qml.execute(circuits, dev_noisy, gradient_fn=None) + circuits_executed = qml.execute(circuits, dev_noisy, diff_method=None) return postproc(circuits_executed) mitig_energy = execute_with_zne(circuit, executor, scale_noise=fold_global) @@ -593,8 +593,6 @@ def executor(circuit): # IEEE International Conference on Quantum Computing and Engineering (2020). # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/thomas_bromley.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/andrea_mari.txt diff --git a/demonstrations_v2/tutorial_error_mitigation/metadata.json b/demonstrations_v2/tutorial_error_mitigation/metadata.json index 0dfc8c3ad3..3d93261a10 100644 --- a/demonstrations_v2/tutorial_error_mitigation/metadata.json +++ b/demonstrations_v2/tutorial_error_mitigation/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2021-11-29T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-12T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py b/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py index 66be8b783e..acecaa1460 100644 --- a/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py +++ b/demonstrations_v2/tutorial_expressivity_fourier_series/demo.py @@ -856,8 +856,6 @@ def random_weights(): # `arXiv:2008.08605 `__ (2020). # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/maria_schuld.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/johannes_jakob_meyer.txt diff --git a/demonstrations_v2/tutorial_falqon/demo.py b/demonstrations_v2/tutorial_falqon/demo.py index 98a2e1bbdc..ad534d2fa5 100644 --- a/demonstrations_v2/tutorial_falqon/demo.py +++ b/demonstrations_v2/tutorial_falqon/demo.py @@ -130,7 +130,9 @@ edges = [(0, 1), (1, 2), (2, 0), (2, 3), (1, 4)] graph = nx.Graph(edges) -nx.draw(graph, with_labels=True, node_color="#e377c2") +positions = nx.spring_layout(graph, seed=1) +nx.draw(graph, with_labels=True, node_color="#e377c2", pos=positions) +plt.show() ###################################################################### # We must first encode this combinatorial problem into a cost Hamiltonian :math:`H_c.` This ends up being @@ -320,7 +322,9 @@ def prob_circuit(): graph = nx.Graph(edges) cmap = ["#00b4d9"]*3 + ["#e377c2"]*2 -nx.draw(graph, with_labels=True, node_color=cmap) +positions = nx.spring_layout(graph, seed=1) +nx.draw(graph, with_labels=True, node_color=cmap, pos=positions) +plt.show() ###################################################################### # Benchmarking FALQON @@ -394,7 +398,9 @@ def prob_circuit(): new_edges = [(0, 1), (1, 2), (2, 0), (2, 3), (1, 4), (4, 5), (5, 2), (0, 6)] new_graph = nx.Graph(new_edges) -nx.draw(new_graph, with_labels=True, node_color="#e377c2") +positions = nx.spring_layout(new_graph, seed=1) +nx.draw(new_graph, with_labels=True, node_color="#e377c2", pos=positions) +plt.show() ###################################################################### # We can now use the PennyLane QAOA module to create a QAOA circuit corresponding to the MaxClique problem. For this @@ -473,8 +479,6 @@ def prob_circuit(params): # Magann, A. B., Rudinger, K. M., Grace, M. D., & Sarovar, M. (2021). Feedback-based quantum optimization. arXiv preprint `arXiv:2103.08619 `__. # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/david_wakeham.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/jack_ceroni.txt diff --git a/demonstrations_v2/tutorial_falqon/metadata.json b/demonstrations_v2/tutorial_falqon/metadata.json index 1df5f4ec09..ff99d8f9f3 100644 --- a/demonstrations_v2/tutorial_falqon/metadata.json +++ b/demonstrations_v2/tutorial_falqon/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2021-05-21T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Optimization" ], diff --git a/demonstrations_v2/tutorial_fermionic_operators/demo.py b/demonstrations_v2/tutorial_fermionic_operators/demo.py index b41b286cd1..d06f98aafb 100644 --- a/demonstrations_v2/tutorial_fermionic_operators/demo.py +++ b/demonstrations_v2/tutorial_fermionic_operators/demo.py @@ -108,7 +108,7 @@ # The matrix representation of the qubit Hamiltonian in the computational basis can be diagonalized # to get its eigenpairs. -from pennylane import numpy as np +import numpy as np val, vec = np.linalg.eigh(h.sparse_matrix().toarray()) print(f"eigenvalues:\n{val}") @@ -136,9 +136,10 @@ # the hydrogen molecule as an example. We first define the atom types and the atomic coordinates. import pennylane as qml +from jax import numpy as jnp symbols = ["H", "H"] -geometry = np.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]], requires_grad=False) +geometry = jnp.array([[-0.67294, 0.0, 0.0], [0.67294, 0.0, 0.0]]) ############################################################################## # Then we compute the one- and two-electron integrals, which are the coefficients :math:`c` in the @@ -227,4 +228,4 @@ # # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt +# diff --git a/demonstrations_v2/tutorial_fermionic_operators/metadata.json b/demonstrations_v2/tutorial_fermionic_operators/metadata.json index 6ac634f0ab..46fc8ef4a2 100644 --- a/demonstrations_v2/tutorial_fermionic_operators/metadata.json +++ b/demonstrations_v2/tutorial_fermionic_operators/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-06-27T00:00:00+00:00", - "dateOfLastModification": "2024-10-30T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py index 03cc87ea24..a4f5439a83 100644 --- a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py +++ b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/demo.py @@ -20,7 +20,7 @@ Introduction ------------ -The :doc:`KAK theorem ` is an important result from Lie theory that states that any Lie group element :math:`U` can be decomposed +The :doc:`KAK theorem ` is an important result from Lie theory that states that any Lie group element :math:`U` can be decomposed as :math:`U = K_1 A K_2,` where :math:`K_{1, 2}` and :math:`A` are elements of two special sub-groups :math:`\mathcal{K}` and :math:`\mathcal{A},` respectively. In special cases, the decomposition simplifies to :math:`U = K A K^\dagger.` @@ -36,7 +36,7 @@ We can use this general result from Lie theory as a powerful circuit decomposition technique. .. note:: We recommend a basic understanding of Lie algebras, see e.g. our :doc:`introduction to (dynamical) Lie algebras for quantum practitioners `. - Otherwise, this demo should be self-contained, though for the mathematically inclined, we further recommend our :doc:`demo on the KAK theorem ` + Otherwise, this demo should be self-contained, though for the mathematically inclined, we further recommend our :doc:`demo on the KAK theorem ` that dives into the mathematical depths of the theorem and provides more background info. Goal: Fast-forwarding time evolutions using the KAK decomposition @@ -91,21 +91,21 @@ g = [op.pauli_rep for op in g] ############################################################################## -# +# # Cartan decomposition # -------------------- -# +# # A Cartan decomposition is a bipartition :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}` into a vertical subspace # :math:`\mathfrak{k}` and an orthogonal horizontal subspace :math:`\mathfrak{m}.` In practice, it can be induced by an -# involution function :math:`\Theta` that fulfills :math:`\Theta(\Theta(g)) = g \ \forall g \in \mathfrak{g}.` Different -# involutions lead to different types of Cartan decompositions, which have been fully classified by Cartan +# involution function :math:`\Theta` that fulfills :math:`\Theta(\Theta(g)) = g \ \forall g \in \mathfrak{g}.` Different +# involutions lead to different types of Cartan decompositions, which have been fully classified by Cartan # (see `the classification of symmetric spaces by Cartan `__). -# +# # .. admonition:: Notation # :class: note # # Note that :math:`\mathfrak{k}` is the small letter k in -# `Fraktur `__ and a +# `Fraktur `__ and a # common — not our — choice for the vertical subspace in a Cartan decomposition. # # One common choice of involution is the so-called even-odd involution for Pauli words, @@ -113,14 +113,13 @@ # It essentially counts whether the number of non-identity Pauli operators in the Pauli word is even or odd. def even_odd_involution(op): - """Generalization of EvenOdd to sums of Paulis""" [pw] = op.pauli_rep return len(pw) % 2 even_odd_involution(X(0)), even_odd_involution(X(0) @ Y(3)) ############################################################################## -# +# # The vertical and horizontal subspaces are the two eigenspaces of the involution, corresponding to the :math:`\pm 1` eigenvalues. # In particular, we have :math:`\Theta(\mathfrak{k}) = \mathfrak{k}` and :math:`\Theta(\mathfrak{m}) = - \mathfrak{m}.` # So in order to perform the Cartan decomposition :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m},` we simply @@ -152,7 +151,7 @@ def cartan_decomposition(g, involution): ############################################################################## -# We have successfully decomposed the 60-dimensional Lie algebra +# We have successfully decomposed the 60-dimensional Lie algebra # into a 24-dimensional vertical subspace and a 36-dimensional subspace. # # Note that not every bipartition of a Lie algebra constitutes a Cartan decomposition. @@ -168,7 +167,7 @@ def cartan_decomposition(g, involution): # In particular, :math:`\mathfrak{k}` is closed under commutation and is therefore a subalgebra, whereas :math:`\mathfrak{m}` is not. # This also has the consequence that the associated Lie group :math:`\mathcal{K} := e^{i \mathfrak{k}}` is a subgroup # of the associated Lie group :math:`\mathcal{G} := e^{i \mathfrak{g}}.` -# +# # We mentioned earlier that we are aiming to do a special case of KAK decomposition where the second unitary :math:`K_2 = K_1^\dagger.` # This is possible whenever the operator that we want to decompose is in the horizontal subspace :math:`\mathfrak{m}`, i.e. we want :math:`\Theta(H) = -H`. # The chosen :math:`H` and :math:`\Theta` fulfill this property, as can be easily verified. @@ -181,7 +180,7 @@ def cartan_decomposition(g, involution): # # Cartan subalgebra # ----------------- -# +# # With this we have identified the first subgroup, :math:`\mathcal{K},` of the KAK decomposition. The other subgroup # is induced by the so-called (horizontal) Cartan subalgebra :math:`\mathfrak{h}.` This is a maximal Abelian subalgebra of :math:`\mathfrak{m}` and it is not unique. # For the case of Pauli words, we can simply pick any element in :math:`\mathfrak{m}` and collect all other operators in :math:`\mathfrak{m}` @@ -238,19 +237,19 @@ def cartan_subalgebra(m, which=0): ############################################################################## # We now have the Cartan decomposition :math:`\mathfrak{g} = \mathfrak{k} \oplus \tilde{\mathfrak{m}} \oplus \mathfrak{h}` # and with that all the necessary ingredients for the KAK decomposition. -# +# # Variational KhK decomposition # ----------------------------- # # The KAK theorem is not constructive in the sense that it proves that there exists such a decomposition, but there is no general way of obtaining # it. In particular, there are no linear algebra subroutines implemented in ``numpy`` or ``scipy`` that just compute it for us. -# Here, we follow the construction of [#Kökcü]_ for the special case of :math:`H` being in the horizontal space and the decomposition +# Here, we follow the construction of [#Kökcü]_ for the special case of :math:`H` being in the horizontal space and the decomposition # simplifying to :math:`H = K^\dagger h K`. # # The authors propose to find a local extremum of the cost function -# +# # .. math:: f(\theta) = \langle K(\theta) v K(\theta)^\dagger, H\rangle -# +# # where :math:`\langle \cdot, \cdot \rangle` is some inner product (in our case, the trace inner product :math:`\langle A, B \rangle = \text{tr}(A^\dagger B)).` # This construction uses the operator :math:`v = \sum_j \pi^j h_j \in \mathfrak{h}` # that is such that :math:`e^{i t v}` is dense in :math:`e^{i \mathcal{h}}.` @@ -265,20 +264,20 @@ def cartan_subalgebra(m, which=0): ############################################################################## -# +# # This procedure has the advantage that we can use an already decomposed ansatz -# +# # .. math:: K(\theta) = \prod_j e^{-i \theta_j k_j} -# +# # for the vertical unitary. -# +# # Now we just have to define the cost function and find an extremum. # In this case, we are going to use gradient descent to minimize the cost function to a minimum. # We are going to use ``jax`` and ``optax`` and write some boilerplate for the optimization procedure. # -# .. note:: -# You can check our demos on parameter optimization in JAX with -# :doc:`Optax ` or +# .. note:: +# You can check our demos on parameter optimization in JAX with +# :doc:`Optax ` or # :doc:`JAXOpt `. # @@ -339,7 +338,7 @@ def loss(theta): theta0 = jnp.ones(len(k), dtype=float) -thetas, energy, _ = run_opt(loss, theta0, n_epochs=600, lr=0.05) +thetas, energy, _ = run_opt(loss, theta0, n_epochs=1000, lr=0.05) plt.plot(energy - np.min(energy)) plt.xlabel("epochs") plt.ylabel("cost") @@ -356,7 +355,7 @@ def loss(theta): ############################################################################## # The special element :math:`h_0` from the Cartan subalgebra :math:`\mathfrak{h}` is given by # rotating the Hamiltonian by the critical :math:`K_c,` -# +# # .. math:: h_0 = K_c^\dagger H K_c. h_0_m = Kc_m.conj().T @ H_m @ Kc_m @@ -369,18 +368,18 @@ def loss(theta): not h_vspace.is_independent(h_0.pauli_rep) ############################################################################## -# +# # The fact that :math:`K_c^\dagger H K_c \in \mathfrak{h}` is crucial for this decomposition to be valid and meaningful. # Otherwise, :math:`h_0` could be anything and we arrive back at the original problem of decomposing :math:`e^{-i t h_0}`. # Here we know that :math:`h_0` is composed of elements of an Abelian Lie algebra :math:`\mathfrak{h}`, such that we can -# trivially decompose its unitary as -# +# trivially decompose its unitary as +# # .. math:: e^{-i t h_0} = e^{-i t \sum_{j=1}^{|\mathfrak{h}|} c_j h_j} = \prod_{j=1}^{|\mathfrak{h}|} e^{-i t c_j h_j}. # # Overall, this gives us the KhK decomposition of :math:`H,` -# +# # .. math:: H = K_c h_0 K_c^\dagger. -# +# # This trivially reproduces the original Hamiltonian. # @@ -408,9 +407,6 @@ def trace_distance(A, B): trace_distance(U_exact_m, U_kak_m) - - - ############################################################################## # Indeed we find that the KAK decomposition that we found reproduces the unitary evolution operator. # Note that this is valid for arbitrary :math:`t,` such that the Hamiltonian simulation operator has a fixed depth. @@ -432,8 +428,8 @@ def trace_distance(A, B): ############################################################################## # Time evolutions # --------------- -# -# We compute multiple time evolutions for different times and compare Suzuki—Trotter products with the KAK decomposition circuit. +# +# We compute multiple time evolutions for different times and compare Suzuki–Trotter products with the KAK decomposition circuit. # ts = jnp.linspace(1., 5., 10) @@ -468,7 +464,7 @@ def compute_res(Us): ############################################################################## -# We see the expected behavior of Suzuki—Trotter product formulas getting worse with an increase in time +# We see the expected behavior of Suzuki–Trotter product formulas getting worse with an increase in time # while the KAK error is constant zero. # # The KAK decomposition is particularly well-suited for smaller systems as the circuit depth is equal to the @@ -478,28 +474,27 @@ def compute_res(Us): ############################################################################## -# +# # Conclusion # ---------- -# +# # The KAK theorem is a very general mathematical result with far-reaching consequences. # While there is no canonical way of obtaining an actual decomposition in practice, we followed # the approach of [#Kökcü]_ which uses a specifically designed loss function and variational # optimization to find the decomposition. # This approach has the advantage that the resulting decomposition is itself already decomposed in terms of rotation gates in the original Lie algebra, # as opposed to other methods such as [#Chu]_ that find :math:`K` as a whole. -# We provided a flexible pipeline that lets users find KAK decompositions in PennyLane for systems with small +# We provided a flexible pipeline that lets users find KAK decompositions in PennyLane for systems with small # DLA (:doc:`Dynamical Lie Algebras `) and specifically decomposed the Heisenberg model Hamiltonian with :math:`n=4` qubits that has a DLA of dimension :math:`60` (:math:`\left(\mathfrak{s u}(2^{n-2})\right)^{\oplus 4}`). # -# As most DLAs scale exponentially in the number of qubits, KAK decompositions are limited to small system sizes +# As most DLAs scale exponentially in the number of qubits, KAK decompositions are limited to small system sizes # or special cases of systems with small DLAs. # This is in line with the notion that fast-forwarding is generally not possible and limited to special systems. # In particular, a KAK decomposition is ultimately always a fast-forwarding of a Hamiltonian. - ############################################################################## -# +# # References # ---------- # diff --git a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json index b66ab6437f..1efc93770b 100644 --- a/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json +++ b/demonstrations_v2/tutorial_fixed_depth_hamiltonian_simulation_via_cartan_decomposition/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-12-19T00:00:00+00:00", - "dateOfLastModification": "2024-12-19T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Computing", "Algorithms" @@ -53,7 +53,7 @@ "relatedContent": [ { "type": "demonstration", - "id": "tutorial_kak_theorem", + "id": "tutorial_kak_decomposition", "weight": 1.0 }, { diff --git a/demonstrations_v2/tutorial_gaussian_transformation/demo.py b/demonstrations_v2/tutorial_gaussian_transformation/demo.py index 03e2f4f611..59b441dfed 100644 --- a/demonstrations_v2/tutorial_gaussian_transformation/demo.py +++ b/demonstrations_v2/tutorial_gaussian_transformation/demo.py @@ -169,4 +169,4 @@ def cost(params): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_general_parshift/demo.py b/demonstrations_v2/tutorial_general_parshift/demo.py index 62a267ca37..3aca7eeb08 100644 --- a/demonstrations_v2/tutorial_general_parshift/demo.py +++ b/demonstrations_v2/tutorial_general_parshift/demo.py @@ -888,4 +888,4 @@ def finite_diff_second(fun): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/david_wierichs.txt +# diff --git a/demonstrations_v2/tutorial_geometric_qml/demo.py b/demonstrations_v2/tutorial_geometric_qml/demo.py index eb43a2c4bc..a88f072089 100644 --- a/demonstrations_v2/tutorial_geometric_qml/demo.py +++ b/demonstrations_v2/tutorial_geometric_qml/demo.py @@ -898,4 +898,4 @@ def opt_func(): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/richard_east.txt +# diff --git a/demonstrations_v2/tutorial_givens_rotations/demo.py b/demonstrations_v2/tutorial_givens_rotations/demo.py index ddbd9ab254..1c312bf402 100644 --- a/demonstrations_v2/tutorial_givens_rotations/demo.py +++ b/demonstrations_v2/tutorial_givens_rotations/demo.py @@ -185,21 +185,23 @@ """ import pennylane as qml -import numpy as np +from jax import numpy as jnp +import jax -dev = qml.device('default.qubit', wires=3) +jax.config.update("jax_enable_x64", True) +dev = qml.device('lightning.qubit', wires=3) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(x, y): # prepares the reference state |100> - qml.BasisState(np.array([1, 0, 0]), wires=[0, 1, 2]) + qml.BasisState(jnp.array([1, 0, 0]), wires=[0, 1, 2]) # applies the single excitations qml.SingleExcitation(x, wires=[0, 1]) qml.SingleExcitation(y, wires=[0, 2]) return qml.state() -x = -2 * np.arcsin(np.sqrt(1/3)) -y = -2 * np.arcsin(np.sqrt(1/2)) +x = -2 * jnp.arcsin(jnp.sqrt(1/3)) +y = -2 * jnp.arcsin(jnp.sqrt(1/2)) print(circuit(x, y)) ############################################################################## @@ -258,12 +260,14 @@ def circuit(x, y): ############################################################################## # Now we continue to build the circuit: -dev2 = qml.device('default.qubit', wires=6) +from jax import random + +dev2 = qml.device('lightning.qubit', wires=6) -@qml.qnode(dev2, interface="autograd") +@qml.qnode(dev2, interface="jax") def circuit2(x, y): # prepares reference state - qml.BasisState(np.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) + qml.BasisState(jnp.array([1, 1, 1, 0, 0, 0]), wires=[0, 1, 2, 3, 4, 5]) # apply all single excitations for i, s in enumerate(singles): qml.SingleExcitation(x[i], wires=s) @@ -273,8 +277,10 @@ def circuit2(x, y): return qml.state() # random angles of rotation -x = np.random.normal(0, 1, len(singles)) -y = np.random.normal(0, 1, len(doubles)) +key = random.PRNGKey(0) +key_x, key_y = random.split(key) +x = random.normal(key_x, shape=(len(singles),)) +y = random.normal(key_y, shape=(len(singles),)) output = circuit2(x, y) @@ -283,6 +289,8 @@ def circuit2(x, y): # involve only states with three particles. # constructs binary representation of states with non-zero amplitude +import numpy as np + states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] print(states) @@ -356,17 +364,17 @@ def circuit2(x, y): dev = qml.device('default.qubit', wires=6) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit3(x, y, z): - qml.BasisState(np.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) qml.SingleExcitation(z, wires=[1, 3]) return qml.state() -x = -2 * np.arcsin(np.sqrt(1/4)) -y = -2 * np.arcsin(np.sqrt(1/3)) -z = -2 * np.arcsin(np.sqrt(1/2)) +x = -2 * jnp.arcsin(jnp.sqrt(1/4)) +y = -2 * jnp.arcsin(jnp.sqrt(1/3)) +z = -2 * jnp.arcsin(jnp.sqrt(1/2)) output = circuit3(x, y, z) states = [np.binary_repr(i, width=6) for i in range(len(output)) if output[i] != 0] @@ -380,9 +388,9 @@ def circuit3(x, y, z): # above, this time controlling on the state of the first qubit and verify that we can prepare the # desired state. To perform the control, we use the :func:`~.pennylane.ctrl` transform: -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit4(x, y, z): - qml.BasisState(np.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) + qml.BasisState(jnp.array([1, 1, 0, 0, 0, 0]), wires=[i for i in range(6)]) qml.DoubleExcitation(x, wires=[0, 1, 2, 3]) qml.DoubleExcitation(y, wires=[0, 1, 4, 5]) # single excitation controlled on qubit 0 @@ -438,9 +446,9 @@ def circuit4(x, y, z): dev = qml.device('default.qubit', wires=4) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def state_preparation(params): - qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) qml.SingleExcitation(params[0], wires=[1, 2]) qml.SingleExcitation(params[1], wires=[1, 3]) # single excitations controlled on qubit 1 @@ -450,11 +458,11 @@ def state_preparation(params): return qml.state() n = 6 -params = np.array([-2 * np.arcsin(1/np.sqrt(n-i)) for i in range(n-1)]) +params = jnp.array([-2 * jnp.arcsin(1/jnp.sqrt(n-i)) for i in range(n-1)]) output = state_preparation(params) # sets very small coefficients to zero -output[np.abs(output) < 1e-10] = 0 +output = jnp.where(jnp.abs(output) < 1e-10, 0.0, output) states = [np.binary_repr(i, width=4) for i in range(len(output)) if output[i] != 0] print("Basis states = ", states) print("Output state =", output) @@ -495,4 +503,4 @@ def state_preparation(params): # # About the author # ---------------- -# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file +# diff --git a/demonstrations_v2/tutorial_givens_rotations/metadata.json b/demonstrations_v2/tutorial_givens_rotations/metadata.json index 7ce03efc41..b7e2d7694b 100644 --- a/demonstrations_v2/tutorial_givens_rotations/metadata.json +++ b/demonstrations_v2/tutorial_givens_rotations/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-06-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_grovers_algorithm/demo.py b/demonstrations_v2/tutorial_grovers_algorithm/demo.py index 8230cc98fd..b98e88383c 100644 --- a/demonstrations_v2/tutorial_grovers_algorithm/demo.py +++ b/demonstrations_v2/tutorial_grovers_algorithm/demo.py @@ -392,4 +392,4 @@ def circuit(): # # About the author # ---------------- -# .. include:: ../_static/authors/ludmila_botelho.txt +# diff --git a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py index ef0d1510f4..88da27d3e5 100644 --- a/demonstrations_v2/tutorial_here_comes_the_sun/demo.py +++ b/demonstrations_v2/tutorial_here_comes_the_sun/demo.py @@ -573,4 +573,4 @@ def circuit(params, operation=None): # # About the author # ---------------- -# .. include:: ../_static/authors/david_wierichs.txt +# diff --git a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py index 779a85668d..e35b5ccb1c 100644 --- a/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py +++ b/demonstrations_v2/tutorial_how_to_quantum_just_in_time_compile_vqe_catalyst/demo.py @@ -192,10 +192,6 @@ def optimization(params): # ###################################################################### -# About the authors -# ----------------- -# -# .. include:: ../_static/authors/ali_asadi.txt -# -# .. include:: ../_static/authors/josh_izaac.txt -# +# About the author +# ---------------- +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_how_to_use_registers/demo.py b/demonstrations_v2/tutorial_how_to_use_registers/demo.py index 981d123dec..24cefdd8dc 100644 --- a/demonstrations_v2/tutorial_how_to_use_registers/demo.py +++ b/demonstrations_v2/tutorial_how_to_use_registers/demo.py @@ -183,4 +183,3 @@ def circuit(): # About the author # ---------------- # -# .. include:: ../_static/authors/austin_huang.txt diff --git a/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py b/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py index b17ea81f06..4225454cd5 100644 --- a/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py +++ b/demonstrations_v2/tutorial_implicit_diff_susceptibility/demo.py @@ -652,8 +652,6 @@ def groundstate_expval_variational(a, z_init) -> float: # "The analytic domain in the implicit function theorem." # `JIPAM. J. Inequal. Pure Appl. Math 4.1 `__, (2003). # -# About the authors -# ----------------- -# .. include:: ../_static/authors/shahnawaz_ahmed.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/juan_felipe_carrasquilla_alvarez.txt diff --git a/demonstrations_v2/tutorial_initial_state_preparation/demo.py b/demonstrations_v2/tutorial_initial_state_preparation/demo.py index 589546603a..8a2cb89e5c 100644 --- a/demonstrations_v2/tutorial_initial_state_preparation/demo.py +++ b/demonstrations_v2/tutorial_initial_state_preparation/demo.py @@ -49,13 +49,11 @@ from pyscf import gto, scf, ci from pennylane.qchem import import_state -from pennylane import numpy as np +import numpy as np R = 1.2 # create the H3+ molecule -mol = gto.M(atom=[["H", (0, 0, 0)], - ["H", (0, 0, R)], - ["H", (0, 0, 2 * R)]], charge=1) +mol = gto.M(atom=[["H", (0, 0, 0)], ["H", (0, 0, R)], ["H", (0, 0, 2 * R)]], charge=1) # perfrom restricted Hartree-Fock and then CISD myhf = scf.RHF(mol).run() myci = ci.CISD(myhf).run() @@ -76,7 +74,7 @@ ############################################################################## # CCSD states # ~~~~~~~~~~~ -# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can +# The function :func:`~.pennylane.qchem.import_state` is general and works similarly for CCSD. It can # automatically detect the input type and apply the appropriate conversion protocol. from pyscf import cc @@ -159,8 +157,6 @@ # Let's take this opportunity to create the Hartree-Fock initial state, to compare the # other states against it later on. -from pennylane import numpy as np - hf_primer = ([[3, 0, 0]], np.array([1.0])) wf_hf = import_state(hf_primer) @@ -224,10 +220,11 @@ import pennylane as qml from pennylane import qchem +from jax import numpy as jnp # generate the molecular Hamiltonian for H3+ symbols = ["H", "H", "H"] -geometry = np.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) +geometry = jnp.array([[0, 0, 0], [0, 0, R/0.529], [0, 0, 2*R/0.529]]) molecule = qchem.Molecule(symbols, geometry, charge=1) H2mol, qubits = qchem.molecular_hamiltonian(molecule) @@ -252,19 +249,32 @@ def circuit_VQE(theta, initial_state): qml.SingleExcitation(theta[i], wires=excitation) return qml.expval(H2mol) + +def cost_fn(param): + return circuit_VQE(param, initial_state=wf_hf) + + ############################################################################## # Next, we create the VQE optimizer, initialize the variational parameters and run the VQE optimization. +import optax +import jax +jax.config.update("jax_enable_x64", True) -opt = qml.GradientDescentOptimizer(stepsize=0.4) -theta = np.array(np.zeros(len(excitations)), requires_grad=True) +opt = optax.sgd(learning_rate=0.4) # sgd stands for StochasticGradientDescent +theta = jnp.array(jnp.zeros(len(excitations))) delta_E, iteration = 10, 0 results_hf = [] +opt_state = opt.init(theta) +prev_energy = cost_fn(theta) # run the VQE optimization loop until convergence threshold is reached while abs(delta_E) > 1e-5: - theta, prev_energy = opt.step_and_cost(circuit_VQE, theta, initial_state=wf_hf) - new_energy = circuit_VQE(theta, initial_state=wf_hf) + gradient = jax.grad(cost_fn)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn(theta) delta_E = new_energy - prev_energy + prev_energy = new_energy results_hf.append(new_energy) if len(results_hf) % 5 == 0: print(f"Step = {len(results_hf)}, Energy = {new_energy:.6f} Ha") @@ -273,18 +283,30 @@ def circuit_VQE(theta, initial_state): ############################################################################## # And compare with how things go when you run it with the CISD initial state: -theta = np.array(np.zeros(len(excitations)), requires_grad=True) + +def cost_fn_cisd(param): + return circuit_VQE(param, initial_state=wf_cisd) + + +theta = jnp.array(jnp.zeros(len(excitations))) delta_E, iteration = 10, 0 results_cisd = [] +opt_state = opt.init(theta) +prev_energy = cost_fn_cisd(theta) while abs(delta_E) > 1e-5: - theta, prev_energy = opt.step_and_cost(circuit_VQE, theta, initial_state=wf_cisd) - new_energy = circuit_VQE(theta, initial_state=wf_cisd) + gradient = jax.grad(cost_fn_cisd)(theta) + updates, opt_state = opt.update(gradient, opt_state) + theta = optax.apply_updates(theta, updates) + new_energy = cost_fn_cisd(theta) delta_E = new_energy - prev_energy + prev_energy = new_energy results_cisd.append(new_energy) if len(results_cisd) % 5 == 0: print(f"Step = {len(results_cisd)}, Energy = {new_energy:.6f} Ha") -print(f"Starting with CISD state took {len(results_cisd)} iterations until convergence.") +print( + f"Starting with CISD state took {len(results_cisd)} iterations until convergence." +) ############################################################################## # Let's visualize the comparison between the two initial states, and see that indeed @@ -339,4 +361,4 @@ def circuit_VQE(theta, initial_state): # # About the author # ---------------- -# .. include:: ../_static/authors/stepan_fomichev.txt +# diff --git a/demonstrations_v2/tutorial_initial_state_preparation/metadata.json b/demonstrations_v2/tutorial_initial_state_preparation/metadata.json index 9ee85aaabf..fc1cf3178f 100644 --- a/demonstrations_v2/tutorial_initial_state_preparation/metadata.json +++ b/demonstrations_v2/tutorial_initial_state_preparation/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-10-20T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_intro_qrom/demo.py b/demonstrations_v2/tutorial_intro_qrom/demo.py index efceb77449..687f26da88 100644 --- a/demonstrations_v2/tutorial_intro_qrom/demo.py +++ b/demonstrations_v2/tutorial_intro_qrom/demo.py @@ -75,7 +75,7 @@ # This line is included for drawing purposes only. -@partial(qml.devices.preprocess.decompose, stopping_condition=lambda obj: False, max_expansion=1) +@partial(qml.transforms.decompose, max_expansion=1) @qml.qnode(dev) def circuit(index): @@ -225,7 +225,7 @@ def circuit(index): # Line added for drawing purposes only -@partial(qml.devices.preprocess.decompose, stopping_condition=lambda obj: False, max_expansion=2) +@partial(qml.transforms.decompose, max_expansion=2) @qml.qnode(qml.device("default.qubit", shots=1)) def circuit(index): qml.BasisState(index, wires=control_wires) diff --git a/demonstrations_v2/tutorial_intro_qrom/metadata.json b/demonstrations_v2/tutorial_intro_qrom/metadata.json index 9fe232c5f7..54ec124cd5 100644 --- a/demonstrations_v2/tutorial_intro_qrom/metadata.json +++ b/demonstrations_v2/tutorial_intro_qrom/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2024-09-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-18T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations_v2/tutorial_intro_qsvt/demo.py b/demonstrations_v2/tutorial_intro_qsvt/demo.py index b54bc0b88d..9e77e09d63 100644 --- a/demonstrations_v2/tutorial_intro_qsvt/demo.py +++ b/demonstrations_v2/tutorial_intro_qsvt/demo.py @@ -73,8 +73,8 @@ it's possible to tune the coefficients of the polynomial. For example .. math:: S(-\pi/2) U(a) S(\pi/2) U(a) S(0) = \begin{pmatrix} - 2a^2-1 & 0\\ - 0 & 2a^2 -1 + 2a^2-1 & 2a\sqrt{1-a^2}\\ + -2a\sqrt{1-a^2} & 2a^2 -1 \end{pmatrix}. The main quantum signal processing theorem states that it is possible to find @@ -86,12 +86,11 @@ find :math:`d+1` angles that implement any real polynomial of parity :math:`d \mod 2` and maximum degree :math:`d.` Multiple QSP sequences can then be used to implement real polynomials of indefinite parity. Finding the desired angles can be done efficiently in practice, but identifying the best -methods is an active area of research. You can learn more in our `QSP demo `_ -and in Ref. [#unification]_. +methods is an active area of research. The :class:`~pennylane.qsvt` function in PennyLane computes the angles internally. -For now, let's look at a simple example of how quantum signal processing can be implemented using +Let's look at a simple example of how quantum signal processing can be implemented using PennyLane. We aim to perform a transformation by the Legendre polynomial -:math:`(5 x^3 - 3x)/2,` for which we use pre-computed optimal angles. +:math:`(5 x^3 - 3x)/2`. As you will soon learn, QSP can be viewed as a special case of QSVT. We thus use the :func:`~.pennylane.qsvt` operation to construct the output matrix and compare the resulting transformation to the target polynomial. @@ -103,24 +102,18 @@ import matplotlib.pyplot as plt -def target_poly(a): - return 0.5 * (5 * a**3 - 3 * a) - - -# pre-optimized angles -angles = [-0.20409113, -0.91173829, 0.91173829, 0.20409113] +target_poly = [0, -3 * 0.5, 0, 5 * 0.5] def qsvt_output(a): # output matrix - out = qml.matrix(qml.qsvt(a, angles, wires=[0])) + out = qml.matrix(qml.qsvt(a, target_poly, encoding_wires=[0], block_encoding="embedding")) return out[0, 0] # top-left entry a_vals = np.linspace(-1, 1, 50) qsvt = [np.real(qsvt_output(a)) for a in a_vals] # neglect small imaginary part -target = [target_poly(a) for a in a_vals] - +target = [np.polyval(target_poly[::-1], a) for a in a_vals] # evaluate polynomial plt.plot(a_vals, target, label="target") plt.plot(a_vals, qsvt, "*", label="qsvt") @@ -258,7 +251,9 @@ def qsvt_output(a): eigvals = np.linspace(-1, 1, 16) A = np.diag(eigvals) # 16-dim matrix wire_order = list(range(5)) -U_A = qml.matrix(qml.qsvt, wire_order=wire_order)(A, angles, wires=wire_order) # block-encoded in 5-qubit system +U_A = qml.matrix(qml.qsvt, wire_order=wire_order)( + A, target_poly, encoding_wires=wire_order, block_encoding="embedding" +) # block-encoded in 5-qubit system qsvt_A = np.real(np.diagonal(U_A))[:16] # retrieve transformed eigenvalues @@ -314,4 +309,4 @@ def qsvt_output(a): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/juan_miguel_arrazola.txt +# diff --git a/demonstrations_v2/tutorial_intro_qsvt/metadata.json b/demonstrations_v2/tutorial_intro_qsvt/metadata.json index 2a2d3d128e..f5bee1e741 100644 --- a/demonstrations_v2/tutorial_intro_qsvt/metadata.json +++ b/demonstrations_v2/tutorial_intro_qsvt/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-05-23T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-21T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py b/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py index d5519cbbbc..dc499e07bc 100644 --- a/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py +++ b/demonstrations_v2/tutorial_isingmodel_PyTorch/demo.py @@ -251,4 +251,4 @@ def closure(): # # About the author # ---------------- -# .. include:: ../_static/authors/aroosa_ijaz.txt +# diff --git a/demonstrations_v2/tutorial_jax_transformations/demo.py b/demonstrations_v2/tutorial_jax_transformations/demo.py index 71e931252b..476e8fa3a4 100644 --- a/demonstrations_v2/tutorial_jax_transformations/demo.py +++ b/demonstrations_v2/tutorial_jax_transformations/demo.py @@ -308,4 +308,4 @@ def my_circuit(): # # About the author # ---------------- -# .. include:: ../_static/authors/chase_roberts.txt +# diff --git a/demonstrations_v2/tutorial_kak_decomposition/demo.py b/demonstrations_v2/tutorial_kak_decomposition/demo.py new file mode 100644 index 0000000000..a4789f8a4e --- /dev/null +++ b/demonstrations_v2/tutorial_kak_decomposition/demo.py @@ -0,0 +1,988 @@ +r"""The KAK decomposition +========================= + +The KAK decomposition is a beautiful mathematical result from Lie theory, with +particular relevance for quantum computing. It can be seen as a +generalization of the singular value decomposition (SVD), as it decomposes a group +element :math:`U` (think: unitary operator) into :math:`U=K_1AK_2,` where +:math:`K_{1,2}` and :math:`A` belong to special subgroups that we will introduce. +This means the KAK decomposition falls under the large umbrella of matrix factorizations, +and it allows us to break down arbitrary quantum circuits into smaller building blocks, +i.e., we can use it for quantum circuit decompositions. + +In this demo, we will dive into Lie algebras and their groups. Then, we will discuss +so-called symmetric spaces, which arise from certain subgroups of those Lie groups. +With these tools in our hands, we will then learn about the KAK decomposition itself, +breaking up the Lie group into the subgroup and the symmetric space. + +We will make all steps explicit on a toy example on paper and in code, which splits +single-qubit gates into a well-known sequence of rotations. +Finally, we will get to know a handy decomposition of arbitrary +two-qubit unitaries into rotation gates as another application of the KAK decomposition. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_kak_decomposition.png + :align: center + :width: 70% + :target: javascript:void(0) + +Along the way, we will put some non-essential mathematical details +as well as a few gotchas regarding the nomenclature into boxes such as this one: + +.. admonition:: Prerequisites + :class: note + + In the following we will assume a basic understanding of vector spaces, + linear maps, and Lie algebras. To review those topics, we recommend a look + at your favourite linear algebra material. For the latter, also see our + :doc:`introduction to (dynamical) Lie algebras `. + +Without further ado, let's get started! + +Lie algebras and their groups +----------------------------- + +We start with Lie algebras, their Lie groups, +and a particular interaction between the two, the *adjoint action*. + +Lie algebras +~~~~~~~~~~~~ + +As mentioned above, we will assume a basic understanding of the mathematical objects +we will use. To warm up, however, let us briefly talk about Lie algebras (for details +see our :doc:`intro to (dynamical) Lie algebras `). + +A *Lie algebra* :math:`\mathfrak{g}` is a vector space with an additional operation +that takes two vectors to a new vector, the *Lie bracket*. To form an algebra, :math:`\mathfrak{g}` must be +closed under the Lie bracket. +For our purposes, the vectors will always be matrices and the Lie bracket will be the matrix +commutator. + +**Example** + +Our working example in this demo will be the *special unitary* algebra in two dimensions, +:math:`\mathfrak{su}(2).` +It consists of traceless complex-valued skew-Hermitian :math:`2\times 2` matrices, which we +can conveniently describe using the Pauli matrices: + +.. math:: + + \mathfrak{su}(2) + &= \left\{i\left(\begin{array}{cc} a & b-ic \\ b+ic & -a \end{array}\right) + {\large |} a, b, c \in \mathbb{R}\right\}\\ + &= \left\{i(a Z + b X + c Y)| a, b, c \in \mathbb{R}\right\}. + +We will also look at a more involved example at the end of the demo. + +.. admonition:: Math detail: our Lie algebras are real + :class: note + + The algebra :math:`\mathfrak{su}(n)` is a *real* Lie algebra, i.e., it is a vector space over + real numbers, :math:`\mathbb{R}.` This means that scalar-vector multiplication is + only valid between vectors (complex-valued matrices) and real scalars. + + There is a simple way to see this. Multiplying a skew-Hermitian matrix + :math:`x\in\mathfrak{su}(n)` by a complex number :math:`c\in\mathbb{C}` will yield + :math:`(cx)^\dagger=\overline{c} x^\dagger=-\overline{c} x,` so that + the result might no longer be skew-Hermitian, i.e. no longer in the algebra! If we keep it to real scalars + :math:`c\in\mathbb{R}` only, we have :math:`\overline{c}=c,` so that + :math:`(cx)^\dagger=-cx` and we're fine. + + We will only consider real Lie algebras here. + +Let us set up :math:`\mathfrak{su}(2)` in code. +Note that the algebra itself consists of *skew*-Hermitian matrices, but we will work +with the Hermitian counterparts as inputs, i.e., we will skip the factor :math:`i.` +We can check that :math:`\mathfrak{su}(2)` is closed under commutators by +computing all nested commutators, the so-called *Lie closure*, and observing +that the closure is not larger than :math:`\mathfrak{su}(2)` itself. +Of course, we could also check the closure manually for this small example. +""" + +from itertools import product, combinations +import pennylane as qml +from pennylane import X, Y, Z +import numpy as np + +su2 = [X(0), Y(0), Z(0)] +print(f"su(2) is {len(su2)}-dimensional") + +all_hermitian = all(qml.equal(qml.adjoint(op).simplify(), op) for op in su2) +print(f"The operators are all Hermitian: {all_hermitian}") + +su2_lie_closed = qml.lie_closure(su2) +print(f"The Lie closure of su(2) is {len(su2_lie_closed)}-dimensional.") + +traces = [op.pauli_rep.trace() for op in su2] +print(f"All operators are traceless: {np.allclose(traces, 0.)}") + +###################################################################### +# We find that :math:`\mathfrak{su}(2)` is indeed closed, and that it is a 3-dimensional +# space, as expected from the explicit expression above. +# We also picked a correct representation with traceless operators. +# +# .. admonition:: Math detail: (semi)simple Lie algebras +# :class: note +# +# Our main result for this demo will be the KAK decomposition, which applies to so-called +# *semisimple* Lie algebras, which are in turn composed of *simple* Lie algebras as building +# blocks. Without going into detail, it often is sufficient to think of these building +# blocks as (1) special orthogonal algebras :math:`\mathfrak{so}(n),` (2) unitary symplectic +# algebras :math:`\mathfrak{sp}(n),` and (3) special unitary algebras :math:`\mathfrak{su}(n).` +# In particular, our example here is of the latter type, so it is not only semisimple, +# but even simple. +# +# Lie group from a Lie algebra +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The topic of Lie groups and Lie algebras is a large field of study and there are many +# things we could talk about in this section. For the sake of brevity, however, we will +# only list a few important properties that are needed further below. For more details +# and proofs, refer to your favourite Lie theory book, which could be [#hall]_ or [#tu]_. +# +# The Lie group :math:`\mathcal{G}` associated to a Lie algebra :math:`\mathfrak{g}` is given +# by the exponential map applied to the algebra: +# +# .. math:: +# +# \mathcal{G}=\exp(\mathfrak{g}). +# +# We will only consider Lie groups :math:`\exp(\mathfrak{g})` arising from a Lie algebra +# :math:`\mathfrak{g}` here. +# As we usually think about the unitary algebras :math:`\mathfrak{u}` and their +# subalgebras, the correspondence is well-known to quantum practitioners: Exponentiate +# a skew-Hermitian matrix to obtain a unitary operation, i.e., a quantum gate. +# +# Interaction between Lie groups and algebras +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# We will make use of a particular interaction between the algebra :math:`\mathfrak{g}` and +# its group :math:`\mathcal{G},` called the *adjoint action* of :math:`\mathcal{G}` on +# :math:`\mathfrak{g}.` It is given by +# +# .. math:: +# +# \text{Ad}: \mathcal{G} \times \mathfrak{g} \to \mathfrak{g}, +# \ (\exp(x),y)\mapsto \text{Ad}_{\exp(x)}(y) = \exp(x) y\exp(-x). +# +# Similarly, we can interpret the Lie bracket as a map of :math:`\mathfrak{g}` acting on itself, +# which is called the *adjoint representation* of :math:`\mathfrak{g}` on itself: +# +# .. math:: +# +# \text{ad}: \mathfrak{g} \times \mathfrak{g} \to \mathfrak{g}, +# \ (x, y) \mapsto \text{ad}_x(y) = [x, y]. +# +# The adjoint group action and adjoint algebra representation do not only carry a very +# similar name, they are intimately related: +# +# .. math:: +# +# \text{Ad}_{\exp(x)}(y) = \exp(\text{ad}_x) (y), +# +# where we applied the exponential map to :math:`\text{ad}_x,` which maps from :math:`\mathfrak{g}` +# to itself, via its series representation. +# We will refer to this relationship as the *adjoint identity*. +# We talk about :math:`\text{Ad}` and :math:`\text{ad}` in more detail in the box below, and refer to our demo +# :doc:`g-sim: Lie algebraic classical simulations ` for +# further discussion. +# +# .. admonition:: Derivation: adjoint representations +# :class: note +# +# We begin this derivation with the *adjoint action* of :math:`\mathcal{G}` on itself, +# given by +# +# .. math:: +# +# \Psi: \mathcal{G} \times \mathcal{G} \to \mathcal{G}, +# \ (\exp(x),\exp(y))\mapsto \Psi_{\exp(x)}(\exp(y)) = \exp(x) \exp(y)\exp(-x). +# +# The map :math:`\Psi_{\exp(x)}` (with fixed subscript) is a smooth map from the Lie group +# :math:`\mathcal{G}` to itself, so that we may differentiate it. This leads to the +# differential :math:`\text{Ad}_{\exp(x)}=d\Psi_{\exp(x)},` which maps the tangent spaces of +# :math:`\mathcal{G}` to itself. At the identity, where +# the algebra :math:`\mathfrak{g}` forms the tangent space, we find +# +# .. math:: +# +# \text{Ad} : \mathcal{G} \times\mathfrak{g} \to \mathfrak{g}, +# \ (\exp(x), y)\mapsto \exp(x) y \exp(-x). +# +# This is the adjoint action of :math:`\mathcal{G}` on :math:`\mathfrak{g}` as we +# introduced above. +# +# Now that we have the adjoint action of :math:`\mathcal{G}` on :math:`\mathfrak{g},` +# we can differentiate it with respect to the subscript argument: +# +# .. math:: +# +# \text{ad}_{\circ}(y)&=d\text{Ad}_\circ(y),\\ +# \text{ad}: \mathfrak{g}\times \mathfrak{g}&\to\mathfrak{g}, +# \ (x, y)\mapsto \text{ad}_x(y) = [x, y]. +# +# It is a non-trivial observation that this differential equals the commutator! +# With :math:`\text{ad}` we arrived at a map that *represents* the action of an algebra element +# :math:`x` on the vector space that is the algebra itself. That is, we found the +# *adjoint representation* of :math:`\mathfrak{g}.` +# +# Finally, note that the adjoint identity can be proven with similar tools as above, +# i.e., chaining derivatives and exponentiation suitably. +# +# Symmetric spaces +# ---------------- +# +# Symmetric spaces are a popular field of study both in physics and mathematics. +# We will not go into depth regarding their interpretation or classification, but refer the +# interested reader to the broad existing literature, including [#arvanitogeorgos]_ and +# [#helgason]_. +# In the following, we mostly care about the algebraic structure of symmetric spaces. +# +# Subalgebras and Cartan decompositions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# A *subalgebra* :math:`\mathfrak{k}` of a Lie algebra :math:`\mathfrak{g}` is a +# vector subspace that is closed under the Lie bracket. Overall, this means that +# :math:`\mathfrak{k}` is closed under addition, scalar multiplication, and the Lie bracket. +# The latter often is simply written as :math:`[\mathfrak{k}, \mathfrak{k}]\subset \mathfrak{k}.` +# +# The algebras we are interested in come with an *inner product* between its elements. +# For our purposes, it is sufficient to assume that it is +# +# .. math:: +# +# \langle x, y\rangle = \text{tr}[x^\dagger y]. +# +# Let's implement the inner product and an orthogonality check based on it: +# + + +def inner_product(op1, op2): + """Compute the trace inner product between two operators.""" + # Use two wires to reuse it in the second example on two qubits later on + return qml.math.trace(qml.matrix(qml.adjoint(op1) @ op2, wire_order=[0, 1])) + + +def is_orthogonal(op, basis): + """Check whether an operator is orthogonal to a space given by some basis.""" + return np.allclose([inner_product(op, basis_op) for basis_op in basis], 0) + + +###################################################################### +# Given a subalgebra :math:`\mathfrak{k}\subset \mathfrak{g},` the inner product allows +# us to define an orthogonal complement +# +# .. math:: +# +# \mathfrak{p} = \{x\in\mathfrak{g} | \langle x, y\rangle=0 \ \forall \ y\in\mathfrak{k}\}. +# +# In this context, :math:`\mathfrak{k}` is commonly called the *vertical space*, +# :math:`\mathfrak{p}` accordingly is the *horizontal space*. +# The KAK decomposition will apply to scenarios in which these spaces satisfy additional +# commutation relations, which do not hold for all subalgebras: +# +# .. math:: +# +# [\mathfrak{k}, \mathfrak{p}] \subset& \mathfrak{p} \qquad \text{(Reductive property)},\\ +# [\mathfrak{p}, \mathfrak{p}] \subset& \mathfrak{k} \qquad \text{(Symmetric property)}. +# +# The first property tells us that :math:`\mathfrak{p}` is left intact by the adjoint action of +# :math:`\mathfrak{k}.` The second property suggests that :math:`\mathfrak{p}` behaves like the +# *opposite* of a subalgebra, i.e., all commutators lie in its complement, the subalgebra +# :math:`\mathfrak{k}.` Due to the adjoint identity from above, the first property also holds for +# group elements acting on algebra elements; for all :math:`x\in\mathfrak{p}` and +# :math:`K\in\mathcal{K}=\exp(\mathfrak{k}),` we have +# +# .. math:: +# +# K x K^\dagger +# = \exp(y) x \exp(-y) +# = \text{Ad}_{\exp(y)}(x) +# = \exp(\text{ad}_y) (x) +# = \sum_{n=0}^\infty \frac{1}{n!} \underset{\in\mathfrak{p}}{\underbrace{(\text{ad}_y)^n (x)}} +# \in \mathfrak{p}. +# +# If the reductive property holds, the quotient space :math:`\mathcal{G}/\mathcal{K}` of the groups +# of :math:`\mathfrak{g}` and :math:`\mathfrak{k}` (see detail box below) is called a +# *reductive homogeneous space*. If both properties hold, :math:`(\mathfrak{k}, \mathfrak{p})` is +# called a *Cartan pair* and we call :math:`\mathfrak{g}=\mathfrak{k} \oplus \mathfrak{p}` a +# *Cartan decomposition*. :math:`(\mathfrak{g}, \mathfrak{k})` is named a *symmetric pair* +# and the quotient :math:`\mathcal{G}/\mathcal{K}` is a *symmetric space*. +# Symmetric spaces are relevant for a wide range of applications in physics +# and have been studied a lot throughout the last hundred years. +# +# .. admonition:: Nomenclature: Cartan decomposition/pair +# :class: warning +# +# Depending on context and field, there sometimes is an additional requirement +# for :math:`\mathfrak{g}=\mathfrak{k}\oplus\mathfrak{p}` to be called a Cartan decomposition. +# Without going into detail, this requirement is that the so-called *Killing form* must be +# negative definite on :math:`\mathfrak{k}` and positive definite on :math:`\mathfrak{p}` +# [#helgason]_. +# +# .. admonition:: Math detail: quotient space +# :class: note +# +# The *quotient space* of a Lie group :math:`\mathcal{G}` and a subgroup :math:`\mathcal{K}` +# is the space of cosets of :math:`\mathcal{K},` i.e., +# :math:`\mathcal{G}/\mathcal{K} = \{g\mathcal{K} | g\in G\}.` In this space, two elements are +# equal if they just differ by multiplying an element from :math:`\mathcal{K}` from the left +# to one of them. The quotient space is a manifold like the two groups :math:`\mathcal{G}` and +# :math:`\mathcal{K},` but in general it will *not* be a group itself. For example, a product +# of two elements is +# :math:`(g'\mathcal{K})(g\mathcal{K})=g'g(g^{-1} \mathcal{K} g) \mathcal{K},` which only is +# a coset again if :math:`g^{-1} \mathcal{K} g\subset \mathcal{K}.` Subgroups for which this +# condition holds for any :math:`g\in \mathcal{G}` are called *normal subgroups*. +# We are interested in cases where the symmetric property +# :math:`[\mathfrak{p}, \mathfrak{p}] \subset \mathfrak{k}` holds, which excludes (non-Abelian) +# normal subgroups, and thus our quotient space :math:`\mathcal{G}/\mathcal{K}` will not be +# a group. +# +# **Example** +# +# For our example, we consider the subalgebra :math:`\mathfrak{k}=\mathfrak{u}(1)` +# of :math:`\mathfrak{su}(2)` that generates Pauli-:math:`Z` rotations: +# +# .. math:: +# +# \mathfrak{k} = \text{span}_{\mathbb{R}} \{iZ\}. +# +# Let us define it in code, and check whether it gives rise to a Cartan decomposition. +# As we want to look at another example later, we wrap everything in a function. +# + + +def check_cartan_decomposition(g, k, space_name): + """Given an algebra g and an operator subspace k, verify that k is a subalgebra + and gives rise to a Cartan decomposition.""" + # Check Lie closure of k + k_lie_closure = qml.pauli.dla.lie_closure(k) + k_is_closed = len(k_lie_closure) == len(k) + print(f"The Lie closure of k is as big as k itself: {k_is_closed}.") + + # Orthogonal complement of k, assuming that everything is given in the same basis. + p = [g_op for g_op in g if is_orthogonal(g_op, k)] + print( + f"k has dimension {len(k)}, p has dimension {len(p)}, which combine to " + f"the dimension {len(g)} of g: {len(k)+len(p)==len(g)}" + ) + + # Check reductive property + k_p_commutators = [qml.commutator(k_op, p_op) for k_op, p_op in product(k, p)] + k_p_coms_in_p = all([is_orthogonal(com, k) for com in k_p_commutators]) + + print(f"All commutators in [k, p] are in p (orthogonal to k): {k_p_coms_in_p}.") + if k_p_coms_in_p: + print(f"{space_name} is a reductive homogeneous space.") + + # Check symmetric property + p_p_commutators = [qml.commutator(*ops) for ops in combinations(p, r=2)] + p_p_coms_in_k = all([is_orthogonal(com, p) for com in p_p_commutators]) + + print(f"All commutators in [p, p] are in k (orthogonal to p): {p_p_coms_in_k}.") + if p_p_coms_in_k: + print(f"{space_name} is a symmetric space.") + + return p + + +u1 = [Z(0)] +space_name = "SU(2)/U(1)" +p = check_cartan_decomposition(su2, u1, space_name) + +###################################################################### +# Cartan subalgebras +# ~~~~~~~~~~~~~~~~~~ +# +# The symmetric property of a Cartan decomposition +# :math:`([\mathfrak{p}, \mathfrak{p}]\subset\mathfrak{k})` tells us that :math:`\mathfrak{p}` +# is *very far* from being a subalgebra (commutators never end up in :math:`\mathfrak{p}` again). +# This also gives us information about potential subalgebras *within* :math:`\ \mathfrak{p}.` +# Assume we have a subalgebra :math:`\mathfrak{a}\subset\mathfrak{p}.` Then the commutator +# between any two elements :math:`x, y\in\mathfrak{a}` must satisfy +# +# .. math:: +# +# [x, y] \in \mathfrak{a} \subset \mathfrak{p} +# &\Rightarrow [x, y]\in\mathfrak{p} \text{(subalgebra property)}, \\ +# [x, y] \in [\mathfrak{a}, \mathfrak{a}] \subset [\mathfrak{p}, \mathfrak{p}] +# \subset \mathfrak{k} &\Rightarrow [x, y]\in\mathfrak{k}\ \text{(symmetric property)}. +# +# That is, the commutator must lie in *both* orthogonal complements :math:`\mathfrak{k}` and +# :math:`\mathfrak{p},` which only have the zero vector in common. This tells us that *all* +# commutators in :math:`\mathfrak{a}` vanish, making it an *Abelian* subalgebra: +# +# .. math:: +# +# [\mathfrak{a}, \mathfrak{a}] = \{0\}. +# +# Such an Abelian subalgebra is a (horizontal) *Cartan subalgebra (CSA)* if it is *maximal*, +# i.e., if it can not be made any larger (higher-dimensional) without leaving :math:`\mathfrak{p}.` +# +# .. admonition:: Nomenclature: Cartan subalgebra +# :class: warning +# +# Depending on context and field, there are inequivalent notions of Cartan subalgebras. +# In particular, there is a common notion of Cartan subalgebras which are not contained +# in a horizontal space. Throughout this demo, we always mean a *horizontal* +# maximal Abelian subalgebra :math:`\mathfrak{a}\subset\mathfrak{p}.` The two notions +# can be made compatible by being precise about the space of which the subalgebra is a CSA. +# +# How many different CSAs are there? Given a CSA :math:`\mathfrak{a},` we can pick a vertical +# element :math:`y\in\mathfrak{k}` and apply the corresponding group element :math:`K=\exp(y)` to +# all elements of the CSA, using the adjoint action we studied above. This will yield a valid +# CSA again: First, :math:`K\mathfrak{a} K^\dagger` remains in :math:`\mathfrak{p}` +# due to the reductive property, as we discussed when introducing the Cartan decomposition. +# Second, the adjoint action will not change the Abelian property because +# +# .. math:: +# +# [K x_1 K^\dagger, K x_2 K^\dagger] = K [x_1, x_2] K^\dagger = K 0 K^\dagger = 0 +# \quad \forall\ x_{1, 2}\in\mathfrak{a}. +# +# Finally, we are guaranteed that :math:`K\mathfrak{a} K^\dagger` remains maximal: +# +# .. admonition:: Math detail: CSAs remain maximal +# :class: note +# +# The reason that :math:`K\mathfrak{a} K^\dagger` is maximal if :math:`\mathfrak{a}` was, is +# that we assume :math:`\mathfrak{g}` to be a semisimple Lie algebra, for which the +# adjoint representation is faithful. This in turn implies that linearly +# independent elements of :math:`\mathfrak{g}` will not be mapped to linearly dependent +# elements by the adjoint action of :math:`K.` +# +# For most :math:`y\in\mathfrak{k},` applying :math:`K=\exp(y)` in this way will yield a +# *different* CSA, so that we find a whole continuum of them. +# It turns out that they *all* can be found by starting with *any* +# :math:`\mathfrak{a}` and applying all of :math:`\mathcal{K}` to it. +# +# *This is what powers the KAK decomposition.* +# +# **Example** +# +# For our example, we established the decomposition +# :math:`\mathfrak{su}(2)=\mathfrak{u}(1)\oplus \mathfrak{p}` with the two-dimensional horizontal +# space :math:`\mathfrak{p} = \text{span}_{\mathbb{R}}\{iX, iY\}.` Starting with the subspace +# :math:`\mathfrak{a}=\text{span}_{\mathbb{R}} \{iY\},` we see that we immediately reach a maximal Abelian +# subalgebra (a CSA), because :math:`[Y, X]\neq 0.` Applying a rotation +# :math:`\exp(i\eta Z)\in\mathcal{K}` to this CSA gives us a new CSA via +# +# .. math:: +# +# \mathfrak{a}'=\{\exp(i\eta Z) (c iY) \exp(-i\eta Z) | c\in\mathbb{R}\} +# =\{c\cos(2\eta) iY + c\sin(2\eta) iX | c\in\mathbb{R}\} . +# +# The vertical group element :math:`\exp(i\eta Z)` simply rotates the CSA within +# :math:`\mathfrak{p}.` Let us not forget to define the CSA in code. + +# CSA generator: iY +a = p[1] + +# Rotate CSA by applying some vertical group element exp(i eta Z) +eta = 0.6 +# The factor -2 compensates the convention -1/2 in the RZ gate +a_prime = qml.RZ(-2 * eta, 0) @ a @ qml.RZ(2 * eta, 0) + +# Expectation from our theoretical calculation +a_prime_expected = np.cos(2 * eta) * a + np.sin(2 * eta) * p[0] +a_primes_equal = np.allclose(qml.matrix(a_prime_expected), qml.matrix(a_prime)) +print(f"The rotated CSAs match between numerics and theory: {a_primes_equal}") + +###################################################################### +# Cartan involutions +# ~~~~~~~~~~~~~~~~~~ +# +# In practice, there often is a more convenient way to obtain a Cartan decomposition +# than by specifying the subalgebra :math:`\mathfrak{k}` or its horizontal counterpart +# :math:`\mathfrak{p}` manually. It goes as follows. +# +# We will look at a map :math:`\theta` from the total Lie algebra :math:`\mathfrak{g}` +# to itself. We demand that :math:`\theta` has the following properties, for +# :math:`x, y\in\mathfrak{g}` and :math:`c\in\mathbb{R}.` +# +# #. It is linear, i.e., :math:`\theta(x + cy)=\theta(x) +c \theta(y),` +# #. It is compatible with the commutator, i.e., :math:`\theta([x, y])=[\theta(x),\theta(y)],` and +# #. It is an *involution*, i.e., :math:`\theta(\theta(x)) = x,` +# or :math:`\theta^2=\mathbb{I}_{\mathfrak{g}}.` +# +# In short, we demand that :math:`\theta` be an *involutive automorphism* of :math:`\mathfrak{g}.` +# +# As an involution, :math:`\theta` only can have the eigenvalues :math:`\pm 1,` with associated +# eigenspaces :math:`\mathfrak{g}_\pm.` Let's see what happens when we compute commutators between +# elements :math:`x_\pm\in\mathfrak{g}_\pm \Leftrightarrow \theta(x_\pm) = \pm x_\pm:` +# +# .. math:: +# +# &\theta([x_+, x_+]) = [\theta(x_+), \theta(x_+)] = [x_+, x_+] +# &\ \Rightarrow\ [x_+, x_+]\in\mathfrak{g}_+ ,\\ +# &\theta([x_+, x_-]) = [\theta(x_+), \theta(x_-)] = -[x_+, x_-] +# &\ \Rightarrow\ [x_+, x_-]\in\mathfrak{g}_- ,\\ +# &\theta([x_-, x_-]) = [\theta(x_-), \theta(x_-)] = (-1)^2 [x_-, x_-] +# &\ \Rightarrow\ [x_-, x_-]\in\mathfrak{g}_+. +# +# Or, in other words, +# :math:`[\mathfrak{g}_+, \mathfrak{g}_+] \subset \mathfrak{g}_+,` +# :math:`[\mathfrak{g}_+, \mathfrak{g}_-] \subset \mathfrak{g}_-,` +# and :math:`[\mathfrak{g}_-, \mathfrak{g}_-] \subset \mathfrak{g}_+.` +# So an involution is enough to find us a Cartan decomposition, with +# :math:`\mathfrak{k}=\mathfrak{g}_+` and :math:`\mathfrak{p}=\mathfrak{g}_-.` +# +# 🤯 +# +# We might want to call such a :math:`\theta` a *Cartan involution*. +# +# .. admonition:: Nomenclature: Cartan involution +# :class: warning +# +# Some people do so, some people again require more properties for such an +# involution to be called Cartan involution. +# For our purposes, let's go with the more general definition and call all +# involutions with the properties above Cartan involutions. +# +# Conversely, if we have a Cartan decomposition based on a subalgebra :math:`\mathfrak{k},` +# we can define the map +# +# .. math:: +# +# \theta_{\mathfrak{k}}(x) = \Pi_{\mathfrak{k}}(x)-\Pi_{\mathfrak{p}}(x), +# +# where :math:`\Pi_{\mathfrak{k},\mathfrak{p}}` are the projectors onto the two vector +# subspaces. Clearly, :math:`\theta_{\mathfrak{k}}` is linear because projectors are. +# It is also compatible with the commutator due to the commutation relations +# between :math:`\mathfrak{k}` and :math:`\mathfrak{p}` (see box below). +# Finally, :math:`\theta_{\mathfrak{k}}` is an involution because +# +# .. math:: +# +# \theta_{\mathfrak{k}}^2=(\Pi_{\mathfrak{k}}-\Pi_{\mathfrak{p}})^2 +# = \Pi_{\mathfrak{k}}^2-\Pi_{\mathfrak{k}}\Pi_{\mathfrak{p}} +# -\Pi_{\mathfrak{p}}\Pi_{\mathfrak{k}}+\Pi_{\mathfrak{p}}^2 +# =\Pi_{\mathfrak{k}}+\Pi_{\mathfrak{p}} +# = \mathbb{I}_{\mathfrak{g}}, +# +# where we used the projectors' property :math:`\Pi_{\mathfrak{k}}^2=\Pi_{\mathfrak{k}}` and +# :math:`\Pi_{\mathfrak{p}}^2=\Pi_{\mathfrak{p}},` as well as the fact that +# :math:`\Pi_{\mathfrak{k}}\Pi_{\mathfrak{p}}=\Pi_{\mathfrak{p}}\Pi_{\mathfrak{k}}=0` because +# the spaces :math:`\mathfrak{k}` and :math:`\mathfrak{p}` are orthogonal to each other. +# +# .. admonition:: Math detail: :math:`\theta_{\mathfrak{k}}` is a homomorphism +# :class: note +# +# To see that :math:`\theta_{\mathfrak{k}}` is compatible with the commutator, i.e., +# an algebra homomorphism, see how a commutator :math:`[x, y]` splits: +# +# .. math:: +# +# [x, y] +# &= [\Pi_{\mathfrak{k}}(x) + \Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y) + \Pi_{\mathfrak{p}}(y)] \\ +# &= \underset{\in \mathfrak{k}}{\underbrace{[\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{k}}(y)]}} +# +\underset{\in \mathfrak{p}}{\underbrace{[\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{p}}(y)]}} +# +\underset{\in \mathfrak{p}}{\underbrace{[\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y)]}} +# +\underset{\in \mathfrak{k}}{\underbrace{[\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{p}}(y)]}}, +# +# where we used :math:`\mathbb{I}_{\mathfrak{g}} = \Pi_{\mathfrak{k}} + \Pi_{\mathfrak{p}}` and the +# commutation relations between :math:`\mathfrak{k}` and :math:`\mathfrak{p}.` +# We can use this split to compute +# +# .. math:: +# +# \theta_{\mathfrak{k}} ([x, y]) +# &=\Pi_{\mathfrak{k}}([x, y]) - \Pi_{\mathfrak{p}}([x, y])\\ +# &=[\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{k}}(y)] + [\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{p}}(y)] +# - [\Pi_{\mathfrak{k}}(x), \Pi_{\mathfrak{p}}(y)] - [\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y)]\\ +# &=[\Pi_{\mathfrak{k}}(x) -\Pi_{\mathfrak{p}}(x), \Pi_{\mathfrak{k}}(y)-\Pi_{\mathfrak{p}}(y)]\\ +# &=[\theta_{\mathfrak{k}} (x),\theta_{\mathfrak{k}} (y)]. +# +# Thus, :math:`\theta_{\mathfrak{k}}` indeed is compatible with the commutator. +# +# This shows us that we can easily switch between a Cartan involution and a Cartan +# decomposition, in either direction! +# +# **Example** +# +# In our example, an involution that reproduces our choice +# :math:`\mathfrak{k}=\text{span}_{\mathbb{R}} \{iZ\}` is :math:`\theta_Z(x) = Z x Z` +# (convince yourself that it is an involution that respects commutators, or verify that +# it matches :math:`\theta_{\mathfrak{k}}` from above). + + +def theta_Z(x): + return qml.simplify(Z(0) @ x @ Z(0)) + + +theta_of_u1 = [theta_Z(x) for x in u1] +u1_is_su2_plus = all(qml.equal(x, theta_of_x) for x, theta_of_x in zip(u1, theta_of_u1)) +print(f"U(1) is the +1 eigenspace: {u1_is_su2_plus}") + +theta_of_p = [theta_Z(x) for x in p] +p_is_su2_minus = all(qml.equal(-x, theta_of_x) for x, theta_of_x in zip(p, theta_of_p)) +print(f"p is the -1 eigenspace: {p_is_su2_minus}") + +###################################################################### +# We can easily get a new subalgebra by modifying the involution, say, to +# :math:`\theta_Y(x) = Y x Y,` expecting that +# :math:`\mathfrak{k}_Y=\text{span}_{\mathbb{R}} \{iY\}` becomes the new subalgebra. + + +def theta_Y(x): + return qml.simplify(Y(0) @ x @ Y(0)) + + +eigvals = [] +for x in su2: + if qml.equal(theta_Y(x), x): + eigvals.append(1) + elif qml.equal(theta_Y(x), -x): + eigvals.append(-1) + else: + raise ValueError("Operator not purely in either eigenspace.") + +print(f"Under theta_Y, the operators\n{su2}\nhave the eigenvalues\n{eigvals}") + +###################################################################### +# This worked! A new involution gave us a new subalgebra and Cartan decomposition. +# +# .. admonition:: Math detail: classification of Cartan decompositions +# :class: note +# +# You might already see that the two different decompositions created by :math:`\theta_Z` +# and :math:`\theta_Y` are very similar. There is a whole field of study that +# characterizes---and even fully classifies---the possible Cartan decompositions +# of semisimple Lie algebras. This classification +# plays a big role when talking about decompositions without getting stuck on details +# like the choice of basis or the representation of the algebra as matrices. +# For example, there are only three types of Cartan decompositions of the special unitary +# algebra :math:`\mathfrak{su}(n),` called AI, AII, and AIII. The subalgebras +# :math:`\mathfrak{k}` for these decompositions are the special orthogonal algebra +# :math:`\mathfrak{so}(n)` (AI), the unitary symplectic algebra :math:`\mathfrak{sp}(n)` (AII), +# and a sum of (special) unitary algebras +# :math:`\mathfrak{su}(p)\oplus\mathfrak{su}(q)\oplus\mathfrak{u}(1)` (AIII, :math:`p+q=n`). +# For a quick overview, see for example the `Wikipedia entry on symmetric spaces +# `__. +# Their involutions are usually represented by complex conjugation (AI), by the adjoint +# action with a Pauli operator (AIII, for qubits, :math:`p=q=2^{N-1}`), or by both +# (AII). It is instructive to try and see why those three are *not* equivalent +# under a unitary basis change! +# +# The KAK decomposition +# --------------------- +# +# Now that we covered all prerequisites, we are ready for our main result. It consists of two +# steps that are good to know individually, so we will look at both of them in sequence. +# We will not conduct formal proofs but leave those to the literature references. +# In the following, let :math:`\mathfrak{g}` be a compact real semisimple Lie algebra and +# :math:`\mathfrak{k}` a subalgebra such that :math:`\mathfrak{g}=\mathfrak{k}\oplus \mathfrak{p}` +# is a Cartan decomposition. +# +# The first step is a decomposition of the Lie group :math:`\mathcal{G}=\exp(\mathfrak{g})` +# into the Lie subgroup +# :math:`\mathcal{K}=\exp(\mathfrak{k})` and the exponential of the horizontal space, +# :math:`\mathcal{P}=\exp(\mathfrak{p}),` *which is not a group* (see box on quotient spaces). +# The decomposition is a simple product within :math:`\mathcal{G}:` +# +# .. math:: +# +# \mathcal{G} &= \mathcal{K}\mathcal{P}, \text{ or }\\ +# \forall\ G\in\mathcal{G}\ \ \exists K\in\mathcal{K}, x\in\mathfrak{p}: \ G &= K \exp(x). +# +# This *KP* decomposition can be seen as the *group version* of +# :math:`\mathfrak{g} = \mathfrak{k} \oplus\mathfrak{p}` and is known as a *global* Cartan +# decomposition of :math:`\mathcal{G}.` +# +# The second step is the further decomposition of the space :math:`\mathcal{P}=\exp(\mathfrak{p}).` +# For this we first need to fix a Cartan subalgebra (CSA) :math:`\mathfrak{a}\subset\mathfrak{p}.` +# The CSA might be given through some application or from context, but there is no +# canonical choice. +# Given a horizontal vector :math:`x\in\mathfrak{p},` we can always construct a second CSA +# :math:`\mathfrak{a}_x\subset\mathfrak{p}` that contains :math:`x.` As any two CSAs can be mapped +# to each other by some subalgebra element :math:`y\in\mathfrak{k}` using the adjoint action :math:`\text{Ad},` +# we know that a :math:`y` exists such that +# +# .. math:: +# +# \exp(y)\mathfrak{a}_x\exp(-y)=\mathfrak{a} +# \quad\Rightarrow\quad x\in(\exp(-y) \mathfrak{a}\exp(y). +# +# Generalizing this statement across all horizontal elements :math:`x\in\mathfrak{p},` we find +# +# .. math:: +# +# \mathfrak{p} \subset \{\exp(-y) \mathfrak{a} \exp(y) | y\in\mathfrak{k}\}. +# +# As we discussed, the converse inclusion also must hold for a reductive space, so that we +# may even replace :math:`\subset` by an equality. +# Now we can use :math:`\exp(\text{Ad}_{K} x)=\text{Ad}_{K}\exp(x)` to move +# this statement to the group level, +# +# .. math:: +# +# \mathcal{P} +# =\exp(\mathfrak{p}) +# = \{\exp(\exp(-y) \mathfrak{a} \exp(y)) | y\in\mathfrak{k}\} +# = \{\exp(K^{-1} \mathfrak{a} K) | K\in\mathcal{K}\} +# = \{K^{-1} \mathcal{A} K | K\in\mathcal{K}\}, +# +# where we abbreviated :math:`\mathcal{A} = \exp(\mathfrak{a}).` +# +# Chaining the two steps together and combining the left factor :math:`K^{-1}` with the group +# :math:`\mathcal{K}` in the *KP* decomposition, we obtain the *KAK decomposition* +# +# .. math:: +# +# \mathcal{G} +# =\mathcal{K}\mathcal{P} +# = \mathcal{K}\{K^{-1} \mathcal{A} K | K\in\mathcal{K}\}, +# = \{K_1 \mathcal{A} K_2 | K_{1,2}\in\mathcal{K}\}, +# = \mathcal{K} \mathcal{A} \mathcal{K} \qquad\textbf{(KAK Decomposition).} +# +# It teaches us that any group element can be decomposed into two factors from the Lie subgroup and +# the exponential of a CSA element, i.e., of commuting elements from the horizontal subspace +# :math:`\mathfrak{p}.` This may already hint at the usefulness of the KAK decomposition for matrix +# factorizations in general, and for quantum circuit decompositions in particular. +# Given a group operation :math:`G=\exp(x)` with :math:`x\in\mathfrak{g},` there are two +# subalgebra elements :math:`y_{1,2}\in\mathfrak{k}` (or subgroup elements +# :math:`K_{1,2}=\exp(y_{1,2})\in \mathcal{K}`) and a Cartan subgalgebra element +# :math:`a\in\mathfrak{a}` so that +# +# .. math:: +# +# G\in\mathcal{G} \quad\Rightarrow\quad G=K_1 \exp(a) K_2. +# +# If :math:`x` happens to be from the horizontal subspace :math:`\mathfrak{p},` so that +# :math:`G\in \mathcal{P}\subset\mathcal{G},` we know that the two subgroup elements :math:`K_1` +# and :math:`K_2` will in fact be related, namely +# +# .. math:: +# +# G\in\mathcal{P} \quad\Rightarrow\quad G=K\exp(a)K^\dagger. +# +# **Example** +# +# Applying what we just learned to our example on :math:`\mathfrak{su}(2),` we can state that +# any single-qubit gate can be implemented by running a gate from +# :math:`\mathcal{K}=\{\exp(i\eta Z) | \eta\in\mathbb{R}\},` a CSA gate +# :math:`\mathcal{A}=\{\exp(i\varphi Y) | \eta\in\mathbb{R}\},` and another gate from +# :math:`\mathcal{K}.` We rediscovered a standard decomposition of an arbitrary +# :math:`SU(2)` gate! PennyLane produces it with :func:`~.pennylane.ops.one_qubit_decomposition`: + +x = 0.2j * su2[0] - 0.1j * su2[1] - 0.2j * su2[2] +G = qml.math.linalg.expm(qml.matrix(x)) +print(qml.ops.one_qubit_decomposition(G, 0, rotations="ZYZ")) + +###################################################################### +# If we pick a *horizontal gate*, i.e., a gate :math:`G\in\mathcal{P}`, we obtain the same +# rotation angle for the initial and final :math:`R_Z` rotations, up to the expected sign, and +# a shift by some multiple of :math:`2\pi.` + +horizontal_x = -0.1j * p[0] - 4.1j * p[1] +print(horizontal_x) +P = qml.math.linalg.expm(qml.matrix(horizontal_x)) +decomp = qml.ops.one_qubit_decomposition(P, 0, rotations="ZYZ") +print(decomp) +angle_match = np.isclose((decomp[0].data[0] + decomp[-1].data[0]) % (2 * np.pi), 0.0) +print(f"First and last rotation angle match up to sign and shift by 2kπ: {angle_match}") + +###################################################################### +# Other choices for involutions or---equivalently---subalgebras :math:`\mathfrak{k}` will +# lead to other decompositions of ``Rot``. For example, using :math:`\theta_Y` from above +# together with the CSA :math:`\mathfrak{a}_Y=\text{span}_{\mathbb{R}} \{iX\},` we find the +# decomposition +# +# .. math:: +# +# \text{Rot}(\phi, \theta, \omega) = R_Y(\eta_1) R_X(\vartheta) R_Y(\eta_2). +# +# And that's it for our main discussion. We conclude this demo by applying the +# KAK decomposition to the group of arbitrary two-qubit gates. +# +# Application: Two-qubit gate decomposition +# ----------------------------------------- +# +# Two-qubit operations are described by the special unitary group :math:`SU(4)` and +# here we will use a decomposition of its algebra :math:`\mathfrak{su}(4)` to decompose +# such gates. +# Specifically, we use the subalgebra that generates single-qubit operations independently +# on either qubit, :math:`\mathfrak{su}(2)\oplus\mathfrak{su}(2).` Let's set it up with our +# tool from earlier: + +# Define su(4). Skip first entry of Pauli group, which is the identity +su4 = list(qml.pauli.pauli_group(2))[1:] +print(f"su(4) is {len(su4)}-dimensional") + +# Define subalgebra su(2) ⊕ su(2) +su2_su2 = [X(0), Y(0), Z(0), X(1), Y(1), Z(1)] +space_name = "SU(4)/(SU(2)xSU(2))" +p = check_cartan_decomposition(su4, su2_su2, space_name) + +###################################################################### +# .. admonition:: Math detail: involution for two-qubit decomposition +# :class: note +# +# The accompanying involution sorts operators by the number of qubits on which they are +# supported (:math:`\mathfrak{k}` is supported on one, :math:`\mathfrak{p}` on two). +# This can be realized with the operation +# +# .. math:: +# +# \theta(x) = -Y_0Y_1 x^T Y_0Y_1. +# +# Intuitively, the conjugation by :math:`Y_0Y_1` adds a minus +# sign for each :math:`X` and :math:`Z` factor in :math:`x,` and the transposition +# adds a minus sign for each :math:`Y.` Taken together, each Pauli operator contributes +# a minus sign. Finally, as we want the single-qubit operators to receive no sign in total +# (:math:`\mathfrak{k}` is the :math:`+1` eigenspace), we add a minus sign overall. +# +# Now we can pick a Cartan subalgebra within :math:`\mathfrak{p},` the vector space +# of all two-qubit Paulis. A common choice is +# +# .. math:: +# +# \mathfrak{a} = \text{span}_{\mathbb{R}}\{iX_0X_1, iY_0Y_1, iZ_0Z_1\}. +# +# These three operators commute, making :math:`\mathfrak{a}` Abelian. +# They also form a *maximal* Abelian algebra within :math:`\mathfrak{p},` which is less obvious. +# +# The KAK decomposition now tells us that any two-qubit gate :math:`U,` being part of +# :math:`SU(4),` can be implemented by a sequence +# +# .. math:: +# +# U &= \exp(y_1) \exp(a)\exp(y_2)\\ +# &= \exp(i[\varphi^x_0 X_0 + \varphi^y_0 Y_0 + \varphi^z_0 Z_0]) +# \exp(i[\varphi^x_1 X_1 + \varphi^y_1 Y_1 + \varphi^z_1 Z_1])\\ +# &\times \exp(i [\eta^x X_0X_1 + \eta^y Y_0Y_1 + \eta^z Z_0Z_1])\\ +# &\times \exp(i[\vartheta^x_0 X_0 + \vartheta^y_0 Y_0 + \vartheta^z_0 Z_0]) +# \exp(i[\vartheta^x_1 X_1 + \vartheta^y_1 Y_1 + \vartheta^z_1 Z_1]). +# +# Here we decomposed the exponentials of the vertical elements :math:`y_{1,2}` further by +# splitting them into exponentials acting on the first and second qubit, respectively. +# +# The three parameters :math:`\eta^{x, y, z}` sometimes are called the *Cartan coordinates* +# of :math:`U,` and they can be used, e.g., to assess the smallest-possible duration to +# implement the gate in hardware. +# +# With this result, we can implement a template that can create any two-qubit gate. +# We'll use :class:`~.pennylane.Rot` for the single-qubit exponentials (which changes +# the meaning of the angles, but maintains the coverage) and are allowed to +# split the Cartan subalgebra term :math:`\exp(a)` into three exponentials, as its +# terms commute. +# + + +def su4_gate(params): + phi0, phi1, eta, theta0, theta1 = np.split(params, range(3, 15, 3)) + qml.Rot(*phi0, wires=0) + qml.Rot(*phi1, wires=1) + qml.IsingXX(eta[0], wires=[0, 1]) + qml.IsingYY(eta[1], wires=[0, 1]) + qml.IsingZZ(eta[2], wires=[0, 1]) + qml.Rot(*theta0, wires=0) + qml.Rot(*theta1, wires=1) + + +params = np.random.random(15) +fig, ax = qml.draw_mpl(su4_gate, wire_order=[0, 1])(params) + +###################################################################### +# And that's a wrap on our application of the KAK decomposition for two-qubit gates! +# +# You may have noticed that this mathematical result only states the existence of a +# decomposition, but does not provide a constructive way of finding +# :math:`y_{1,2}` and :math:`a` for a given gate :math:`U.` For this, +# some additional work is required, as explained in [#kokcu_fdhs]_, for example. +# +# Conclusion +# ---------- +# +# In this demo we learned about the KAK decomposition and how it breaks up a +# Lie group using the Cartan decomposition of its Lie algebra. +# This allows us to break down arbitrary quantum gates from that group, +# as we implemented in code for the groups of single-qubit and two-qubit gates, +# :math:`SU(2)` and :math:`SU(4).` +# +# If you are interested in other applications of Lie theory in the field of +# quantum computing, you are in luck! It has been a handy tool throughout the last +# decades, e.g., for the simulation of quantum circuits [#somma]_ [#goh]_ and their +# compression [#kokcu_comp]_ [#gu]_, in quantum optimal control [#dirr]_, and for trainability +# analyses [#fontana]_ [#ragone]_. For Lie algebraic classical simulation of quantum circuits, +# also take a look at the :doc:`g-sim ` and +# :doc:`(g+P)-sim ` demos. +# +# References +# ---------- +# +# .. [#hall] +# +# Brian C. Hall +# "Lie Groups, Lie Algebras, and Representations. An Elementary Introduction" +# `Graduate Texts in Mathematics, Springer `__, 2015. +# +# .. [#tu] +# +# Loring W. Tu +# "An Introduction to Manifolds" +# `Universitext, Springer `__, 2011. +# +# .. [#arvanitogeorgos] +# +# Andreas Arvanitogeorgos +# "An Introduction to Lie Groups and the Geometry of Homogeneous Spaces" +# `Student Mathematical Library **22** `__, 2003 +# +# .. [#helgason] +# +# Sigurdur Helgason +# "Differential geometry, Lie groups, and symmetric spaces" +# `Graduate Studies in Mathematics **34** `__, 2001 +# +# .. [#goh] +# +# Matthew L. Goh, Martin Larocca, Lukasz Cincio, M. Cerezo, Frédéric Sauvage +# "Lie-algebraic classical simulations for variational quantum computing" +# `arXiv:2308.01432 `__, 2023. +# +# .. [#somma] +# +# Rolando D. Somma +# "Quantum Computation, Complexity, and Many-Body Physics" +# `arXiv:quant-ph/0512209 `__, 2005. +# +# .. [#kokcu_fdhs] +# +# Efekan Kökcü, Thomas Steckmann, Yan Wang, J. K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper +# "Fixed Depth Hamiltonian Simulation via Cartan Decomposition" +# `arXiv:2104.00728 `__, 2021. +# `PRL (closed access) `__, 2022. +# +# .. [#kokcu_comp] +# +# Efekan Kökcü, Daan Camps, Lindsay Bassman, James K. Freericks, Wibe A. de Jong, Roel Van Beeumen, Alexander F. Kemper +# "Algebraic Compression of Quantum Circuits for Hamiltonian Evolution" +# `arXiv:2108.03282 `__, 2021. +# `PRA (closed access) `__, 2022. +# +# .. [#gu] +# +# Shouzhen Gu, Rolando D. Somma, Burak Şahinoğlu +# "Fast-forwarding quantum evolution" +# `Quantum **5** `__, 2021. +# +# .. [#dirr] +# +# G. Dirr, U. Helmke +# "Lie Theory for Quantum Control" +# `GAMM-Mitteilungen **31** `__, 2008. +# +# .. [#fontana] +# +# Enrico Fontana, Dylan Herman, Shouvanik Chakrabarti, Niraj Kumar, Romina Yalovetzky, Jamie Heredge, Shree Hari Sureshbabu, Marco Pistoia +# "The Adjoint Is All You Need: Characterizing Barren Plateaus in Quantum Ansätze" +# `Nat. Commun. **15** `__, 2024. +# +# .. [#ragone] +# +# Michael Ragone, Bojko N. Bakalov, Frédéric Sauvage, Alexander F. Kemper, Carlos Ortiz Marrero, Martin Larocca, M. Cerezo +# "A Unified Theory of Barren Plateaus for Deep Parametrized Quantum Circuits" +# `Nat. Commun. **15** `__, 2024. +# +# About the author +# ---------------- diff --git a/demonstrations_v2/tutorial_kak_decomposition/metadata.json b/demonstrations_v2/tutorial_kak_decomposition/metadata.json new file mode 100644 index 0000000000..94e43e6012 --- /dev/null +++ b/demonstrations_v2/tutorial_kak_decomposition/metadata.json @@ -0,0 +1,174 @@ +{ + "title": "The KAK decomposition", + "authors": [ + { + "username": "dwierichs" + } + ], + "dateOfPublication": "2024-11-25T00:00:00+00:00", + "dateOfLastModification": "2025-01-07T09:00:00+00:00", + "categories": [ + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_kak_decomposition.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_kak_decomposition.png" + } + ], + "seoDescription": "Learn about the KAK decomposition and how it powers circuit decompositions.", + "doi": "", + "references": [ + { + "id": "hall", + "type": "book", + "title": "Lie Groups, Lie Algebras, and Representations. An Elementary Introduction", + "authors": "Brian C. Hall", + "year": "2015", + "publisher": "Springer", + "journal": "Graduate Texts in Mathematics", + "doi": "10.1007/978-3-319-13467-3", + "url": "https://link.springer.com/book/10.1007/978-3-319-13467-3" + }, + { + "id": "tu", + "type": "book", + "title": "An Introduction to Manifolds", + "authors": "Loring W. Tu", + "year": "2011", + "publisher": "Springer", + "journal": "Universitext", + "doi": "10.1007/978-1-4419-7400-6", + "url": "https://link.springer.com/book/10.1007/978-1-4419-7400-6" + }, + { + "id": "arvanitogeorgos", + "type": "book", + "title": "An Introduction to Lie Groups and the Geometry of Homogeneous Spaces", + "authors": "Andreas Arvanitogeorgos", + "year": "2003", + "publisher": "American Mathematical Society", + "journal": "Student Mathematical Library", + "url": "https://bookstore.ams.org/stml-22" + }, + { + "id": "helgason", + "type": "book", + "title": "Differential geometry, Lie groups, and symmetric spaces", + "authors": "Sigurdur Helgason", + "year": "2001", + "publisher": "American Mathematical Society", + "journal": "Graduate Studies in Mathematics", + "doi": "10.1090/gsm/034", + "url": "https://bookstore.ams.org/gsm-34" + }, + { + "id": "goh", + "type": "preprint", + "title": "lie-algebraic classical simulations for variational quantum computing", + "authors": "matthew l. goh, martin larocca, lukasz cincio, m. cerezo, frédéric sauvage", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2308.01432", + "url": "https://arxiv.org/abs/2308.01432" + }, + { + "id": "somma", + "type": "preprint", + "title": "quantum computation, complexity, and many-body physics", + "authors": "rolando d. somma", + "year": "2005", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.quant-ph/0512209", + "url": "https://arxiv.org/abs/quant-ph/0512209" + }, + { + "id": "kokcu_fdhs", + "type": "article", + "title": "Fixed Depth Hamiltonian Simulation via Cartan Decomposition", + "authors": "Efekan Kökcü, Thomas Steckmann, Yan Wang, J. K. Freericks, Eugene F. Dumitrescu, Alexander F. Kemper", + "year": "2022", + "publisher": "American Physical Society", + "journal": "", + "doi": "10.1103/PhysRevLett.129.070501", + "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.129.070501" + }, + { + "id": "kokcu_comp", + "type": "article", + "title": "Algebraic Compression of Quantum Circuits for Hamiltonian Evolution", + "authors": "Efekan Kökcü, Daan Camps, Lindsay Bassman, James K. Freericks, Wibe A. de Jong, Roel Van Beeumen, Alexander F. Kemper", + "year": "2022", + "publisher": "American Physical Society", + "journal": "", + "doi": "10.1103/PhysRevA.105.032420", + "url": "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.105.032420" + }, + { + "id": "gu", + "type": "article", + "title": "Fast-forwarding quantum evolution", + "authors": "Shouzhen Gu, Rolando D. Somma, Burak Şahinoğlu", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "doi": "10.22331/q-2021-11-15-577", + "url": "https://quantum-journal.org/papers/q-2021-11-15-577/#" + }, + { + "id": "dirr", + "type": "article", + "title": "Lie Theory for Quantum Control", + "authors": "G. Dirr, U. Helmke", + "year": "2008", + "publisher": "Gesellschaft für Angewandte Mathematik und Mechanik", + "journal": "Surveys for Applied Mathematics and Mechanics", + "doi": "10.1002/gamm.200890003", + "url": "https://onlinelibrary.wiley.com/doi/abs/10.1002/gamm.200890003" + }, + { + "id": "fontana", + "type": "article", + "title": "the adjoint is all you need: characterizing barren plateaus in quantum ansätze", + "authors": "enrico fontana, dylan herman, shouvanik chakrabarti, niraj kumar, romina yalovetzky, jamie heredge, shree hari sureshbabu, marco pistoia", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2309.07902", + "url": "https://arxiv.org/abs/2309.07902" + }, + { + "id": "ragone", + "type": "preprint", + "title": "a unified theory of barren plateaus for deep parametrized quantum circuits", + "authors": "michael ragone, bojko n. bakalov, frédéric sauvage, alexander f. kemper, carlos ortiz marrero, martin larocca, m. cerezo", + "year": "2023", + "publisher": "", + "journal": "", + "doi": "10.48550/arxiv.2309.09342", + "url": "https://arxiv.org/abs/2309.09342" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_liealgebra", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_liesim", + "weight": 1.0 + } + ] +} diff --git a/demonstrations_v2/tutorial_kernel_based_training/demo.py b/demonstrations_v2/tutorial_kernel_based_training/demo.py index b24b4bcd1b..b1b8bbfab9 100644 --- a/demonstrations_v2/tutorial_kernel_based_training/demo.py +++ b/demonstrations_v2/tutorial_kernel_based_training/demo.py @@ -687,4 +687,4 @@ def model_evals_nn(n_data, n_params, n_steps, split, batch_size): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/maria_schuld.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_kernels_module/demo.py b/demonstrations_v2/tutorial_kernels_module/demo.py index 8f8ea23d12..83619300b1 100644 --- a/demonstrations_v2/tutorial_kernels_module/demo.py +++ b/demonstrations_v2/tutorial_kernels_module/demo.py @@ -628,21 +628,6 @@ def target_alignment( # `Artificial Intelligence Review 43.2: 179-192 `__, 2015. # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/peter-jan_derks.txt -# -# -# .. include:: ../_static/authors/paul_k_faehrmann.txt -# -# -# .. include:: ../_static/authors/elies_gil-fuster.txt -# -# -# .. include:: ../_static/authors/tom_hubregtsen.txt -# -# -# .. include:: ../_static/authors/johannes_jakob_meyer.txt -# -# -# .. include:: ../_static/authors/david_wierichs.txt \ No newline at end of file +# About the author +# ---------------- +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_lcu_blockencoding/demo.py b/demonstrations_v2/tutorial_lcu_blockencoding/demo.py index a0af2bc4bf..097f640bf2 100644 --- a/demonstrations_v2/tutorial_lcu_blockencoding/demo.py +++ b/demonstrations_v2/tutorial_lcu_blockencoding/demo.py @@ -275,8 +275,6 @@ def lcu_circuit(): # block_encode ############################################################################## -# About the authors -# ----------------- -# .. include:: ../_static/authors/juan_miguel_arrazola.txt -# .. include:: ../_static/authors/jay_soni.txt -# .. include:: ../_static/authors/diego_guala.txt +# About the author +# ---------------- +# diff --git a/demonstrations_v2/tutorial_learning_few_data/demo.py b/demonstrations_v2/tutorial_learning_few_data/demo.py index cb42c92ab7..aa0a1d70eb 100644 --- a/demonstrations_v2/tutorial_learning_few_data/demo.py +++ b/demonstrations_v2/tutorial_learning_few_data/demo.py @@ -596,10 +596,6 @@ def run_iterations(n_train): # `Journal of Open Source Software `__, 2019. # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/luis_mantilla_calderon.txt -# -# .. include:: ../_static/authors/maurice_weber.txt diff --git a/demonstrations_v2/tutorial_learning_from_experiments/demo.py b/demonstrations_v2/tutorial_learning_from_experiments/demo.py index 47ac16b729..729043f63c 100644 --- a/demonstrations_v2/tutorial_learning_from_experiments/demo.py +++ b/demonstrations_v2/tutorial_learning_from_experiments/demo.py @@ -539,4 +539,4 @@ def enhanced_circuit(ts=False): # # About the author # ---------------- -# .. include:: ../_static/authors/joseph_bowles.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_learningshallow/demo.py b/demonstrations_v2/tutorial_learningshallow/demo.py index 9f762f7714..8366492c1d 100644 --- a/demonstrations_v2/tutorial_learningshallow/demo.py +++ b/demonstrations_v2/tutorial_learningshallow/demo.py @@ -465,4 +465,4 @@ def sewing_test(): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/tutorial_liealgebra/demo.py b/demonstrations_v2/tutorial_liealgebra/demo.py index 59e6e2e65b..acb16a942f 100644 --- a/demonstrations_v2/tutorial_liealgebra/demo.py +++ b/demonstrations_v2/tutorial_liealgebra/demo.py @@ -458,4 +458,4 @@ def IsingGenerators(n, bc="open"): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/tutorial_liesim_extension/demo.py b/demonstrations_v2/tutorial_liesim_extension/demo.py index b0f3226e98..b11a640b6e 100644 --- a/demonstrations_v2/tutorial_liesim_extension/demo.py +++ b/demonstrations_v2/tutorial_liesim_extension/demo.py @@ -133,7 +133,7 @@ def TFIM(n): # # For that, let us construct a concrete example. First we pick two elements from :math:`\mathfrak{g}` such that their product is not in :math:`\mathfrak{g}.` -p = dla[-5] @ dla[-2] +p = dla[-5] @ dla[-1] p = next(iter(p)) # strip any scalar coefficients dla_vspace = qml.pauli.PauliVSpace(dla, dtype=complex) dla_vspace.is_independent(p.pauli_rep) @@ -414,9 +414,11 @@ def Moment_step(ops, dla): # # We can now choose arbitrary DLA gates and a maximum of `one` :math:`P` gate to evolve the expectation value vector. +P_index = Moment[comp_moment].index(1.*p) # pick the gate in the Moment space that p corresponds to + e_t = e_in e_t = expm(0.5 * adjoint_repr[dim_g-1]) @ e_t # the same U gate -e_t = expm(0.5 * adjoint_repr[74]) @ e_t # the same P gate +e_t = expm(0.5 * adjoint_repr[P_index]) @ e_t # the same P gate e_t = expm(0.5 * adjoint_repr[dim_g-2]) @ e_t # the same V gate ############################################################################## diff --git a/demonstrations_v2/tutorial_liesim_extension/metadata.json b/demonstrations_v2/tutorial_liesim_extension/metadata.json index c91f1393be..7da2c15c82 100644 --- a/demonstrations_v2/tutorial_liesim_extension/metadata.json +++ b/demonstrations_v2/tutorial_liesim_extension/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-06-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-13T00:00:00+00:00", "categories": [ "Quantum Computing", "Quantum Machine Learning" diff --git a/demonstrations_v2/tutorial_local_cost_functions/demo.py b/demonstrations_v2/tutorial_local_cost_functions/demo.py index f981a9f485..e7781d9eed 100644 --- a/demonstrations_v2/tutorial_local_cost_functions/demo.py +++ b/demonstrations_v2/tutorial_local_cost_functions/demo.py @@ -534,4 +534,4 @@ def cost_tunable(rotations): # # About the author # ---------------- -# .. include:: ../_static/authors/thomas_storwick.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_magic_state_distillation/demo.py b/demonstrations_v2/tutorial_magic_state_distillation/demo.py index eb1bfd6990..a473544da4 100644 --- a/demonstrations_v2/tutorial_magic_state_distillation/demo.py +++ b/demonstrations_v2/tutorial_magic_state_distillation/demo.py @@ -335,4 +335,4 @@ def faulty_T0_state(random_key, r): ###################################################################### # About the author # ---------------- -# .. include:: ../_static/authors/david_ittah.txt +# diff --git a/demonstrations_v2/tutorial_mapping/demo.py b/demonstrations_v2/tutorial_mapping/demo.py index e1f1911189..96a757a506 100644 --- a/demonstrations_v2/tutorial_mapping/demo.py +++ b/demonstrations_v2/tutorial_mapping/demo.py @@ -202,11 +202,14 @@ # coordinates. from pennylane import qchem -from pennylane import numpy as np +from jax import numpy as jnp +import jax + +jax.config.update("jax_enable_x64", True) symbols = ['H', 'H'] -geometry = np.array([[0.0, 0.0, -0.69434785], - [0.0, 0.0, 0.69434785]], requires_grad = False) +geometry = jnp.array([[0.0, 0.0, -0.69434785], + [0.0, 0.0, 0.69434785]]) mol = qchem.Molecule(symbols, geometry) @@ -289,7 +292,7 @@ # Note that we need to exponentiate these operators to be able to use them in the circuit # [#Yordanov]_. We also use a set of pre-defined parameters to construct the excitation gates. -params = np.array([0.22347661, 0.0, 0.0]) +params = jnp.array([0.22347661, 0.0, 0.0]) dev = qml.device("default.qubit", wires=qubits) diff --git a/demonstrations_v2/tutorial_mapping/metadata.json b/demonstrations_v2/tutorial_mapping/metadata.json index f7be98076a..fe0ee177a4 100644 --- a/demonstrations_v2/tutorial_mapping/metadata.json +++ b/demonstrations_v2/tutorial_mapping/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-05-06T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing", diff --git a/demonstrations_v2/tutorial_mbqc/demo.py b/demonstrations_v2/tutorial_mbqc/demo.py index bcbe5d524f..5f16f54ed9 100644 --- a/demonstrations_v2/tutorial_mbqc/demo.py +++ b/demonstrations_v2/tutorial_mbqc/demo.py @@ -806,9 +806,6 @@ def CNOT_MBQC(input_state): # ############################################################################## -# -# About the authors +# About the author # ---------------- -# -# .. include:: ../_static/authors/joost_bus.txt -# .. include:: ../_static/authors/radoica_draskic.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_measurement_optimize/demo.py b/demonstrations_v2/tutorial_measurement_optimize/demo.py index 860a8700e6..198b7d2bfd 100644 --- a/demonstrations_v2/tutorial_measurement_optimize/demo.py +++ b/demonstrations_v2/tutorial_measurement_optimize/demo.py @@ -110,11 +110,13 @@ import functools import warnings -from pennylane import numpy as np +import jax +from jax import numpy as jnp import pennylane as qml +jax.config.update("jax_enable_x64", True) -dataset = qml.data.load('qchem', molname="H2", bondlength=0.7)[0] +dataset = qml.data.load("qchem", molname="H2", bondlength=0.7)[0] H, num_qubits = dataset.hamiltonian, len(dataset.hamiltonian.wires) print("Required number of qubits:", num_qubits) @@ -141,7 +143,7 @@ ) # generate the cost function -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_circuit(params): ansatz(params, wires=range(num_qubits)) return qml.expval(H) @@ -150,7 +152,9 @@ def cost_circuit(params): # If we evaluate this cost function, we can see that it corresponds to 15 different # executions under the hood—one per expectation value: -params = np.random.normal(0, np.pi, len(singles) + len(doubles)) +from jax import random as random +key, scale = random.PRNGKey(0), jnp.pi +params = random.normal(key, shape=(len(singles) + len(doubles),)) * scale with qml.Tracker(dev) as tracker: # track the number of executions print("Cost function value:", cost_circuit(params)) @@ -387,20 +391,20 @@ def cost_circuit(params): dev = qml.device("default.qubit", wires=3) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit1(weights): qml.StronglyEntanglingLayers(weights, wires=range(3)) return qml.expval(obs[0]) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit2(weights): qml.StronglyEntanglingLayers(weights, wires=range(3)) return qml.expval(obs[1]) param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=3) -rng = np.random.default_rng(192933) -weights = rng.normal(scale=0.1, size=param_shape) +key, scale = random.PRNGKey(192933), 0.1 +weights = scale * random.normal(key, shape=param_shape) print("Expectation value of XYI = ", circuit1(weights)) print("Expectation value of XIZ = ", circuit2(weights)) @@ -409,15 +413,15 @@ def circuit2(weights): # Now, let's use our QWC approach to reduce this down to a *single* measurement # of the probabilities in the shared eigenbasis of both QWC observables: -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit_qwc(weights): qml.StronglyEntanglingLayers(weights, wires=range(3)) # rotate wire 0 into the shared eigenbasis - qml.RY(-np.pi / 2, wires=0) + qml.RY(-jnp.pi / 2, wires=0) # rotate wire 1 into the shared eigenbasis - qml.RX(np.pi / 2, wires=1) + qml.RX(jnp.pi / 2, wires=1) # wire 2 does not require a rotation @@ -428,7 +432,6 @@ def circuit_qwc(weights): rotated_probs = circuit_qwc(weights) print(rotated_probs) - ############################################################################## # We're not quite there yet; we have only calculated the probabilities of the variational circuit # rotated into the shared eigenbasis—the :math:`|\langle \phi_n |\psi\rangle|^2.` To recover the @@ -440,12 +443,12 @@ def circuit_qwc(weights): # generate the eigenvalues of the full Pauli terms, making sure that the order # of the eigenvalues in the Kronecker product corresponds to the tensor product. -eigenvalues_XYI = np.kron(np.kron([1, -1], [1, -1]), [1, 1]) -eigenvalues_XIZ = np.kron(np.kron([1, -1], [1, 1]), [1, -1]) +eigenvalues_XYI = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, -1])), jnp.array([1, 1])) +eigenvalues_XIZ = jnp.kron(jnp.kron(jnp.array([1, -1]), jnp.array([1, 1])), jnp.array([1, -1])) # Taking the linear combination of the eigenvalues and the probabilities -print("Expectation value of XYI = ", np.dot(eigenvalues_XYI, rotated_probs)) -print("Expectation value of XIZ = ", np.dot(eigenvalues_XIZ, rotated_probs)) +print("Expectation value of XYI = ", jnp.dot(eigenvalues_XYI, rotated_probs)) +print("Expectation value of XIZ = ", jnp.dot(eigenvalues_XIZ, rotated_probs)) ############################################################################## @@ -455,7 +458,7 @@ def circuit_qwc(weights): # Luckily, PennyLane automatically performs this QWC grouping under the hood. We simply # return the two QWC Pauli terms from the QNode: -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(weights): qml.StronglyEntanglingLayers(weights, wires=range(3)) return [ @@ -606,14 +609,16 @@ def format_pauli_word(term): ]) plt.margins(x=0.1) + coords = nx.spring_layout(G, seed=1) nx.draw( G, + coords, labels={node: format_pauli_word(node) for node in terms}, with_labels=True, node_size=500, font_size=8, node_color="#9eded1", - edge_color="#c1c1c1" + edge_color="#c1c1c1", ) ############################################################################## @@ -621,7 +626,7 @@ def format_pauli_word(term): # version above!): C = nx.complement(G) - coords = nx.spring_layout(C) + coords = nx.spring_layout(C, seed=1) nx.draw( C, @@ -634,7 +639,6 @@ def format_pauli_word(term): edge_color="#c1c1c1" ) - ############################################################################## # Now that we have the complement graph, we can perform a greedy coloring to # determine the minimum number of QWC groups: @@ -711,16 +715,17 @@ def format_pauli_word(term): # However, this isn't strictly necessary—recall previously that the QNode # has the capability to *automatically* measure qubit-wise commuting observables! -dev = qml.device("default.qubit", wires=4) +dev = qml.device("lightning.qubit", wires=4) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(weights, group=None, **kwargs): qml.StronglyEntanglingLayers(weights, wires=range(4)) return [qml.expval(o) for o in group] param_shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=3, n_wires=4) -weights = np.random.normal(scale=0.1, size=param_shape) -result = [circuit(weights, group=g) for g in obs_groupings] +key = random.PRNGKey(1) +weights = random.normal(key, shape=param_shape) * 0.1 +result = [jnp.array(circuit(weights, group=g)) for g in obs_groupings] print("Term expectation values:") for group, expvals in enumerate(result): @@ -728,7 +733,7 @@ def circuit(weights, group=None, **kwargs): # Since all the coefficients of the Hamiltonian are unity, # we can simply sum the expectation values. -print(" = ", np.sum(np.hstack(result))) +print(" = ", jnp.sum(jnp.hstack(result))) ############################################################################## @@ -737,9 +742,9 @@ def circuit(weights, group=None, **kwargs): # problems), we can use the option ``grouping_type="qwc"`` in :class:`~.pennylane.Hamiltonian` to # automatically optimize the measurements. -H = qml.Hamiltonian(coeffs=np.ones(len(terms)), observables=terms, grouping_type="qwc") +H = qml.Hamiltonian(coeffs=jnp.ones(len(terms)), observables=terms, grouping_type="qwc") _, H_ops = H.terms() -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_fn(weights): qml.StronglyEntanglingLayers(weights, wires=range(4)) return qml.expval(H) @@ -790,7 +795,7 @@ def cost_fn(weights): # # Qubit-wise commuting group information for a wide variety of molecules has been # pre-computed, and is available for download in -# in the `PennyLane Datasets library `__. +# in the `PennyLane Datasets library `__. ############################################################################## # References @@ -836,4 +841,4 @@ def cost_fn(weights): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# diff --git a/demonstrations_v2/tutorial_measurement_optimize/metadata.json b/demonstrations_v2/tutorial_measurement_optimize/metadata.json index 597ab1dfff..c8e5d19f8c 100644 --- a/demonstrations_v2/tutorial_measurement_optimize/metadata.json +++ b/demonstrations_v2/tutorial_measurement_optimize/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-01-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_mitigation_advantage/demo.py b/demonstrations_v2/tutorial_mitigation_advantage/demo.py index efffb21c68..c3c9bb27dd 100644 --- a/demonstrations_v2/tutorial_mitigation_advantage/demo.py +++ b/demonstrations_v2/tutorial_mitigation_advantage/demo.py @@ -275,4 +275,4 @@ def time_evolution(theta_h, n_layers = 10, obs = qml.PauliZ(4)): # # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/tutorial_mol_geo_opt/demo.py b/demonstrations_v2/tutorial_mol_geo_opt/demo.py index 50f092ddb6..307bea6a5d 100644 --- a/demonstrations_v2/tutorial_mol_geo_opt/demo.py +++ b/demonstrations_v2/tutorial_mol_geo_opt/demo.py @@ -100,10 +100,13 @@ """ -from pennylane import numpy as np +import jax +from jax import numpy as jnp + +jax.config.update("jax_enable_x64", True) symbols = ["H", "H", "H"] -x = np.array([0.028, 0.054, 0.0, 0.986, 1.610, 0.0, 1.855, 0.002, 0.0], requires_grad=True) +x = jnp.array([[0.028, 0.054, 0.0], [0.986, 1.610, 0.0], [1.855, 0.002, 0.0]]) ############################################################################## # Next, we need to build the parametrized electronic Hamiltonian :math:`H(x).` @@ -191,12 +194,12 @@ def H(x): # The ``hf`` array is used by the :class:`~.pennylane.BasisState` operation to initialize # the qubit register. Then, the :class:`~.pennylane.DoubleExcitation` operations are applied # First, we define the quantum device used to compute the expectation value. -# In this example, we use the ``lightning.qubit`` simulator: +# In this example, we use the ``default.qubit`` simulator: num_wires = 6 -dev = qml.device("lightning.qubit", wires=num_wires) +dev = qml.device("default.qubit", wires=num_wires) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(params, obs, wires): qml.BasisState(hf, wires=wires) qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3]) @@ -259,10 +262,11 @@ def cost(params, x): def finite_diff(f, x, delta=0.01): """Compute the central-difference finite difference of a function""" gradient = [] + x = jnp.ravel(x) for i in range(len(x)): - shift = np.zeros_like(x) - shift[i] += 0.5 * delta + shift = jnp.zeros_like(x) + shift = shift.at[i].set(0.5*delta) res = (f(x + shift) - f(x - shift)) * delta**-1 gradient.append(res) @@ -272,7 +276,7 @@ def finite_diff(f, x, delta=0.01): def grad_x(params, x): grad_h = finite_diff(H, x) grad = [circuit(params, obs=obs, wires=range(num_wires)) for obs in grad_h] - return np.array(grad) + return jnp.array(grad).reshape(x.shape) ############################################################################## @@ -285,18 +289,12 @@ def grad_x(params, x): # each optimization step. This approach does not require nested VQE # optimization of the circuit parameters for each set of nuclear coordinates. # -# We start by defining the classical optimizers: - -opt_theta = qml.GradientDescentOptimizer(stepsize=0.4) -opt_x = qml.GradientDescentOptimizer(stepsize=0.8) - -############################################################################## -# Next, we initialize the circuit parameters :math:`\theta.` The angles +# First, we initialize the circuit parameters :math:`\theta.` The angles # :math:`\theta_1` and :math:`\theta_2` are set to zero so that the # initial state :math:`\vert\Psi(\theta_1, \theta_2)\rangle` # is the Hartree-Fock state. -theta = np.array([0.0, 0.0], requires_grad=True) +theta = jnp.array([0.0, 0.0]) ############################################################################## # The initial set of nuclear coordinates :math:`x,` defined at @@ -305,17 +303,15 @@ def grad_x(params, x): # for the starting geometry that we are aiming to improve due to the electronic # correlation effects included in the trial state :math:`\vert\Psi(\theta)\rangle.` # -# We carry out the optimization over a maximum of 100 steps. +# We carry out the optimization over a maximum of 36 steps. # The circuit parameters and the nuclear coordinates are optimized until the # maximum component of the nuclear gradient :math:`\nabla_x g(\theta,x)` is # less than or equal to :math:`10^{-5}` Hartree/Bohr. Typically, this is the # convergence criterion used for optimizing molecular geometries in # quantum chemistry simulations. -from functools import partial - # store the values of the cost function -energy = [] +energies = [] # store the values of the bond length bond_length = [] @@ -323,33 +319,30 @@ def grad_x(params, x): # Factor to convert from Bohrs to Angstroms bohr_angs = 0.529177210903 -for n in range(100): - - # Optimize the circuit parameters - theta.requires_grad = True - x.requires_grad = False - theta, _ = opt_theta.step(cost, theta, x) +for n in range(36): + # gradient for params + g_param = jax.grad(cost, argnums=[0])(theta, x)[0] + theta = theta - 0.8 * g_param - # Optimize the nuclear coordinates - x.requires_grad = True - theta.requires_grad = False - _, x = opt_x.step(cost, theta, x, grad_fn=grad_x) - - energy.append(cost(theta, x)) - bond_length.append(np.linalg.norm(x[0:3] - x[3:6]) * bohr_angs) + # gradient for coordinates + value, _ = jax.value_and_grad(cost, argnums=1)(theta, x) + grad = grad_x(theta, x) + x = x - 0.8 * grad + energies.append(value) + bond_length.append(jnp.linalg.norm(x[0] - x[1]) * bohr_angs) if n % 4 == 0: - print(f"Step = {n}, E = {energy[-1]:.8f} Ha, bond length = {bond_length[-1]:.5f} A") + print(f"Step = {n}, E = {energies[-1]:.8f} Ha, bond length = {bond_length[-1]:.5f} A") # Check maximum component of the nuclear gradient - if np.max(grad_x(theta, x)) <= 1e-05: + if jnp.max(grad_x(theta, x)) <= 1e-04: break -print("\n" f"Final value of the ground-state energy = {energy[-1]:.8f} Ha") +print("\n" f"Final value of the ground-state energy = {energies[-1]:.8f} Ha") print("\n" "Ground-state equilibrium geometry") print("%s %4s %8s %8s" % ("symbol", "x", "y", "z")) for i, atom in enumerate(symbols): - print(f" {atom} {x[3 * i]:.4f} {x[3 * i + 1]:.4f} {x[3 * i + 2]:.4f}") + print(f" {atom} {x[0][i]:.4f} {x[1][i]:.4f} {x[2][i]:.4f}") ############################################################################## # Next, we plot the values of the ground state energy of the molecule @@ -363,10 +356,10 @@ def grad_x(params, x): # Add energy plot on column 1 E_fci = -1.27443765658 -E_vqe = np.array(energy) +E_vqe = jnp.array(energies) ax1 = fig.add_subplot(121) ax1.plot(range(n + 1), E_vqe - E_fci, "go", ls="dashed") -ax1.plot(range(n + 1), np.full(n + 1, 0.001), color="red") +ax1.plot(range(n + 1), jnp.full(n + 1, 0.001), color="red") ax1.set_xlabel("Optimization step", fontsize=13) ax1.set_ylabel("$E_{VQE} - E_{FCI}$ (Hartree)", fontsize=13) ax1.text(5, 0.0013, r"Chemical accuracy", fontsize=13) @@ -378,7 +371,7 @@ def grad_x(params, x): d_fci = 0.986 ax2 = fig.add_subplot(122) ax2.plot(range(n + 1), bond_length, "go", ls="dashed") -ax2.plot(range(n + 1), np.full(n + 1, d_fci), color="red") +ax2.plot(range(n + 1), jnp.full(n + 1, d_fci), color="red") ax2.set_ylim([0.965, 0.99]) ax2.set_xlabel("Optimization step", fontsize=13) ax2.set_ylabel("bond length ($\AA$)", fontsize=13) @@ -392,7 +385,7 @@ def grad_x(params, x): ############################################################################## # | # Notice that despite the fact that the ground-state energy is already converged -# within chemical accuracy (:math:`0.0016` Ha) after the fourth iteration, more +# within chemical accuracy (:math:`0.0016` Ha) after the first iteration, more # optimization steps are required to find the equilibrium bond length of the # molecule. # @@ -467,4 +460,4 @@ def grad_x(params, x): # # About the author # ---------------- -# .. include:: ../_static/authors/alain_delgado.txt +# diff --git a/demonstrations_v2/tutorial_mol_geo_opt/metadata.json b/demonstrations_v2/tutorial_mol_geo_opt/metadata.json index c876927c3d..13b4eb8be7 100644 --- a/demonstrations_v2/tutorial_mol_geo_opt/metadata.json +++ b/demonstrations_v2/tutorial_mol_geo_opt/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2021-06-30T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_neutral_atoms/demo.py b/demonstrations_v2/tutorial_neutral_atoms/demo.py index 3d385adf34..ab4014817e 100644 --- a/demonstrations_v2/tutorial_neutral_atoms/demo.py +++ b/demonstrations_v2/tutorial_neutral_atoms/demo.py @@ -820,4 +820,4 @@ def neutral_atom_CZ(distance, coupling): # # About the author # ---------------- -# .. include:: ../_static/authors/alvaro_ballon.txt +# diff --git a/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py b/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py index 0598b75d54..a30369ad68 100644 --- a/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py +++ b/demonstrations_v2/tutorial_noisy_circuit_optimization/demo.py @@ -543,4 +543,4 @@ def param_shift(theta1): # # About the author # ---------------- -# .. include:: ../_static/authors/nathan_killoran.txt +# diff --git a/demonstrations_v2/tutorial_noisy_circuits/demo.py b/demonstrations_v2/tutorial_noisy_circuits/demo.py index a0985a3e92..1491215009 100644 --- a/demonstrations_v2/tutorial_noisy_circuits/demo.py +++ b/demonstrations_v2/tutorial_noisy_circuits/demo.py @@ -298,4 +298,3 @@ def cost(x, target): # About the author # ---------------- # -# .. include:: ../_static/authors/juan_miguel_arrazola.txt diff --git a/demonstrations_v2/tutorial_odegen/demo.py b/demonstrations_v2/tutorial_odegen/demo.py index 2802df3536..257bb3884c 100644 --- a/demonstrations_v2/tutorial_odegen/demo.py +++ b/demonstrations_v2/tutorial_odegen/demo.py @@ -369,4 +369,4 @@ def partial_step(grad_circuit, opt_state, theta): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/tutorial_optimal_control/demo.py b/demonstrations_v2/tutorial_optimal_control/demo.py index 2d1ac0d8bb..9d4e116196 100644 --- a/demonstrations_v2/tutorial_optimal_control/demo.py +++ b/demonstrations_v2/tutorial_optimal_control/demo.py @@ -843,4 +843,4 @@ def node(params): # # About the author # ---------------- -# .. include:: ../_static/authors/david_wierichs.txt +# diff --git a/demonstrations_v2/tutorial_pasqal/demo.py b/demonstrations_v2/tutorial_pasqal/demo.py index 0f35239b27..b49898f392 100644 --- a/demonstrations_v2/tutorial_pasqal/demo.py +++ b/demonstrations_v2/tutorial_pasqal/demo.py @@ -359,4 +359,4 @@ def cost(): # # About the author # ---------------- -# .. include:: ../_static/authors/nathan_killoran.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_phase_kickback/demo.py b/demonstrations_v2/tutorial_phase_kickback/demo.py index 4b530854ba..36a41c7382 100644 --- a/demonstrations_v2/tutorial_phase_kickback/demo.py +++ b/demonstrations_v2/tutorial_phase_kickback/demo.py @@ -183,4 +183,4 @@ def check_key(lock, key): # # About the author # ---------------- -# .. include:: ../_static/authors/danial_motlagh.txt +# diff --git a/demonstrations_v2/tutorial_photonics/demo.py b/demonstrations_v2/tutorial_photonics/demo.py index a379a489aa..16aee1822d 100644 --- a/demonstrations_v2/tutorial_photonics/demo.py +++ b/demonstrations_v2/tutorial_photonics/demo.py @@ -962,5 +962,5 @@ def measurement2_1(a, theta, alpha, phi): # (`arXiv `__) # # About the author -# ~~~~~~~~~~~~~~~~ -# .. include:: ../_static/authors/alvaro_ballon.txt +# ---------------- +# diff --git a/demonstrations_v2/tutorial_pulse_programming101/demo.py b/demonstrations_v2/tutorial_pulse_programming101/demo.py index 7b6a39c942..b1b4ce33ea 100644 --- a/demonstrations_v2/tutorial_pulse_programming101/demo.py +++ b/demonstrations_v2/tutorial_pulse_programming101/demo.py @@ -484,4 +484,4 @@ def qnode(theta, t=duration): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/korbinian_kottmann.txt +# diff --git a/demonstrations_v2/tutorial_qaoa_intro/demo.py b/demonstrations_v2/tutorial_qaoa_intro/demo.py index 36dc01a337..789d724ae4 100644 --- a/demonstrations_v2/tutorial_qaoa_intro/demo.py +++ b/demonstrations_v2/tutorial_qaoa_intro/demo.py @@ -246,8 +246,9 @@ def circuit(params, **kwargs): edges = [(0, 1), (1, 2), (2, 0), (2, 3)] graph = nx.Graph(edges) +positions = nx.spring_layout(graph, seed=1) -nx.draw(graph, with_labels=True) +nx.draw(graph, with_labels=True, pos=positions) plt.show() @@ -513,4 +514,4 @@ def probability_circuit(gamma, alpha): # # About the author # ---------------- -# .. include:: ../_static/authors/jack_ceroni.txt +# diff --git a/demonstrations_v2/tutorial_qaoa_intro/metadata.json b/demonstrations_v2/tutorial_qaoa_intro/metadata.json index 89b69fa6d9..2fc2f5eb95 100644 --- a/demonstrations_v2/tutorial_qaoa_intro/metadata.json +++ b/demonstrations_v2/tutorial_qaoa_intro/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-11-18T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Optimization" ], diff --git a/demonstrations_v2/tutorial_qchem_external/demo.py b/demonstrations_v2/tutorial_qchem_external/demo.py index 77dff37479..ae25f561bf 100644 --- a/demonstrations_v2/tutorial_qchem_external/demo.py +++ b/demonstrations_v2/tutorial_qchem_external/demo.py @@ -48,12 +48,12 @@ """ import pennylane as qml -from pennylane import numpy as np +import numpy as np symbols = ["H", "O", "H"] geometry = np.array([[-0.0399, -0.0038, 0.0000], [ 1.5780, 0.8540, 0.0000], - [ 2.7909, -0.5159, 0.0000]], requires_grad = False) + [ 2.7909, -0.5159, 0.0000]]) molecule = qml.qchem.Molecule(symbols, geometry) H, qubits = qml.qchem.molecular_hamiltonian(molecule, method="pyscf") @@ -227,4 +227,4 @@ # # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt +# diff --git a/demonstrations_v2/tutorial_qchem_external/metadata.json b/demonstrations_v2/tutorial_qchem_external/metadata.json index eb5d12152d..63e5c1b469 100644 --- a/demonstrations_v2/tutorial_qchem_external/metadata.json +++ b/demonstrations_v2/tutorial_qchem_external/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2023-01-03T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry", "Devices and Performance" ], diff --git a/demonstrations_v2/tutorial_qft/demo.py b/demonstrations_v2/tutorial_qft/demo.py index f682bcdbd7..487e1a68c6 100644 --- a/demonstrations_v2/tutorial_qft/demo.py +++ b/demonstrations_v2/tutorial_qft/demo.py @@ -97,7 +97,7 @@ plt.style.use('pennylane.drawer.plot') # This line is to expand the circuit to see the operators -@partial(qml.devices.preprocess.decompose, stopping_condition = lambda obj: False, max_expansion=1) +@partial(qml.transforms.decompose, max_expansion=1) def circuit(): qml.QFT(wires=range(4)) diff --git a/demonstrations_v2/tutorial_qft/metadata.json b/demonstrations_v2/tutorial_qft/metadata.json index d99c9dd532..99d3b07ae4 100644 --- a/demonstrations_v2/tutorial_qft/metadata.json +++ b/demonstrations_v2/tutorial_qft/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-04-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-18T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" diff --git a/demonstrations_v2/tutorial_qft_arithmetics/demo.py b/demonstrations_v2/tutorial_qft_arithmetics/demo.py index bd4655afd3..3d67048454 100644 --- a/demonstrations_v2/tutorial_qft_arithmetics/demo.py +++ b/demonstrations_v2/tutorial_qft_arithmetics/demo.py @@ -431,5 +431,3 @@ def factorization(n, wires_m, wires_k, wires_solution): # About the author # ---------------- # -# .. include:: ../_static/authors/guillermo_alonso.txt -# diff --git a/demonstrations_v2/tutorial_qgrnn/demo.py b/demonstrations_v2/tutorial_qgrnn/demo.py index 913fc9aea4..c349f0748f 100644 --- a/demonstrations_v2/tutorial_qgrnn/demo.py +++ b/demonstrations_v2/tutorial_qgrnn/demo.py @@ -224,10 +224,10 @@ ising_graph = nx.cycle_graph(qubit_number) +positions = nx.spring_layout(ising_graph, seed=1) print(f"Edges: {ising_graph.edges}") -nx.draw(ising_graph) - +nx.draw(ising_graph, pos=positions) ###################################################################### @@ -303,8 +303,6 @@ def create_hamiltonian_matrix(n_qubits, graph, weights, bias): plt.show() - - ###################################################################### # Preparing Quantum Data # ^^^^^^^^^^^^^^^^^^^^^^ @@ -472,9 +470,10 @@ def swap_test(control, register1, register2): # Defines the interaction graph for the new qubit system new_ising_graph = nx.complete_graph(reg2) +positions = nx.spring_layout(new_ising_graph, seed=1) print(f"Edges: {new_ising_graph.edges}") -nx.draw(new_ising_graph) +nx.draw(new_ising_graph, pos=positions) ###################################################################### @@ -611,8 +610,6 @@ def cost_function(weight_params, bias_params): plt.show() - - ###################################################################### # These images look very similar, indicating that the QGRNN has done a good job # learning the target Hamiltonian. @@ -675,4 +672,4 @@ def cost_function(weight_params, bias_params): # # About the author # ---------------- -# .. include:: ../_static/authors/jack_ceroni.txt +# diff --git a/demonstrations_v2/tutorial_qgrnn/metadata.json b/demonstrations_v2/tutorial_qgrnn/metadata.json index 9e0c81ffb2..5bcafc2c23 100644 --- a/demonstrations_v2/tutorial_qgrnn/metadata.json +++ b/demonstrations_v2/tutorial_qgrnn/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-07-27T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2025-01-10T00:00:00+00:00", "categories": [ "Quantum Machine Learning" ], diff --git a/demonstrations_v2/tutorial_qnn_module_tf/demo.py b/demonstrations_v2/tutorial_qnn_module_tf/demo.py index a78ddae642..8f8c906c1c 100644 --- a/demonstrations_v2/tutorial_qnn_module_tf/demo.py +++ b/demonstrations_v2/tutorial_qnn_module_tf/demo.py @@ -232,4 +232,4 @@ def qnode(inputs, weights): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/thomas_bromley.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qnn_module_torch/demo.py b/demonstrations_v2/tutorial_qnn_module_torch/demo.py index 4a7eee615f..f3f59a77ff 100644 --- a/demonstrations_v2/tutorial_qnn_module_torch/demo.py +++ b/demonstrations_v2/tutorial_qnn_module_torch/demo.py @@ -291,4 +291,4 @@ def forward(self, x): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/thomas_bromley.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qsvt_hardware/demo.py b/demonstrations_v2/tutorial_qsvt_hardware/demo.py index 9314e3f87f..e5c3324473 100644 --- a/demonstrations_v2/tutorial_qsvt_hardware/demo.py +++ b/demonstrations_v2/tutorial_qsvt_hardware/demo.py @@ -26,22 +26,31 @@ - **Projection angles**: A list of angles that will determine the coefficients of the polynomial to be applied. - **Block encoding**: The strategy used to encode the Hamiltonian. We will use the :doc:`linear combinations of unitaries ` approach via the PennyLane :class:`~.qml.PrepSelPrep` operation. -Calculating angles is not a trivial task, but there are tools such as `pyqsp `_ that do the job for us. -For instance, to find the angles to apply the polynomial :math:`p(x) = -x + \frac{x^3}{2}+ \frac{x^5}{2},` we can run this code: - -.. code-block:: python - - from pyqsp.angle_sequence import QuantumSignalProcessingPhases - import numpy as np - - # Define the polynomial, the coefficients are in the order of the polynomial degree. - poly = np.array([0,-1, 0, 0.5, 0 , 0.5]) - - ang_seq = QuantumSignalProcessingPhases(poly, signal_operator="Wx") - -The angles obtained after execution are as follows: - +Calculating angles is not a trivial task, but we can use the PennyLane function :func:`~.pennylane.poly_to_angles` +to obtain them. There are also tools such as `pyqsp `_ that can do the job for us. +Let's try both tools to calculate the angles for applying the +polynomial :math:`p(x) = -x + \frac{x^3}{2}+ \frac{x^5}{2}`. + +The :func:`~.pennylane.poly_to_angles` function in PennyLane accepts the coefficients of the +polynomial, ordered from lowest to highest power, as input. We also need to define the routine for +which the angles are computed, which is ``'QSVT'`` here. """ +import pennylane as qml +poly = [0, -1.0, 0, 1/2, 0, 1/2] +angles_pl = qml.poly_to_angles(poly, "QSVT") +print(angles_pl) + +###################################################################### +# To find the angles with ``pyqsp`` we can run this code: +# +# .. code-block:: python +# +# from pyqsp.angle_sequence import QuantumSignalProcessingPhases +# import numpy as np +# +# ang_seq = QuantumSignalProcessingPhases(np.array(poly), signal_operator="Wx") +# +# The angles obtained after execution are as follows: ang_seq = [ -1.5115007723754004, @@ -53,31 +62,18 @@ ] ###################################################################### -# We use these angles to apply the polynomial transformation. -# However, we are not finished yet: these angles have been calculated following the "Wx" -# convention [#unification]_, while :class:`~.qml.PrepSelPrep` follows a different one. Moreover, the angles obtained in the -# context of QSP (the ones given by ``pyqsp``) are not the same as the ones we have to use in QSVT. That is why -# we must transform the angles: - -import numpy as np - +# The ``pyqsp`` angles are obtained in the +# context of QSP and are not the same as the ones we have to use in QSVT. +# We can use the :func:`~.pennylane.transform_angles` function to transform the angles: -def convert_angles(angles): - num_angles = len(angles) - update_vals = np.zeros(num_angles) - - update_vals[0] = 3 * np.pi / 4 - (3 + len(angles) % 4) * np.pi / 2 - update_vals[1:-1] = np.pi / 2 - update_vals[-1] = -np.pi / 4 - - return angles + update_vals - - -angles = convert_angles(ang_seq) -print(angles) +angles_pyqsp = qml.transform_angles(ang_seq, "QSP", "QSVT") +print(angles_pyqsp) ###################################################################### -# Using these angles, we can now start working with the template. +# Note that these angles are not exactly the same as those obtained with +# :func:`~.pennylane.poly_to_angles`, but they will both produce the same polynomial transformation. +# Using the angles computed with :func:`~.pennylane.poly_to_angles` or ``pyqsp``, we can now start +# working with the template. # # QSVT on hardware # ----------------- @@ -88,6 +84,7 @@ def convert_angles(angles): # a Hamiltonian and manually apply the polynomial of interest: import pennylane as qml +import numpy as np from numpy.linalg import matrix_power as mpow coeffs = np.array([0.2, -0.7, -0.6]) @@ -113,8 +110,8 @@ def convert_angles(angles): block_encode = qml.PrepSelPrep(H, control=control_wires) projectors = [ - qml.PCPhase(angles[i], dim=2 ** len(H.wires), wires=control_wires + H.wires) - for i in range(len(angles)) + qml.PCPhase(angles_pl[i], dim=2 ** len(H.wires), wires=control_wires + H.wires) + for i in range(len(angles_pl)) ] diff --git a/demonstrations_v2/tutorial_qsvt_hardware/metadata.json b/demonstrations_v2/tutorial_qsvt_hardware/metadata.json index acc4370c21..ed09c58b37 100644 --- a/demonstrations_v2/tutorial_qsvt_hardware/metadata.json +++ b/demonstrations_v2/tutorial_qsvt_hardware/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-09-18T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-06T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing", diff --git a/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py b/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py index 538272d28d..cecafc688b 100644 --- a/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py +++ b/demonstrations_v2/tutorial_quantum_analytic_descent/demo.py @@ -707,8 +707,6 @@ def plot_cost_and_model(f, model, params, shift_radius=5 * np.pi / 8, num_points # `arXiv preprint arXiv:2008.06517 `__. # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/elies_gil-fuster.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/david_wierichs.txt diff --git a/demonstrations_v2/tutorial_quantum_chemistry/demo.py b/demonstrations_v2/tutorial_quantum_chemistry/demo.py index 68db18429c..b015fb33b7 100644 --- a/demonstrations_v2/tutorial_quantum_chemistry/demo.py +++ b/demonstrations_v2/tutorial_quantum_chemistry/demo.py @@ -328,4 +328,4 @@ # # About the author # ---------------- -# .. include:: ../_static/authors/alain_delgado.txt +# diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py b/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py index 45083d90fe..987ef06e31 100644 --- a/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/demo.py @@ -741,10 +741,10 @@ def make_kraus_ops(num_wires: int): tape0 = QuantumTape(ops=ops_0, measurements=tape.measurements, shots=channel_shots[0].item()) tape1 = QuantumTape(ops=ops_1, measurements=tape.measurements, shots=channel_shots[1].item()) -(shots0,) = qml.execute([tape0], device=device, cache=False, gradient_fn=None) +(shots0,) = qml.execute([tape0], device=device, cache=False, diff_method=None) samples[choices == 0] = shots0 -(shots1,) = qml.execute([tape1], device=device, cache=False, gradient_fn=None) +(shots1,) = qml.execute([tape1], device=device, cache=False, diff_method=None) samples[choices == 1] = shots1 ###################################################################### @@ -887,10 +887,6 @@ def make_kraus_ops(num_wires: int): # (`arXiv `__) # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/gideon_uchehara.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/matija_medvidovic.txt -# -# .. include:: ../_static/authors/anuj_apte.txt diff --git a/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json b/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json index ff928c43f3..af6563fe52 100644 --- a/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json +++ b/demonstrations_v2/tutorial_quantum_circuit_cutting/metadata.json @@ -12,7 +12,7 @@ } ], "dateOfPublication": "2022-09-02T00:00:00+00:00", - "dateOfLastModification": "2024-11-06T00:00:00+00:00", + "dateOfLastModification": "2024-11-12T00:00:00+00:00", "categories": [ "Algorithms", "Quantum Computing" ], diff --git a/demonstrations_v2/tutorial_quantum_gans/demo.py b/demonstrations_v2/tutorial_quantum_gans/demo.py index 6222a82b27..70664070e3 100644 --- a/demonstrations_v2/tutorial_quantum_gans/demo.py +++ b/demonstrations_v2/tutorial_quantum_gans/demo.py @@ -599,4 +599,4 @@ def forward(self, x): # # About the author # ---------------- -# .. include:: ../_static/authors/james_ellis.txt +# diff --git a/demonstrations_v2/tutorial_quantum_metrology/demo.py b/demonstrations_v2/tutorial_quantum_metrology/demo.py index 6b4f2acb85..8ed1dad270 100644 --- a/demonstrations_v2/tutorial_quantum_metrology/demo.py +++ b/demonstrations_v2/tutorial_quantum_metrology/demo.py @@ -359,4 +359,4 @@ def opt_cost(weights, phi=phi, gamma=gamma, J=J, W=W): # # About the author # ---------------- -# .. include:: ../_static/authors/johannes_jakob_meyer.txt +# diff --git a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py index 32c2071a18..f693b3d3aa 100644 --- a/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py +++ b/demonstrations_v2/tutorial_quantum_natural_gradient/demo.py @@ -496,4 +496,4 @@ def layer1_off_diag_double(params): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py index 23dab8e3ce..a8e584f07c 100644 --- a/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py +++ b/demonstrations_v2/tutorial_quantum_transfer_learning/demo.py @@ -609,4 +609,4 @@ def visualize_model(model, num_images=6, fig_name="Predictions"): # # About the author # ---------------- -# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_quanvolution/demo.py b/demonstrations_v2/tutorial_quanvolution/demo.py index 0b455fce13..56594d67b1 100644 --- a/demonstrations_v2/tutorial_quanvolution/demo.py +++ b/demonstrations_v2/tutorial_quanvolution/demo.py @@ -371,4 +371,4 @@ def MyModel(): # # About the author # ---------------- -# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qubit_rotation/demo.py b/demonstrations_v2/tutorial_qubit_rotation/demo.py index 231af2f71d..19e7577d12 100644 --- a/demonstrations_v2/tutorial_qubit_rotation/demo.py +++ b/demonstrations_v2/tutorial_qubit_rotation/demo.py @@ -370,4 +370,4 @@ def cost(x): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_qubit_tapering/demo.py b/demonstrations_v2/tutorial_qubit_tapering/demo.py index f2af09bfa4..5c55be064e 100644 --- a/demonstrations_v2/tutorial_qubit_tapering/demo.py +++ b/demonstrations_v2/tutorial_qubit_tapering/demo.py @@ -122,10 +122,12 @@ coordinates. """ import pennylane as qml -from pennylane import numpy as np +from jax import numpy as jnp +import jax +jax.config.update("jax_enable_x64", True) symbols = ["He", "H"] -geometry = np.array([[0.00000000, 0.00000000, -0.87818361], +geometry = jnp.array([[0.00000000, 0.00000000, -0.87818361], [0.00000000, 0.00000000, 0.87818362]]) molecule = qml.qchem.Molecule(symbols, geometry, charge=1) @@ -169,7 +171,7 @@ H_tapered = qml.taper(H, generators, paulixops, paulix_sector) H_tapered_coeffs, H_tapered_ops = H_tapered.terms() -H_tapered = qml.Hamiltonian(np.real(H_tapered_coeffs), H_tapered_ops) +H_tapered = qml.Hamiltonian(jnp.real(jnp.array(H_tapered_coeffs)), H_tapered_ops) print(H_tapered) ############################################################################## @@ -215,24 +217,24 @@ # Hartree-Fock energies for each Hamiltonian. dev = qml.device("default.qubit", wires=H.wires) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def circuit(): - qml.BasisState(np.array([1, 1, 0, 0]), wires=H.wires) + qml.BasisState(jnp.array([1, 1, 0, 0]), wires=H.wires) return qml.state() qubit_state = circuit() HF_energy = qubit_state.T @ H.sparse_matrix().toarray() @ qubit_state -print(f"HF energy: {np.real(HF_energy):.8f} Ha") +print(f"HF energy: {jnp.real(HF_energy):.8f} Ha") -dev = qml.device("default.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="autograd") +dev = qml.device("lightning.qubit", wires=H_tapered.wires) +@qml.qnode(dev, interface="jax") def circuit(): - qml.BasisState(np.array([1, 1]), wires=H_tapered.wires) + qml.BasisState(jnp.array([1, 1]), wires=H_tapered.wires) return qml.state() qubit_state = circuit() HF_energy = qubit_state.T @ H_tapered.sparse_matrix().toarray() @ qubit_state -print(f"HF energy (tapered): {np.real(HF_energy):.8f} Ha") +print(f"HF energy (tapered): {jnp.real(HF_energy):.8f} Ha") ############################################################################## # These values are identical to the reference Hartree-Fock energy :math:`-2.8543686493` Ha. @@ -257,9 +259,9 @@ def circuit(): wire_order=H.wires, op_wires=single) for single in singles ] -dev = qml.device("default.qubit", wires=H_tapered.wires) +dev = qml.device("lightning.qubit", wires=H_tapered.wires) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def tapered_circuit(params): qml.BasisState(state_tapered, wires=H_tapered.wires) for idx, tapered_op in enumerate(tapered_doubles + tapered_singles): @@ -270,13 +272,29 @@ def tapered_circuit(params): # We define an optimizer and the initial values of the circuit parameters and optimize the circuit # parameters with respect to the ground state energy. -optimizer = qml.GradientDescentOptimizer(stepsize=0.5) -params = np.zeros(len(doubles) + len(singles), requires_grad=True) +import optax +import catalyst -for n in range(1, 41): - params, energy = optimizer.step_and_cost(tapered_circuit, params) - if not n % 5: - print(f"n: {n}, E: {energy:.8f} Ha, Params: {params}") +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +init_params = jnp.zeros(len(doubles) + len(singles)) + +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = catalyst.grad(tapered_circuit)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) + +loss_history = [] + +opt_state = opt.init(init_params) +params = init_params + +for i in range(1, 41): + params, opt_state = update_step(i, params, opt_state) + energy = tapered_circuit(params) + if not i % 5: + print(f"n: {i}, E: {energy:.8f} Ha, Params: {params}") ############################################################################## # The computed energy matches the FCI energy, :math:`-2.862595242378` Ha, while the number of qubits @@ -309,6 +327,4 @@ def tapered_circuit(params): # # About the author # ---------------- -# .. include:: ../_static/authors/utkarsh_azad.txt # -# .. include:: ../_static/authors/soran_jahangiri.txt diff --git a/demonstrations_v2/tutorial_qubit_tapering/metadata.json b/demonstrations_v2/tutorial_qubit_tapering/metadata.json index d65cec6324..fc85d10815 100644 --- a/demonstrations_v2/tutorial_qubit_tapering/metadata.json +++ b/demonstrations_v2/tutorial_qubit_tapering/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2022-05-16T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py index 9be0a9266f..e2da31eb6e 100644 --- a/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py +++ b/demonstrations_v2/tutorial_qutrits_bernstein_vazirani/demo.py @@ -405,5 +405,5 @@ def circuit(): # `__ # About the author # ---------------- -# .. include:: ../_static/authors/guillermo_alonso.txt +# diff --git a/demonstrations_v2/tutorial_resource_estimation/demo.py b/demonstrations_v2/tutorial_resource_estimation/demo.py index c171a76578..0c033e4dee 100644 --- a/demonstrations_v2/tutorial_resource_estimation/demo.py +++ b/demonstrations_v2/tutorial_resource_estimation/demo.py @@ -65,12 +65,15 @@ `6-31g basis set `_ as an example. """ import pennylane as qml -from pennylane import numpy as np +import numpy as np +import jax + +jax.config.update("jax_enable_x64", True) symbols = ['O', 'H', 'H'] geometry = np.array([[0.00000000, 0.00000000, 0.28377432], [0.00000000, 1.45278171, -1.00662237], - [0.00000000, -1.45278171, -1.00662237]], requires_grad=False) + [0.00000000, -1.45278171, -1.00662237]]) ############################################################################## # Then we construct a molecule object and compute the one- and two-electron @@ -315,4 +318,4 @@ # `__ # About the author # ---------------- -# .. include:: ../_static/authors/soran_jahangiri.txt \ No newline at end of file +# diff --git a/demonstrations_v2/tutorial_resource_estimation/metadata.json b/demonstrations_v2/tutorial_resource_estimation/metadata.json index 9bc9fc0efe..17f82f2766 100644 --- a/demonstrations_v2/tutorial_resource_estimation/metadata.json +++ b/demonstrations_v2/tutorial_resource_estimation/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2022-11-21T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_rosalin/demo.py b/demonstrations_v2/tutorial_rosalin/demo.py index 117b1ed410..aed37d6224 100644 --- a/demonstrations_v2/tutorial_rosalin/demo.py +++ b/demonstrations_v2/tutorial_rosalin/demo.py @@ -663,4 +663,4 @@ def cost(weights): # # About the author # ---------------- -# .. include:: ../_static/authors/josh_izaac.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_rotoselect/demo.py b/demonstrations_v2/tutorial_rotoselect/demo.py index 430103293d..cca7eba0d8 100644 --- a/demonstrations_v2/tutorial_rotoselect/demo.py +++ b/demonstrations_v2/tutorial_rotoselect/demo.py @@ -467,4 +467,4 @@ def rotoselect_cycle(cost, params, generators): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/angus_lowe.txt +# diff --git a/demonstrations_v2/tutorial_sc_qubits/demo.py b/demonstrations_v2/tutorial_sc_qubits/demo.py index 9de73a5a3d..f3f0bb98e2 100644 --- a/demonstrations_v2/tutorial_sc_qubits/demo.py +++ b/demonstrations_v2/tutorial_sc_qubits/demo.py @@ -875,5 +875,5 @@ def H_evolve(state, phi, time): # IBM Research Blog. Retrieved 2022-03-15 # # About the author -# ~~~~~~~~~~~~~~~~ -# .. include:: ../_static/authors/alvaro_ballon.txt +# ---------------- +# diff --git a/demonstrations_v2/tutorial_spsa/demo.py b/demonstrations_v2/tutorial_spsa/demo.py index 7344e643ea..4f80797981 100644 --- a/demonstrations_v2/tutorial_spsa/demo.py +++ b/demonstrations_v2/tutorial_spsa/demo.py @@ -554,5 +554,4 @@ def circuit(param): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/antal_szava.txt -# .. include:: ../_static/authors/david_wierichs.txt +# diff --git a/demonstrations_v2/tutorial_state_preparation/demo.py b/demonstrations_v2/tutorial_state_preparation/demo.py index 2d54848218..ecb7782b75 100644 --- a/demonstrations_v2/tutorial_state_preparation/demo.py +++ b/demonstrations_v2/tutorial_state_preparation/demo.py @@ -198,4 +198,4 @@ def cost_fn(params): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/juan_miguel_arrazola.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py b/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py index e1b45f06bd..9366f82edf 100644 --- a/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py +++ b/demonstrations_v2/tutorial_stochastic_parameter_shift/demo.py @@ -416,4 +416,4 @@ def spsr_circuit(gate_pars, s=None, sign=+1): # # About the author # ---------------- -# .. include:: ../_static/authors/nathan_killoran.txt +# diff --git a/demonstrations_v2/tutorial_teleportation/demo.py b/demonstrations_v2/tutorial_teleportation/demo.py index 3c1d7fad2a..f6e308fdb2 100644 --- a/demonstrations_v2/tutorial_teleportation/demo.py +++ b/demonstrations_v2/tutorial_teleportation/demo.py @@ -437,4 +437,3 @@ def teleport_state(state): # About the author # ---------------- # -# .. include:: ../_static/authors/matthew_silverman.txt diff --git a/demonstrations_v2/tutorial_tensor_network_basics/demo.py b/demonstrations_v2/tutorial_tensor_network_basics/demo.py new file mode 100644 index 0000000000..2d2d7472a0 --- /dev/null +++ b/demonstrations_v2/tutorial_tensor_network_basics/demo.py @@ -0,0 +1,583 @@ +r"""Introducing tensor networks for quantum practitioners +========================================================= + +If you are well-versed in the topics of `quantum computing `__ or quantum information, chances are you have heard (quite often) about tensor networks. In fact, tensor networks are a widely used tool with applications ranging across physics, math, and computer science. + +Part of the excitement surrounding tensor networks is due to their ability to represent complex data efficiently, which allows for—among other things—fast classical simulations. In addition, the diagrammatic language accompanying tensor networks makes working with them intuitive and suitable for describing a vast range of mathematical concepts, including quantum circuits. + +.. figure:: ../_static/demo_thumbnails/opengraph_demo_thumbnails/OGthumbnail_tensor_network_basics.png + :align: center + :width: 70% + :target: javascript:void(0) + +The :ref:`first part of this demo ` introduces the basic tensor network notions and definitions to quantum practitioners who are familiar with quantum computing but new to tensor networks. + +Then, building on this introduction, in the :ref:`second part ` we explore topics aimed at more seasoned readers. Check this section out if you want to understand how tensor networks and quantum circuits are related! + +Without further ado, let’s dive right in! 🤓📚 + +.. _part_one: + +A first glimpse into the tensor networks world +---------------------------------------------- + +From matrices to tensors +~~~~~~~~~~~~~~~~~~~~~~~~ + +First, we start by answering the question: **what is a tensor?** + +A common and intuitive way of thinking about tensors is as generalizations of vectors and matrices. That is, we can think of them as multidimensional arrays—i.e., multidimensional maps that are linear with respect to every parameter. A tensor of dimensions :math:`d_1 \times d_2 \times \ldots \times d_r` can be expressed as + +.. math:: + T_{i_1, i_2, \ldots, i_r} \in \mathbb{C}^{d_1 \times d_2 \times \ldots \times d_r}, + +where each :math:`i_n` is an **index** of dimension :math:`d_n` and the number of indices :math:`r` is known as the **rank** of the tensor. We say :math:`T` is a rank-:math:`r` tensor. We will denote the :math:`(i_1, i_2, \ldots, i_r)`-th entry of the tensor :math:`T` as :math:`(T)_{i_1, i_2, \ldots, i_r}` — this is a single number. + +For example, a scalar :math:`s` is a rank-0 tensor, a vector :math:`v_i` is a rank-1 tensor, and a matrix :math:`G_{j,i}` is a rank-2 tensor. + +.. note:: + Some authors refer to the indices :math:`i_n` as the "dimensions of the tensor". In this demo, however, the term **dimension** refers to the range of integer values :math:`d_n` that each index :math:`i_n` can take, namely :math:`i_n \in \{1, \ldots, d_n\}`. + +A beautiful and powerful tool accompanying tensors is their graphical language representation. The diagram of a tensor is simply a geometric shape with a leg sticking out of it for every index in the tensor. For example, + +.. figure:: ../_static/demonstration_assets/tn_basics/01-tensor.png + :align: center + :width: 40% + +We can apply this same idea to represent a scalar, a vector, and a matrix: + +.. figure:: ../_static/demonstration_assets/tn_basics/02-tensors.png + :align: center + :width: 40% + +Does the last diagram seem familiar? It is because this is the representation of a single-qubit gate! Later in this demo, we will study the relation between quantum circuits and tensor networks. + +When working within the quantum computing notation, we adopt the convention that drawing the leg of a quantum state (i.e., a vector) to the right corresponds to a ket, i.e., a vector living in the Hilbert space, while drawing the legs to the left means they are a bra vector, i.e., living in the dual space. + +.. figure:: ../_static/demonstration_assets/tn_basics/03-braket.png + :align: center + :width: 55% + +.. note:: + The diagrammatic representation of tensors is rooted in category theory, which equips the diagrams with all the relevant information so they can be used in proofs and formal reasoning! 💡 [#Selinger2010]_ + +Creating a tensor in code is straightforward, and chances are you have already created one yourself. Using ``numpy``, all we have to do is create a ``np.array`` of the desired rank. For instance, we can start by creating a rank-1 tensor (a vector). +""" + +import numpy as np + +tensor_rank1 = np.array([1, 2, 3, 4]) +print("rank: ", tensor_rank1.ndim) +print("dimensions: ", tensor_rank1.shape) + +############################################################################## +# Then, we can use this to construct a rank-2 tensor (a matrix). + +tensor_rank2 = np.array([tensor_rank1, tensor_rank1, tensor_rank1]) +print("rank: ", tensor_rank2.ndim) +print("dimensions: ", tensor_rank2.shape) + +############################################################################## +# As you might have guessed, we can repeat this procedure to create a rank-3 tensor. + +tensor_rank3 = np.array([tensor_rank2, tensor_rank2]) +print("rank: ", tensor_rank3.ndim) +print("dimensions: ", tensor_rank3.shape) +print("Rank-3 tensor: \n", tensor_rank3) + +############################################################################## +# We can create a tensor of arbitrary rank following a similar procedure. This recursive approach illustrates how a rank-:math:`r` tensor can be seen as consisting of nested rank-:math:`(r-1)` tensors. This translates into code as adding another level to the nested bracket structure: ``[tensor_rank_r-1]``. +# +# Now that we understand what a tensor is—and even know how to code one—let us look at how to combine them to create a tensor network. + +############################################################################## +# From matrix multiplication to tensor contractions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Matrix-matrix and matrix-vector multiplications are familiar operations within the context of quantum computing. We can now study these operations through the lens of the tensor notation introduced above. First, a matrix :math:`G` and a vector :math:`v` can be multiplied such that the :math:`j`-th element of the resulting vector is +# +# .. math:: +# (w)_j = \sum_i (G)_{j, i} (v)_i . +# +# .. figure:: ../_static/demonstration_assets/tn_basics/04-matrix-vector.png +# :align: center +# :width: 55% +# +# .. note:: +# +# Recall we are adopting the convention of drawing a ket vector with its leg pointing right, as done in quantum circuits. In turn, this means the "input" index of a matrix—its column index—points towards the left while the "output" index—its row index—points to the right. In the example above for :math:`G_{j, i}`, the input and output indices are index :math:`i` and index :math:`j`, respectively. +# +# We see that summing over the shared index :math:`i` is equivalent to **contracting** the corresponding legs from the matrix and vector diagrams. As expected, the result of this multiplication is another rank-1 tensor with dangling leg :math:`j`. Similarly, we can look at the matrix-matrix multiplication :math:`G^3 = G^2 \cdot G^1` and its :math:`(k,i)`-th element is given by +# +# .. math:: +# (G^3)_{k,i} = \sum_j (G^2)_{k,j} (G^1)_{j,i} . +# +# .. figure:: ../_static/demonstration_assets/tn_basics/05-matrix-matrix.png +# :align: center +# :width: 55% +# +# Here, the resulting tensor has two dangling indices, :math:`i` and :math:`k`, defining a matrix, as expected! +# +# We can now generalize this concept to tensors, and consequently, to more than two legs being contracted. For example, let us look at three tensors :math:`A_{i,j,k}`, :math:`B_{j,l,m}`, and :math:`C_{k,m,n}`. To contract them, all we need to do is to sum over repeated indices (:math:`j`, :math:`k`, :math:`m`). To obtain the :math:`(i,l,n)`-th element of the resulting tensor :math:`D`, we perform this contraction by summing over :math:`j, k, m` +# +# .. math:: +# (D)_{i,l,n} = \sum_{j,k,m} (A)_{i,j,k} (B)_{j,l,m} (C)_{k,m,n} . +# +# Using the tensor product :math:`\otimes` between the 3 tensors and summing over the repeated indices we can obtain a similar expression for the full tensor :math:`D` [#Bridgeman2017]_ +# +# .. math:: +# D_{i,l,n} = \sum_{j,k,m} A_{i,j,k} \otimes B_{j,l,m} \otimes C_{k,m,n}. +# +# The resulting rank-3 tensor consists of the remaining open legs from the initial tensors :math:`(i,l,n)`. The first equation involving only scalars is the more widely used expression for the contraction operation, and thus, we will use it throughout this demo unless otherwise stated. The diagrammatic representation of this contraction is obtained by connecting all the legs with the same indices. +# +# .. figure:: ../_static/demonstration_assets/tn_basics/06-tensor-tensor.png +# :align: center +# :width: 55% +# +# With the above contraction, we have formed a network of tensors, i.e., a **tensor network**! +# +# .. note:: +# A common question arising when drawing a tensor is: "What is the correct order to draw the indices?" For instance, in the figure above, we have adopted the convention that a tensor :math:`A_{i,j,k}` corresponds to a diagram with the first leg (:math:`i`) pointing left, the second leg (:math:`j`) pointing upwards, and the third leg (:math:`k`) pointing right, and similarly for the other two tensors. However, this need not be the case. We could have defined the first leg to be the one pointing upwards, for example. Based on the use case, and the user, some conventions might seem more natural than others. The only important thing to keep in mind is to be consistent. In other words, once we choose a convention for the order, we should apply it to all the tensors to avoid contracting the wrong indices. +# +# In our code, we can perform a tensor contraction using the ``numpy`` function ``np.einsum``. To do so, we can start by creating the 3 tensors to be contracted by reshaping a 1D array (created using ``np.arange``) into rank-3 tensors of the correct dimensions. + +# Create the individual rank-3 tensors +A = np.arange(6).reshape(1, 2, 3) # ijk +B = np.arange(6).reshape(2, 3, 1) # jlm +C = np.arange(6).reshape(3, 1, 2) # kmn + +############################################################################## +# The ``np.einsum`` function takes as inputs the tensors to be contracted and a string showing the indices of each tensor and (optionally) the indices of the output tensor. + +D = np.einsum("ijk, jlm, kmn -> iln", A, B, C) +print(D.shape) + +############################################################################## +# The CNOT gate +# ^^^^^^^^^^^^^ +# +# To end this section, we want to discuss a common example of a tensor network contraction arising in quantum computing, namely the **CNOT** gate. The `CNOT gate `__ can be expressed in the computational basis as +# +# .. math:: +# \mathrm{CNOT} = |0\rangle \langle 0 | \otimes I + |1\rangle \langle 1 | \otimes X. +# +# That is, if the control qubit is in the :math:`|1\rangle` state, we apply the :math:`X` gate on the target qubit; otherwise, we leave it untouched. However, we can also rewrite this equation as a contraction. To do so, we define two tensors: +# +# .. math:: +# T^1 = \begin{pmatrix} +# |0\rangle \langle 0|\\ +# |1\rangle \langle 1 | +# \end{pmatrix} +# +# and +# +# .. math:: +# T^2 = \begin{pmatrix} +# I \\ +# X +# \end{pmatrix}. +# +# This means :math:`T^1_{i,j,k}` and :math:`T^2_{l,j,m}` are two rank-3 tensors, where the index :math:`j` *picks* the elements in the column vector while the other two indices correspond to the indices of the internal tensors (matrices). Specifically, the :math:`j = 0` element of :math:`T^1` and :math:`T^2` are :math:`|0\rangle \langle 0 |` and :math:`I`, respectively; with :math:`|1\rangle \langle 1|` and :math:`X` for their :math:`j = 1` element. This means we can redefine the CNOT expression from above as +# +# .. math:: +# \mathrm{CNOT}_{i,l,k,m} = \sum_j T^1_{i,j,k} \otimes T^2_{l,j,m} . +# +# We recognize this expression as the second definition for the contraction operation we introduced earlier. Hence, the CNOT gate can be seen as the contraction between :math:`T^1` and :math:`T^2:` +# +# .. math:: +# (\mathrm{CNOT})_{i,l,k,m} = \sum_j (T^1)_{i,j,k} (T^2)_{l,j,m}. +# +# It turns out that :math:`T^1` and :math:`T^2` are special tensors known as the COPY and XOR tensors, respectively, and therefore have special diagrammatic representations. +# +# .. figure:: ../_static/demonstration_assets/tn_basics/07-t1-t2.png +# :align: center +# :width: 60% +# +# Then, their contraction results in the well-known CNOT quantum circuit representation. +# +# .. figure:: ../_static/demonstration_assets/tn_basics/08-cnot.png +# :align: center +# :width: 45% +# +# .. note:: +# By looking at the elements of the COPY tensor, we can interpret them as being equal to :math:`1` when all the indices have the same value (0 or 1) and vanishing otherwise. On the other hand, the elements of the XOR tensor can be understood as being equal to :math:`1` when the values of the three indices contain an even number of 1's and vanishing otherwise. We anticipate that the COPY tensor can be used to obtain the diagonal of a matrix. This will be useful in the last section of this demo. +# +# This demonstrates the relation between the CNOT acting as a rank-4 tensor with dimensions :math:`2 \times 2 \times 2 \times 2` and its decomposition in terms of two rank-3 local tensors (:math:`T^1` and :math:`T^2`) of dimensions :math:`2 \times 2 \times 2`. +# +# .. note:: +# +# More generally, we can find decompositions of multi-qubit gates into local tensors employing the ubiquitous singular value decomposition (`SVD `__). This method is explained in detail in our :doc:`introduction to matrix product states for quantum practitioners demo `. This decomposition is helpful when contracting non-local tensors, as is often required in quantum circuits. + +############################################################################## +# The cost of contracting a network +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# A common task when dealing with tensors is the contraction of large networks resulting in a single tensor (including scalars). To arrive at this final tensor, we can start with a single tensor and contract it with adjacent tensors one at a time. The order in which this is carried out is known as the **contraction path** or **bubbling**. +# +# While the final tensor is independent of the order of the contraction, the number of operations performed can vary greatly depending on the order in which we contract the intermediate tensors. Moreover, in a general setup, finding the optimal order of indices to be contracted is not a trivial task. +# +# .. note:: +# +# Actually, finding the optimal contraction path of a tensor network with an arbitrary structure is an NP-complete problem [#Arad2010]_. +# +# For this reason, in this section we will look at how to calculate the computational cost or the **complexity** of a tensor network contraction. First, we look at a simple matrix-matrix contraction. Given two rank-2 tensors :math:`G^1_{j,i}` and :math:`G^2_{k,j}`, we have seen that the :math:`(k,i)`-th element of the resulting contraction along the :math:`j`-th index is +# +# .. math:: +# (G^3)_{k,i} = \sum_{j=1}^{d_j} (G^2)_{k,j} (G^1)_{j,i}, +# +# where the indices :math:`(i, j, k)` have dimensions :math:`(d_i, d_j, d_k)`, respectively. Thus, obtaining the :math:`(k,i)`-th element requires :math:`\mathcal{O}(d_j)` operations. To construct the full tensor :math:`G^3`, we must repeat this procedure :math:`d_i \times d_k` times (once for every possible value of :math:`i` and :math:`k`). Therefore, the total complexity of the contraction is +# +# .. math:: +# \mathcal{O}(d_i \times d_j \times d_k) +# +# To illustrate the importance of choosing an efficient contraction path, let us look at a similar contraction between three rank-3 tensors: :math:`A_{i,j,k} \in \mathbb{C}^{d_i \times d_j \times d_k}`, :math:`B_{j,l,m} \in \mathbb{C}^{d_j \times d_l \times d_m}`, and :math:`C_{k,m,n} \in \mathbb{C}^{d_k \times d_m \times d_n}`. +# +# .. figure:: ../_static/demonstration_assets/tn_basics/09-contraction.png +# :align: center +# :width: 50% +# +# In particular, let us look at an example where :math:`d_l = 1`, :math:`d_k = d_j = 10`, :math:`d_m = 10^2`, and :math:`d_n = d_i = 10^3`. First, we look at the complexity of contracting :math:`AB` followed by its contraction with :math:`C`. As outlined in the procedure above, the first contraction results in a complexity of +# +# .. math:: +# \sum_{j} (A)_{i,j,k} (B)_{j,l,m} \implies \mathcal{O}(d_i \times d_l \times d_m \times d_k \times d_j ) = \mathcal{O}(d_i \times d_m \times d_j^2 ) = \mathcal{O}(10^3 10^2 (10^1)^2) = \mathcal{O}(10^7) +# +# Then, contracting the resulting tensor :math:`AB_{i, k, l, m}` with :math:`C_{k,m,n}` requires +# +# .. math:: +# \sum_{k, m} (AB)_{i, k, l, m} (C)_{k,m,n} \implies \mathcal{O}(d_i \times d_l \times d_k \times d_m \times d_n) = \mathcal{O}(d_j \times d_m \times d_i^2) = \mathcal{O}(10^1 10^2 (10^3)^2) = \mathcal{O}(10^9) +# +# operations. Since :math:`d_j < d_m < d_i`, asymptotically, the whole contraction will have a cost of :math:`\mathcal{O}(d_j \times d_m \times d_i^2) = \mathcal{O}(10^9)`. Alternatively, we could first contract :math:`B` and :math:`C`, incurring the cost +# +# .. math:: +# \sum_{m} (B)_{j,l,m} (C)_{k,m,n} \implies \mathcal{O}(d_j \times d_l \times d_m \times d_k \times d_n) = \mathcal{O}(d_i \times d_m \times d_j^2 ) = \mathcal{O}(10^3 10^2 (10^1)^2) = \mathcal{O}(10^7). +# +# Then, contracting the resulting tensor with :math:`A` results in +# +# .. math:: +# \sum_{j, k} (A)_{i,j,k} (BC)_{j,l,k,n} \implies \mathcal{O}(d_j \times d_k \times d_l \times d_n \times d_i) = \mathcal{O}(d_i^2 \times d_j^2) = \mathcal{O}((10^3)^2 (10^1)^2) =\mathcal{O}(10^8). +# +# This means the second contraction path results in an asymptotic cost of :math:`\mathcal{O}(d_i^2 \times d_j^2) = \mathcal{O}(10^8)`—lower than the first contraction path. +# +# To see this in practice, let us implement the above contractions using ``np.einsum``. First, we create the 3 tensors with the dimensions specified in the example above. We demonstrate a different form of creating tensors of the desired dimensions using the ``random`` module. + +import timeit + +di = 1000 +dm = 100 +dj = 10 + +np.random.seed(20) + +A = np.random.rand(di, dj, dj) # ijk +B = np.random.rand(dj,1,dm) # jlm +C = np.random.rand(dj,dm,di) # kmn + +############################################################################## +# Then, we perform the individual contractions between pairs of tensors and time them using ``timeit``. We repeat the contraction ``20`` times and average the computation time to account for smaller fluctuations. First, we start by contracting :math:`A` and :math:`B`. + +iterations = 20 + +contraction = "np.einsum('ijk, jlm -> iklm', A, B)" +execution_time = timeit.timeit(contraction, globals=globals(), number=iterations) + +time_AB = execution_time * 1000 / iterations +print(f"Computation cost for AB contraction: {time_AB:.8f} ms") + +############################################################################## +# Then, we contract the result with :math:`C`. + +AB = np.einsum('ijk, jlm -> iklm', A, B) +contraction = "np.einsum('iklm, kmn -> iln', AB, C)" +execution_time = timeit.timeit(contraction, globals=globals(), number=iterations) + +time_ABC = execution_time * 1000 / iterations +print(f"Computation cost for (AB)C contraction: {time_ABC:.8f} ms") + +############################################################################## +# As expected, the last contraction is much more costly than the first one. We now repeat the procedure, contracting :math:`B` and :math:`C` first. + +contraction = "np.einsum('jlm, kmn -> jlkn', B, C)" +execution_time = timeit.timeit(contraction, globals=globals(), number=iterations) + +time_BC = execution_time * 1000 / iterations +print(f"Computation cost for BC contraction: {time_BC:.8f} ms") + +############################################################################## +# We see that this contraction is of the same order of magnitude as the contraction between :math:`A` and :math:`B`, as expected from the complexity analysis, since they both yield :math:`\mathcal{O}(d_i \times d_m \times d_j^2 ) = \mathcal{O}(10^7)`. Then, we perform the contraction between the resulting tensor and :math:`A`. + +BC = np.einsum('jlm, kmn -> jlkn', B, C) + +contraction = "np.einsum('ijk, jlkn -> iln', A, BC)" +execution_time = timeit.timeit(contraction, globals=globals(), number=iterations) + +time_BCA = execution_time * 1000 / iterations +print(f"Computation cost for A(BC) contraction: {time_BCA:.8f} ms") + +############################################################################## +# We can then compare the total time for each of the paths: + +print(f"Computation cost for path 1: {time_AB + time_ABC}") +print(f"Computation cost for path 2: {time_BC + time_BCA}") + +############################################################################## +# From this, we see that the second contraction path results in a lower complexity compared to the first one, just as we expected! 💪 + +############################################################################## +# Intermezzo +# ---------- +# +# So far, we have discussed the definition of a tensor, how to combine them to create a tensor network, and how to calculate the complexity of the contraction operations. Hopefully, after this brief introduction, you will feel more comfortable whenever tensor networks are brought into the conversation. +# +# Perhaps you even feel motivated to dive deeper into the vast world of tensor networks! +# +# To help you with this endeavour, in the following sections we will summarize some ubiquitous algorithms used to connect tensor networks and quantum computers. These topics can become quite technical real fast, so we can only scratch the surface in this demo. For this reason, we will reference the relevant sources when pertinent. Now, take a sip of coffee, brace yourself, and let's continue! ☕️ + +############################################################################## +# .. _part_two: +# +# Connecting tensor networks and quantum circuits +# ----------------------------------------------------------- +# +# Contraction paths +# ~~~~~~~~~~~~~~~~~ +# +# In the previous section, we explored how the choice of the contraction path affects the computational cost of the tensor network contraction through a toy example. As shown in [#Lam1997]_, finding an optimal contraction path is equivalent to solving the "multiplication problem," and thus, it is in general NP-hard. In this section, we provide a general description of the widespread techniques used to tackle this ubiquitous task. +# +# .. note:: +# +# In special cases, by restricting the geometry and/or the values of the tensor networks, it is possible to find provably efficient contraction paths. A well-studied tensor network ansatz with efficient contraction schemes is the Matrix Product States (MPS) [#Schollwoeck2011]_. This section will, however, focus on tensor networks with arbitrary structures. +# +# First, we set up the framework of the problem. While multiway contractions—contractions between more than 2 tensors at a time—are possible, we will consider only pairwise contractions since the former can always be decomposed in terms of the latter. In addition, contracting a tensor network doesn't need to result in a single tensor. However, here we consider only the single tensor case as it underlies the more general scenario [#Gray2021]_. +# +# The underlying idea behind finding a contraction path is based on the construction of the computational graph, i.e., a rooted binary tree—also known as the **contraction tree**—that specifies the sequence of pairwise contractions to be executed. In this tree structure, a leaf node corresponds to a tensor from the original network (blue tensors) and the pairwise contractions (red lines) give rise to the intermediate tensors (red tensors) corresponding to the rest of the nodes in the contraction tree. +# +# .. figure:: ../_static/demonstration_assets/tn_basics/10-contraction-tree.png +# :align: center +# :width: 50% +# +# Transforming a tensor network with an arbitrary structure into this binary tree can be achieved by a **tree embedding** of the tensor network graph [#Bienstock1990]_. Thus, optimization of the contraction path is equivalent to a search over tree embeddings of the network. +# +# .. note:: +# +# Besides finding a contraction path that minimizes the computational cost, we can also attempt to find a path that optimizes the memory cost. That is a contraction path in which all intermediate tensors are below a certain size. +# +# Now, how do we traverse the space of trees to optimize over? The most straightforward idea is to perform an exhaustive search through all of them. As explained in [#Pfeifer2014]_, this can be done (with some additional improvements) using the following well-known algorithms: +# +# - Depth-first search +# - Breadth-first search +# - Dynamic programming +# +# While the exhaustive approach scales like :math:`\mathcal{O}(N!)`, with :math:`N` the number of tensors in the network, it can handle a handful of tensors within seconds, providing a good benchmark. In addition, compared to the following algorithms, the exhaustive search guarantees finding the global minimum and optimizing the desired metric - space and/or time. +# +# .. note:: +# +# A recursive implementation of the depth-first search is used by default in the well-known package ``opt_einsum`` `(see docs) `_. +# +# Further approaches introduced in [#Gray2021]_ are based on alternative common graph-theoretic tasks, rather than searching over the contraction tree space, such as the `balanced bi-partitioning `_ and `community detection `_ algorithms. And even though these are only heuristics that do not guarantee an optimal contraction path, they can often achieve an arbitrarily close to optimal performance. +# +# An extra level of optimization, known as **hyper-optimization**, is introduced by the use of different algorithms to find the optimal contraction based on the specific tensor network, as some algorithms are better suited for certain network structures. For an in-depth exploration of these heuristics, please refer to [#Gray2021]_. +# +# As we will explore in the next section, we can use tensor networks to simulate quantum circuits. In particular, the calculation of an expectation value corresponds to the contraction of the tensor network into a single tensor (scalar). In ``Pennylane``, this simulation can be performed using the :class:`~pennylane.devices.default_tensor.DefaultTensor` device, and the method used to find the contraction path can be chosen via the ``contraction_optimizer`` keyword argument. + +import pennylane as qml + +dev = qml.device("default.tensor", method="tn", contraction_optimizer="auto-hq") + +############################################################################## +# The different types of values accepted for ``contraction_optimizer`` are determined by the ``optimize`` parameter in ``Quimb`` (see `docs `_) as this is the backend behind the :class:`~pennylane.devices.default_tensor.DefaultTensor` device. See our `simulate quantum circuits with tensor networks demo `_ to learn more about the use of this device in ``Pennylane``. +# +# Slicing +# ^^^^^^^ +# +# The size of (intermediate) tensors can grow exponentially with the number of indices and dimensions, especially for large-scale tensor networks. Thus, we might run into memory problems when performing the contractions. A useful additional technique to split these tensors into more manageable pieces is known as **slicing**. +# +# The idea is to change space for computation time, by temporarily fixing the values of some indices in the tensors, performing independently the contraction for each fixed value, and summing the results [#Gray2021]_. +# +# To end this demo, let us answer the question: **how can we use tensor networks to simulate the output of a quantum circuit?** + +############################################################################## +# Quantum circuits are tensor networks +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Until now, we have looked at general tensor networks, while ✨sparkling✨ the discussions with examples related to quantum circuits. Here, we leverage the components we have learned to explore this relation more in-depth. +# +# First, it is important to emphasize that quantum circuits don't just "look" or "behave" like tensor networks, but rather they **are** tensor networks! Quantum circuits are a special class of tensor networks where each horizontal wire corresponds to the Hilbert space of a single qubit and the tensors acting on these subsystems are restricted to be unitary operators, denoting the time evolution of the state (from left to right). +# +# A general quantum circuit acting on :math:`N` qubits can be expressed in terms of the initial quantum state :math:`| \psi_0 \rangle` and the unitary propagator :math:`U` such that the evolved state is +# +# .. math:: +# | \psi \rangle = U |\psi_0 \rangle, +# +# which can also be depicted diagrammatically as +# +# .. figure:: ../_static/demonstration_assets/tn_basics/11-circuit.png +# :align: center +# :width: 45% +# +# In the right-hand side of the equality we have assumed a specific form for the :math:`U` tensor in terms of local 2-qubit gates, which is often the case when dealing with real quantum hardware. In addition, it is common for the initial state to be a product state such as :math:`|0\rangle^{\otimes N}`, hence the form of the tensor in the diagram as :math:`N` independent tensors of rank-1. However, an arbitrary input state is in general represented as one big rank-:math:`N` tensor. +# +# Now we can ask ourselves: what quantities can we compute from this tensor network? 🤔 +# +# Expectation values +# ^^^^^^^^^^^^^^^^^^ +# +# As anticipated in the previous section, a natural quantity to compute using the tensor network arising from a quantum circuit is the expectation value of an observable :math:`O` evaluated at the quantum state :math:`|\psi \rangle` +# +# .. math:: +# \langle O \rangle = \langle \psi | O | \psi \rangle = \langle \psi_0| U^\dagger O U |\psi_0 \rangle . +# +# If the observable is a linear combination of hermitian operators (e.g., a Hamiltonian) +# +# .. math:: +# O = \sum_i c_i O_i , +# +# we can calculate the total expectation value "naïvely" by computing the inner product for each component :math:`O_i` and summing up the weighted results: +# +# .. math:: +# O = \sum_i c_i \langle O_i \rangle = \sum_i c_i \langle \psi | O_i | \psi \rangle. +# +# However, it is possible to perform this operation more efficiently using tensor networks by means of a structure called Matrix Product Operator (MPO) [#Pirvu2010]_. The idea is to construct an efficient representation of the observable :math:`O` which can be contracted with the tensor network from :math:`|\psi \rangle`. Constructing these networks efficiently for Hamiltonians of arbitrary structure is an interesting task, which goes beyond the scope of this demo. +# +# When the observable of interest is *local*, i.e., it acts on a few neighbouring qubits, we can calculate the expectation value by considering only the section of the quantum circuit within the **reverse light cone** (causal cone) of the observable :math:`O_l`—i.e., the gates that affect the calculation of the expectation value. +# +# .. figure:: ../_static/demonstration_assets/tn_basics/12-expectation-local.png +# :align: center +# :width: 70% +# +# Then, the sections outside of the light cone (grayed-out gates in the figure above) can be ignored since these are contractions resulting in the identity: :math:`G G^\dagger = I`. This helps us decrease the size of the tensor to be contracted, and consequently, the computational expense, by focusing on the section of the circuit with support inside the light cone of the observable +# +# .. math:: +# \langle O_l \rangle = \langle \psi_l | O_l | \psi_l \rangle, +# +# where :math:`| \psi_l \rangle` is the section of the evolved state within the light cone of :math:`O_l`. +# +# Sampling +# ^^^^^^^^ +# +# In addition to calculating expectation values, we can also use the tensor network arising from a quantum circuit to sample bitstrings from the evolved probability distribution :math:`| \psi \rangle`—emulating what you would obtain from a real quantum computer. Since this is a ubiquitous task in quantum information, several algorithms have been proposed to generate samples from probability distributions represented as tensor networks. In particular, here we discuss the "Perfect Sampling Algorithm" applied to unitary tensor networks [#Ferris2012]_, as this generates uncorrelated samples, unlike Markov-based approaches. +# +# .. note:: +# The method used in `Quimb `_ (the backend behind :class:`~pennylane.devices.default_tensor.DefaultTensor`) to generate samples from the quantum circuit is also based on this algorithm. +# +# A cornerstone behind this algorithm is the well-known `chain rule `_, which allows us to write the joint probability of an event using only conditional probabilities. Using this, we can express the probability of sampling the bitstring :math:`(x_1, x_2, x_3, \ldots, x_N)` from :math:`| \psi \rangle` as +# +# .. math:: +# p(x_1, x_2, x_3, \ldots, x_N) = p(x_1) p(x_2|x_1) p(x_3| x_1 x_2) \ldots p(x_N | x_1 x_2 x_3 \ldots x_{N-1}). +# +# Thus, to obtain a sample from the joint distribution on the left side of the equation, we can compute the terms on the right side by means of marginal distributions. First, we start by computing the marginal probability :math:`p(x_1)`. To do so, we compute the reduced density matrix :math:`\rho_{1}` by tracing out all the other qubits: +# +# .. math:: +# \rho_{1} = \mathrm{Tr}_{2,3,\ldots,N}(| \psi \rangle \langle \psi |). +# +# Then, the diagonal of this :math:`2 \times 2` density matrix gives us the probability of sampling 0 or 1, i.e., :math:`p(x_1 = 0)` and :math:`p(x_1 = 1)`. This diagonal corresponds to the following probability vector +# +# .. math:: +# | p_{x_1} \rangle = \sum_{i=0}^{1} p(x_1=i) | i \rangle = \mathrm{diag}(\rho_1) = \mathrm{diag}\left( \mathrm{Tr}_{2,3,\ldots,N}(| \psi \rangle \langle \psi |) \right). +# +# The tensor network corresponding to the computation of this vector is +# +# .. figure:: ../_static/demonstration_assets/tn_basics/13-sample.png +# :align: center +# :width: 70% +# .. note:: +# In this diagram, we have extracted the diagonal of the reduced density matrix by contracting it with the COPY tensor introduced earlier in this demo! +# +# Once we obtain the probability vector, we can generate a random sample weighted by these probabilities. To do so, we generate a random number :math:`r \in [0,1]` and choose :math:`x_1 = 0` if :math:`r < p(x_1=0)` and :math:`x_1 = 1` otherwise. We save this sample as :math:`\hat{x}_1`. +# +# Next, we can calculate the following term :math:`p(x_2|\hat{x}_1)` conditioned on the sample we have just obtained. To accomplish this, we *project* the first qubit to be :math:`\hat{x}_1`. We can do this by contracting the computational basis state :math:`| \hat{x}_1 \rangle` with :math:`|\psi \rangle`, resulting in a smaller state :math:`|\psi_{x_1} \rangle`. Then, we can proceed exactly as we did in the previous step, calculating the reduced density matrix :math:`\rho_2` by tracing out the remaining qubits :math:`3,4,\ldots, N` and computing the probability vector from its diagonal +# +# .. math:: +# | p_{x_2 | \hat{x}_1} \rangle = \mathrm{diag} \left( \mathrm{Tr}_{3,4, \ldots,N}(| \psi_{x_1} \rangle \langle \psi_{x_1} |) \right). +# +# From this vector, we sample the next value :math:`\hat{x}_2` (just like we sampled :math:`\hat{x}_1`) and use it to compute the next term :math:`p(x_3| \hat{x}_1 \hat{x}_2)` using the same procedure. The following diagram shows the full tensor network for this step including the projection onto the computational basis state :math:`| \hat{x}_1 \rangle`. +# +# .. figure:: ../_static/demonstration_assets/tn_basics/14-sample-cntd.png +# :align: center +# :width: 70% +# +# Analogously as done with the expectation values, these contractions only involve the sections of the circuit within the light cone of *both* the projection with :math:`| \hat{x}_1 \rangle` and the contraction with the COPY tensor (diagonal computation). This procedure can be repeated recursively using the chain rule equation until we obtain the full bitstring :math:`(\hat{x}_1, \hat{x}_2, \hat{x}_3, \ldots, \hat{x}_N)`. Then to obtain more samples, we just repeat the procedure from the beginning! +# +# .. note:: +# By generating each bitstring independently from each other, i.e., by restarting the sampling algorithm without knowledge of the previous samples, we ensure perfect sampling from the probability distribution, contrary to other Markov-based algorithms [#Schuch2008]_ . We then say the sample is *memoryless*. +# +# We can reduce the computational cost of the sampling algorithm by **caching** results from previous contractions. When we draw a new sample that partially matches a previously explored configuration (marginal probability), we can reuse the cached results and avoid contracting this part of the network over again. +# +# For example, let's assume we have performed the perfect sampling algorithm once and obtained the sample :math:`0110`. If the next sample we need to generate starts with the substring :math:`01`, we can reuse the marginal probabilities up to :math:`p(x_3|01)` and only calculate the new parts of the sequence. The same caching idea can be applied to other tensor network algorithms involving many contractions. + +############################################################################## +# +# Conclusion +# ---------- +# +# And that is it for this demo! 🎉 +# +# Although the world of tensor networks and their relation to quantum computing is vastly wider than what we could ever cover in one demo, we hope that after these explanations you now feel equipped with the tools needed to dive deeper into this topic by yourself. +# +# If you want to learn more about using tensor networks as a diagrammatic tool, check out these amazing `lecture notes on quantum tensor networks `_ by J.Biamonte. In addition, check out the `Tensor Network website `_ for great explanations on many important algorithms and tensor network structures by Flatiron Institute. + +############################################################################## +# References +# ---------- +# +# .. [#Selinger2010] +# P. Selinger. +# "A Survey of Graphical Languages for Monoidal Categories", +# ``__, in *New Structures for Physics*, Springer Berlin Heidelberg, pp. 289–355, 2010. +# +# .. [#Arad2010] +# I. Arad and Z. Landau. +# "Quantum computation and the evaluation of tensor networks", +# ``__, 2010. +# +# .. [#Bridgeman2017] +# J. C. Bridgeman and C. T. Chubb. +# "Hand-waving and interpretive dance: an introductory course on tensor networks," +# ``__, Journal of Physics A: Mathematical and Theoretical, vol. 50, no. 22, 2017. +# +# .. [#Lam1997] +# C.-C. Lam, P. Sadayappan, and R. Wenger. +# "On Optimizing a Class of Multi-Dimensional Loops with Reduction for Parallel Execution", +# ``__, Parallel Processing Letters, Vol. 7, No. 2, pp. 157-168, 1997. +# +# .. [#Gray2021] +# J. Gray, S. Kourtis, and S. Choi. +# "Hyper-optimized tensor network contraction", +# ``__, Quantum, Vol. 5, pp. 410, 2021. +# +# .. [#Bienstock1990] +# D. Bienstock. +# "On embedding graphs in trees," +# ``__, Journal of Combinatorial Theory, Series B, vol. 49, no. 1, pp. 103–136, 1990. +# +# .. [#Pfeifer2014] +# R. N. C. Pfeifer, J. Haegeman, and F. Verstraete. +# "Faster identification of optimal contraction sequences for tensor networks", +# ``__, Physical Review +# +# .. [#Pirvu2010] +# B. Pirvu, V. Murg, J. I. Cirac, and F. Verstraete. +# "Matrix product operator representations," +# ``__, New Journal of Physics, vol. 12, no. 2, p. 025012, 2010. +# +# .. [#Ferris2012] +# A. J. Ferris and G. Vidal. +# "Perfect sampling with unitary tensor networks," +# ``__, Physical Review B, vol. 85, no. 16, 2012. +# +# .. [#Schuch2008] +# N. Schuch, M. M. Wolf, F. Verstraete, and J. I. Cirac. +# "Simulation of Quantum Many-Body Systems with Strings of Operators and Monte Carlo Tensor Contractions," +# ``__, Physical Review Letters, vol. 100, no. 4, Jan 2008. +# +# .. [#Schollwoeck2011] +# U. Schollwöck. +# "The density-matrix renormalization group in the age of matrix product states," +# ``__, Annals of Physics, vol. 326, no. 1, pp. 96–192, Jan 2011. + + + +############################################################################## +# About the author +# ---------------- +# +# diff --git a/demonstrations_v2/tutorial_tensor_network_basics/metadata.json b/demonstrations_v2/tutorial_tensor_network_basics/metadata.json new file mode 100644 index 0000000000..c150cc28a3 --- /dev/null +++ b/demonstrations_v2/tutorial_tensor_network_basics/metadata.json @@ -0,0 +1,172 @@ +{ + "title": "Introducing tensor networks for quantum practitioners", + "authors": [ + { + "username": "emiliano" + } + ], + "dateOfPublication": "2025-01-23T09:00:00+00:00", + "dateOfLastModification": "2025-01-23T09:00:00+00:00", + "categories": [ + "Getting Started", + "Quantum Computing", + "Algorithms" + ], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_tensor_network_basics.png" + }, + { + "type": "large_thumbnail", + "uri": "/_static/demo_thumbnails/large_demo_thumbnails/thumbnail_large_tensor_network_basics.png" + } + ], + "seoDescription": "Discover the fundamentals of tensor networks with this comprehensive demo. Learn how tensors generalize vectors and matrices, explore their intuitive diagrams, and see how they connect to quantum circuits, with insights for both beginners and advanced readers.", + "doi": "", + "references": [ + { + "id": "Selinger2010", + "type": "book", + "title": "A Survey of Graphical Languages for Monoidal Categories", + "authors": "P. Selinger", + "year": "2010", + "publisher": "Springer Berlin Heidelberg", + "journal": "New Structures for Physics", + "doi": "10.1007/978-3-642-12821-9_4", + "url": "http://dx.doi.org/10.1007/978-3-642-12821-9_4" + }, + { + "id": "Arad2010", + "type": "preprint", + "title": "Quantum computation and the evaluation of tensor networks", + "authors": "I. Arad, Z. Landau", + "year": "2010", + "publisher": "", + "journal": "", + "doi": "10.48550/arXiv.0805.0040", + "url": "https://arxiv.org/abs/0805.0040" + }, + { + "id": "Bridgeman2017", + "type": "article", + "title": "Hand-waving and interpretive dance: an introductory course on tensor networks", + "authors": "J. C. Bridgeman, C. T. Chubb", + "year": "2017", + "publisher": "", + "journal": "Journal of Physics A: Mathematical and Theoretical", + "doi": "10.1088/1751-8121/aa6dc3", + "url": "http://dx.doi.org/10.1088/1751-8121/aa6dc3" + }, + { + "id": "Lam1997", + "type": "article", + "title": "On Optimizing a Class of Multi-Dimensional Loops with Reduction for Parallel Execution", + "authors": "C.-C. Lam, P. Sadayappan, R. Wenger", + "year": "1997", + "publisher": "", + "journal": "Parallel Processing Letters", + "doi": "10.1142/S0129626497000176", + "url": "https://doi.org/10.1142/S0129626497000176" + }, + { + "id": "Gray2021", + "type": "article", + "title": "Hyper-optimized tensor network contraction", + "authors": "J. Gray, S. Kourtis, S. Choi", + "year": "2021", + "publisher": "", + "journal": "Quantum", + "doi": "10.22331/q-2021-03-15-410", + "url": "https://quantum-journal.org/papers/q-2021-03-15-410/" + }, + { + "id": "Bienstock1990", + "type": "article", + "title": "On embedding graphs in trees", + "authors": "Dan Bienstock", + "year": "1990", + "journal": "Journal of Combinatorial Theory, Series B", + "volume": "49", + "number": "1", + "pages": "103–136", + "doi": "10.1016/0095-8956(90)90066-9", + "url": "https://www.sciencedirect.com/science/article/pii/0095895690900669" + }, + { + "id": "Pfeifer2014", + "type": "article", + "title": "Faster identification of optimal contraction sequences for tensor networks", + "authors": "R. N. C. Pfeifer, J. Haegeman, F. Verstraete", + "year": "2014", + "publisher": "", + "journal": "Physical Review E", + "doi": "10.1103/PhysRevE.90.033315", + "url": "http://dx.doi.org/10.1103/PhysRevE.90.033315" + }, + { + "id": "Pirvu2010", + "type": "article", + "title": "Matrix product operator representations", + "authors": "B. Pirvu, V. Murg, J. I. Cirac, F. Verstraete", + "year": "2010", + "publisher": "", + "journal": "New Journal of Physics", + "doi": "10.1088/1367-2630/12/2/025012", + "url": "http://dx.doi.org/10.1088/1367-2630/12/2/025012" + }, + { + "id": "Ferris2012", + "type": "article", + "title": "Perfect sampling with unitary tensor networks", + "authors": "A. J. Ferris, G. Vidal", + "year": "2012", + "publisher": "", + "journal": "Physical Review B", + "doi": "10.1103/PhysRevB.85.165146", + "url": "http://dx.doi.org/10.1103/PhysRevB.85.165146" + }, + { + "id": "Schuch2008", + "type": "article", + "title": "Simulation of Quantum Many-Body Systems with Strings of Operators and Monte Carlo Tensor Contractions", + "authors": "N. Schuch, M. M. Wolf, F. Verstraete, J. I. Cirac", + "year": "2008", + "publisher": "", + "journal": "Physical Review Letters", + "doi": "10.1103/PhysRevLett.100.040501", + "url": "https://doi.org/10.1103/PhysRevLett.100.040501" + }, + { + "id": "Schollwoeck2011", + "type": "article", + "title": "The density-matrix renormalization group in the age of matrix product states", + "authors": "U. Schollwöck", + "year": "2011", + "publisher": "", + "journal": "Annals of Physics", + "doi": "10.1016/j.aop.2010.09.012", + "url": "https://doi.org/10.1016/j.aop.2010.09.012" + } + ], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [ + { + "type": "demonstration", + "id": "tutorial_tn_circuits", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_mps", + "weight": 1.0 + }, + { + "type": "demonstration", + "id": "tutorial_How_to_simulate_quantum_circuits_with_tensor_networks", + "weight": 1.0 + } + ] +} \ No newline at end of file diff --git a/demonstrations_v2/tutorial_tensor_network_basics/requirements.in b/demonstrations_v2/tutorial_tensor_network_basics/requirements.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/demonstrations_v2/tutorial_testing_symmetry/demo.py b/demonstrations_v2/tutorial_testing_symmetry/demo.py index d3bffa531e..61e0776885 100644 --- a/demonstrations_v2/tutorial_testing_symmetry/demo.py +++ b/demonstrations_v2/tutorial_testing_symmetry/demo.py @@ -465,4 +465,4 @@ def asymm(hamiltonian, time): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/david_wakeham.txt +# diff --git a/demonstrations_v2/tutorial_tn_circuits/demo.py b/demonstrations_v2/tutorial_tn_circuits/demo.py index e317690f13..3ec8c94378 100644 --- a/demonstrations_v2/tutorial_tn_circuits/demo.py +++ b/demonstrations_v2/tutorial_tn_circuits/demo.py @@ -416,12 +416,6 @@ def costfunc(params): # 4916, URL https://www.sciencedirect.com/science/article/pii/S0003491614001596. # # -# About the authors -# ^^^^^^^^^^^^^^^^^ -# .. include:: ../_static/authors/diego_guala.txt +# About the author +# ---------------- # -# .. include:: ../_static/authors/esther_cruz-rico.txt -# -# .. include:: ../_static/authors/shaoming_zhang.txt -# -# .. include:: ../_static/authors/juan_miguel_arrazola.txt diff --git a/demonstrations_v2/tutorial_toric_code/demo.py b/demonstrations_v2/tutorial_toric_code/demo.py index 6ac3f0f783..b3fd289b5e 100644 --- a/demonstrations_v2/tutorial_toric_code/demo.py +++ b/demonstrations_v2/tutorial_toric_code/demo.py @@ -1014,4 +1014,4 @@ def hadamard_test(x_prep, z_prep, x_loop, z_loop): # # About the author # ---------------- -# .. include:: ../_static/authors/christina_lee.txt +# diff --git a/demonstrations_v2/tutorial_trapped_ions/demo.py b/demonstrations_v2/tutorial_trapped_ions/demo.py index 507d970bdc..690fa3e378 100644 --- a/demonstrations_v2/tutorial_trapped_ions/demo.py +++ b/demonstrations_v2/tutorial_trapped_ions/demo.py @@ -1101,5 +1101,5 @@ def cnot_gate(basis_state): # (`arXiv `__) # # About the author -# ~~~~~~~~~~~~~~~~ -# .. include:: ../_static/authors/alvaro_ballon.txt +# ---------------- +# diff --git a/demonstrations_v2/tutorial_univariate_qvr/demo.py b/demonstrations_v2/tutorial_univariate_qvr/demo.py index 1507de82cd..14a4e46b7f 100644 --- a/demonstrations_v2/tutorial_univariate_qvr/demo.py +++ b/demonstrations_v2/tutorial_univariate_qvr/demo.py @@ -1264,7 +1264,6 @@ def testing_workflow( # (2020) # # -# About the authors +# About the author # ---------------- -# .. include:: ../_static/authors/jack_stephen_baker.txt -# .. include:: ../_static/authors/santosh_kumar_radha.txt +# diff --git a/demonstrations_v2/tutorial_variational_classifier/demo.py b/demonstrations_v2/tutorial_variational_classifier/demo.py index 5af2347883..a979f5011f 100644 --- a/demonstrations_v2/tutorial_variational_classifier/demo.py +++ b/demonstrations_v2/tutorial_variational_classifier/demo.py @@ -554,4 +554,4 @@ def cost(weights, bias, X, Y): # # About the author # ---------------- -# .. include:: ../_static/authors/maria_schuld.txt +# diff --git a/demonstrations_v2/tutorial_vqe/demo.py b/demonstrations_v2/tutorial_vqe/demo.py index 5795b87464..baba6119e6 100644 --- a/demonstrations_v2/tutorial_vqe/demo.py +++ b/demonstrations_v2/tutorial_vqe/demo.py @@ -292,4 +292,4 @@ def cost_fn(param): # # About the author # ---------------- -# .. include:: ../_static/authors/alain_delgado.txt +# diff --git a/demonstrations_v2/tutorial_vqe_qng/demo.py b/demonstrations_v2/tutorial_vqe_qng/demo.py index b3de324702..d47e556d49 100644 --- a/demonstrations_v2/tutorial_vqe_qng/demo.py +++ b/demonstrations_v2/tutorial_vqe_qng/demo.py @@ -473,10 +473,6 @@ def cost(params): # `__ # # -# About the authors -# ----------------- -# .. include:: ../_static/authors/maggie_li.txt -# -# .. include:: ../_static/authors/lana_bozanic.txt -# -# .. include:: ../_static/authors/sukin_sim.txt \ No newline at end of file +# About the author +# ---------------- +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py b/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py index a65b93352c..8a66f57ed0 100644 --- a/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py +++ b/demonstrations_v2/tutorial_vqe_spin_sectors/demo.py @@ -49,7 +49,7 @@ nuclear coordinates in `atomic units `_. """ -from pennylane import numpy as np +import numpy as np symbols = ["H", "H"] coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) @@ -191,18 +191,17 @@ def circuit(params, wires): # a cost function that can be evaluated with the circuit parameters: -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_fn(params): circuit(params, wires=range(qubits)) return qml.expval(H) - ############################################################################## # As a reminder, we also built the total spin operator :math:`\hat{S}^2` for which # we can now define a function to compute its expectation value: -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def S2_exp_value(params): circuit(params, wires=range(qubits)) return qml.expval(S2) @@ -227,35 +226,40 @@ def total_spin(params): # Now, we proceed to minimize the cost function to find the ground state. We define # the classical optimizer and initialize the circuit parameters. -opt = qml.GradientDescentOptimizer(stepsize=0.8) -np.random.seed(0) # for reproducibility -theta = np.random.normal(0, np.pi, len(singles) + len(doubles), requires_grad=True) -print(theta) +from jax import random +import optax + +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +key = random.PRNGKey(0) +init_params = random.normal(key, shape=(len(singles) + len(doubles),)) * np.pi ############################################################################## # We carry out the optimization over a maximum of 100 steps, aiming to reach a # convergence tolerance of :math:`10^{-6}` for the value of the cost function. +import catalyst max_iterations = 100 -conv_tol = 1e-06 - -for n in range(max_iterations): - - theta, prev_energy = opt.step_and_cost(cost_fn, theta) - energy = cost_fn(theta) - spin = total_spin(theta) +prev_energy = 0.0 +@qml.qjit +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(cost_fn)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) - conv = np.abs(energy - prev_energy) +loss_history = [] - if n % 4 == 0: - print(f"Step = {n}, Energy = {energy:.8f} Ha, S = {spin:.4f}") +opt_state = opt.init(init_params) +params = init_params - if conv <= conv_tol: - break +for i in range(max_iterations): + params, opt_state = update_step(i, params, opt_state) + energy = cost_fn(params) print("\n" f"Final value of the ground-state energy = {energy:.8f} Ha") -print("\n" f"Optimal value of the circuit parameters = {theta}") +print("\n" f"Optimal value of the circuit parameters = {params}") ############################################################################## # As a result, we have estimated the lowest-energy state of the hydrogen molecule @@ -304,13 +308,13 @@ def circuit(params, wires): # and the total spin operator for the new circuit. -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def cost_fn(params): circuit(params, wires=range(qubits)) return qml.expval(H) -@qml.qnode(dev, interface="autograd") +@qml.qnode(dev, interface="jax") def S2_exp_value(params): circuit(params, wires=range(qubits)) return qml.expval(S2) @@ -320,29 +324,31 @@ def S2_exp_value(params): # Finally, we generate the new set of initial parameters, and proceed with the VQE algorithm to # optimize the variational circuit. -np.random.seed(0) -theta = np.random.normal(0, np.pi, len(singles) + len(doubles), requires_grad=True) +opt = optax.sgd(learning_rate=0.8) # sgd stands for StochasticGradientDescent +key = random.PRNGKey(0) +init_params = random.normal(key, shape=(len(singles) + len(doubles),)) * np.pi max_iterations = 100 -conv_tol = 1e-06 -for n in range(max_iterations): +@qml.qjit +def update_step(i, params, opt_state): + """Perform a single gradient update step""" + grads = qml.grad(cost_fn)(params) + updates, opt_state = opt.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return (params, opt_state) - theta, prev_energy = opt.step_and_cost(cost_fn, theta) +loss_history = [] - energy = cost_fn(theta) - spin = total_spin(theta) +opt_state = opt.init(init_params) +params = init_params - conv = np.abs(energy - prev_energy) - - if n % 4 == 0: - print(f"Step = {n}, Energy = {energy:.8f} Ha, S = {spin:.4f}") - - if conv <= conv_tol: - break +for i in range(max_iterations): + params, opt_state = update_step(i, params, opt_state) + energy = cost_fn(params) print("\n" f"Final value of the energy = {energy:.8f} Ha") -print("\n" f"Optimal value of the circuit parameters = {theta}") +print("\n" f"Optimal value of the circuit parameters = {params}") ############################################################################## # As expected, the VQE algorithms has found the lowest-energy state with total spin @@ -376,4 +382,4 @@ def S2_exp_value(params): # # About the author # ---------------- -# .. include:: ../_static/authors/alain_delgado.txt +# diff --git a/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json b/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json index e8afdd4075..e656601199 100644 --- a/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json +++ b/demonstrations_v2/tutorial_vqe_spin_sectors/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-10-13T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-12-13T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/demonstrations_v2/tutorial_vqe_vqd/demo.py b/demonstrations_v2/tutorial_vqe_vqd/demo.py index cb618a7887..f894077297 100644 --- a/demonstrations_v2/tutorial_vqe_vqd/demo.py +++ b/demonstrations_v2/tutorial_vqe_vqd/demo.py @@ -100,7 +100,7 @@ def circuit(): from functools import partial # This line is added to better visualise the circuit -@partial(qml.devices.preprocess.decompose, stopping_condition = lambda obj:False, max_expansion=1) +@partial(qml.transforms.decompose, max_expansion=1) def ansatz(theta, wires): singles, doubles = qml.qchem.excitations(2, n_qubits) diff --git a/demonstrations_v2/tutorial_vqe_vqd/metadata.json b/demonstrations_v2/tutorial_vqe_vqd/metadata.json index cfd303ebac..3b497a13a3 100644 --- a/demonstrations_v2/tutorial_vqe_vqd/metadata.json +++ b/demonstrations_v2/tutorial_vqe_vqd/metadata.json @@ -9,7 +9,7 @@ } ], "dateOfPublication": "2024-08-26T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2024-11-18T00:00:00+00:00", "categories": [ "Quantum Chemistry", "How-to" diff --git a/demonstrations_v2/tutorial_vqls/demo.py b/demonstrations_v2/tutorial_vqls/demo.py index d6682fd0f6..0837b7ebae 100644 --- a/demonstrations_v2/tutorial_vqls/demo.py +++ b/demonstrations_v2/tutorial_vqls/demo.py @@ -530,4 +530,4 @@ def prepare_and_sample(weights): # # About the author # ---------------- -# .. include:: ../_static/authors/andrea_mari.txt \ No newline at end of file +# \ No newline at end of file diff --git a/demonstrations_v2/tutorial_vqt/demo.py b/demonstrations_v2/tutorial_vqt/demo.py index 4ffce13164..b29ddc7fc5 100644 --- a/demonstrations_v2/tutorial_vqt/demo.py +++ b/demonstrations_v2/tutorial_vqt/demo.py @@ -572,4 +572,4 @@ def trace_distance(one, two): # # About the author # ---------------- -# .. include:: ../_static/authors/jack_ceroni.txt +# diff --git a/demonstrations_v2/tutorial_zx_calculus/demo.py b/demonstrations_v2/tutorial_zx_calculus/demo.py index b8faff5014..a63330a5eb 100644 --- a/demonstrations_v2/tutorial_zx_calculus/demo.py +++ b/demonstrations_v2/tutorial_zx_calculus/demo.py @@ -867,4 +867,4 @@ def mod_5_4(): # # About the author # ---------------- -# .. include:: ../_static/authors/romain_moyard.txt +# diff --git a/demonstrations_v2/vqe_parallel/demo.py b/demonstrations_v2/vqe_parallel/demo.py index 89a64e91f7..7c298007e8 100644 --- a/demonstrations_v2/vqe_parallel/demo.py +++ b/demonstrations_v2/vqe_parallel/demo.py @@ -15,6 +15,9 @@ *Author: Tom Bromley — Posted: 14 February 2020. Last updated: 29 August 2023.* +.. warning:: + This demo requires Python <=3.10 and uses the PennyLane-Rigetti plugin, which is only compatible with PennyLane v0.40 or below. To run this demo with newer versions of PennyLane, you will need to use `a different simulator device `__. + This tutorial showcases how using asynchronously-evaluated parallel QPUs can speed up the calculation of the potential energy surface of molecular hydrogen (:math:`H_2`). @@ -79,7 +82,7 @@ print("Number of terms: {}\n".format(len(h_ops))) for op in h_ops: - print("Measurement {} on wires {}".format(op.name, op.wires)) + print("Measurement {} on wires {}".format(str(op), op.wires)) ############################################################################## # .. rst-class:: sphx-glr-script-out @@ -89,21 +92,21 @@ # # Number of terms: 15 # -# Measurement Identity on wires -# Measurement PauliZ on wires -# Measurement PauliZ on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliY', 'PauliX', 'PauliX', 'PauliY'] on wires -# Measurement ['PauliY', 'PauliY', 'PauliX', 'PauliX'] on wires -# Measurement ['PauliX', 'PauliX', 'PauliY', 'PauliY'] on wires -# Measurement ['PauliX', 'PauliY', 'PauliY', 'PauliX'] on wires -# Measurement PauliZ on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement PauliZ on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliZ', 'PauliZ'] on wires -# Measurement ['PauliZ', 'PauliZ'] on wires +# Measurement I(0) on wires Wires([0]) +# Measurement Z(0) on wires Wires([0]) +# Measurement Z(1) on wires Wires([1]) +# Measurement Z(0) @ Z(1) on wires Wires([0, 1]) +# Measurement Y(0) @ X(1) @ X(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement Y(0) @ Y(1) @ X(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ X(1) @ Y(2) @ Y(3) on wires Wires([0, 1, 2, 3]) +# Measurement X(0) @ Y(1) @ Y(2) @ X(3) on wires Wires([0, 1, 2, 3]) +# Measurement Z(2) on wires Wires([2]) +# Measurement Z(0) @ Z(2) on wires Wires([0, 2]) +# Measurement Z(3) on wires Wires([3]) +# Measurement Z(0) @ Z(3) on wires Wires([0, 3]) +# Measurement Z(1) @ Z(2) on wires Wires([1, 2]) +# Measurement Z(1) @ Z(3) on wires Wires([1, 3]) +# Measurement Z(2) @ Z(3) on wires Wires([2, 3]) ############################################################################## # Defining the energy function @@ -387,4 +390,4 @@ def compute_energy_parallel_optimized(H, devs, param): ############################################################################## # About the author # ---------------- -# .. include:: ../_static/authors/thomas_bromley.txt +# diff --git a/demonstrations_v2/vqe_parallel/metadata.json b/demonstrations_v2/vqe_parallel/metadata.json index d27d1dd51b..e05599b7a2 100644 --- a/demonstrations_v2/vqe_parallel/metadata.json +++ b/demonstrations_v2/vqe_parallel/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2020-02-14T00:00:00+00:00", - "dateOfLastModification": "2024-10-07T00:00:00+00:00", + "dateOfLastModification": "2025-01-14T00:00:00+00:00", "categories": [ "Quantum Chemistry" ], diff --git a/lib/qml/app/app.py b/lib/qml/app/app.py index 49a4c5cbda..405480880d 100644 --- a/lib/qml/app/app.py +++ b/lib/qml/app/app.py @@ -67,7 +67,7 @@ def sync_v2(): v2_demo_dir.mkdir() shutil.copy2(v1_demo, v2_demo) shutil.copy2(v1_metadata, v2_metadata) - with open(v2_demo / "requirements.in", "w"): + with open(v2_demo_dir / "requirements.in", "w"): pass print( From 60e982eb56c73047cc5725f980d984a1874569ae Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 13:00:24 -0500 Subject: [PATCH 18/29] fix v2 --- lib/qml/app/app.py | 2 -- pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/qml/app/app.py b/lib/qml/app/app.py index 405480880d..c7bfffff21 100644 --- a/lib/qml/app/app.py +++ b/lib/qml/app/app.py @@ -67,8 +67,6 @@ def sync_v2(): v2_demo_dir.mkdir() shutil.copy2(v1_demo, v2_demo) shutil.copy2(v1_metadata, v2_metadata) - with open(v2_demo_dir / "requirements.in", "w"): - pass print( f"Copied new demo {v1_demo} to {v2_demo_dir}. Please updated the requirements.in file." diff --git a/pyproject.toml b/pyproject.toml index 08066ea478..b095f7a491 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ numpy = "~1.24" pyyaml = "^6.0.1" pennylane-sphinx-theme = { git = "https://github.com/PennyLaneAI/pennylane-sphinx-theme.git", branch = "sphinx-update" } pypandoc = "1.5" -pennylane = "0.39.0" +pennylane = "0.40.0" [tool.poetry.group.executable-dependencies.dependencies] ########################################################### From 424b4b0fa15dce80657b11522018967928050ac7 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 15:47:54 -0500 Subject: [PATCH 19/29] clean conf.py --- conf.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/conf.py b/conf.py index 18e1cdf5b9..b18834efad 100644 --- a/conf.py +++ b/conf.py @@ -19,7 +19,6 @@ from jinja2 import FileSystemLoader, Environment import yaml from pennylane import PennyLaneDeprecationWarning -from pathlib import Path sys.path.insert(0, os.path.abspath(".")) sys.path.insert(0, os.path.join(os.path.dirname(__file__))) @@ -86,8 +85,6 @@ "doc_module" : ("pennylane"), "junit": "../test-results/sphinx-gallery/junit.xml", "reset_modules": ("module_resets.reset_jax", "matplotlib", "seaborn"), - "show_signature": False, - 'download_all_examples': False, } From 70797e244e907aa7f70c97982caa7b4861908fda Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 16:17:28 -0500 Subject: [PATCH 20/29] add requirements-parser --- poetry.lock | 28 +++++++++++++++++++++++++++- pyproject.toml | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 18fd8a9163..489914587b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6486,6 +6486,21 @@ files = [ [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "requirements-parser" +version = "0.11.0" +description = "This is a small Python module for parsing Pip requirement files." +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "requirements_parser-0.11.0-py3-none-any.whl", hash = "sha256:50379eb50311834386c2568263ae5225d7b9d0867fb55cf4ecc93959de2c2684"}, + {file = "requirements_parser-0.11.0.tar.gz", hash = "sha256:35f36dc969d14830bf459803da84f314dc3d17c802592e9e970f63d0359e5920"}, +] + +[package.dependencies] +packaging = ">=23.2" +types-setuptools = ">=69.1.0" + [[package]] name = "retworkx" version = "0.16.0" @@ -8198,6 +8213,17 @@ files = [ {file = "types_retry-0.9.9.20241221.tar.gz", hash = "sha256:ebad6d495a5a04ab0d06d4156a665528c3b84a8461aa019dd6e5d3e33c2aa1e0"}, ] +[[package]] +name = "types-setuptools" +version = "75.8.0.20250110" +description = "Typing stubs for setuptools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types_setuptools-75.8.0.20250110-py3-none-any.whl", hash = "sha256:a9f12980bbf9bcdc23ecd80755789085bad6bfce4060c2275bc2b4ca9f2bc480"}, + {file = "types_setuptools-75.8.0.20250110.tar.gz", hash = "sha256:96f7ec8bbd6e0a54ea180d66ad68ad7a1d7954e7281a710ea2de75e355545271"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -9068,4 +9094,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "c1fd3a82c5d0fc610e7453408c13d5b194b0d4e6f891da0f2dc9583172248cdc" +content-hash = "c97a9a3f3641038a1fc1477299576e1f176418a9cb122b036df1e925eb2998e7" diff --git a/pyproject.toml b/pyproject.toml index b095f7a491..a33898b021 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ typer = "^0.15.1" poetry = "^1.8.5" poetry-plugin-export = "^1.8.0" dulwich = "<0.22" +requirements-parser = "^0.11.0" [tool.poetry.group.base.dependencies] # Base dependencies needed to build the website without any code execution (*-norun) @@ -56,7 +57,6 @@ pennylane-qulacs = "0.40.0" pennylane-catalyst = "0.10.0" ########################################################## - matplotlib = "3.7.2" jax = "0.4.28" jaxlib = "0.4.28" From 00f517ce15815f960c77699147f7ba1c1cb64b9c Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Mon, 27 Jan 2025 16:43:15 -0500 Subject: [PATCH 21/29] qiskit-aer spec --- poetry.lock | 49 ++++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 2 +- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 18fd8a9163..ab50c7b7eb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6105,16 +6105,51 @@ visualization = ["Pillow (>=4.2.1)", "matplotlib (>=3.3)", "pydot", "pylatexenc [[package]] name = "qiskit-aer" -version = "0.16.0" +version = "0.15.1" description = "Aer - High performance simulators for Qiskit" optional = false python-versions = ">=3.7" files = [ - {file = "qiskit_aer-0.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd540c47f10d13b03f54003344aa55cc350ea5901780282f85dc01fc3131df39"}, - {file = "qiskit_aer-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d4be301e9252d8dce421e0a958e5dec254096564f967f3a93f7311ee9c311e1"}, - {file = "qiskit_aer-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60dcae3da0dbb158ff65438467c17bd5b71933c3a778c758b96614e43af5b4b6"}, - {file = "qiskit_aer-0.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fff96ccd3743f4b61f294953554775fcd2ea7030cf2d229b897ca53d4d5ddfe"}, - {file = "qiskit_aer-0.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4900214e33e6902a10669d60bfd0d0a7c82a61329b3a171273f494e295c9dc43"}, + {file = "qiskit-aer-0.15.1.tar.gz", hash = "sha256:45f320790c9239bbe781a1ee14a329a20ad08878f01746fe405c836d202b2560"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8c403b4895ac3f00fe55e72473b3f4e4fbc8840f93c75d4a33da5de4230dfef"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ad6cf30554cde3ae27850082c3673113385a5ee40b387557d306f35576c5d44"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02e2af134eb72bf3cde1fd959701655a392a53236d9bb9658278cba520a83aae"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5083333de4838da9436ceb76b6f964fb3184a8756561586bde03a4aa5fccf723"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d42260fad7c81d71a12870f2269a959e1c782bc72ba14c85cf107d87e53a13ce"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f07b6f937bde64cb88d037e8805cdd3b6e2985231ac7dd18f27a7af4aa653a2c"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-win32.whl", hash = "sha256:354dd010928cf2f72a92a133ff906c5d173262e6d25d06bb5823d869e2fded93"}, + {file = "qiskit_aer-0.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d5948c3f910a3f4b7e997ce8e80ca7376715b1f3556244da0c84bd7d2e4b081"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6613f1238fba954e744a16e10c61732765541fde42f17029038d0d96b78ba6ee"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:83198f4a7b9949008297675725e9fec01ba47e9d7eec3f755c3eb720aaf78932"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196c8de494ff26195ef6fb40f5c9672b6281ab3fd768dc1f1866e7b3968c4d98"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:334f8b323dd06793b11ad8aa8c7bcd56819e696017ce421db3cdc3c48f9da53e"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4cb7d606808d7b437b783d1d9ded20063ce86e463736b7d6201a93caccf050e"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabe19cfe9a93b76801da31e81b12671a301e0873d7eaf077d06c92e11394136"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-win32.whl", hash = "sha256:601ee3ad01a2aeef489f146ed0baf62965465b47324786ba88d80a1293740ac2"}, + {file = "qiskit_aer-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:8d8685f23b844352a3f8f2991adaba91a43515e8883cd1cbdc654b4c61d104a9"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df16643006cf25a1ed477a120b6146859f09e8dee09ca720befb3a1febee9546"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d22c96bae21dbe4b97c30785ead2c2b53f897938da49bca6b4ef29d187765a6"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1288fd5c36235f5fedfc228956049e87bdd804cbc2b3a487a4453d9e7e72f420"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0783607942c724329172e21b53354c9d569420e02dbfd06c407ed588833cf6"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbad79290e4b850dca163b7960769a1a8db6d44abf232ecf0a6ce88740c83ab9"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2257b4828df8cb3f37e153c220cd72f54a81d89875711efbc3ac2f265e0ae4a"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-win32.whl", hash = "sha256:0f8a3f97f1bbeabb7d229879f7a0b6b8709f864fbc13ae78ec1569a65033ea3b"}, + {file = "qiskit_aer-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:af6501808b584b764e959af7a1edb2ef890c9a78f1ce418921dbdf6fd09ce0fc"}, + {file = "qiskit_aer-0.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:adf939bfd5997043ce9910ffe9025b471f535df961ec58cf3de1627c6937eb2b"}, + {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d3442bd809ca825a3f94d39ec0a3a2d2b32518c20dba4b80d365aebbee455b8"}, + {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d346e8ee8df20fafb60158e843fac3c86f5b427ae5fc2fbfac9d48f99374abeb"}, + {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ff845bc0fd290e5ea931fc0f359016cc1a31de6cf5bc21618968db8f8c7b295"}, + {file = "qiskit_aer-0.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91fbcdf34aa2dccc4424c7a3cae609b8b11cb6ee31bff33f4b54fd05f36d2f00"}, + {file = "qiskit_aer-0.15.1-cp38-cp38-win32.whl", hash = "sha256:45ef73adf280205e4a48b3be18b5d8d4e9d89ab5ac57a76daa58f6fa684c5c30"}, + {file = "qiskit_aer-0.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:4d32d6a90598e0ce529637622af077860ebc09d50b3b3ce0474a1659f9651f13"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0eb19c9cc3ff0293a6967ff0a36479383d1b15f5e20d4a63d01bc7804c62b580"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:00a26359b34bfe070549b3bf6b5a4c51d9cc16d47381a4f55bc886a55dd101f9"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f655025da0326bf2ac86757ab75db83922cdcfd67d062e345745fa9b1273aae"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a77fa5ff6c3f6210bc46de61b407c928ff3ed47c0ea6eabe94a5ba714eeff76"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad0c9982705a7bff81cd6edd157f9bbd907ab5256a6d6a3203a4a2759467ddc8"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdd2f74ac62b197a18b6846c3f1c90a85d6aa3daa7889ec380dfa3a10473627"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-win32.whl", hash = "sha256:347ce7c735b926a9cd9cb8e7cfa9446d7b46f0ea7f236ddb16d96445651f2fc1"}, + {file = "qiskit_aer-0.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:c97db2386e4236643c63b6ffa922aa7be8764746a6fd89158012d9947dabdcbb"}, ] [package.dependencies] @@ -9068,4 +9103,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "c1fd3a82c5d0fc610e7453408c13d5b194b0d4e6f891da0f2dc9583172248cdc" +content-hash = "199761c35618827dec96ce2c70d46d432404fbc5f22d0dffad92f9fe6d4b3848" diff --git a/pyproject.toml b/pyproject.toml index b095f7a491..81ecbbcd0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ aiohttp = "3.9.5" fsspec = "2024.6.1" h5py = "3.11.0" qiskit = ">=1.0.0" -qiskit-aer = ">=0.14.0" +qiskit-aer = ">=0.14.0,<0.16.0" sphinxcontrib-applehelp = "1.0.8" From 8b828cee0a3fabd4ad01eb146dbc3eac949c7064 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 31 Jan 2025 13:57:04 -0500 Subject: [PATCH 22/29] single demo builds --- conf.py | 3 +- constraints.txt | 26 + .../covalent_cloud_gpu/requirements.in | 2 +- .../ml_classical_shadows/requirements.in | 2 +- .../requirements.in | 2 +- .../tutorial_QUBO/requirements.in | 2 +- .../tutorial_bluequbit/requirements.in | 2 +- .../tutorial_error_mitigation/requirements.in | 2 +- .../requirements.in | 2 +- .../tutorial_rl_pulse/requirements.in | 4 +- .../tutorial_vqe_vqd/requirements.in | 2 + .../tutorial_vqt/requirements.in | 2 +- lib/qml/app/app.py | 1 + lib/qml/lib/cmds.py | 2 +- lib/qml/lib/demo.py | 95 ++- lib/qml/lib/pip_tools.py | 97 +++ poetry.lock | 604 ++++++++++-------- pyproject.toml | 2 + 18 files changed, 560 insertions(+), 292 deletions(-) create mode 100644 constraints.txt create mode 100644 lib/qml/lib/pip_tools.py diff --git a/conf.py b/conf.py index b18834efad..f2bdb84e40 100644 --- a/conf.py +++ b/conf.py @@ -58,12 +58,13 @@ html_baseurl = "https://pennylane.ai/qml/" demo_staging_dir = os.getenv("DEMO_STAGING_DIR", "demonstrations") +gallery_output_dir = os.getenv("GALLERY_OUTPUT_DIR", "demos") sphinx_gallery_conf = { # path to your example scripts "examples_dirs": [demo_staging_dir], # path where to save gallery generated examples - "gallery_dirs": ["demos"], + "gallery_dirs": [gallery_output_dir], # execute files that match the following filename pattern, # and skip those that don't. If the following option is not provided, # all example scripts in the 'examples_dirs' folder will be skiped. diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000000..d0f1dd2c21 --- /dev/null +++ b/constraints.txt @@ -0,0 +1,26 @@ +pennylane==0.40.0 +pennylane-cirq==0.40.0 +pennylane-qiskit==0.40.0 +pennylane-qulacs==0.40.0 +pennylane-catalyst==0.10.0 +pennylane-qrack==0.11.1 +pyqrack==1.32.12 +numpy~=1.24 +matplotlib==3.7.2 +jax==0.4.28 +jaxlib==0.4.28 +jaxopt==0.8.3 +aiohttp==3.9.5 +fsspec==2024.6.1 +h5py==3.11.0 +openqaoa-core==0.2.5 +qiskit>=1.0.0 +qiskit-aer>=0.14.0,<0.16.0 +qiskit_ibm_runtime==0.29.0 +torch==2.1.2+cpu ; sys_platform != 'darwin' +torch==2.1.2 ; sys_platform == 'darwin' +torchvision==0.16.2+cpu ; sys_platform != 'darwin' +torchvision==0.16.2 ; sys_platform == 'darwin' +tensorflow==2.14.1 +optax==0.2.3 + diff --git a/demonstrations_v2/covalent_cloud_gpu/requirements.in b/demonstrations_v2/covalent_cloud_gpu/requirements.in index 25fa5e54bb..51beccb636 100644 --- a/demonstrations_v2/covalent_cloud_gpu/requirements.in +++ b/demonstrations_v2/covalent_cloud_gpu/requirements.in @@ -1,3 +1,3 @@ covalent==0.227.0rc0 -covalent_cloud +covalent_cloud==0.16.1 scikit-learn diff --git a/demonstrations_v2/ml_classical_shadows/requirements.in b/demonstrations_v2/ml_classical_shadows/requirements.in index 035d3232d0..0744272bed 100644 --- a/demonstrations_v2/ml_classical_shadows/requirements.in +++ b/demonstrations_v2/ml_classical_shadows/requirements.in @@ -1,4 +1,4 @@ networkx -neural_tangents +neural_tangents==0.6.2 scipy scikit-learn diff --git a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in index c420b7df84..5a5719c866 100644 --- a/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in +++ b/demonstrations_v2/tutorial_How_to_simulate_quantum_circuits_with_tensor_networks/requirements.in @@ -1 +1 @@ -quimb +quimb==1.8.2 diff --git a/demonstrations_v2/tutorial_QUBO/requirements.in b/demonstrations_v2/tutorial_QUBO/requirements.in index 8056917e84..fb9352dd64 100644 --- a/demonstrations_v2/tutorial_QUBO/requirements.in +++ b/demonstrations_v2/tutorial_QUBO/requirements.in @@ -1,5 +1,5 @@ dimod docplex dwave-ocean-sdk==7.0.0 -openqaoa-core +openqaoa-core==0.2.5 pandas diff --git a/demonstrations_v2/tutorial_bluequbit/requirements.in b/demonstrations_v2/tutorial_bluequbit/requirements.in index 5f4fca9dc8..151e9680cf 100644 --- a/demonstrations_v2/tutorial_bluequbit/requirements.in +++ b/demonstrations_v2/tutorial_bluequbit/requirements.in @@ -1 +1 @@ -bluequbit +bluequbit==0.9.3b1 diff --git a/demonstrations_v2/tutorial_error_mitigation/requirements.in b/demonstrations_v2/tutorial_error_mitigation/requirements.in index 79d711c560..7d94a7bace 100644 --- a/demonstrations_v2/tutorial_error_mitigation/requirements.in +++ b/demonstrations_v2/tutorial_error_mitigation/requirements.in @@ -1,4 +1,4 @@ -mitiq +mitiq==0.32.0 qiskit qiskit_aer qiskit_ibm_runtime diff --git a/demonstrations_v2/tutorial_learning_few_data/requirements.in b/demonstrations_v2/tutorial_learning_few_data/requirements.in index 773918cee0..b7dccc567d 100644 --- a/demonstrations_v2/tutorial_learning_few_data/requirements.in +++ b/demonstrations_v2/tutorial_learning_few_data/requirements.in @@ -2,5 +2,5 @@ jax jaxlib optax pandas -seaborn +seaborn==0.13.2 scikit-learn diff --git a/demonstrations_v2/tutorial_rl_pulse/requirements.in b/demonstrations_v2/tutorial_rl_pulse/requirements.in index 6fb80fb580..c57950a599 100644 --- a/demonstrations_v2/tutorial_rl_pulse/requirements.in +++ b/demonstrations_v2/tutorial_rl_pulse/requirements.in @@ -1,5 +1,5 @@ -flax +flax==0.9.0 jax jaxlib optax -qutip +qutip==4.7.3 diff --git a/demonstrations_v2/tutorial_vqe_vqd/requirements.in b/demonstrations_v2/tutorial_vqe_vqd/requirements.in index 52f0b6758a..7555f688f2 100644 --- a/demonstrations_v2/tutorial_vqe_vqd/requirements.in +++ b/demonstrations_v2/tutorial_vqe_vqd/requirements.in @@ -1,3 +1,5 @@ jax jaxlib optax +torch +pennylane==0.39.0 diff --git a/demonstrations_v2/tutorial_vqt/requirements.in b/demonstrations_v2/tutorial_vqt/requirements.in index a215aec472..0521c13e7d 100644 --- a/demonstrations_v2/tutorial_vqt/requirements.in +++ b/demonstrations_v2/tutorial_vqt/requirements.in @@ -1,3 +1,3 @@ networkx scipy -seaborn +seaborn==0.13.2 diff --git a/lib/qml/app/app.py b/lib/qml/app/app.py index c7bfffff21..46be7efd3a 100644 --- a/lib/qml/app/app.py +++ b/lib/qml/app/app.py @@ -47,6 +47,7 @@ def build( venv_path=ctx.build_venv_path, demos=demos, target=format, + constraints_file=ctx.repo_root / "constraints.txt", execute=execute, ) diff --git a/lib/qml/lib/cmds.py b/lib/qml/lib/cmds.py index f7a89846d2..40c75a2088 100644 --- a/lib/qml/lib/cmds.py +++ b/lib/qml/lib/cmds.py @@ -64,7 +64,7 @@ def pip_install( Raises: CalledProcessError: The command does not complete successfully """ - cmd = [str(python), "-m", "pip", "install"] + cmd = [str(python), "-m", "uv", "pip", "install"] if requirements: cmd.extend(("--requirement", str(requirements))) if constraints: diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index 0fbcdf32bf..e8910bc043 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -4,6 +4,7 @@ import shutil from qml.lib import fs, cmds from qml.lib.virtual_env import Virtualenv +from qml.lib.pip_tools import RequirementsGenerator import os import sys from logging import getLogger @@ -11,6 +12,7 @@ from enum import Enum import re import functools +import requirements logger = getLogger("qml") @@ -75,13 +77,16 @@ def executable(self) -> bool: @functools.cached_property def requirements(self) -> frozenset[str]: - """Return a list of this demo's unversioned - requirements.""" - if path := self.requirements_file: - with open(path, "r") as f: - return frozenset(f.read().splitlines()) + if not (path := self.requirements_file): + return self.CORE_DEPENDENCIES - return frozenset() + reqs = set(self.CORE_DEPENDENCIES) + with open(path, "r") as f: + for req in requirements.parse(f): + reqs.discard(req.name) + reqs.add(req.line) + + return frozenset(reqs) def find(search_dir: Path, *names: str) -> Iterator[Demo]: @@ -117,6 +122,7 @@ def build( demos: Sequence[Demo], target: BuildTarget, execute: bool, + constraints_file: Path, quiet: bool = False, ) -> None: """Build the provided demos using 'sphinx-build', optionally @@ -134,25 +140,59 @@ def build( logger.info("Building %d demos", len(demos)) build_venv = Virtualenv(venv_path) + _install_build_dependencies(build_venv, build_dir) + + requirements_generator = RequirementsGenerator( + build_venv, + global_constraints_file=constraints_file, + extra_index_urls=("https://download.pytorch.org/whl/cpu",), + ) + + for demo in demos: + _build_demo( + sphinx_dir=sphinx_dir, + build_dir=build_dir, + build_venv=build_venv, + requirements_generator=requirements_generator, + target=target, + execute=execute, + demo=demo, + ) + + +def _build_demo( + sphinx_dir: Path, + build_dir: Path, + build_venv: Virtualenv, + demo: Demo, + target: BuildTarget, + requirements_generator: "RequirementsGenerator", + execute: bool, + quiet: bool = False, +): + out_dir = sphinx_dir / "demos" / demo.name + if out_dir.exists(): + shutil.rmtree(out_dir) + out_dir.mkdir(parents=True) + + with open(out_dir / "requirements.txt", "w") as f: + f.write(requirements_generator.generate_requirements(demo.requirements)) + + if execute: + cmds.pip_install(build_venv.python, requirements=(out_dir / "requirements.txt")) + stage_dir = build_dir / "demonstrations" if stage_dir.exists(): shutil.rmtree(stage_dir) - stage_dir.mkdir(parents=True) + # Need a 'GALLERY_HEADER' file for sphinx-gallery with open(stage_dir / "GALLERY_HEADER.rst", "w"): pass - for demo in demos: - # Use copy2 to perserve file modification time - shutil.copy2(demo.py_file, (stage_dir / demo.name).with_suffix(".py")) - - for resource in demo.resources: - fs.copy_any(resource, (stage_dir / resource.name)) - - _install_build_dependencies(build_venv, build_dir) - if execute: - _install_execution_dependencies(build_venv, build_dir, demos, quiet=quiet) + shutil.copy2(demo.py_file, (stage_dir / demo.name).with_suffix(".py")) + for resource in demo.resources: + fs.copy_any(resource, (stage_dir / resource.name)) cmd = [ str(build_venv.path / "bin" / "sphinx-build"), @@ -163,7 +203,10 @@ def build( cmd.extend(("-D", "plot_gallery=0")) cmd.extend((str(sphinx_dir), str(build_dir / target.value))) - sphinx_env = os.environ | {"DEMO_STAGING_DIR": str(stage_dir.resolve())} + sphinx_env = os.environ | { + "DEMO_STAGING_DIR": str(stage_dir.resolve()), + "GALLERY_OUTPUT_DIR": str(out_dir.resolve()), + } subprocess.run(cmd, env=sphinx_env).check_returncode() @@ -181,6 +224,20 @@ def _install_build_dependencies(venv: Virtualenv, build_dir: Path): cmds.pip_install(venv.python, "-r", build_requirements_file) +def _generate_constraints(build_dir: Path): + constraints_file = (build_dir / "constraints.txt").resolve() + cmds.poetry_export( + sys.executable, + constraints_file, + format="constraints.txt", + groups=("executable-dependencies",), + ) + if sys.platform == "darwin": + _fix_pytorch_constraint_macos(constraints_file) + + return constraints_file + + def _install_execution_dependencies( venv: Virtualenv, build_dir: Path, demos: Sequence[Demo], quiet: bool ): @@ -190,7 +247,7 @@ def _install_execution_dependencies( cmds.poetry_export( sys.executable, constraints_file, - format="constraints.txt", + format="requirements.txt", groups=("executable-dependencies",), ) if sys.platform == "darwin": diff --git a/lib/qml/lib/pip_tools.py b/lib/qml/lib/pip_tools.py new file mode 100644 index 0000000000..c8c2ada77e --- /dev/null +++ b/lib/qml/lib/pip_tools.py @@ -0,0 +1,97 @@ +import subprocess +from .virtual_env import Virtualenv +from collections import defaultdict +from pathlib import Path +import requirements +import tempfile +import itertools +from collections.abc import Mapping, Sequence + + +class RequirementsGenerator: + """Generates 'requirements.txt' from 'requirements.in' files, with versions constrained + by a global 'constraints.txt' file.""" + + global_constraints: Mapping[str, Sequence[str]] + extra_index_urls: Sequence[str] + + def __init__( + self, + venv: Virtualenv, + global_constraints_file: Path, + *, + extra_index_urls: Sequence[str] | None = None, + ): + self.venv = venv + + global_constraints: dict[str, tuple[str, ...]] = defaultdict(tuple) + with open(global_constraints_file, "r") as f: + for req in requirements.parse(f): + global_constraints[req.name] += (req.line,) + + self.global_constraints = global_constraints + self.extra_index_urls = extra_index_urls if extra_index_urls else () + self._requirements_in_cache: dict[frozenset[str], str] = {} + + def generate_requirements(self, requirements_in: frozenset[str]): + """Generate a 'requirements.txt' file for the given list of input requirements, with versions from + global constraints. If any of the input requirements are version-qualified, the versions will + override the global constraints. + """ + if cached := self._requirements_in_cache.get(requirements_in): + return cached + + constraints: dict[str, tuple[str, ...]] = defaultdict(tuple) + for req_str in requirements_in: + req = next(requirements.parse(req_str)) + if req.line != req.name: + constraints[req.name] += (req.line,) + + for package in filter( + lambda package: package not in constraints, self.global_constraints + ): + constraints[package] = self.global_constraints[package] + + with tempfile.TemporaryDirectory() as tmpdir: + constraints_file = Path(tmpdir, "constraints.txt") + requirements_file = Path(tmpdir, "requirements.txt") + + with open(constraints_file, "w") as f: + for spec in itertools.chain.from_iterable(constraints.values()): + f.write(spec + "\n") + + with open(requirements_file, "w") as f: + for req_str in requirements_in: + f.write(req_str + "\n") + + subprocess.run( + ( + self.venv.python, + "-m", + "uv", + "pip", + "compile", + "--index-strategy", + "unsafe-best-match", + "--extra-index-url", + "https://download.pytorch.org/whl/cpu", + "--emit-index-url", + "--constraints", + str(constraints_file), + "--output-file", + str(requirements_file), + "--no-header", + "--no-strip-extras", + "--no-strip-markers", + "--universal", + "--quiet", + "--no-annotate", + str(requirements_file), + ) + ).check_returncode() + + with open(requirements_file, "r") as f: + reqs = f.read() + self._requirements_in_cache[requirements_in] = reqs + + return reqs diff --git a/poetry.lock b/poetry.lock index ab50c7b7eb..5ba4e08853 100644 --- a/poetry.lock +++ b/poetry.lock @@ -287,13 +287,13 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "authlib" -version = "1.4.0" +version = "1.4.1" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false python-versions = ">=3.9" files = [ - {file = "Authlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"}, - {file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"}, + {file = "Authlib-1.4.1-py2.py3-none-any.whl", hash = "sha256:edc29c3f6a3e72cd9e9f45fff67fc663a2c364022eb0371c003f22d5405915c1"}, + {file = "authlib-1.4.1.tar.gz", hash = "sha256:30ead9ea4993cdbab821dc6e01e818362f92da290c04c7f6a1940f86507a790d"}, ] [package.dependencies] @@ -1943,13 +1943,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "fastapi" -version = "0.115.7" +version = "0.115.8" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.7-py3-none-any.whl", hash = "sha256:eb6a8c8bf7f26009e8147111ff15b5177a0e19bb4a45bc3486ab14804539d21e"}, - {file = "fastapi-0.115.7.tar.gz", hash = "sha256:0f106da6c01d88a6786b3248fb4d7a940d071f6f488488898ad5d354b25ed015"}, + {file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"}, + {file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"}, ] [package.dependencies] @@ -2090,61 +2090,61 @@ testing = ["clu", "clu (<=0.0.9)", "einops", "gymnasium[accept-rom-license,atari [[package]] name = "fonttools" -version = "4.55.6" +version = "4.55.8" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.55.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:57d55fc965e5dd20c8a60d880e0f43bafb506be87af0b650bdc42591e41e0d0d"}, - {file = "fonttools-4.55.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:127999618afe3a2490fad54bab0650c5fbeab1f8109bdc0205f6ad34306deb8b"}, - {file = "fonttools-4.55.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3226d40cb92787e09dcc3730f54b3779dfe56bdfea624e263685ba17a6faac4"}, - {file = "fonttools-4.55.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e82772f70b84e17aa36e9f236feb2a4f73cb686ec1e162557a36cf759d1acd58"}, - {file = "fonttools-4.55.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a632f85bd73e002b771bcbcdc512038fa5d2e09bb18c03a22fb8d400ea492ddf"}, - {file = "fonttools-4.55.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:791e0cf862cdd3a252df395f1bb5f65e3a760f1da3c7ce184d0f7998c266614d"}, - {file = "fonttools-4.55.6-cp310-cp310-win32.whl", hash = "sha256:94f7f2c5c5f3a6422e954ecb6d37cc363e27d6f94050a7ed3f79f12157af6bb2"}, - {file = "fonttools-4.55.6-cp310-cp310-win_amd64.whl", hash = "sha256:2d15e02b93a46982a8513a208e8f89148bca8297640527365625be56151687d0"}, - {file = "fonttools-4.55.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0879f99eabbf2171dfadd9c8c75cec2b7b3aa9cd1f3955dd799c69d60a5189ef"}, - {file = "fonttools-4.55.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d77d83ca77a4c3156a2f4cbc7f09f5a8503795da658fa255b987ad433a191266"}, - {file = "fonttools-4.55.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07478132407736ee5e54f9f534e73923ae28e9bb6dba17764a35e3caf7d7fea3"}, - {file = "fonttools-4.55.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c06fbc2fd76b9bab03eddfd8aa9fb7c0981d314d780e763c80aa76be1c9982"}, - {file = "fonttools-4.55.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09ed667c4753e1270994e5398cce8703e6423c41702a55b08f843b2907b1be65"}, - {file = "fonttools-4.55.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ee6ed68af8d57764d69da099db163aaf37d62ba246cfd42f27590e3e6724b55"}, - {file = "fonttools-4.55.6-cp311-cp311-win32.whl", hash = "sha256:9f99e7876518b2d059a9cc67c506168aebf9c71ac8d81006d75e684222f291d2"}, - {file = "fonttools-4.55.6-cp311-cp311-win_amd64.whl", hash = "sha256:3aa6c684007723895aade9b2fe76d07008c9dc90fd1ef6c310b3ca9c8566729f"}, - {file = "fonttools-4.55.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:51120695ee13001533e50abd40eec32c01b9c6f44c5567db38a7acd3eedcd19d"}, - {file = "fonttools-4.55.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:76ac5a595f86892b49ba86ba2e46185adc76328ce6eff0583b30e5c3ab02a914"}, - {file = "fonttools-4.55.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7535a5ac386e549e2b00b34c59b53f805e2423000676723b6867df3c10df04"}, - {file = "fonttools-4.55.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c42009177d3690894288082d5e3dac6bdc9f5d38e25054535e341a19cf5183a4"}, - {file = "fonttools-4.55.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:88f74bc19dbab3dee6a00ca67ca54bb4793e44ff0c4dcf1fa61d68651ae3fa0a"}, - {file = "fonttools-4.55.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bc6f58976ffc19fe1630119a2736153b66151d023c6f30065f31c9e8baed1303"}, - {file = "fonttools-4.55.6-cp312-cp312-win32.whl", hash = "sha256:4259159715142c10b0f4d121ef14da3fa6eafc719289d9efa4b20c15e57fef82"}, - {file = "fonttools-4.55.6-cp312-cp312-win_amd64.whl", hash = "sha256:d91fce2e9a87cc0db9f8042281b6458f99854df810cfefab2baf6ab2acc0f4b4"}, - {file = "fonttools-4.55.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9394813cc73fa22c5413ec1c5745c0a16f68dd2b890f7c55eaba5cb40187ed55"}, - {file = "fonttools-4.55.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ac817559a7d245454231374e194b4e457dca6fefa5b52af466ab0516e9a09c6e"}, - {file = "fonttools-4.55.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34405f1314f1e88b1877a9f9e497fe45190e8c4b29a6c7cd85ed7f666a57d702"}, - {file = "fonttools-4.55.6-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5469bbf555047efd8752d85faeb2a3510916ddc6c50dd6fb168edf1677408f"}, - {file = "fonttools-4.55.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a8004a19195eb8a8a13de69e26ec9ed60a5bc1fde336d0021b47995b368fac9"}, - {file = "fonttools-4.55.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:73a4aaf672e7b2265c6354a69cbbadf71b7f3133ecb74e98fec4c67c366698a3"}, - {file = "fonttools-4.55.6-cp313-cp313-win32.whl", hash = "sha256:73bdff9c44d36c57ea84766afc20517eda0c9bb1571b4a09876646264bd5ff3b"}, - {file = "fonttools-4.55.6-cp313-cp313-win_amd64.whl", hash = "sha256:132fa22be8a99784de8cb171b30425a581f04a40ec1c05183777fb2b1fe3bac9"}, - {file = "fonttools-4.55.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8398928acb8a57073606feb9a310682d4a7e2d7536f2c61719261f4c0974504c"}, - {file = "fonttools-4.55.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2f78ebfdef578d4db7c44bc207ac5f9a5c1f22c9db606460dcc8ad48e183338"}, - {file = "fonttools-4.55.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fb545f3a4ebada908fa717ec732277de18dd10161f03ee3b3144d34477804de"}, - {file = "fonttools-4.55.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1062daa0390b32bfd062ded2b450db9e9cf10e5a9919561c13f535e818b1952b"}, - {file = "fonttools-4.55.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:860ab9ed3f9e088d3bdb77b9074e656635f173b039e77d550b603cba052a0dca"}, - {file = "fonttools-4.55.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:03701e7de70c71eb5965cb200986b0c11dfa3cf8e843e4f517ee30a0f43f0a25"}, - {file = "fonttools-4.55.6-cp38-cp38-win32.whl", hash = "sha256:f66561fbfb75785d06513b8025a50be37bf970c3c413e87581cc6eff10bc78f1"}, - {file = "fonttools-4.55.6-cp38-cp38-win_amd64.whl", hash = "sha256:edf159a8f1e48dc4683a715b36da76dd2f82954b16bfe11a215d58e963d31cfc"}, - {file = "fonttools-4.55.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61aa1997c520bee4cde14ffabe81efc4708c500c8c81dce37831551627a2be56"}, - {file = "fonttools-4.55.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7954ea66a8d835f279c17d8474597a001ddd65a2c1ca97e223041bfbbe11f65e"}, - {file = "fonttools-4.55.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f4e88f15f5ed4d2e4bdfcc98540bb3987ae25904f9be304be9a604e7a7050a1"}, - {file = "fonttools-4.55.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d419483a6295e83cabddb56f1c7b7bfdc8169de2fcb5c68d622bd11140355f9"}, - {file = "fonttools-4.55.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:acc74884afddc2656bffc50100945ff407574538c152931c402fccddc46f0abc"}, - {file = "fonttools-4.55.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a55489c7e9d5ea69690a2afad06723c3d0c48c6d276a25391ea97cb31a16b37c"}, - {file = "fonttools-4.55.6-cp39-cp39-win32.whl", hash = "sha256:8c9de8d16d02ecc8b65e3f3d2d1e3002be2c4a3f094d580faf76d7f768bd45fe"}, - {file = "fonttools-4.55.6-cp39-cp39-win_amd64.whl", hash = "sha256:471961af7a4b8461fac0c8ee044b4986e6fe3746d4c83a1aacbdd85b4eb53f93"}, - {file = "fonttools-4.55.6-py3-none-any.whl", hash = "sha256:d20ab5a78d0536c26628eaadba661e7ae2427b1e5c748a0a510a44d914e1b155"}, - {file = "fonttools-4.55.6.tar.gz", hash = "sha256:1beb4647a0df5ceaea48015656525eb8081af226fe96554089fd3b274d239ef0"}, + {file = "fonttools-4.55.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d11600f5343092697d7434f3bf77a393c7ae74be206fe30e577b9a195fd53165"}, + {file = "fonttools-4.55.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c96f2506ce1a0beeaa9595f9a8b7446477eb133f40c0e41fc078744c28149f80"}, + {file = "fonttools-4.55.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b5f05ef72e846e9f49ccdd74b9da4309901a4248434c63c1ee9321adcb51d65"}, + {file = "fonttools-4.55.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba45b637da80a262b55b7657aec68da2ac54b8ae7891cd977a5dbe5fd26db429"}, + {file = "fonttools-4.55.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:edcffaeadba9a334c1c3866e275d7dd495465e7dbd296f688901bdbd71758113"}, + {file = "fonttools-4.55.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b9f9fce3c9b2196e162182ec5db8af8eb3acd0d76c2eafe9fdba5f370044e556"}, + {file = "fonttools-4.55.8-cp310-cp310-win32.whl", hash = "sha256:f089e8da0990cfe2d67e81d9cf581ff372b48dc5acf2782701844211cd1f0eb3"}, + {file = "fonttools-4.55.8-cp310-cp310-win_amd64.whl", hash = "sha256:01ea3901b0802fc5f9e854f5aeb5bc27770dd9dd24c28df8f74ba90f8b3f5915"}, + {file = "fonttools-4.55.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95f5a1d4432b3cea6571f5ce4f4e9b25bf36efbd61c32f4f90130a690925d6ee"}, + {file = "fonttools-4.55.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d20f152de7625a0008ba1513f126daaaa0de3b4b9030aa72dd5c27294992260"}, + {file = "fonttools-4.55.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a3ff5bb95fd5a3962b2754f8435e6d930c84fc9e9921c51e802dddf40acd56"}, + {file = "fonttools-4.55.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99d4fd2b6d0a00c7336c8363fccc7a11eccef4b17393af75ca6e77cf93ff413"}, + {file = "fonttools-4.55.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d637e4d33e46619c79d1a6c725f74d71b574cd15fb5bbb9b6f3eba8f28363573"}, + {file = "fonttools-4.55.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0f38bfb6b7a39c4162c3eb0820a0bdf8e3bdd125cd54e10ba242397d15e32439"}, + {file = "fonttools-4.55.8-cp311-cp311-win32.whl", hash = "sha256:acfec948de41cd5e640d5c15d0200e8b8e7c5c6bb82afe1ca095cbc4af1188ee"}, + {file = "fonttools-4.55.8-cp311-cp311-win_amd64.whl", hash = "sha256:604c805b41241b4880e2dc86cf2d4754c06777371c8299799ac88d836cb18c3b"}, + {file = "fonttools-4.55.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63403ee0f2fa4e1de28e539f8c24f2bdca1d8ecb503fa9ea2d231d9f1e729809"}, + {file = "fonttools-4.55.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:302e1003a760b222f711d5ba6d1ad7fd5f7f713eb872cd6a3eb44390bc9770af"}, + {file = "fonttools-4.55.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e72a7816ff8a759be9ca36ca46934f8ccf4383711ef597d9240306fe1878cb8d"}, + {file = "fonttools-4.55.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c2b50b54e6e8b3564b232e57e8f58be217cf441cf0155745d9e44a76f9c30f"}, + {file = "fonttools-4.55.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7230f7590f9570d26ee903b6a4540274494e200fae978df0d9325b7b9144529"}, + {file = "fonttools-4.55.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:466a78984f0572305c3c48377f4e3f7f4e909f1209f45ef8e7041d5c8a744a56"}, + {file = "fonttools-4.55.8-cp312-cp312-win32.whl", hash = "sha256:243cbfc0b7cb1c307af40e321f8343a48d0a080bc1f9466cf2b5468f776ef108"}, + {file = "fonttools-4.55.8-cp312-cp312-win_amd64.whl", hash = "sha256:a19059aa892676822c1f05cb5a67296ecdfeb267fe7c47d4758f3e8e942c2b2a"}, + {file = "fonttools-4.55.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:332883b6280b9d90d2ba7e9e81be77cf2ace696161e60cdcf40cfcd2b3ed06fa"}, + {file = "fonttools-4.55.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6b8d7c149d47b47de7ec81763396c8266e5ebe2e0b14aa9c3ccf29e52260ab2f"}, + {file = "fonttools-4.55.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dfae7c94987149bdaa0388e6c937566aa398fa0eec973b17952350a069cff4e"}, + {file = "fonttools-4.55.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0fe12f06169af2fdc642d26a8df53e40adc3beedbd6ffedb19f1c5397b63afd"}, + {file = "fonttools-4.55.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f971aa5f50c22dc4b63a891503624ae2c77330429b34ead32f23c2260c5618cd"}, + {file = "fonttools-4.55.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:708cb17b2590b7f6c6854999df0039ff1140dda9e6f56d67c3599ba6f968fab5"}, + {file = "fonttools-4.55.8-cp313-cp313-win32.whl", hash = "sha256:cfe9cf30f391a0f2875247a3e5e44d8dcb61596e5cf89b360cdffec8a80e9961"}, + {file = "fonttools-4.55.8-cp313-cp313-win_amd64.whl", hash = "sha256:1e10efc8ee10d6f1fe2931d41bccc90cd4b872f2ee4ff21f2231a2c293b2dbf8"}, + {file = "fonttools-4.55.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9b6fcff4dc755b32faff955d989ee26394ddad3a90ea7d558db17a4633c8390c"}, + {file = "fonttools-4.55.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:02c41322e5bdcb484b61b776fcea150215c83619b39c96aa0b44d4fd87bb5574"}, + {file = "fonttools-4.55.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9164f44add0acec0f12fce682824c040dc52e483bfe3838c37142897150c8364"}, + {file = "fonttools-4.55.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2248ebfbcea0d0b3cb459d76a9f67f2eadc10ec0d07e9cadab8777d3f016bf2"}, + {file = "fonttools-4.55.8-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3461347016c94cb42b36caa907e11565878c4c2c375604f3651d11dc06d1ab3e"}, + {file = "fonttools-4.55.8-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:67df1c3935838fb9e56f227d7f506c9043b149a4a3b667bef17929c7a1114d19"}, + {file = "fonttools-4.55.8-cp38-cp38-win32.whl", hash = "sha256:cb121d6dd34625cece32234a5fa0359475bb118838b6b4295ffdb13b935edb04"}, + {file = "fonttools-4.55.8-cp38-cp38-win_amd64.whl", hash = "sha256:285c1ac10c160fbdff6d05358230e66c4f98cbbf271f3ec7eb34e967771543e8"}, + {file = "fonttools-4.55.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8abd135e427d88e461a4833c03cf96cfb9028c78c15d58123291f22398e25492"}, + {file = "fonttools-4.55.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65cb8f97eed7906dcf19bc2736b70c6239e9d7e77aad7c6110ba7239ae082e81"}, + {file = "fonttools-4.55.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:450c354c04a6e12a3db968e915fe05730f79ff3d39560947ef8ee6eaa2ab2212"}, + {file = "fonttools-4.55.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2232012a1502b2b8ab4c6bc1d3524bfe90238c0c1a50ac94a0a2085aa87a58a5"}, + {file = "fonttools-4.55.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d39f0c977639be0f9f5505d4c7c478236737f960c567a35f058649c056e41434"}, + {file = "fonttools-4.55.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:de78d6d0dbe32561ce059265437021f4746e56073c4799f0f1095828ae7232bd"}, + {file = "fonttools-4.55.8-cp39-cp39-win32.whl", hash = "sha256:bf4b5b3496ddfdd4e57112e77ec51f1ab388d35ac17322c1248addb2eb0d429a"}, + {file = "fonttools-4.55.8-cp39-cp39-win_amd64.whl", hash = "sha256:ccf8ae02918f431953d338db4d0a675a395faf82bab3a76025582cf32a2f3b7b"}, + {file = "fonttools-4.55.8-py3-none-any.whl", hash = "sha256:07636dae94f7fe88561f9da7a46b13d8e3f529f87fdb221b11d85f91eabceeb7"}, + {file = "fonttools-4.55.8.tar.gz", hash = "sha256:54d481d456dcd59af25d4a9c56b2c4c3f20e9620b261b84144e5950f33e8df17"}, ] [package.extras] @@ -3873,6 +3873,32 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} +[[package]] +name = "narwhals" +version = "1.24.1" +description = "Extremely lightweight compatibility layer between dataframe libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "narwhals-1.24.1-py3-none-any.whl", hash = "sha256:d8983fe14851c95d60576ddca37c094bd4ed24ab9ea98396844fb20ad9aaf184"}, + {file = "narwhals-1.24.1.tar.gz", hash = "sha256:b09b8253d945f23cdb683a84685abf3afb9f96114d89e9f35dc876e143f65007"}, +] + +[package.extras] +core = ["duckdb", "pandas", "polars", "pyarrow", "pyarrow-stubs"] +cudf = ["cudf (>=24.10.0)"] +dask = ["dask[dataframe] (>=2024.8)"] +dev = ["covdefaults", "hypothesis", "pre-commit", "pytest", "pytest-cov", "pytest-env", "pytest-randomly", "typing-extensions"] +docs = ["black", "duckdb", "jinja2", "markdown-exec[ansi]", "mkdocs", "mkdocs-autorefs", "mkdocs-material", "mkdocstrings[python]", "pandas", "polars (>=1.0.0)", "pyarrow"] +duckdb = ["duckdb (>=1.0)"] +extra = ["scikit-learn"] +ibis = ["ibis-framework (>=6.0.0)", "packaging", "pyarrow-hotfix", "rich"] +modin = ["modin"] +pandas = ["pandas (>=0.25.3)"] +polars = ["polars (>=0.20.3)"] +pyarrow = ["pyarrow (>=11.0.0)"] +pyspark = ["pyspark (>=3.5.0)"] + [[package]] name = "natsort" version = "8.4.0" @@ -4894,18 +4920,21 @@ type = ["mypy (>=1.11.2)"] [[package]] name = "plotly" -version = "5.24.1" +version = "6.0.0" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, - {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, + {file = "plotly-6.0.0-py3-none-any.whl", hash = "sha256:f708871c3a9349a68791ff943a5781b1ec04de7769ea69068adcd9202e57653a"}, + {file = "plotly-6.0.0.tar.gz", hash = "sha256:c4aad38b8c3d65e4a5e7dd308b084143b9025c2cc9d5317fc1f1d30958db87d3"}, ] [package.dependencies] +narwhals = ">=1.15.1" packaging = "*" -tenacity = ">=6.2.0" + +[package.extras] +express = ["numpy"] [[package]] name = "plucky" @@ -5896,120 +5925,120 @@ files = [ [[package]] name = "pyzmq" -version = "26.2.0" +version = "26.2.1" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.7" files = [ - {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, - {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, - {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"}, - {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"}, - {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"}, - {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"}, - {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"}, - {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"}, - {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"}, - {file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"}, - {file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"}, - {file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"}, - {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"}, - {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"}, - {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"}, - {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"}, - {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"}, - {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"}, - {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"}, - {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"}, - {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"}, - {file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"}, - {file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"}, - {file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"}, - {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"}, - {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"}, - {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"}, - {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"}, - {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"}, - {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"}, - {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"}, - {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"}, - {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"}, - {file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"}, - {file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"}, - {file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"}, - {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"}, - {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"}, - {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"}, - {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"}, - {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"}, - {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"}, - {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"}, - {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"}, - {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"}, - {file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"}, - {file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"}, - {file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"}, - {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"}, - {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"}, - {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"}, - {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"}, - {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"}, - {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"}, - {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"}, - {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"}, - {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"}, - {file = "pyzmq-26.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8"}, - {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8"}, - {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1"}, - {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9"}, - {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27"}, - {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097"}, - {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93"}, - {file = "pyzmq-26.2.0-cp37-cp37m-win32.whl", hash = "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951"}, - {file = "pyzmq-26.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231"}, - {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f"}, - {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2"}, - {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6"}, - {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289"}, - {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732"}, - {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780"}, - {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640"}, - {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd"}, - {file = "pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988"}, - {file = "pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f"}, - {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2"}, - {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c"}, - {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98"}, - {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9"}, - {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db"}, - {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073"}, - {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc"}, - {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940"}, - {file = "pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44"}, - {file = "pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec"}, - {file = "pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb"}, - {file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"}, - {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"}, - {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"}, - {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"}, - {file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"}, - {file = "pyzmq-26.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c"}, - {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6"}, - {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c"}, - {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6"}, - {file = "pyzmq-26.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed"}, - {file = "pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20"}, - {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"}, - {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5"}, - {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc"}, - {file = "pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277"}, - {file = "pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3"}, - {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a"}, - {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6"}, - {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a"}, - {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4"}, - {file = "pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f"}, - {file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"}, + {file = "pyzmq-26.2.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:f39d1227e8256d19899d953e6e19ed2ccb689102e6d85e024da5acf410f301eb"}, + {file = "pyzmq-26.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a23948554c692df95daed595fdd3b76b420a4939d7a8a28d6d7dea9711878641"}, + {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95f5728b367a042df146cec4340d75359ec6237beebf4a8f5cf74657c65b9257"}, + {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f7b01b3f275504011cf4cf21c6b885c8d627ce0867a7e83af1382ebab7b3ff"}, + {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a00370a2ef2159c310e662c7c0f2d030f437f35f478bb8b2f70abd07e26b24"}, + {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:8531ed35dfd1dd2af95f5d02afd6545e8650eedbf8c3d244a554cf47d8924459"}, + {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cdb69710e462a38e6039cf17259d328f86383a06c20482cc154327968712273c"}, + {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e7eeaef81530d0b74ad0d29eec9997f1c9230c2f27242b8d17e0ee67662c8f6e"}, + {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:361edfa350e3be1f987e592e834594422338d7174364763b7d3de5b0995b16f3"}, + {file = "pyzmq-26.2.1-cp310-cp310-win32.whl", hash = "sha256:637536c07d2fb6a354988b2dd1d00d02eb5dd443f4bbee021ba30881af1c28aa"}, + {file = "pyzmq-26.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:45fad32448fd214fbe60030aa92f97e64a7140b624290834cc9b27b3a11f9473"}, + {file = "pyzmq-26.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:d9da0289d8201c8a29fd158aaa0dfe2f2e14a181fd45e2dc1fbf969a62c1d594"}, + {file = "pyzmq-26.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c059883840e634a21c5b31d9b9a0e2b48f991b94d60a811092bc37992715146a"}, + {file = "pyzmq-26.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed038a921df836d2f538e509a59cb638df3e70ca0fcd70d0bf389dfcdf784d2a"}, + {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9027a7fcf690f1a3635dc9e55e38a0d6602dbbc0548935d08d46d2e7ec91f454"}, + {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d75fcb00a1537f8b0c0bb05322bc7e35966148ffc3e0362f0369e44a4a1de99"}, + {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0019cc804ac667fb8c8eaecdb66e6d4a68acf2e155d5c7d6381a5645bd93ae4"}, + {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f19dae58b616ac56b96f2e2290f2d18730a898a171f447f491cc059b073ca1fa"}, + {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f5eeeb82feec1fc5cbafa5ee9022e87ffdb3a8c48afa035b356fcd20fc7f533f"}, + {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:000760e374d6f9d1a3478a42ed0c98604de68c9e94507e5452951e598ebecfba"}, + {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:817fcd3344d2a0b28622722b98500ae9c8bfee0f825b8450932ff19c0b15bebd"}, + {file = "pyzmq-26.2.1-cp311-cp311-win32.whl", hash = "sha256:88812b3b257f80444a986b3596e5ea5c4d4ed4276d2b85c153a6fbc5ca457ae7"}, + {file = "pyzmq-26.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:ef29630fde6022471d287c15c0a2484aba188adbfb978702624ba7a54ddfa6c1"}, + {file = "pyzmq-26.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:f32718ee37c07932cc336096dc7403525301fd626349b6eff8470fe0f996d8d7"}, + {file = "pyzmq-26.2.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:a6549ecb0041dafa55b5932dcbb6c68293e0bd5980b5b99f5ebb05f9a3b8a8f3"}, + {file = "pyzmq-26.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0250c94561f388db51fd0213cdccbd0b9ef50fd3c57ce1ac937bf3034d92d72e"}, + {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ee4297d9e4b34b5dc1dd7ab5d5ea2cbba8511517ef44104d2915a917a56dc8"}, + {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2a9cb17fd83b7a3a3009901aca828feaf20aa2451a8a487b035455a86549c09"}, + {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:786dd8a81b969c2081b31b17b326d3a499ddd1856e06d6d79ad41011a25148da"}, + {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2d88ba221a07fc2c5581565f1d0fe8038c15711ae79b80d9462e080a1ac30435"}, + {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c84c1297ff9f1cd2440da4d57237cb74be21fdfe7d01a10810acba04e79371a"}, + {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46d4ebafc27081a7f73a0f151d0c38d4291656aa134344ec1f3d0199ebfbb6d4"}, + {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:91e2bfb8e9a29f709d51b208dd5f441dc98eb412c8fe75c24ea464734ccdb48e"}, + {file = "pyzmq-26.2.1-cp312-cp312-win32.whl", hash = "sha256:4a98898fdce380c51cc3e38ebc9aa33ae1e078193f4dc641c047f88b8c690c9a"}, + {file = "pyzmq-26.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0741edbd0adfe5f30bba6c5223b78c131b5aa4a00a223d631e5ef36e26e6d13"}, + {file = "pyzmq-26.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:e5e33b1491555843ba98d5209439500556ef55b6ab635f3a01148545498355e5"}, + {file = "pyzmq-26.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:099b56ef464bc355b14381f13355542e452619abb4c1e57a534b15a106bf8e23"}, + {file = "pyzmq-26.2.1-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:651726f37fcbce9f8dd2a6dab0f024807929780621890a4dc0c75432636871be"}, + {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57dd4d91b38fa4348e237a9388b4423b24ce9c1695bbd4ba5a3eada491e09399"}, + {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d51a7bfe01a48e1064131f3416a5439872c533d756396be2b39e3977b41430f9"}, + {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7154d228502e18f30f150b7ce94f0789d6b689f75261b623f0fdc1eec642aab"}, + {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f1f31661a80cc46aba381bed475a9135b213ba23ca7ff6797251af31510920ce"}, + {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:290c96f479504439b6129a94cefd67a174b68ace8a8e3f551b2239a64cfa131a"}, + {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f2c307fbe86e18ab3c885b7e01de942145f539165c3360e2af0f094dd440acd9"}, + {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b314268e716487bfb86fcd6f84ebbe3e5bec5fac75fdf42bc7d90fdb33f618ad"}, + {file = "pyzmq-26.2.1-cp313-cp313-win32.whl", hash = "sha256:edb550616f567cd5603b53bb52a5f842c0171b78852e6fc7e392b02c2a1504bb"}, + {file = "pyzmq-26.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:100a826a029c8ef3d77a1d4c97cbd6e867057b5806a7276f2bac1179f893d3bf"}, + {file = "pyzmq-26.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:6991ee6c43e0480deb1b45d0c7c2bac124a6540cba7db4c36345e8e092da47ce"}, + {file = "pyzmq-26.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:25e720dba5b3a3bb2ad0ad5d33440babd1b03438a7a5220511d0c8fa677e102e"}, + {file = "pyzmq-26.2.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:9ec6abfb701437142ce9544bd6a236addaf803a32628d2260eb3dbd9a60e2891"}, + {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e1eb9d2bfdf5b4e21165b553a81b2c3bd5be06eeddcc4e08e9692156d21f1f6"}, + {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90dc731d8e3e91bcd456aa7407d2eba7ac6f7860e89f3766baabb521f2c1de4a"}, + {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6a93d684278ad865fc0b9e89fe33f6ea72d36da0e842143891278ff7fd89c3"}, + {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1bb37849e2294d519117dd99b613c5177934e5c04a5bb05dd573fa42026567e"}, + {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:632a09c6d8af17b678d84df442e9c3ad8e4949c109e48a72f805b22506c4afa7"}, + {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:fc409c18884eaf9ddde516d53af4f2db64a8bc7d81b1a0c274b8aa4e929958e8"}, + {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:17f88622b848805d3f6427ce1ad5a2aa3cf61f12a97e684dab2979802024d460"}, + {file = "pyzmq-26.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3ef584f13820d2629326fe20cc04069c21c5557d84c26e277cfa6235e523b10f"}, + {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:160194d1034902937359c26ccfa4e276abffc94937e73add99d9471e9f555dd6"}, + {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:574b285150afdbf0a0424dddf7ef9a0d183988eb8d22feacb7160f7515e032cb"}, + {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44dba28c34ce527cf687156c81f82bf1e51f047838d5964f6840fd87dfecf9fe"}, + {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9fbdb90b85c7624c304f72ec7854659a3bd901e1c0ffb2363163779181edeb68"}, + {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a7ad34a2921e8f76716dc7205c9bf46a53817e22b9eec2e8a3e08ee4f4a72468"}, + {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:866c12b7c90dd3a86983df7855c6f12f9407c8684db6aa3890fc8027462bda82"}, + {file = "pyzmq-26.2.1-cp37-cp37m-win32.whl", hash = "sha256:eeb37f65350d5c5870517f02f8bbb2ac0fbec7b416c0f4875219fef305a89a45"}, + {file = "pyzmq-26.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4eb3197f694dfb0ee6af29ef14a35f30ae94ff67c02076eef8125e2d98963cd0"}, + {file = "pyzmq-26.2.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:36d4e7307db7c847fe37413f333027d31c11d5e6b3bacbb5022661ac635942ba"}, + {file = "pyzmq-26.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1c6ae0e95d0a4b0cfe30f648a18e764352d5415279bdf34424decb33e79935b8"}, + {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5b4fc44f5360784cc02392f14235049665caaf7c0fe0b04d313e763d3338e463"}, + {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:51431f6b2750eb9b9d2b2952d3cc9b15d0215e1b8f37b7a3239744d9b487325d"}, + {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdbc78ae2065042de48a65f1421b8af6b76a0386bb487b41955818c3c1ce7bed"}, + {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d14f50d61a89b0925e4d97a0beba6053eb98c426c5815d949a43544f05a0c7ec"}, + {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:004837cb958988c75d8042f5dac19a881f3d9b3b75b2f574055e22573745f841"}, + {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b2007f28ce1b8acebdf4812c1aab997a22e57d6a73b5f318b708ef9bcabbe95"}, + {file = "pyzmq-26.2.1-cp38-cp38-win32.whl", hash = "sha256:269c14904da971cb5f013100d1aaedb27c0a246728c341d5d61ddd03f463f2f3"}, + {file = "pyzmq-26.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:31fff709fef3b991cfe7189d2cfe0c413a1d0e82800a182cfa0c2e3668cd450f"}, + {file = "pyzmq-26.2.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a4bffcadfd40660f26d1b3315a6029fd4f8f5bf31a74160b151f5c577b2dc81b"}, + {file = "pyzmq-26.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e76ad4729c2f1cf74b6eb1bdd05f6aba6175999340bd51e6caee49a435a13bf5"}, + {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8b0f5bab40a16e708e78a0c6ee2425d27e1a5d8135c7a203b4e977cee37eb4aa"}, + {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8e47050412f0ad3a9b2287779758073cbf10e460d9f345002d4779e43bb0136"}, + {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f18ce33f422d119b13c1363ed4cce245b342b2c5cbbb76753eabf6aa6f69c7d"}, + {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ceb0d78b7ef106708a7e2c2914afe68efffc0051dc6a731b0dbacd8b4aee6d68"}, + {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ebdd96bd637fd426d60e86a29ec14b8c1ab64b8d972f6a020baf08a30d1cf46"}, + {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:03719e424150c6395b9513f53a5faadcc1ce4b92abdf68987f55900462ac7eec"}, + {file = "pyzmq-26.2.1-cp39-cp39-win32.whl", hash = "sha256:ef5479fac31df4b304e96400fc67ff08231873ee3537544aa08c30f9d22fce38"}, + {file = "pyzmq-26.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:f92a002462154c176dac63a8f1f6582ab56eb394ef4914d65a9417f5d9fde218"}, + {file = "pyzmq-26.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:1fd4b3efc6f62199886440d5e27dd3ccbcb98dfddf330e7396f1ff421bfbb3c2"}, + {file = "pyzmq-26.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:380816d298aed32b1a97b4973a4865ef3be402a2e760204509b52b6de79d755d"}, + {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97cbb368fd0debdbeb6ba5966aa28e9a1ae3396c7386d15569a6ca4be4572b99"}, + {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf7b5942c6b0dafcc2823ddd9154f419147e24f8df5b41ca8ea40a6db90615c"}, + {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fe6e28a8856aea808715f7a4fc11f682b9d29cac5d6262dd8fe4f98edc12d53"}, + {file = "pyzmq-26.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd8fdee945b877aa3bffc6a5a8816deb048dab0544f9df3731ecd0e54d8c84c9"}, + {file = "pyzmq-26.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ee7152f32c88e0e1b5b17beb9f0e2b14454235795ef68c0c120b6d3d23d12833"}, + {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:baa1da72aecf6a490b51fba7a51f1ce298a1e0e86d0daef8265c8f8f9848eb77"}, + {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:49135bb327fca159262d8fd14aa1f4a919fe071b04ed08db4c7c37d2f0647162"}, + {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bacc1a10c150d58e8a9ee2b2037a70f8d903107e0f0b6e079bf494f2d09c091"}, + {file = "pyzmq-26.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:09dac387ce62d69bec3f06d51610ca1d660e7849eb45f68e38e7f5cf1f49cbcb"}, + {file = "pyzmq-26.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70b3a46ecd9296e725ccafc17d732bfc3cdab850b54bd913f843a0a54dfb2c04"}, + {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:59660e15c797a3b7a571c39f8e0b62a1f385f98ae277dfe95ca7eaf05b5a0f12"}, + {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0f50db737d688e96ad2a083ad2b453e22865e7e19c7f17d17df416e91ddf67eb"}, + {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a003200b6cd64e89b5725ff7e284a93ab24fd54bbac8b4fa46b1ed57be693c27"}, + {file = "pyzmq-26.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f9ba5def063243793dec6603ad1392f735255cbc7202a3a484c14f99ec290705"}, + {file = "pyzmq-26.2.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1238c2448c58b9c8d6565579393148414a42488a5f916b3f322742e561f6ae0d"}, + {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eddb3784aed95d07065bcf94d07e8c04024fdb6b2386f08c197dfe6b3528fda"}, + {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0f19c2097fffb1d5b07893d75c9ee693e9cbc809235cf3f2267f0ef6b015f24"}, + {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0995fd3530f2e89d6b69a2202e340bbada3191014352af978fa795cb7a446331"}, + {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7c6160fe513654e65665332740f63de29ce0d165e053c0c14a161fa60dd0da01"}, + {file = "pyzmq-26.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8ec8e3aea6146b761d6c57fcf8f81fcb19f187afecc19bf1701a48db9617a217"}, + {file = "pyzmq-26.2.1.tar.gz", hash = "sha256:17d72a74e5e9ff3829deb72897a175333d3ef5b5413948cae3cf7ebf0b02ecca"}, ] [package.dependencies] @@ -6353,99 +6382,99 @@ tests = ["pytest (>=5.2)", "pytest-rerunfailures"] [[package]] name = "rapidfuzz" -version = "3.11.0" +version = "3.12.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.9" files = [ - {file = "rapidfuzz-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e7f442fb9cca81e9df32333fb075ef729052bcabe05b0afc0441f462299114"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:906f1f2a1b91c06599b3dd1be207449c5d4fc7bd1e1fa2f6aef161ea6223f165"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed59044aea9eb6c663112170f2399b040d5d7b162828b141f2673e822093fa8"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cb1965a28b0fa64abdee130c788a0bc0bb3cf9ef7e3a70bf055c086c14a3d7e"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b488b244931d0291412917e6e46ee9f6a14376625e150056fe7c4426ef28225"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f0ba13557fec9d5ffc0a22826754a7457cc77f1b25145be10b7bb1d143ce84c6"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3871fa7dfcef00bad3c7e8ae8d8fd58089bad6fb21f608d2bf42832267ca9663"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b2669eafee38c5884a6e7cc9769d25c19428549dcdf57de8541cf9e82822e7db"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ffa1bb0e26297b0f22881b219ffc82a33a3c84ce6174a9d69406239b14575bd5"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:45b15b8a118856ac9caac6877f70f38b8a0d310475d50bc814698659eabc1cdb"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-win32.whl", hash = "sha256:22033677982b9c4c49676f215b794b0404073f8974f98739cb7234e4a9ade9ad"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:be15496e7244361ff0efcd86e52559bacda9cd975eccf19426a0025f9547c792"}, - {file = "rapidfuzz-3.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:714a7ba31ba46b64d30fccfe95f8013ea41a2e6237ba11a805a27cdd3bce2573"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-win32.whl", hash = "sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842"}, - {file = "rapidfuzz-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1b3ebc62d4bcdfdeba110944a25ab40916d5383c5e57e7c4a8dc0b6c17211a"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c6d7fea39cb33e71de86397d38bf7ff1a6273e40367f31d05761662ffda49e4"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99aebef8268f2bc0b445b5640fd3312e080bd17efd3fbae4486b20ac00466308"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4469307f464ae3089acf3210b8fc279110d26d10f79e576f385a98f4429f7d97"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb97c53112b593f89a90b4f6218635a9d1eea1d7f9521a3b7d24864228bbc0aa"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef8937dae823b889c0273dfa0f0f6c46a3658ac0d851349c464d1b00e7ff4252"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d95f9e9f3777b96241d8a00d6377cc9c716981d828b5091082d0fe3a2924b43e"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b1d67d67f89e4e013a5295e7523bc34a7a96f2dba5dd812c7c8cb65d113cbf28"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d994cf27e2f874069884d9bddf0864f9b90ad201fcc9cb2f5b82bacc17c8d5f2"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-win32.whl", hash = "sha256:ba26d87fe7fcb56c4a53b549a9e0e9143f6b0df56d35fe6ad800c902447acd5b"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f7efdd7b7adb32102c2fa481ad6f11923e2deb191f651274be559d56fc913b"}, - {file = "rapidfuzz-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:ed78c8e94f57b44292c1a0350f580e18d3a3c5c0800e253f1583580c1b417ad2"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e60814edd0c9b511b5f377d48b9782b88cfe8be07a98f99973669299c8bb318a"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f28952da055dbfe75828891cd3c9abf0984edc8640573c18b48c14c68ca5e06"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e8f93bc736020351a6f8e71666e1f486bb8bd5ce8112c443a30c77bfde0eb68"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76a4a11ba8f678c9e5876a7d465ab86def047a4fcc043617578368755d63a1bc"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc0e0d41ad8a056a9886bac91ff9d9978e54a244deb61c2972cc76b66752de9c"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8ea35f2419c7d56b3e75fbde2698766daedb374f20eea28ac9b1f668ef4f74"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd340bbd025302276b5aa221dccfe43040c7babfc32f107c36ad783f2ffd8775"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:494eef2c68305ab75139034ea25328a04a548d297712d9cf887bf27c158c388b"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a167344c1d6db06915fb0225592afdc24d8bafaaf02de07d4788ddd37f4bc2f"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c7af25bda96ac799378ac8aba54a8ece732835c7b74cfc201b688a87ed11152"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d2a0f7e17f33e7890257367a1662b05fecaf56625f7dbb6446227aaa2b86448b"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d0d26c7172bdb64f86ee0765c5b26ea1dc45c52389175888ec073b9b28f4305"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-win32.whl", hash = "sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b"}, - {file = "rapidfuzz-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1bac4873f6186f5233b0084b266bfb459e997f4c21fc9f029918f44a9eccd304"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f9f12c2d0aa52b86206d2059916153876a9b1cf9dfb3cf2f344913167f1c3d4"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd501de6f7a8f83557d20613b58734d1cb5f0be78d794cde64fe43cfc63f5f2"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4416ca69af933d4a8ad30910149d3db6d084781d5c5fdedb713205389f535385"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0821b9bdf18c5b7d51722b906b233a39b17f602501a966cfbd9b285f8ab83cd"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0edecc3f90c2653298d380f6ea73b536944b767520c2179ec5d40b9145e47aa"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4513dd01cee11e354c31b75f652d4d466c9440b6859f84e600bdebfccb17735a"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d9727b85511b912571a76ce53c7640ba2c44c364e71cef6d7359b5412739c570"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ab9eab33ee3213f7751dc07a1a61b8d9a3d748ca4458fffddd9defa6f0493c16"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6b01c1ddbb054283797967ddc5433d5c108d680e8fa2684cf368be05407b07e4"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3857e335f97058c4b46fa39ca831290b70de554a5c5af0323d2f163b19c5f2a6"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d98a46cf07c0c875d27e8a7ed50f304d83063e49b9ab63f21c19c154b4c0d08d"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-win32.whl", hash = "sha256:c36539ed2c0173b053dafb221458812e178cfa3224ade0960599bec194637048"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:ec8d7d8567e14af34a7911c98f5ac74a3d4a743cd848643341fc92b12b3784ff"}, - {file = "rapidfuzz-3.11.0-cp39-cp39-win_arm64.whl", hash = "sha256:62171b270ecc4071be1c1f99960317db261d4c8c83c169e7f8ad119211fe7397"}, - {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f06e3c4c0a8badfc4910b9fd15beb1ad8f3b8fafa8ea82c023e5e607b66a78e4"}, - {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fe7aaf5a54821d340d21412f7f6e6272a9b17a0cbafc1d68f77f2fc11009dcd5"}, - {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25398d9ac7294e99876a3027ffc52c6bebeb2d702b1895af6ae9c541ee676702"}, - {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a52eea839e4bdc72c5e60a444d26004da00bb5bc6301e99b3dde18212e41465"}, - {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c87319b0ab9d269ab84f6453601fd49b35d9e4a601bbaef43743f26fabf496c"}, - {file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3048c6ed29d693fba7d2a7caf165f5e0bb2b9743a0989012a98a47b975355cca"}, - {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b04f29735bad9f06bb731c214f27253bd8bedb248ef9b8a1b4c5bde65b838454"}, - {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7864e80a0d4e23eb6194254a81ee1216abdc53f9dc85b7f4d56668eced022eb8"}, - {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3794df87313dfb56fafd679b962e0613c88a293fd9bd5dd5c2793d66bf06a101"}, - {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d71da0012face6f45432a11bc59af19e62fac5a41f8ce489e80c0add8153c3d1"}, - {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff38378346b7018f42cbc1f6d1d3778e36e16d8595f79a312b31e7c25c50bd08"}, - {file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6668321f90aa02a5a789d4e16058f2e4f2692c5230252425c3532a8a62bc3424"}, - {file = "rapidfuzz-3.11.0.tar.gz", hash = "sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbb7ea2fd786e6d66f225ef6eef1728832314f47e82fee877cb2a793ebda9579"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ae41361de05762c1eaa3955e5355de7c4c6f30d1ef1ea23d29bf738a35809ab"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc3c39e0317e7f68ba01bac056e210dd13c7a0abf823e7b6a5fe7e451ddfc496"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69f2520296f1ae1165b724a3aad28c56fd0ac7dd2e4cff101a5d986e840f02d4"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34dcbf5a7daecebc242f72e2500665f0bde9dd11b779246c6d64d106a7d57c99"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:773ab37fccf6e0513891f8eb4393961ddd1053c6eb7e62eaa876e94668fc6d31"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ecf0e6de84c0bc2c0f48bc03ba23cef2c5f1245db7b26bc860c11c6fd7a097c"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4dc2ebad4adb29d84a661f6a42494df48ad2b72993ff43fad2b9794804f91e45"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8389d98b9f54cb4f8a95f1fa34bf0ceee639e919807bb931ca479c7a5f2930bf"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:165bcdecbfed9978962da1d3ec9c191b2ff9f1ccc2668fbaf0613a975b9aa326"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:129d536740ab0048c1a06ccff73c683f282a2347c68069affae8dbc423a37c50"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b67e390261ffe98ec86c771b89425a78b60ccb610c3b5874660216fcdbded4b"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-win32.whl", hash = "sha256:a66520180d3426b9dc2f8d312f38e19bc1fc5601f374bae5c916f53fa3534a7d"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:82260b20bc7a76556cecb0c063c87dad19246a570425d38f8107b8404ca3ac97"}, + {file = "rapidfuzz-3.12.1-cp310-cp310-win_arm64.whl", hash = "sha256:3a860d103bbb25c69c2e995fdf4fac8cb9f77fb69ec0a00469d7fd87ff148f46"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d9afad7b16d01c9e8929b6a205a18163c7e61b6cd9bcf9c81be77d5afc1067a"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb424ae7240f2d2f7d8dda66a61ebf603f74d92f109452c63b0dbf400204a437"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42149e6d13bd6d06437d2a954dae2184dadbbdec0fdb82dafe92860d99f80519"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:760ac95d788f2964b73da01e0bdffbe1bf2ad8273d0437565ce9092ae6ad1fbc"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cf27e8e4bf7bf9d92ef04f3d2b769e91c3f30ba99208c29f5b41e77271a2614"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00ceb8ff3c44ab0d6014106c71709c85dee9feedd6890eff77c814aa3798952b"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b61c558574fbc093d85940c3264c08c2b857b8916f8e8f222e7b86b0bb7d12"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:346a2d8f17224e99f9ef988606c83d809d5917d17ad00207237e0965e54f9730"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d60d1db1b7e470e71ae096b6456e20ec56b52bde6198e2dbbc5e6769fa6797dc"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2477da227e266f9c712f11393182c69a99d3c8007ea27f68c5afc3faf401cc43"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8499c7d963ddea8adb6cffac2861ee39a1053e22ca8a5ee9de1197f8dc0275a5"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12802e5c4d8ae104fb6efeeb436098325ce0dca33b461c46e8df015c84fbef26"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-win32.whl", hash = "sha256:e1061311d07e7cdcffa92c9b50c2ab4192907e70ca01b2e8e1c0b6b4495faa37"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6e4ed63e204daa863a802eec09feea5448617981ba5d150f843ad8e3ae071a4"}, + {file = "rapidfuzz-3.12.1-cp311-cp311-win_arm64.whl", hash = "sha256:920733a28c3af47870835d59ca9879579f66238f10de91d2b4b3f809d1ebfc5b"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f6235b57ae3faa3f85cb3f90c9fee49b21bd671b76e90fc99e8ca2bdf0b5e4a3"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af4585e5812632c357fee5ab781c29f00cd06bea58f8882ff244cc4906ba6c9e"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5942dc4460e5030c5f9e1d4c9383de2f3564a2503fe25e13e89021bcbfea2f44"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b31ab59e1a0df5afc21f3109b6cfd77b34040dbf54f1bad3989f885cfae1e60"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97c885a7a480b21164f57a706418c9bbc9a496ec6da087e554424358cadde445"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d844c0587d969ce36fbf4b7cbf0860380ffeafc9ac5e17a7cbe8abf528d07bb"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93c95dce8917bf428064c64024de43ffd34ec5949dd4425780c72bd41f9d969"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:834f6113d538af358f39296604a1953e55f8eeffc20cb4caf82250edbb8bf679"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a940aa71a7f37d7f0daac186066bf6668d4d3b7e7ef464cb50bc7ba89eae1f51"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ec9eaf73501c9a7de2c6938cb3050392e2ee0c5ca3921482acf01476b85a7226"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c5ec360694ac14bfaeb6aea95737cf1a6cf805b5fe8ea7fd28814706c7fa838"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b5e176524653ac46f1802bdd273a4b44a5f8d0054ed5013a8e8a4b72f254599"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-win32.whl", hash = "sha256:6f463c6f1c42ec90e45d12a6379e18eddd5cdf74138804d8215619b6f4d31cea"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:b894fa2b30cd6498a29e5c470cb01c6ea898540b7e048a0342775a5000531334"}, + {file = "rapidfuzz-3.12.1-cp312-cp312-win_arm64.whl", hash = "sha256:43bb17056c5d1332f517b888c4e57846c4b5f936ed304917eeb5c9ac85d940d4"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:97f824c15bc6933a31d6e3cbfa90188ba0e5043cf2b6dd342c2b90ee8b3fd47c"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a973b3f5cabf931029a3ae4a0f72e3222e53d412ea85fc37ddc49e1774f00fbf"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7880e012228722dec1be02b9ef3898ed023388b8a24d6fa8213d7581932510"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c78582f50e75e6c2bc38c791ed291cb89cf26a3148c47860c1a04d6e5379c8e"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d7d9e6a04d8344b0198c96394c28874086888d0a2b2f605f30d1b27b9377b7d"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5620001fd4d6644a2f56880388179cc8f3767670f0670160fcb97c3b46c828af"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0666ab4c52e500af7ba5cc17389f5d15c0cdad06412c80312088519fdc25686d"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:27b4d440fa50b50c515a91a01ee17e8ede719dca06eef4c0cccf1a111a4cfad3"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83dccfd5a754f2a0e8555b23dde31f0f7920601bfa807aa76829391ea81e7c67"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b572b634740e047c53743ed27a1bb3b4f93cf4abbac258cd7af377b2c4a9ba5b"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7fa7b81fb52902d5f78dac42b3d6c835a6633b01ddf9b202a3ca8443be4b2d6a"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1d4fbff980cb6baef4ee675963c081f7b5d6580a105d6a4962b20f1f880e1fb"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-win32.whl", hash = "sha256:3fe8da12ea77271097b303fa7624cfaf5afd90261002314e3b0047d36f4afd8d"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:6f7e92fc7d2a7f02e1e01fe4f539324dfab80f27cb70a30dd63a95445566946b"}, + {file = "rapidfuzz-3.12.1-cp313-cp313-win_arm64.whl", hash = "sha256:e31be53d7f4905a6a038296d8b773a79da9ee9f0cd19af9490c5c5a22e37d2e5"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bef5c91d5db776523530073cda5b2a276283258d2f86764be4a008c83caf7acd"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:841e0c2a5fbe8fc8b9b1a56e924c871899932c0ece7fbd970aa1c32bfd12d4bf"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046fc67f3885d94693a2151dd913aaf08b10931639cbb953dfeef3151cb1027c"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4d2d39b2e76c17f92edd6d384dc21fa020871c73251cdfa017149358937a41d"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5857dda85165b986c26a474b22907db6b93932c99397c818bcdec96340a76d5"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c26cd1b9969ea70dbf0dbda3d2b54ab4b2e683d0fd0f17282169a19563efeb1"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf56ea4edd69005786e6c80a9049d95003aeb5798803e7a2906194e7a3cb6472"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fbe7580b5fb2db8ebd53819171ff671124237a55ada3f64d20fc9a149d133960"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:018506a53c3b20dcbda8c93d4484b9eb1764c93d5ea16be103cf6b0d8b11d860"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:325c9c71b737fcd32e2a4e634c430c07dd3d374cfe134eded3fe46e4c6f9bf5d"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:930756639643e3aa02d3136b6fec74e5b9370a24f8796e1065cd8a857a6a6c50"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0acbd27543b158cb915fde03877383816a9e83257832818f1e803bac9b394900"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-win32.whl", hash = "sha256:80ff9283c54d7d29b2d954181e137deee89bec62f4a54675d8b6dbb6b15d3e03"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:fd37e53f0ed239d0cec27b250cec958982a8ba252ce64aa5e6052de3a82fa8db"}, + {file = "rapidfuzz-3.12.1-cp39-cp39-win_arm64.whl", hash = "sha256:4a4422e4f73a579755ab60abccb3ff148b5c224b3c7454a13ca217dfbad54da6"}, + {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b7cba636c32a6fc3a402d1cb2c70c6c9f8e6319380aaf15559db09d868a23e56"}, + {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b79286738a43e8df8420c4b30a92712dec6247430b130f8e015c3a78b6d61ac2"}, + {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dc1937198e7ff67e217e60bfa339f05da268d91bb15fec710452d11fe2fdf60"}, + {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b85817a57cf8db32dd5d2d66ccfba656d299b09eaf86234295f89f91be1a0db2"}, + {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04283c6f3e79f13a784f844cd5b1df4f518ad0f70c789aea733d106c26e1b4fb"}, + {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a718f740553aad5f4daef790191511da9c6eae893ee1fc2677627e4b624ae2db"}, + {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cbdf145c7e4ebf2e81c794ed7a582c4acad19e886d5ad6676086369bd6760753"}, + {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0d03ad14a26a477be221fddc002954ae68a9e2402b9d85433f2d0a6af01aa2bb"}, + {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1187aeae9c89e838d2a0a2b954b4052e4897e5f62e5794ef42527bf039d469e"}, + {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd47dfb1bca9673a48b923b3d988b7668ee8efd0562027f58b0f2b7abf27144c"}, + {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187cdb402e223264eebed2fe671e367e636a499a7a9c82090b8d4b75aa416c2a"}, + {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6899b41bf6c30282179f77096c1939f1454836440a8ab05b48ebf7026a3b590"}, + {file = "rapidfuzz-3.12.1.tar.gz", hash = "sha256:6a98bbca18b4a37adddf2d8201856441c26e9c981d8895491b5bc857b5f780eb"}, ] [package.extras] @@ -6521,6 +6550,21 @@ files = [ [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "requirements-parser" +version = "0.11.0" +description = "This is a small Python module for parsing Pip requirement files." +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "requirements_parser-0.11.0-py3-none-any.whl", hash = "sha256:50379eb50311834386c2568263ae5225d7b9d0867fb55cf4ecc93959de2c2684"}, + {file = "requirements_parser-0.11.0.tar.gz", hash = "sha256:35f36dc969d14830bf459803da84f314dc3d17c802592e9e970f63d0359e5920"}, +] + +[package.dependencies] +packaging = ">=23.2" +types-setuptools = ">=69.1.0" + [[package]] name = "retworkx" version = "0.16.0" @@ -8233,6 +8277,17 @@ files = [ {file = "types_retry-0.9.9.20241221.tar.gz", hash = "sha256:ebad6d495a5a04ab0d06d4156a665528c3b84a8461aa019dd6e5d3e33c2aa1e0"}, ] +[[package]] +name = "types-setuptools" +version = "75.8.0.20250110" +description = "Typing stubs for setuptools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types_setuptools-75.8.0.20250110-py3-none-any.whl", hash = "sha256:a9f12980bbf9bcdc23ecd80755789085bad6bfce4060c2275bc2b4ca9f2bc480"}, + {file = "types_setuptools-75.8.0.20250110.tar.gz", hash = "sha256:96f7ec8bbd6e0a54ea180d66ad68ad7a1d7954e7281a710ea2de75e355545271"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -8286,6 +8341,33 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "uv" +version = "0.5.25" +description = "An extremely fast Python package and project manager, written in Rust." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uv-0.5.25-py3-none-linux_armv6l.whl", hash = "sha256:bf0f3a36f70b8024c297f36c1626478f8af12dd964df182ed2bbd696acdbf708"}, + {file = "uv-0.5.25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c51553c808a7dfa0b3a8ec2bb29c041751bc492b8ba296d05294f1b04164afb0"}, + {file = "uv-0.5.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:788faa6eaf687dabf96435ce25631780971d0d4b523fa104c53f339fcf00827e"}, + {file = "uv-0.5.25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:51d0b1781a6a6f3ac662b6eb7a8647ad6b9897f044cdc15113e9460e1b341686"}, + {file = "uv-0.5.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b8ea6cf78d51b26881ec739568ae52e2b909bb994120b8e97f11ae635b95a4"}, + {file = "uv-0.5.25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97854cc3cbda73e926f357c93037c91d49767e7dd25bddb548cb515d1d350a7"}, + {file = "uv-0.5.25-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfc3eaa6c53a324f9521e1db62f50d8989337cd90085d53f5b7159a6f0e092b5"}, + {file = "uv-0.5.25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4aaea1693ee065a038b60c7d00ed8c540d4eed4dc0312d437e2a968cd6a2d0b"}, + {file = "uv-0.5.25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3e702d69bfbcd6a828efb5ecb849be188f6b2cf896da73de0c56dd354c7c244"}, + {file = "uv-0.5.25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8342a523292c4d5ed4ca80f2782961ad2e0fb9a9e428a6ce04d2765eb01d59"}, + {file = "uv-0.5.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:70c50f0a44096abc800f76a77430a3fa8ffe441e083c2ee5468b9ee7ddea1f2c"}, + {file = "uv-0.5.25-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:5e89063fd4477c5c0f334879bbb0fb8f3fcb9520c2a109e8a236f48c41f956e9"}, + {file = "uv-0.5.25-py3-none-musllinux_1_1_i686.whl", hash = "sha256:39119b7a480893c6d27798b6f2856da9da2d33ac22ab0918c9cb3a5bab677b7b"}, + {file = "uv-0.5.25-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:83495e1e2b7c79c1d60802f9a27822b3278ae21dcd06a84f792220919a6dbde7"}, + {file = "uv-0.5.25-py3-none-win32.whl", hash = "sha256:f265f5310c2ae1e9bcbdda56b3f18b032c148c480b10407c13f9b166bb1ef205"}, + {file = "uv-0.5.25-py3-none-win_amd64.whl", hash = "sha256:22c32f4df91c18361a4da6fe29719f448cbaa7272f38e5476da7ea5eba2a1b83"}, + {file = "uv-0.5.25-py3-none-win_arm64.whl", hash = "sha256:d3be5b4cc38cd03210981eef361e6783abb5cd50882847d60769e749a43ca58e"}, + {file = "uv-0.5.25.tar.gz", hash = "sha256:3379343829dacf5e98e6439db62ae0f6e19fa6fad2922b4d6ee49634f1aa906c"}, +] + [[package]] name = "uvicorn" version = "0.18.3" @@ -9103,4 +9185,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "199761c35618827dec96ce2c70d46d432404fbc5f22d0dffad92f9fe6d4b3848" +content-hash = "b8b56fcccf6f619a072ff372f72984c8f0a76ac61777c65b51a0ab3a0332698e" diff --git a/pyproject.toml b/pyproject.toml index ff39aeb84e..9a5756de6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ pyyaml = "^6.0.1" pennylane-sphinx-theme = { git = "https://github.com/PennyLaneAI/pennylane-sphinx-theme.git", branch = "sphinx-update" } pypandoc = "1.5" pennylane = "0.40.0" +uv = "^0.5.25" [tool.poetry.group.executable-dependencies.dependencies] ########################################################### @@ -57,6 +58,7 @@ pennylane-qulacs = "0.40.0" pennylane-catalyst = "0.10.0" ########################################################## +numpy = "~1.24" matplotlib = "3.7.2" jax = "0.4.28" jaxlib = "0.4.28" From 8d1e3ecfe420360b7830754b4c375ffd5d13a34e Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 7 Feb 2025 11:42:05 -0500 Subject: [PATCH 23/29] don't generate full requirements --- lib/qml/lib/cmds.py | 10 +++++- lib/qml/lib/demo.py | 70 ++-------------------------------------- lib/qml/lib/fs.py | 8 +++++ lib/qml/lib/pip_tools.py | 1 + 4 files changed, 21 insertions(+), 68 deletions(-) diff --git a/lib/qml/lib/cmds.py b/lib/qml/lib/cmds.py index 40c75a2088..7e8e5381be 100644 --- a/lib/qml/lib/cmds.py +++ b/lib/qml/lib/cmds.py @@ -64,7 +64,15 @@ def pip_install( Raises: CalledProcessError: The command does not complete successfully """ - cmd = [str(python), "-m", "uv", "pip", "install"] + cmd = [ + str(python), + "-m", + "uv", + "pip", + "install", + "--index-strategy", + "unsafe-best-match", + ] if requirements: cmd.extend(("--requirement", str(requirements))) if constraints: diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index e8910bc043..6cdc09e59a 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -10,7 +10,6 @@ from logging import getLogger import subprocess from enum import Enum -import re import functools import requirements @@ -171,21 +170,17 @@ def _build_demo( quiet: bool = False, ): out_dir = sphinx_dir / "demos" / demo.name - if out_dir.exists(): - shutil.rmtree(out_dir) - out_dir.mkdir(parents=True) + fs.clean_dir(out_dir) with open(out_dir / "requirements.txt", "w") as f: f.write(requirements_generator.generate_requirements(demo.requirements)) if execute: + logger.info("Installing dependencies for demo '%s'", demo.name) cmds.pip_install(build_venv.python, requirements=(out_dir / "requirements.txt")) stage_dir = build_dir / "demonstrations" - if stage_dir.exists(): - shutil.rmtree(stage_dir) - stage_dir.mkdir(parents=True) - + fs.clean_dir(stage_dir) # Need a 'GALLERY_HEADER' file for sphinx-gallery with open(stage_dir / "GALLERY_HEADER.rst", "w"): pass @@ -222,62 +217,3 @@ def _install_build_dependencies(venv: Virtualenv, build_dir: Path): format="requirements.txt", ) cmds.pip_install(venv.python, "-r", build_requirements_file) - - -def _generate_constraints(build_dir: Path): - constraints_file = (build_dir / "constraints.txt").resolve() - cmds.poetry_export( - sys.executable, - constraints_file, - format="constraints.txt", - groups=("executable-dependencies",), - ) - if sys.platform == "darwin": - _fix_pytorch_constraint_macos(constraints_file) - - return constraints_file - - -def _install_execution_dependencies( - venv: Virtualenv, build_dir: Path, demos: Sequence[Demo], quiet: bool -): - """Install dependencies for executing provided demos into - `venv`.""" - constraints_file = (build_dir / "constraints.txt").resolve() - cmds.poetry_export( - sys.executable, - constraints_file, - format="requirements.txt", - groups=("executable-dependencies",), - ) - if sys.platform == "darwin": - _fix_pytorch_constraint_macos(constraints_file) - - requirements: set[str] = set(Demo.CORE_DEPENDENCIES) - for demo in demos: - if demo.executable: - requirements.update(demo.requirements) - - logger.info("Installing execution dependencies") - cmds.pip_install( - venv.python, *requirements, constraints=constraints_file, quiet=quiet - ) - - -def _fix_pytorch_constraint_macos(constraints_file: Path): - """`poetry export` keeps the '+cpu' extra on torch and torchvision when exporting - on MacOS, which uses the PyPi wheels. Because the wheels have no '+cpu' extra on - PyPi, package resolution will fail. - - This function strips the +cpu extra from the constraint. - """ - with open(constraints_file, "r") as f: - constraints = f.read() - - for pattern in (r"(torch==[0-9\.]+)\+cpu", r"(torchvision==[0-9\.]+)\+cpu"): - constraints = re.sub( - pattern, lambda m: m.group(1), constraints, flags=re.MULTILINE - ) - - with open(constraints_file, "w") as f: - f.write(constraints) diff --git a/lib/qml/lib/fs.py b/lib/qml/lib/fs.py index 135023f4ba..8c363cb96c 100644 --- a/lib/qml/lib/fs.py +++ b/lib/qml/lib/fs.py @@ -18,3 +18,11 @@ def file_sha(path: Path) -> bytes: m.update(f.read()) return m.digest() + + +def clean_dir(path: Path) -> None: + """Creates empty directory at ``path``. If it already exists, delete its contents.""" + if path.exists(): + shutil.rmtree(path) + + path.mkdir(parents=True, exist_ok=True) diff --git a/lib/qml/lib/pip_tools.py b/lib/qml/lib/pip_tools.py index c8c2ada77e..a44099d2b9 100644 --- a/lib/qml/lib/pip_tools.py +++ b/lib/qml/lib/pip_tools.py @@ -71,6 +71,7 @@ def generate_requirements(self, requirements_in: frozenset[str]): "uv", "pip", "compile", + "--no-deps", "--index-strategy", "unsafe-best-match", "--extra-index-url", From a505b55e0e4ea71d3be94f3e6a6f470988c094f3 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 7 Feb 2025 11:46:27 -0500 Subject: [PATCH 24/29] sync --- demonstrations_v2/tutorial_liealgebra/demo.py | 2 +- demonstrations_v2/tutorial_liealgebra/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demonstrations_v2/tutorial_liealgebra/demo.py b/demonstrations_v2/tutorial_liealgebra/demo.py index acb16a942f..dcb419dcff 100644 --- a/demonstrations_v2/tutorial_liealgebra/demo.py +++ b/demonstrations_v2/tutorial_liealgebra/demo.py @@ -284,7 +284,7 @@ def IsingGenerators(n, bc="open"): print(f"open: {len(periodic_)} = {2*n*(2*n-1)} = 2 * 2n * (2n - 1)/2") ############################################################### -# This Ising-type Lie algebra is one of only a few handful DLAs that have polynomial scaling, see [#Wiersma]_ for a full classification in 1D +# This Ising-type Lie algebra is one of only a few handful DLAs that have polynomial scaling (see [#Wiersma]_ for a full classification in 1D) # and are thus efficiently simulatable [#Somma]_ [#Goh]_. Less common but also relevant # is the `symplectic algebra `_ :math:`\mathfrak{sp}(2N).` # diff --git a/demonstrations_v2/tutorial_liealgebra/metadata.json b/demonstrations_v2/tutorial_liealgebra/metadata.json index 94e1a00810..cbaff49835 100644 --- a/demonstrations_v2/tutorial_liealgebra/metadata.json +++ b/demonstrations_v2/tutorial_liealgebra/metadata.json @@ -6,7 +6,7 @@ } ], "dateOfPublication": "2024-02-27T00:00:00+00:00", - "dateOfLastModification": "2024-12-18T00:00:00+00:00", + "dateOfLastModification": "2025-02-03T00:00:00+00:00", "categories": [ "Quantum Computing", "Getting Started" From 1e86b656c12c0de7d82e33c800602a9dc07788f4 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 7 Feb 2025 14:48:29 -0500 Subject: [PATCH 25/29] pack demos --- constraints.txt | 1 - .../tutorial_error_mitigation/requirements.in | 5 + lib/qml/lib/cmds.py | 24 ++- lib/qml/lib/demo.py | 88 +++++++++- lib/qml/lib/fs.py | 6 + lib/qml/lib/pip_tools.py | 55 +++--- poetry.lock | 156 +++++++++++++++++- pyproject.toml | 3 + 8 files changed, 296 insertions(+), 42 deletions(-) diff --git a/constraints.txt b/constraints.txt index d0f1dd2c21..131b721954 100644 --- a/constraints.txt +++ b/constraints.txt @@ -23,4 +23,3 @@ torchvision==0.16.2+cpu ; sys_platform != 'darwin' torchvision==0.16.2 ; sys_platform == 'darwin' tensorflow==2.14.1 optax==0.2.3 - diff --git a/demonstrations_v2/tutorial_error_mitigation/requirements.in b/demonstrations_v2/tutorial_error_mitigation/requirements.in index 7d94a7bace..7faec58028 100644 --- a/demonstrations_v2/tutorial_error_mitigation/requirements.in +++ b/demonstrations_v2/tutorial_error_mitigation/requirements.in @@ -1,4 +1,9 @@ mitiq==0.32.0 +ply==3.11 qiskit qiskit_aer qiskit_ibm_runtime +pennylane-cirq +pennylane-qiskit +jax +jaxlib diff --git a/lib/qml/lib/cmds.py b/lib/qml/lib/cmds.py index 7e8e5381be..73907c5712 100644 --- a/lib/qml/lib/cmds.py +++ b/lib/qml/lib/cmds.py @@ -50,6 +50,7 @@ def pip_install( requirements: Path | None = None, constraints: Path | None = None, quiet: bool = True, + use_uv: bool = True, ): """Executes `pip install` with the given python interpreter and args. @@ -64,15 +65,20 @@ def pip_install( Raises: CalledProcessError: The command does not complete successfully """ - cmd = [ - str(python), - "-m", - "uv", - "pip", - "install", - "--index-strategy", - "unsafe-best-match", - ] + cmd = [str(python), "-m"] + if use_uv: + cmd.extend( + [ + "uv", + "pip", + "install", + "--index-strategy", + "unsafe-best-match", + ] + ) + else: + cmd.extend(["pip", "install"]) + if requirements: cmd.extend(("--requirement", str(requirements))) if constraints: diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index 6cdc09e59a..0fdc0b35d1 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from pathlib import Path +from pathlib import Path, PurePosixPath from collections.abc import Sequence, Iterator import shutil from qml.lib import fs, cmds @@ -12,6 +12,8 @@ from enum import Enum import functools import requirements +import json +import lxml.html logger = getLogger("qml") @@ -142,7 +144,7 @@ def build( _install_build_dependencies(build_venv, build_dir) requirements_generator = RequirementsGenerator( - build_venv, + Path(sys.executable), global_constraints_file=constraints_file, extra_index_urls=("https://download.pytorch.org/whl/cpu",), ) @@ -156,6 +158,7 @@ def build( target=target, execute=execute, demo=demo, + package=target is BuildTarget.JSON, ) @@ -167,7 +170,7 @@ def _build_demo( target: BuildTarget, requirements_generator: "RequirementsGenerator", execute: bool, - quiet: bool = False, + package: bool, ): out_dir = sphinx_dir / "demos" / demo.name fs.clean_dir(out_dir) @@ -204,6 +207,15 @@ def _build_demo( } subprocess.run(cmd, env=sphinx_env).check_returncode() + if package: + _package_demo( + demo, + build_dir / "pack", + sphinx_dir / "_static", + build_dir / target.value, + out_dir, + ) + def _install_build_dependencies(venv: Virtualenv, build_dir: Path): """Install dependencies for running sphinx-build into `venv`.""" @@ -216,4 +228,72 @@ def _install_build_dependencies(venv: Virtualenv, build_dir: Path): groups=("base",), format="requirements.txt", ) - cmds.pip_install(venv.python, "-r", build_requirements_file) + cmds.pip_install(venv.python, "-r", build_requirements_file, use_uv=False) + + +def _link_rewriter( + static_dir: Path, image_dir: Path, asset_paths: set[tuple[Path, str]], link: str +): + if "_images/" in link: + _, path = link.split("_images/", maxsplit=2) + asset_paths.add((image_dir / path, f"images/{path}")) + return f"_assets/images/{path}" + elif "_static/" in link: + _, path = link.split("_static/", maxsplit=2) + asset_paths.add((static_dir / path, f"static/{path}")) + return f"_assets/static/{path}" + + return link + + +def _package_demo( + demo: Demo, + pack_dir: Path, + static_dir: Path, + sphinx_output: Path, + sphinx_gallery_output: Path, +): + dest = pack_dir / demo.name + fs.clean_dir(dest) + + with open( + (sphinx_output / "demos" / demo.name / demo.name).with_suffix(".fjson"), "r" + ) as f: + html_body = json.load(f)["body"] + + asset_paths: set[tuple[Path, str]] = set() + html_body: str = lxml.html.rewrite_links( + html_body, + functools.partial( + _link_rewriter, static_dir, sphinx_output / "_images", asset_paths + ), + ) + with open(dest / "body.html", "w") as f: + f.write(html_body) + + for asset, asset_dest in asset_paths: + fs.copy_parents(asset, Path(dest, "_assets", asset_dest)) + + shutil.copy( + (sphinx_gallery_output / demo.name).with_suffix(".ipynb"), dest / "demo.ipynb" + ) + shutil.copy(demo.metadata_file, dest / "metadata.json") + shutil.copy(demo.py_file, dest / "demo.py") + shutil.copy(sphinx_gallery_output / "requirements.txt", dest / "requirements.txt") + for resource in demo.resources: + fs.copy_any(resource, dest / resource.relative_to(demo.path)) + + with open(demo.metadata_file, "r") as f: + metadata = json.load(f) + + for preview_image in metadata["previewImages"]: + if (uri := preview_image["uri"]).startswith("/_static/"): + src = static_dir / uri.removeprefix("/_static/") + path = PurePosixPath( + "_assets", "thumbnails", preview_image["type"] + ).with_suffix(src.suffix) + preview_image["uri"] = path.as_posix() + fs.copy_parents(src, dest / path) + + with open(dest / "metadata.json", "w") as f: + json.dump(metadata, f, indent=2) diff --git a/lib/qml/lib/fs.py b/lib/qml/lib/fs.py index 8c363cb96c..1399f64510 100644 --- a/lib/qml/lib/fs.py +++ b/lib/qml/lib/fs.py @@ -11,6 +11,12 @@ def copy_any(src: Path, dest: Path, exist_ok: bool = False): shutil.copy2(src, dest) +def copy_parents(src: Path, dest: Path): + """Copy `src` to `dest` with all parent directories.""" + dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, dest) + + def file_sha(path: Path) -> bytes: """Return the SHA256 hash of file at ``path``.""" with open(path, "rb") as f: diff --git a/lib/qml/lib/pip_tools.py b/lib/qml/lib/pip_tools.py index a44099d2b9..40d413fc67 100644 --- a/lib/qml/lib/pip_tools.py +++ b/lib/qml/lib/pip_tools.py @@ -1,5 +1,4 @@ import subprocess -from .virtual_env import Virtualenv from collections import defaultdict from pathlib import Path import requirements @@ -17,12 +16,12 @@ class RequirementsGenerator: def __init__( self, - venv: Virtualenv, + python_bin: Path, global_constraints_file: Path, *, extra_index_urls: Sequence[str] | None = None, ): - self.venv = venv + self.python_bin = python_bin global_constraints: dict[str, tuple[str, ...]] = defaultdict(tuple) with open(global_constraints_file, "r") as f: @@ -64,31 +63,33 @@ def generate_requirements(self, requirements_in: frozenset[str]): for req_str in requirements_in: f.write(req_str + "\n") + cmd = [ + str(self.python_bin), + "-m", + "uv", + "pip", + "compile", + "--index-strategy", + "unsafe-best-match", + "--emit-index-url", + "--constraints", + str(constraints_file), + "--output-file", + str(requirements_file), + "--no-header", + "--no-strip-extras", + "--no-strip-markers", + "--universal", + "--quiet", + "--no-annotate", + ] + for index_url in self.extra_index_urls: + cmd.extend(("--extra-index-url", index_url)) + + cmd.append(str(requirements_file)) + subprocess.run( - ( - self.venv.python, - "-m", - "uv", - "pip", - "compile", - "--no-deps", - "--index-strategy", - "unsafe-best-match", - "--extra-index-url", - "https://download.pytorch.org/whl/cpu", - "--emit-index-url", - "--constraints", - str(constraints_file), - "--output-file", - str(requirements_file), - "--no-header", - "--no-strip-extras", - "--no-strip-markers", - "--universal", - "--quiet", - "--no-annotate", - str(requirements_file), - ) + cmd, ).check_returncode() with open(requirements_file, "r") as f: diff --git a/poetry.lock b/poetry.lock index 5ba4e08853..f38f1dc084 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3380,6 +3380,160 @@ files = [ {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, ] +[[package]] +name = "lxml" +version = "5.3.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, + {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, + {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, + {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, + {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, + {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, + {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, + {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, + {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, + {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, + {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, + {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.11)"] + [[package]] name = "mako" version = "1.3.8" @@ -9185,4 +9339,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10.0" -content-hash = "b8b56fcccf6f619a072ff372f72984c8f0a76ac61777c65b51a0ab3a0332698e" +content-hash = "bd5c369f183c35cf054090471692ea9ab48dc632fabc106054a27f8db8a14ad3" diff --git a/pyproject.toml b/pyproject.toml index 9a5756de6c..34cb6c93e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ poetry = "^1.8.5" poetry-plugin-export = "^1.8.0" dulwich = "<0.22" requirements-parser = "^0.11.0" +lxml = "^5.3.0" +uv = "^0.5.25" [tool.poetry.group.base.dependencies] # Base dependencies needed to build the website without any code execution (*-norun) @@ -45,6 +47,7 @@ pypandoc = "1.5" pennylane = "0.40.0" uv = "^0.5.25" + [tool.poetry.group.executable-dependencies.dependencies] ########################################################### ###################### IMPORTANT NOTE ##################### From 03363b48213864e0b514a6f9e87f62b912abac43 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 7 Feb 2025 15:03:40 -0500 Subject: [PATCH 26/29] copy hardware images --- lib/qml/lib/demo.py | 51 ++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index 0fdc0b35d1..aa7a03d921 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -15,6 +15,7 @@ import json import lxml.html + logger = getLogger("qml") @@ -231,21 +232,6 @@ def _install_build_dependencies(venv: Virtualenv, build_dir: Path): cmds.pip_install(venv.python, "-r", build_requirements_file, use_uv=False) -def _link_rewriter( - static_dir: Path, image_dir: Path, asset_paths: set[tuple[Path, str]], link: str -): - if "_images/" in link: - _, path = link.split("_images/", maxsplit=2) - asset_paths.add((image_dir / path, f"images/{path}")) - return f"_assets/images/{path}" - elif "_static/" in link: - _, path = link.split("_static/", maxsplit=2) - asset_paths.add((static_dir / path, f"static/{path}")) - return f"_assets/static/{path}" - - return link - - def _package_demo( demo: Demo, pack_dir: Path, @@ -253,6 +239,16 @@ def _package_demo( sphinx_output: Path, sphinx_gallery_output: Path, ): + """Package a demo into a .zip file for distribution. + + Args: + demo: The demo to package + pack_dir: The directory in which to place the packaged demo + static_dir: The /static directory in the repo root + sphinx_output: The directory containing the sphinx output + sphinx_gallery_output: The directory containing files genreated by + sphinx-gallery + """ dest = pack_dir / demo.name fs.clean_dir(dest) @@ -295,5 +291,30 @@ def _package_demo( preview_image["uri"] = path.as_posix() fs.copy_parents(src, dest / path) + for hardware in metadata.get("hardware", []): + if (uri := hardware["logo"]).startswith("/_static/"): + src = static_dir / uri.removeprefix("/_static/") + path = PurePosixPath("_assets", "logos", Path(src).name) + hardware["logo"] = path.as_posix() + fs.copy_parents(src, dest / path) + with open(dest / "metadata.json", "w") as f: json.dump(metadata, f, indent=2) + + zip_file = shutil.make_archive(dest.name, "zip", dest.parent, dest.name) + shutil.move(zip_file, pack_dir / f"{demo.name}.zip") + + +def _link_rewriter( + static_dir: Path, image_dir: Path, asset_paths: set[tuple[Path, str]], link: str +): + if "_images/" in link: + _, path = link.split("_images/", maxsplit=2) + asset_paths.add((image_dir / path, f"images/{path}")) + return f"_assets/images/{path}" + elif "_static/" in link: + _, path = link.split("_static/", maxsplit=2) + asset_paths.add((static_dir / path, f"static/{path}")) + return f"_assets/static/{path}" + + return link From bbd53cd6142165d5df24fd65fafd6b6778968ed0 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 7 Feb 2025 15:07:41 -0500 Subject: [PATCH 27/29] skip non-executable demos --- lib/qml/lib/demo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index aa7a03d921..51a94ed958 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -175,6 +175,7 @@ def _build_demo( ): out_dir = sphinx_dir / "demos" / demo.name fs.clean_dir(out_dir) + execute = execute and demo.executable with open(out_dir / "requirements.txt", "w") as f: f.write(requirements_generator.generate_requirements(demo.requirements)) From b4c1e66f4592f264ca4626401fa970e9f5b4d6a7 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 7 Feb 2025 15:49:30 -0500 Subject: [PATCH 28/29] jax --- demonstrations_v2/tutorial_quantum_gans/requirements.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demonstrations_v2/tutorial_quantum_gans/requirements.in b/demonstrations_v2/tutorial_quantum_gans/requirements.in index caad7aa85b..ed9cbafe23 100644 --- a/demonstrations_v2/tutorial_quantum_gans/requirements.in +++ b/demonstrations_v2/tutorial_quantum_gans/requirements.in @@ -1,3 +1,5 @@ pandas torch torchvision +jax +jaxlib From 2212966a34f7c2429f7c66ee837b80639919fc10 Mon Sep 17 00:00:00 2001 From: Jack Brown Date: Fri, 7 Feb 2025 15:50:25 -0500 Subject: [PATCH 29/29] jax --- lib/qml/lib/demo.py | 6 +++++- lib/qml/lib/pip_tools.py | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/qml/lib/demo.py b/lib/qml/lib/demo.py index 51a94ed958..d5ebc5cb12 100644 --- a/lib/qml/lib/demo.py +++ b/lib/qml/lib/demo.py @@ -35,6 +35,8 @@ class Demo: "matplotlib", "numpy", "pennylane", + "jax", + "jaxlib" ) ) """Dependencies installed for every demo that do not @@ -182,7 +184,9 @@ def _build_demo( if execute: logger.info("Installing dependencies for demo '%s'", demo.name) - cmds.pip_install(build_venv.python, requirements=(out_dir / "requirements.txt")) + cmds.pip_install( + build_venv.python, requirements=out_dir / "requirements.txt", quiet=True + ) stage_dir = build_dir / "demonstrations" fs.clean_dir(stage_dir) diff --git a/lib/qml/lib/pip_tools.py b/lib/qml/lib/pip_tools.py index 40d413fc67..4e86da313a 100644 --- a/lib/qml/lib/pip_tools.py +++ b/lib/qml/lib/pip_tools.py @@ -71,17 +71,18 @@ def generate_requirements(self, requirements_in: frozenset[str]): "compile", "--index-strategy", "unsafe-best-match", - "--emit-index-url", - "--constraints", - str(constraints_file), - "--output-file", - str(requirements_file), "--no-header", "--no-strip-extras", "--no-strip-markers", "--universal", "--quiet", "--no-annotate", + "--no-deps", + "--emit-index-url", + "--constraints", + str(constraints_file), + "--output-file", + str(requirements_file), ] for index_url in self.extra_index_urls: cmd.extend(("--extra-index-url", index_url))