From 678d8a7dc64d94e87d4022fdf3a2a483bd2a029e Mon Sep 17 00:00:00 2001 From: lujiale Date: Thu, 14 Oct 2021 03:05:39 +0000 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E9=80=80=20'Pull=20Request=20!194=20:?= =?UTF-8?q?=20MindQuantum=20with=20HiQ=205.0'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 76 -- .cmake-format.yaml | 48 - .codespell.allow | 16 - .cppcheck.suppressions | 12 - .gitignore | 5 - .jenkins/check/config/filter_cmakelint.txt | 4 - .jenkins/check/config/filter_cppcheck.txt | 3 - .jenkins/check/config/filter_cpplint.txt | 14 - .jenkins/check/config/filter_pylint.txt | 43 - .jenkins/check/config/whitelizard.txt | 8 - .pre-commit-config.yaml | 153 --- CMakeLists.txt | 126 -- NOTICE | 2 +- README.md | 4 +- RELEASE.md | 136 +-- build.bat | 27 - build.sh | 34 +- cmake/Modules/CheckLinkerFlag.cmake | 86 -- cmake/Modules/CheckSourceCompiles.cmake | 82 -- cmake/Modules/CheckSourceCompilesLocal.cmake | 125 -- cmake/Modules/apple/FindOpenMP.cmake | 725 ----------- cmake/binscope.cmake | 45 - cmake/compiler_flags.cmake | 109 -- cmake/linker_flags.cmake | 234 ---- cmake/macros.cmake | 521 -------- cmake/macros_more.cmake | 57 - cmake/options.cmake | 164 --- cmake/os_detection.cmake | 88 -- cmake/packages.cmake | 200 --- cmake/projectq.cmake | 63 - cmake/pybind11.cmake | 274 ----- cmake/quest.cmake | 74 -- install_with_docker.md | 2 +- install_with_docker_en.md | 4 +- mindquantum/__init__.py | 59 +- mindquantum/algorithm/__init__.py | 26 - .../{algorithm/nisq => ansatz}/__init__.py | 18 +- .../{algorithm/nisq => ansatz}/_ansatz.py | 3 +- .../hardware_efficient.py} | 35 +- .../max_2_sat.py} | 82 +- .../max_cut_ansatz.py => ansatz/max_cut.py} | 32 +- .../qubit_ucc.py} | 30 +- .../nisq/chem => ansatz}/unitary_cc.py | 48 +- mindquantum/circuit/__init__.py | 45 + mindquantum/{core => }/circuit/circuit.py | 281 ++--- .../utils.py => circuit/high_level_ops.py} | 160 +-- mindquantum/circuit/module_circuit.py | 183 +++ .../library => circuit}/quantum_fourier.py | 9 +- mindquantum/circuit/state_evolution.py | 130 ++ .../operators => circuit}/time_evolution.py | 19 +- .../{algorithm/nisq/chem => circuit}/uccsd.py | 90 +- mindquantum/core/__init__.py | 38 - mindquantum/core/circuit/__init__.py | 42 - mindquantum/core/circuit/module_circuit.py | 125 -- mindquantum/core/gates/measurement.py | 332 ----- mindquantum/core/operators/__init__.py | 51 - mindquantum/core/operators/hamiltonian.py | 141 --- mindquantum/core/third_party/__init__.py | 40 - mindquantum/engine/__init__.py | 5 +- mindquantum/engine/circuitengine.py | 7 +- mindquantum/framework/__init__.py | 42 - mindquantum/{core/gates => gate}/__init__.py | 54 +- mindquantum/{core/gates => gate}/basic.py | 134 +-- mindquantum/{core/gates => gate}/basicgate.py | 259 +--- mindquantum/gate/hamiltonian.py | 68 ++ .../{core/operators => gate}/projector.py | 3 +- .../{io/display => hiqfermion}/__init__.py | 11 +- .../transforms}/__init__.py | 8 +- .../transforms}/transform.py | 15 +- .../nisq/chem => hiqfermion/ucc}/__init__.py | 14 +- .../ucc}/qubit_hamiltonian.py | 7 +- .../nisq/chem => hiqfermion/ucc}/quccsd.py | 7 +- .../nisq/chem => hiqfermion/ucc}/uccsd0.py | 7 +- mindquantum/io/__init__.py | 26 - mindquantum/io/display/_config.py | 56 - mindquantum/io/display/circuit_text_drawer.py | 145 --- mindquantum/io/display/measure_res_drawer.py | 67 -- mindquantum/io/qasm/hiqasm.py | 379 ------ mindquantum/io/qasm/openqasm.py | 225 ---- mindquantum/nn/__init__.py | 29 + mindquantum/nn/_check_qnn_input.py | 74 ++ mindquantum/nn/evolution.py | 147 +++ .../nn/mindquantum_ansatz_only_layer.py | 169 +++ mindquantum/nn/mindquantum_layer.py | 141 +++ mindquantum/nn/pqc.py | 212 ++++ mindquantum/{io/qasm => ops}/__init__.py | 16 +- .../{core/operators => ops}/_base_operator.py | 5 +- .../operators => ops}/fermion_operator.py | 11 +- .../operators => ops}/polynomial_tensor.py | 8 +- .../qubit_excitation_operator.py | 20 +- .../{core/operators => ops}/qubit_operator.py | 12 +- .../{core => }/parameterresolver/__init__.py | 2 +- .../parameterresolver/parameterresolver.py | 59 +- mindquantum/simulator/__init__.py | 23 - mindquantum/simulator/simulator.py | 683 ----------- mindquantum/src/CMakeLists.txt | 69 -- mindquantum/src/backends/CMakeLists.txt | 25 - .../src/backends/projectq/CMakeLists.txt | 25 - mindquantum/src/backends/projectq/projectq.h | 487 -------- .../src/backends/projectq/projectq_utils.h | 61 - mindquantum/src/backends/quest/CMakeLists.txt | 23 - mindquantum/src/backends/quest/quest.h | 323 ----- mindquantum/src/backends/quest/quest_utils.h | 218 ---- mindquantum/src/binding.cc | 177 --- mindquantum/src/core/popcnt.h | 29 - mindquantum/src/core/type.h | 140 --- mindquantum/src/core/utils.h | 143 --- mindquantum/src/gate/basic_gate.h | 126 -- mindquantum/src/gate/gates.h | 222 ---- mindquantum/src/hamiltonian/hamiltonian.h | 59 - mindquantum/src/matrix/two_dim_matrix.h | 63 - mindquantum/src/pr/parameter_resolver.h | 68 -- mindquantum/src/projector/projector.h | 49 - mindquantum/src/sparse/algo.h | 194 --- mindquantum/src/sparse/csrhdmatrix.h | 111 -- mindquantum/src/sparse/paulimat.h | 89 -- mindquantum/src/sparse/sparse_utils.h | 93 -- mindquantum/src/utils.cc | 48 - mindquantum/third_party/__init__.py | 6 - .../third_party/interaction_operator.py | 5 +- mindquantum/third_party/unitary_cc.py | 5 +- mindquantum/utils/__init__.py | 15 +- mindquantum/{io => utils}/beauty_print.py | 4 - mindquantum/utils/f.py | 100 +- .../utils.py => utils/utils_operator.py} | 23 +- mindquantum/version.py | 16 + pyproject.toml | 98 -- requirements.txt | 12 +- setup.cfg | 81 -- setup.py | 546 +-------- tests/cmake-ldtest/.gitignore | 1 - tests/cmake-ldtest/CMakeLists.txt.in | 46 - tests/cmake-ldtest/shared_lib.cpp | 19 - tests/cmake-ldtest/shared_lib.hpp | 20 - tests/cmake-ldtest/shared_test.cpp | 22 - tests/st/__init__.py | 1 - tests/st/runtest.sh | 2 +- .../qaoa => tests/st/test_ansatz}/__init__.py | 6 - .../st/test_ansatz/test_hardware_efficient.py | 40 + tests/st/test_ansatz/test_max_2_sat.py | 40 + tests/st/test_ansatz/test_max_cut.py | 39 + tests/st/test_ansatz/test_qubit_ucc.py | 66 + tests/st/test_ansatz/test_ucc.py | 65 + tests/st/test_circuit/test_circuit.py | 107 ++ tests/st/test_circuit/test_high_level_ops.py | 143 +++ tests/st/test_engine/test_circuitengine.py | 40 + tests/st/test_gate/test_gate.py | 180 +++ tests/st/test_gate/test_projector.py | 41 + tests/st/test_hiqfermion/test_quccsd.py | 64 + tests/st/test_hiqfermion/test_transforms.py | 39 + tests/st/test_hiqfermion/test_uccsd0.py | 94 ++ tests/st/test_nn/test_mindquantum.py | 70 ++ tests/st/test_nn/test_nn.py | 147 +++ tests/st/test_ops/test_fermion_ops.py | 112 ++ tests/st/test_ops/test_polynomial_tensor.py | 47 + .../st/test_ops/test_qubit_excitation_ops.py | 134 +++ tests/st/test_ops/test_qubit_ops.py | 118 ++ .../test_parameter_resolver.py | 51 + tests/st/test_utils/test_io.py | 30 - tests/st/test_utils/test_utils.py | 25 +- tests/st/test_utils/test_utils_operator.py | 49 +- tests/ut/__init__.py | 1 - tests/ut/runtest.sh | 2 +- tests/ut/test_mindquantum.py | 1 - third_party/patch/projectq/projectq.patch001 | 969 --------------- third_party/patch/projectq/projectq.patch002 | 322 ----- third_party/patch/quest/quest.patch001 | 211 ---- tutorials/0.frequently_asked_questions.ipynb | 427 ------- .../1.parameterized_quantum_circuit.ipynb | 183 ++- ...experience_of_quantum_neural_network.ipynb | 481 +++----- .../3.classification_of_iris__by_qnn.ipynb | 664 ++++++++++ .../3.classification_of_iris_by_qnn.ipynb | 867 ------------- tutorials/4.quantum_phase_estimation.ipynb | 434 ------- ...m_approximate_optimization_algorithm.ipynb | 481 -------- tutorials/6.qnn_for_nlp.ipynb | 903 -------------- ...earch_algorithm_based_on_mindquantum.ipynb | 1069 ----------------- tutorials/README.md | 9 - tutorials/benchmarks/README.md | 2 +- tutorials/benchmarks/grad/_parse_args.py | 1 - tutorials/benchmarks/grad/mindquantum_grad.py | 36 +- .../grad/tensorflow_quantum_grad.py | 1 - tutorials/benchmarks/mnist/README.md | 2 +- tutorials/benchmarks/mnist/_parse_args.py | 1 - tutorials/benchmarks/mnist/mnist.py | 55 +- tutorials/benchmarks/mnist/mnist_tf.py | 1 - tutorials/benchmarks/qaoa/_parse_args.py | 1 - tutorials/benchmarks/qaoa/qaoa_mindquantum.py | 37 +- tutorials/benchmarks/qaoa/qaoa_paddle.py | 1 - tutorials/images/error_circuit.png | 0 tutorials/images/faq_circuit1.jpg | 0 tutorials/images/faq_circuit2.jpg | 0 tutorials/images/faq_circuit3.jpg | 0 tutorials/images/faq_circuit4.jpg | 0 tutorials/images/faq_circuit5.jpg | 0 tutorials/images/grover_algorithm_circuit.png | 0 tutorials/images/quantum_phase_estimation.png | 0 tutorials/qnn_for_nlp.ipynb | 845 +++++++++++++ ...m_approximate_optimization_algorithm.ipynb | 383 ++++++ .../source/1.parameterized_quantum_circuit.py | 41 - ...al_experience_of_quantum_neural_network.py | 108 -- .../source/3.classification_of_iris_by_qnn.py | 171 --- .../source/4.quantum_phase_estimation.py | 36 - ...r_search_algorithm_based_on_mindquantum.py | 158 --- tutorials/source/iris_classification.py | 151 +++ .../source/parameterized_quantum_circuit.py | 91 ++ .../{6.qnn_for_nlp.py => qnn_for_nlp.py} | 53 +- ...tum_approximate_optimization_algorithm.py} | 53 +- ...mistry.py => vqe_for_quantum_chemistry.py} | 53 +- ....ipynb => vqe_for_quantum_chemistry.ipynb} | 243 ++-- 209 files changed, 6221 insertions(+), 17671 deletions(-) delete mode 100644 .clang-format delete mode 100644 .cmake-format.yaml delete mode 100644 .codespell.allow delete mode 100644 .cppcheck.suppressions delete mode 100644 .jenkins/check/config/filter_cmakelint.txt delete mode 100644 .jenkins/check/config/filter_cppcheck.txt delete mode 100644 .jenkins/check/config/filter_cpplint.txt delete mode 100644 .jenkins/check/config/filter_pylint.txt delete mode 100644 .jenkins/check/config/whitelizard.txt delete mode 100644 .pre-commit-config.yaml delete mode 100644 CMakeLists.txt delete mode 100644 build.bat mode change 100755 => 100644 build.sh delete mode 100644 cmake/Modules/CheckLinkerFlag.cmake delete mode 100644 cmake/Modules/CheckSourceCompiles.cmake delete mode 100644 cmake/Modules/CheckSourceCompilesLocal.cmake delete mode 100644 cmake/Modules/apple/FindOpenMP.cmake delete mode 100644 cmake/binscope.cmake delete mode 100644 cmake/compiler_flags.cmake delete mode 100644 cmake/linker_flags.cmake delete mode 100644 cmake/macros.cmake delete mode 100644 cmake/macros_more.cmake delete mode 100644 cmake/options.cmake delete mode 100644 cmake/os_detection.cmake delete mode 100644 cmake/packages.cmake delete mode 100644 cmake/projectq.cmake delete mode 100644 cmake/pybind11.cmake delete mode 100644 cmake/quest.cmake delete mode 100644 mindquantum/algorithm/__init__.py rename mindquantum/{algorithm/nisq => ansatz}/__init__.py (65%) rename mindquantum/{algorithm/nisq => ansatz}/_ansatz.py (95%) rename mindquantum/{algorithm/nisq/chem/hardware_efficient_ansatz.py => ansatz/hardware_efficient.py} (87%) rename mindquantum/{algorithm/nisq/qaoa/max_2_sat_ansatz.py => ansatz/max_2_sat.py} (65%) rename mindquantum/{algorithm/nisq/qaoa/max_cut_ansatz.py => ansatz/max_cut.py} (83%) rename mindquantum/{algorithm/nisq/chem/qubit_ucc_ansatz.py => ansatz/qubit_ucc.py} (89%) rename mindquantum/{algorithm/nisq/chem => ansatz}/unitary_cc.py (69%) create mode 100644 mindquantum/circuit/__init__.py rename mindquantum/{core => }/circuit/circuit.py (67%) rename mindquantum/{core/circuit/utils.py => circuit/high_level_ops.py} (66%) create mode 100644 mindquantum/circuit/module_circuit.py rename mindquantum/{algorithm/library => circuit}/quantum_fourier.py (87%) create mode 100644 mindquantum/circuit/state_evolution.py rename mindquantum/{core/operators => circuit}/time_evolution.py (76%) rename mindquantum/{algorithm/nisq/chem => circuit}/uccsd.py (75%) delete mode 100644 mindquantum/core/__init__.py delete mode 100644 mindquantum/core/circuit/__init__.py delete mode 100644 mindquantum/core/circuit/module_circuit.py delete mode 100644 mindquantum/core/gates/measurement.py delete mode 100644 mindquantum/core/operators/__init__.py delete mode 100644 mindquantum/core/operators/hamiltonian.py delete mode 100644 mindquantum/core/third_party/__init__.py delete mode 100644 mindquantum/framework/__init__.py rename mindquantum/{core/gates => gate}/__init__.py (64%) rename mindquantum/{core/gates => gate}/basic.py (77%) rename mindquantum/{core/gates => gate}/basicgate.py (61%) create mode 100644 mindquantum/gate/hamiltonian.py rename mindquantum/{core/operators => gate}/projector.py (96%) rename mindquantum/{io/display => hiqfermion}/__init__.py (76%) rename mindquantum/{algorithm/library => hiqfermion/transforms}/__init__.py (87%) rename mindquantum/{algorithm/nisq/chem => hiqfermion/transforms}/transform.py (98%) rename mindquantum/{algorithm/nisq/chem => hiqfermion/ucc}/__init__.py (65%) rename mindquantum/{algorithm/nisq/chem => hiqfermion/ucc}/qubit_hamiltonian.py (86%) rename mindquantum/{algorithm/nisq/chem => hiqfermion/ucc}/quccsd.py (97%) rename mindquantum/{algorithm/nisq/chem => hiqfermion/ucc}/uccsd0.py (98%) delete mode 100644 mindquantum/io/__init__.py delete mode 100644 mindquantum/io/display/_config.py delete mode 100644 mindquantum/io/display/circuit_text_drawer.py delete mode 100644 mindquantum/io/display/measure_res_drawer.py delete mode 100644 mindquantum/io/qasm/hiqasm.py delete mode 100644 mindquantum/io/qasm/openqasm.py create mode 100644 mindquantum/nn/__init__.py create mode 100644 mindquantum/nn/_check_qnn_input.py create mode 100644 mindquantum/nn/evolution.py create mode 100644 mindquantum/nn/mindquantum_ansatz_only_layer.py create mode 100644 mindquantum/nn/mindquantum_layer.py create mode 100644 mindquantum/nn/pqc.py rename mindquantum/{io/qasm => ops}/__init__.py (60%) rename mindquantum/{core/operators => ops}/_base_operator.py (99%) rename mindquantum/{core/operators => ops}/fermion_operator.py (96%) rename mindquantum/{core/operators => ops}/polynomial_tensor.py (99%) rename mindquantum/{core/operators => ops}/qubit_excitation_operator.py (95%) rename mindquantum/{core/operators => ops}/qubit_operator.py (96%) rename mindquantum/{core => }/parameterresolver/__init__.py (97%) rename mindquantum/{core => }/parameterresolver/parameterresolver.py (88%) delete mode 100644 mindquantum/simulator/__init__.py delete mode 100644 mindquantum/simulator/simulator.py delete mode 100644 mindquantum/src/CMakeLists.txt delete mode 100644 mindquantum/src/backends/CMakeLists.txt delete mode 100644 mindquantum/src/backends/projectq/CMakeLists.txt delete mode 100644 mindquantum/src/backends/projectq/projectq.h delete mode 100644 mindquantum/src/backends/projectq/projectq_utils.h delete mode 100644 mindquantum/src/backends/quest/CMakeLists.txt delete mode 100644 mindquantum/src/backends/quest/quest.h delete mode 100644 mindquantum/src/backends/quest/quest_utils.h delete mode 100644 mindquantum/src/binding.cc delete mode 100644 mindquantum/src/core/popcnt.h delete mode 100644 mindquantum/src/core/type.h delete mode 100644 mindquantum/src/core/utils.h delete mode 100644 mindquantum/src/gate/basic_gate.h delete mode 100644 mindquantum/src/gate/gates.h delete mode 100644 mindquantum/src/hamiltonian/hamiltonian.h delete mode 100644 mindquantum/src/matrix/two_dim_matrix.h delete mode 100644 mindquantum/src/pr/parameter_resolver.h delete mode 100644 mindquantum/src/projector/projector.h delete mode 100644 mindquantum/src/sparse/algo.h delete mode 100644 mindquantum/src/sparse/csrhdmatrix.h delete mode 100644 mindquantum/src/sparse/paulimat.h delete mode 100644 mindquantum/src/sparse/sparse_utils.h delete mode 100644 mindquantum/src/utils.cc rename mindquantum/{io => utils}/beauty_print.py (98%) rename mindquantum/{core/operators/utils.py => utils/utils_operator.py} (93%) create mode 100644 mindquantum/version.py delete mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 tests/cmake-ldtest/.gitignore delete mode 100644 tests/cmake-ldtest/CMakeLists.txt.in delete mode 100644 tests/cmake-ldtest/shared_lib.cpp delete mode 100644 tests/cmake-ldtest/shared_lib.hpp delete mode 100644 tests/cmake-ldtest/shared_test.cpp rename {mindquantum/algorithm/nisq/qaoa => tests/st/test_ansatz}/__init__.py (75%) create mode 100644 tests/st/test_ansatz/test_hardware_efficient.py create mode 100644 tests/st/test_ansatz/test_max_2_sat.py create mode 100644 tests/st/test_ansatz/test_max_cut.py create mode 100644 tests/st/test_ansatz/test_qubit_ucc.py create mode 100644 tests/st/test_ansatz/test_ucc.py create mode 100644 tests/st/test_circuit/test_circuit.py create mode 100644 tests/st/test_circuit/test_high_level_ops.py create mode 100755 tests/st/test_engine/test_circuitengine.py create mode 100644 tests/st/test_gate/test_gate.py create mode 100644 tests/st/test_gate/test_projector.py create mode 100644 tests/st/test_hiqfermion/test_quccsd.py create mode 100644 tests/st/test_hiqfermion/test_transforms.py create mode 100644 tests/st/test_hiqfermion/test_uccsd0.py create mode 100644 tests/st/test_nn/test_mindquantum.py create mode 100755 tests/st/test_nn/test_nn.py create mode 100644 tests/st/test_ops/test_fermion_ops.py create mode 100644 tests/st/test_ops/test_polynomial_tensor.py create mode 100644 tests/st/test_ops/test_qubit_excitation_ops.py create mode 100644 tests/st/test_ops/test_qubit_ops.py create mode 100644 tests/st/test_parameter_resolver/test_parameter_resolver.py delete mode 100644 tests/st/test_utils/test_io.py delete mode 100644 third_party/patch/projectq/projectq.patch001 delete mode 100644 third_party/patch/projectq/projectq.patch002 delete mode 100644 third_party/patch/quest/quest.patch001 delete mode 100644 tutorials/0.frequently_asked_questions.ipynb create mode 100644 tutorials/3.classification_of_iris__by_qnn.ipynb delete mode 100644 tutorials/3.classification_of_iris_by_qnn.ipynb delete mode 100644 tutorials/4.quantum_phase_estimation.ipynb delete mode 100644 tutorials/5.quantum_approximate_optimization_algorithm.ipynb delete mode 100644 tutorials/6.qnn_for_nlp.ipynb delete mode 100644 tutorials/8.grover_search_algorithm_based_on_mindquantum.ipynb delete mode 100644 tutorials/README.md delete mode 100644 tutorials/images/error_circuit.png delete mode 100644 tutorials/images/faq_circuit1.jpg delete mode 100644 tutorials/images/faq_circuit2.jpg delete mode 100644 tutorials/images/faq_circuit3.jpg delete mode 100644 tutorials/images/faq_circuit4.jpg delete mode 100644 tutorials/images/faq_circuit5.jpg delete mode 100644 tutorials/images/grover_algorithm_circuit.png delete mode 100644 tutorials/images/quantum_phase_estimation.png create mode 100644 tutorials/qnn_for_nlp.ipynb create mode 100644 tutorials/quantum_approximate_optimization_algorithm.ipynb delete mode 100644 tutorials/source/1.parameterized_quantum_circuit.py delete mode 100644 tutorials/source/2.initial_experience_of_quantum_neural_network.py delete mode 100644 tutorials/source/3.classification_of_iris_by_qnn.py delete mode 100644 tutorials/source/4.quantum_phase_estimation.py delete mode 100644 tutorials/source/8.grover_search_algorithm_based_on_mindquantum.py create mode 100644 tutorials/source/iris_classification.py create mode 100644 tutorials/source/parameterized_quantum_circuit.py rename tutorials/source/{6.qnn_for_nlp.py => qnn_for_nlp.py} (88%) rename tutorials/source/{5.quantum_approximate_optimization_algorithm.py => quantum_approximate_optimization_algorithm.py} (70%) rename tutorials/source/{7.vqe_for_quantum_chemistry.py => vqe_for_quantum_chemistry.py} (71%) rename tutorials/{7.vqe_for_quantum_chemistry.ipynb => vqe_for_quantum_chemistry.ipynb} (78%) diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 2a59a88d8..000000000 --- a/.clang-format +++ /dev/null @@ -1,76 +0,0 @@ -BasedOnStyle: Google -AccessModifierOffset: -3 -AlignConsecutiveMacros: true -AlignEscapedNewlines: Right -AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: Yes -BraceWrapping: - AfterClass: false - AfterControlStatement: false - AfterEnum: false - AfterExternBlock: false - AfterFunction: false - AfterNamespace: false - AfterStruct: false - AfterUnion: false - BeforeCatch: false - BeforeElse: false - SplitEmptyFunction: false - SplitEmptyRecord: false - SplitEmptyNamespace: false -BreakBeforeBinaryOperators: All -BreakBeforeBraces: Custom -BreakBeforeConceptDeclarations: true -BreakBeforeInheritanceComma: true -BreakConstructorInitializers: BeforeComma -ColumnLimit: 120 -CompactNamespaces: false -FixNamespaceComments: true -IncludeBlocks: Regroup -IncludeCategories: - - Regex: '<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tdmath|threads|time|uchar|wchar|wctype)\.h>' - Priority: 1 - - Regex: '<[[:alnum:]_\.]+.h>' - Priority: 2 - - Regex: '<[[:alnum:]_\.]+>' - Priority: 3 - - Regex: '<[[:alnum:]_\.]+' - Priority: 4 - - Regex: '[<"]tweedledum/([A-Za-z0-9.\/\-_])+[>"]' - Priority: 5 - - Regex: '<(catch2|boost|eigen)\/' - Priority: 6 - - Regex: '"]' - Priority: 8 - - Regex: '[<"](driver_types.h)[>"]' - Priority: 8 - - Regex: '"[[:alpha:]\/]*config\.hpp"' - Priority: 10 - - Regex: '"([A-Za-z0-9.\/\-_])+"' - Priority: 20 -IndentPPDirectives: AfterHash -IndentRequires: true -IndentWidth: 4 -KeepEmptyLinesAtTheStartOfBlocks: false -NamespaceIndentation: None -PenaltyBreakAssignment: 40 -ReflowComments: true -SortIncludes: true -SortUsingDeclarations: true -SpaceAfterCStyleCast: true -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatements -SpaceBeforeRangeBasedForLoopColon: true -SpaceInEmptyParentheses: false -SpacesInAngles: false -SpacesInCStyleCastParentheses: false -Standard: c++17 -TabWidth: 4 -UseTab: Never -WhitespaceSensitiveMacros: ['CLANG_DIAG_ON', 'CLANG_DIAG_OFF', 'GCC_DIAG_ON', 'GCC_DIAG_OFF', 'MSVC_DIAG_ON', 'MSVC_DIAG_OFF'] diff --git a/.cmake-format.yaml b/.cmake-format.yaml deleted file mode 100644 index 5d3058835..000000000 --- a/.cmake-format.yaml +++ /dev/null @@ -1,48 +0,0 @@ -markup: - first_comment_is_literal: true -format: - disable: false - line_width: 120 - tab_size: 2 - use_tabchars: false - max_subgroups_hwrap: 2 - max_pargs_hwrap: 6 - max_rows_cmdline: 2 - separate_ctrl_name_with_space: false - separate_fn_name_with_space: false - dangle_parens: false - dangle_align: prefix - min_prefix_chars: 4 - max_prefix_chars: 10 - max_lines_hwrap: 2 - line_ending: unix - command_case: canonical - keyword_case: unchanged - enable_sort: true - autosort: false - require_valid_layout: false -parse: - additional_commands: - test_compile_option: - pargs: - nargs: 1+ - flags: - - AUTO_ADD_CO - kwargs: - LANGS: + - FLAGS: + - GENEX: 1 - test_link_option: - pargs: - nargs: 1+ - flags: - - AUTO_ADD_LO - - VERBATIM - kwargs: - LANGS: + - FLAGS: + - GENEX: 1 -lint: - argument_var_pattern: _?[a-z][a-z0-9_]+ - local_var_pattern: _?([a-z][a-z0-9_]+|[A-Z][A-Z0-9_]+) - macro_pattern: '[0-9a-z_]+' diff --git a/.codespell.allow b/.codespell.allow deleted file mode 100644 index ce7bda948..000000000 --- a/.codespell.allow +++ /dev/null @@ -1,16 +0,0 @@ -nd -te -ans -tbe -TBE -dout -claus -crate -imBED -rouge -noone -outputOf -followings -functionAble -Aline -ket diff --git a/.cppcheck.suppressions b/.cppcheck.suppressions deleted file mode 100644 index 4e5147a24..000000000 --- a/.cppcheck.suppressions +++ /dev/null @@ -1,12 +0,0 @@ -unusedFunction -missingInclude -missingReturn - -unusedStructMember:mindquantum/src/core/type.h:79 -unusedStructMember:mindquantum/src/core/type.h:80 -unusedStructMember:mindquantum/src/core/type.h:81 -unusedStructMember:mindquantum/src/core/type.h:82 -unusedStructMember:mindquantum/src/core/type.h:83 -unusedStructMember:mindquantum/src/core/type.h:84 - -objectIndex:mindquantum/src/core/utils.h:87 diff --git a/.gitignore b/.gitignore index a2dfbcd5b..727a73a69 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,7 @@ .vscode __pycache__/ *.pyc -*.pyd *.so -*.dylib *.so.* *.o *.out @@ -20,6 +18,3 @@ output .ipynb_checkpoints somas_meta analyze_fail.dat -VERSION.txt -requirements.txt -.eggs diff --git a/.jenkins/check/config/filter_cmakelint.txt b/.jenkins/check/config/filter_cmakelint.txt deleted file mode 100644 index c7bebf8e2..000000000 --- a/.jenkins/check/config/filter_cmakelint.txt +++ /dev/null @@ -1,4 +0,0 @@ -# MindQuantum -"mindquantum/cmake" "whitespace/indent" -"mindquantum/cmake/Modules" "whitespace/indent" -"mindquantum/cmake/Modules/apple" "whitespace/indent" diff --git a/.jenkins/check/config/filter_cppcheck.txt b/.jenkins/check/config/filter_cppcheck.txt deleted file mode 100644 index 3dbcf575e..000000000 --- a/.jenkins/check/config/filter_cppcheck.txt +++ /dev/null @@ -1,3 +0,0 @@ -# MindQuantum - -"mindquantum/mindquantum/src/binding.cc" "syntaxError" diff --git a/.jenkins/check/config/filter_cpplint.txt b/.jenkins/check/config/filter_cpplint.txt deleted file mode 100644 index fb0ffebad..000000000 --- a/.jenkins/check/config/filter_cpplint.txt +++ /dev/null @@ -1,14 +0,0 @@ -# MindQuantum -"mindquantum/tests/cmake-ldtest" "whitespace/braces" - -"mindquantum/mindquantum/src" "whitespace/comments" - -"mindquantum/mindquantum/src" "build/include_subdir" -"mindquantum/mindquantum/src/backends/quest" "build/include_subdir" -"mindquantum/mindquantum/src/backends/projectq" "build/include_subdir" -"mindquantum/mindquantum/src/gate" "build/include_subdir" -"mindquantum/mindquantum/src/hamiltonian" "build/include_subdir" -"mindquantum/mindquantum/src/matrix" "build/include_subdir" -"mindquantum/mindquantum/src/pr" "build/include_subdir" -"mindquantum/mindquantum/src/projector" "build/include_subdir" -"mindquantum/mindquantum/src/sparse" "build/include_subdir" diff --git a/.jenkins/check/config/filter_pylint.txt b/.jenkins/check/config/filter_pylint.txt deleted file mode 100644 index 723a06e3b..000000000 --- a/.jenkins/check/config/filter_pylint.txt +++ /dev/null @@ -1,43 +0,0 @@ -# MindQuantum -"mindquantum" "ungrouped-imports" -"mindquantum" "bad-whitespace" -"mindquantum/mindquantum/framework" "unused-argument" -"mindquantum/mindquantum/framework" "arguments-differ" -"mindquantum/setup.py" "invalid-name" -"mindquantum/setup.py" "missing-docstring" -"mindquantum/setup.py" "wrong-import-order" -"mindquantum/mindquantum/framework/_check_qnn_input.py" "duplicate-string-formatting-argument" -"mindquantum/mindquantum/core/ops/_base_operator.py" "bare-except" -"mindquantum/mindquantum/algorithm/nisq" "arguments-differ" -"mindquantum/mindquantum/core/circuit/circuit.py" "redefined-outer-name" -"mindquantum/mindquantum/core/circuit/circuit.py" "invalid-name" - -# Tests -"mindquantum/tests/st" "missing-docstring" -"mindquantum/tests/st" "unused-variable" -"mindquantum/tests/st" "len-as-condition" -"mindquantum/tests/ut" "missing-docstring" -"mindquantum/tests/st" "wrong-import-position" -"mindquantum/tests/st" "protected-access" - -# Benchmarks -"mindquantum/tutorials/benchmarks" "wrong-import-position" -"mindquantum/tutorials/benchmarks" "redefined-outer-name" -"mindquantum/tutorials/benchmarks" "protected-access" -"mindquantum/tutorials/benchmarks" "missing-docstring" -"mindquantum/tutorials/benchmarks" "unused-argument" - -# Tutorials -"mindquantum/tutorials" "missing-docstring" -"mindquantum/tutorials" "wrong-import-position" -"mindquantum/tutorials" "unused-argument" -"mindquantum/tutorials" "redefined-outer-name" -"mindquantum/tutorials" "pointless-statement" -"mindquantum/tutorials" "reimported" -"mindquantum/tutorials" "expression-not-assigned" -"mindquantum/tutorials" "function-redefined" -"mindquantum/tutorials" "unused-variable" -"mindquantum/tutorials" "len-as-condition" -"mindquantum/tutorials" "superfluous-parens" -"mindquantum/tutorials" "invalid-name" -"mindquantum/tutorials" "unused-import" diff --git a/.jenkins/check/config/whitelizard.txt b/.jenkins/check/config/whitelizard.txt deleted file mode 100644 index d748aac7a..000000000 --- a/.jenkins/check/config/whitelizard.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Scene1: -# function_name1, function_name2 -# Scene2: -# file_path:function_name1, function_name2 -# -mindquantum/mindquantum/src/binding.cc:mindquantum::PYBIND11_MODULE -mindquantum/mindquantum/io/qasm/hiqasm.py:trans_v01 -mindquantum/mindquantum/io/qasm/hiqasm.py:to_string diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 6f3bf9ad4..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,153 +0,0 @@ -# To use: -# -# pre-commit run -a -# -# Or: -# -# pre-commit install # (runs every time you commit in git) -# -# To update this file: -# -# pre-commit autoupdate -# -# See https://github.com/pre-commit/pre-commit - ---- - -ci: - skip: [check-manifest] - -exclude: >- - (?x)^( - .*/kernel[0-9]+\.hpp| - third_party/patch/.* - )$ - -repos: - - repo: meta - hooks: - - id: check-useless-excludes - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 - hooks: - - id: check-added-large-files - - id: check-case-conflict - - id: check-merge-conflict - - id: check-symlinks - - id: check-yaml - - id: check-toml - - id: debug-statements - - id: end-of-file-fixer - - id: mixed-line-ending - - id: trailing-whitespace - - id: fix-encoding-pragma - - # Changes tabs to spaces - - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.10 - hooks: - - id: remove-tabs - - - repo: https://github.com/codespell-project/codespell - rev: v2.1.0 - hooks: - - id: codespell - files: (.*mindquantum/.*) - args: [-q, '7', -S, '.git,third_party', -I, .codespell.allow] - - - repo: https://github.com/cheshirekow/cmake-format-precommit - rev: v0.6.13 - hooks: - - id: cmake-format - additional_dependencies: [pyyaml] - - id: cmake-lint - exclude: ^(cmake/Modules/.*)$ - additional_dependencies: [pyyaml] - - - repo: https://github.com/PyCQA/isort - rev: 5.9.3 - hooks: - - id: isort - name: isort (python) - - - repo: https://github.com/psf/black - rev: 21.9b0 - hooks: - - id: black - language_version: python3 - # This is a slow hook, so only run this if --hook-stage manual is passed - stages: [manual] - - - repo: https://gitlab.com/PyCQA/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - name: flake8-strict - exclude: (test_.*\.py)$ - additional_dependencies: [flake8-comprehensions, flake8-breakpoint, - flake8-eradicate, flake8-mutable, pep8-naming, - flake8-docstrings, flake8-secure-coding-standard] - - id: flake8 - name: flake8-test-files - files: (.test_*\.py)$ - additional_dependencies: [flake8-comprehensions, flake8-breakpoint, - flake8-eradicate, flake8-mutable] - - - repo: https://github.com/pre-commit/mirrors-pylint - rev: v3.0.0a4 - hooks: - - id: pylint - name: pylint-strict - exclude: (test_.*\.py)$ - args: [--score=n, --load-plugins=pylint_secure_coding_standard] - # This is a slow hook, so only run this if --hook-stage manual is passed - stages: [manual] - additional_dependencies: [pybind11>=2.6, numpy, scipy, projectq, openfermion, sympy, matplotlib, rich, - pylint-secure-coding-standard] - - id: pylint - name: pylint-test-files - files: ^(test_.*\.py)$ - args: ['--score=n'] - # This is a slow hook, so only run this if --hook-stage manual is passed - stages: [manual] - additional_dependencies: [pybind11>=2.6, numpy, scipy, projectq, openfermion, sympy, matplotlib, rich] - - - repo: https://github.com/mgedmin/check-manifest - rev: '0.47' - hooks: - - id: check-manifest - additional_dependencies: ['setuptools-scm[toml]', 'pybind11>=2.6'] - - - repo: https://github.com/Takishima/cmake-pre-commit-hooks/ - rev: v1.4.0 - hooks: - - id: clang-format - args: [-i] - - id: clang-tidy - stages: [manual] - args: [-Bbuild, -B.pre-commit-build] - exclude: >- - (?x)^( - .*/kernel[0-9]+\.hpp| - .*third_party/.*| - .*\.cu - )$ - - - repo: https://github.com/pocc/pre-commit-hooks - rev: v1.3.4 - hooks: - - id: cppcheck - args: [--force, - --inline-suppr, - --std=c++17, - --language=c++, - --suppressions-list=.cppcheck.suppressions] - - id: cpplint - args: [--root=src, - '--extensions=cxx,cu,hh,cpp,hxx,cuh,h++,cc,c,hpp,c++,h,tpp,txx,cl', - '--filter=-build/header_guard,-build/c++11', - --quiet, - --repository=mindquantum, - --linelength=120, - --recursive] diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index e02654cbc..000000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,126 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -cmake_minimum_required(VERSION 3.15) - -set(_policy_list - CMP0012 - CMP0015 - CMP0022 - CMP0023 - CMP0028 - CMP0042 - CMP0048 - CMP0051 - CMP0054 - CMP0056 - CMP0057 - CMP0066 - CMP0067 - CMP0068 - CMP0074 - CMP0076 - CMP0077 - CMP0079 - CMP0094 - CMP0104) -foreach(_policy ${_policy_list}) - if(POLICY ${_policy}) - cmake_policy(SET ${_policy} NEW) - endif() - # cmake-format: off - # CMP0012: if() recognizes numbers and booleans - # CMP0015: paths relative to source dir for link_directories - # CMP0028: :: in target names - # CMP0042: MACOS_RPATH - # CMP0048: allow VERSION in project() - # CMP0051: list TARGET_OBJECTS in SOURCES property - # CMP0054: no more de-referencing of "expr" in if() statements - # CMP0056: try_compile(): link flags - # CMP0057: if IN_LIST - # CMP0066: try_compile(): use per-config flags, like CMAKE_CXX_FLAGS_RELEASE - # CMP0067: try_compile(): honor language standard variables (like C++11) - # CMP0068: RPATH on Mac OS does not affect install_name - # CMP0074: XXX_ROOT variables for find_package(XXX) - # CMP0076: target_sources relative paths - # CMP0077: option() honors normal variables - # CMP0079: target_link_libraries allows use with targets in other directories - # (CMake 3.13 minimum) - # CMP0094: FindPython* use LOCATION strategy (stop at first valid version) - # CMP0104: Empty CUDA_ARCHITECTURES target property is an error - # cmake-format: on -endforeach() - -list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) -list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/Modules) - -# ============================================================================== -# Macro definitions - -include(${CMAKE_CURRENT_LIST_DIR}/cmake/macros.cmake) - -# ============================================================================== -# Create the MindQuantum project -project(MindQuantum LANGUAGES C CXX) - -# Set a default build type if none was specified -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE - Release - CACHE STRING "Choose the type of build." FORCE) - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo") -endif() - -if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/mindquantum) -endif() - -# ============================================================================== -# OS-detection - -include(${CMAKE_CURRENT_LIST_DIR}/cmake/os_detection.cmake) - -# ============================================================================== -# Options - -include(${CMAKE_CURRENT_LIST_DIR}/cmake/options.cmake) - -# ============================================================================== -# Package dependencies - -include(${CMAKE_CURRENT_LIST_DIR}/cmake/packages.cmake) - -# ============================================================================== -# Setup compiler flags - -include(${CMAKE_CURRENT_LIST_DIR}/cmake/compiler_flags.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/cmake/linker_flags.cmake) - -# ============================================================================== -# Add submodule dependencies (now rather than later so that the relevant macros/variables are defined) - -add_subdirectory(mindquantum/src) - -# ============================================================================== -# Some more macro definitions - -include(${CMAKE_CURRENT_LIST_DIR}/cmake/macros_more.cmake) - -# ============================================================================== diff --git a/NOTICE b/NOTICE index fd00f5349..58d2ef061 100644 --- a/NOTICE +++ b/NOTICE @@ -1,2 +1,2 @@ MindSpore MindQuantum -Copyright 2019-2021 Huawei Technologies Co., Ltd +Copyright 2019-2021 Huawei Technologies Co., Ltd \ No newline at end of file diff --git a/README.md b/README.md index e8482181b..4fc5722b0 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,7 @@ git clone https://gitee.com/mindspore/mindquantum.git ```bash cd ~/mindquantum -bash build.sh -cd output -pip install mindquantum-*.whl +python setup.py install --user ``` ### Install by pip diff --git a/RELEASE.md b/RELEASE.md index 3575b2b25..05f152698 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,110 +1,34 @@ -# MindQuantum 0.3.0 +# MindQuantum 0.3.1 -## MindQuantum 0.3.0 Release Notes +## MindQuantum 0.3.1 Release Notes ### Major Features and Improvements -### API Change - -#### Backwards Incompatible Change - -We unified the abbreviations of some nouns in MindQuantum. - -- `isparameter` property of gate changes to `parameterized` - - - - - - - - - -
0.2.0 0.3.0
- -```python ->>> from mindquantum import RX ->>> gate = RX('a').on(0) ->>> gate.isparameter -True -``` - - - -```python ->>> from mindquantum import RX ->>> gate = RX('a').on(0) ->>> gate.parameterized -True -``` - -
- -- `para_name` of a quantum circuit changes to `params_name` - - - - - - - - - -
0.2.0 0.3.0
- -```python ->>> from mindquantum import Circuit ->>> circ = Circuit().rx('a', 0) ->>> circ.para_name -['a'] -``` - - - -```python ->>> from mindquantum import Circuit ->>> circ = Circuit().rx('a', 0) ->>> circ.params_name -['a'] -``` - -
- -The quantum neural network API was redesigned in this version. From now on, we can easily build a hybrid quantum neural network with the help of `Simulator` in `PYNATIVE_MODE`. - -The following API was removed. - -1. `generate_pqc_operator` -2. `PQC` -3. `MindQuantumLayer` -4. `generate_evolution_operator` -5. `Evolution` -6. `MindQuantumAnsatzOnlyLayer` -7. `MindQuantumAnsatzOnlyOperator` - -The new API was shown as below. - -1. `MQOps` -2. `MQN2Ops` -3. `MQAnsatzOnlyOps` -4. `MQN2AnsatzOnlyOps` -5. `MQEncoderOnlyOps` -6. `MQN2EncoderOnlyOps` -7. `MQLayer` -8. `MQN2Layer` -9. `MQAnsatzOnlyLayer` -10. `MQN2AnsatzOnlyLayer` +- Three tutorials have been rewritten to make them easier to read +- Circuit information such as qubit number, parameters will update immediately after you add gate +- The UN operator now support parameterized gate +- New ansatz that solving max 2 sat problem now are supported + +### Contributors + +Thanks goes to these wonderful people: + +yufan, wengwenkang, xuxusheng, wangzidong, yangkang, lujiale, fanyi, zhangwengang, wangkaisheng, zhoufeng, wangsiyuan, gongxiaoqing, chengxianbin, sunxiyin, wenwenkang, lvdingshun, cuijiangyu, chendiqing, zhangkai, Damien Ngyuen, Zotov Yuriy, liqin, zengjinglin, cuixiaopeng. + +Contributions of any kind are welcome! + +# MindQuantum 0.2.0 ## MindQuantum 0.2.0 Release Notes ### Major Features and Improvements -1. Parameterized FermionOperator and QubitOperator for quantum chemistry -2. Different kinds of transformation between FermionOperator and QubitOperator -3. UCCSD, QAOA and hardware efficient ansatz supported -4. MindQuantumAnsatzOnlyLayer for simulating circuit with ansatz only circuit -5. TimeEvolution with first order Trotter decomposition -6. High level operations for modifying quantum circuit +* Parameterized FermionOperator and QubitOperator for quantum chemistry +* Different kinds of transformation between FermionOperator and QubitOperator +* UCCSD, QAOA and hardware efficient ansatz supported +* MindQuantumAnsatzOnlyLayer for simulating circuit with ansatz only circuit +* TimeEvolution with first order Trotter decomposition +* High level operations for modifying quantum circuit ### Contributors @@ -114,18 +38,20 @@ yufan, wengwenkang, xuxusheng, wanzidong, yankang, lujiale, fanyi, zhangwengang, Contributions of any kind are welcome! +# MindQuantum 0.1.0 + ## MindQuantum 0.1.0 Release Notes Initial release of MindQuantum. ### Major Features and Improvements -1. Easily build parameterized quantum circuit. -2. Effectively simulate quantum circuit. -3. Calculating the gradient of parameters of quantum circuit. -4. PQC (parameterized quantum circuit) operator that naturally compatible with other operators in mindspore framework. -5. Evolution operator that evaluate a quantum circuit and return the quantum state. -6. Data parallelization for PQC operator. +* Easily build parameterized quantum circuit. +* Effectively simulate quantum circuit. +* Calculating the gradient of parameters of quantum circuit. +* PQC (parameterized quantum circuit) operator that naturally compatible with other operators in mindspore framework. +* Evolution operator that evaluate a quantum circuit and return the quantum state. +* Data parallelization for PQC operator. ### Contributors @@ -133,4 +59,4 @@ Thanks goes to these wonderful people: yufan, wengwenkang, xuxusheng, wanzidong, yankang, lujiale, wangkaisheng, zhoufeng, wangsiyuan, gongxiaoqing, chengxianbin, sunxiyin, wenwenkang, lvdingshun, cuijiangyu, chendiqing, zhangkai, Damien Ngyuen, Zotov Yuriy, liqin, zengjinglin, cuixiaopeng. -Contributions of any kind are welcome! +Contributions of any kind are welcome! \ No newline at end of file diff --git a/build.bat b/build.bat deleted file mode 100644 index bac67c1cc..000000000 --- a/build.bat +++ /dev/null @@ -1,27 +0,0 @@ -@rem Copyright 2020 Huawei Technologies Co., Ltd -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem ============================================================================ -@echo off -@title mindquantum_build - -SET BASE_PATH=%CD% -SET BUILD_PATH=%BASE_PATH%/build -SET OUTPUT=%BASE_PATH%/output - -IF NOT EXIST "%BUILD_PATH%" ( - md "build" -) - -cd %BASE_PATH% -python %BASE_PATH%/setup.py bdist_wheel -d %OUTPUT% --set ENABLE_PROJECTQ --unset ENABLE_QUEST %* diff --git a/build.sh b/build.sh old mode 100755 new mode 100644 index 6855ef0ee..4e55df72b --- a/build.sh +++ b/build.sh @@ -13,18 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +set -e + BASEPATH=$(cd "$(dirname $0)"; pwd) OUTPUT_PATH="${BASEPATH}/output" -if command -v python3 >/dev/null 2>&1; then - PYTHON=python3 -elif command -v python >/dev/null 2>&1; then - PYTHON=python3 -else - echo 'Unable to locate python or python3!' 1>&2 - exit 1 -fi - -# ============================================================================== +PYTHON=$(which python3) mk_new_dir() { local create_dir="$1" # the target to make @@ -36,21 +29,22 @@ mk_new_dir() { mkdir -pv "${create_dir}" } +write_checksum() { + cd "$OUTPUT_PATH" || exit + PACKAGE_LIST=$(ls mindquantum-*.whl) || exit + for PACKAGE_NAME in $PACKAGE_LIST; do + echo $PACKAGE_NAME + sha256sum -b "$PACKAGE_NAME" >"$PACKAGE_NAME.sha256" + done +} -# ============================================================================== - -set -e - -cd ${BASEPATH} mk_new_dir "${OUTPUT_PATH}" -args=(--set ENABLE_PROJECTQ --unset ENABLE_QUEST) +${PYTHON} ${BASEPATH}/setup.py bdist_wheel -if [[ $1 = "gpu" ]]; then - args+=(--set ENABLE_CUDA --unset MULTITHREADED --set VERBOSE_CMAKE) -fi +mv ${BASEPATH}/dist/*whl ${OUTPUT_PATH} -${PYTHON} ${BASEPATH}/setup.py bdist_wheel -d ${OUTPUT_PATH} "${args[@]}" +write_checksum echo "------Successfully created mindquantum package------" diff --git a/cmake/Modules/CheckLinkerFlag.cmake b/cmake/Modules/CheckLinkerFlag.cmake deleted file mode 100644 index da5ec50fa..000000000 --- a/cmake/Modules/CheckLinkerFlag.cmake +++ /dev/null @@ -1,86 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -CheckLinkerFlag ---------------- - -.. versionadded:: 3.18 - -Check whether the compiler supports a given link flag. - -.. command:: check_linker_flag - - .. code-block:: cmake - - check_linker_flag( ) - -Check that the link ```` is accepted by the ```` compiler without -a diagnostic. Stores the result in an internal cache entry named ````. - -This command temporarily sets the ``CMAKE_REQUIRED_LINK_OPTIONS`` variable -and calls the :command:`check_source_compiles` command from the -:module:`CheckSourceCompiles` module. See that module's documentation -for a listing of variables that can otherwise modify the build. - -The underlying implementation relies on the :prop_tgt:`LINK_OPTIONS` property -to check the specified flag. The ``LINKER:`` prefix, as described in the -:command:`target_link_options` command, can be used as well. - -A positive result from this check indicates only that the compiler did not -issue a diagnostic message when given the link flag. Whether the flag has any -effect or even a specific one is beyond the scope of this module. - -.. note:: - Since the :command:`try_compile` command forwards flags from variables - like :variable:`CMAKE__FLAGS`, unknown flags in such variables may - cause a false negative for this check. -#]=======================================================================] - -include_guard(GLOBAL) - -include(CMakeCheckCompilerFlagCommonPatterns) - -function(CHECK_LINKER_FLAG _lang _flag _var) - get_property(_supported_languages GLOBAL PROPERTY ENABLED_LANGUAGES) - if(NOT _lang IN_LIST _supported_languages) - message(SEND_ERROR "check_linker_flag: ${_lang}: unknown language.") - return() - endif() - - include(CheckSourceCompiles) - - set(CMAKE_REQUIRED_LINK_OPTIONS "${_flag}") - - # Normalize locale during test compilation. - set(_locale_vars LC_ALL LC_MESSAGES LANG) - foreach(v IN LISTS _locale_vars) - set(_locale_vars_saved_${v} "$ENV{${v}}") - set(ENV{${v}} C) - endforeach() - - if(_lang MATCHES "^(C|CXX)$") - set(_source "int main() { return 0; }") - elseif(_lang STREQUAL "Fortran") - set(_source " program test\n stop\n end program") - elseif(_lang MATCHES "CUDA") - set(_source "__host__ int main() { return 0; }") - elseif(_lang MATCHES "HIP") - set(_source "__host__ int main() { return 0; }") - elseif(_lang MATCHES "^(OBJC|OBJCXX)$") - set(_source "#ifndef __OBJC__\n# error \"Not an Objective-C++ compiler\"\n#endif\nint main(void) { return 0; }") - else() - message(SEND_ERROR "check_linker_flag: ${_lang}: unsupported language.") - return() - endif() - check_compiler_flag_common_patterns(_common_patterns) - - check_source_compiles(${_lang} "${_source}" ${_var} ${_common_patterns}) - - foreach(v IN LISTS _locale_vars) - set(ENV{${v}} ${_locale_vars_saved_${v}}) - endforeach() - set(${_var} - "${${_var}}" - PARENT_SCOPE) -endfunction() diff --git a/cmake/Modules/CheckSourceCompiles.cmake b/cmake/Modules/CheckSourceCompiles.cmake deleted file mode 100644 index 2a196315a..000000000 --- a/cmake/Modules/CheckSourceCompiles.cmake +++ /dev/null @@ -1,82 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -# lint_cmake: -whitespace/indent - -#[=======================================================================[.rst: -CheckSourceCompiles ----------------------- - -.. versionadded:: 3.19 - -Check if given source compiles and links into an executable. - -.. command:: check_source_compiles - - .. code-block:: cmake - - check_source_compiles( - [FAIL_REGEX [...]] - [SRC_EXT ]) - - Check that the source supplied in ```` can be compiled as a source - file for the requested language and linked as an executable (so it must - contain at least a ``main()`` function). The result will be stored in the - internal cache variable specified by ````, with a boolean true - value for success and boolean false for failure. If ``FAIL_REGEX`` is - provided, then failure is determined by checking if anything in the output - matches any of the specified regular expressions. - - By default, the test source file will be given a file extension that matches - the requested language. The ``SRC_EXT`` option can be used to override this - with ``.`` instead. - - The underlying check is performed by the :command:`try_compile` command. The - compile and link commands can be influenced by setting any of the following - variables prior to calling ``check_source_compiles()``: - - ``CMAKE_REQUIRED_FLAGS`` - Additional flags to pass to the compiler. Note that the contents of - :variable:`CMAKE__FLAGS _FLAGS>` and its associated - configuration-specific variable are automatically added to the compiler - command before the contents of ``CMAKE_REQUIRED_FLAGS``. - - ``CMAKE_REQUIRED_DEFINITIONS`` - A :ref:`;-list ` of compiler definitions of the form - ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by - ```` will also be added automatically. - - ``CMAKE_REQUIRED_INCLUDES`` - A :ref:`;-list ` of header search paths to pass to - the compiler. These will be the only header search paths used by - ``try_compile()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES` - directory property will be ignored. - - ``CMAKE_REQUIRED_LINK_OPTIONS`` - A :ref:`;-list ` of options to add to the link - command(see :command:`try_compile` for further details). - - ``CMAKE_REQUIRED_LIBRARIES`` - A :ref:`;-list ` of libraries to add to the link - command. These can be the name of system libraries or they can be - :ref:`Imported Targets ` (see :command:`try_compile` for - further details). - - ``CMAKE_REQUIRED_QUIET`` - If this variable evaluates to a boolean true value, all status messages - associated with the check will be suppressed. - - The check is only performed once, with the result cached in the variable - named by ````. Every subsequent CMake run will re-use this cached - value rather than performing the check again, even if the ```` changes. - In order to force the check to be re-evaluated, the variable named by - ```` must be manually removed from the cache. - -#]=======================================================================] - -include_guard(GLOBAL) -include(CheckSourceCompilesLocal) - -function(CHECK_SOURCE_COMPILES _lang _source _var) - cmake_check_source_compiles(${_lang} "${_source}" ${_var} ${ARGN}) -endfunction() diff --git a/cmake/Modules/CheckSourceCompilesLocal.cmake b/cmake/Modules/CheckSourceCompilesLocal.cmake deleted file mode 100644 index f4d86616b..000000000 --- a/cmake/Modules/CheckSourceCompilesLocal.cmake +++ /dev/null @@ -1,125 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -# lint_cmake: -whitespace/indent - -include_guard(GLOBAL) - -cmake_policy(PUSH) -cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced -cmake_policy(SET CMP0057 NEW) # if() supports IN_LIST - -function(CMAKE_CHECK_SOURCE_COMPILES _lang _source _var) - if(NOT DEFINED "${_var}") - - if(_lang STREQUAL "C") - set(_lang_textual "C") - set(_lang_ext "c") - elseif(_lang STREQUAL "CXX") - set(_lang_textual "C++") - set(_lang_ext "cxx") - elseif(_lang STREQUAL "CUDA") - set(_lang_textual "CUDA") - set(_lang_ext "cu") - elseif(_lang STREQUAL "Fortran") - set(_lang_textual "Fortran") - set(_lang_ext "F90") - elseif(_lang STREQUAL "ISPC") - set(_lang_textual "ISPC") - set(_lang_ext "ispc") - elseif(_lang STREQUAL "OBJC") - set(_lang_textual "Objective-C") - set(_lang_ext "m") - elseif(_lang STREQUAL "OBJCXX") - set(_lang_textual "Objective-C++") - set(_lang_ext "mm") - else() - message(SEND_ERROR "check_source_compiles: ${_lang}: unknown language.") - return() - endif() - - get_property(_supported_languages GLOBAL PROPERTY ENABLED_LANGUAGES) - if(NOT _lang IN_LIST _supported_languages) - message(SEND_ERROR "check_source_compiles: ${_lang}: needs to be enabled before use.") - return() - endif() - - set(_FAIL_REGEX) - set(_SRC_EXT) - set(_key) - foreach(arg ${ARGN}) - if("${arg}" MATCHES "^(FAIL_REGEX|SRC_EXT)$") - set(_key "${arg}") - elseif(_key STREQUAL "FAIL_REGEX") - list(APPEND _FAIL_REGEX "${arg}") - elseif(_key STREQUAL "SRC_EXT") - set(_SRC_EXT "${arg}") - set(_key "") - else() - message(FATAL_ERROR "Unknown argument:\n ${arg}\n") - endif() - endforeach() - - if(NOT _SRC_EXT) - set(_SRC_EXT ${_lang_ext}) - endif() - - if(CMAKE_REQUIRED_LINK_OPTIONS) - set(CHECK_${LANG}_SOURCE_COMPILES_ADD_LINK_OPTIONS LINK_OPTIONS ${CMAKE_REQUIRED_LINK_OPTIONS}) - else() - set(CHECK_${LANG}_SOURCE_COMPILES_ADD_LINK_OPTIONS) - endif() - if(CMAKE_REQUIRED_LIBRARIES) - set(CHECK_${LANG}_SOURCE_COMPILES_ADD_LIBRARIES LINK_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) - else() - set(CHECK_${LANG}_SOURCE_COMPILES_ADD_LIBRARIES) - endif() - if(CMAKE_REQUIRED_INCLUDES) - set(CHECK_${LANG}_SOURCE_COMPILES_ADD_INCLUDES "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") - else() - set(CHECK_${LANG}_SOURCE_COMPILES_ADD_INCLUDES) - endif() - file(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.${_SRC_EXT}" "${_source}\n") - - if(NOT CMAKE_REQUIRED_QUIET) - message(CHECK_START "Performing Test ${_var}") - endif() - try_compile( - ${_var} ${CMAKE_BINARY_DIR} - ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.${_SRC_EXT} - COMPILE_DEFINITIONS -D${_var} ${CMAKE_REQUIRED_DEFINITIONS} ${CHECK_${LANG}_SOURCE_COMPILES_ADD_LINK_OPTIONS} - ${CHECK_${LANG}_SOURCE_COMPILES_ADD_LIBRARIES} - CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${CMAKE_REQUIRED_FLAGS} "${CHECK_${LANG}_SOURCE_COMPILES_ADD_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) - - foreach(_regex ${_FAIL_REGEX}) - if("${OUTPUT}" MATCHES "${_regex}") - set(${_var} 0) - endif() - endforeach() - - if(${_var}) - set(${_var} - 1 - CACHE INTERNAL "Test ${_var}") - if(NOT CMAKE_REQUIRED_QUIET) - message(CHECK_PASS "Success") - endif() - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Performing ${_lang_textual} SOURCE FILE Test ${_var} succeeded with the following output:\n" "${OUTPUT}\n" - "Source file was:\n${_source}\n") - else() - if(NOT CMAKE_REQUIRED_QUIET) - message(CHECK_FAIL "Failed") - endif() - set(${_var} - "" - CACHE INTERNAL "Test ${_var}") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Performing ${_lang_textual} SOURCE FILE Test ${_var} failed with the following output:\n" "${OUTPUT}\n" - "Source file was:\n${_source}\n") - endif() - endif() -endfunction() - -cmake_policy(POP) diff --git a/cmake/Modules/apple/FindOpenMP.cmake b/cmake/Modules/apple/FindOpenMP.cmake deleted file mode 100644 index 1d8547875..000000000 --- a/cmake/Modules/apple/FindOpenMP.cmake +++ /dev/null @@ -1,725 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or -# https://cmake.org/licensing for details. - -# lint_cmake: -convention/filename,-whitespace/indent,-package/stdargs - -# cmake-lint: disable=C0103,C0111,R0912,R0915,E1121 - -#[=======================================================================[.rst: -FindOpenMP ----------- - -Finds Open Multi-Processing (OpenMP) support. - -This module can be used to detect OpenMP support in a compiler. If -the compiler supports OpenMP, the flags required to compile with -OpenMP support are returned in variables for the different languages. -The variables may be empty if the compiler does not need a special -flag to support OpenMP. - -.. versionadded:: 3.5 - Clang support. - -Variables -^^^^^^^^^ - -.. versionadded:: 3.10 - The module exposes the components ``C``, ``CXX``, and ``Fortran``. - Each of these controls the various languages to search OpenMP support for. - -Depending on the enabled components the following variables will be set: - -``OpenMP_FOUND`` - Variable indicating that OpenMP flags for all requested languages have been found. - If no components are specified, this is true if OpenMP settings for all enabled languages - were detected. -``OpenMP_VERSION`` - Minimal version of the OpenMP standard detected among the requested languages, - or all enabled languages if no components were specified. - -This module will set the following variables per language in your -project, where ```` is one of C, CXX, or Fortran: - -``OpenMP__FOUND`` - Variable indicating if OpenMP support for ```` was detected. -``OpenMP__FLAGS`` - OpenMP compiler flags for ````, separated by spaces. -``OpenMP__INCLUDE_DIRS`` - Directories that must be added to the header search path for ```` - when using OpenMP. - -For linking with OpenMP code written in ````, the following -variables are provided: - -``OpenMP__LIB_NAMES`` - :ref:`;-list ` of libraries for OpenMP programs for ````. -``OpenMP__LIBRARY`` - Location of the individual libraries needed for OpenMP support in ````. -``OpenMP__LIBRARIES`` - A list of libraries needed to link with OpenMP code written in ````. - -Additionally, the module provides :prop_tgt:`IMPORTED` targets: - -``OpenMP::OpenMP_`` - Target for using OpenMP from ````. - -Specifically for Fortran, the module sets the following variables: - -``OpenMP_Fortran_HAVE_OMPLIB_HEADER`` - Boolean indicating if OpenMP is accessible through ``omp_lib.h``. -``OpenMP_Fortran_HAVE_OMPLIB_MODULE`` - Boolean indicating if OpenMP is accessible through the ``omp_lib`` Fortran module. - -The module will also try to provide the OpenMP version variables: - -``OpenMP__SPEC_DATE`` - .. versionadded:: 3.7 - - Date of the OpenMP specification implemented by the ```` compiler. -``OpenMP__VERSION_MAJOR`` - Major version of OpenMP implemented by the ```` compiler. -``OpenMP__VERSION_MINOR`` - Minor version of OpenMP implemented by the ```` compiler. -``OpenMP__VERSION`` - OpenMP version implemented by the ```` compiler. - -The specification date is formatted as given in the OpenMP standard: -``yyyymm`` where ``yyyy`` and ``mm`` represents the year and month of -the OpenMP specification implemented by the ```` compiler. - -For some compilers, it may be necessary to add a header search path to find -the relevant OpenMP headers. This location may be language-specific. Where -this is needed, the module may attempt to find the location, but it can be -provided directly by setting the ``OpenMP__INCLUDE_DIR`` cache variable. -Note that this variable is an _input_ control to the module. Project code -should use the ``OpenMP__INCLUDE_DIRS`` _output_ variable if it needs -to know what include directories are needed. -#]=======================================================================] - -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) # if() recognizes numbers and booleans -cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced -cmake_policy(SET CMP0057 NEW) # if IN_LIST - -function(_OPENMP_FLAG_CANDIDATES LANG) - if(NOT OpenMP_${LANG}_FLAG) - unset(OpenMP_FLAG_CANDIDATES) - - set(OMP_FLAG_GNU "-fopenmp") - # set(OMP_FLAG_Clang "-fopenmp=libomp" "-fopenmp=libiomp5" "-fopenmp" "-Xclang -fopenmp") - set(OMP_FLAG_Clang "-fopenmp") - set(OMP_FLAG_AppleClang "-Xclang -fopenmp") - set(OMP_FLAG_HP "+Oopenmp") - if(WIN32) - set(OMP_FLAG_Intel "-Qopenmp") - elseif(CMAKE_${LANG}_COMPILER_ID STREQUAL "Intel" AND "${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS - "15.0.0.20140528") - set(OMP_FLAG_Intel "-openmp") - else() - set(OMP_FLAG_Intel "-qopenmp") - endif() - if(CMAKE_${LANG}_COMPILER_ID STREQUAL "IntelLLVM" AND "x${CMAKE_${LANG}_COMPILER_FRONTEND_VARIANT}" STREQUAL - "xMSVC") - set(OMP_FLAG_IntelLLVM "-Qiopenmp") - else() - set(OMP_FLAG_IntelLLVM "-fiopenmp") - endif() - set(OMP_FLAG_MSVC "-openmp") - set(OMP_FLAG_PathScale "-openmp") - set(OMP_FLAG_NAG "-openmp") - set(OMP_FLAG_Absoft "-openmp") - set(OMP_FLAG_NVHPC "-mp") - set(OMP_FLAG_PGI "-mp") - set(OMP_FLAG_Flang "-fopenmp") - set(OMP_FLAG_SunPro "-xopenmp") - set(OMP_FLAG_XL "-qsmp=omp") - # Cray compiler activate OpenMP with -h omp, which is enabled by default. - set(OMP_FLAG_Cray " " "-h omp") - - # If we know the correct flags, use those - if(DEFINED OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}) - set(OpenMP_FLAG_CANDIDATES "${OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}}") - # Fall back to reasonable default tries otherwise - else() - set(OpenMP_FLAG_CANDIDATES "-openmp" "-fopenmp" "-mp" " ") - endif() - set(OpenMP_${LANG}_FLAG_CANDIDATES - "${OpenMP_FLAG_CANDIDATES}" - PARENT_SCOPE) - else() - set(OpenMP_${LANG}_FLAG_CANDIDATES - "${OpenMP_${LANG}_FLAG}" - PARENT_SCOPE) - endif() -endfunction() - -# sample openmp source code to test -set(OpenMP_C_CXX_TEST_SOURCE - " -#include -int main(void) { -#ifdef _OPENMP - omp_get_max_threads(); - return 0; -#elif defined(__HIP_DEVICE_COMPILE__) - return 0; -#else - breaks_on_purpose -#endif -} -") - -# in Fortran, an implementation may provide an omp_lib.h header or omp_lib module, or both (OpenMP standard, section -# 3.1) Furthmore !$ is the Fortran equivalent of #ifdef _OPENMP (OpenMP standard, 2.2.2) Without the conditional -# compilation, some compilers (e.g. PGI) might compile OpenMP code while not actually enabling OpenMP, building code -# sequentially -set(OpenMP_Fortran_TEST_SOURCE - " - program test - @OpenMP_Fortran_INCLUDE_LINE@ - !$ integer :: n - n = omp_get_num_threads() - end program test - ") - -function(_OPENMP_WRITE_SOURCE_FILE LANG SRC_FILE_CONTENT_VAR SRC_FILE_NAME SRC_FILE_FULLPATH) - set(WORK_DIR ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FindOpenMP) - if("${LANG}" STREQUAL "C") - set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.c") - file(WRITE "${SRC_FILE}" "${OpenMP_C_CXX_${SRC_FILE_CONTENT_VAR}}") - elseif("${LANG}" STREQUAL "CXX") - set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.cpp") - file(WRITE "${SRC_FILE}" "${OpenMP_C_CXX_${SRC_FILE_CONTENT_VAR}}") - elseif("${LANG}" STREQUAL "Fortran") - set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.f90") - file(WRITE "${SRC_FILE}_in" "${OpenMP_Fortran_${SRC_FILE_CONTENT_VAR}}") - configure_file("${SRC_FILE}_in" "${SRC_FILE}" @ONLY) - endif() - set(${SRC_FILE_FULLPATH} - "${SRC_FILE}" - PARENT_SCOPE) -endfunction() - -include(CMakeParseImplicitLinkInfo) - -function(_OPENMP_GET_FLAGS LANG FLAG_MODE OPENMP_FLAG_VAR OPENMP_LIB_NAMES_VAR) - _openmp_flag_candidates("${LANG}") - _openmp_write_source_file("${LANG}" "TEST_SOURCE" OpenMPTryFlag _OPENMP_TEST_SRC) - - unset(OpenMP_VERBOSE_COMPILE_OPTIONS) - separate_arguments(OpenMP_VERBOSE_OPTIONS NATIVE_COMMAND "${CMAKE_${LANG}_VERBOSE_FLAG}") - foreach(_VERBOSE_OPTION IN LISTS OpenMP_VERBOSE_OPTIONS) - if(NOT _VERBOSE_OPTION MATCHES "^-Wl,") - list(APPEND OpenMP_VERBOSE_COMPILE_OPTIONS ${_VERBOSE_OPTION}) - endif() - endforeach() - - foreach(OPENMP_FLAG IN LISTS OpenMP_${LANG}_FLAG_CANDIDATES) - set(OPENMP_FLAGS_TEST "${OPENMP_FLAG}") - if(OpenMP_VERBOSE_COMPILE_OPTIONS) - string(APPEND OPENMP_FLAGS_TEST " ${OpenMP_VERBOSE_COMPILE_OPTIONS}") - endif() - string(REGEX REPLACE "[-/=+]" "" OPENMP_PLAIN_FLAG "${OPENMP_FLAG}") - try_compile( - OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} - ${_OPENMP_TEST_SRC} - CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" - LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} - OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT) - - if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) - set("${OPENMP_FLAG_VAR}" - "${OPENMP_FLAG}" - PARENT_SCOPE) - - if(CMAKE_${LANG}_VERBOSE_FLAG) - unset(OpenMP_${LANG}_IMPLICIT_LIBRARIES) - unset(OpenMP_${LANG}_IMPLICIT_LINK_DIRS) - unset(OpenMP_${LANG}_IMPLICIT_FWK_DIRS) - unset(OpenMP_${LANG}_LOG_VAR) - - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Detecting ${LANG} OpenMP compiler ABI info compiled with the following output:\ -\n${OpenMP_TRY_COMPILE_OUTPUT}\n\n") - - cmake_parse_implicit_link_info( - "${OpenMP_TRY_COMPILE_OUTPUT}" OpenMP_${LANG}_IMPLICIT_LIBRARIES OpenMP_${LANG}_IMPLICIT_LINK_DIRS - OpenMP_${LANG}_IMPLICIT_FWK_DIRS OpenMP_${LANG}_LOG_VAR "${CMAKE_${LANG}_IMPLICIT_OBJECT_REGEX}") - - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Parsed ${LANG} OpenMP implicit link information from above output:\n${OpenMP_${LANG}_LOG_VAR}\n\n") - - unset(_OPENMP_LIB_NAMES) - foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_IMPLICIT_LIBRARIES) - get_filename_component(_OPENMP_IMPLICIT_LIB_DIR "${_OPENMP_IMPLICIT_LIB}" DIRECTORY) - get_filename_component(_OPENMP_IMPLICIT_LIB_NAME "${_OPENMP_IMPLICIT_LIB}" NAME) - get_filename_component(_OPENMP_IMPLICIT_LIB_PLAIN "${_OPENMP_IMPLICIT_LIB}" NAME_WE) - string(REGEX REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PLAIN_ESC - "${_OPENMP_IMPLICIT_LIB_PLAIN}") - string(REGEX REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PATH_ESC "${_OPENMP_IMPLICIT_LIB}") - if(NOT - ("${_OPENMP_IMPLICIT_LIB}" IN_LIST CMAKE_${LANG}_IMPLICIT_LINK_LIBRARIES - OR "${CMAKE_${LANG}_STANDARD_LIBRARIES}" MATCHES - "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)" - OR "${CMAKE_${LANG}_LINK_EXECUTABLE}" MATCHES - "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)")) - if(_OPENMP_IMPLICIT_LIB_DIR) - set(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY - "${_OPENMP_IMPLICIT_LIB}" - CACHE FILEPATH "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP") - else() - find_library( - OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY - NAMES "${_OPENMP_IMPLICIT_LIB_NAME}" - DOC "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP" - HINTS ${OpenMP_${LANG}_IMPLICIT_LINK_DIRS} - CMAKE_FIND_ROOT_PATH_BOTH NO_DEFAULT_PATH) - endif() - mark_as_advanced(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY) - list(APPEND _OPENMP_LIB_NAMES ${_OPENMP_IMPLICIT_LIB_PLAIN}) - endif() - endforeach() - set("${OPENMP_LIB_NAMES_VAR}" - "${_OPENMP_LIB_NAMES}" - PARENT_SCOPE) - else() - # We do not know how to extract implicit OpenMP libraries for this compiler. Assume that it handles them - # automatically, e.g. the Intel Compiler on Windows should put the dependency in its object files. - set("${OPENMP_LIB_NAMES_VAR}" - "" - PARENT_SCOPE) - endif() - break() - elseif((CMAKE_${LANG}_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_${LANG}_COMPILER_VERSION VERSION_GREATER_EQUAL - "7.0") OR (CMAKE_${LANG}_COMPILER_ID STREQUAL "Clang" - AND APPLE)) - # Check for separate OpenMP library on AppleClang 7+ - find_library( - OpenMP_libomp_LIBRARY - NAMES omp gomp iomp5 - HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}) - mark_as_advanced(OpenMP_libomp_LIBRARY) - - if(OpenMP_libomp_LIBRARY) - # Try without specifying include directory first. We only want to explicitly add a search path if the header - # can't be found on the default header search path already. - try_compile( - OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} - ${_OPENMP_TEST_SRC} - CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" - LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY} - OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT) - - if(NOT OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) - # Retry with the LINK_DIRECTORIES path set - - get_filename_component(_omp_dir ${OpenMP_libomp_LIBRARY} DIRECTORY) - set(OpenMP_libomp_LIBRARY_DIR - ${_omp_dir} - PARENT_SCOPE) - set(_linkDirFlags "-DLINK_DIRECTORIES:STRING=${_omp_dir} ") - - try_compile( - OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} - ${_OPENMP_TEST_SRC} - CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" ${_linkDirFlags} - LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY} - OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT) - endif() - - if(NOT OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) - find_path(OpenMP_${LANG}_INCLUDE_DIR omp.h) - mark_as_advanced(OpenMP_${LANG}_INCLUDE_DIR) - set(OpenMP_${LANG}_INCLUDE_DIR - "${OpenMP_${LANG}_INCLUDE_DIR}" - PARENT_SCOPE) - if(OpenMP_${LANG}_INCLUDE_DIR) - try_compile( - OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} - ${_OPENMP_TEST_SRC} - CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" ${_linkDirFlags} - "-DINCLUDE_DIRECTORIES:STRING=${OpenMP_${LANG}_INCLUDE_DIR}" - LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY} - OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT) - endif() - else() - set(_tmp) - endif() - if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) - set("${OPENMP_FLAG_VAR}" - "${OPENMP_FLAG}" - PARENT_SCOPE) - set("${OPENMP_LIB_NAMES_VAR}" - "libomp" - PARENT_SCOPE) - if(OpenMP_libomp_LIBRARY_DIR) - set(OpenMP_libomp_LIBRARY_DIR - "${OpenMP_libomp_LIBRARY_DIR}" - PARENT_SCOPE) - endif() - break() - endif() - endif() - elseif(CMAKE_${LANG}_COMPILER_ID STREQUAL "Clang" AND WIN32) - # Check for separate OpenMP library for Clang on Windows - find_library( - OpenMP_libomp_LIBRARY - NAMES libomp libgomp libiomp5 - HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}) - mark_as_advanced(OpenMP_libomp_LIBRARY) - if(OpenMP_libomp_LIBRARY) - try_compile( - OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} - ${_OPENMP_TEST_SRC} - CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}" - LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY} - OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT) - if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG}) - set("${OPENMP_FLAG_VAR}" - "${OPENMP_FLAG}" - PARENT_SCOPE) - set("${OPENMP_LIB_NAMES_VAR}" - "libomp" - PARENT_SCOPE) - break() - endif() - endif() - else() - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Detecting ${LANG} OpenMP failed with the following output:\n${OpenMP_TRY_COMPILE_OUTPUT}\n\n") - endif() - set("${OPENMP_LIB_NAMES_VAR}" - "NOTFOUND" - PARENT_SCOPE) - set("${OPENMP_FLAG_VAR}" - "NOTFOUND" - PARENT_SCOPE) - endforeach() - - unset(OpenMP_VERBOSE_COMPILE_OPTIONS) -endfunction() - -set(OpenMP_C_CXX_CHECK_VERSION_SOURCE - " -#include -#include -const char ompver_str[] = { 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M', - 'P', '-', 'd', 'a', 't', 'e', '[', - ('0' + ((_OPENMP/100000)%10)), - ('0' + ((_OPENMP/10000)%10)), - ('0' + ((_OPENMP/1000)%10)), - ('0' + ((_OPENMP/100)%10)), - ('0' + ((_OPENMP/10)%10)), - ('0' + ((_OPENMP/1)%10)), - ']', '\\0' }; -int main(void) -{ - puts(ompver_str); - return 0; -} -") - -set(OpenMP_Fortran_CHECK_VERSION_SOURCE - " - program omp_ver - @OpenMP_Fortran_INCLUDE_LINE@ - integer, parameter :: zero = ichar('0') - integer, parameter :: ompv = openmp_version - character, dimension(24), parameter :: ompver_str =& - (/ 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M', 'P', '-',& - 'd', 'a', 't', 'e', '[',& - char(zero + mod(ompv/100000, 10)),& - char(zero + mod(ompv/10000, 10)),& - char(zero + mod(ompv/1000, 10)),& - char(zero + mod(ompv/100, 10)),& - char(zero + mod(ompv/10, 10)),& - char(zero + mod(ompv/1, 10)), ']' /) - print *, ompver_str - end program omp_ver -") - -function(_OPENMP_GET_SPEC_DATE LANG SPEC_DATE) - _openmp_write_source_file("${LANG}" "CHECK_VERSION_SOURCE" OpenMPCheckVersion _OPENMP_TEST_SRC) - - unset(_includeDirFlags) - if(OpenMP_${LANG}_INCLUDE_DIR) - set(_includeDirFlags "-DINCLUDE_DIRECTORIES:STRING=${OpenMP_${LANG}_INCLUDE_DIR}") - endif() - - unset(_linkDirFlags) - if(OpenMP_libomp_LIBRARY_DIR) - set(_linkDirFlags "-DLINK_DIRECTORIES:STRING=${OpenMP_libomp_LIBRARY_DIR}") - endif() - - set(BIN_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FindOpenMP/ompver_${LANG}.bin") - string(REGEX REPLACE "[-/=+]" "" OPENMP_PLAIN_FLAG "${OPENMP_FLAG}") - try_compile( - OpenMP_SPECTEST_${LANG}_${OPENMP_PLAIN_FLAG} "${CMAKE_BINARY_DIR}" - "${_OPENMP_TEST_SRC}" - CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OpenMP_${LANG}_FLAGS}" ${_linkDirFlags} ${_includeDirFlags} - COPY_FILE ${BIN_FILE} - OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT) - - if(${OpenMP_SPECTEST_${LANG}_${OPENMP_PLAIN_FLAG}}) - file( - STRINGS ${BIN_FILE} specstr - LIMIT_COUNT 1 - REGEX "INFO:OpenMP-date") - set(regex_spec_date ".*INFO:OpenMP-date\\[0*([^]]*)\\].*") - if("${specstr}" MATCHES "${regex_spec_date}") - set(${SPEC_DATE} - "${CMAKE_MATCH_1}" - PARENT_SCOPE) - endif() - else() - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Detecting ${LANG} OpenMP version failed with the following output:\n${OpenMP_TRY_COMPILE_OUTPUT}\n\n") - endif() -endfunction() - -macro(_OPENMP_SET_VERSION_BY_SPEC_DATE LANG) - set(OpenMP_SPEC_DATE_MAP - # Preview versions - "201611=5.0" # OpenMP 5.0 preview 1 - # Combined versions, 2.5 onwards - "201811=5.0" - "201511=4.5" - "201307=4.0" - "201107=3.1" - "200805=3.0" - "200505=2.5" - # C/C++ version 2.0 - "200203=2.0" - # Fortran version 2.0 - "200011=2.0" - # Fortran version 1.1 - "199911=1.1" - # C/C++ version 1.0 (there's no 1.1 for C/C++) - "199810=1.0" - # Fortran version 1.0 - "199710=1.0") - if(MSVC) - list(APPEND OpenMP_SPEC_DATE_MAP "2019=2.0") - endif() - - if(OpenMP_${LANG}_SPEC_DATE) - string(REGEX MATCHALL "${OpenMP_${LANG}_SPEC_DATE}=([0-9]+)\\.([0-9]+)" _version_match "${OpenMP_SPEC_DATE_MAP}") - else() - set(_version_match "") - endif() - if(NOT _version_match STREQUAL "") - set(OpenMP_${LANG}_VERSION_MAJOR ${CMAKE_MATCH_1}) - set(OpenMP_${LANG}_VERSION_MINOR ${CMAKE_MATCH_2}) - set(OpenMP_${LANG}_VERSION "${OpenMP_${LANG}_VERSION_MAJOR}.${OpenMP_${LANG}_VERSION_MINOR}") - else() - unset(OpenMP_${LANG}_VERSION_MAJOR) - unset(OpenMP_${LANG}_VERSION_MINOR) - unset(OpenMP_${LANG}_VERSION) - endif() - unset(_version_match) - unset(OpenMP_SPEC_DATE_MAP) -endmacro() - -foreach(LANG IN ITEMS C CXX) - if(CMAKE_${LANG}_COMPILER_LOADED) - if(NOT DEFINED OpenMP_${LANG}_FLAGS - OR "${OpenMP_${LANG}_FLAGS}" STREQUAL "NOTFOUND" - OR NOT DEFINED OpenMP_${LANG}_LIB_NAMES - OR "${OpenMP_${LANG}_LIB_NAMES}" STREQUAL "NOTFOUND") - _openmp_get_flags("${LANG}" "${LANG}" OpenMP_${LANG}_FLAGS_WORK OpenMP_${LANG}_LIB_NAMES_WORK) - set(OpenMP_${LANG}_FLAGS - "${OpenMP_${LANG}_FLAGS_WORK}" - CACHE STRING "${LANG} compiler flags for OpenMP parallelization" FORCE) - set(OpenMP_${LANG}_LIB_NAMES - "${OpenMP_${LANG}_LIB_NAMES_WORK}" - CACHE STRING "${LANG} compiler libraries for OpenMP parallelization" FORCE) - if(OpenMP_libomp_LIBRARY_DIR) - set(OpenMP_libomp_LIBRARY_DIR - "${OpenMP_libomp_LIBRARY_DIR}" - CACHE STRING "Directory to OpenMP library directory" FORCE) - mark_as_advanced(OpenMP_libomp_LIBRARY_DIR) - endif() - mark_as_advanced(OpenMP_${LANG}_FLAGS OpenMP_${LANG}_LIB_NAMES) - endif() - endif() -endforeach() - -if(CMAKE_Fortran_COMPILER_LOADED) - if(NOT DEFINED OpenMP_Fortran_FLAGS - OR "${OpenMP_Fortran_FLAGS}" STREQUAL "NOTFOUND" - OR NOT DEFINED OpenMP_Fortran_LIB_NAMES - OR "${OpenMP_Fortran_LIB_NAMES}" STREQUAL "NOTFOUND" - OR NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_MODULE) - set(OpenMP_Fortran_INCLUDE_LINE "use omp_lib\n implicit none") - _openmp_get_flags("Fortran" "FortranHeader" OpenMP_Fortran_FLAGS_WORK OpenMP_Fortran_LIB_NAMES_WORK) - if(OpenMP_Fortran_FLAGS_WORK) - set(OpenMP_Fortran_HAVE_OMPLIB_MODULE - TRUE - CACHE BOOL INTERNAL "") - endif() - - set(OpenMP_Fortran_FLAGS - "${OpenMP_Fortran_FLAGS_WORK}" - CACHE STRING "Fortran compiler flags for OpenMP parallelization") - set(OpenMP_Fortran_LIB_NAMES - "${OpenMP_Fortran_LIB_NAMES_WORK}" - CACHE STRING "Fortran compiler libraries for OpenMP parallelization") - mark_as_advanced(OpenMP_Fortran_FLAGS OpenMP_Fortran_LIB_NAMES) - endif() - - if(NOT DEFINED OpenMP_Fortran_FLAGS - OR "${OpenMP_Fortran_FLAGS}" STREQUAL "NOTFOUND" - OR NOT DEFINED OpenMP_Fortran_LIB_NAMES - OR "${OpenMP_Fortran_LIB_NAMES}" STREQUAL "NOTFOUND" - OR NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_HEADER) - set(OpenMP_Fortran_INCLUDE_LINE "implicit none\n include 'omp_lib.h'") - _openmp_get_flags("Fortran" "FortranModule" OpenMP_Fortran_FLAGS_WORK OpenMP_Fortran_LIB_NAMES_WORK) - if(OpenMP_Fortran_FLAGS_WORK) - set(OpenMP_Fortran_HAVE_OMPLIB_HEADER - TRUE - CACHE BOOL INTERNAL "") - endif() - - set(OpenMP_Fortran_FLAGS - "${OpenMP_Fortran_FLAGS_WORK}" - CACHE STRING "Fortran compiler flags for OpenMP parallelization") - - set(OpenMP_Fortran_LIB_NAMES - "${OpenMP_Fortran_LIB_NAMES}" - CACHE STRING "Fortran compiler libraries for OpenMP parallelization") - endif() - - if(OpenMP_Fortran_HAVE_OMPLIB_MODULE) - set(OpenMP_Fortran_INCLUDE_LINE "use omp_lib\n implicit none") - else() - set(OpenMP_Fortran_INCLUDE_LINE "implicit none\n include 'omp_lib.h'") - endif() -endif() - -if(NOT OpenMP_FIND_COMPONENTS) - set(OpenMP_FINDLIST C CXX Fortran) -else() - set(OpenMP_FINDLIST ${OpenMP_FIND_COMPONENTS}) -endif() - -unset(_OpenMP_MIN_VERSION) - -include(FindPackageHandleStandardArgs) - -foreach(LANG IN LISTS OpenMP_FINDLIST) - if(CMAKE_${LANG}_COMPILER_LOADED) - if(NOT OpenMP_${LANG}_SPEC_DATE AND OpenMP_${LANG}_FLAGS) - _openmp_get_spec_date("${LANG}" OpenMP_${LANG}_SPEC_DATE_INTERNAL) - set(OpenMP_${LANG}_SPEC_DATE - "${OpenMP_${LANG}_SPEC_DATE_INTERNAL}" - CACHE INTERNAL "${LANG} compiler's OpenMP specification date") - endif() - - _openmp_set_version_by_spec_date("${LANG}") - - set(OpenMP_${LANG}_FIND_QUIETLY ${OpenMP_FIND_QUIETLY}) - set(OpenMP_${LANG}_FIND_REQUIRED ${OpenMP_FIND_REQUIRED}) - set(OpenMP_${LANG}_FIND_VERSION ${OpenMP_FIND_VERSION}) - set(OpenMP_${LANG}_FIND_VERSION_EXACT ${OpenMP_FIND_VERSION_EXACT}) - - set(_OPENMP_${LANG}_REQUIRED_VARS OpenMP_${LANG}_FLAGS) - if("${OpenMP_${LANG}_LIB_NAMES}" STREQUAL "NOTFOUND") - set(_OPENMP_${LANG}_REQUIRED_LIB_VARS OpenMP_${LANG}_LIB_NAMES) - else() - foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_LIB_NAMES) - list(APPEND _OPENMP_${LANG}_REQUIRED_LIB_VARS OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY) - endforeach() - endif() - - set(_names_mismatch) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17) - set(_names_mismatch NAME_MISMATCHED) - endif() - - find_package_handle_standard_args( - OpenMP_${LANG} ${_names_mismatch} - REQUIRED_VARS OpenMP_${LANG}_FLAGS ${_OPENMP_${LANG}_REQUIRED_LIB_VARS} - VERSION_VAR OpenMP_${LANG}_VERSION) - - if(OpenMP_${LANG}_FOUND) - if(DEFINED OpenMP_${LANG}_VERSION) - if(NOT _OpenMP_MIN_VERSION OR _OpenMP_MIN_VERSION VERSION_GREATER OpenMP_${LANG}_VERSION) - set(_OpenMP_MIN_VERSION OpenMP_${LANG}_VERSION) - endif() - endif() - set(OpenMP_${LANG}_LIBRARIES "") - foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_LIB_NAMES) - list(APPEND OpenMP_${LANG}_LIBRARIES "${OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY}") - endforeach() - if(OpenMP_${LANG}_INCLUDE_DIR) - set(OpenMP_${LANG}_INCLUDE_DIRS ${OpenMP_${LANG}_INCLUDE_DIR}) - else() - set(OpenMP_${LANG}_INCLUDE_DIRS "") - endif() - - if(NOT TARGET OpenMP::OpenMP_${LANG}) - add_library(OpenMP::OpenMP_${LANG} INTERFACE IMPORTED) - endif() - if(OpenMP_${LANG}_FLAGS) - separate_arguments(_OpenMP_${LANG}_OPTIONS NATIVE_COMMAND "${OpenMP_${LANG}_FLAGS}") - set_property(TARGET OpenMP::OpenMP_${LANG} PROPERTY INTERFACE_COMPILE_OPTIONS - "$<$:${_OpenMP_${LANG}_OPTIONS}>") - unset(_OpenMP_${LANG}_OPTIONS) - endif() - if(OpenMP_${LANG}_INCLUDE_DIRS) - set_property(TARGET OpenMP::OpenMP_${LANG} PROPERTY INTERFACE_INCLUDE_DIRECTORIES - "$") - endif() - if(OpenMP_libomp_LIBRARY_DIR) - # NB: As long as this file is for MacOS related workaround, we should be ok with -LXXX ... - set_property(TARGET OpenMP::OpenMP_${LANG} PROPERTY INTERFACE_LINK_OPTIONS "-L${OpenMP_libomp_LIBRARY_DIR}") - endif() - if(OpenMP_${LANG}_LIBRARIES) - set_property(TARGET OpenMP::OpenMP_${LANG} PROPERTY INTERFACE_LINK_LIBRARIES "${OpenMP_${LANG}_LIBRARIES}") - endif() - endif() - endif() -endforeach() - -unset(_OpenMP_REQ_VARS) -foreach(LANG IN ITEMS C CXX Fortran) - if((NOT OpenMP_FIND_COMPONENTS AND CMAKE_${LANG}_COMPILER_LOADED) OR LANG IN_LIST OpenMP_FIND_COMPONENTS) - list(APPEND _OpenMP_REQ_VARS "OpenMP_${LANG}_FOUND") - endif() -endforeach() - -find_package_handle_standard_args( - OpenMP - REQUIRED_VARS ${_OpenMP_REQ_VARS} - VERSION_VAR ${_OpenMP_MIN_VERSION} - HANDLE_COMPONENTS) - -set(OPENMP_FOUND ${OpenMP_FOUND}) - -if(CMAKE_Fortran_COMPILER_LOADED AND OpenMP_Fortran_FOUND) - if(NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_MODULE) - set(OpenMP_Fortran_HAVE_OMPLIB_MODULE - FALSE - CACHE BOOL INTERNAL "") - endif() - if(NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_HEADER) - set(OpenMP_Fortran_HAVE_OMPLIB_HEADER - FALSE - CACHE BOOL INTERNAL "") - endif() -endif() - -if(NOT - (CMAKE_C_COMPILER_LOADED - OR CMAKE_CXX_COMPILER_LOADED - OR CMAKE_Fortran_COMPILER_LOADED)) - message(SEND_ERROR "FindOpenMP requires the C, CXX or Fortran languages to be enabled") -endif() - -unset(OpenMP_C_CXX_TEST_SOURCE) -unset(OpenMP_Fortran_TEST_SOURCE) -unset(OpenMP_C_CXX_CHECK_VERSION_SOURCE) -unset(OpenMP_Fortran_CHECK_VERSION_SOURCE) -unset(OpenMP_Fortran_INCLUDE_LINE) - -cmake_policy(POP) diff --git a/cmake/binscope.cmake b/cmake/binscope.cmake deleted file mode 100644 index fa9d9e7dc..000000000 --- a/cmake/binscope.cmake +++ /dev/null @@ -1,45 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -# ~~~ -# Generate a custom ` binscope` target to call binscope on a list of targets. -# -# gen_binscope_target( [target ...]) -# ~~~ -function(gen_binscope_target) - if(BINSCOPE_OUTPUT) - set(_binscope_output "${BINSCOPE_OUTPUT}") - else() - set(_binscope_output "${PROJECT_BINARY_DIR}/binscope_output.xls") - endif() - - set(_binscope_args) - list(APPEND _binscope_args "-a") - list(APPEND _binscope_args "-o") - list(APPEND _binscope_args "${_binscope_output}") - - foreach(tgt ${ARGN}) - list(APPEND _binscope_args "-f") - list(APPEND _binscope_args "$") - endforeach() - - add_custom_target( - binscope - COMMAND ${binscope_exec} ${_binscope_args} - COMMENT "Running binscope") -endfunction() diff --git a/cmake/compiler_flags.cmake b/cmake/compiler_flags.cmake deleted file mode 100644 index feb603d93..000000000 --- a/cmake/compiler_flags.cmake +++ /dev/null @@ -1,109 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -# C++ standard flags -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED OFF) -set(CMAKE_CXX_EXTENSIONS OFF) - -# Always generate position independent code -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# set(CMAKE_CXX_VISIBILITY_PRESET hidden) - -# RPATH settings... Funadamentally, we do not want to use RPATH but RUNPATH. In order to achieve this, we use a -# combination of these CMake options, some target properties (namely INSTALL_RPATH; see *_set_rpath macros in -# macros.cmake) and some linker flags (see linker_flags.cmake) -# -# All of this should achieve the desired effect on all platforms and compilers - -set(CMAKE_BUILD_SKIP_RPATH TRUE) -set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) - -# CMake usually does not add /usr/local/include to any compiler commands. This can lead to some issues on Mac OS when -# using the -isysroot option so we allow for explicit -I/usr/local/include on the command line. -if(APPLE) - list(REMOVE_ITEM CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES /usr/local/include) - list(REMOVE_ITEM CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES /usr/local/include) - list(REMOVE_ITEM CMAKE_CUDA_IMPLICIT_INCLUDE_DIRECTORIES /usr/local/include) - list(REMOVE_ITEM CMAKE_C_IMPLICIT_LINK_DIRECTORIES /usr/local/lib) - list(REMOVE_ITEM CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES /usr/local/lib) -endif() - -# ------------------------------------------------------------------------------ - -test_compile_option( - _compile_flags_release - LANGS CXX DPCXX - FLAGS "-ffast-math /fp:fast -fast" "-O3 /Ox" - AUTO_ADD_CO - GENEX "$,$>,$>") - -# -------------------------------------- - -test_compile_option( - _dpcpp_flags - LANGS DPCXX - FLAGS "-fsycl" - AUTO_ADD_CO) - -# -------------------------------------- - -if(ENABLE_PROFILING) - test_compile_option( - _profiling_flags - LANGS CXX DPCXX - FLAGS "-pg -prof-gen /Qprof-gen" "-fprofile-instr-generate" - AUTO_ADD_CO) -endif() - -# -------------------------------------- - -if(ENABLE_STACK_PROTECTION) - test_compile_option( - _stack_protection - LANGS CXX DPCXX - FLAGS "-fstack-protector-all" - AUTO_ADD_CO) -endif() - -# ------------------------------------------------------------------------------ - -if(NOT VERSION_INFO) - execute_process( - COMMAND ${Python_EXECUTABLE} setup.py --version - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE _version_info - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) - set(VERSION_INFO "\"${_version_info}\"") -endif() - -# -------------------------------------- - -add_compile_definitions( - "$<$:ENABLE_OPENMP>" "$<$:VERSION_INFO=${VERSION_INFO}>" - "$<$,$>:_FORTIFY_SOURCE=2>") - -# ============================================================================== -# Platform specific flags - -if(WIN32) - add_compile_definitions(_USE_MATH_DEFINES _CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) -endif() - -# ============================================================================== diff --git a/cmake/linker_flags.cmake b/cmake/linker_flags.cmake deleted file mode 100644 index 6f1fab301..000000000 --- a/cmake/linker_flags.cmake +++ /dev/null @@ -1,234 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. -# -# ============================================================================== - -# lint_cmake: -whitespace/indent - -# NB: no -Wl, here, CMake automatically adds the correct prefix for the linker -if(LINKER_STRIP_ALL) - test_link_option( - _linker_flags - LANGS CXX DPCXX CUDA NVCXX - FLAGS "--strip-all -s" - AUTO_ADD_LO - GENEX "$,$>,$>") -endif() - -# test_link_option( -# _linker_flags -# LANGS CXX DPCXX CUDA NVCXX -# FLAGS "-z,now" -# AUTO_ADD_LO) - -# ------------------------------------------------------------------------------ - -if(LINKER_NOEXECSTACK) - test_link_option( - _link_no_execstack - LANGS CXX DPCXX CUDA NVCXX - FLAGS "-z,noexecstack" - AUTO_ADD_LO) -endif() - -# ------------------------------------------------------------------------------ - -if(LINKER_RELRO) - test_link_option( - _link_relro - LANGS CXX DPCXX CUDA NVCXX - FLAGS "-z,relro" - AUTO_ADD_LO) -endif() - -# ------------------------------------------------------------------------------ - -if(ENABLE_RUNPATH) - if(LINKER_DTAGS) - test_link_option( - _linker_dtags - LANGS CXX DPCXX CUDA NVCXX - FLAGS "--enable-new-dtags" - AUTO_ADD_LO) - endif() -else() - if(LINKER_DTAGS) - test_link_option( - _linker_dtags - LANGS CXX DPCXX CUDA NVCXX - FLAGS "--disable-new-dtags" - AUTO_ADD_LO) - endif() - - if(CUDA_STATIC) - test_link_option( - _nvhpc_static_flags - LANGS NVCXX - FLAGS "-static-nvidia" "-Mnorpath" - AUTO_ADD_LO VERBATIM) - endif() -endif() - -if(UNIX AND NOT APPLE) - if(NOT DEFINED _cmake_rpath_check) - set(_cmake_rpath_check FALSE) - find_program(_readelf readelf) - if(_readelf) - set(_cmake_rpath_check TRUE) - else() - message(STATUS "Readelf program not found -> skipping RPATH/RUNPATH check") - endif() - # cmake-lint: disable=C0103 - set(_cmake_rpath_check - ${_cmake_rpath_check} - CACHE BOOL "Do an extended CMake test to make sure no RPATH are set?") - - mark_as_advanced(_readelf _cmake_rpath_check) - endif() -endif() - -# ============================================================================== - -if(_cmake_rpath_check) - foreach(_lang CXX CUDA NVCXX DPCXX) - is_language_enabled(${_lang} _enabled) - if(_enabled) - message(CHECK_START "Performing extended CMake RPATH test for ${_lang}") - list(APPEND CMAKE_MESSAGE_INDENT " ") - set(LANG ${_lang}) - set(LANGS ${_lang}) - - if(_lang STREQUAL CUDA) - set(LANGS "${LANGS} CXX") - elseif(_lang STREQUAL NVCXX) - set(LANGS "${LANGS} CXX") - get_property(_flags GLOBAL PROPERTY _nvcxx_try_compile_extra_flags) - if(_flags) - string(APPEND CMAKE_REQUIRED_FLAGS " ${_flags}") - list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${_flags}) - endif() - set(CMAKE_EXTRA_CONTENT "set(CMAKE_NVCXX_FLAGS_INIT \"${CMAKE_NVCXX_FLAGS_INIT} -v\")\n -set(CMAKE_NVCXX_LDFLAGS_INIT \"${CMAKE_NVCXX_LDFLAGS_INIT} -v\")") - endif() - - file(REMOVE ${CMAKE_SOURCE_DIR}/tests/cmake-ldtest/CMakeLists.txt) - configure_file(${CMAKE_SOURCE_DIR}/tests/cmake-ldtest/CMakeLists.txt.in - ${CMAKE_SOURCE_DIR}/tests/cmake-ldtest/CMakeLists.txt @ONLY) - - # ------------------------------------ - - message(CHECK_START "Compiling test library (${_lang})") - set(_binary_dir ${CMAKE_BINARY_DIR}/cmake-ldtest-${_lang}) - try_compile( - _create_shared_lib_${lang} ${_binary_dir} - ${CMAKE_SOURCE_DIR}/tests/cmake-ldtest cmake-ldtest - CMAKE_FLAGS -DCMAKE_VERBOSE_MAKEFILE=ON -DLINKER_FLAGS=${_linker_dtags_CXX} - OUTPUT_VARIABLE _compile_output) - if(_create_shared_lib_${lang}) - message(CHECK_PASS "succeeded") - else() - message(CHECK_FAIL "failed") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Failed to compile CMake RPATH extended ${_lang} test project.\nOutput of build:\n${_compile_output}\n") - endif() - - # ------------------------------------ - - if(_create_shared_lib_${lang}) - if(ENABLE_RUNPATH) - set(_name "RPATH") - else() - set(_name "RUNPATH") - endif() - - message(CHECK_START "Looking for absence of ${_name} (${_lang})") - - find_library( - _shared_lib_${_lang} - NAMES shared_lib_${_lang} libshared_lib_${_lang} - PATHS ${_binary_dir} REQUIRED - NO_DEFAULT_PATH) - mark_as_advanced(_shared_lib_${_lang}) - - execute_process( - COMMAND ${_readelf} -Wd ${_shared_lib_${_lang}} - OUTPUT_VARIABLE _dyn_symbols - OUTPUT_STRIP_TRAILING_WHITESPACE) - - # Local helper macro to add RPATH to the log file - macro(_rpath_add_to_log name success msg) - if(${success}) - set(_file "CMakeOutput.log") - set(_state_msg "succeeded") - else() - set(_file "CMakeError.log") - set(_state_msg "failed") - endif() - file( - APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_file} - "\n\nLooking for absence of ${name} in ${_shared_lib_${_lang}} ${_state_msg}.\n" - "Output of build for ${_shared_lib_${_lang}}:\n${_compile_output}\nOutput of readelf -Wd:" - "\n${_dyn_symbols}\n\n${msg}\n\n") - endmacro() - - set(_test_result FALSE) - if(ENABLE_RUNPATH AND ${_dyn_symbols} MATCHES ".*\\(RPATH\\)[ ]+([^\n\r\t]*)") - # Most not have RPATH but found one -> not good - _rpath_add_to_log(${_name} FALSE "RPATH detected: ${CMAKE_MATCH_1}") - elseif(ENABLE_RUNPATH AND ${_dyn_symbols} MATCHES ".*\\(RUNPATH\\)[ ]+([^\n\r\t]*)") - set(_test_result TRUE) - _rpath_add_to_log(${_name} TRUE "Found RUNPATH: ${CMAKE_MATCH_1} and no RPATH") - # -------------------------------- - elseif(NOT ENABLE_RUNPATH AND ${_dyn_symbols} MATCHES ".*\\(RUNPATH\\)[ ]+([^\n\r\t]*)") - # Most not have RUNPATH but found one -> not good - _rpath_add_to_log(${_name} FALSE "RUNPATH detected: ${CMAKE_MATCH_1}") - elseif(NOT ENABLE_RUNPATH AND ${_dyn_symbols} MATCHES ".*\\(RPATH\\)[ ]+([^\n\r\t]*)") - set(_test_result TRUE) - _rpath_add_to_log(${_name} TRUE "Found RPATH: ${CMAKE_MATCH_1} and no RUNPATH") - # -------------------------------- - else() - _rpath_add_to_log(${_name} FALSE "No RPATH or RUNPATH found.") - message(CHECK_FAIL "failed") - message(FATAL_ERROR "No RPATH or RUNPATH found in ${_shared_lib}") - endif() - - if(_test_result) - message(CHECK_PASS "succeeded") - else() - message(CHECK_FAIL "failed") - endif() - endif() - - # ------------------------------------ - - list(POP_BACK CMAKE_MESSAGE_INDENT) - if(_test_result) - message(CHECK_PASS "succeeded") - # cmake-lint: disable=C0103 - - # Only perform the RPATH/RUNPATH check once - set(_cmake_rpath_check - FALSE - CACHE INTERNAL "") - else() - message(CHECK_FAIL "failed") - message(FATAL_ERROR "Failed extended RPATH test: cannot continue!") - endif() - endif() - endforeach() -endif() - -# ============================================================================== diff --git a/cmake/macros.cmake b/cmake/macros.cmake deleted file mode 100644 index ec3d1bec4..000000000 --- a/cmake/macros.cmake +++ /dev/null @@ -1,521 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -# lint_cmake: -whitespace/indent - -include(CheckCompilerFlag OPTIONAL RESULT_VARIABLE _check_compiler_flag) -if(NOT _check_compiler_flag) - include(Internal/CMakeCheckCompilerFlag) -endif() - -# Check if a language has been enabled without attempting to enable it -# -# is_language_enabled( ) -# -# If the language has already been enabled, is set to TRUE. Otherwise it is set to FALSE. -function(is_language_enabled _lang _var) - get_property(_supported_languages GLOBAL PROPERTY ENABLED_LANGUAGES) - if(NOT _lang IN_LIST _supported_languages) - set(${_var} - FALSE - PARENT_SCOPE) - else() - set(${_var} - TRUE - PARENT_SCOPE) - endif() -endfunction() - -# ============================================================================== - -# ~~~ -# Convenience function to test for the existence of some compiler flags for a a particular language -# -# check_compiler_flag( [...]) -# -# Check whether a compiler option is valid for the compiler. For each set of compiler options provided in the -# lists , it will test whether one of the element can be used by the corresponding compiler. If a flag is valid, -# it will be added to the GLOBAL property named _ as well as to a variable with the same name. If the -# property already exists, any valid flag is appended to the current value. -# -# Each call to this function also sets the _added_count variable to the number of flags added automatically (if any). -# ~~~ -function(check_compiler_flags lang var_prefix) - # cmake-lint: disable=C0103,E1120 - set(_${lang}_opts) - - foreach(_flag_list ${ARGN}) - separate_arguments(_flag_list) - - foreach(_flag ${_flag_list}) - # Drop the first character (most likely either '-' or '/') - string(SUBSTRING ${_flag} 1 -1 _flag_name) - string(REGEX REPLACE "^-+" "" _flag_name ${_flag_name}) - string(REGEX REPLACE "[-:/,=]" "_" _flag_name ${_flag_name}) - - cmake_check_compiler_flag(${lang} ${_flag} ${lang}_compiler_has_${_flag_name}) - if(${lang}_compiler_has_${_flag_name}) - list(APPEND _${lang}_opts ${_flag}) - break() - endif() - endforeach() - endforeach() - - # Is there a property that already corresponds to this? - get_property(_opts GLOBAL PROPERTY ${var_prefix}_${lang}) - if(_opts) - list(APPEND _opts ${_${lang}_opts}) - else() - define_property( - GLOBAL - PROPERTY ${var_prefix}_${lang} - BRIEF_DOCS "Compiler flags for ${var_prefix}" - FULL_DOCS "Compiler flags for ${var_prefix}") - set(_opts ${_${lang}_opts}) - endif() - - # Set GLOBAL property so that other parts of the code can have access to it - set_property(GLOBAL PROPERTY ${var_prefix}_${lang} ${_opts}) - - list(LENGTH _${lang}_opts _added_count) - set(_added_count - ${_added_count} - PARENT_SCOPE) - - # Also set a variable for convenience - set(${var_prefix}_${lang} - ${_opts} - PARENT_SCOPE) -endfunction() - -# ~~~ -# Convenience function to test for the existence of some compiler flags for a set of languages. -# -# test_compile_option( -# LANGS [...] -# FLAGS [...] -# [AUTO_ADD_CO] -# [GENEX ]) -# -# Check that a compiler option can be applied to each of the specified languages . For each set of compiler -# options provided in the lists , it will test whether one of the element can be used by the corresponding -# compiler. If a flag is valid, it will be added to the GLOBAL property named _ as well as to a variable -# with the same name. -# If AUTO_ADD_CO is specified, the compiler option will be automatically added globally using -# add_compile_option(...). By default, the generator expression used in that function call restricts the compile option -# to the current language (ie. $<$:${_flag}>). This can be changed by using the -# argument (which defaults to "$"). -# -# NB: This function calls check_compiler_flags() internally. -# -# ~~~ -function(test_compile_option prefix) - cmake_parse_arguments(PARSE_ARGV 1 TEST_CO "AUTO_ADD_CO" "GENEX" "LANGS;FLAGS") - - if(NOT TEST_CO_LANGS) - message(FATAL_ERROR "Missing LANGS argument") - endif() - if(NOT TEST_CO_FLAGS) - message(FATAL_ERROR "Missing FLAGS argument") - endif() - - if(NOT TEST_CO_GENEX) - set(TEST_CO_GENEX "$") - endif() - - # cmake-lint: disable=C0103 - foreach(lang ${TEST_CO_LANGS}) - is_language_enabled(${lang} _enabled) - if(_enabled) - check_compiler_flags(${lang} ${prefix} ${TEST_CO_FLAGS}) - - set(${prefix}_${lang} - ${${prefix}_${lang}} - PARENT_SCOPE) - - if(TEST_CO_AUTO_ADD_CO) - string(CONFIGURE "${TEST_CO_GENEX}" _genex @ONLY) - list(LENGTH ${prefix}_${lang} _L) - math(EXPR _start_idx "${_L} - ${_added_count}") - list(SUBLIST ${prefix}_${lang} ${_start_idx} -1 _added_flags) - foreach(_flag ${_added_flags}) - add_compile_options("$<${_genex}:${_flag}>") - endforeach() - endif() - else() - set(${prefix}_${lang} PARENT_SCOPE) - endif() - endforeach() -endfunction() - -# ============================================================================== - -# ~~~ -# Convenience function to test for the existence of some compiler flags for a a particular language -# -# check_link_flag( [VERBATIM] [...]) -# -# Check whether a linker option is valid for the linker. For each set of linker options provided in the lists -# , it will test whether one of the element can be used by the corresponding compiler. If a flag is valid, it -# will be added to the GLOBAL property named _ as well as to a variable with the same name. If the -# property already exists, any valid flag is appended to the current value. -# -# If VERBATIM is passed as argument, the flag is passed onto the linker without prepending the 'LINKER:' prefix. -# -# Each call to this function also sets the _added_count variable to the number of flags added automatically (if any). -# ~~~ -function(check_link_flags lang var_prefix) - # cmake-lint: disable=R0915,C0103,E1120 - - cmake_parse_arguments(PARSE_ARGV 2 CHECK_LF "VERBATIM" "" "") - set(_${lang}_link_opts) - # This is a CMake 3.18 addition - include(CheckLinkerFlag OPTIONAL RESULT_VARIABLE _check_linker_flags) - - set(_wrapper_flag ${CMAKE_${lang}_LINKER_WRAPPER_FLAG}) - list(GET _wrapper_flag -1 _last) - set(_separate_options FALSE) - if(_last STREQUAL " ") - set(_separate_options TRUE) - list(REMOVE_AT _wrapper_flag -1) - endif() - string(REPLACE ";" " " _wrapper_flag ${_wrapper_flag}) - - foreach(_flag_list ${CHECK_LF_UNPARSED_ARGUMENTS}) - separate_arguments(_flag_list) - - foreach(_flag ${_flag_list}) - # Drop the first character (most likely either '-' or '/') - string(SUBSTRING ${_flag} 1 -1 _flag_name) - string(REGEX REPLACE "^-+" "" _flag_name ${_flag_name}) - string(REGEX REPLACE "[-:/,=]" "_" _flag_name ${_flag_name}) - if(CHECK_LF_VERBATIM) - set(_prefix) - else() - set(_prefix "LINKER:") - endif() - - if(_check_linker_flags) - check_linker_flag(${lang} "${_prefix}${_flag}" ${lang}_linker_has_${_flag_name}) - else() - if(NOT CHECK_LF_VERBATIM) - if(_separate_options) - string(REPLACE "," ";" _flags ${_flag}) - set(_expanded_flag) - foreach(subflag ${_flags}) - set(_expanded_flag "${_expanded_flag} ${_wrapper_flag} ${subflag}") - endforeach() - else() - set(_expanded_flag "${_wrapper_flag}${_flag}") - endif() - else() - set(_expanded_flag "${_flag}") - endif() - - set(CMAKE_REQUIRED_LINK_OPTIONS ${_expanded_flag}) - check_compiler_flag(${lang} "" ${lang}_linker_has_${_flag_name}) - endif() - - if(${lang}_linker_has_${_flag_name}) - list(APPEND _${lang}_link_opts ${_flag}) - break() - endif() - endforeach() - endforeach() - - # Is there a property that already corresponds to this? - get_property(_opts GLOBAL PROPERTY ${var_prefix}_${lang}) - if(_opts) - list(APPEND _opts ${_${lang}_link_opts}) - else() - define_property( - GLOBAL - PROPERTY ${var_prefix}_${lang} - BRIEF_DOCS "Linker flags ${var_prefix}" - FULL_DOCS "Linker flags ${var_prefix}") - set(_opts ${_${lang}_link_opts}) - endif() - - # Set GLOBAL property so that other parts of the code can have access to it - set_property(GLOBAL PROPERTY ${var_prefix}_${lang} ${_opts}) - - list(LENGTH _${lang}_link_opts _added_count) - set(_added_count - ${_added_count} - PARENT_SCOPE) - - # Also set a variable for convenience - set(${var_prefix}_${lang} - ${_opts} - PARENT_SCOPE) -endfunction() - -# ~~~ -# Convenience function to test for the existence of some linker flags for a set of languages. -# -# test_link_option( -# LANGS [...] -# FLAGS [...] -# [AUTO_ADD_LO] -# [VERBATIM] -# [GENEX ]) -# -# Check that a linker option can be applied to each of the specified languages . For each set of linker -# options provided in the lists , it will test whether one of the element can be used by the corresponding -# linker. If a flag is valid, it will be added to the GLOBAL property named _ as well as to a variable -# with the same name. -# If VERBATIM is passed as argument, the flag is passed onto the linker without prepending the 'LINKER:' prefix. -# If AUTO_ADD_LO is specified, the linker option will be automatically added globally using -# add_compile_option(...). By default, the generator expression used in that function call restricts the compile option -# to the current language (ie. $<$:LINKER:${_flag}>). This can be changed by using the -# argument (which defaults to "$"). -# -# NB: This function calls check_link_flags() internally. -# ~~~ -function(test_link_option prefix) - cmake_parse_arguments(PARSE_ARGV 1 TEST_LO "AUTO_ADD_LO;VERBATIM" "GENEX" "LANGS;FLAGS") - - if(NOT TEST_LO_LANGS) - message(FATAL_ERROR "Missing LANGS argument") - endif() - if(NOT TEST_LO_FLAGS) - message(FATAL_ERROR "Missing FLAGS argument") - endif() - - if(NOT TEST_LO_GENEX) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18) - set(TEST_LO_GENEX "$") - else() - set(TEST_LO_GENEX "1") - endif() - endif() - - # cmake-lint: disable=C0103 - foreach(lang ${TEST_LO_LANGS}) - is_language_enabled(${lang} _enabled) - if(_enabled) - set(_args ${TEST_LO_FLAGS}) - if(TEST_LO_VERBATIM) - set(_args "VERBATIM;${_args}") - endif() - check_link_flags(${lang} ${prefix} ${_args}) - - set(${prefix}_${lang} - ${${prefix}_${lang}} - PARENT_SCOPE) - - if(TEST_LO_AUTO_ADD_LO) - string(CONFIGURE "${TEST_LO_GENEX}" _genex @ONLY) - list(LENGTH ${prefix}_${lang} _L) - math(EXPR _start_idx "${_L} - ${_added_count}") - list(SUBLIST ${prefix}_${lang} ${_start_idx} -1 _added_flags) - foreach(_flag ${_added_flags}) - if(TEST_LO_VERBATIM) - add_link_options("$<${_genex}:${_flag}>") - else() - add_link_options("$<${_genex}:LINKER:${_flag}>") - endif() - endforeach() - endif() - else() - set(${prefix}_${lang} PARENT_SCOPE) - endif() - endforeach() -endfunction() - -# ============================================================================== - -# ~~~ -# Append a value to a property (creating the latter if necessary) -# -# append_to_property( -# ] | -# TARGET | -# SOURCE | -# [DIRECTORY | TARGET_DIRECTORY ] | -# INSTALL | -# TEST | -# CACHE | -# VARIABLE > -# ) -# ~~~ -macro(append_to_property name scope value) - get_property(_prop ${scope} PROPERTY ${name}) - if(_prop) - list(APPEND _prop ${value}) - else() - define_property( - ${scope} - PROPERTY ${name} - BRIEF_DOCS "${scope} property for ${name}" - FULL_DOCS "${scope} property for ${name}") - set(_prop ${value}) - endif() - - set_property(${scope} PROPERTY ${name} ${_prop}) -endmacro() - -# ============================================================================== - -# Automatically set the output directory for a particular target with a potential hint -# -# set_output_directory_auto( ) -# -# Automatically set the output directory for . must be an existing path in the current CMake project -# directory. It is only used if the CMake variable IN_PLACE_BUILD is set to a truthful value. Otherwise, the macro will -# look at ${target}_OUTPUT_DIR CMake variable (if it exists) to set the output directory (it will create the directory -# if it does not already exist on the filesystem). -# -macro(set_output_directory_auto target hint) - string(TOUPPER ${target} _TARGET) - - if(IN_PLACE_BUILD) - # Automatically calculate the output directory - set(${_TARGET}_OUTPUT_DIR ${CMAKE_SOURCE_DIR}/${hint}) - endif() - - # Normalize variable name - if(${target}_OUTPUT_DIR) - set(${_TARGET}_OUTPUT_DIR ${${target}_OUTPUT_DIR}) - endif() - - # Create output directory if it does not exist already - if(${_TARGET}_OUTPUT_DIR AND NOT EXISTS ${${_TARGET}_OUTPUT_DIR}) - file(MAKE_DIRECTORY ${${_TARGET}_OUTPUT_DIR}) - endif() - - # Properly set output directory for a target so that during an installation using either 'pip install' or 'python3 - # setup.py install' the libraries get built in the proper directory - if(${_TARGET}_OUTPUT_DIR) - set_target_properties( - ${target} - PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${${_TARGET}_OUTPUT_DIR} - LIBRARY_OUTPUT_DIRECTORY_DEBUG ${${_TARGET}_OUTPUT_DIR} - LIBRARY_OUTPUT_DIRECTORY_RELEASE ${${_TARGET}_OUTPUT_DIR} - LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${${_TARGET}_OUTPUT_DIR} - LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL ${${_TARGET}_OUTPUT_DIR}) - elseif(IS_PYTHON_BUILD) - message( - WARNING "IS_PYTHON_BUILD=ON but ${_TARGET}_OUTPUT_DIR " - "was not defined! The shared library for target ${target} " - "will probably not be copied to the correct directory. " - "Did you forget to add a CMakeExtension in setup.py?") - elseif(CMAKE_LIBRARY_OUTPUT_DIRECTORY) - set_target_properties( - ${target} - PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} - LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} - LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} - LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} - LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) - endif() -endmacro() - -# ============================================================================== - -# Set RPATH of target only if building for Python (ie. IS_PYTHON_BUILD=ON) or if building in-place (IN_PLACE_BUILD=ON) -macro(python_install_set_rpath target path) - if((IS_PYTHON_BUILD OR IN_PLACE_BUILD) AND LINKER_RPATH) - if(APPLE) - set_target_properties(${target} PROPERTIES INSTALL_RPATH "@loader_path/${path}") - elseif(UNIX) - set_target_properties(${target} PROPERTIES INSTALL_RPATH "$ORIGIN/${path}") - endif() - endif() -endmacro() - -# ============================================================================== - -include(FindPackageHandleStandardArgs) -# Find a Python module in the current (potential virtual) environment -# -# find_python_module( [REQUIRED|EXACT|QUIET] [VERSION ]) -# -# Usage is similar to the builtin find_package(...) -function(find_python_module module) - # cmake-lint: disable=C0103 - cmake_parse_arguments(PARSE_ARGV 1 PYMOD "REQUIRED;EXACT;QUIET" "VERSION" "") - - string(REPLACE "-" "_" module_name ${module}) - string(TOUPPER ${module_name} MODULE) - if(NOT PYMOD_${MODULE}) - if(PYMOD_REQUIRED) - set(PYMOD_${module}_FIND_REQUIRED TRUE) - set(PYMOD_${MODULE}_FIND_REQUIRED TRUE) - endif() - if(PYMOD_QUIET) - set(PYMOD_${module}_FIND_QUIETLY TRUE) - set(PYMOD_${MODULE}_FIND_QUIETLY TRUE) - endif() - if(PYMOD_EXACT) - set(PYMOD_${module}_FIND_VERSION_EXACT TRUE) - set(PYMOD_${MODULE}_FIND_VERSION_EXACT TRUE) - endif() - if(PYMOD_VERSION) - set(PYMOD_${module}_FIND_VERSION ${PYMOD_VERSION}) - set(PYMOD_${MODULE}_FIND_VERSION ${PYMOD_VERSION}) - endif() - - execute_process( - COMMAND "${Python_EXECUTABLE}" "-c" "import os, ${module_name}; print(os.path.dirname(${module_name}.__file__))" - RESULT_VARIABLE _${MODULE}_status - OUTPUT_VARIABLE _${MODULE}_location - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - - if(NOT _${MODULE}_status) - set(PYMOD_${MODULE}_PATH - ${_${MODULE}_location} - CACHE STRING "Location of Python module ${module}") - - if(PYMOD_VERSION) - execute_process( - COMMAND "${Python_EXECUTABLE}" "-c" "import ${module_name}; print(${module_name}.__version__)" - RESULT_VARIABLE _${MODULE}_status - OUTPUT_VARIABLE _${MODULE}_version - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - - if(NOT _${MODULE}_status) - set(PYMOD_${MODULE}_VERSION - ${_${MODULE}_version} - CACHE STRING "Version of Python module ${module}") - set(PYMOD_${module}_VERSION - ${PYMOD_${MODULE}_VERSION} - CACHE STRING "Version of Python module ${module}") - endif() - endif() - endif() - endif() - - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19 AND CMAKE_VERSION VERSION_LESS 3.20) - set(CMAKE_FIND_PACKAGE_NAME PYMOD_${module}) - endif() - - find_package_handle_standard_args( - PYMOD_${module_name} - REQUIRED_VARS PYMOD_${MODULE}_PATH - VERSION_VAR PYMOD_${MODULE}_VERSION) - - set(PYMOD_${MODULE}_FOUND - ${PYMOD_${MODULE}_FOUND} - CACHE INTERNAL "") - - mark_as_advanced(PYMOD_${MODULE}_FOUND PYMOD_${MODULE}_PATH PYMOD_${MODULE}_VERSION) -endfunction() - -# ============================================================================== diff --git a/cmake/macros_more.cmake b/cmake/macros_more.cmake deleted file mode 100644 index b75445a93..000000000 --- a/cmake/macros_more.cmake +++ /dev/null @@ -1,57 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -# Add a Python library (overload of the original python_add_library()) -# -# python_add_library() -# -# Override the original python_add_library() to keep track of all python libraries and properly set some target -# properties depending on the current CMake version. -macro(python_add_library target) - set(_args ${ARGN}) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17) - # Position 0 is the library type - list(GET _args 0 _lib_type) - if(_lib_type STREQUAL "MODULE") - list(INSERT _args 1 WITH_SOABI) - endif() - endif() - - _python_add_library(${target} ${_args}) - append_to_property(_doc_targets GLOBAL ${target}) - append_to_property(_python_targets GLOBAL ${target}) - - if(_python_so_extension) - set_target_properties(${target} PROPERTIES SUFFIX "${_python_so_extension}") - endif() -endmacro() - -# Add a Pybind11 library (overload of the original pybind11_add_module()) -# -# python_add_library() -# -# Override the original python_add_module() to keep track of all python libraries and properly set some target -# properties depending on the current CMake version. -function(pybind11_add_module target) - _pybind11_add_module(${target} ${ARGN}) - - append_to_property(_doc_targets GLOBAL ${target}) - append_to_property(_python_targets GLOBAL ${target}) -endfunction() - -# ============================================================================== diff --git a/cmake/options.cmake b/cmake/options.cmake deleted file mode 100644 index 6b4a9cb78..000000000 --- a/cmake/options.cmake +++ /dev/null @@ -1,164 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -include(CMakeDependentOption) - -# ============================================================================== -# Python related options - -if(APPLE) - option(PYTHON_VIRTUALENV_COMPAT "(Mac OS X) Make CMake search for Python Framework *after* any available\ - unix-style package. Can be useful in case of virtual environments." ON) -else() - option(PYTHON_VIRTUALENV_COMPAT "(Mac OS X) Make CMake search for Python Framework *after* any available\ - unix-style package. Can be useful in case of virtual environments." OFF) -endif() - -option(IS_PYTHON_BUILD "Is CMake called from setup.py? (e.g. python3 setup.py install?)" OFF) -option(IN_PLACE_BUILD "Are we building in-place for testing/development?" ON) - -# ============================================================================== -# CUDA related options - -if(DEFINED ENABLE_CUDA) - set(_enable_cuda_init ${ENABLE_CUDA}) -elseif(DEFINED GPUACCELERATED) - set(_enable_cuda_init ${GPUACCELERATED}) -else() - set(_enable_cuda_init OFF) -endif() - -option(ENABLE_CUDA "Enable building of CUDA libraries" _enable_cuda_init) -option(CUDA_ALLOW_UNSUPPORTED_COMPILER "Allow the use of an unsupported compiler version" OFF) -option(CUDA_STATIC "Use static version of Nvidia CUDA libraries during linking (also applies to nvc++)" OFF) - -# ============================================================================== -# Compilation options - -option(ENABLE_OPENMP "Use OpenMP for multi-threading" ON) - -option(ENABLE_PROJECTQ "Enable ProjectQ support" ON) -option(ENABLE_QUEST "Enable QuEST support" ON) - -# ------------------------------------------------------------------------------ - -option(ENABLE_PROFILING "Enable compilation with profiling flags." OFF) -option(ENABLE_STACK_PROTECTION "Enable the use of -fstack-protector during compilation" ON) - -# ============================================================================== -# Linking options - -option(ENABLE_RUNPATH "Prefer RUNPATH over RPATH when linking" ON) - -option(LINKER_DTAGS "Use --enable-new-dtags or --disable-new-dtags during linking" ON) -option(LINKER_NOEXECSTACK "Use -z,noexecstack during linking" ON) -option(LINKER_RELRO "Use -z,relro during linking for certain targets" ON) -option(LINKER_RPATH "Enable the use of RPATH/RUNPATH related flags during linking" ON) -option(LINKER_STRIP_ALL "Use --strip-all during linking" ON) - -# ============================================================================== -# Package related options - -# ============================================================================== -# Other CMake related options - -option(BUILD_TESTING "Build the test suite?" OFF) - -# NB: most if not all of our libraries have the type explicitly specified. -option(BUILD_SHARED_LIBS "Build shared libs" OFF) - -option(USE_VERBOSE_MAKEFILE "Use verbose Makefiles" ON) - -# ============================================================================== -# ============================================================================== -# Python related options - -if(PYTHON_VIRTUALENV_COMPAT) - set(CMAKE_FIND_FRAMEWORK LAST) -endif() - -# ------------------------------------------------------------------------------ - -if(IS_PYTHON_BUILD AND IN_PLACE_BUILD) - message(FATAL_ERROR "Cannot specify both IS_PYTHON_BUILD=ON and IN_PLACE_BUILD=ON!") -endif() - -# ============================================================================== -# CUDA related options - -if(CUDA_ALLOW_UNSUPPORTED_COMPILER) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler") -endif() - -if(ENABLE_CUDA) - enable_language(CUDA) - - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17) - find_package(CUDAToolkit REQUIRED) - else() - find_package(CUDA REQUIRED) - - if(CUDA_LIBRARIES) - if(NOT TARGET CUDA::cudart) - add_library(CUDA::cudart IMPORTED INTERFACE) - target_include_directories(CUDA::cudart SYSTEM INTERFACE "${CUDA_INCLUDE_DIRS}") - target_link_libraries(CUDA::cudart INTERFACE "${CUDA_LIBRARIES}") - endif() - endif() - - if(CUDA_cudart_static_LIBRARY) - if(NOT TARGET CUDA::cudart_static) - add_library(CUDA::cudart_static IMPORTED INTERFACE) - target_include_directories(CUDA::cudart_static SYSTEM INTERFACE "${CUDA_INCLUDE_DIRS}") - target_link_libraries(CUDA::cudart_static INTERFACE "${CUDA_cudart_static_LIBRARY}" Threads::Threads) - endif() - endif() - - find_library( - CUDA_driver_LIBRARY - NAMES cuda_driver cuda - HINTS ${CUDA_TOOLKIT_ROOT_DIR} ENV CUDA_PATH - PATH_SUFFIXES nvidia/current lib64 lib/x64 lib) - if(NOT CUDA_driver_LIBRARY) - # Don't try any stub directories until we have exhausted all other search locations. - find_library( - CUDA_driver_LIBRARY - NAMES cuda_driver cuda - HINTS ${CUDA_TOOLKIT_ROOT_DIR} ENV CUDA_PATH - PATH_SUFFIXES lib64/stubs lib/x64/stubs lib/stubs stubs) - endif() - mark_as_advanced(CUDA_driver_LIBRARY) - if(CUDA_driver_LIBRARY) - add_library(CUDA::cuda_driver IMPORTED INTERFACE) - target_include_directories(CUDA::cuda_driver SYSTEM INTERFACE "${CUDA_INCLUDE_DIRS}") - target_link_libraries(CUDA::cuda_driver INTERFACE "${CUDA_driver_LIBRARY}") - endif() - endif() -endif() - -# ============================================================================== -# Compilation options - -# ============================================================================== -# Other CMake related options - -if(USE_VERBOSE_MAKEFILE) - set(CMAKE_VERBOSE_MAKEFILE ON) -endif() - -# ============================================================================== diff --git a/cmake/os_detection.cmake b/cmake/os_detection.cmake deleted file mode 100644 index c412f76d9..000000000 --- a/cmake/os_detection.cmake +++ /dev/null @@ -1,88 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -if(UNIX) - if(APPLE) - set(OS_NAME - "OSX" - CACHE STRING "Operating system name" FORCE) - else() - # Tested with: - # - # * ArchLinux (x86_64 & aarch64) - # * CentOS - # * Debian - # * Fedora - # * OpenSUSE (leap) - - set(_fname /etc/os-release) - if(NOT EXISTS ${_fname}) - set(_fname /usr/lib/os-release) - if(NOT EXISTS ${_fname}) - set(_fname) - endif() - endif() - - if(_fname) - set(_regex "^ID=\"?([a-zA-Z]+)\"?") - file(STRINGS ${_fname} OS_NAME_RAW REGEX ${_regex}) - string(REGEX MATCH ${_regex} OS_NAME_RAW ${OS_NAME_RAW}) - set(OS_NAME ${CMAKE_MATCH_1}) - - set(OS_RELEASE 0) - set(_regex "^VERSION_ID=\"?([0-9\\.]+)\"?") - file(STRINGS ${_fname} OS_RELEASE_RAW REGEX ${_regex}) - if(OS_RELEASE_RAW) - string(REGEX MATCH ${_regex} OS_RELEASE_RAW ${OS_RELEASE_RAW}) - set(OS_RELEASE ${CMAKE_MATCH_1}) - endif() - elseif(NOT $ENV{NIX_STORE} STREQUAL "") # CMake 3.14: if(DEFINED ENV{NIX_STORE}) - set(OS_NAME "nix") - set(OS_RELEASE 0) - # Try to find out Nix version if possible - find_program(_nix nix) - if(_nix) - execute_process( - COMMAND ${_nix} --version - OUTPUT_VARIABLE _nix_version - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) - set(OS_RELEASE ${_nix_version}) - endif() - else() - set(OS_NAME "unknown") - set(OS_RELEASE "0") - endif() - endif() # APPLE -endif() # UNIX - -# ============================================================================== - -message(STATUS "Detected processor: ${CMAKE_SYSTEM_PROCESSOR}") -if(MQ_SKIP_SYSTEM_PROCESSOR_DETECTION) - # custom setup: required variables are passed through cache / CMake's command-line -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64.*|x86_64.*|AMD64.*") - set(X86_64 1) -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686.*|i386.*|x86.*") - set(X86 1) -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*|arm64.*|ARM64.*)") - set(AARCH64 1) -else() - message(WARNING "MindQuantum: unrecognized target processor configuration") -endif() - -# ============================================================================== diff --git a/cmake/packages.cmake b/cmake/packages.cmake deleted file mode 100644 index d6aa0d62a..000000000 --- a/cmake/packages.cmake +++ /dev/null @@ -1,200 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -# lint_cmake: -whitespace/indent - -# OpenMP - -set(PARALLEL_LIBS) -if(ENABLE_OPENMP) - if(APPLE) - find_program(BREW_CMD brew PATHS /usr/local/bin) - if(BREW_CMD) - # Homebrew installs libomp in ${LLVM_PREFIX}/lib and the headers in /usr/local/include - execute_process(COMMAND ${BREW_CMD} --prefix llvm OUTPUT_VARIABLE LLVM_PREFIX) - string(STRIP ${LLVM_PREFIX} LLVM_PREFIX) - list(APPEND CMAKE_LIBRARY_PATH ${LLVM_PREFIX}/lib) - else() - # MacPorts install libomp in /opt/local/lib/libomp and the headers in /opt/local/include/libomp - find_library( - LIBOMP_LIB omp gomp libomp - HINTS /opt/local/lib - PATH_SUFFIXES libomp - NO_DEFAULT_PATH) - fin_path( - LIBOMP_INC - omp.h - HINTS - /opt/local/include - PATH_SUFFIXES - libomp - NO_DEFAULT_PATH) - if(LIBOMP_LIB AND LIBOMP_INC) - get_filename_component(LIBOMP_DIR ${LIBOMP_LIB} DIRECTORY) - list(APPEND CMAKE_LIBRARY_PATH ${LIBOMP_DIR}) - list(APPEND CMAKE_INCLUDE_PATH ${LIBOMP_INC}) - endif() - endif() - endif() - - # ---------------------------------------------------------------------------- - - if(APPLE) - list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Modules/apple) - endif() - find_package(OpenMP) - if(OpenMP_FOUND) - list(APPEND PARALLEL_LIBS OpenMP::OpenMP_CXX) - endif() -endif() - -# ============================================================================== - -find_package(Threads REQUIRED) -list(APPEND PARALLEL_LIBS Threads::Threads) - -find_package(Patch REQUIRED) -include(FetchContent) -set(FETCHCONTENT_QUIET OFF) -set(BUILD_DIR ${CMAKE_BINARY_DIR}) -set(DEP_DIR ${BUILD_DIR}/_deps) -set(PATCH_DIR ${CMAKE_SOURCE_DIR}/third_party/patch) - -# ============================================================================== - -# Only helps set the Python executable for CMake >= 3.16 -if(DEFINED PYTHON_EXECUTABLE) - set(Python_EXECUTABLE ${PYTHON_EXECUTABLE}) # cmake-lint: disable=C0103 -endif() - -find_package(Python 3.5 COMPONENTS Interpreter Development) - -# NB: This should be removed for CMake >= 3.16 -if(NOT Python_FOUND) - # Use PYTHON_EXECUTABLE if it is defined, otherwise default to python - if(PYTHON_EXECUTABLE) - set(Python_EXECUTABLE ${PYTHON_EXECUTABLE}) # cmake-lint: disable=C0103 - elseif(NOT Python_EXECUTABLE) - find_program(Python_EXECUTABLE NAMES python3 python) - if(NOT Python_EXECUTABLE) - message(FATAL_ERROR "Unable to locate Python!") - endif() - endif() - - execute_process( - COMMAND "${Python_EXECUTABLE}" --version - RESULT_VARIABLE result - OUTPUT_VARIABLE _python_version) - string(STRIP "${_python_version}" Python_VERSION) - - if(Python_VERSION VERSION_LESS 3.6) - message(FATAL_ERROR "Cannot use Python ${Python_VERSION} (${Python_EXECUTABLE}): version too old!") - endif() - - execute_process( - COMMAND "${Python_EXECUTABLE}" -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())" - RESULT_VARIABLE result - OUTPUT_VARIABLE _python_inc) - string(STRIP "${_python_inc}" _python_inc) - - execute_process( - COMMAND "${Python_EXECUTABLE}" -c "import distutils.sysconfig as sysconfig; import os; \ - print(os.path.join(sysconfig.get_config_var('LIBDIR'), sysconfig.get_config_var('LDLIBRARY')))" - RESULT_VARIABLE result - OUTPUT_VARIABLE _python_lib) - string(STRIP "${_python_lib}" _python_lib) - - # Define an imported library for Python - macro(_python_import_library lib_name) - if(lib MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") - set(_type SHARED) - else() - set(_type STATIC) - endif() - - add_library(${lib_name} ${_type} IMPORTED) - target_include_directories(${lib_name} INTERFACE "${_python_inc}") - # cmake-lint: disable=C0307 - set_target_properties(${lib_name} PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION - "${_python_lib}") - endmacro() - - _python_import_library(Python::Python) - if(WIN32 - OR CYGWIN - OR MSYS) - # On Windows/Cygwin/MSYS Python::Module is an alias for Python::Python. See CMake code for FindPython. - _python_import_library(Python::Module) - else() - if(NOT TARGET Python::Module) - add_library(Python::Module INTERFACE IMPORTED) - endif() - target_include_directories(Python::Module INTERFACE "${_python_inc}") - target_link_options(Python::Module INTERFACE $<$:LINKER:-undefined,dynamic_lookup> - $<$:LINKER:-z,nodefs> $<$:LINKER:-b,erok>) - endif() -endif() - -if(CMAKE_VERSION VERSION_LESS 3.17) - message(CHECK_START "Looking for python SOABI") - - execute_process( - COMMAND "${Python_EXECUTABLE}" "-c" "from sysconfig import get_config_var; \ -print(get_config_var ('EXT_SUFFIX') or s.get_config_var ('SO'))" - RESULT_VARIABLE _soabi_success - OUTPUT_VARIABLE _python_so_extension - ERROR_VARIABLE _soabi_error_value - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if(NOT _soabi_success MATCHES 0) - message(CHECK_FAIL "failed") - message(FATAL_ERROR "Failed to extract Python SOABI extension:\n${_soabi_error_value}") - else() - message(CHECK_PASS "done") - endif() -endif() - -# ------------------------------------------------------------------------------ - -include(${CMAKE_CURRENT_LIST_DIR}/pybind11.cmake) - -# ------------------------------------------------------------------------------ - -if(ENABLE_PROJECTQ) - include(${CMAKE_CURRENT_LIST_DIR}/projectq.cmake) -endif() - -if(ENABLE_QUEST) - include(${CMAKE_CURRENT_LIST_DIR}/quest.cmake) -endif() - -# ============================================================================== -# For Huawei internal security assessment - -if(BINSCOPE) - get_filename_component(_binscope_path ${BINSCOPE} DIRECTORY) - get_filename_component(_binscope_name ${BINSCOPE} NAME) -endif() - -find_program( - binscope_exec - NAMES binscope ${_binscope_name} - HINTS ${_binscope_path}) -include(${CMAKE_CURRENT_LIST_DIR}/binscope.cmake) - -# ============================================================================== diff --git a/cmake/projectq.cmake b/cmake/projectq.cmake deleted file mode 100644 index b96d22d5f..000000000 --- a/cmake/projectq.cmake +++ /dev/null @@ -1,63 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -set(PKG_NAME projectq) -set(PKG_ROOT ${DEP_DIR}/${PKG_NAME}-src) - -set(URL "https://gitee.com/mirrors/ProjectQ/repository/archive/v0.5.1.tar.gz") - -file(GLOB _patch_files "${PATCH_DIR}/projectq/*.patch*") - -# ============================================================================== -# Fetch the source code - -FetchContent_Declare(${PKG_NAME} URL ${URL}) -FetchContent_Populate(${PKG_NAME}) - -# ============================================================================== -# Patch the source code - -# cmake-lint: disable=C0103 -set(${PKG_NAME}_PATCHED - FALSE - CACHE STRING INTERNAL) - -if(NOT ${PKG_NAME}_PATCHED) - message("Patching for ${PKG_NAME}") - foreach(_file ${_patch_files}) - execute_process( - COMMAND ${Patch_EXECUTABLE} -p1 - INPUT_FILE ${_file} - WORKING_DIRECTORY ${PKG_ROOT} - RESULT_VARIABLE _result) - - if(NOT _result EQUAL 0) - message(FATAL_ERROR "Failed patch: ${_file}") - endif() - endforeach() - - set(${PKG_NAME}_PATCHED - TRUE - CACHE STRING INTERNAL FORCE) -endif() - -# ============================================================================== - -add_library(projectq INTERFACE) -target_compile_features(projectq INTERFACE cxx_std_14) -target_include_directories(projectq INTERFACE ${PKG_ROOT}) diff --git a/cmake/pybind11.cmake b/cmake/pybind11.cmake deleted file mode 100644 index 1062fe0c1..000000000 --- a/cmake/pybind11.cmake +++ /dev/null @@ -1,274 +0,0 @@ -# ============================================================================== -# -# Copyright 2020 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -# -# Looking for pybind11 can be somewhat complicated now that there are two -# possible packages: -# - pybind11 -# - pybind11-global -# -# Here we try our best to look for pybind11 in all possible locations and in a -# meaningful way. -# In practice, this means that we look for a valid package in the -# following order: -# 1. First we look in a virtualenv (if one is active) for -# a) pybind11-global -# b) pybind11 >= 2.6.0 -# c) pybind11 < 2.6.0 && pybind11-cmake -# 2. pybind11-gobal in user site (if not in a virtualenv) -# 3. pybind11-gobal in global site package (also done if within a virtualenv) -# 4. pybind11 >= 2.6.0 in user and global sites -# NB: if the version of pybind11 is more recent than pybind11-global, we -# choose pybind11 -# 5. pybind11 < 2.6.0 && pybind11-cmake in user and global sites -# -# ============================================================================== - -# ~~~ -# Make sure that pybind11 finds the correct python version -# - pybind11 < 2.6.0 uses find_package(PythonInterp ...) -# - pybind11 >= 2.6.0 looks for the `python` command or PYTHON_EXECUTABLE -# => specify PYTHON_EXECUTABLE manually to guarantee we find the same -# interpreter and libraries as for other python packages -# ~~~ -set(PYTHON_EXECUTABLE ${Python_EXECUTABLE}) -set(PYBIND11_PYTHON_VERSION ${Python_VERSION}) # maybe not strictly required - -message(CHECK_START "Looking for pybind11") -list(APPEND CMAKE_MESSAGE_INDENT " ") - -# ============================================================================== -# First detect whether we are in a virtualenv or not - -execute_process( - COMMAND ${Python_EXECUTABLE} -c "import sys; print(int(sys.prefix != sys.base_prefix))" - OUTPUT_VARIABLE _is_virtualenv - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - -if(_is_virtualenv) - # ~~~ - # Try to find in order within the virtualenv: - # - pybind11-global - # - pybind11 >= 2.6.0 - # - pybind11 < 2.6.0 with pybind11-cmake - # - if all of that fails, revert to user and global sites (as far as is possible) - # ~~~ - - message(CHECK_START "Looking for pybind11-global in virtualenv") - - # Look for pybind11-global - execute_process( - COMMAND ${Python_EXECUTABLE} -c "import sys, os; print(os.path.join(sys.prefix, 'share', 'cmake', 'pybind11'))" - OUTPUT_VARIABLE pybind11_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if(NOT EXISTS pybind11_DIR) - message(CHECK_FAIL "Not-found") - - # Could not find pybind11-global, try pybind11 >= 2.6.0 - message(CHECK_START "Looking for pybind11 >= 2.6.0 in virtualenv") - - find_python_module(pybind11 VERSION 2.6.0) - if(PYMOD_PYBIND11_FOUND) - message(CHECK_PASS "Found") - execute_process( - COMMAND ${Python_EXECUTABLE} -m pybind11 --cmakedir - OUTPUT_VARIABLE pybind11_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - else() - message(CHECK_FAIL "Not-found") - # Now try pybind11 < 2.6.0 && pybind11-cmake - find_python_module(pybind11-cmake) - if(PYMOD_PYBIND11_CMAKE_FOUND) - execute_process( - COMMAND ${Python_EXECUTABLE} -c "import pybind11_cmake; print(pybind11_cmake.__path__[0])" - OUTPUT_VARIABLE pybind11_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - endif() - endif() - else() - message(CHECK_PASS "Found") - endif() - - # Try to find some valid pybind11 config within the virtualenv - find_package(pybind11 2.6.0 CONFIG QUIET NO_DEFAULT_PATH) -endif() - -# ------------------------------------------------------------------------------ - -if(NOT pybind11_FOUND) - message(CHECK_START "Looking for pybind11-global in global and user sites") - - if(NOT _is_virtualenv) - # Try pybind11-global in user site - execute_process( - COMMAND ${Python_EXECUTABLE} -m site --user-base - RESULT_VARIABLE _status - OUTPUT_VARIABLE _user_base - OUTPUT_STRIP_TRAILING_WHITESPACE) - - # NB: _status == 0 means user site enabled. Anything else and we really should not consider it - if(_status EQUAL 0 AND EXISTS "${_user_base}/share/cmake/pybind11/") - # cmake-lint: disable=C0103 - set(pybind11_DIR "${_user_base}/share/cmake/pybind11/") - endif() - endif() - - # Try to find pybind11-global either in user-site or global-site (even in the case of a virtualenv since we did not - # find anything useful in it previously) - find_package(pybind11 2.6.0 CONFIG QUIET) - - if(pybind11_FOUND) - message(CHECK_PASS "Found") - else() - message(CHECK_PASS "Not-found") - endif() - - if(NOT _is_virtualenv) - message(CHECK_START "Looking for pybind11 Python module") - - # Try to find pybind11 either in user-site or global-site - find_python_module(pybind11 VERSION 2.6.0) - - if(PYMOD_PYBIND11_FOUND) - message(CHECK_PASS "Found") - else() - message(CHECK_PASS "Not-found") - endif() - - if(PYMOD_PYBIND11_FOUND AND PYMOD_PYBIND11_VERSION VERSION_GREATER pybind11_VERSION) - # We prefer pybind11 over pybind11 global if its version is more recent. This could typically be the case if a - # user installs a more recent version of pybind11 in its user site. - execute_process( - COMMAND ${Python_EXECUTABLE} -m pybind11 --cmakedir - OUTPUT_VARIABLE pybind11_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - endif() - - message(CHECK_START "Looking for pybind11 (final)") - - # Look for pybind11 again in case we have pybind11 more recent than pybind11-global - find_package(pybind11 2.6.0 CONFIG QUIET NO_DEFAULT_PATH) - - # If we are not in a virtualenv, we should still try to look for pybind11 < 2.6.0 && pybind11-cmake - if(NOT pybind11_FOUND) - message(CHECK_PASS "Not-found") - - message(CHECK_START "Looking for pybind11-cmake") - # If everything else fails, rely on the pybind11_cmake package - find_python_module(pybind11-cmake) - if(PYMOD_PYBIND11_CMAKE_FOUND) - message(CHECK_PASS "Done") - execute_process( - COMMAND ${Python_EXECUTABLE} -c "import pybind11_cmake; print(pybind11_cmake.__path__[0])" - OUTPUT_VARIABLE pybind11_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - else() - message(CHECK_FAIL "Failed") - endif() - else() - message(CHECK_PASS "Found") - endif() - endif() -endif() - -# ------------------------------------------------------------------------------ -# Now look for pybind11 using the CONFIG method one last time. This would typically only be useful in the case of -# pybind11 < 2.6.0 located in either user or global sites. - -list(POP_BACK CMAKE_MESSAGE_INDENT) -if(pybind11_DIR) - message(CHECK_PASS "Done") -else() - message(CHECK_FAIL "Failed") -endif() -find_package(pybind11 2.6.0 CONFIG NO_DEFAULT_PATH) - -# ============================================================================== - -# With pybind11-cmake 1.0.0 we might need to fix the include path -if(PYMOD_PYBIND11_CMAKE_FOUND) - include(CheckCXXSourceCompiles) - check_cxx_source_compiles("#include -int main() {return 0;}" pybind11_compiles) - - if(NOT pybind11_compiles) - # This could happen with pybind11_cmake == 1.0.0 because there is a typo in the pybind11Config.cmake - execute_process( - COMMAND ${Python_EXECUTABLE} -c "import pybind11 -print(pybind11.get_include(False) + ';' + pybind11.get_include(True))" - OUTPUT_VARIABLE _pybind11_inc_dir - OUTPUT_STRIP_TRAILING_WHITESPACE) - get_directory_property(_inc_dirs INCLUDE_DIRECTORIES) - list(FILTER _inc_dirs EXCLUDE REGEX .*pybind11_INCLUDE_DIR$) - list(APPEND _inc_dirs ${_pybind11_inc_dir}) - set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES "${_inc_dirs}") - endif() -endif() - -# ============================================================================== - -if(pybind11_FOUND) - if(NOT TARGET pybind11::headers) - message(SEND_ERROR "Target pybind11::headers was not defined! Perhaps try updating pybind11 on your system?") - endif() - if(NOT TARGET pybind11::pybind11) - message(SEND_ERROR "Target pybind11::pybind11 was not defined! Perhaps try updating pybind11 on your system?") - endif() - if(NOT TARGET pybind11::module) - message(SEND_ERROR "Target pybind11::module was not defined! Perhaps try updating pybind11 on your system?") - endif() - - # NB: workardound for NVHPC compiler that requires -isystem (e.g. for /usr/include) - get_target_property(_include_dir pybind11::pybind11_headers INTERFACE_INCLUDE_DIRECTORIES) - - if(_include_dir) - target_include_directories(pybind11::pybind11_headers SYSTEM INTERFACE ${_include_dir}) - endif() -else() - message(STATUS "pybind11 was not found on your system or it is an incompatible version") - message(STATUS " -> will be fetching pybind11 from an external Git repository") - - set(PKG_NAME pybind11) - set(PKG_ROOT ${DEP_DIR}/${PKG_NAME}-src) - - set(URL "https://gitee.com/mirrors/pybind11/repository/archive/v2.7.1.tar.gz") - - FetchContent_Declare(${PKG_NAME} URL ${URL}) - FetchContent_GetProperties(${PKG_NAME}) - if(NOT ${PKG_NAME}_POPULATED) - FetchContent_Populate(${PKG_NAME}) - endif() - - include_directories(${PKG_ROOT}/include) - add_subdirectory(${PKG_ROOT} pybind11-build) -endif() - -# ------------------------------------------------------------------------------ - -# For debugging -if(pybind11_FOUND) - message(STATUS "Found pybind11 using the CONFIG method in ${pybind11_DIR}") - message(STATUS "Found pybind11 and defined the pybind11::pybind11 imported target:") - message(STATUS " - include: ${pybind11_INCLUDE_DIR}") - message(STATUS " - version: ${pybind11_VERSION}") -endif() - -# ============================================================================== - -mark_as_advanced(pybind11_DIR) - -# ============================================================================== diff --git a/cmake/quest.cmake b/cmake/quest.cmake deleted file mode 100644 index 639421ad8..000000000 --- a/cmake/quest.cmake +++ /dev/null @@ -1,74 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -set(PKG_NAME quest) -set(PKG_ROOT ${DEP_DIR}/${PKG_NAME}-src) - -set(URL "https://gitee.com/donghufeng/QuEST/repository/archive/v3.2.1.tar.gz") - -file(GLOB _patch_files "${PATCH_DIR}/quest/*.patch*") - -# ============================================================================== -# Fetch the source code - -if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) - set(CMAKE_CUDA_ARCHITECTURES 60) -endif() - -FetchContent_Declare(${PKG_NAME} URL ${URL}) -FetchContent_Populate(${PKG_NAME}) - -# ============================================================================== -# Patch the source code - -# cmake-lint: disable=C0103 -set(${PKG_NAME}_PATCHED - FALSE - CACHE STRING INTERNAL) - -if(NOT ${PKG_NAME}_PATCHED) - message("Patching for ${PKG_NAME}") - foreach(_file ${_patch_files}) - execute_process( - COMMAND ${Patch_EXECUTABLE} -p1 - INPUT_FILE ${_file} - WORKING_DIRECTORY ${PKG_ROOT} - RESULT_VARIABLE _result) - - if(NOT _result EQUAL 0) - message(FATAL_ERROR "Failed patch: ${_file}") - endif() - endforeach() - - set(${PKG_NAME}_PATCHED - TRUE - CACHE STRING INTERNAL FORCE) -endif() - -# ============================================================================== - -# Add QuEST as a sub-directory (NB: only used to create a new scope for temporary variables) -function(add_quest_subdirectory) - set(GPUACCELERATED ${ENABLE_CUDA}) - set(TESTING OFF) - set(GPU_COMPUTE_CAPABILITY ${CMAKE_CUDA_ARCHITECTURES}) - add_subdirectory(${PKG_ROOT} ${CMAKE_BINARY_DIR}/_deps/${PKG_NAME}-build EXCLUDE_FROM_ALL) -endfunction() - -add_quest_subdirectory() -set_output_directory_auto(QuEST mindquantum) diff --git a/install_with_docker.md b/install_with_docker.md index 66d4e4896..85fbd3119 100644 --- a/install_with_docker.md +++ b/install_with_docker.md @@ -44,7 +44,7 @@ docker run -it swr.cn-south-1.myhuaweicloud.com/mindspore/mindspore-cpu:{tag} /b ```python import numpy as np import mindspore.context as context -import mindspore.operators as ops +import mindspore.ops as ops from mindspore import Tensor context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") diff --git a/install_with_docker_en.md b/install_with_docker_en.md index 0c822a885..9b771eb6c 100644 --- a/install_with_docker_en.md +++ b/install_with_docker_en.md @@ -44,7 +44,7 @@ After entering the MindSpore container according to the above steps, to test whe ```python import numpy as np import mindspore.context as context -import mindspore.operators as ops +import mindspore.ops as ops from mindspore import Tensor context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") @@ -102,4 +102,4 @@ At this point, you have successfully installed the MindSpore CPU version by Dock ```python python -c 'import mindquantum' -``` +``` \ No newline at end of file diff --git a/mindquantum/__init__.py b/mindquantum/__init__.py index 739befa11..213ac46de 100644 --- a/mindquantum/__init__.py +++ b/mindquantum/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,42 +16,34 @@ import os import warnings -import sys -from . import core -from .core import gates -from .core import operators +from . import ops +from . import circuit from . import engine -from . import framework +from . import gate +from . import nn +from . import parameterresolver from . import utils -from . import algorithm -from . import simulator -from . import io -from .core import * -from .algorithm import * -from .utils import * -from .simulator import * -from .framework import * -from .io import * +from . import hiqfermion +from . import ansatz +from .circuit import * +from .gate import * +from .parameterresolver import * +from .version import __version__ +__version_info__ = tuple(__version__.split('.')) -if sys.version_info < (3, 8): # pragma: no cover - from importlib_metadata import version, PackageNotFoundError -else: # pragma: no cover - from importlib.metadata import version, PackageNotFoundError - -try: - __version__ = version("mindquantum") - __version_info__ = tuple(__version__.split('.')) - __all__ = ['__version__', '__version_info__'] -except PackageNotFoundError: - __all__ = [] - - -__all__.extend(core.__all__) -__all__.extend(algorithm.__all__) -__all__.extend(utils.__all__) -__all__.extend(simulator.__all__) -__all__.extend(framework.__all__) -__all__.extend(io.__all__) +__all__ = ['__version__', '__version_info__'] +__all__.extend(circuit.__all__) +__all__.extend(gate.__all__) +__all__.extend(parameterresolver.__all__) __all__.sort() + +total_num_core = os.cpu_count() +omp_num_threads = os.environ.get('OMP_NUM_THREADS') +if omp_num_threads is None: + omp_num_threads = total_num_core +warnings.warn( + "[NOTE] Current simulator thread is {}. If your simulation is slow, \ +set OMP_NUM_THREADS to a appropriate number according to your model.".format( + omp_num_threads)) diff --git a/mindquantum/algorithm/__init__.py b/mindquantum/algorithm/__init__.py deleted file mode 100644 index c8bc18fce..000000000 --- a/mindquantum/algorithm/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Quantum algorithms""" - -from . import library -from . import nisq -from .library import * -from .nisq import * - -__all__ = [] -__all__.extend(library.__all__) -__all__.extend(nisq.__all__) -__all__.sort() diff --git a/mindquantum/algorithm/nisq/__init__.py b/mindquantum/ansatz/__init__.py similarity index 65% rename from mindquantum/algorithm/nisq/__init__.py rename to mindquantum/ansatz/__init__.py index b57765ef8..6a3c7ebed 100644 --- a/mindquantum/algorithm/nisq/__init__.py +++ b/mindquantum/ansatz/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""NISQ algorithms""" +"""Implementation of different ansatz.""" from ._ansatz import Ansatz -from . import chem -from . import qaoa -from .chem import * -from .qaoa import * +from .max_cut import MaxCutAnsatz +from .max_2_sat import Max2SATAnsatz +from .hardware_efficient import HardwareEfficientAnsatz +from .unitary_cc import UCCAnsatz +from .qubit_ucc import QubitUCCAnsatz -__all__ = ['Ansatz'] -__all__.extend(chem.__all__) -__all__.extend(qaoa.__all__) -__all__.sort() +__all__ = ['Ansatz', 'MaxCutAnsatz', 'Max2SATAnsatz', 'HardwareEfficientAnsatz', + "UCCAnsatz", "QubitUCCAnsatz"] diff --git a/mindquantum/algorithm/nisq/_ansatz.py b/mindquantum/ansatz/_ansatz.py similarity index 95% rename from mindquantum/algorithm/nisq/_ansatz.py rename to mindquantum/ansatz/_ansatz.py index b9c4360fd..f491a0308 100644 --- a/mindquantum/algorithm/nisq/_ansatz.py +++ b/mindquantum/ansatz/_ansatz.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +15,7 @@ """Basic class of ansatz.""" from abc import abstractmethod -from mindquantum.core.circuit import Circuit +from mindquantum.circuit import Circuit class Ansatz: diff --git a/mindquantum/algorithm/nisq/chem/hardware_efficient_ansatz.py b/mindquantum/ansatz/hardware_efficient.py similarity index 87% rename from mindquantum/algorithm/nisq/chem/hardware_efficient_ansatz.py rename to mindquantum/ansatz/hardware_efficient.py index c19991fa7..184cb25b8 100644 --- a/mindquantum/algorithm/nisq/chem/hardware_efficient_ansatz.py +++ b/mindquantum/ansatz/hardware_efficient.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,10 +16,9 @@ import itertools import numpy as np -from mindquantum.core.gates import BasicGate, X -from mindquantum.core.circuit import Circuit -from mindquantum.core.circuit.utils import AP, A -from .._ansatz import Ansatz +from mindquantum.gate import BasicGate, X +from mindquantum.circuit import Circuit, AP, A +from ._ansatz import Ansatz def _check_single_rot_gate_seq(single_rot_gate_seq): @@ -61,19 +59,27 @@ class HardwareEfficientAnsatz(Ansatz): the entanglemtn gate will be act on any two qbuits. Besides, you can specific which two qubits you want to do entanglement by setting the entangle_mapping to a list of two qubits tuple. Default: "linear". - depth (int): The depth of ansatz. Default: 1. + depth (int): Repeat the entanglement gate layer and single_rot_gate_seq in depth times. Default: 1. Examples: >>> from mindquantum.ansatz import HardwareEfficientAnsatz >>> from mindquantum import RY, RZ, Z - >>> hea = HardwareEfficientAnsatz(3, [RY, RZ], Z, [(0, 1), (0, 2)]) + >>> hea = HardwareEfficientAnsatz(3, [RY, RZ], Z, [(1, 0), (2, 0)]) >>> hea.circuit - q0: ──RY(d0_n0_0)────RZ(d0_n0_1)────●────●────RY(d1_n0_0)────RZ(d1_n0_1)── - │ │ - q1: ──RY(d0_n1_0)────RZ(d0_n1_1)────Z────┼────RY(d1_n1_0)────RZ(d1_n1_1)── - │ - q2: ──RY(d0_n2_0)────RZ(d0_n2_1)─────────Z────RY(d1_n2_0)────RZ(d1_n2_1)── - + RY(d0_n0_0|0) + RZ(d0_n0_1|0) + RY(d0_n1_0|1) + RZ(d0_n1_1|1) + RY(d0_n2_0|2) + RZ(d0_n2_1|2) + Z(1 <-: 0) + Z(2 <-: 0) + RY(d1_n0_0|0) + RZ(d1_n0_1|0) + RY(d1_n1_0|1) + RZ(d1_n1_1|1) + RY(d1_n2_0|2) + RZ(d1_n2_1|2) """ def __init__(self, n_qubits, @@ -85,7 +91,7 @@ def __init__(self, if not isinstance(depth, int) or depth <= 0: raise ValueError(f"depth requires a positive int, but get {depth}") if not isinstance(entangle_gate, - BasicGate) or entangle_gate.parameterized: + BasicGate) or entangle_gate.isparameter: raise ValueError( f"entangle gate requires a non parameterized gate, but get {entangle_gate}" ) @@ -126,6 +132,7 @@ def _get_entangle_mapping(self, entangle_mapping): raise TypeError( f"Element of entangle_mapping need a tuple, but get {type(i)}" ) + entangle_mapping = [(j, i) for i, j in entangle_mapping] else: raise ValueError("entangle_mapping can only be 'all', 'linear', \ or a list of tuple of the qubits that the entanglement gate act on.") diff --git a/mindquantum/algorithm/nisq/qaoa/max_2_sat_ansatz.py b/mindquantum/ansatz/max_2_sat.py similarity index 65% rename from mindquantum/algorithm/nisq/qaoa/max_2_sat_ansatz.py rename to mindquantum/ansatz/max_2_sat.py index 61315f53c..a4b7a477f 100644 --- a/mindquantum/algorithm/nisq/qaoa/max_2_sat_ansatz.py +++ b/mindquantum/ansatz/max_2_sat.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,11 +15,10 @@ """Max-2-SAT ansatz.""" from math import copysign as sign -from mindquantum.core.circuit import Circuit, UN -from mindquantum.core.circuit.utils import CPN -from mindquantum.core.gates import H, RX -from mindquantum.core.operators import TimeEvolution, QubitOperator -from .._ansatz import Ansatz +from mindquantum.ansatz import Ansatz +from mindquantum.circuit import Circuit, CPN, UN, TimeEvolution +from mindquantum.gate import H, RX +from mindquantum.ops import QubitOperator def _get_clause_act_qubits_num(clauses): @@ -47,12 +45,9 @@ def _check_clause(clauses): if not isinstance(clause, tuple): raise TypeError(f"clause requires a tuple, but get {type(clause)}") if len(clause) != 2: - raise ValueError( - f"each clause must contain two integers, but get {len(clause)}" - ) + raise ValueError(f"each clause must contain two integers, but get {len(clause)}") if 0 in clause: - raise ValueError( - "clause must contain non-zero integers, but get 0") + raise ValueError("clause must contain non-zero integers, but get 0") class Max2SATAnsatz(Ansatz): @@ -83,14 +78,36 @@ class Max2SATAnsatz(Ansatz): Examples: >>> from mindquantum.ansatz import Max2SATAnsatz - >>> clauses = [(2, -3)] + >>> clauses = [(1, 2), (2, -3)] >>> max2sat = Max2SATAnsatz(clauses, 2) >>> max2sat.circuit - q0: ───────────────────────────────────────────────────────────────────── - - q1: ──H────RZ(0.25*beta_0)─────●───────────────────────●────RX(alpha_0)── - │ │ - q2: ──H────RZ(-0.25*beta_0)────X────RZ(-0.5*beta_0)────X────RX(alpha_0)── + H(0) + H(1) + H(2) + RZ(0.25*beta_0|0) + RZ(0.5*beta_0|1) + X(1 <-: 0) + RZ(0.5*beta_0|1) + X(1 <-: 0) + RZ(-0.25*beta_0|2) + X(2 <-: 1) + RZ(-0.5*beta_0|2) + X(2 <-: 1) + RX(alpha_0|0) + RX(alpha_0|1) + RX(alpha_0|2) + RZ(0.25*beta_1|0) + RZ(0.5*beta_1|1) + X(1 <-: 0) + RZ(0.5*beta_1|1) + X(1 <-: 0) + RZ(-0.25*beta_1|2) + X(2 <-: 1) + RZ(-0.5*beta_1|2) + X(2 <-: 1) + RX(alpha_1|0) + RX(alpha_1|1) + RX(alpha_1|2) >>> max2sat.hamiltonian 0.5 [] + @@ -106,9 +123,9 @@ def __init__(self, clauses, depth=1): if depth <= 0: raise ValueError(f"depth must be greater than 0, but get {depth}.") _check_clause(clauses) - super(Max2SATAnsatz, - self).__init__('Max2SAT', _get_clause_act_qubits_num(clauses), - clauses, depth) + super(Max2SATAnsatz, self).__init__('Max2SAT', + _get_clause_act_qubits_num(clauses), + clauses, depth) self.clauses = clauses self.depth = depth @@ -116,11 +133,10 @@ def _build_hc(self, clauses): """Build hc circuit.""" ham = QubitOperator() for clause in clauses: - ham += (sign(1, clause[0]) * QubitOperator( - f'Z{abs(clause[0]) - 1}', 'beta') + sign(1, clause[1]) * - QubitOperator(f'Z{abs(clause[1]) - 1}', 'beta') + - sign(1, clause[0]) * sign(1, clause[1]) * QubitOperator( - f'Z{abs(clause[0]) - 1} Z{abs(clause[1]) - 1}', 'beta') + ham += (sign(1, clause[0]) * QubitOperator(f'Z{abs(clause[0]) - 1}', 'beta') + + sign(1, clause[1]) * QubitOperator(f'Z{abs(clause[1]) - 1}', 'beta') + + sign(1, clause[0]) * sign(1, clause[1]) * + QubitOperator(f'Z{abs(clause[0]) - 1} Z{abs(clause[1]) - 1}', 'beta') ) / 4 return TimeEvolution(ham).circuit @@ -140,20 +156,18 @@ def hamiltonian(self): """ qo = QubitOperator() for clause in self.clauses: - qo += ( - QubitOperator('') + - sign(1, clause[0]) * QubitOperator(f'Z{abs(clause[0]) - 1}') + - sign(1, clause[1]) * QubitOperator(f'Z{abs(clause[1]) - 1}') + - sign(1, clause[0]) * sign(1, clause[1]) * - QubitOperator(f'Z{abs(clause[0]) - 1} Z{abs(clause[1]) - 1}') - ) / 4 + qo += (QubitOperator('') + + sign(1, clause[0]) * QubitOperator(f'Z{abs(clause[0]) - 1}') + + sign(1, clause[1]) * QubitOperator(f'Z{abs(clause[1]) - 1}') + + sign(1, clause[0]) * sign(1, clause[1]) * + QubitOperator(f'Z{abs(clause[0]) - 1} Z{abs(clause[1]) - 1}') + ) / 4 return qo def _implement(self, clauses, depth): """Implement of max 2 sat ansatz.""" self._circuit = UN(H, _get_clause_act_qubits(clauses)) for d in range(depth): - self._circuit += CPN(self._build_hc(clauses), - {'beta': f'beta_{d}'}) + self._circuit += CPN(self._build_hc(clauses), {'beta': f'beta_{d}'}) self._circuit += CPN(self._build_hb(clauses), {'alpha': f'alpha_{d}'}) diff --git a/mindquantum/algorithm/nisq/qaoa/max_cut_ansatz.py b/mindquantum/ansatz/max_cut.py similarity index 83% rename from mindquantum/algorithm/nisq/qaoa/max_cut_ansatz.py rename to mindquantum/ansatz/max_cut.py index 0924cb108..b4c32ef11 100644 --- a/mindquantum/algorithm/nisq/qaoa/max_cut_ansatz.py +++ b/mindquantum/ansatz/max_cut.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +14,10 @@ # ============================================================================ """MaxCut ansatz.""" -from mindquantum.core.gates import H, RX, ZZ -from mindquantum.core.circuit import Circuit, UN -from mindquantum.core.operators import QubitOperator -from mindquantum.core.circuit.utils import CPN -from .._ansatz import Ansatz +from mindquantum.gate import H, RX, ZZ +from mindquantum.circuit import Circuit, CPN, UN +from mindquantum.ops import QubitOperator +from ._ansatz import Ansatz def _get_graph_act_qubits_num(graph): @@ -78,13 +76,23 @@ class MaxCutAnsatz(Ansatz): Examples: >>> from mindquantum.ansatz import MaxCutAnsatz >>> graph = [(0, 1), (1, 2), (0, 2)] - >>> maxcut = MaxCutAnsatz(graph, 1) + >>> maxcut = MaxCutAnsatz(graph, 2) >>> maxcut.circuit - q0: ──H────ZZ(beta_0)──────────────────ZZ(beta_0)────RX(alpha_0)── - │ │ - q1: ──H────ZZ(beta_0)────ZZ(beta_0)────────┼─────────RX(alpha_0)── - │ │ - q2: ──H──────────────────ZZ(beta_0)────ZZ(beta_0)────RX(alpha_0)── + H(0) + H(1) + H(2) + ZZ(beta_0|0 1) + ZZ(beta_0|1 2) + ZZ(beta_0|0 2) + RX(alpha_0|0) + RX(alpha_0|1) + RX(alpha_0|2) + ZZ(beta_1|0 1) + ZZ(beta_1|1 2) + ZZ(beta_1|0 2) + RX(alpha_1|0) + RX(alpha_1|1) + RX(alpha_1|2) >>> maxcut.hamiltonian 1.5 [] + diff --git a/mindquantum/algorithm/nisq/chem/qubit_ucc_ansatz.py b/mindquantum/ansatz/qubit_ucc.py similarity index 89% rename from mindquantum/algorithm/nisq/chem/qubit_ucc_ansatz.py rename to mindquantum/ansatz/qubit_ucc.py index 459263777..effdb8adc 100644 --- a/mindquantum/algorithm/nisq/chem/qubit_ucc_ansatz.py +++ b/mindquantum/ansatz/qubit_ucc.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +18,12 @@ import itertools import numpy -from mindquantum.core.gates import CNOT, X, RY -from mindquantum.core.circuit import Circuit -from mindquantum.core.parameterresolver import ParameterResolver as PR -from mindquantum.core.operators import QubitExcitationOperator -from mindquantum.core.operators.utils import hermitian_conjugated -from .._ansatz import Ansatz +from mindquantum.gate import CNOT, X, RY +from mindquantum.circuit import Circuit +from mindquantum.parameterresolver import ParameterResolver as PR +from mindquantum.ops import QubitExcitationOperator +from mindquantum.utils import hermitian_conjugated +from ._ansatz import Ansatz def _check_int_list(input_list, name): @@ -74,13 +73,16 @@ class QubitUCCAnsatz(Ansatz): 0 >>> qucc = QubitUCCAnsatz(4, 2, trotter_step=2) >>> qucc.circuit[:10] - q0: ──X──────────●──────────X───────────────────────────────X──────────●──────────X─────── - │ │ │ │ │ │ - q1: ──┼──────────┼──────────┼────X──────────●──────────X────┼──────────┼──────────┼────X── - │ │ │ │ │ │ │ │ │ │ - q2: ──●────RY(t_0_q_s_0)────●────●────RY(t_0_q_s_1)────●────┼──────────┼──────────┼────┼── - │ │ │ │ - q3: ────────────────────────────────────────────────────────●────RY(t_0_q_s_2)────●────●── + CNOT(0 <-: 2) + RY(t_0_q_s_0|2 <-: 0) + CNOT(0 <-: 2) + CNOT(1 <-: 2) + RY(t_0_q_s_1|2 <-: 1) + CNOT(1 <-: 2) + CNOT(0 <-: 3) + RY(t_0_q_s_2|3 <-: 0) + CNOT(0 <-: 3) + CNOT(1 <-: 3) >>> qucc.n_qubits 4 >>> qucc_2 = QubitUCCAnsatz(occ_orb=[0, 1], vir_orb=[2]) diff --git a/mindquantum/algorithm/nisq/chem/unitary_cc.py b/mindquantum/ansatz/unitary_cc.py similarity index 69% rename from mindquantum/algorithm/nisq/chem/unitary_cc.py rename to mindquantum/ansatz/unitary_cc.py index fbf53d3b0..29b220af4 100644 --- a/mindquantum/algorithm/nisq/chem/unitary_cc.py +++ b/mindquantum/ansatz/unitary_cc.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,12 +14,10 @@ # ============================================================================ """Unitary coupled-cluster ansatz.""" -from mindquantum.core.circuit import Circuit -from mindquantum.core.operators import TimeEvolution -from mindquantum.core.circuit.utils import add_prefix -from .uccsd0 import uccsd0_singlet_generator -from .transform import Transform -from .._ansatz import Ansatz +from mindquantum.circuit import Circuit, TimeEvolution, add_prefix +from mindquantum.hiqfermion.transforms import Transform +from mindquantum.hiqfermion.ucc import uccsd0_singlet_generator +from ._ansatz import Ansatz def _check_int_list(input_list, name): @@ -65,28 +62,25 @@ class UCCAnsatz(Ansatz): ... vir_orb=[2, 3], ... generalized=True, ... trotter_step=2) - >>> circuit = ucc.circuit.remove_barrier() - >>> len(circuit) + >>> circuit_list = list(ucc.circuit) + >>> len(circuit_list) 3624 - >>> params_list = ucc.circuit.params_name + >>> params_list = ucc.circuit.para_name >>> len(params_list) - 48 - >>> circuit[-10:] - q0: ────────────────────────────────────────────────────────────────────────── - - q1: ────────────────────────────────────────────────────────────────────────── - - q2: ────────────────────────────────────────────────────────────────────────── - - q3: ────────────────────────────────────────────────────────────────────────── - - q4: ────────────────────────────────────────────────────────────────────────── - - q5: ──●────RX(7π/2)───────H───────●────────────────────────────●───────H────── - │ │ │ - q6: ──┼───────────────────────────┼────────────────────────────┼────────────── - │ │ │ - q7: ──X───────H────────RX(π/2)────X────RZ(-0.5*t_1_d0_d_17)────X────RX(7π/2)── + 40 + >>> for i in range(10): + ... print(circuit_list[i]) + ... + RX(1.571|2) + H(4) + X(3 <-: 2) + X(4 <-: 3) + RZ(-1.0*t_0_d0_s_0|4) + X(4 <-: 3) + X(3 <-: 2) + H(4) + RX(10.996|2) + H(2) """ def __init__(self, n_qubits=None, diff --git a/mindquantum/circuit/__init__.py b/mindquantum/circuit/__init__.py new file mode 100644 index 000000000..6deb9de8e --- /dev/null +++ b/mindquantum/circuit/__init__.py @@ -0,0 +1,45 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +""" +Circuit. + +Quantum circuit module. +""" + +from .circuit import Circuit +from .circuit import pauli_word_to_circuits +from .module_circuit import UN, SwapParts, U3 +from .uccsd import generate_uccsd +from .uccsd import decompose_single_term_time_evolution +from .time_evolution import TimeEvolution +from .high_level_ops import controlled +from .high_level_ops import dagger +from .high_level_ops import apply +from .high_level_ops import add_prefix +from .high_level_ops import change_param_name +from .high_level_ops import A +from .high_level_ops import AP +from .high_level_ops import C +from .high_level_ops import CPN +from .high_level_ops import D +from .state_evolution import StateEvolution +from .quantum_fourier import qft + +__all__ = [ + 'Circuit', 'StateEvolution', 'TimeEvolution', 'U3', 'UN', 'SwapParts', + 'qft', 'pauli_word_to_circuits', 'decompose_single_term_time_evolution', + 'generate_uccsd', 'controlled', 'dagger', 'apply', 'add_prefix', + 'change_param_name', 'C', 'D', 'A', 'AP', 'CPN' +] diff --git a/mindquantum/core/circuit/circuit.py b/mindquantum/circuit/circuit.py similarity index 67% rename from mindquantum/core/circuit/circuit.py rename to mindquantum/circuit/circuit.py index 69cf05c35..fcbfc3469 100644 --- a/mindquantum/core/circuit/circuit.py +++ b/mindquantum/circuit/circuit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +18,21 @@ from typing import List import copy import numpy as np -from rich.console import Console -import mindquantum.core.gates as G -from mindquantum.core.gates.basic import _check_gate_type -from mindquantum.core.parameterresolver import ParameterResolver as PR -from mindquantum.io import bprint, brick_model - -GateSeq = List[G.BasicGate] +from projectq.ops import QubitOperator as pq_operator +from openfermion.ops import QubitOperator as of_operator +from mindquantum.ops import QubitOperator as hiq_operator +from mindquantum.gate import BasicGate +from mindquantum.gate import I +from mindquantum.gate import X +from mindquantum.gate import Y +from mindquantum.gate import Z +from mindquantum.gate import Hamiltonian +import mindquantum.gate as G +from mindquantum.gate.basic import _check_gate_type +from mindquantum.utils import bprint +from mindquantum.parameterresolver import ParameterResolver as PR + +GateSeq = List[BasicGate] def _two_dim_array_to_list(data): @@ -68,15 +75,6 @@ def collect(self, keys): else: self.map[k] += 1 - def collect_only_one(self, keys, raise_msg): - """collect item only single time, otherwise raise error""" - if not isinstance(keys, list): - keys = [keys] - for k in keys: - if k in self.map: - raise ValueError(raise_msg) - self.map[k] = 1 - def delete(self, keys): """delete items""" if not isinstance(keys, list): @@ -114,13 +112,6 @@ def merge(self, other): else: self.map[k] = v - def merge_only_one(self, other, raise_msg): - """merge with other collection container""" - for k, _ in other.map.items(): - if k in self.map: - raise ValueError(raise_msg) - self.map[k] = 1 - def unmerge(self, other): """delete with other collection container""" for k, v in other.map.items(): @@ -158,12 +149,12 @@ class Circuit(list): Examples: - >>> from mindquantum import Circuit, RX, X >>> circuit1 = Circuit() >>> circuit1 += RX('a').on(0) >>> circuit1 *= 2 - >>> circuit1 - q0: ──RX(a)────RX(a)── + >>> print(circuit1) + RX(a|0) + RX(a|0) >>> circuit2 = Circuit([X.on(0,1)]) >>> circuit3= circuit1 + circuit2 >>> assert len(circuit3) == 3 @@ -174,53 +165,40 @@ class Circuit(list): |with 1 parameters are : a.| |Number qubit of circuit: 2 | ============================= - >>> circuit3 - q0: ──RX(a)────RX(a)────X── - │ - q1: ────────────────────●── """ def __init__(self, gates=None): list.__init__([]) self.all_qubits = CollectionMap() self.all_paras = CollectionMap() - self.all_measures = CollectionMap() if gates is not None: if isinstance(gates, Iterable): self.extend(gates) else: self.append(gates) - self.has_cpp_obj = False def append(self, gate): """Append a gate.""" _check_gate_type(gate) - if isinstance(gate, G.Measure): - self.all_measures.collect_only_one( - gate, f'measure key {gate.key} already exist.') + super().append(gate) self.all_qubits.collect(gate.obj_qubits) self.all_qubits.collect(gate.ctrl_qubits) - if gate.parameterized: + if gate.isparameter: self.all_paras.collect(list(gate.coeff.keys())) - super().append(gate) - self.has_cpp_obj = False def extend(self, gates): """Extend a circuit.""" if isinstance(gates, Circuit): - self.all_measures.merge_only_one(gates.all_measures, - "Measure already exist.") + super().extend(gates) self.all_qubits.merge(gates.all_qubits) self.all_paras.merge(gates.all_paras) - super().extend(gates) else: for gate in gates: self.append(gate) - self.has_cpp_obj = False def __add__(self, gates): out = Circuit() out.extend(self) - if isinstance(gates, G.BasicGate): + if isinstance(gates, BasicGate): out.append(gates) else: out.extend(gates) @@ -230,7 +208,7 @@ def __radd__(self, gates): return Circuit(gates) + self def __iadd__(self, gates): - if isinstance(gates, G.BasicGate): + if isinstance(gates, BasicGate): self.append(gates) elif isinstance(gates, Circuit): self.extend(gates) @@ -269,33 +247,19 @@ def __setitem__(self, k, v): old_v = self[k] self.all_qubits.delete(old_v.obj_qubits) self.all_qubits.delete(old_v.ctrl_qubits) - if old_v.parameterized: + if old_v.isparameter: self.all_paras.delete(list(old_v.coeff.keys())) - if isinstance(old_v, G.Measure): - self.all_measures.delete(old_v) super().__setitem__(k, v) self.all_qubits.collect(v.obj_qubits) self.all_qubits.collect(v.ctrl_qubits) - if v.parameterized: + if v.isparameter: self.all_paras.collect(list(v.coeff.keys())) - if isinstance(v, G.Measure): - self.all_measures.collect_only_one( - v, f'measure key {v.key} already exist.') - self.has_cpp_obj = False def __getitem__(self, sliced): if isinstance(sliced, int): return super().__getitem__(sliced) return Circuit(super().__getitem__(sliced)) - @property - def has_measure(self): - return self.all_measures.size != 0 - - @property - def parameterized(self): - return self.all_paras.size != 0 - def insert(self, index, gates): """ Insert a quantum gate or quantum circuit in index. @@ -304,30 +268,23 @@ def insert(self, index, gates): index (int): Index to set gate. gates (Union[BasicGate, list[BasicGate]]): Gates you need to insert. """ - if isinstance(gates, G.BasicGate): + if isinstance(gates, BasicGate): super().insert(index, gates) self.all_qubits.collect(gates.obj_qubits) self.all_qubits.collect(gates.ctrl_qubits) - if gates.parameterized: + if gates.isparameter: self.all_paras.collect(list(gates.coeff.keys())) - if isinstance(gates, G.Measure): - self.all_measures.collect_only_one( - gates, f'measure key {gates.key} already exist.') elif isinstance(gates, Iterable): for gate in gates[::-1]: _check_gate_type(gate) self.insert(index, gate) self.all_qubits.collect(gate.obj_qubits) self.all_qubits.collect(gate.ctrl_qubits) - if gate.parameterized: + if gate.isparameter: self.all_paras.collect(list(gate.coeff.keys())) - if isinstance(gate, G.Measure): - self.all_measures.collect_only_one( - gate, f'measure key {gate.key} already exist.') else: raise TypeError("Unsupported type for quantum gate: {}".format( type(gates))) - self.has_cpp_obj = False def no_grad(self): """ @@ -335,7 +292,6 @@ def no_grad(self): """ for gate in self: gate.no_grad() - self.has_cpp_obj = False return self def requires_grad(self): @@ -344,41 +300,17 @@ def requires_grad(self): """ for gate in self: gate.requires_grad() - self.has_cpp_obj = False return self def __str__(self): - return self.__repr__() + return '\n'.join(repr(i) for i in self) def __repr__(self): - from mindquantum.io.display._config import _CIRCUIT_STYLE - s = brick_model(self) - console = Console(record=True) - if not console.is_jupyter: - console.width = len(s) - with console.capture() as capture: - console.print(s, style=_CIRCUIT_STYLE['style'], width=len(s)) - s = capture.get() - return s - - def _repr_html_(self): - """repr for jupyter nontebook""" - from mindquantum.io.display._config import CIRCUIT_HTML_FORMAT - from mindquantum.io.display._config import _CIRCUIT_STYLE - console = Console(record=True) - s = brick_model(self) - console.width = len(s) - with console.capture() as _: - console.print(s, style=_CIRCUIT_STYLE['style'], width=len(s)) - s = console.export_html(code_format=CIRCUIT_HTML_FORMAT, - inline_styles=True) - return '\n'.join(s.split('\n')[1:]) + return self.__str__() @property def n_qubits(self): - if self.all_qubits: - return max(self.all_qubits.keys()) + 1 - return 0 + return max(self.all_qubits.keys()) + 1 def summary(self, show=True): """ @@ -404,7 +336,7 @@ def summary(self, show=True): self.num_non_para_gate = 0 self.num_para_gate = 0 for gate in self: - if gate.parameterized: + if gate.isparameter: self.num_para_gate += 1 else: self.num_non_para_gate += 1 @@ -422,13 +354,14 @@ def summary(self, show=True): for i in info: print(i) + @property def hermitian(self): """ Get the hermitian of this quantum circuit. Examples: >>> circ = Circuit(RX({'a': 0.2}).on(0)) - >>> herm_circ = circ.hermitian() + >>> herm_circ = circ.hermitian >>> herm_circ[0].coeff {'a': -0.2} """ @@ -453,7 +386,7 @@ def parameter_resolver(self): return pr @property - def params_name(self): + def para_name(self): """ Get the parameter name of this circuit. @@ -461,10 +394,10 @@ def params_name(self): list, a list that contains the parameter name. Examples: - >>> from mindquantum.core.gates import RX - >>> from mindquantum.core.circuit import Circuit + >>> from mindquantum.gate import RX + >>> from mindquantum.circuit import Circuit >>> circuit = Circuit(RX({'a': 1, 'b': 2}).on(0)) - >>> circuit.params_name + >>> circuit.para_name ['a', 'b'] """ return list(self.all_paras.keys()) @@ -480,8 +413,8 @@ def apply_value(self, pr): Circuit, a non parameterized circuit. Examples: - >>> from mindquantum.core.gates import X, RX - >>> from mindquantum.core.circuit import Circuit + >>> from mindquantum.gate import X, RX + >>> from mindquantum.circuit import Circuit >>> circuit = Circuit() >>> circuit += X.on(0) >>> circuit += RX({'a': 2}).on(0) @@ -492,10 +425,10 @@ def apply_value(self, pr): """ circuit = Circuit() for gate in self: - if not gate.parameterized: + if not gate.isparameter: circuit += gate else: - if set(gate.coeff.params_name).issubset(pr): + if set(gate.coeff.para_name).issubset(pr): coeff = gate.coeff.combination(pr) else: coeff = 1 * gate.coeff @@ -503,69 +436,6 @@ def apply_value(self, pr): gate.ctrl_qubits) return circuit - def remove_barrier(self): - """Remove all barrier gates""" - circ = Circuit() - for g in self: - if not isinstance(g, G.BarrierGate): - circ += g - return circ - - def remove_measure(self): - """Remove all measure gate.""" - circ = Circuit() - for g in self: - if not isinstance(g, G.Measure): - circ += g - return circ - - def remove_measure_on_qubits(self, qubits): - """ - Remove all measure gate on some certain qubits. - - Args: - qubit (Union[int, List[int]]): The qubits you want to remove measure. - - Examples: - >>> from mindquantum import UN, H, Measure - >>> circ = UN(H, 3).measure_all() - >>> circ += H.on(0) - >>> circ += Measure('q0_1').on(0) - >>> circ.remove_measure_on_qubits(0) - q0: ──H──────H──── - - q1: ──H────M(q1)── - - q2: ──H────M(q2)── - """ - if not isinstance(qubits, list): - qubits = [qubits] - circ = Circuit() - for gate in self: - if isinstance(gate, G.Measure) and gate.obj_qubits[0] in qubits: - continue - circ += gate - return circ - - def get_cpp_obj(self, hermitian=False): - """Get cpp obj of circuit.""" - if not self.has_cpp_obj: - self.has_cpp_obj = True - self.cpp_obj = [ - i.get_cpp_obj() for i in self - if not isinstance(i, G.BarrierGate) - ] - self.herm_cpp_obj = [ - i.get_cpp_obj() for i in self.hermitian() - if not isinstance(i, G.BarrierGate) - ] - - if hasattr(self, 'cpp_obj') and hasattr(self, 'herm_cpp_obj'): - if hermitian: - return self.herm_cpp_obj - return self.cpp_obj - raise ValueError("Circuit does not generate cpp obj yet.") - def mindspore_data(self): """ Serialize the circuit. The result can be used by QNN operators. @@ -580,7 +450,7 @@ def mindspore_data(self): 'gate_requires_grad': [] } for gate in self: - if gate.parameterized: + if gate.isparameter: m_data['gate_names'].append(gate.name) m_data['gate_matrix'].append([[["0.0", "0.0"], ["0.0", "0.0"]], [["0.0", "0.0"], ["0.0", @@ -663,40 +533,39 @@ def zz(self, para, obj_qubits, ctrl_qubits=None): self.append(G.ZZ(para).on(obj_qubits, ctrl_qubits)) return self - def measure(self, key, obj_qubit=None): - """Add a measure gate.""" - if obj_qubit is None: - self.append(G.Measure().on(key)) - else: - self.append(G.Measure(key).on(obj_qubit)) - return self - - def measure_all(self, subfix=None): - """Measure all qubits""" - for i in range(self.n_qubits): - s = f"q{i}" if subfix is None else f"q{i}_{subfix}" - self += G.Measure(s).on(i) - return self - - def barrier(self, show=True): - """Add a barrier.""" - self.append(G.BarrierGate(show)) - return self - def un(self, gate, maps_obj, maps_ctrl=None): - """ - Map a quantum gate to different objective qubits and control qubits. - Please refers to UN. - """ - from mindquantum import UN - self += UN(gate, maps_obj, maps_ctrl) - return self +def pauli_word_to_circuits(qubitops): + """ + Convert a single pauli word qubit operator to a quantum circuit. - def get_qs(self, backend='projectq', pr=None, ket=False, seed=42): - from mindquantum import Simulator - sim = Simulator(backend, self.n_qubits, seed) - sim.apply_circuit(self, pr) - return sim.get_qs(ket) + Args: + qubitops (QubitOperator, Hamiltonian): The single pauli word qubit operator. + Returns: + Circuit, a quantum circuit. -__all__ = ['Circuit'] + Examples: + >>> from mindquantum.ops import QubitOperator + >>> qubitops = QubitOperator('X0 Y1') + >>> pauli_word_to_circuits(qubitops) + X(0) + Y(1) + """ + if not isinstance(qubitops, + (pq_operator, of_operator, hiq_operator, Hamiltonian)): + raise TypeError( + "Require a QubitOperator or a Hamiltonian, but get {}!".format( + type(qubitops))) + if isinstance(qubitops, Hamiltonian): + qubitops = qubitops.hamiltonian + if len(qubitops.terms) > 1: + raise Exception("Onle work for QubitOperator with single pauliword!") + gate_map = {'X': X, 'Y': Y, 'Z': Z} + for ops in qubitops.terms.keys(): + circ = Circuit() + if ops: + for ind, single_op in ops: + circ += gate_map[single_op].on(ind) + else: + circ += I.on(0) + return circ diff --git a/mindquantum/core/circuit/utils.py b/mindquantum/circuit/high_level_ops.py similarity index 66% rename from mindquantum/core/circuit/utils.py rename to mindquantum/circuit/high_level_ops.py index c702b1a1e..3d97ed9da 100644 --- a/mindquantum/core/circuit/utils.py +++ b/mindquantum/circuit/high_level_ops.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,137 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""Tools for MindQuantum eDSL""" +"""High level circuit operators.""" + from types import FunctionType, MethodType import copy -import numpy as np -from projectq.ops import QubitOperator as pq_operator -from openfermion.ops import QubitOperator as of_operator - - -def decompose_single_term_time_evolution(term, para): - """ - Decompose a time evolution gate into basic quantum gates. - - This function only work for the hamiltonian with only single pauli word. - For example, exp(-i * t * ham), ham can only be a single pauli word, such - as ham = X0 x Y1 x Z2, and at this time, term will be - ((0, 'X'), (1, 'Y'), (2, 'Z')). When the evolution time is expressd as - t = a*x + b*y, para would be {'x':a, 'y':b}. - Args: - term (tuple, QubitOperator): the hamiltonian term of just the - evolution qubit operator. - para (Union[dict, numbers.Number]): the parameters of evolution operator. - - Returns: - Circuit, a quantum circuit. - - Example: - >>> from mindquantum.core.operators import QubitOperator - >>> ham = QubitOperator('X0 Y1') - >>> circuit = decompose_single_term_time_evolution(ham, {'a':1}) - >>> print(circuit) - H(0) - RX(1.571,1) - X(1 <-: 0) - RZ(a|1) - X(1 <-: 0) - RX(10.996,1) - H(0) - """ - from mindquantum import gates as G - from mindquantum.core.circuit import Circuit - from mindquantum.core.parameterresolver import ParameterResolver as PR - if not isinstance(term, tuple): - try: - if len(term.terms) != 1: - raise ValueError("Only work for single term time \ - evolution operator, but get {}".format(len(term))) - term = list(term.terms.keys())[0] - except TypeError: - raise Exception("Not supported type:{}".format(type(term))) - - out = [] - term = sorted(term) - rxs = [] - if len(term) == 1: # single pauli operator - if term[0][1] == 'X': - out.append(G.RX(para).on(term[0][0])) - elif term[0][1] == 'Y': - out.append(G.RY(para).on(term[0][0])) - else: - out.append(G.RZ(para).on(term[0][0])) - else: - for index, action in term: - if action == 'X': - out.append(G.H.on(index)) - elif action == 'Y': - rxs.append(len(out)) - out.append(G.RX(np.pi / 2).on(index)) - - out.append(G.BarrierGate(False)) - for i in range(len(term) - 1): - out.append(G.X.on(term[i + 1][0], term[i][0])) - out.append(G.BarrierGate(False)) - if isinstance(para, (dict, PR)): - out.append( - G.RZ({i: 2 * j - for i, j in para.items()}).on(term[-1][0])) - else: - out.append(G.RZ(2 * para).on(term[-1][0])) - for i in range(len(out) - 1)[::-1]: - if i in rxs: - out.append(G.RX(np.pi * 3.5).on(out[i].obj_qubits)) - else: - out.append(out[i]) - return Circuit(out) - - -def pauli_word_to_circuits(qubitops): - """ - Convert a single pauli word qubit operator to a quantum circuit. - - Args: - qubitops (QubitOperator, Hamiltonian): The single pauli word qubit operator. - - Returns: - Circuit, a quantum circuit. - - Examples: - >>> from mindquantum.core.operators import QubitOperator - >>> qubitops = QubitOperator('X0 Y1') - >>> pauli_word_to_circuits(qubitops) - X(0) - Y(1) - """ - from mindquantum import gates as G - from mindquantum import operators as ops - from mindquantum.core import Circuit - allow_ops = (pq_operator, of_operator, ops.QubitOperator, ops.Hamiltonian) - if not isinstance(qubitops, allow_ops): - raise TypeError( - "Require a QubitOperator or a Hamiltonian, but get {}!".format( - type(qubitops))) - if isinstance(qubitops, ops.Hamiltonian): - qubitops = qubitops.hamiltonian - if len(qubitops.terms) > 1: - raise Exception("Onle work for QubitOperator with single pauliword!") - gate_map = {'X': G.X, 'Y': G.Y, 'Z': G.Z} - for ops in qubitops.terms.keys(): - circ = Circuit() - if ops: - for ind, single_op in ops: - circ += gate_map[single_op].on(ind) - else: - circ += G.I.on(0) - return circ +from mindquantum.circuit import Circuit +from mindquantum.parameterresolver import ParameterResolver as PR def _add_ctrl_qubits(circ, ctrl_qubits): """Add control qubits on a circuit.""" - from mindquantum.core import Circuit - from mindquantum import gates as G if not isinstance(ctrl_qubits, (int, list)): raise TypeError( "Require a int or a list of int for ctrl_qubits, but get {}!". @@ -167,8 +46,7 @@ def _add_ctrl_qubits(circ, ctrl_qubits): curr_ctrl = list(curr_ctrl.union(ctrl_qubits)) curr_ctrl.sort() new_gate = copy.deepcopy(gate) - if not isinstance(gate, (G.Measure, G.BarrierGate)): - new_gate.ctrl_qubits = curr_ctrl + new_gate.ctrl_qubits = curr_ctrl new_gate.generate_description() circ_out += new_gate return circ_out @@ -184,7 +62,7 @@ def controlled(circuit_fn): or a function that can generate a quantum circuit. Examples: - >>> from mindquantum.core.circuit import qft, controlled + >>> from mindquantum.circuit import qft, controlled >>> u1 = qft([0, 1]) >>> u2 = controlled(u1) >>> u3 = controlled(qft) @@ -200,7 +78,6 @@ def controlled(circuit_fn): H(1 <-: 2) SWAP(0 1 <-: 2) """ - from mindquantum.core import Circuit if isinstance(circuit_fn, (FunctionType, MethodType)): def wrapper(ctrl_qubits, *arg, **keywords): @@ -226,7 +103,7 @@ def dagger(circuit_fn): or a function that can generate a quantum circuit. Examples: - >>> from mindquantum.core.circuit import qft, dagger + >>> from mindquantum.circuit import qft, dagger >>> u1 = qft([0, 1]) >>> u2 = dagger(u1) >>> u3 = dagger(qft) @@ -242,25 +119,23 @@ def dagger(circuit_fn): PS(-1.571|0 <-: 1) H(0) """ - from mindquantum.core import Circuit if isinstance(circuit_fn, (FunctionType, MethodType)): def wrapper(*arg, **keywords): circ = circuit_fn(*arg, **keywords) if not isinstance(circ, Circuit): return dagger(circ) - return circ.hermitian() + return circ.hermitian return wrapper if isinstance(circuit_fn, Circuit): - return circuit_fn.hermitian() + return circuit_fn.hermitian raise TypeError( "Input need a circuit or a function that can generate a circuit.") def _apply_circuit(circ, qubits): """Apply a circuit to other different qubits.""" - from mindquantum.core import Circuit old_qubits = set([]) for g in circ: old_qubits.update(g.obj_qubits) @@ -293,7 +168,7 @@ def apply(circuit_fn, qubits): qubits (list[int]): The new qubits that you want to apply. Examples: - >>> from mindquantum.core.circuit import qft, apply + >>> from mindquantum.circuit import qft, apply >>> u1 = qft([0, 1]) >>> u2 = apply(u1, [1, 2]) >>> u3 = apply(qft, [1, 2]) @@ -309,7 +184,6 @@ def apply(circuit_fn, qubits): H(2) SWAP(1 2) """ - from mindquantum.core import Circuit if not isinstance(qubits, list): raise TypeError(f"New qubits need a list, but get {type(qubits)}!") if len(qubits) > 1: @@ -335,12 +209,10 @@ def wrapper(*arg, **keywords): def _add_prefix(circ, prefix): """Add prefix to every parameters in circuit.""" - from mindquantum.core import Circuit - from mindquantum.core import ParameterResolver as PR out = Circuit() for g in circ: g = copy.deepcopy(g) - if g.parameterized: + if g.isparameter: pr = PR() for k, v in g.coeff.items(): pr[f'{prefix}_{k}'] = v @@ -361,7 +233,7 @@ def add_prefix(circuit_fn, prefix): prefix (str): The prefix you want to add to every parameters. Examples: - >>> from mindquantum.core.circuit import qft, add_prefix + >>> from mindquantum.circuit import qft, add_prefix >>> from mindquantum import RX, H, Circuit >>> u = lambda qubit: Circuit([H.on(0), RX('a').on(qubit)]) >>> u1 = u(0) @@ -375,7 +247,6 @@ def add_prefix(circuit_fn, prefix): H(0) RX(ansatz_a|0) """ - from mindquantum.core import Circuit if not isinstance(prefix, str): raise TypeError(f"prefix need string, but get {type(prefix)}") if isinstance(circuit_fn, (FunctionType, MethodType)): @@ -395,12 +266,10 @@ def wrapper(*arg, **keywords): def _change_param_name(circ, name_map): """Change the parameter of circuit according to the name map.""" - from mindquantum.core import Circuit - from mindquantum.core import ParameterResolver as PR out = Circuit() for g in circ: g = copy.deepcopy(g) - if g.parameterized: + if g.isparameter: pr = PR() for k, v in g.coeff.items(): if k not in name_map: @@ -423,7 +292,7 @@ def change_param_name(circuit_fn, name_map): name_map (dict): The parameter name mapping dict. Examples: - >>> from mindquantum.core.circuit import qft, change_param_name + >>> from mindquantum.circuit import qft, change_param_name >>> from mindquantum import RX, H, Circuit >>> u = lambda qubit: Circuit([H.on(0), RX('a').on(qubit)]) >>> u1 = u(0) @@ -437,7 +306,6 @@ def change_param_name(circuit_fn, name_map): H(0) RX(b|0) """ - from mindquantum.core import Circuit if not isinstance(name_map, dict): raise TypeError( f"Parameters name map need map, but get {type(name_map)}") diff --git a/mindquantum/circuit/module_circuit.py b/mindquantum/circuit/module_circuit.py new file mode 100644 index 000000000..d27c1cfdd --- /dev/null +++ b/mindquantum/circuit/module_circuit.py @@ -0,0 +1,183 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Module circuit""" + +from collections.abc import Iterable +import numpy as np +from mindquantum.gate import ParameterGate +from mindquantum.gate import SWAP +from mindquantum.gate.basic import _check_gate_type +from .circuit import Circuit + + +def _is_parameterized_gate_class(gate_class): + if not hasattr(gate_class, 'isparameter') and issubclass( + gate_class, ParameterGate): + return True + return False + + +class UN(Circuit): + """ + Map a quantum gate to different objective qubits and control qubits. + + Args: + gate (BasicGate): A quantum gate. + maps_obj (Union[int, list[int]]): Objective qubits. If a int is given and maps_obj is None, + then the gate will act on qubit from 0 to this int. + coeff (Union[str, List[str], numbers.Number, List[numbers.Number]]): The parameters for + gate (if it is a parameterized gate). + maps_ctrl (Union[int, list[int]]): Control qubits. Default: None. + + Returns: + Circuit, Return a quantum circuit. + + Examples: + >>> from mindquantum import UN, RX, H, X, SWAP + >>> circuit1 = UN(X, maps_obj = [0, 1], maps_ctrl = [2, 3]) + >>> print(circuit1) + X(0 <-: 2) + X(1 <-: 3) + >>> circuit2 = UN(SWAP, maps_obj =[[0, 1], [2, 3]]) + >>> print(circuit2) + SWAP(0 1) + SWAP(2 3) + >>> circuit3 = UN(H, 3) + >>> print(circuit3) + H(0) + H(1) + H(2) + >>> circuit4 = UN(RX, 2, 'a') + >>> print(circuit4) + RX(a_0|0) + RX(a_1|1) + >>> circuit5 = UN(RX, 2, ['a', 'b']) + >>> print(circuit5) + RX(a|0) + RX(b|1) + >>> circuit6 = UN(RX('a'), [0, 3]) + >>> print(circuit6) + RX(a|0) + RX(a|3) + """ + def __init__(self, gate, maps_obj, coeff=None, maps_ctrl=None): + self._check_gate_and_coeff(gate, coeff) + objs, ctrls = self._get_objs_ctrls(maps_obj, maps_ctrl) + if isinstance(coeff, (str, int, float)): + if isinstance(coeff, str): + coeff = [f'{coeff}_{i}' for i, _ in enumerate(objs)] + else: + coeff = [coeff for i in objs] + elif isinstance(coeff, Iterable): + if len(coeff) != len(objs): + raise ValueError( + f"coeff size of correct, need {len(objs)}, but get {len(coeff)}" + ) + if _is_parameterized_gate_class(gate): + gates = [gate(i).on(j, k) for i, j, k in zip(coeff, objs, ctrls)] + else: + gates = [gate.on(i, j) for i, j in zip(objs, ctrls)] + Circuit.__init__(self, gates) + + def _check_gate_and_coeff(self, gate, coeff): + _check_gate_type(gate) + if _is_parameterized_gate_class(gate) and coeff is None: + raise ValueError( + "Given a parameterized gate without coeff specified") + if not _is_parameterized_gate_class(gate) and not coeff is None: + raise ValueError("Non parameterized gate do not need coeff") + + def _get_objs_ctrls(self, maps_obj, maps_ctrl): + """get objs and ctrls""" + if isinstance(maps_obj, int): + if isinstance(maps_ctrl, int): + raise ValueError( + "You do not need UN for both obj and ctrl qubit are single value" + ) + objs = range(maps_obj) + ctrls = [None for i in objs] + elif isinstance(maps_obj, Iterable): + objs = [i for i in maps_obj] + if maps_ctrl is None: + ctrls = [None for i in objs] + elif isinstance(maps_ctrl, Iterable): + if len(maps_ctrl) != len(maps_obj): + raise ValueError( + "size of obj qubits and ctrl qubits not match") + ctrls = [i for i in maps_ctrl] + else: + raise ValueError( + f"maps_ctrl need a Iterable type, but get {type(maps_ctrl)}" + ) + else: + raise ValueError( + f"maps_obj need a Iterable or a int, but get {type(maps_obj)}") + return objs, ctrls + + +class SwapParts(Circuit): + """ + Swap two different part of quantum circuit, with or without control qubits. + + Args: + a (Iterable): The first part you need to swap. + b (Iterable): The second part you need to swap. + maps_ctrl (int, Iterable): Control the swap by a single qubit or by + different qubits or just no control qubit. Default: None. + + Examples: + >>> from mindquantum import SwapParts + >>> SwapParts([1, 2], [3, 4], 0) + SWAP(1 3 <-: 0) + SWAP(2 4 <-: 0) + """ + def __init__(self, a: Iterable, b: Iterable, maps_ctrl=None): + if not isinstance(a, Iterable) or not isinstance(b, Iterable): + raise Exception("Swap part should be iterable!") + maps = [[a[i], b[i]] for i in range(len(a))] + if isinstance(maps_ctrl, int): + maps_ctrl = [maps_ctrl for _ in maps] + Circuit.__init__(self, UN(SWAP, maps, maps_ctrl=maps_ctrl)) + + +class U3(Circuit): + """ + This circuit represent arbitrary single qubit gate. + + Args: + a (Union[numbers.Number, dict, ParameterResolver]): First parameter for U3 circuit. + b (Union[numbers.Number, dict, ParameterResolver]): Second parameter for U3 circuit. + c (Union[numbers.Number, dict, ParameterResolver]): Third parameter for U3 circuit. + obj_qubit (int): Which qubit the U3 circuit will act on. Default: None. + + Examples: + >>> from mindquantum import U3 + >>> U3('a','b','c') + RZ(a|0) + RX(-1.571|0) + RZ(b|0) + RX(1.571|0) + RZ(c|0) + """ + def __init__(self, a, b, c, obj_qubit=None): + if obj_qubit is None: + obj_qubit = 0 + circ = Circuit() + circ.rz(a, obj_qubit) + circ.rx(-np.pi / 2, obj_qubit) + circ.rz(b, obj_qubit) + circ.rx(np.pi / 2, obj_qubit) + circ.rz(c, obj_qubit) + Circuit.__init__(self, circ) diff --git a/mindquantum/algorithm/library/quantum_fourier.py b/mindquantum/circuit/quantum_fourier.py similarity index 87% rename from mindquantum/algorithm/library/quantum_fourier.py rename to mindquantum/circuit/quantum_fourier.py index 4dc8c69c6..5f7fd613f 100644 --- a/mindquantum/algorithm/library/quantum_fourier.py +++ b/mindquantum/circuit/quantum_fourier.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +15,8 @@ """Quantum fourier transform.""" import numpy as np -from mindquantum.core.gates import H, PhaseShift -from mindquantum.core.circuit import SwapParts, Circuit +from mindquantum.gate import H, PhaseShift +from mindquantum.circuit import SwapParts, Circuit def _rn(k): @@ -42,8 +41,8 @@ def qft(qubits): qubits (list[int]): Qubits you want to apply quantum fourier transform. Examples: - >>> from mindquantum.core.circuit import qft - >>> from mindquantum.core.circuit import StateEvolution + >>> from mindquantum.circuit import qft + >>> from mindquantum.circuit import StateEvolution >>> print(StateEvolution(qft([0, 1])).final_state(ket=True)) 0.5¦00⟩ 0.5¦01⟩ diff --git a/mindquantum/circuit/state_evolution.py b/mindquantum/circuit/state_evolution.py new file mode 100644 index 000000000..158cee073 --- /dev/null +++ b/mindquantum/circuit/state_evolution.py @@ -0,0 +1,130 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Evaluate a quantum circuit.""" + +from collections import Counter +import numpy as np +import matplotlib.pyplot as plt + +from mindspore import Tensor +from mindquantum.parameterresolver import ParameterResolver as PR +from mindquantum.nn import generate_evolution_operator +from mindquantum.utils import normalize +from mindquantum.utils import ket_string +from mindquantum.circuit import Circuit + + +def _generate_n_qubits_index(n_qubits): + out = [] + for i in range(1 << n_qubits): + out.append(bin(i)[2:].zfill(n_qubits)) + return out + + +class StateEvolution: + """ + Calculate the final state of a parameterized or non parameterized quantum circuit. + + Args: + circuit (Circuit): The circuit that you want to do evolution. + + Examples: + >>> from mindquantum.circuit import StateEvolution + >>> from mindquantum.circuit import qft + >>> print(StateEvolution(qft([0, 1])).final_state(ket=True)) + 0.5¦00⟩ + 0.5¦01⟩ + 0.5¦10⟩ + 0.5¦11⟩ + """ + def __init__(self, circuit): + if not isinstance(circuit, Circuit): + raise TypeError( + f'Input circuit should be a quantum circuit, but get {type(circuit)}' + ) + self.circuit = circuit + self.evol = generate_evolution_operator(self.circuit) + self.index = _generate_n_qubits_index(self.circuit.n_qubits) + + def final_state(self, param=None, ket=False): + """ + Get the final state of the input quantum circuit. + + Args: + param (Union[Tensor, numpy.ndarray, ParameterResolver, dict]): The + parameter for the parameterized quantum circuit. If None, the + quantum circuit should be a non parameterized quantum circuit. + Default: None. + ket (bool): Whether to print the final state in ket format. Default: False. + + Returns: + numpy.ndarray, the final state in numpy array format. + """ + if param is None: + if self.circuit.para_name: + raise ValueError( + "Require a non parameterized quantum circuit, since not parameters specified." + ) + return self.evol() if not ket else '\n'.join( + ket_string(self.evol())) + if isinstance(param, np.ndarray): + return self.evol(Tensor(param)) if not ket else '\n'.join( + ket_string(self.evol(Tensor(param)))) + if isinstance(param, Tensor): + return self.evol(param) if not ket else '\n'.join( + ket_string(self.evol(param))) + if isinstance(param, (PR, dict)): + data = [param[i] for i in self.circuit.para_name] + data = Tensor(np.array(data).astype(np.float32)) + return self.evol(data) if not ket else '\n'.join( + ket_string(self.evol(data))) + raise TypeError( + f"parameter requires a numpy array or a ParameterResolver or a dict, ut get {type(param)}" + ) + + def sampling(self, shots=1, param=None, show=False): + """ + Sampling the bit string based on the final state. + + Args: + shots (int): How many samples you want to get. Default: 1. + param (Union[Tensor, numpy.ndarray, ParameterResolver, dict]): The + parameter for the parameterized quantum circuit. If None, the + quantum circuit should be a non parameterized quantum circuit. + Default: None. + show (bool): Whether to show the sampling result in bar plot. Default: False. + + Returns: + dict, a dict with key as bit string and value as number of samples. + + Examples: + >>> from mindquantum.circuit import StateEvolution + >>> from mindquantum.circuit import qft + >>> import numpy as np + >>> np.random.seed(42) + >>> StateEvolution(qft([0, 1])).sampling(100) + {'00': 29, '01': 24, '10': 23, '11': 24} + """ + final_state = self.final_state(param) + amps = normalize(np.abs(final_state)**2)**2 + sampling = Counter(np.random.choice(self.index, p=amps, size=shots)) + result = dict(zip(self.index, [0] * len(self.index))) + result.update(sampling) + if show: + plt.bar(result.keys(), result.values()) + if self.circuit.n_qubits > 2: + plt.xticks(rotation=45) + plt.show() + return result diff --git a/mindquantum/core/operators/time_evolution.py b/mindquantum/circuit/time_evolution.py similarity index 76% rename from mindquantum/core/operators/time_evolution.py rename to mindquantum/circuit/time_evolution.py index cbd991ff1..8fbed0dea 100644 --- a/mindquantum/core/operators/time_evolution.py +++ b/mindquantum/circuit/time_evolution.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,12 +14,12 @@ # ============================================================================ """Circuit for time evolution.""" -from mindquantum.core.operators import QubitOperator -from mindquantum.core.parameterresolver import ParameterResolver -from mindquantum.core.circuit.utils import decompose_single_term_time_evolution +from mindquantum.ops import QubitOperator +from mindquantum.parameterresolver import ParameterResolver +from .circuit import Circuit +from .uccsd import decompose_single_term_time_evolution -#TODO: 🔥🔥🔥🔥🔥《含时演化算符教程》↪️编写含时演化算符的使用教程,介绍Trotter分解,介绍该模块的应用 class TimeEvolution: r""" The time evolution operator that can generate a crosponded circuit. @@ -44,12 +43,13 @@ class TimeEvolution: Default: None. Examples: - >>> from mindquantum.core.operators import TimeEvolution, QubitOperator + >>> from mindquantum.circuit import TimeEvolution + >>> from mindquantum.ops import QubitOperator >>> h = QubitOperator('Z0 Z1', 'p') >>> TimeEvolution(h).circuit - q0: ──●───────────────●── - │ │ - q1: ──X────RZ(2*p)────X── + X(1 <-: 0) + RZ(2*p|1) + X(1 <-: 0) """ def __init__(self, ops: QubitOperator, time=None): if time is None: @@ -62,7 +62,6 @@ def __init__(self, ops: QubitOperator, time=None): @property def circuit(self): """Get the first order trotter decomposition circuit of this time evolution operator.""" - from ..circuit import Circuit circ = Circuit() for k, v in self.ops.terms.items(): pr_tmp = self.time * v diff --git a/mindquantum/algorithm/nisq/chem/uccsd.py b/mindquantum/circuit/uccsd.py similarity index 75% rename from mindquantum/algorithm/nisq/chem/uccsd.py rename to mindquantum/circuit/uccsd.py index c7346bd40..5af6565b9 100644 --- a/mindquantum/algorithm/nisq/chem/uccsd.py +++ b/mindquantum/circuit/uccsd.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +18,19 @@ import itertools import numpy as np from openfermion.chem import MolecularData -from mindquantum.core.operators import FermionOperator -from mindquantum.core.operators.utils import down_index, up_index, get_fermion_operator -from mindquantum.algorithm.nisq.chem.transform import Transform +from mindquantum.ops import FermionOperator +from mindquantum.utils import down_index +from mindquantum.utils import up_index +from mindquantum.utils import get_fermion_operator +from mindquantum.hiqfermion.transforms import Transform from mindquantum.third_party.interaction_operator import InteractionOperator -from mindquantum.core.circuit.utils import decompose_single_term_time_evolution -from mindquantum.core.circuit import Circuit +from mindquantum.gate import RX +from mindquantum.gate import H +from mindquantum.gate import X +from mindquantum.gate import RZ +from mindquantum.gate import RY +from mindquantum.parameterresolver import ParameterResolver +from .circuit import Circuit def _para_uccsd_singlet_generator(mol, th=0): @@ -176,6 +182,78 @@ def _pauli2circuit(pauli_ansatz): return circuit +def decompose_single_term_time_evolution(term, para): + """ + Decompose a time evolution gate into basic quantum gates. + + This function only work for the hamiltonian with only single pauli word. + For example, exp(-i * t * ham), ham can only be a single pauli word, such + as ham = X0 x Y1 x Z2, and at this time, term will be + ((0, 'X'), (1, 'Y'), (2, 'Z')). When the evolution time is expressd as + t = a*x + b*y, para would be {'x':a, 'y':b}. + + Args: + term (tuple, QubitOperator): the hamiltonian term of just the + evolution qubit operator. + para (Union[dict, numbers.Number]): the parameters of evolution operator. + + Returns: + Circuit, a quantum circuit. + + Example: + >>> from mindquantum.ops import QubitOperator + >>> ham = QubitOperator('X0 Y1') + >>> circuit = decompose_single_term_time_evolution(ham, {'a':1}) + >>> print(circuit) + H(0) + RX(1.571,1) + X(1 <-: 0) + RZ(a|1) + X(1 <-: 0) + RX(10.996,1) + H(0) + """ + if not isinstance(term, tuple): + try: + if len(term.terms) != 1: + raise ValueError("Only work for single term time \ + evolution operator, but get {}".format(len(term))) + term = list(term.terms.keys())[0] + except TypeError: + raise Exception("Not supported type:{}".format(type(term))) + + out = [] + term = sorted(term) + rxs = [] + if len(term) == 1: # single pauli operator + if term[0][1] == 'X': + out.append(RX(para).on(term[0][0])) + elif term[0][1] == 'Y': + out.append(RY(para).on(term[0][0])) + else: + out.append(RZ(para).on(term[0][0])) + else: + for index, action in term: + if action == 'X': + out.append(H.on(index)) + elif action == 'Y': + rxs.append(len(out)) + out.append(RX(np.pi / 2).on(index)) + + for i in range(len(term) - 1): + out.append(X.on(term[i + 1][0], term[i][0])) + if isinstance(para, (dict, ParameterResolver)): + out.append(RZ({i: 2 * j for i, j in para.items()}).on(term[-1][0])) + else: + out.append(RZ(2 * para).on(term[-1][0])) + for i in range(len(out) - 1)[::-1]: + if i in rxs: + out.append(RX(np.pi * 3.5).on(out[i].obj_qubits)) + else: + out.append(out[i]) + return Circuit(out) + + def generate_uccsd(molecular, th=0): """ Generate a uccsd quantum circuit based on a molecular data generated by diff --git a/mindquantum/core/__init__.py b/mindquantum/core/__init__.py deleted file mode 100644 index 7934cb02b..000000000 --- a/mindquantum/core/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""MindQuantum core features (eDSL)""" - -from . import circuit -from . import gates -from . import operators -from . import parameterresolver -from . import third_party -from .circuit import * -from .gates import * -from .operators import * -from .parameterresolver import * -from .third_party import * - -# Provide alias for convenience -from . import operators as ops - -__all__ = [] -__all__.extend(circuit.__all__) -__all__.extend(gates.__all__) -__all__.extend(operators.__all__) -__all__.extend(parameterresolver.__all__) -__all__.extend(third_party.__all__) -__all__.sort() diff --git a/mindquantum/core/circuit/__init__.py b/mindquantum/core/circuit/__init__.py deleted file mode 100644 index c057604c9..000000000 --- a/mindquantum/core/circuit/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -""" -Circuit. - -Quantum circuit module. -""" - -from .circuit import Circuit -from .module_circuit import UN, SwapParts, U3 -from .utils import decompose_single_term_time_evolution -from .utils import pauli_word_to_circuits -from .utils import controlled -from .utils import dagger -from .utils import apply -from .utils import add_prefix -from .utils import change_param_name -from .utils import C -from .utils import A -from .utils import D -from .utils import AP -from .utils import CPN - -__all__ = [ - 'Circuit', 'U3', 'UN', 'SwapParts', 'C', 'A', 'D', 'AP', 'CPN', - 'decompose_single_term_time_evolution', 'pauli_word_to_circuits', - 'controlled', 'dagger', 'apply', 'add_prefix', 'change_param_name' -] -__all__.sort() diff --git a/mindquantum/core/circuit/module_circuit.py b/mindquantum/core/circuit/module_circuit.py deleted file mode 100644 index 92b14d97e..000000000 --- a/mindquantum/core/circuit/module_circuit.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Module circuit""" - -from collections.abc import Iterable -import numpy as np -from mindquantum.core.gates import BasicGate -from mindquantum.core.gates import SWAP -from mindquantum.core.gates.basic import _check_gate_type -from .circuit import Circuit - - -class UN(Circuit): - """ - Map a quantum gate to different objective qubits and control qubits. - - Args: - gate (BasicGate): A quantum gate. - maps_obj (Union[int, list[int]]): Objective qubits. - maps_ctrl (Union[int, list[int]]): Control qubits. Default: None. - - Returns: - Circuit, Return a quantum circuit. - - Examples: - >>> from mindquantum import UN - >>> circuit1 = UN(X, maps_obj = [0, 1], maps_ctrl = [2, 3]) - >>> print(circuit1) - X(0 <-: 2) - X(1 <-: 3) - >>> circuit2 = UN(SWAP, maps_obj =[[0, 1], [2, 3]]) - >>> print(circuit2) - SWAP(0 1) - SWAP(2 3) - """ - def __init__(self, gate: BasicGate, maps_obj, maps_ctrl=None): - _check_gate_type(gate) - if isinstance(maps_obj, Iterable): - if maps_ctrl is None: - gates = [gate.on(i) for i in maps_obj] - else: - if isinstance(maps_ctrl, Iterable): - gates = [ - gate.on(maps_obj[i], maps_ctrl[i]) - for i in range(len(maps_obj)) - ] - else: - gates = [gate.on(i, maps_ctrl) for i in maps_obj] - else: - if maps_ctrl is None: - gates = [gate.on(i) for i in range(maps_obj)] - else: - if isinstance(maps_ctrl, Iterable): - gates = [gate.on(maps_obj, i) for i in maps_ctrl] - else: - - gates = [gate.on(maps_obj, maps_ctrl)] - Circuit.__init__(self, gates) - - -class SwapParts(Circuit): - """ - Swap two different part of quantum circuit, with or without control qubits. - - Args: - a (Iterable): The first part you need to swap. - b (Iterable): The second part you need to swap. - maps_ctrl (int, Iterable): Control the swap by a single qubit or by - different qubits or just no control qubit. Default: None. - - Examples: - >>> from mindquantum import SwapParts - >>> SwapParts([1, 2], [3, 4], 0) - SWAP(1 3 <-: 0) - SWAP(2 4 <-: 0) - """ - def __init__(self, a: Iterable, b: Iterable, maps_ctrl=None): - if not isinstance(a, Iterable) or not isinstance(b, Iterable): - raise Exception("Swap part should be iterable!") - maps = [[a[i], b[i]] for i in range(len(a))] - Circuit.__init__(self, UN(SWAP, maps, maps_ctrl)) - - -class U3(Circuit): - """ - This circuit represent arbitrary single qubit gate. - - Args: - a (Union[numbers.Number, dict, ParameterResolver]): First parameter for U3 circuit. - b (Union[numbers.Number, dict, ParameterResolver]): Second parameter for U3 circuit. - c (Union[numbers.Number, dict, ParameterResolver]): Third parameter for U3 circuit. - obj_qubit (int): Which qubit the U3 circuit will act on. Default: None. - - Examples: - >>> from mindquantum import U3 - >>> U3('a','b','c') - RZ(a|0) - RX(-1.571|0) - RZ(b|0) - RX(1.571|0) - RZ(c|0) - """ - def __init__(self, a, b, c, obj_qubit=None): - if obj_qubit is None: - obj_qubit = 0 - circ = Circuit() - circ.rz(a, obj_qubit) - circ.rx(-np.pi / 2, obj_qubit) - circ.rz(b, obj_qubit) - circ.rx(np.pi / 2, obj_qubit) - circ.rz(c, obj_qubit) - Circuit.__init__(self, circ) diff --git a/mindquantum/core/gates/measurement.py b/mindquantum/core/gates/measurement.py deleted file mode 100644 index 2fd1cedc7..000000000 --- a/mindquantum/core/gates/measurement.py +++ /dev/null @@ -1,332 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Basic module for quantum gate.""" -from collections.abc import Iterable -import numpy as np -from rich.console import Console -from mindquantum import mqbackend as mb -from mindquantum.io.display import measure_text_drawer -from .basic import NoneParameterGate - - -class Measure(NoneParameterGate): - """ - Measurement gate that measure quantum qubits. - - Args: - name (str): The key of this measurement gate. In a quantum circuit, the - key of different measurement gate should be unique. - - Examples: - >>> import numpy as np - >>> from mindquantum import qft, Circuit - >>> from mindquantum import Measure - >>> from mindquantum import Simulator - >>> circ = qft(range(2)) - >>> circ += Measure('q0').on(0) - >>> circ += Measure().on(1) - >>> circ - q0: ──H────PS(π/2)─────────✖────M(q0)── - │ │ - q1: ──────────●───────H────✖─────M(1)── - >>> sim = Simulator('projectq', circ.n_qubits) - >>> sim.apply_circuit(Circuit().h(0).x(1, 0)) - >>> sim - projectq simulator with 2 qubits. - Current quantum state: - √2/2¦00⟩ - √2/2¦11⟩ - >>> res = sim.sampling(circ, shots=2000) - >>> res - shots: 2000 - Keys: 1 q0│0.00 0.123 0.246 0.37 0.493 0.616 - ──────────┼───────────┴───────────┴───────────┴───────────┴───────────┴ - 00│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - │ - 10│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - 11│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - - {'00': 986, '10': 517, '11': 497} - >>> sim - projectq simulator with 2 qubits. - Current quantum state: - √2/2¦00⟩ - √2/2¦11⟩ - >>> sim.apply_circuit(circ[:-2]) - >>> sim - projectq simulator with 2 qubits. - Current quantum state: - √2/2¦00⟩ - (√2/4-√2/4j)¦10⟩ - (√2/4+√2/4j)¦11⟩ - >>> np.abs(sim.get_qs())**2 - array([0.5 , 0. , 0.25, 0.25]) - """ - def __init__(self, name=""): - self.key = name - NoneParameterGate.__init__(self, name) - self.name = 'M' - - def get_cpp_obj(self): - out = mb.get_measure_gate(self.key) - out.obj_qubits = self.obj_qubits - return out - - def __str__(self): - info = "" - if self.key and self.obj_qubits: - info = f'({self.obj_qubits[0]}, key={self.key})' - elif self.key: - info = f'(key={self.key})' - elif self.obj_qubits: - info = f'({self.obj_qubits[0]})' - return f"Measure{info}" - - def __repr__(self): - return self.__str__() - - def on(self, obj_qubits, ctrl_qubits=None): - """ - Apply this measurement gate on which qubit. - - Args: - obj_qubits (int): A non negative int that referring to its index number. - ctrl_qubits (int): Should be None for measure gate. - - Examples: - >>> from mindquantum import Circuit, Measure - >>> from mindquantum import Simulator - >>> sim = Simulator('projectq', 2) - >>> circ = Circuit().h(0).x(1, 0) - >>> circ += Measure('q0').on(0) - >>> circ += Measure('q1').on(1) - >>> circ - q0: ──H────●────M(q0)── - │ - q1: ───────X────M(q1)── - >>> res = sim.apply_circuit(circ) - >>> res - shots: 1 - Keys: q1 q0│0.00 0.2 0.4 0.6 0.8 1.0 - ───────────┼───────────┴───────────┴───────────┴───────────┴───────────┴ - 11│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - │ - - {'11': 1} - >>> sim - projectq simulator with 2 qubits. - Current quantum state: - 1¦11⟩ - """ - if ctrl_qubits is not None: - raise ValueError("Measure gate can not have control qubit") - if obj_qubits is None: - raise ValueError("The object qubit of measurement can not be none") - if not isinstance(obj_qubits, int): - raise ValueError("The object qubit of measurement must be a \ -non-negative integer referring to its index number") - if obj_qubits < 0: - raise ValueError("The object qubit of measurement must be a \ -non-negative integer referring to its index number") - new_gate = Measure(self.key) - new_gate.obj_qubits = [obj_qubits] - if not new_gate.key: - new_gate.key = f'q{obj_qubits}' - return new_gate - - def __hash__(self): - return hash(self.key) - - def __eq__(self, other): - if self.key == other.key: - return True - return False - - def hermitian(self): - """Hermitian gate of measure return its self""" - return self.__class__(self.key).on(self.obj_qubits[0]) - - def check_obj_qubits(self): - if not self.obj_qubits: - raise ValueError("Empty measure obj qubit") - if len(self.obj_qubits) > 1: - raise ValueError("Measure gate only apply on a single qubit") - - def define_projectq_gate(self): - raise NotImplementedError - - -class MeasureResult: - """ - Measurement result container - - Examples: - >>> from mindquantum import qft - >>> from mindquantum import Simulator - >>> sim = Simulator('projectq', 2) - >>> res = sim.sampling(qft(range(2)).measure_all(), shots=1000) - >>> res - shots: 1000 - Keys: 1 0│0.00 0.065 0.131 0.196 0.261 0.326 - ─────────┼───────────┴───────────┴───────────┴───────────┴───────────┴ - 00│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - 01│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - 10│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - │ - 11│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - - {'00': 250, '01': 235, '10': 261, '11': 254} - >>> res.data - {'00': 250, '01': 235, '10': 261, '11': 254} - """ - def __init__(self): - self.measures = [] - self.keys = [] - self.samples = np.array([]) - self.bit_string_data = {} - self.shots = 0 - - def add_measure(self, measure): - """ - Add a measurement gate into this measurement result container. Measure key - should be unique in this measurement result container. - """ - if not isinstance(measure, Iterable): - measure = [measure] - for m in measure: - if not isinstance(m, Measure): - raise ValueError("Measurement gates need to \ -be objects of class 'Measurement' ") - for m in measure: - if m.key in self.keys: - raise ValueError(f"Measure key {m.key} already defined.") - self.measures.append(m) - self.keys.append(m.key) - - @property - def keys_map(self): - return {i: j for j, i in enumerate(self.keys)} - - def collect_data(self, samples): - """ - collect the measured bit string - - Args: - samples (numpy.ndarray): A two dimensional (N x M) numpy array that stores - the sampling bit string in 0 or 1, where N represents the number of shot - times, and M represents the number of keys in this measurement container - """ - self.samples = samples - out = {} - res = np.fliplr(self.samples) - self.shots = len(self.samples) - for s in res: - s = ''.join([str(i) for i in s]) - if s in out: - out[s] += 1 - else: - out[s] = 1 - keys = sorted(list(out.keys())) - self.bit_string_data = {key: out[key] for key in keys} - - def select_keys(self, *keys): - """ - Select certain measurement keys from this measurement container - - Args: - keys (tuple[str]): The key you want to select. - - Examples: - >>> from mindquantum import Simulator - >>> from mindquantum import qft, H - >>> circ = qft(range(2)).measure('q0_0', 0).measure('q1_0', 1) - >>> circ.h(0).measure('q0_1', 0) - >>> circ - q0: ──H────PS(π/2)─────────✖────M(q0_0)────H────M(q0_1)── - │ │ - q1: ──────────●───────H────✖────M(q1_0)────────────────── - >>> sim = Simulator('projectq', circ.n_qubits) - >>> res = sim.sampling(circ, shots=500) - >>> new_res = res.select_keys('q0_1', 'q1_0') - >>> new_res - shots: 500 - Keys: q1_0 q0_1│0.00 0.07 0.139 0.209 0.278 0.348 - ───────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴ - 00│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - 01│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - 10│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - 11│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - │ - - {'00': 115, '01': 121, '10': 125, '11': 139} - """ - for key in keys: - if key not in self.keys: - raise ValueError(f'{key} not in this measure result.') - keys_map = self.keys_map - idx = [keys_map[key] for key in keys] - samples = self.samples[:, idx] - res = MeasureResult() - res.add_measure([self.measures[i] for i in idx]) - res.collect_data(samples) - return res - - @property - def data(self): - """ - Get the sampling data. - - Returns: - dict: The samping data. - """ - return self.bit_string_data - - def __str__(self): - return self.__repr__() - - def __repr__(self): - from mindquantum.io.display._config import _MEA_RES_STYLE - res = measure_text_drawer(self) - res.append(self.data.__str__()) - s = '\n'.join(res) - console = Console(record=True) - if not console.is_jupyter: - with console.capture() as capture: - console.print(s, style=_MEA_RES_STYLE['style']) - s = capture.get() - return s - - def _repr_html_(self): - """repr for jupyter notebook""" - from mindquantum.io.display._config import _MEA_RES_STYLE - from mindquantum.io.display._config import MEA_HTML_FORMAT - res = measure_text_drawer(self) - res.append(self.data.__str__()) - s = '\n'.join(res) - console = Console(record=True) - with console.capture() as _: - console.print(s, style=_MEA_RES_STYLE['style']) - s = console.export_html(code_format=MEA_HTML_FORMAT, - inline_styles=True) - return '\n'.join(s.split('\n')[1:]) diff --git a/mindquantum/core/operators/__init__.py b/mindquantum/core/operators/__init__.py deleted file mode 100644 index d76c7c220..000000000 --- a/mindquantum/core/operators/__init__.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -""" -MindQuantum operators library. An operator is composed of a combination of one or more basic gates. - -Contains classes representing: -- Qubit operators -- Fermion operators -- TimeEvolution operator - -""" -from mindquantum.third_party.interaction_operator import InteractionOperator -from .fermion_operator import FermionOperator -from .hamiltonian import Hamiltonian -from .polynomial_tensor import PolynomialTensor -from .projector import Projector -from .qubit_excitation_operator import QubitExcitationOperator -from .qubit_operator import QubitOperator -from .time_evolution import TimeEvolution -from .utils import count_qubits -from .utils import commutator -from .utils import normal_ordered -from .utils import get_fermion_operator -from .utils import number_operator -from .utils import hermitian_conjugated -from .utils import up_index -from .utils import down_index -from .utils import sz_operator - -__all__ = [ - "FermionOperator", "Hamiltonian", "PolynomialTensor", "Projector", - "QubitExcitationOperator", "QubitOperator", "TimeEvolution", - "count_qubits", "commutator", "normal_ordered", "get_fermion_operator", - "number_operator", "hermitian_conjugated", "up_index", "down_index", - "sz_operator" -] -__all__.append('InteractionOperator') -__all__.sort() diff --git a/mindquantum/core/operators/hamiltonian.py b/mindquantum/core/operators/hamiltonian.py deleted file mode 100644 index 3c4ea65b2..000000000 --- a/mindquantum/core/operators/hamiltonian.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Hamiltonian module.""" - -import scipy.sparse as sp -import numpy as np -from projectq.ops import QubitOperator as pq_operator -from openfermion.ops import QubitOperator as of_operator -from mindquantum import mqbackend as mb - -MODE = {'origin': 0, 'backend': 1, 'frontend': 2} -EDOM = {0: 'origin', 1: 'backend', 2: 'frontend'} - - -class Hamiltonian: - """ - A QubitOperator hamiltonian wrapper. - - Args: - hamiltonian (QubitOperator): The pauli word qubit operator. - - Examples: - >>> from mindquantum.core.operators import QubitOperator - >>> from mindquantum import Hamiltonian - >>> ham = Hamiltonian(QubitOperator('Z0 Y1', 0.3)) - >>> ham.mindspore_data() - {'hams_pauli_coeff': [0.3], - 'hams_pauli_word': [['Z', 'Y']], - 'hams_pauli_qubit': [[0, 1]]} - """ - def __init__(self, hamiltonian): - from mindquantum.core.operators import QubitOperator as hiq_operator - support_type = (pq_operator, of_operator, hiq_operator, sp.csr_matrix) - if not isinstance(hamiltonian, support_type): - raise TypeError( - "Require a QubitOperator or a csr_matrix, but get {}!".format( - type(hamiltonian))) - if isinstance(hamiltonian, sp.csr_matrix): - if len(hamiltonian.shape - ) != 2 or hamiltonian.shape[0] != hamiltonian.shape[1]: - raise ValueError( - f"Hamiltonian requires a two dimension square csr_matrix, but get shape {hamiltonian.shape}" - ) - if np.log2(hamiltonian.shape[0]) % 1 != 0: - raise ValueError( - f"size of hamiltonian sparse matrix should be power of 2, but get {hamiltonian.shape}" - ) - self.hamiltonian = hiq_operator('') - self.sparse_mat = hamiltonian - self.how_to = MODE['frontend'] - self.n_qubits = int(np.log2(self.sparse_mat.shape[0])) - else: - self.hamiltonian = hamiltonian - self.sparse_mat = sp.csr_matrix(np.eye(2, dtype=np.complex64)) - self.how_to = MODE['origin'] - self.n_qubits = None - self.ham_termlist = [(i, j) for i, j in self.hamiltonian.terms.items()] - - def __str__(self): - return self.hamiltonian.__str__() - - def __repr__(self): - return self.hamiltonian.__repr__() - - def sparse(self, n_qubits=1): - """ - Calculate the sparse matrix of this hamiltonian in pqc operator - - Args: - n_qubits (int): The total qubit of this hamiltonian, only need when mode is - 'frontend'. Default: 1. - """ - if EDOM[self.how_to] != 'origin': - raise ValueError('Already a sparse hamiltonian.') - self.n_qubits = n_qubits - self.how_to = MODE['backend'] - return self - - def get_cpp_obj(self, hermitian=False): - """get_cpp_obj""" - if not hermitian: - if not hasattr(self, 'ham_cpp'): - if self.how_to == MODE['origin']: - ham = mb.hamiltonian(self.ham_termlist) - elif self.how_to == MODE['backend']: - ham = mb.hamiltonian(self.ham_termlist, self.n_qubits) - else: - dim = self.sparse_mat.shape[0] - nnz = self.sparse_mat.nnz - csr_mat = mb.csr_hd_matrix(dim, nnz, - self.sparse_mat.indptr, - self.sparse_mat.indices, - self.sparse_mat.data) - ham = mb.hamiltonian(csr_mat, self.n_qubits) - self.ham_cpp = ham - return self.ham_cpp - if self.how_to == MODE['backend'] or self.how_to == MODE['origin']: - return self.get_cpp_obj() - if not hasattr(self, 'herm_ham_cpp'): - herm_sparse_mat = self.sparse_mat.conjugate().T.tocsr() - dim = herm_sparse_mat.shape[0] - nnz = herm_sparse_mat.nnz - csr_mat = mb.csr_hd_matrix(dim, nnz, herm_sparse_mat.indptr, - herm_sparse_mat.indices, - herm_sparse_mat.data) - self.herm_ham_cpp = mb.hamiltonian(csr_mat, self.n_qubits) - return self.herm_ham_cpp - - def mindspore_data(self): - """ - Generate hamiltonian information for PQC operator. - """ - m_data = { - "hams_pauli_coeff": [], - "hams_pauli_word": [], - "hams_pauli_qubit": [] - } - for term, coeff in self.ham_termlist: - m_data["hams_pauli_coeff"].append(float(coeff)) - m_data["hams_pauli_word"].append([]) - m_data["hams_pauli_qubit"].append([]) - for qubit, word in term: - m_data["hams_pauli_qubit"][-1].append(qubit) - m_data["hams_pauli_word"][-1].append(word) - return m_data - - -__all__ = ['Hamiltonian'] diff --git a/mindquantum/core/third_party/__init__.py b/mindquantum/core/third_party/__init__.py deleted file mode 100644 index 5111cf386..000000000 --- a/mindquantum/core/third_party/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Third-party modules for MindQuantum eDSL.""" - -import pkgutil -import importlib - -# Allow extending this namespace. -__path__ = pkgutil.extend_path(__path__, __name__) - -# Automatically import all submodules and bring their exported symbols (__all__) into our namespace -__all__ = [] -for (_, pkg_name, _) in pkgutil.iter_modules(path=__path__): - imported_module = importlib.import_module('.' + pkg_name, package=__name__) - - if hasattr(imported_module, '__all__'): - __all__.extend(imported_module.__all__) - for symbol_name in imported_module.__all__: - globals().setdefault(symbol_name, - getattr(imported_module, symbol_name)) - else: - for attr_name in dir(imported_module): - if attr_name[0] != '_': - __all__.append(attr_name) - globals().setdefault(attr_name, - getattr(imported_module, attr_name)) - -__all__.sort() diff --git a/mindquantum/engine/__init__.py b/mindquantum/engine/__init__.py index 37a8b6941..e8e5b87d9 100644 --- a/mindquantum/engine/__init__.py +++ b/mindquantum/engine/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,5 +18,7 @@ from .circuitengine import CircuitEngine from .circuitengine import circuit_generator -__all__ = ["BasicQubit", "CircuitEngine", "circuit_generator"] + +__all__ = ['BasicQubit', 'CircuitEngine', 'circuit_generator'] + __all__.sort() diff --git a/mindquantum/engine/circuitengine.py b/mindquantum/engine/circuitengine.py index e8bdb10b1..50e3fc601 100644 --- a/mindquantum/engine/circuitengine.py +++ b/mindquantum/engine/circuitengine.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +14,7 @@ # ============================================================================ """Simple engine to generate parameterized quantum circuit.""" -from mindquantum.core.circuit import Circuit +from mindquantum.circuit import Circuit class BasicQubit: @@ -102,7 +101,7 @@ def generator(n_qubits, *args, **kwds): n_qubits (int): qubit number of quantum circuit. Examples: - >>> import mindquantum.core.gates as G + >>> import mindquantum.gate as G >>> from mindquantum.engine import circuit_generator >>> @circuit_generator(2,prefix='p') >>> def ansatz(qubits, prefix): @@ -112,7 +111,7 @@ def generator(n_qubits, *args, **kwds): X(1 <-: 0) RX(p_0|1) >>> print(type(ansatz)) - + """ def deco(fn): eng = CircuitEngine() diff --git a/mindquantum/framework/__init__.py b/mindquantum/framework/__init__.py deleted file mode 100644 index 1d8a93820..000000000 --- a/mindquantum/framework/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Quantum neural networks operators and cells.""" -import warnings - -__all__ = [] -try: - import mindspore - from .layer import MQAnsatzOnlyLayer - from .layer import MQN2AnsatzOnlyOps - from .layer import MQLayer - from .layer import MQN2Layer - from .operations import MQOps - from .operations import MQN2Ops - from .operations import MQAnsatzOnlyOps - from .operations import MQN2AnsatzOnlyOps - from .operations import MQEncoderOnlyOps - from .operations import MQN2EncoderOnlyOps - __all__.extend([ - "MQAnsatzOnlyLayer", "MQN2AnsatzOnlyOps", "MQLayer", "MQN2Layer", - "MQOps", "MQN2Ops", "MQAnsatzOnlyOps", "MQN2AnsatzOnlyOps", - "MQEncoderOnlyOps", "MQN2EncoderOnlyOps" - ]) -except ImportError: - warnings.warn( - "MindSpore not installed, you may not be able to use hybrid quantum classical neural network." - ) - -__all__.sort() diff --git a/mindquantum/core/gates/__init__.py b/mindquantum/gate/__init__.py similarity index 64% rename from mindquantum/core/gates/__init__.py rename to mindquantum/gate/__init__.py index a8b5ac5c3..35c66331c 100644 --- a/mindquantum/core/gates/__init__.py +++ b/mindquantum/gate/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,52 +19,41 @@ """ from .basic import BasicGate +from .basic import IntrinsicOneParaGate from .basic import NoneParameterGate from .basic import ParameterGate -from .basic import IntrinsicOneParaGate -from .basic import HERMITIAN_PROPERTIES -from .basicgate import BarrierGate -from .basicgate import CNOTGate -from .basicgate import HGate from .basicgate import IGate from .basicgate import XGate from .basicgate import YGate from .basicgate import ZGate -from .basicgate import gene_univ_parameterized_gate -from .basicgate import UnivMathGate +from .basicgate import HGate from .basicgate import SWAPGate -from .basicgate import ISWAPGate +from .basicgate import CNOTGate +from .basicgate import H +from .basicgate import CNOT +from .basicgate import X +from .basicgate import Y +from .basicgate import Z +from .basicgate import I +from .basicgate import S +from .basicgate import Power +from .basicgate import SWAP +from .basicgate import UnivMathGate from .basicgate import RX from .basicgate import RY from .basicgate import RZ from .basicgate import PhaseShift -from .basicgate import SGate -from .basicgate import TGate from .basicgate import XX from .basicgate import YY from .basicgate import ZZ -from .basicgate import Power -from .basicgate import I -from .basicgate import X -from .basicgate import Y -from .basicgate import Z -from .basicgate import H -from .basicgate import S -from .basicgate import T -from .basicgate import SWAP -from .basicgate import ISWAP -from .basicgate import CNOT -from .basicgate import BARRIER -from .measurement import Measure -from .measurement import MeasureResult +from .hamiltonian import Hamiltonian +from .projector import Projector + __all__ = [ - "BasicGate", "NoneParameterGate", "ParameterGate", "IntrinsicOneParaGate", - "HERMITIAN_PROPERTIES", "BarrierGate", "CNOTGate", "HGate", "IGate", - "XGate", "YGate", "ZGate", "gene_univ_parameterized_gate", "UnivMathGate", - "SWAPGate", "ISWAPGate", "RX", "RY", "RZ", "PhaseShift", "SGate", "TGate", - "XX", "YY", "ZZ", "Power", "I", "X", "Y", "Z", "H", "S", "T", "SWAP", - "ISWAP", "CNOT", "BARRIER", "Measure", "MeasureResult" + 'BasicGate', 'IntrinsicOneParaGate', 'NoneParameterGate', 'ParameterGate', + 'H', 'CNOT', 'X', 'Y', 'Z', 'I', 'S', 'Power', 'SWAP', 'UnivMathGate', + 'RX', 'RY', 'RZ', 'PhaseShift', 'XX', 'YY', 'ZZ', 'IGate', 'XGate', + 'YGate', 'ZGate', 'HGate', 'SWAPGate', 'CNOTGate', 'Hamiltonian', + 'Projector' ] - -__all__.sort() diff --git a/mindquantum/core/gates/basic.py b/mindquantum/gate/basic.py similarity index 77% rename from mindquantum/core/gates/basic.py rename to mindquantum/gate/basic.py index 794521874..c462b2e3c 100644 --- a/mindquantum/core/gates/basic.py +++ b/mindquantum/gate/basic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,15 +19,7 @@ from abc import abstractmethod from collections.abc import Iterable import numpy as np -from mindquantum.core.parameterresolver import ParameterResolver as PR -from mindquantum.utils.f import _common_exp -from mindquantum import mqbackend as mb - -HERMITIAN_PROPERTIES = { - 'self_hermitian': 0, # the hermitian of this gate is its self - 'do_hermitian': 1, # just do hermitian when you need hermitian - 'params_opposite': 2 # use the negative parameters for hermitian -} +from mindquantum.parameterresolver import ParameterResolver as PR class BasicGate(): @@ -37,20 +28,16 @@ class BasicGate(): Args: name (str): the name of this gate. - parameterized (bool): whether this is a parameterized gate. Default: False. + isparameter (bool): whether this is a parameterized gate. Default: False. """ - def __init__(self, name, parameterized=False): + def __init__(self, name, isparameter=False): if not isinstance(name, str): raise TypeError("Excepted string for gate name, get {}".format( type(name))) self.name = name - self.parameterized = parameterized + self.isparameter = isparameter self.str = self.name self.projectq_gate = None - self.obj_qubits = [] - self.ctrl_qubits = [] - self.hermitian_property = HERMITIAN_PROPERTIES['self_hermitian'] - self.daggered = False @abstractmethod def matrix(self, *args): @@ -66,19 +53,13 @@ def define_projectq_gate(self): def generate_description(self): """Description generator.""" - name = self.name - if self.hermitian_property == HERMITIAN_PROPERTIES[ - 'do_hermitian'] and self.daggered: - name += '†' - if self.ctrl_qubits: + if hasattr(self, 'ctrl_qubits') and self.ctrl_qubits: obj_str = ' '.join([str(i) for i in self.obj_qubits]) ctrl_str = ' '.join([str(i) for i in self.ctrl_qubits]) - self.str = "{}({} <-: {})".format(name, obj_str, ctrl_str) - elif self.obj_qubits: + self.str = "{}({} <-: {})".format(self.name, obj_str, ctrl_str) + elif hasattr(self, 'obj_qubits'): self.str = "{}({})".format( - name, ' '.join([str(i) for i in self.obj_qubits])) - else: - self.str = name + self.name, ' '.join([str(i) for i in self.obj_qubits])) def on(self, obj_qubits, ctrl_qubits=None): """ @@ -97,7 +78,7 @@ def on(self, obj_qubits, ctrl_qubits=None): Gate, Return a new gate. Examples: - >>> from mindquantum.core.gates import X + >>> from mindquantum.gate import X >>> x = X.on(1) >>> x.obj_qubits [1] @@ -157,7 +138,7 @@ def __repr__(self): def __eq__(self, other): _check_gate_type(other) if self.name != other.name or \ - self.parameterized != other.parameterized or \ + self.isparameter != other.isparameter or \ self.obj_qubits != other.obj_qubits or \ self.ctrl_qubits != other.ctrl_qubits: return False @@ -165,7 +146,7 @@ def __eq__(self, other): def __or__(self, qubits): if not isinstance(qubits, tuple): - qubits = (qubits, ) + qubits = (qubits,) qubits = list(qubits) @@ -194,22 +175,6 @@ def __init__(self, name): self.coeff = None self.matrix_value = None - def get_cpp_obj(self): - """Get the cpp obj of this gate.""" - cpp_gate = mb.get_gate_by_name(self.name) - cpp_gate.obj_qubits = self.obj_qubits - cpp_gate.ctrl_qubits = self.ctrl_qubits - if self.daggered: - cpp_gate.daggered = True - if self.hermitian_property == HERMITIAN_PROPERTIES["do_hermitian"]: - cpp_gate.base_matrix = mb.dim2matrix(self.matrix()) - if self.hermitian_property == HERMITIAN_PROPERTIES[ - "params_opposite"]: - raise ValueError( - f"Hermitian properties of None parameterized gate {self} can not be params_opposite" - ) - return cpp_gate - def matrix(self, *args): """ Get the matrix of this none parameterized gate. @@ -221,10 +186,7 @@ def hermitian(self): Get hermitian gate of this none parameterized gate. """ hermitian_gate = deepcopy(self) - hermitian_gate.daggered = not hermitian_gate.daggered - if self.hermitian_property == HERMITIAN_PROPERTIES["do_hermitian"]: - hermitian_gate.matrix_value = np.conj(self.matrix_value.T) - hermitian_gate.generate_description() + hermitian_gate.matrix_value = np.conj(self.matrix_value.T) return hermitian_gate def define_projectq_gate(self): @@ -237,7 +199,6 @@ def check_obj_qubits(self): """Check obj qubit number""" n_qubits_exp = np.log2(len(self.matrix_value)).astype(int) n_qubits = len(self.obj_qubits) - self.n_qubits = n_qubits_exp if n_qubits_exp != n_qubits: raise ValueError( f"obj_qubits of {self.name} requires {n_qubits_exp} qubits, but get {n_qubits}" @@ -257,7 +218,7 @@ def __init__(self, name, coeff=None): if isinstance(coeff, (int, float, complex)): NoneParameterGate.__init__(self, name) self.coeff = coeff - self.str = self.str + "({})".format(_common_exp(self.coeff, 3)) + self.str = self.str + "({})".format(round(self.coeff, 3)) else: BasicGate.__init__(self, name, True) if coeff is None: @@ -279,25 +240,21 @@ def __init__(self, name, coeff=None): self.str = self.str + "({})".format(self.coeff.expression()) def generate_description(self): - name = self.name - if self.hermitian_property == HERMITIAN_PROPERTIES[ - 'do_hermitian'] and self.daggered: - name += '†' BasicGate.generate_description(self) - if not hasattr(self, 'obj_qubits') or not self.obj_qubits: - if self.parameterized: - self.str = f'{name}({self.coeff.expression()})' + if not hasattr(self, 'obj_qubits'): + if self.isparameter: + self.str = f'{self.name}({self.coeff.expression()})' else: - self.str = f'{name}({_common_exp(self.coeff, 3)})' + self.str = f'{self.name}({round(self.coeff, 3)})' else: - if self.parameterized: + if self.isparameter: self.str = self.str[:len( - name) + 1] + str(self.coeff.expression())\ - + '|' + self.str[len(name) + 1:] + self.name) + 1] + str(self.coeff.expression())\ + + '|' + self.str[len(self.name) + 1:] else: self.str = self.str[:len( - name) + 1] + str(_common_exp(self.coeff, 3))\ - + '|' + self.str[len(name) + 1:] + self.name) + 1] + str(round(self.coeff, 3))\ + + '|' + self.str[len(self.name) + 1:] @abstractmethod def matrix(self, *paras_out): @@ -343,10 +300,10 @@ def requires_grad(self): All parameters requires grad. Inplace operation. Returns: - BasicGate, a parameterized gate with all parameters need to + BaseGate, a parameterized gate with all parameters need to update gradient. """ - if self.parameterized: + if self.isparameter: self.coeff.requires_grad() return self @@ -355,10 +312,10 @@ def no_grad(self): All parameters do not need grad. Inplace operation. Returns: - BasicGate, a parameterized gate with all parameters not need to + BaseGate, a parameterized gate with all parameters not need to update gradient. """ - if self.parameterized: + if self.isparameter: self.coeff.no_grad() return self @@ -370,7 +327,7 @@ def requires_grad_part(self, names): names (tuple[str]): Parameters that requires grad. Returns: - BasicGate, with some part of parameters need to update gradient. + BaseGate, with some part of parameters need to update gradient. """ self.coeff.requires_grad_part(names) return self @@ -383,7 +340,7 @@ def no_grad_part(self, names): names (tuple[str]): Parameters that not requires grad. Returns: - BasicGate, with some part of parameters not need to update gradient. + BaseGate, with some part of parameters not need to update gradient. """ self.coeff.no_grad_part(names) return self @@ -403,7 +360,7 @@ class IntrinsicOneParaGate(ParameterGate): coeff (Union[dict, ParameterResolver]): the parameter of this gate. Default: Nnoe. Examples: - >>> from mindquantum.core.gates import RX + >>> from mindquantum.gate import RX >>> rx1 = RX(1.2) >>> rx1 RX(1.2) @@ -415,19 +372,6 @@ class IntrinsicOneParaGate(ParameterGate): """ def __init__(self, name, coeff=None): ParameterGate.__init__(self, name, coeff) - self.hermitian_property = HERMITIAN_PROPERTIES['params_opposite'] - - def get_cpp_obj(self): - """Get cpp obj of this gate.""" - cpp_gate = mb.get_gate_by_name(self.name) - cpp_gate.obj_qubits = self.obj_qubits - cpp_gate.ctrl_qubits = self.ctrl_qubits - cpp_gate.daggered = self.daggered - if not self.parameterized: - cpp_gate.apply_value(self.coeff) - else: - cpp_gate.params = self.coeff.get_cpp_obj() - return cpp_gate def hermitian(self): """ @@ -443,7 +387,6 @@ def hermitian(self): RX(a*(-1.0 - 2.0*I)) """ hermitian_gate = deepcopy(self) - hermitian_gate.daggered = not hermitian_gate.daggered hermitian_gate.coeff = 1 * self.coeff if isinstance(self.coeff, PR): hermitian_gate.coeff *= -1 @@ -475,7 +418,7 @@ def matrix(self, *paras_out): numpy.ndarray, Return the numpy array of the matrix. Examples: - >>> from mindquantum.core.gates import RX + >>> from mindquantum.gate import RX >>> rx1 = RX(0) >>> rx1.matrix() array([[1.+0.j, 0.-0.j], @@ -485,7 +428,7 @@ def matrix(self, *paras_out): array([[0.36+0.j , 0. -0.93j], [0. -0.93j, 0.36+0.j ]]) """ - if self.parameterized: + if self.isparameter: theta = 0 if isinstance(paras_out[0], dict): theta = self.linearcombination(self.coeff, paras_out[0]) @@ -516,7 +459,7 @@ def diff_matrix(self, *paras_out, about_what=None): array([[-0.42+0.j , 0. -0.27j], [ 0. -0.27j, -0.42+0.j ]]) """ - if self.parameterized: + if self.isparameter: theta = 0 if isinstance(paras_out[0], dict): theta = self.linearcombination(self.coeff, paras_out[0]) @@ -537,17 +480,22 @@ def check_obj_qubits(self): """Check obj qubit number""" n_qubits = len(self.obj_qubits) n_qubits_exp = np.log2(len(self._matrix(0))).astype(int) - self.n_qubits = n_qubits_exp if n_qubits_exp != n_qubits: raise ValueError( f"obj_qubits of {self.name} requires {n_qubits_exp} qubits, but get {n_qubits}" ) +def _is_gate_class(gate_class): + if not hasattr(gate_class, 'isparameter'): + return issubclass(gate_class, BasicGate) + return isinstance(gate_class, BasicGate) + + def _check_gate_type(gate): - if not isinstance(gate, BasicGate): - raise TypeError("Require a quantum gate, but get {}".format( - type(gate))) + msg = "Require a quantum gate, but get {}".format(type(gate)) + if not _is_gate_class(gate): + raise TypeError(msg) def _check_qubit_id(qubit_id): diff --git a/mindquantum/core/gates/basicgate.py b/mindquantum/gate/basicgate.py similarity index 61% rename from mindquantum/core/gates/basicgate.py rename to mindquantum/gate/basicgate.py index 45047ad7d..db70d3cb8 100644 --- a/mindquantum/core/gates/basicgate.py +++ b/mindquantum/gate/basicgate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,41 +14,19 @@ # ============================================================================ """Basic quantum gate.""" -from types import FunctionType, MethodType -from copy import deepcopy import numpy as np import projectq.ops as pjops from scipy.linalg import fractional_matrix_power -from mindquantum.core.parameterresolver import ParameterResolver as PR -from mindquantum import mqbackend as mb -from .basic import HERMITIAN_PROPERTIES +from mindquantum.parameterresolver import ParameterResolver as PR from .basic import IntrinsicOneParaGate from .basic import NoneParameterGate -class BarrierGate(NoneParameterGate): - """ - BARRIER gate do nothing but set a barrier for drawing circuit. - """ - def __init__(self, show=True): - NoneParameterGate.__init__(self, 'BARRIER') - self.show = show - - def get_cpp_obj(self): - return None - - def hermitian(self): - return BarrierGate(self.show) - - def on(self, obj_qubits, ctrl_qubits=None): - raise NotImplementedError - - class CNOTGate(NoneParameterGate): r""" Control-X gate. - More usage, please see :class:`mindquantum.core.gates.XGate`. + More usage, please see :class:`mindquantum.gate.XGate`. """ def __init__(self): NoneParameterGate.__init__(self, 'CNOT') @@ -59,14 +36,6 @@ def define_projectq_gate(self): """Define the corresponded projectq gate.""" self.projectq_gate = pjops.CNOT - def on(self, obj_qubits, ctrl_qubits=None): - out = super(CNOTGate, self).on(obj_qubits, ctrl_qubits) - if ctrl_qubits is None: - raise ValueError("A control qubit is needed for CNOT gate!") - out.ctrl_qubits = [] - out.obj_qubits = [obj_qubits, ctrl_qubits] - return out - class HGate(NoneParameterGate): r""" @@ -76,7 +45,7 @@ class HGate(NoneParameterGate): {\rm H}=\frac{1}{\sqrt{2}}\begin{pmatrix}1&1\\1&-1\end{pmatrix} - More usage, please see :class:`mindquantum.core.gates.XGate`. + More usage, please see :class:`mindquantum.gate.XGate`. """ def __init__(self): NoneParameterGate.__init__(self, 'H') @@ -95,7 +64,7 @@ class IGate(NoneParameterGate): {\rm I}=\begin{pmatrix}1&0\\0&1\end{pmatrix} - More usage, please see :class:`mindquantum.core.gates.XGate`. + More usage, please see :class:`mindquantum.gate.XGate`. """ def __init__(self): NoneParameterGate.__init__(self, 'I') @@ -123,7 +92,7 @@ class XGate(NoneParameterGate): X^\theta = RX(\theta\pi) Examples: - >>> from mindquantum.core.gates import X + >>> from mindquantum.gate import X >>> x1 = X.on(0) >>> cnot = X.on(0, 1) >>> print(x1) @@ -134,7 +103,7 @@ class XGate(NoneParameterGate): array([[0, 1], [1, 0]]) >>> x1**2 - RX(2π) + RX(6.283) >>> (x1**'a').coeff {'a': 3.141592653589793} >>> (x1**{'a' : 2}).coeff @@ -169,7 +138,7 @@ class YGate(NoneParameterGate): {\rm Y}=\begin{pmatrix}0&-i\\i&0\end{pmatrix} - More usage, please see :class:`mindquantum.core.gates.XGate`. + More usage, please see :class:`mindquantum.gate.XGate`. """ def __init__(self): NoneParameterGate.__init__(self, 'Y') @@ -200,7 +169,7 @@ class ZGate(NoneParameterGate): {\rm Z}=\begin{pmatrix}1&0\\0&-1\end{pmatrix} - More usage, please see :class:`mindquantum.core.gates.XGate`. + More usage, please see :class:`mindquantum.gate.XGate`. """ def __init__(self): NoneParameterGate.__init__(self, 'Z') @@ -223,100 +192,18 @@ def define_projectq_gate(self): self.projectq_gate = pjops.Z -def gene_univ_parameterized_gate(name, matrix_generator, - diff_matrix_generator): - """ - Generate a customer parameterized gate based on the single parameter defined - unitary matrix. - - Args: - name (str): The name of this gate. - matrix_generator (Union[FunctionType, MethodType]): A function or a method that - take exactly one argument to generate a unitary matrix. - diff_matrix_generator (Union[FunctionType, MethodType]): A function or a method - that take exactly one argument to generate the derivative of this unitary matrix. - - Returns: - _UnivParameterizedGate, a customer parameterized gate. - - Examples: - >>> import numpy as np - >>> from mindquantum import gene_univ_parameterized_gate - >>> from mindquantum import Simulator, Circuit - >>> def matrix(theta): - ... return np.array([[np.exp(1j * theta), 0], - ... [0, np.exp(-1j * theta)]]) - >>> def diff_matrix(theta): - ... return 1j*np.array([[np.exp(1j * theta), 0], - ... [0, -np.exp(-1j * theta)]]) - >>> TestGate = gene_univ_parameterized_gate('Test', matrix, diff_matrix) - >>> circ = Circuit().h(0) - >>> circ += TestGate('a').on(0) - >>> circ - q0: ──H────Test(a)── - >>> circ.get_qs(pr={'a': 1.2}) - array([0.25622563+0.65905116j, 0.25622563-0.65905116j]) - """ - if not isinstance(matrix_generator, (FunctionType, MethodType)): - raise ValueError('matrix_generator requires a function or a method.') - if not isinstance(diff_matrix_generator, (FunctionType, MethodType)): - raise ValueError('matrix_generator requires a function or a method.') - - class _UnivParameterizedGate(IntrinsicOneParaGate): - """The customer parameterized gate.""" - def __init__(self, coeff): - IntrinsicOneParaGate.__init__(self, name, coeff) - self.matrix_generator = matrix_generator - self.diff_matrix_generator = diff_matrix_generator - self.hermitian_property = HERMITIAN_PROPERTIES['do_hermitian'] - - def _matrix(self, theta): - if self.daggered: - return np.conj(self.matrix_generator(theta)).T - return self.matrix_generator(theta) - - def _diff_matrix(self, theta): - if self.daggered: - return np.conj(self.matrix_generator(theta)).T - return self.diff_matrix_generator(theta) - - def hermitian(self): - hermitian_gate = deepcopy(self) - hermitian_gate.daggered = not hermitian_gate.daggered - hermitian_gate.coeff = 1 * self.coeff - hermitian_gate.generate_description() - return hermitian_gate - - def get_cpp_obj(self): - cpp_gate = mb.basic_gate(self.name, self.hermitian_property, - self._matrix, self._diff_matrix) - cpp_gate.daggered = self.daggered - cpp_gate.obj_qubits = self.obj_qubits - cpp_gate.ctrl_qubits = self.ctrl_qubits - if not self.parameterized: - cpp_gate.apply_value(self.coeff) - else: - cpp_gate.params = self.coeff.get_cpp_obj() - return cpp_gate - - def define_projectq_gate(self): - raise NotImplementedError - - return _UnivParameterizedGate - - class UnivMathGate(NoneParameterGate): r""" Universal math gate. - More usage, please see :class:`mindquantum.core.gates.XGate`. + More usage, please see :class:`mindquantum.gate.XGate`. Args: name (str): the name of this gate. mat (np.ndarray): the matrix value of this gate. Examples: - >>> from mindquantum.core.gates import UnivMathGate + >>> from mindquantum.gate import UnivMathGate >>> x_mat=np.array([[0,1],[1,0]]) >>> X_gate=UnivMathGate('X',x_mat) >>> x1=X_gate.on(0,1) @@ -326,27 +213,17 @@ class UnivMathGate(NoneParameterGate): def __init__(self, name, mat): NoneParameterGate.__init__(self, name) self.matrix_value = mat - self.hermitian_property = HERMITIAN_PROPERTIES['do_hermitian'] def define_projectq_gate(self): """Define the corresponded projectq gate.""" self.projectq_gate = None - def get_cpp_obj(self): - mat = mb.dim2matrix(self.matrix()) - cpp_gate = mb.basic_gate(False, self.name, self.hermitian_property, - mat) - cpp_gate.daggered = self.daggered - cpp_gate.obj_qubits = self.obj_qubits - cpp_gate.ctrl_qubits = self.ctrl_qubits - return cpp_gate - class SWAPGate(NoneParameterGate): """ SWAP gate that swap two different qubits. - More usage, please see :class:`mindquantum.core.gates.XGate`. + More usage, please see :class:`mindquantum.gate.XGate`. """ def __init__(self): NoneParameterGate.__init__(self, 'SWAP') @@ -358,25 +235,6 @@ def define_projectq_gate(self): self.projectq_gate = pjops.Swap -class ISWAPGate(NoneParameterGate): - r""" - ISWAP gate that swap two different qubits and phase - :math:`\left|01\right>` and :math:`\left|10\right>` amplitudes by - :math:`i`. - - More usage, please see :class:`mindquantum.core.gates.XGate`. - """ - def __init__(self): - NoneParameterGate.__init__(self, 'ISWAP') - self.hermitian_property = HERMITIAN_PROPERTIES['do_hermitian'] - self.matrix_value = np.array([[1, 0, 0, 0], [0, 0, 1j, 0], - [0, 1j, 0, 0], [0, 0, 0, 1]]) - - def define_projectq_gate(self): - """Define the corresponded projectq gate.""" - self.projectq_gate = pjops.Swap - - class RX(IntrinsicOneParaGate): r""" Rotation gate around x-axis. @@ -409,7 +267,7 @@ class RX(IntrinsicOneParaGate): parameterized gate, see above for detail explanation. Default: None. Examples: - >>> from mindquantum.core.gates import RX + >>> from mindquantum.gate import RX >>> import numpy as np >>> rx1 = RX(0.5) >>> np.round(rx1.matrix(), 2) @@ -421,7 +279,7 @@ class RX(IntrinsicOneParaGate): [0. -0.05j, 0.999+0.j ]]) >>> rx3 = RX({'a' : 0.2, 'b': 0.5}).on(0, 2) >>> print(rx3) - RX(0.2*a + 0.5*b|0 <-: 2) + RX(a b|0 <-: 2) >>> np.round(rx3.matrix({'a' : 1, 'b' : 2}), 2) array([[0.83+0.j , 0. -0.56j], [0. -0.56j, 0.83+0.j ]]) @@ -457,7 +315,7 @@ class RZ(IntrinsicOneParaGate): {\rm RZ}=\begin{pmatrix}\exp(-i\theta/2)&0\\ 0&\exp(i\theta/2)\end{pmatrix} - More usage, please see :class:`mindquantum.core.gates.RX`. + More usage, please see :class:`mindquantum.gate.RX`. """ def __init__(self, coeff=None): IntrinsicOneParaGate.__init__(self, 'RZ', coeff) @@ -484,7 +342,7 @@ class RY(IntrinsicOneParaGate): {\rm RY}=\begin{pmatrix}\cos(\theta/2)&-\sin(\theta/2)\\ \sin(\theta/2)&\cos(\theta/2)\end{pmatrix} - More usage, please see :class:`mindquantum.core.gates.RX`. + More usage, please see :class:`mindquantum.gate.RX`. """ def __init__(self, coeff=None): IntrinsicOneParaGate.__init__(self, 'RY', coeff) @@ -512,7 +370,7 @@ class PhaseShift(IntrinsicOneParaGate): {\rm PhaseShift}=\begin{pmatrix}1&0\\ 0&\exp(i\theta)\end{pmatrix} - More usage, please see :class:`mindquantum.core.gates.RX`. + More usage, please see :class:`mindquantum.gate.RX`. """ def __init__(self, coeff=None): IntrinsicOneParaGate.__init__(self, 'PS', coeff) @@ -528,68 +386,6 @@ def define_projectq_gate(self): self.projectq_gate = pjops.R -class SGate(PhaseShift): - r""" - S gate with matrix as : - - .. math:: - {\rm X}=\begin{pmatrix}1&0\\0&i\end{pmatrix} - - More usage, please see :class:`mindquantum.core.gates.XGate`. - """ - def __init__(self): - PhaseShift.__init__(self, np.pi / 2) - self.name = 'S' - self.str = self.name - self.hermitian_property = HERMITIAN_PROPERTIES['do_hermitian'] - - def generate_description(self): - PhaseShift.generate_description(self) - idx = self.str.find('|') - if idx != -1: - self.str = self.name + ('†(' if self.daggered else - '(') + self.str[idx + 1:] - else: - self.str = self.name + ('†' if self.daggered else '') - - def get_cpp_obj(self): - f = -1 if self.daggered else 1 - f = f * np.pi / 2 - return PhaseShift(f).on(self.obj_qubits, - self.ctrl_qubits).get_cpp_obj() - - -class TGate(PhaseShift): - r""" - T gate with matrix as : - - .. math:: - {\rm X}=\begin{pmatrix}1&0\\0&(1+i)/sqrt(2)\end{pmatrix} - - More usage, please see :class:`mindquantum.core.gates.XGate`. - """ - def __init__(self): - PhaseShift.__init__(self, np.pi / 4) - self.name = 'T' - self.str = self.name - self.hermitian_property = HERMITIAN_PROPERTIES['do_hermitian'] - - def generate_description(self): - PhaseShift.generate_description(self) - idx = self.str.find('|') - if idx != -1: - self.str = self.name + ('†(' if self.daggered else - '(') + self.str[idx + 1:] - else: - self.str = self.name + ('†' if self.daggered else '') - - def get_cpp_obj(self): - f = -1 if self.daggered else 1 - f = f * np.pi / 4 - return PhaseShift(f).on(self.obj_qubits, - self.ctrl_qubits).get_cpp_obj() - - class XX(IntrinsicOneParaGate): r""" Ising XX gate. @@ -598,7 +394,7 @@ class XX(IntrinsicOneParaGate): {\rm XX_\theta}=\cos(\theta)I\otimes I-i\sin(\theta)\sigma_x\otimes\sigma_x - More usage, please see :class:`mindquantum.core.gates.RX`. + More usage, please see :class:`mindquantum.gate.RX`. """ def __init__(self, coeff=None): IntrinsicOneParaGate.__init__(self, 'XX', coeff) @@ -630,7 +426,7 @@ class YY(IntrinsicOneParaGate): {\rm YY_\theta}=\cos(\theta)I\otimes I-i\sin(\theta)\sigma_y\otimes\sigma_y - More usage, please see :class:`mindquantum.core.gates.RX`. + More usage, please see :class:`mindquantum.gate.RX`. """ def __init__(self, coeff=None): IntrinsicOneParaGate.__init__(self, 'YY', coeff) @@ -662,7 +458,7 @@ class ZZ(IntrinsicOneParaGate): {\rm ZZ_\theta}=\cos(\theta)I\otimes I-i\sin(\theta)\sigma_Z\otimes\sigma_Z - More usage, please see :class:`mindquantum.core.gates.RX`. + More usage, please see :class:`mindquantum.gate.RX`. """ def __init__(self, coeff=None): IntrinsicOneParaGate.__init__(self, 'ZZ', coeff) @@ -689,7 +485,7 @@ class Power(NoneParameterGate): Power operator on a non parameterized gate. Args: - gates (:class:`mindquantum.core.gates.NoneParameterGate`): The basic gate you need to apply power operator. + gates (:class:`mindquantum.gate.NoneParameterGate`): The basic gate you need to apply power operator. t (int, float): The exponenet. Default: 0.5. Examples: @@ -703,30 +499,17 @@ def __init__(self, gate: NoneParameterGate, t=0.5): NoneParameterGate.__init__(self, '{}^{}'.format(gate.name, round(t, 2))) self.matrix_value = fractional_matrix_power(gate.matrix(), t) - self.hermitian_property = HERMITIAN_PROPERTIES['do_hermitian'] def define_projectq_gate(self): """Define the corresponded projectq gate.""" self.projectq_gate = None - def get_cpp_obj(self): - mat = mb.dim2matrix(self.matrix()) - cpp_gate = mb.basic_gate(False, self.name, self.hermitian_property, - mat) - cpp_gate.daggered = self.daggered - cpp_gate.obj_qubits = self.obj_qubits - cpp_gate.ctrl_qubits = self.ctrl_qubits - return cpp_gate - I = IGate() X = XGate() Y = YGate() Z = ZGate() H = HGate() -S = SGate() -T = TGate() +S = PhaseShift(np.pi / 2) SWAP = SWAPGate() -ISWAP = ISWAPGate() CNOT = CNOTGate() -BARRIER = BarrierGate(show=False) diff --git a/mindquantum/gate/hamiltonian.py b/mindquantum/gate/hamiltonian.py new file mode 100644 index 000000000..847690230 --- /dev/null +++ b/mindquantum/gate/hamiltonian.py @@ -0,0 +1,68 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Hamiltonian module.""" + +from projectq.ops import QubitOperator as pq_operator +from openfermion.ops import QubitOperator as of_operator +from mindquantum.ops import QubitOperator as hiq_operator + + +class Hamiltonian: + """ + A QubitOperator hamiltonian wrapper. + + Args: + hamiltonian (QubitOperator): The pauli word qubit operator. + + Examples: + >>> from mindquantum.ops import QubitOperator + >>> from mindquantum import Hamiltonian + >>> ham = Hamiltonian(QubitOperator('Z0 Y1', 0.3)) + >>> ham.mindspore_data() + {'hams_pauli_coeff': [0.3], + 'hams_pauli_word': [['Z', 'Y']], + 'hams_pauli_qubit': [[0, 1]]} + """ + def __init__(self, hamiltonian): + if not isinstance(hamiltonian, + (pq_operator, of_operator, hiq_operator)): + raise TypeError("Require a QubitOperator, but get {}!".format( + type(hamiltonian))) + self.hamiltonian = hamiltonian + self.ham_termlist = [(i, j) for i, j in hamiltonian.terms.items()] + + def __str__(self): + return self.hamiltonian.__str__() + + def __repr__(self): + return self.hamiltonian.__repr__() + + def mindspore_data(self): + """ + Generate hamiltonian information for PQC operator. + """ + m_data = { + "hams_pauli_coeff": [], + "hams_pauli_word": [], + "hams_pauli_qubit": [] + } + for term, coeff in self.ham_termlist: + m_data["hams_pauli_coeff"].append(float(coeff)) + m_data["hams_pauli_word"].append([]) + m_data["hams_pauli_qubit"].append([]) + for qubit, word in term: + m_data["hams_pauli_qubit"][-1].append(qubit) + m_data["hams_pauli_word"][-1].append(word) + return m_data diff --git a/mindquantum/core/operators/projector.py b/mindquantum/gate/projector.py similarity index 96% rename from mindquantum/core/operators/projector.py rename to mindquantum/gate/projector.py index eb710428b..8bbcc995e 100644 --- a/mindquantum/core/operators/projector.py +++ b/mindquantum/gate/projector.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,7 +45,7 @@ class Projector: proj (str): The string format of the projector. Examples: - >>> from mindquantum.core.gates import Projector + >>> from mindquantum.gate import Projector >>> p = Projector('II010') >>> p I2 ⊗ ¦010⟩⟨010¦ diff --git a/mindquantum/io/display/__init__.py b/mindquantum/hiqfermion/__init__.py similarity index 76% rename from mindquantum/io/display/__init__.py rename to mindquantum/hiqfermion/__init__.py index f88a436f5..69a85ec4d 100644 --- a/mindquantum/io/display/__init__.py +++ b/mindquantum/hiqfermion/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,11 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""display""" +"""HiQFermion module""" -from .circuit_text_drawer import brick_model -from .measure_res_drawer import measure_text_drawer - -__all__ = ['brick_model', 'measure_text_drawer'] - -__all__.sort() +from . import transforms +from . import ucc diff --git a/mindquantum/algorithm/library/__init__.py b/mindquantum/hiqfermion/transforms/__init__.py similarity index 87% rename from mindquantum/algorithm/library/__init__.py rename to mindquantum/hiqfermion/transforms/__init__.py index 2987cef3e..2ecd9c4b0 100644 --- a/mindquantum/algorithm/library/__init__.py +++ b/mindquantum/hiqfermion/transforms/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,10 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""Circuit library""" +"""transform""" +from .transform import Transform -from .quantum_fourier import qft - -__all__ = ['qft'] +__all__ = ['Transform'] __all__.sort() diff --git a/mindquantum/algorithm/nisq/chem/transform.py b/mindquantum/hiqfermion/transforms/transform.py similarity index 98% rename from mindquantum/algorithm/nisq/chem/transform.py rename to mindquantum/hiqfermion/transforms/transform.py index 0c2f165dd..6bf5afe00 100644 --- a/mindquantum/algorithm/nisq/chem/transform.py +++ b/mindquantum/hiqfermion/transforms/transform.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2020 Huawei Technologies Co.,ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +18,9 @@ from math import floor, log import numpy as np -from mindquantum.core.operators.fermion_operator import FermionOperator -from mindquantum.core.operators.qubit_operator import QubitOperator -from mindquantum.core.operators.utils import count_qubits, number_operator, normal_ordered +from mindquantum.ops.fermion_operator import FermionOperator +from mindquantum.ops.qubit_operator import QubitOperator +from mindquantum.utils import count_qubits, number_operator, normal_ordered class Transform: @@ -41,7 +40,7 @@ class Transform: n_qubits (int): The total qubits of this operator. Default: None Examples: - >>> from mindquantum.core.operators import FermionOperator + >>> from mindquantum.ops import FermionOperator >>> op1 = FermionOperator('1^') >>> op1 1.0 [1^] @@ -458,9 +457,9 @@ def reversed_jordan_wigner(self): # Handle Pauli X and Y. else: raising_term = FermionOperator( - ((pauli_operator[0], 1), )) + ((pauli_operator[0], 1),)) lowering_term = FermionOperator( - ((pauli_operator[0], 0), )) + ((pauli_operator[0], 0),)) if pauli_operator[1] == 'Y': raising_term *= 1.j lowering_term *= -1.j @@ -469,7 +468,7 @@ def reversed_jordan_wigner(self): # Account for the phase terms. for j in reversed(range(pauli_operator[0])): - z_term = QubitOperator(((j, 'Z'), )) + z_term = QubitOperator(((j, 'Z'),)) working_term = z_term * working_term term_key = list(working_term.terms)[0] transformed_pauli *= working_term.terms[term_key] diff --git a/mindquantum/algorithm/nisq/chem/__init__.py b/mindquantum/hiqfermion/ucc/__init__.py similarity index 65% rename from mindquantum/algorithm/nisq/chem/__init__.py rename to mindquantum/hiqfermion/ucc/__init__.py index 94ac71c96..0a14e2bde 100644 --- a/mindquantum/algorithm/nisq/chem/__init__.py +++ b/mindquantum/hiqfermion/ucc/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,24 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""Algorithm for quantum chemistry""" +"""Quantum unitary coupled cluster.""" from mindquantum.third_party.unitary_cc import uccsd_singlet_generator from mindquantum.third_party.unitary_cc import uccsd_singlet_get_packed_amplitudes -from .transform import Transform from .qubit_hamiltonian import get_qubit_hamiltonian from .uccsd0 import uccsd0_singlet_generator from .quccsd import quccsd_generator -from .hardware_efficient_ansatz import HardwareEfficientAnsatz -from .qubit_ucc_ansatz import QubitUCCAnsatz -from .uccsd import generate_uccsd -from .unitary_cc import UCCAnsatz __all__ = [ - 'Transform', 'get_qubit_hamiltonian', 'uccsd_singlet_generator', - 'uccsd_singlet_get_packed_amplitudes', 'uccsd0_singlet_generator', - 'quccsd_generator', 'HardwareEfficientAnsatz', 'QubitUCCAnsatz', - 'generate_uccsd', 'UCCAnsatz' + 'uccsd_singlet_generator', 'uccsd_singlet_get_packed_amplitudes', + 'get_qubit_hamiltonian', 'uccsd0_singlet_generator', 'quccsd_generator' ] __all__.sort() diff --git a/mindquantum/algorithm/nisq/chem/qubit_hamiltonian.py b/mindquantum/hiqfermion/ucc/qubit_hamiltonian.py similarity index 86% rename from mindquantum/algorithm/nisq/chem/qubit_hamiltonian.py rename to mindquantum/hiqfermion/ucc/qubit_hamiltonian.py index a72cb5d11..b8d7216c9 100644 --- a/mindquantum/algorithm/nisq/chem/qubit_hamiltonian.py +++ b/mindquantum/hiqfermion/ucc/qubit_hamiltonian.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +14,9 @@ # ============================================================================ """Get qubit hamiltonian""" -from mindquantum.core.operators import InteractionOperator -from mindquantum.core.operators.utils import get_fermion_operator -from .transform import Transform +from mindquantum.ops import InteractionOperator +from mindquantum.utils import get_fermion_operator +from mindquantum.hiqfermion.transforms import Transform def get_qubit_hamiltonian(mol): diff --git a/mindquantum/algorithm/nisq/chem/quccsd.py b/mindquantum/hiqfermion/ucc/quccsd.py similarity index 97% rename from mindquantum/algorithm/nisq/chem/quccsd.py rename to mindquantum/hiqfermion/ucc/quccsd.py index 558e1fc68..81a961d4b 100644 --- a/mindquantum/algorithm/nisq/chem/quccsd.py +++ b/mindquantum/hiqfermion/ucc/quccsd.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +18,9 @@ import warnings import numpy -from mindquantum.core.operators import QubitExcitationOperator -from mindquantum.core.operators.utils import hermitian_conjugated -from mindquantum.core.parameterresolver import ParameterResolver as PR +from mindquantum.ops import QubitExcitationOperator +from mindquantum.parameterresolver import ParameterResolver as PR +from mindquantum.utils import hermitian_conjugated def _check_int_list(input_list, name): diff --git a/mindquantum/algorithm/nisq/chem/uccsd0.py b/mindquantum/hiqfermion/ucc/uccsd0.py similarity index 98% rename from mindquantum/algorithm/nisq/chem/uccsd0.py rename to mindquantum/hiqfermion/ucc/uccsd0.py index ac74331d2..d1a6b10ac 100644 --- a/mindquantum/algorithm/nisq/chem/uccsd0.py +++ b/mindquantum/hiqfermion/ucc/uccsd0.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +18,9 @@ import warnings import numpy -from mindquantum.core.operators import FermionOperator -from mindquantum.core.operators.utils import hermitian_conjugated, normal_ordered -from mindquantum.core.parameterresolver import ParameterResolver as PR +from mindquantum.ops import FermionOperator +from mindquantum.parameterresolver import ParameterResolver as PR +from mindquantum.utils import hermitian_conjugated, normal_ordered def _check_int_list(input_list, name): diff --git a/mindquantum/io/__init__.py b/mindquantum/io/__init__.py deleted file mode 100644 index 164d4bc4a..000000000 --- a/mindquantum/io/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Input/Output module for MindQuantum.""" - -from . import display -from . import qasm -from .display import * -from .qasm import * -from .beauty_print import bprint - -__all__ = ['bprint'] -__all__.extend(display.__all__) -__all__.extend(qasm.__all__) -__all__.sort() diff --git a/mindquantum/io/display/_config.py b/mindquantum/io/display/_config.py deleted file mode 100644 index acc65e9a9..000000000 --- a/mindquantum/io/display/_config.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Configuration""" - -CIRCUIT_HTML_FORMAT = """\ -
-
{code}
-""" - -MEA_HTML_FORMAT = """\ -
-
{code}
-""" - -_res_text_drawer_config = { - 'vline': '│', - 'max_size': 60, - 'spilit': 5, - 'hline': '─', - 'cross_mask': '┼', - 'axis_mask': '┴', - 'box_high': '▓', - 'box_low': '▒', - 'deci': 3, -} - -_text_drawer_config = { - 'ctrl_mask': '●', #⨉ - 'circ_line': '─', - 'ctrl_line': '│', - 'cross_mask': '┼', - 'v_n': 1, - 'swap_mask': ['@', '@'], # ✖, ⨯⨯ - 'edge_num': 2, - 'barrier': '‖' -} - -_text_drawer_config['edge'] = _text_drawer_config[ - 'circ_line'] * _text_drawer_config['edge_num'] - -_CIRCUIT_STYLE = {'style': 'blue bold'} -_MEA_RES_STYLE = {'style': 'yellow'} -_DAGGER_MASK = '†' diff --git a/mindquantum/io/display/circuit_text_drawer.py b/mindquantum/io/display/circuit_text_drawer.py deleted file mode 100644 index d52645751..000000000 --- a/mindquantum/io/display/circuit_text_drawer.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Text draw a circuit""" -import numpy as np -from ._config import _text_drawer_config -from ._config import _DAGGER_MASK as _dm - - -def _get_qubit_range(gate): - """_get_qubit_range""" - out = [] - out.extend(gate.obj_qubits) - out.extend(gate.ctrl_qubits) - return out - - -def brick_model(circ): - """Split a circuit into layers.""" - from mindquantum import gates as G - n = circ.n_qubits - v_n = _text_drawer_config['v_n'] - blocks = [] - qubit_hight = np.zeros(n, dtype=int) - for gate in circ: - if isinstance(gate, G.BarrierGate): - qrange = range(n) - else: - qrange = _get_qubit_range(gate) - max_hight = np.max(qubit_hight[range(min(qrange), max(qrange) + 1)]) - if len(blocks) <= max_hight: - blocks.append([]) - blocks[max_hight].append(gate) - qubit_hight[range(min(qrange), max(qrange) + 1)] = max_hight + 1 - blocks = [_single_block_drawer(i, n) for i in blocks] - res = {} - max_q = 0 - for i in range(n): - res[i * (v_n + 1)] = f'q{i}: ' - max_q = max(max_q, len(res[i * (v_n + 1)])) - for i in range(n): - res[i * (v_n + 1)] = res[i * (v_n + 1)].ljust(max_q, ' ') - if i != n - 1: - for j in range(v_n): - res[i * (v_n + 1) + j + 1] = ' ' * max_q - for block in blocks: - for k, v in block.items(): - res[k] += v - return '\n'.join([res[i] for i in range((n - 1) * (v_n + 1) + 1)]) - - -def _single_gate_drawer(gate): - """_single_gate_drawer""" - from mindquantum import gates as G - from mindquantum.core.gates.basic import HERMITIAN_PROPERTIES - if isinstance(gate, G.CNOTGate): - gate = G.X.on(*gate.obj_qubits) - main_text = gate.name - if isinstance(gate, G.SWAPGate): - main_text = _text_drawer_config['swap_mask'][0] - elif issubclass(gate.__class__, G.ParameterGate): - if isinstance(gate, (G.SGate, G.TGate)): - main_text = gate.name + ('†' if gate.daggered else '') - else: - main_text = str(gate.__class__(gate.coeff)) - elif isinstance(gate, G.Measure): - main_text = f"{gate.name}({gate.key})" - elif isinstance(gate, G.NoneParameterGate): - if gate.hermitian_property == HERMITIAN_PROPERTIES['do_hermitian']: - if gate.daggered: - main_text += _dm - main_text = _text_drawer_config['edge'] + main_text + _text_drawer_config[ - 'edge'] - res = {} - for i in gate.obj_qubits: - res[i] = main_text - if isinstance(gate, G.SWAPGate): - max_idx = max(gate.obj_qubits) - lower_txt = _text_drawer_config['swap_mask'][1] - res[max_idx] = _text_drawer_config[ - 'edge'] + lower_txt + _text_drawer_config['edge'] - for i in gate.ctrl_qubits: - res[i] = _text_drawer_config['ctrl_mask'] - res[i] = res[i].center(len(main_text), - _text_drawer_config['circ_line']) - res['len'] = len(main_text) - return res - - -def _single_block_drawer(block, n_qubits): - """single block drawer""" - from mindquantum import gates as G - v_n = _text_drawer_config['v_n'] - text_gates = {} - if isinstance(block[0], G.BarrierGate): - if not block[0].show: - tmp = '' - else: - tmp = _text_drawer_config['barrier'] - for i in range((n_qubits - 1) * v_n + n_qubits): - text_gates[i] = tmp - return text_gates - for gate in block: - text_gate = _single_gate_drawer(gate) - qrange = _get_qubit_range(gate) - for q in range(min(qrange), max(qrange) + 1): - ind = q * (v_n + 1) - if q in qrange: - text_gates[ind] = text_gate[q] - else: - text_gates[ind] = _text_drawer_config['cross_mask'] - text_gates[ind] = text_gates[ind].center( - text_gate['len'], _text_drawer_config['circ_line']) - for q in range(min(qrange), max(qrange)): - for i in range(v_n): - ind = q * (v_n + 1) + i + 1 - text_gates[ind] = _text_drawer_config['ctrl_line'] - text_gates[ind] = text_gates[ind].center(text_gate['len'], ' ') - max_l = max([len(j) for j in text_gates.values()]) - for k, v in text_gates.items(): - if len(v) != max_l: - if k % (v_n + 1) == 0: - text_gates[k] = text_gates[k].center( - max_l, _text_drawer_config['circ_line']) - else: - text_gates[k] = text_gates[k].center(max_l, ' ') - for i in range((n_qubits - 1) * v_n + n_qubits): - if i not in text_gates: - if i % (v_n + 1) == 0: - text_gates[i] = _text_drawer_config['circ_line'] * max_l - else: - text_gates[i] = ' ' * max_l - return text_gates diff --git a/mindquantum/io/display/measure_res_drawer.py b/mindquantum/io/display/measure_res_drawer.py deleted file mode 100644 index 6439b57ce..000000000 --- a/mindquantum/io/display/measure_res_drawer.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Text measure result""" -# from mindquantum.core.gates import MeasureResult -import math -from ._config import _res_text_drawer_config - - -def _trans(v, l, m): - return math.ceil(v / m * l) - - -def measure_text_drawer(res): - """Draw measure result.""" - max_size = _res_text_drawer_config['max_size'] - vline = _res_text_drawer_config['vline'] - hline = _res_text_drawer_config['hline'] - cross_mask = _res_text_drawer_config['cross_mask'] - box_high = _res_text_drawer_config['box_high'] - box_low = _res_text_drawer_config['box_low'] - axis_mask = _res_text_drawer_config['axis_mask'] - split = _res_text_drawer_config['spilit'] - deci = _res_text_drawer_config['deci'] - keys = res.keys - max_shot = max(res.data.values()) - max_prop = max_shot / res.shots - if max_prop == 0: - max_prop = 1 - if max_prop / 0.8 > 1: - max_prop = 1 - else: - max_prop /= 0.8 - ket_exp = 'Keys: ' - ket_exp += ' '.join(keys[::-1]) - s = [f'shots: {res.shots}'] - s.append(ket_exp + vline) - axis_num = '' - axis = '' - for i in range(split): - axis_num = str(round(max_prop / split * (split - i), deci)).rjust( - int(max_size / split)) + axis_num - axis = axis_mask.rjust(int(max_size / split), hline) + axis - axis_num = '0.00' + axis_num[4:] - s[-1] += axis_num - s.append(hline * len(ket_exp) + cross_mask + axis) - for k, v in res.data.items(): - state = k - state = state.rjust(len(ket_exp)) - state += vline - state += (box_high if v == max_shot else box_low) * _trans( - v / res.shots, max_size, max_prop) - s.append(state) - s.append(' ' * len(ket_exp) + vline + ' ' * max_size) - return s diff --git a/mindquantum/io/qasm/hiqasm.py b/mindquantum/io/qasm/hiqasm.py deleted file mode 100644 index 234aa1e23..000000000 --- a/mindquantum/io/qasm/hiqasm.py +++ /dev/null @@ -1,379 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""hiqqasm""" -import numpy as np -from .openqasm import _find_qubit_id -from .openqasm import u3 - -HIQASM_GATE_SET = { - '0.1': { - 'np': ['X', 'Y', 'Z', 'S', 'T', 'H', 'CNOT', 'CZ', 'ISWAP', 'CCNOT'], - 'p': [ - 'RX', 'RY', 'RZ', 'U', 'CRX', 'CRY', 'CRZ', 'XX', 'YY', 'ZZ', - 'CCRX', 'CCRY', 'CCRZ' - ], - } -} - - -def random_hiqasm(n_qubits, gate_num, version='0.1', seed=42): - """ - Generate random hiqasm supported circuit. - - Args: - n_qubits (int): Total number of qubit in this quantum circuit. - gate_num (int): Total number of gate in this quantum circuit. - version (str): version of HIQASM. Default: '0.1'. - seed (int): The random seed to generate this random quantum circuit. - - Returns: - str, quantum in HIQASM format. - """ - if not isinstance(n_qubits, (int, np.int64)) or n_qubits < 1: - raise ValueError( - f'qubit number should be a non negative int, but get {n_qubits}.') - if not isinstance(gate_num, (int, np.int64)) or gate_num < 1: - raise ValueError( - f'gate number should be a non negative int, but get {n_qubits}.') - np.random.seed(seed) - gate_set = HIQASM_GATE_SET[version] - np_set = gate_set['np'] - p_set = gate_set['p'] - if version == '0.1': - qasm = [ - '# HIQASM 0.1', '# Instruction stdins', '', - f'ALLOCATE q {n_qubits}', 'RESET q' - ] - if n_qubits == 1: - np_set = np_set[:6] - p_set = p_set[:4] - elif n_qubits == 2: - np_set = np_set[:9] - p_set = p_set[:10] - while len(qasm) - 5 < gate_num: - g_set = np.random.choice(np.array([np_set, p_set], dtype=object)) - gate = np.random.choice(g_set) - pval = np.random.uniform(-np.pi, np.pi, 3) - qidx = np.arange(n_qubits) - np.random.shuffle(qidx) - if gate in ['X', 'Y', 'Z', 'S', 'T', 'H']: - qasm.append(f'{gate} q[{qidx[0]}]') - elif gate in ['CNOT', 'CZ', 'ISWAP']: - qasm.append(f'{gate} q[{qidx[0]}],q[{qidx[1]}]') - elif gate == 'CCNOT': - qasm.append('{} q[{}],q[{}],q[{}]'.format(gate, *qidx[:3])) - elif gate in ['RX', 'RY', 'RZ']: - qasm.append(f'{gate} q[{qidx[0]}] {pval[0]}') - elif gate == 'U': - qasm.append('U q[{}] {},{},{}'.format(qidx[0], *pval)) - elif gate in ['CRX', 'CRY', 'CRZ', 'XX', 'YY', 'ZZ']: - qasm.append('{} q[{}],q[{}] {}'.format(gate, *qidx[:2], - pval[0])) - elif gate in ['CCRX', 'CCRY', 'CCRZ']: - qasm.append('{} q[{}],q[{}],q[{}] {}'.format( - gate, *qidx[:3], pval[0])) - else: - raise NotImplementedError( - f"gate {gate} not implement in HIQASM {version}") - qasm.append('MEASURE q') - qasm.append('DEALLOCATE q') - qasm.append('') - return '\n'.join(qasm) - raise NotImplementedError(f'version {version} not implemented') - - -class HiQASM: - """ - Convert a circuit to hiqasm format. - """ - def __init__(self): - from mindquantum import Circuit - self.circuit = Circuit() - self.cmds = [] - - def filter(self, cmds): - """filter empty cmds and head.""" - out = [] - version = None - n_qubits = None - for cmd in cmds: - cmd = cmd.strip() - if not cmd: - continue - if startswithany(cmd, '#', 'ALLOCATE', 'RESET', 'DEALLOCATE'): - if cmd.startswith('# HIQASM'): - version = cmd.split(' ')[-1] - if cmd.startswith('ALLOCATE q '): - n_qubits = int(cmd.split(' ')[-1]) - continue - out.append(cmd) - if n_qubits is None: - raise ValueError('Can not find qubit number in qasm') - if version is None: - raise ValueError('Can not find version in qasm') - return out, version, n_qubits - - def to_string(self, circuit, version='0.1'): - """Convert circuit to hiqasm""" - from mindquantum import gates as G - if version == '0.1': - if circuit.parameterized: - raise ValueError( - "Cannot convert parameterized circuit to HIQASM") - self.circuit = circuit - self.cmds = [ - f"# HIQASM {version}", "# Instruction stdins", "", - f'ALLOCATE q {circuit.n_qubits}', 'RESET q' - ] - for gate in circuit: - ctrl_qubits = gate.ctrl_qubits - n_ctrl_qubits = len(ctrl_qubits) - obj_qubits = gate.obj_qubits - n_obj_qubits = len(obj_qubits) - if n_ctrl_qubits > 2: - raise ValueError( - f"HIQASM do not support more than two control qubits gate: {gate}" - ) - if n_obj_qubits > 2: - raise ValueError( - f"HIQASM do not support more than two object qubit gate: {gate}" - ) - if self._to_string_non_parametric(gate, ctrl_qubits, - obj_qubits, version): - pass - elif self._to_string_parametric(gate, ctrl_qubits, obj_qubits, - version): - pass - elif isinstance(gate, G.ISWAPGate): - if n_ctrl_qubits == 0: - self.cmds.append( - f'ISWAP q[{obj_qubits[0]}],q[{obj_qubits[1]}]') - else: - _not_implement(version, gate) - elif isinstance(gate, G.Measure): - if n_ctrl_qubits == 0: - self.cmds.append(f'MEASURE q[{obj_qubits[0]}]') - else: - _not_implement(version, gate) - else: - _not_implement(version, gate) - self.cmds.append('DEALLOCATE q') - self.cmds.append('') - else: - raise NotImplementedError - return '\n'.join(self.cmds) - - def _to_string_non_parametric(self, gate, ctrl_qubits, obj_qubits, - version): - """Conversion of simple gates to string""" - from mindquantum.core import gates as G - n_ctrl_qubits = len(ctrl_qubits) - - if isinstance(gate, G.XGate): - if n_ctrl_qubits == 0: - self.cmds.append(f'X q[{obj_qubits[0]}]') - elif n_ctrl_qubits == 1: - self.cmds.append( - f'CNOT q[{ctrl_qubits[0]}],q[{obj_qubits[0]}]') - elif n_ctrl_qubits == 2: - self.cmd.append( - f'CCNOT q[{ctrl_qubits[0]}],q[{ctrl_qubits[1]}],q[{obj_qubits[0]}]' - ) - else: - _not_implement(version, gate) - elif isinstance(gate, G.YGate): - if n_ctrl_qubits == 0: - self.cmds.append(f'Y q[{obj_qubits[0]}]') - else: - _not_implement(version, gate) - elif isinstance(gate, G.ZGate): - if n_ctrl_qubits == 0: - self.cmds.append(f'Z q[{obj_qubits[0]}]') - elif n_ctrl_qubits == 1: - self.cmds.append(f'CZ q[{ctrl_qubits[0]}],q[{obj_qubits[0]}]') - else: - _not_implement(version, gate) - elif isinstance(gate, G.SGate): - if n_ctrl_qubits == 0: - if gate.dagger: - _not_implement(version, gate) - self.cmds.append(f'S q[{obj_qubits[0]}]') - else: - _not_implement(version, gate) - elif isinstance(gate, G.TGate): - if n_ctrl_qubits == 0: - if gate.daggered: - _not_implement(version, gate) - self.cmds.append(f'T q[{obj_qubits[0]}]') - else: - _not_implement(version, gate) - elif isinstance(gate, G.HGate): - if n_ctrl_qubits == 0: - self.cmds.append(f'H q[{obj_qubits[0]}]') - else: - _not_implement(version, gate) - else: - return False - return True - - def _to_string_parametric(self, gate, ctrl_qubits, obj_qubits, version): - """Conversion of parametric gates to string""" - from mindquantum.core import gates as G - n_ctrl_qubits = len(ctrl_qubits) - - if isinstance(gate, (G.RX, G.RY, G.RZ)): - if n_ctrl_qubits == 0: - self.cmds.append( - f'{gate.name} q[{obj_qubits[0]}] {gate.coeff}') - elif n_ctrl_qubits == 1: - self.cmds.append( - f'C{gate.name} q[{ctrl_qubits[0]}],q[{obj_qubits[0]}] {gate.coeff}' - ) - elif n_ctrl_qubits == 2: - self.cmds.append( - f'CC{gate.name} q[{ctrl_qubits[0]}],q[{ctrl_qubits[1]}],q[{obj_qubits[0]}] {gate.coeff}' - ) - else: - _not_implement(version, gate) - elif isinstance(gate, (G.XX, G.YY, G.ZZ)): - if n_ctrl_qubits == 0: - self.cmds.append( - f'{gate.name} q[{obj_qubits[0]}],q[{obj_qubits[1]}] {gate.coeff}' - ) - else: - _not_implement(version, gate) - else: - return False - return True - - def from_string(self, string): - """Read a hiqasm string""" - cmds = string.split('\n') - self.cmds, version, n_qubits = self.filter(cmds) - if version == '0.1': - self.trans_v01(self.cmds, n_qubits) - else: - raise ValueError(f'HIQASM {version} not implement yet') - return self.circuit - - def from_file(self, file_name): - """Read a hiqasm file""" - with open(file_name, 'r') as f: - cmds = f.readlines() - self.from_string('\n'.join(cmds)) - return self.circuit - - def to_file(self, file_name, circuit, version='0.1'): - cs = self.to_string(circuit, version) - with open(file_name, 'w') as f: - f.writelines(cs) - print(f"write circuit to {file_name} finished!") - - def trans_v01(self, cmds, n_qubits): - """Trans method for hiqasm version 0.1""" - from mindquantum import Circuit - import mindquantum.core.gates as G - self.circuit = Circuit() - for cmd in cmds: - q = _find_qubit_id(cmd) - if cmd.startswith('CNOT '): - self.circuit.x(q[1], q[0]) - elif cmd.startswith('CZ '): - self.circuit.z(q[1], q[0]) - elif cmd.startswith('ISWAP '): - self.circuit += G.ISWAP.on(q[:2]) - elif cmd.startswith('CCNOT '): - self.circuit.x(q[-1], q[:2]) - elif cmd.startswith('CRX '): - self.circuit.rx(*_extr_parameter(cmd), q[1], q[0]) - elif cmd.startswith('CRY '): - self.circuit.ry(*_extr_parameter(cmd), q[1], q[0]) - elif cmd.startswith('CRZ '): - self.circuit.rz(*_extr_parameter(cmd), q[1], q[0]) - elif cmd.startswith('XX '): - self.circuit.xx(*_extr_parameter(cmd), q[:2]) - elif cmd.startswith('YY '): - self.circuit.yy(*_extr_parameter(cmd), q[:2]) - elif cmd.startswith('ZZ '): - self.circuit.zz(*_extr_parameter(cmd), q[:2]) - elif cmd.startswith('CCRX '): - self.circuit.rx(*_extr_parameter(cmd), q[-1], q[:2]) - elif cmd.startswith('CCRY '): - self.circuit.ry(*_extr_parameter(cmd), q[-1], q[:2]) - elif cmd.startswith('CCRZ '): - self.circuit.rz(*_extr_parameter(cmd), q[-1], q[:2]) - elif cmd.startswith('MEASURE '): - q = _find_qubit_id(cmd) - if q: - self.circuit.measure(f'k{self.circuit.all_measures.size}', - q[0]) - else: - for midx in range(n_qubits): - self.circuit.measure( - f'k{self.circuit.all_measures.size}', midx) - elif self._trans_v01_single_qubit(cmd, q[0]): - pass - else: - raise ValueError(f"transfer cmd {cmd} not implement yet!") - - def _trans_v01_single_qubit(self, cmd, qubit): - """Trans method for hiqasm version 0.1 (single-qubit gates)""" - from mindquantum.core import gates as G - if cmd.startswith('H '): - self.circuit.h(qubit) - elif cmd.startswith('X '): - self.circuit.x(qubit) - elif cmd.startswith('Y '): - self.circuit.y(qubit) - elif cmd.startswith('Z '): - self.circuit.z(qubit) - elif cmd.startswith('S '): - self.circuit += G.S.on(qubit) - elif cmd.startswith('T '): - self.circuit += G.T.on(qubit) - elif cmd.startswith('U '): - self.circuit += u3(*_extr_parameter(cmd), qubit) - elif cmd.startswith('RX '): - self.circuit.rx(*_extr_parameter(cmd), qubit) - elif cmd.startswith('RY '): - self.circuit.ry(*_extr_parameter(cmd), qubit) - elif cmd.startswith('RZ '): - self.circuit.rz(*_extr_parameter(cmd), qubit) - else: - return False - return True - - -def _extr_parameter(cmd): - """extra parameter for parameterized gate in hiqasm cmd""" - return [float(i) for i in cmd.split(' ')[-1].split(',')] - - -def startswithany(cmd, *s): - """Checkout whether cmd starts with any string in s""" - for i in s: - if cmd.startswith(i): - return True - return False - - -def _not_implement(version, gate): - """not implement error""" - raise ValueError(f'{gate} not implement in HIQASM {version}') - - -if __name__ == '__main__': - print(random_hiqasm(1, 10)) diff --git a/mindquantum/io/qasm/openqasm.py b/mindquantum/io/qasm/openqasm.py deleted file mode 100644 index bf7a96309..000000000 --- a/mindquantum/io/qasm/openqasm.py +++ /dev/null @@ -1,225 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""openqasm""" -import numpy as np - - -def _find_qubit_id(cmd): - """Find qubit id in openqasm cmd""" - left = [] - right = [] - for i, j in enumerate(cmd): - if j == '[': - left.append(i) - elif j == ']': - right.append(i) - if len(left) != len(right): - raise ValueError(f"Parsing failed for cmd {cmd}") - idx = [] - for l, r in zip(left, right): - idx.append(int(cmd[l + 1:r])) - return idx - - -def _extr_parameter(cmd): - """extra parameter for parameterized gate in openqasm cmd""" - l = cmd.find('(') - r = cmd.find(')') - if l == -1 or r == -1: - raise ValueError(f"no parameter found in cmd {cmd}") - all_expre = cmd[l + 1:r] - all_expre = all_expre.split(',') - out = [] - for expre in all_expre: - if 'pi' in expre: - expre = expre.replace('pi', str(np.pi)) - if '*' in expre: - tmp = expre.split('*') - if len(tmp) != 2: - raise ValueError(f"cannot parse cmd {cmd}") - expre = str(float(tmp[0]) * float(tmp[1])) - if '/' in expre: - tmp = expre.split('/') - if len(tmp) != 2: - raise ValueError(f"cannot parse cmd {cmd}") - expre = str(float(tmp[0]) / float(tmp[1])) - out.append(float(expre)) - return out[0] if len(all_expre) == 1 else out - - -def u3(theta, psi, lambd, q): - """decomp u3 gate""" - from mindquantum import Circuit - circ = Circuit().rz(psi + 3 * np.pi, q) - circ.rx(np.pi / 2, q).rz(theta + np.pi, q) - circ.rx(np.pi / 2, q).rz(lambd, q) - return circ - - -def u1(lambd, q): - """openqasm u1 gate""" - from mindquantum import Circuit - return Circuit().rz(lambd, q) - - -def isgateinstance(gate, gates): - """Check whether gate is any instance of supported gate type""" - if isinstance(gates, list): - gates = (gates, ) - for gate_test in gates: - for g in gate_test: - if isinstance(gate, g): - return True - return False - - -class OpenQASM: - """ - Convert a circuit to openqasm format - """ - def __init__(self): - from mindquantum import Circuit - self.circuit = Circuit() - self.cmds = [] - - def to_string(self, circuit, version="2.0"): - """ - Convert circuit to different version of openqasm - """ - import mindquantum.core.gates as G - single_np = [G.XGate, G.YGate, G.ZGate] - single_p = [G.RX, G.RY, G.RZ, G.PhaseShift] - double_np = [G.SWAPGate, G.CNOTGate] - double_p = [G.XX, G.YY, G.ZZ] - if version == "2.0": - self.circuit = circuit - self.cmds = [f"OPENQASM {version};", "include \"qelib1.inc\";"] - self.cmds.append(f"qreg q[{circuit.n_qubits}];") - for gate in self.circuit: - if isgateinstance(gate, (single_np, single_p)): - if len(gate.ctrl_qubits) > 1: - raise ValueError( - f"Multiple control for gate {gate} not implement") - if isgateinstance(gate, single_np): - obj = gate.obj_qubits[0] - if gate.ctrl_qubits: - ctrl = gate.ctrl_qubits[0] - self.cmds.append( - f"c{gate.name.lower()} q[{ctrl}],q[{obj}];") - else: - self.cmds.append(f"{gate.name.lower()} q[{obj}];") - else: - obj = gate.obj_qubits[0] - p = gate.coeff - if gate.ctrl_qubits: - ctrl = gate.ctrl_qubits[0] - self.cmds.append( - f"c{gate.name.lower()}({p}) q[{ctrl}],q[{obj}];" - ) - else: - self.cmds.append( - f"{gate.name.lower()}({p}) q[{obj}];") - if isgateinstance(gate, (double_np, double_p)): - if gate.ctrl_qubits: - raise ValueError( - f"control two qubits gate {gate} not implement") - if isgateinstance(gate, double_np): - obj = gate.obj_qubits - if isinstance(gate, G.SWAPGate): - self.cmds.append(f"cx q[{obj[1]}],q[{obj[0]}];") - self.cmds.append(f"cx q[{obj[0]}],q[{obj[1]}];") - self.cmds.append(f"cx q[{obj[1]}],q[{obj[0]}];") - if isinstance(gate, G.CNOTGate): - self.cmds.append(f"cx q[{obj[1]}],q[{obj[0]}];") - else: - obj = gate.obj_qubits - p = gate.coeff - self.cmds.append( - f"{gate.name.lower()}({p}) q[{obj[0]}],q[{obj[1]}];" - ) - return self.cmds - raise ValueError(f"openqasm version {version} not implement") - - def to_file(self, file_name, circuit, version="2.0"): - """ - Convert circuit to different version of openqasm and save into a file. - """ - self.to_string(circuit, version) - with open(file_name, 'w') as f: - f.writelines("\n".join(self.cmds)) - print(f"write circuit to {file_name} finished!") - - def from_file(self, file_name): - """ - Read a openqasm file - """ - with open(file_name, 'r') as f: - cmds = f.readlines() - self.cmds, version = self.filter(cmds) - if version == '2.0': - self.trans_v2(self.cmds) - else: - raise ValueError(f"OPENQASM {version} not implement yet") - - def filter(self, cmds): - """ - filter empty cmds and head. - """ - out = [] - version = None - for cmd in cmds: - cmd = cmd.strip() - if not cmd or cmd.startswith('//') or cmd.startswith( - 'include') or cmd.startswith("qreg"): - pass - elif cmd.startswith('OPENQASM'): - version = cmd.split(' ')[-1][:-1] - else: - out.append(cmd[:-1]) - return out, version - - def trans_v2(self, cmds): - """ - trans method for openqasm version 2 - """ - from mindquantum import Circuit - from mindquantum.core.circuit import controlled - self.circuit = Circuit() - for cmd in cmds: - q = _find_qubit_id(cmd) - if cmd.startswith("h "): - self.circuit.h(q[0]) - elif cmd.startswith("x "): - self.circuit.x(q[0]) - elif cmd.startswith("y "): - self.circuit.y(q[0]) - elif cmd.startswith("cx "): - self.circuit.x(q[1], q[0]) - elif cmd.startswith("cz "): - self.circuit.z(*q[::-1]) - elif cmd.startswith("rz("): - self.circuit.rz(_extr_parameter(cmd), q[0]) - elif cmd.startswith("ry("): - self.circuit.ry(_extr_parameter(cmd), q[0]) - elif cmd.startswith("rx("): - self.circuit.rx(_extr_parameter(cmd), q[0]) - elif cmd.startswith("u3("): - self.circuit += u3(*_extr_parameter(cmd), q[0]) - elif cmd.startswith("cu1("): - self.circuit += controlled(u1(_extr_parameter(cmd), - q[1]))(q[0]) - else: - raise ValueError(f"transfer cmd {cmd} not implement yet!") diff --git a/mindquantum/nn/__init__.py b/mindquantum/nn/__init__.py new file mode 100644 index 000000000..99a41c272 --- /dev/null +++ b/mindquantum/nn/__init__.py @@ -0,0 +1,29 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Quantum neural networks operators and cells.""" + +from .pqc import generate_pqc_operator, PQC +from .mindquantum_layer import MindQuantumLayer +from .evolution import generate_evolution_operator, Evolution +from .mindquantum_ansatz_only_layer import MindQuantumAnsatzOnlyLayer +from .mindquantum_ansatz_only_layer import MindQuantumAnsatzOnlyOperator + +__all__ = [ + "generate_pqc_operator", "PQC", "MindQuantumLayer", + "generate_evolution_operator", "Evolution", "MindQuantumAnsatzOnlyLayer", + "MindQuantumAnsatzOnlyOperator" +] + +__all__.sort() diff --git a/mindquantum/nn/_check_qnn_input.py b/mindquantum/nn/_check_qnn_input.py new file mode 100644 index 000000000..1552857a2 --- /dev/null +++ b/mindquantum/nn/_check_qnn_input.py @@ -0,0 +1,74 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Check input for quantum neural network.""" + +from collections.abc import Iterable +from mindquantum.circuit import Circuit + + +def _check_circuit(circuit, msg): + if not isinstance(circuit, Circuit): + raise TypeError("{} requires a quantum circuit, but get {}!".format( + msg, type(circuit))) + + +def _check_non_parameterized_circuit(circuit: Circuit): + if not isinstance(circuit, Circuit): + raise TypeError( + "Requires a non parameterized quantum circuit, but get {}!".format( + type(circuit))) + for g in circuit: + if g.isparameter: + raise ValueError( + "Requires a non parameterized quantum circuit, but {} is parameterized gate!" + .format(g)) + + +def _check_type_or_iterable_type(inputs, require, msg): + if not isinstance(inputs, Iterable): + if not isinstance(inputs, require): + raise TypeError( + "{msg} requires {req} or several {req}s, but get {inp}!". + format(msg=msg, req=require, inp=type(inputs))) + else: + for inp in inputs: + if not isinstance(inp, require): + raise TypeError( + "{msg} requires {req} or several {req}s, but {inp} is not {req}!" + .format(msg=msg, req=require, inp=inp)) + + +def _check_list_of_string(inputs, msg): + if not isinstance(inputs, list): + raise TypeError("{} requires a list of string, but get {}!".format( + msg, type(inputs))) + for inp in inputs: + if not isinstance(inp, str): + raise TypeError( + "{} requires a list of string, but {} is not string.".format( + msg, inp)) + + +def _check_parameters_of_circuit(encoder_params_names, ansatz_params_names, + circuit: Circuit): + _check_list_of_string(encoder_params_names, 'Encoder parameter names') + _check_list_of_string(ansatz_params_names, 'Ansatz parameter names') + all_names = [] + all_names.extend(encoder_params_names) + all_names.extend(ansatz_params_names) + circ_names = circuit.para_name + if not set(all_names) == set(circ_names): + raise ValueError( + "Parameter names you input not match with parameters in circuit.") diff --git a/mindquantum/nn/evolution.py b/mindquantum/nn/evolution.py new file mode 100644 index 000000000..34027ad38 --- /dev/null +++ b/mindquantum/nn/evolution.py @@ -0,0 +1,147 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Mindspore quantum simulator evolution operator.""" + +import numpy as np +from mindquantum.ops import QubitOperator +from mindspore import Tensor +from mindspore.ops.primitive import PrimitiveWithInfer +from mindspore.ops.primitive import prim_attr_register +from mindspore._checkparam import Validator as validator +from mindspore.common import dtype as mstype +from mindquantum.gate import Hamiltonian +from ._check_qnn_input import _check_circuit +from ._check_qnn_input import _check_non_parameterized_circuit +from ._check_qnn_input import _check_type_or_iterable_type +from ._check_qnn_input import _check_parameters_of_circuit + + +class Evolution(PrimitiveWithInfer): + r""" + Inputs of this operation is generated by MindQuantum framework. + + Inputs: + - **n_qubits** (int) - The qubit number of quantum simulator. + - **param_names** (list[str]) - The parameters names. + - **gate_names** (list[str]) - The name of each gate. + - **gate_matrix** (list[list[list[list[float]]]]) - Real part and image part of the matrix of quantum gate. + - **gate_obj_qubits** (list[list[int]]) - Object qubits of each gate. + - **gate_ctrl_qubits** (list[list[int]]) - Control qubits of each gate. + - **gate_params_names** (list[list[str]]) - Parameter names of each gate. + - **gate_coeff** (list[list[float]]) - Coefficient of eqch parameter of each gate. + - **gate_requires_grad** (list[list[bool]]) - Whether to calculate gradient of parameters of gates. + - **hams_pauli_coeff** (list[list[float]]) - Coefficient of pauli words. + - **hams_pauli_word** (list[list[list[str]]]) - Pauli words. + - **hams_pauli_qubit** (list[list[list[int]]]) - The qubit that pauli matrix act on. + + Outputs: + - **Quantum state** (Tensor) - The quantum state after evolution. + + Supported Platforms: + ``CPU`` + """ + @prim_attr_register + def __init__(self, n_qubits, param_names, gate_names, gate_matrix, + gate_obj_qubits, gate_ctrl_qubits, gate_params_names, + gate_coeff, gate_requires_grad, hams_pauli_coeff, + hams_pauli_word, hams_pauli_qubit): + """Initialize Evolutino""" + self.init_prim_io_names(inputs=['param_data'], outputs=['state']) + self.n_qubits = n_qubits + + def check_shape_size(self, param_data): + if len(param_data) != 1: + raise ValueError("PQC input param_data should have dimension size \ +equal to 1, but got {}.".format(len(param_data))) + + def infer_shape(self, param_data): + self.check_shape_size(param_data) + return [1 << self.n_qubits, 2] + + def infer_dtype(self, param_data): + args = {'param_data': param_data} + validator.check_tensors_dtypes_same_and_valid(args, mstype.float_type, + self.name) + return param_data + + def __call__(self, tmp=None): + if tmp is None: + if self.param_names: + raise ValueError( + "Parameterized circuit shuold have parameter input.") + tmp = Tensor(np.array([0]).astype(np.float32)) + state = super().__call__(tmp) + state = state.asnumpy() + state = state[:, 0] + state[:, 1] * 1j + return state + + +def generate_evolution_operator(circuit, param_names=None, hams=None): + """ + A method to generate a parameterized quantum circuit simulation operator. + + Args: + circuit (Circuit): The whole circuit combined with + encoder circuit and ansatz circuit, can be a parameterized circuit + or a non parameterized circuit. + param_names (list[str]): The list of parameter names, if None, than the + circuit should be a non parameterized circuit, otherwise, param_names will + be take from circuit. Default: None. + hams (Union[Hamiltonian, list[Hamiltonian]]): The measurement + hamiltonian. If None, than no hamiltonians will be applied on the + final quantum state. Default: None. + + Returns: + Evolution, A parameterized quantum circuit simulator operator supported by mindspore framework. + + Examples: + >>> import numpy as np + >>> from mindspore import Tensor + >>> import mindquantum.gate as G + >>> from mindquantum import Circuit + >>> from mindquantum.nn import generate_evolution_operator + >>> circ = Circuit(G.RX('a').on(0)) + >>> evol = generate_evolution_operator(circ, ['a']) + >>> state = evol(Tensor(np.array([0.5]).astype(np.float32))) + array([0.9689124+0.j , 0. -0.24740396j], dtype=complex64) + >>> G.RX(0.5).matrix()[:, 0] + array([0.96891242+0.j , 0. -0.24740396j]) + """ + if param_names is None: + param_names = circuit.para_name + if not param_names: + _check_non_parameterized_circuit(circuit) + else: + _check_circuit(circuit, 'circuit') + _check_parameters_of_circuit([], param_names, circuit) + if hams is not None: + _check_type_or_iterable_type(hams, Hamiltonian, 'Hamiltonian') + if isinstance(hams, Hamiltonian): + hams = [hams] + if hams is None: + ham_ms_data = Hamiltonian(QubitOperator()).mindspore_data() + else: + ham_ms_data = {} + for ham in hams: + for k, v in ham.mindspore_data().items(): + if k not in ham_ms_data: + ham_ms_data[k] = [v] + else: + ham_ms_data[k].append(v) + evol = Evolution(circuit.n_qubits, + param_names=param_names, + **circuit.mindspore_data(), + **ham_ms_data) + return evol diff --git a/mindquantum/nn/mindquantum_ansatz_only_layer.py b/mindquantum/nn/mindquantum_ansatz_only_layer.py new file mode 100644 index 000000000..296b9b95a --- /dev/null +++ b/mindquantum/nn/mindquantum_ansatz_only_layer.py @@ -0,0 +1,169 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Basic mindquanutm neural layer with ansatz only.""" + +import numpy as np +from mindspore import Tensor +from mindquantum.circuit import Circuit +from .mindquantum_layer import MindQuantumLayer + + +class MindQuantumAnsatzOnlyOperator(MindQuantumLayer): + """ + An Ansatz only Mindquantum operator. + + This operator only need an ansatz circuit and the parameter data for ansatz + circuit. + + Args: + param_names (list[str]): Parameters names of ansatz circuit. + The order of this parameters is the same as the order of trainable + parameters. + circuit (Circuit): The ansatz circuit. + measurements (Union[Hamiltonian, list[Hamiltonian], Projector, list[Projector]]): + Hamiltonian or a list of Hamiltonian for measurement. + n_threads (int): Number of threads for data parallel. Default: 1. + + Inputs: + - **input** (Tensor) - Tensor of shape :math:`(E_{in}, )`, + where :math:`E_{in}` is the number of parameters in ansatz circuit. + + Outputs: + Tensor of shape :math:`(1, 1)`. + + Supported Platforms: + ``CPU`` + + Examples: + >>> from mindquantum import Circuit, RX, Hamiltonian + >>> from mindquantum.ops import QubitOperator + >>> from mindquantum.nn import MindQuantumAnsatzOnlyOperator + >>> from mindspore import Tensor + >>> import numpy as np + >>> circuit = Circuit(RX('a').on(0)) + >>> ham = Hamiltonian(QubitOperator('Z0')) + >>> mea = MindQuantumAnsatzOnlyOperator(circuit.para_name, circuit, ham) + >>> data = Tensor(np.array([0.5]).astype(np.float32)) + >>> mea(data) + Tensor(shape=[1, 1], dtype=Float32, value= + [[ 8.77582550e-01]]) + """ + def __init__(self, param_names, circuit, measurements, n_threads=1): + circuit, dummy_para = _add_dummy_encoder(circuit) + super(MindQuantumAnsatzOnlyOperator, + self).__init__([dummy_para], param_names, circuit, measurements, + 'normal', n_threads) + self.fake_data = Tensor(np.array([[0]]).astype(np.float32)) + del self.weight + + def construct(self, data): + x, _, _ = self.pqc(self.fake_data, data) + return x + + +def _add_dummy_encoder(circ): + """add a dummy parameterized gate""" + para_name = circ.para_name + index = 0 + while True: + name = f'_d_{index}' + if name not in para_name: + dummy_circ = Circuit().rx(name, 0).no_grad() + return dummy_circ + circ, name + index += 1 + + +class MindQuantumAnsatzOnlyLayer(MindQuantumLayer): + """ + An ansatz only trainable Mindquantum layer. + + A mindquantum layer simulate a parameterized quantum circuit and get the + measurement result. The quantum circuit is construct only by an ansatz circuit. + + Args: + param_names (list[str]): Parameters names of ansatz circuit. + The order of this parameters is the same as the order of trainable + parameters. + circuit (Circuit): The ansatz circuit. + measurements (Union[Hamiltonian, list[Hamiltonian], Projector, list[Projector]]): + Hamiltonian or a list of Hamiltonian for measurement. + weight_init (Union[Tensor, str, Initializer, numbers.Number]): The + trainable weight_init parameter. The dtype is same as input x. The + values of str refer to the function `initializer`. + Default: 'normal'. + n_threads (int): Number of threads for data parallel. Default: 1. + + Inputs: + No inputs needed. + + Outputs: + Tensor of shape :math:`(1, 1)`. + + Supported Platforms: + ``CPU`` + + Examples: + >>> from mindquantum import Circuit, H, RX, RY, RZ, Hamiltonian + >>> from mindquantum.nn import MindQuantumAnsatzOnlyLayer + >>> from mindquantum.ops import QubitOperator + >>> from mindspore import Tensor + >>> import mindspore.nn as nn + >>> import numpy as np + >>> circuit = Circuit([H.on(0), RZ(0.4).on(0), RX('a').on(0), RY('b').on(0)]) + >>> ham = Hamiltonian(QubitOperator('Z0')) + >>> init = Tensor(np.array([0, 0]).astype(np.float32)) + >>> net = MindQuantumAnsatzOnlyLayer(circuit.para_name, circuit, ham, init) + >>> opti = nn.Adam(net.trainable_params(), learning_rate=0.8) + >>> train_net = nn.TrainOneStepCell(net, opti) + >>> for i in range(1000): + ... train_net() + >>> net() + Tensor(shape=[1, 1], dtype=Float32, value= + [[-1.00000000e+00]]) + >>> net.weight.asnumpy() + array([-4.712389 , 1.9707963], dtype=float32) + + """ + def __init__(self, + param_names, + circuit, + measurements, + weight_init='normal', + n_threads=1): + circuit, dummy_para = _add_dummy_encoder(circuit) + super(MindQuantumAnsatzOnlyLayer, + self).__init__([dummy_para], param_names, circuit, measurements, + weight_init, n_threads) + self.fake_data = Tensor(np.array([[0]]).astype(np.float32)) + + def construct(self): + x, _, _ = self.pqc(self.fake_data, self.weight) + return x + + def final_state(self, measurements=None): + """ + Get the quantum state after evolution. + + Args: + measurements (Hamiltonian): Hamiltonian for measurement. If None, no hamiltonians + will be used. Default: None. + + Returns: + numpy.ndarray, the final quantum state. + """ + fake_data = Tensor(np.array([]).astype(np.float32)) + return super().final_state(fake_data, + self.weight, + measurements=measurements) diff --git a/mindquantum/nn/mindquantum_layer.py b/mindquantum/nn/mindquantum_layer.py new file mode 100644 index 000000000..7c16f6b14 --- /dev/null +++ b/mindquantum/nn/mindquantum_layer.py @@ -0,0 +1,141 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Basic mindquanutm neural layer.""" + +import numpy as np +from mindspore import Tensor +import mindspore.nn as nn +from mindspore.common.parameter import Parameter +from mindspore.common.initializer import initializer +from .pqc import generate_pqc_operator +from .evolution import generate_evolution_operator + + +class MindQuantumLayer(nn.Cell): + """ + A trainable Mindquantum layer. + + A mindquantum layer simulate a parameterized quantum circuit and get the + measurement result. The quantum circuit is construct by a encode circuit + and a trainable ansatz circuit. The encode circuit will encode classical + data into quantum state, and the trainable ansatz circuit apply on the + quantum state. + + Args: + encoder_params_names (list[str]): Parameters names of encoder circuit. + The order of this parameters is the same as the order of data + passed in construct. + ansatz_params_names (list[str]): Parameters names of ansatz circuit. + The order of this parameters is the same as the order of trainable + parameters. + circuit (Circuit): Quantum circuit construct by + encode circuit and ansatz circuit. + measurements (Union[Hamiltonian, list[Hamiltonian], Projector, list[Projector]]): + Hamiltonian or a list of Hamiltonian for measurement. + weight_init (Union[Tensor, str, Initializer, numbers.Number]): The + trainable weight_init parameter. The dtype is same as input x. The + values of str refer to the function `initializer`. + Default: 'normal'. + n_threads (int): Number of threads for data parallel. Default: 1. + + Inputs: + - **input** (Tensor) - Tensor of shape :math:`(N, E_{in})`, where :math:`N` is batch + size, :math:`E_{in}` is the number of parameters in encoder circuit. + + Outputs: + Tensor of shape :math:`(N, H_{out})`, where :math:`H_{out}` is the + number of hamiltonians. + + Supported Platforms: + ``CPU`` + + Examples: + >>> from mindquantum.ops import QubitOperator + >>> from mindquantum.nn import MindQuantumLayer + >>> from mindquantum import Circuit, Hamiltonian + >>> import mindquantum.gate as G + >>> encoder_circ = Circuit([G.RX('a').on(0)]) + >>> encoder_circ.no_grad() + >>> ansatz = Circuit([G.RY('b').on(0)]) + >>> ham = Hamiltonian(QubitOperator('Z0')) + >>> net = MindQuantumLayer(['a'], ['b'], encoder_circ + ansatz, ham) + >>> res = net(Tensor(np.array([[1.0]]).astype(np.float32))) + >>> res.asnumpy() + array([[0.54030216]], dtype=float32) + """ + def __init__(self, + encoder_params_names, + ansatz_params_names, + circuit, + measurements, + weight_init='normal', + n_threads=1): + super(MindQuantumLayer, self).__init__() + self.circuit = circuit + self.measurements = measurements + self.encoder_params_names = encoder_params_names + self.ansatz_params_names = ansatz_params_names + self.pqc = generate_pqc_operator(encoder_params_names, + ansatz_params_names, + circuit, + measurements, + n_threads=n_threads) + self.weight = Parameter(initializer(weight_init, + len(ansatz_params_names)), + name="weight") + + def final_state(self, + encoder_data, + ansatz_data=None, + circuit=None, + measurements=None): + """ + Get the quantum state after evolution. + + Args: + encoder_data (Tensor): A one dimension tensor for encoder circuit. + ansatz_data (Tensor): A one dimension tensor for ansatz circuit. If + ansatz_data is None, then the traind parameter will be used. + Default: None. + circuit (Circuit): Quantum circuit construct + by encode circuit and ansatz circuit. If None, the circuit for + train will be used. Default: None. + measurements (list[Hamiltonian]): Hamiltonian or a + list of Hamiltonian for measurement. If None, no hamiltonians + will be used. Default: None. + + Returns: + numpy.ndarray, the final quantum state. + """ + if circuit is None: + circuit = self.circuit + if ansatz_data is None: + ansatz_data = self.weight + if len(encoder_data.shape) != 1: + raise ValueError("Except a one dimension tensor for encoder_data!") + data = np.array([]) + data = np.append(data, encoder_data.asnumpy()) + data = np.append(data, ansatz_data.asnumpy()) + data = Tensor(data.astype(np.float32)) + param_names = [] + param_names.extend(self.encoder_params_names) + param_names.extend(self.ansatz_params_names) + evol = generate_evolution_operator(circuit, param_names, measurements) + state = evol(data) + return state + + def construct(self, x): + x, _, _ = self.pqc(x, self.weight) + return x diff --git a/mindquantum/nn/pqc.py b/mindquantum/nn/pqc.py new file mode 100644 index 000000000..e8381f306 --- /dev/null +++ b/mindquantum/nn/pqc.py @@ -0,0 +1,212 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Mindspore quantum simulator operator.""" + +from mindspore.ops.primitive import PrimitiveWithInfer +from mindspore.ops.primitive import prim_attr_register +from mindspore._checkparam import Validator as validator +from mindspore.common import dtype as mstype +from mindspore.ops._grad.grad_base import bprop_getters +import mindspore.ops as P +from mindquantum.circuit import Circuit +from mindquantum.gate import Hamiltonian, IGate +from mindquantum.gate import Projector +from mindquantum.ops import QubitOperator +from mindquantum.utils import count_qubits +from ._check_qnn_input import _check_type_or_iterable_type +from ._check_qnn_input import _check_circuit +from ._check_qnn_input import _check_parameters_of_circuit + + +class PQC(PrimitiveWithInfer): + r""" + Evaluate a parameterized quantum circuit and calculate the gradient of each parameters. + + Inputs of this operation is generated by MindQuantum framework. + + Inputs: + - **n_qubits** (int) - The qubit number of quantum simulator. + - **encoder_params_names** (list[str]) - The parameters names of encoder circuit. + - **ansatz_params_names** (list[str]) - The parameters names of ansatz circuit. + - **gate_names** (list[str]) - The name of each gate. + - **gate_matrix** (list[list[list[list[float]]]]) - Real part and image part of the matrix of quantum gate. + - **gate_obj_qubits** (list[list[int]]) - Object qubits of each gate. + - **gate_ctrl_qubits** (list[list[int]]) - Control qubits of each gate. + - **gate_params_names** (list[list[str]]) - Parameter names of each gate. + - **gate_coeff** (list[list[float]]) - Coefficient of eqch parameter of each gate. + - **gate_requires_grad** (list[list[bool]]) - Whether to calculate gradient of parameters of gates. + - **hams_pauli_coeff** (list[list[float]]) - Coefficient of pauli words. + - **hams_pauli_word** (list[list[list[str]]]) - Pauli words. + - **hams_pauli_qubit** (list[list[list[int]]]) - The qubit that pauli matrix act on. + - **is_projector** (bool) - Whether the measurement operator is a subspace bitstring measurement. + - **projectors** (Union[str, list[str]]) - The projector bitstrings. + - **n_threads** (int) - Thread to evaluate input data. + + Outputs: + - **expected_value** (Tensor) - The expected value of hamiltonian. + - **g1** (Tensor) - Gradient of encode circuit parameters. + - **g2** (Tensor) - Gradient of ansatz circuit parameters. + + Supported Platforms: + ``CPU`` + """ + @prim_attr_register + def __init__(self, n_qubits, encoder_params_names, ansatz_params_names, + gate_names, gate_matrix, gate_obj_qubits, gate_ctrl_qubits, + gate_params_names, gate_coeff, gate_requires_grad, + hams_pauli_coeff, hams_pauli_word, hams_pauli_qubit, + is_projector, projectors, n_threads): + """Initialize PQC""" + self.init_prim_io_names( + inputs=['encoder_data', 'ansatz_data'], + outputs=['results', 'encoder_gradient', 'ansatz_gradient']) + if is_projector: + self.n_meas = len(projectors) + else: + self.n_meas = len(hams_pauli_coeff) + + def check_shape_size(self, encoder_data, ansatz_data): + """check shape size""" + if len(encoder_data) != 2: + raise ValueError( + "PQC input encoder_data should have dimension size \ +equal to 2, but got {}.".format(len(encoder_data))) + if len(ansatz_data) != 1: + raise ValueError( + "PQC input ansatz_data should have dimension size \ +equal to 1, but got {}.".format(len(ansatz_data))) + if encoder_data[1] != len(self.encoder_params_names): + raise ValueError( + f'Input encoder_data length {encoder_data[1]} \ +mismatch with circuit encoder parameter number {len(self.encoder_params_names)}') + if ansatz_data[0] != len(self.ansatz_params_names): + raise ValueError( + f'Input ansatz_data length {ansatz_data[1]} \ +mismatch with circuit ansatz parameter number {len(self.ansatz_params_names)}') + + def infer_shape(self, encoder_data, ansatz_data): + self.check_shape_size(encoder_data, ansatz_data) + return [encoder_data[0], self.n_meas], [ + encoder_data[0], self.n_meas, + len(self.encoder_params_names) + ], [encoder_data[0], self.n_meas, + len(self.ansatz_params_names)] + + def infer_dtype(self, encoder_data, ansatz_data): + args = {'encoder_data': encoder_data, 'ansatz_data': ansatz_data} + validator.check_tensors_dtypes_same_and_valid(args, mstype.float_type, + self.name) + return encoder_data, encoder_data, encoder_data + + +@bprop_getters.register(PQC) +def bprop_pqc(self): + """Grad definition for `PQC` operation.""" + t = P.Transpose() + mul = P.Mul() + sum_ = P.ReduceSum() + + def bprop(encoder_data, ansatz_data, out, dout): + dx = t(out[1], (2, 0, 1)) + dx = mul(dout[0], dx) + dx = sum_(dx, 2) + dx = t(dx, (1, 0)) + dy = P.tensor_dot(dout[0], out[2], ((0, 1), (0, 1))) + return dx, dy + + return bprop + + +def generate_pqc_operator(encoder_params_names, + ansatz_params_names, + circuit: Circuit, + measurements, + n_threads=1): + """ + A method to generate a parameterized quantum circuit simulation operator. + + Args: + encoder_params_names (list[str]): The list of parameter names for + encoder circuit. + ansatz_params_names (list[str]): The list of parameter names for ansatz + circuit. + circuit (Circuit): The whole circuit combined with + encoder circuit and ansatz circuit. + measurements (Union[Hamiltonian, list[Hamiltonian], Projector, list[Projector]]): The measurement + operators, would be hamiltonians or projectors. + n_threads (int): Number of threads for data parallelize. Default: 1. + + Returns: + PQC, A parameterized quantum circuit simulator operator supported by mindspore framework. + + Examples: + >>> from mindquantum.ops import QubitOperator + >>> from mindquantum import Circuit + >>> import mindquantum.gate as G + >>> from mindquantum.nn import generate_pqc_operator + >>> encoder_circ = Circuit([G.RX('a').on(0)]) + >>> ansatz_circ = Circuit([G.RY('b').on(0)]) + >>> circ = encoder_circ + ansatz_circ + >>> ham = G.Hamiltonian(QubitOperator('Z0')) + >>> pqc = generate_pqc_operator(['a'], ['b'], circ, ham) + """ + _check_circuit(circuit, 'Circuit') + _check_type_or_iterable_type(measurements, (Hamiltonian, Projector), + 'Hamiltonian or Projector') + _check_parameters_of_circuit(encoder_params_names, ansatz_params_names, + circuit) + if not isinstance(n_threads, int) or n_threads <= 0: + raise TypeError( + "n_threads requires a positive int, but get {}".format(n_threads)) + is_projector = False + if isinstance(measurements, (Projector, Hamiltonian)): + measurements = [measurements] + hams = [Hamiltonian(QubitOperator())] + pros = [Projector('')] + if isinstance(measurements[0], Hamiltonian): + hams = measurements + n_qubits_ham = count_qubits(hams[0].hamiltonian) + if n_qubits_ham > circuit.n_qubits: + circuit += IGate().on(n_qubits_ham - 1) + if isinstance(measurements[0], Projector): + for pro in measurements: + if pro.n_qubits != circuit.n_qubits: + raise ValueError( + f"The qubit of projector {pro} not match with quantum circuit." + ) + is_projector = True + pros = measurements + ham_ms_data = {} + for ham in hams: + for k, v in ham.mindspore_data().items(): + if k not in ham_ms_data: + ham_ms_data[k] = [v] + else: + ham_ms_data[k].append(v) + proj_ms_data = {} + for pro in pros: + for k, v in pro.mindspore_data().items(): + if k not in proj_ms_data: + proj_ms_data[k] = [v] + else: + proj_ms_data[k].append(v) + return PQC(circuit.n_qubits, + encoder_params_names=encoder_params_names, + ansatz_params_names=ansatz_params_names, + **circuit.mindspore_data(), + **ham_ms_data, + is_projector=is_projector, + **proj_ms_data, + n_threads=n_threads) diff --git a/mindquantum/io/qasm/__init__.py b/mindquantum/ops/__init__.py similarity index 60% rename from mindquantum/io/qasm/__init__.py rename to mindquantum/ops/__init__.py index c5e7f9228..6bc9fa34d 100644 --- a/mindquantum/io/qasm/__init__.py +++ b/mindquantum/ops/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,12 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""QASM flavors support (OpenQASM, HiQASM, etc.)""" +"""Fermion operator and qubit operator.""" -from .openqasm import OpenQASM -from .hiqasm import random_hiqasm -from .hiqasm import HiQASM +from mindquantum.third_party.interaction_operator import InteractionOperator +from .fermion_operator import FermionOperator +from .qubit_operator import QubitOperator +from .polynomial_tensor import PolynomialTensor +from .qubit_excitation_operator import QubitExcitationOperator -__all__ = ['OpenQASM', 'random_hiqasm', 'HiQASM'] +__all__ = [ + 'FermionOperator', 'QubitOperator', 'PolynomialTensor', + 'InteractionOperator', 'QubitExcitationOperator' +] __all__.sort() diff --git a/mindquantum/core/operators/_base_operator.py b/mindquantum/ops/_base_operator.py similarity index 99% rename from mindquantum/core/operators/_base_operator.py rename to mindquantum/ops/_base_operator.py index e944170a3..c5a5461ad 100644 --- a/mindquantum/core/operators/_base_operator.py +++ b/mindquantum/ops/_base_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Portions Copyright 2021 Huawei Technologies Co., Ltd # Portions Copyright 2017 The OpenFermion Developers. # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +21,7 @@ from abc import ABCMeta, abstractmethod import numpy as np -from mindquantum.core.parameterresolver import ParameterResolver as PR +from mindquantum.parameterresolver import ParameterResolver as PR EQ_TOLERANCE = 1e-8 @@ -131,7 +130,7 @@ def _parse_sequence(self, terms): return () if isinstance(terms[0], int): self._validate_term(tuple(terms)) - return (terms, ) + return (terms,) for sub_term in terms: self._validate_term(sub_term) diff --git a/mindquantum/core/operators/fermion_operator.py b/mindquantum/ops/fermion_operator.py similarity index 96% rename from mindquantum/core/operators/fermion_operator.py rename to mindquantum/ops/fermion_operator.py index 18c994b5f..0084d3254 100644 --- a/mindquantum/core/operators/fermion_operator.py +++ b/mindquantum/ops/fermion_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Portions Copyright 2021 Huawei Technologies Co., Ltd # Portions Copyright 2017 The OpenFermion Developers. # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +14,7 @@ # ============================================================================ """This module is generated the Fermion Operator""" -from mindquantum.core.parameterresolver import ParameterResolver as PR +from mindquantum.parameterresolver import ParameterResolver as PR from ._base_operator import _Operator @@ -57,7 +56,7 @@ class FermionOperator(_Operator): coefficient for the corresponding single operators Default: 1.0. Examples: - >>> from mindquantum.core.operators import FermionOperator + >>> from mindquantum.ops import FermionOperator >>> a_p_dagger = FermionOperator('1^') >>> a_p_dagger 1.0 [1^] @@ -198,7 +197,7 @@ def imag(self): FermionOperator, the imag part of this fermion operator. Examples: - >>> from mindquantum.core.operators import FermionOperator + >>> from mindquantum.ops import FermionOperator >>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a') >>> f.imag.compress() 2.0 [0] @@ -218,7 +217,7 @@ def real(self): FermionOperator, the real part of this fermion operator. Examples: - >>> from mindquantum.core.operators import FermionOperator + >>> from mindquantum.ops import FermionOperator >>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a') >>> f.real.compress() 1.0 [0] + @@ -237,7 +236,7 @@ def normal_ordered(self): FermionOperator, the normal ordered FermionOperator. Exmples: - >>> from mindquantum.core.operators import FermionOperator + >>> from mindquantum.ops import FermionOperator >>> origin = FermionOperator('0 1^') >>> origin 1.0 [0 1^] diff --git a/mindquantum/core/operators/polynomial_tensor.py b/mindquantum/ops/polynomial_tensor.py similarity index 99% rename from mindquantum/core/operators/polynomial_tensor.py rename to mindquantum/ops/polynomial_tensor.py index 8edf55748..e1acf69b8 100644 --- a/mindquantum/core/operators/polynomial_tensor.py +++ b/mindquantum/ops/polynomial_tensor.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Portions Copyright (c) 2020 Huawei Technologies Co.,ltd. # Portions Copyright 2017 The OpenFermion Developers. # @@ -68,7 +67,7 @@ class PolynomialTensor: Examples: >>> import numpy as np - >>> from mindquantum.core.operators import PolynomialTensor + >>> from mindquantum.ops import PolynomialTensor >>> constant = 1 >>> one_body_term = np.array([[1,0],[0,1]]) >>> two_body_term = two_body_term = np.array([[[[1,0],[0,1]],[[1,0],[0,1]]],[[[1,0],[0,1]],[[1,0],[0,1]]]]) @@ -454,7 +453,7 @@ def __truediv__(self, divisor): the divisor is 0.'.format(type(self))) return quotient - # be careful with this function + # ba careful with this function def __div__(self, divisor): """ For compatibility with Python 2. """ return self.__truediv__(divisor) @@ -492,6 +491,3 @@ def __str__(self): def __repr__(self): return str(self) - - -__all__ = ['PolynomialTensor'] diff --git a/mindquantum/core/operators/qubit_excitation_operator.py b/mindquantum/ops/qubit_excitation_operator.py similarity index 95% rename from mindquantum/core/operators/qubit_excitation_operator.py rename to mindquantum/ops/qubit_excitation_operator.py index 5575a488b..06fe38be3 100644 --- a/mindquantum/core/operators/qubit_excitation_operator.py +++ b/mindquantum/ops/qubit_excitation_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +14,9 @@ # ============================================================================ """This module implements qubit-excitation operators""" -from mindquantum.core.operators.fermion_operator import FermionOperator -from mindquantum.core.operators.qubit_operator import QubitOperator -from mindquantum.core.parameterresolver import ParameterResolver as PR +from mindquantum.ops.fermion_operator import FermionOperator +from mindquantum.ops.qubit_operator import QubitOperator +from mindquantum.parameterresolver import ParameterResolver as PR from ._base_operator import _Operator @@ -66,7 +65,7 @@ class QubitExcitationOperator(_Operator): Examples: >>> from mindquantum.hiqfermion.transforms import Transform - >>> from mindquantum.core.operators import QubitExcitationOperator + >>> from mindquantum.ops import QubitExcitationOperator >>> from mindquantum.hiqfermion.transforms import Transform >>> op = QubitExcitationOperator(((4, 1), (1, 0), (0, 0)), 2.5) >>> op @@ -120,7 +119,7 @@ def to_qubit_operator(self): according to the definition of Qubit excitation operators. Examples: - >>> from mindquantum.core.operators import QubitExcitationOperator + >>> from mindquantum.ops import QubitExcitationOperator >>> op = QubitExcitationOperator("7^ 1") >>> op.to_qubit_operator() 0.25 [X1 X7] + @@ -250,7 +249,7 @@ def imag(self): QubitExcitationOperator, the image part of this qubit excitation operator. Examples: - >>> from mindquantum.core.operators import QubitExcitationOperator + >>> from mindquantum.ops import QubitExcitationOperator >>> f = QubitExcitationOperator(((1, 0),), 1 + 2j) >>> f += QubitExcitationOperator(((1, 1),), 'a') >>> f.imag.compress() @@ -271,7 +270,7 @@ def real(self): QubitExcitationOperator, the real part of this qubit excitation operator. Examples: - >>> from mindquantum.core.operators import QubitExcitationOperator + >>> from mindquantum.ops import QubitExcitationOperator >>> f = QubitExcitationOperator(((1, 0),), 1 + 2j) >>> f += QubitExcitationOperator(((1, 1),), 'a') >>> f.real.compress() @@ -291,7 +290,7 @@ def normal_ordered(self): QubitExcitationOperator, the normal ordered operator. Examples: - >>> from mindquantum.core.operators import QubitExcitationOperator + >>> from mindquantum.ops import QubitExcitationOperator >>> op = QubitExcitationOperator("7 1^") >>> op 1.0 [Q7 Q1^] @@ -352,6 +351,3 @@ def _qubit_excitation_tuple_to_string(term): else: s.append(str(i[0])) return ' '.join(s) - - -__all__ = ['QubitExcitationOperator'] diff --git a/mindquantum/core/operators/qubit_operator.py b/mindquantum/ops/qubit_operator.py similarity index 96% rename from mindquantum/core/operators/qubit_operator.py rename to mindquantum/ops/qubit_operator.py index 52f834775..b4c9c0e0c 100644 --- a/mindquantum/core/operators/qubit_operator.py +++ b/mindquantum/ops/qubit_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Portions Copyright (c) 2020 Huawei Technologies Co.,ltd. # Portions Copyright 2017 The OpenFermion Developers. @@ -19,7 +18,7 @@ # Apache 2.0 license. """This is the module for the Qubit Operator. """ -from mindquantum.core.parameterresolver import ParameterResolver as PR +from mindquantum.parameterresolver import ParameterResolver as PR from ._base_operator import _Operator EQ_TOLERANCE = 1e-8 @@ -95,7 +94,7 @@ class QubitOperator(_Operator): represent by a string or a symbol or a parameter resolver. Default: 1.0. Examples: - >>> from mindquantum.core.operators import QubitOperator + >>> from mindquantum.ops import QubitOperator >>> ham = ((QubitOperator('X0 Y3', 0.5) + 0.6 * QubitOperator('X0 Y3'))) # Equivalently @@ -181,7 +180,7 @@ def real(self): QubitOperator, the real part of this qubit operator. Examples: - >>> from mindquantum.core.operators import QubitOperator + >>> from mindquantum.ops import QubitOperator >>> f = QubitOperator('X0', 1 + 2j) + QubitOperator('Y0', 'a') >>> f.real.compress() 1.0 [X0] + @@ -202,7 +201,7 @@ def imag(self): QubitOperator, the imag part of this qubit operator. Examples: - >>> from mindquantum.core.operators import QubitOperator + >>> from mindquantum.ops import QubitOperator >>> f = QubitOperator('X0', 1 + 2j) + QubitOperator('Y0', 'a') >>> f.imag.compress() 2.0 [X0] @@ -294,6 +293,3 @@ def __str__(self): def __repr__(self): return str(self) - - -__all__ = ['QubitOperator'] diff --git a/mindquantum/core/parameterresolver/__init__.py b/mindquantum/parameterresolver/__init__.py similarity index 97% rename from mindquantum/core/parameterresolver/__init__.py rename to mindquantum/parameterresolver/__init__.py index e777ba2e5..2718839ff 100644 --- a/mindquantum/core/parameterresolver/__init__.py +++ b/mindquantum/parameterresolver/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,4 +16,5 @@ from .parameterresolver import ParameterResolver + __all__ = ["ParameterResolver"] diff --git a/mindquantum/core/parameterresolver/parameterresolver.py b/mindquantum/parameterresolver/parameterresolver.py similarity index 88% rename from mindquantum/core/parameterresolver/parameterresolver.py rename to mindquantum/parameterresolver/parameterresolver.py index 865a5dc7b..3b8874b70 100644 --- a/mindquantum/core/parameterresolver/parameterresolver.py +++ b/mindquantum/parameterresolver/parameterresolver.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +18,6 @@ from copy import deepcopy import numpy as np import sympy as sp -from mindquantum import mqbackend as mb class ParameterResolver(dict): @@ -62,12 +60,7 @@ def __init__(self, data=None): v, type(v))) super(ParameterResolver, self).__init__(data) self.no_grad_parameters = set() - self.requires_grad_parameters = set(self.params_name) - - def get_cpp_obj(self): - """Get cpp obj of this parameter resolver""" - return mb.parameter_resolver(self, self.no_grad_parameters, - self.requires_grad_parameters) + self.requires_grad_parameters = set(self.para_name) def __setitem__(self, keys, values): """ @@ -136,7 +129,7 @@ def __sub__(self, pr): Subtraction a parameter resolver with other parameter. Returns: - :class:`mindquantum.core.parameterresolver.ParameterResolver` + :class:`mindquantum.parameterresolver.ParameterResolver` Args: pr (ParameterResolver): The parameter resolver need to subtract. @@ -168,7 +161,7 @@ def __imul__(self, num): Parameter support inplace multiply. Returns: - :class:`mindquantum.core.parameterresolver.ParameterResolver` + :class:`mindquantum.parameterresolver.ParameterResolver` Args: num (number): Multiply factor. @@ -192,7 +185,7 @@ def __mul__(self, num): Multiply num with every value of parameter resolver. Returns: - :class:`mindquantum.core.parameterresolver.ParameterResolver` + :class:`mindquantum.parameterresolver.ParameterResolver` Args: num (number): Multiply factor. @@ -213,7 +206,7 @@ def __mul__(self, num): def __rmul__(self, num): """ - See :class:`mindquantum.core.parameterresolver.ParameterResolver.__mul__`. + See :class:`mindquantum.parameterresolver.ParameterResolver.__mul__`. """ return self.__mul__(num) @@ -224,7 +217,7 @@ def __eq__(self, other): return super().__eq__(other) and no_grad_eq and requires_grad_eq @property - def params_name(self): + def para_name(self): """ Get the parameters name. @@ -234,7 +227,7 @@ def params_name(self): Examples: >>> from mindquantum import ParameterResolver >>> pr = ParameterResolver({'a': 1, 'b': 2}) - >>> pr.params_name + >>> pr.para_name ['a', 'b'] """ return list(self.keys()) @@ -272,7 +265,7 @@ def requires_grad(self): {'a', 'b'} """ self.no_grad_parameters = set() - self.requires_grad_parameters = set(self.params_name) + self.requires_grad_parameters = set(self.para_name) return self def no_grad(self): @@ -289,7 +282,7 @@ def no_grad(self): >>> pr.requires_grad_parameters set() """ - self.no_grad_parameters = set(self.params_name) + self.no_grad_parameters = set(self.para_name) self.requires_grad_parameters = set() return self @@ -365,7 +358,7 @@ def update(self, others): Raises: ValueError: If some parameters require grad and not require grad in - other parameter resolver and vice versa. + other parameter resolver and vise versa. Examples: >>> from mindquantum import ParameterResolver @@ -417,7 +410,7 @@ def expression(self): sympy.Expr, the symbol expression of this parameter resolver. Examples: - >>> from mindquantum.core.parameterresolver import ParameterResolver as PR + >>> from mindquantum.parameterresolver import ParameterResolver as PR >>> pr = PR({'a' : 2, 'b' : 0.3}) >>> pr.expression() 2*a + 0.3*b @@ -435,7 +428,7 @@ def conjugate(self): ParameterResolver, the conjugate version of this parameter resolver. Examples: - >>> from mindquantum.core.parameterresolver import ParameterResolver as PR + >>> from mindquantum.parameterresolver import ParameterResolver as PR >>> pr = PR({'a' : 1, 'b': 1j}) >>> pr.conjugate().expression() a - 1.0*I*b @@ -482,7 +475,7 @@ def real(self): ParameterResolver, the real part of this parameter resolver. Examples: - >>> from mindquantum.core.parameterresolver import ParameterResolver as PR + >>> from mindquantum.parameterresolver import ParameterResolver as PR >>> pr = PR({'a': 1.2 + 1.3j}) >>> pr.real() {'a': 1.2} @@ -501,7 +494,7 @@ def imag(self): ParameterResolver, the image part of this parameter resolver. Examples: - >>> from mindquantum.core.parameterresolver import ParameterResolver as PR + >>> from mindquantum.parameterresolver import ParameterResolver as PR >>> pr = PR({'a': 1.2 + 1.3j}) >>> pr.imag() {'a': 1.3} @@ -518,28 +511,4 @@ def _check_pr_type(pr): type(pr))) -def _check_and_generate_pr_type(pr, names: list): - """_check_and_generate_pr_type""" - if isinstance(pr, _num_type): - if len(names) != 1: - raise ValueError( - f"number of given parameters value is less than parameters ({len(names)})" - ) - pr = np.array([pr]) - if not isinstance(pr, ParameterResolver) and not isinstance( - pr, np.ndarray): - raise TypeError( - f"parameter requires a parameter resolver or a numpy array, but get type{type(pr)}" - ) - - if isinstance(pr, np.ndarray): - if len(pr) != len(names) or len(pr.shape) != 1: - raise ValueError( - f"given parameter value size ({pr.shape}) not match with parameter size ({len(names)})" - ) - pr = ParameterResolver(dict(zip(names, pr))) - - return pr - - _num_type = (int, float, complex, np.int32, np.int64, np.float32, np.float64) diff --git a/mindquantum/simulator/__init__.py b/mindquantum/simulator/__init__.py deleted file mode 100644 index dbeaa7345..000000000 --- a/mindquantum/simulator/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Simulator.""" - -from .simulator import Simulator -from .simulator import GradOpsWrapper -from .simulator import get_supported_simulator - -__all__ = ['Simulator', 'GradOpsWrapper', 'get_supported_simulator'] -__all__.sort() diff --git a/mindquantum/simulator/simulator.py b/mindquantum/simulator/simulator.py deleted file mode 100644 index e0461e845..000000000 --- a/mindquantum/simulator/simulator.py +++ /dev/null @@ -1,683 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Simulator.""" -from mindquantum.core.gates.basic import BasicGate -import numpy as np -from mindquantum.core.circuit import Circuit -from mindquantum.core.operators import Hamiltonian -from mindquantum.core.operators.hamiltonian import MODE -from mindquantum.core.parameterresolver import ParameterResolver -from mindquantum.core.parameterresolver.parameterresolver import _check_and_generate_pr_type -from mindquantum.core.gates import MeasureResult -from mindquantum.core.gates import Measure -from mindquantum.core.gates import BarrierGate -from mindquantum.utils import ket_string -from mindquantum import mqbackend as mb - -SUPPORTED_SIMULATOR = ['projectq'] - - -def get_supported_simulator(): - """Get simulator name that supported by MindQuantum """ - return SUPPORTED_SIMULATOR - - -class Simulator: - """ - Quantum simulator that simulate quantum circuit. - - Args: - backend (str): which backend you want. The supported backend can be found - in SUPPORTED_SIMULATOR - n_qubits (int): number of quantum simulator. - seed (int): the random seed for this simulator. Default: 42. - - Examples: - >>> from mindquantum import Simulator - >>> from mindquantum import qft - >>> sim = Simulator('projectq', 2) - >>> sim.apply_circuit(qft(range(2))) - >>> sim.get_qs() - array([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j]) - """ - def __init__(self, backend, n_qubits, seed=42): - if not isinstance(backend, str): - raise TypeError(f"backend need a string, but get {type(backend)}") - if backend not in SUPPORTED_SIMULATOR: - raise ValueError(f"backend {backend} not supported!") - if not isinstance(n_qubits, int) or n_qubits < 0: - raise ValueError( - f"n_qubits of simulator should be a non negative int, but get {n_qubits}" - ) - if not isinstance(seed, int) or seed < 0 or seed > 2**32 - 1: - raise ValueError(f"seed must be between 0 and 2**32 - 1") - self.backend = backend - self.seed = seed - self.n_qubits = n_qubits - if backend == 'projectq': - self.sim = mb.projectq(seed, n_qubits) - - def __str__(self): - state = self.get_qs() - s = f"{self.backend} simulator with {self.n_qubits} qubit{'s' if self.n_qubits > 1 else ''}." - s += f"\nCurrent quantum state:\n" - if self.n_qubits < 4: - s += '\n'.join(ket_string(state)) - else: - s += state.__str__() - return s - - def __repr__(self): - return self.__str__() - - def reset(self): - """ - Reset simulator to zero state. - - Examples: - >>> from mindquantum import Simulator - >>> from mindquantum import qft - >>> sim = Simulator('projectq', 2) - >>> sim.apply_circuit(qft(range(2))) - >>> sim.reset() - >>> sim.get_qs() - array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]) - """ - self.sim.reset() - - def flush(self): - """ - Flush gate that works for projectq simulator. The projectq simulator - will cache several gate and fushion these gate into a bigger gate, and - than act on the quantum state. The flush command will ask the simulator - to fushion currently stored gate and act on the quantum state. - - Examples: - >>> from mindquantum import Simulator - >>> from mindquantum import H - >>> sim = Simulator('projectq', 1) - >>> sim.apply_gate(H.on(0)) - >>> sim.flush() - """ - if self.backend == 'projectq': - self.sim.run() - - def apply_gate(self, gate, parameter_resolver=None): - """ - Apply a gate on this simulator, can be a quantum gate or a measurement operator - - Args: - gate (BasicGate): The gate you want to apply. - parameter_resolver (Union[numbers.Number, numpy.ndarray, ParameterResolver]): The - parameter for parameterized gate. Default: None. - - Returns: - int or None, if the gate if a measure gate, then return a collapsed state, Otherwise - return None. - - Examples: - >>> import numpy as np - >>> from mindquantum import Simulator - >>> from mindquantum import RY, Measure - >>> sim = Simulator('projectq', 1) - >>> sim.apply_gate(RY('a').on(0), np.pi/2) - >>> sim.get_qs() - array([0.70710678+0.j, 0.70710678+0.j]) - >>> sim.apply_gate(Measure().on(0)) - 1 - >>> sim.get_qs() - array([0.+0.j, 1.+0.j]) - """ - if not isinstance(gate, BasicGate): - raise TypeError( - f"gate requires a quantum gate, but get {type(gate)}") - if not isinstance(gate, BarrierGate): - if isinstance(gate, Measure): - return self.sim.apply_measure(gate.get_cpp_obj()) - if parameter_resolver is None: - if gate.parameterized: - raise ValueError( - "apply a parameterized gate needs a parameter_resolver" - ) - self.sim.apply_gate(gate.get_cpp_obj()) - else: - parameter_resolver = _check_and_generate_pr_type( - parameter_resolver, gate.coeff.params_name) - self.sim.apply_gate(gate.get_cpp_obj(), - parameter_resolver.get_cpp_obj(), False) - return None - - def apply_circuit(self, circuit, parameter_resolver=None): - """ - Apply a circuit on this simulator. - - Args: - circuit (Circuit): The quantum circuit you want to apply on this simulator. - parameter_resolver (Union[ParameterResolver, dict, numpy.ndarray]): The - parameter resolver for this circuit. If the circuit is not parameterized, - this arg should be None. Default: None. - - Returns: - MeasureResult or None, if the circuit has measure gate, then return a MeasureResult, - otherwise return None. - - Examples: - >>> import numpy as np - >>> from mindquantum import Circuit, H - >>> from mindquantum import Simulator - >>> sim = Simulator('projectq', 2) - >>> sim.apply_circuit(Circuit().un(H, 2)) - >>> sim.apply_circuit(Circuit().ry('a', 0).ry('b', 1), np.array([1.1, 2.2])) - >>> sim - projectq simulator with 2 qubits. - Current quantum state: - -0.0721702531972066¦00⟩ - -0.30090405886869676¦01⟩ - 0.22178317006196263¦10⟩ - 0.9246947752567126¦11⟩ - >>> sim.apply_circuit(Circuit().measure(0).measure(1)) - shots: 1 - Keys: q1 q0│0.00 0.2 0.4 0.6 0.8 1.0 - ───────────┼───────────┴───────────┴───────────┴───────────┴───────────┴ - 11│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - │ - {'11': 1} - """ - if not isinstance(circuit, Circuit): - raise TypeError( - f"circuit must be Circuit, but get {type(Circuit)}") - if circuit.has_measure: - res = MeasureResult() - res.add_measure(circuit.all_measures.keys()) - if parameter_resolver is None: - if circuit.params_name: - raise ValueError( - "Applying a parameterized circuit needs a parameter_resolver" - ) - if circuit.has_measure: - parameter_resolver = ParameterResolver() - samples = np.array( - self.sim.apply_circuit_with_measure( - circuit.get_cpp_obj(), - parameter_resolver.get_cpp_obj(), - res.keys_map)).reshape((1, -1)) - res.collect_data(samples) - return res - self.sim.apply_circuit(circuit.get_cpp_obj()) - else: - if not isinstance(parameter_resolver, - (ParameterResolver, dict, np.ndarray)): - raise TypeError( - f"parameter_resolver requires a ParameterResolver, but get {type(parameter_resolver)}" - ) - if isinstance(parameter_resolver, dict): - parameter_resolver = ParameterResolver(parameter_resolver) - if isinstance(parameter_resolver, np.ndarray): - if len(parameter_resolver.shape - ) != 1 or parameter_resolver.shape[0] != len( - circuit.params_name): - raise ValueError( - f"size of parameters input ({parameter_resolver.shape}) not\ -match with circuit parameters ({len(circuit.params_name)}, )") - parameter_resolver = ParameterResolver( - dict(zip(circuit.params_name, parameter_resolver))) - if circuit.has_measure: - samples = np.array( - self.sim.apply_circuit_with_measure( - circuit.get_cpp_obj(), - parameter_resolver.get_cpp_obj(), - res.keys_map)).reshape((1, -1)) - res.collect_data(samples) - return res - self.sim.apply_circuit(circuit.get_cpp_obj(), - parameter_resolver.get_cpp_obj()) - return None - - def sampling(self, circuit, pr=None, shots=1, seed=None): - """ - Samping the measure qubit in circuit. Sampling do not change the origin quantum - state of this simulator. - - Args: - circuit (Circuit): The circuit that you want to evolution and do sampling. - pr (Union[None, dict, ParameterResolver]): The parameter - resolver for this circuit, if this circuit is a parameterized circuit. - Default: None. - shots (int): How many shots you want to sampling this circuit. Default: 1 - seed (int): Random seed for random sampling. Default: None. - - Returns: - MeasureResult, the measure result of sampling. - - Examples: - >>> from mindquantum import Circuit, Measure - >>> from mindquantum import Simulator - >>> circ = Circuit().ry('a', 0).ry('b', 1) - >>> circ += Measure('q0_0').on(0) - >>> circ += Measure('q0_1').on(0) - >>> circ += Measure('q1').on(1) - >>> sim = Simulator('projectq', circ.n_qubits) - >>> res = sim.sampling(circ, {'a': 1.1, 'b': 2.2}, shots=100, seed=42) - >>> res - shots: 100 - Keys: q1 q0_1 q0_0│0.00 0.122 0.245 0.367 0.49 0.612 - ──────────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴ - 000│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - 011│▒▒▒▒▒▒▒ - │ - 100│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - │ - 111│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - │ - {'000': 17, '011': 8, '100': 49, '111': 26} - """ - if not isinstance(circuit, Circuit): - raise TypeError( - f"sampling circuit need a quantum circuit but get {type(circuit)}" - ) - if not isinstance(shots, int) or shots < 0: - raise ValueError( - f"sampling shot should be non negative int, but get {shots}") - if circuit.parameterized: - if pr is None: - raise ValueError( - "Sampling a parameterized circuit need a ParameterResolver" - ) - pr = ParameterResolver(pr) - else: - pr = ParameterResolver() - if seed is None: - seed = self.seed - elif not isinstance(seed, int) or seed < 0 or seed > 2**23 - 1: - raise ValueError(f"seed must be between 0 and 2**23 - 1") - res = MeasureResult() - res.add_measure(circuit.all_measures.keys()) - samples = np.array( - self.sim.sampling(circuit.get_cpp_obj(), pr.get_cpp_obj(), shots, - res.keys_map, seed)).reshape((shots, -1)) - res.collect_data(samples) - return res - - def apply_hamiltonian(self, hamiltonian: Hamiltonian): - """ - Apply hamiltonian to a simulator, this hamiltonian can be - hermitian or non hermitian. - - Notes: - The quantum state may be not a normalized quantum state after apply hamiltonian. - - Args: - hamiltonian (Hamiltonian): the hamiltonian you want to apply. - - Examples: - >>> from mindquantum import Simulator - >>> from mindquantum import Circuit, Hamiltonian - >>> from mindquantum.core.operators import QubitOperator - >>> import scipy.sparse as sp - >>> sim = Simulator('projectq', 1) - >>> sim.apply_circuit(Circuit().h(0)) - >>> sim.get_qs() - array([0.70710678+0.j, 0.70710678+0.j]) - >>> ham1 = Hamiltonian(QubitOperator('Z0')) - sim.apply_hamiltonian(ham1) - >>> sim.get_qs() - array([ 0.70710678+0.j, -0.70710678+0.j]) - - >>> sim.reset() - >>> ham2 = Hamiltonian(sp.csr_matrix([[1, 2], [3, 4]])) - >>> sim.apply_hamiltonian(ham2) - >>> sim.get_qs() - array([1.+0.j, 3.+0.j]) - """ - - if not isinstance(hamiltonian, Hamiltonian): - raise TypeError( - f"hamiltonian requires a Hamiltonian, but got {type(hamiltonian)}" - ) - if hamiltonian.how_to != MODE['origin']: - if hamiltonian.n_qubits != self.n_qubits: - raise ValueError( - f"Hamiltonian qubits is {hamiltonian.n_qubits}, not match \ -with simulator qubits number {self.n_qubits}") - self.sim.apply_hamiltonian(hamiltonian.get_cpp_obj()) - - def get_expectation(self, hamiltonian): - r""" - Get expectation of the given hamiltonian. The hamiltonian could be non hermitian. - - .. math:: - - E = \left<\psi\right|H\left|\psi\right> - - Args: - hamiltonian (Hamiltonian): The hamiltonian you want to get expectation. - - Returns: - numbers.Number, the expectation value. - - Examples: - >>> from mindquantum.core.operators import QubitOperator - >>> from mindquantum import Circuit, Simulator - >>> from mindquantum import Hamiltonian - >>> sim = Simulator('projectq', 1) - >>> sim.apply_circuit(Circuit().ry(1.2, 0)) - >>> ham = Hamiltonian(QubitOperator('Z0')) - >>> sim.get_expectation(ham) - (0.36235775447667357+0j) - """ - if not isinstance(hamiltonian, Hamiltonian): - raise TypeError( - f"hamiltonian requires a Hamiltonian, but got {type(hamiltonian)}" - ) - if hamiltonian.how_to != MODE['origin']: - if hamiltonian.n_qubits != self.n_qubits: - raise ValueError( - f"Hamiltonian qubits is {hamiltonian.n_qubits}, not match \ -with simulator qubits number {self.n_qubits}") - return self.sim.get_expectation(hamiltonian.get_cpp_obj()) - - def get_qs(self, ket=False): - """ - Get current quantum state of this simulator. - - Returns: - numpy.ndarray, the current quantum state. - - Examples: - >>> from mindquantum import qft, Simulator - >>> sim = Simulator('projectq', 2) - >>> sim.apply_circuit(qft(range(2))) - >>> sim.get_qs() - array([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j]) - """ - state = np.array(self.sim.get_qs()) - if ket: - return '\n'.join(ket_string(state)) - return state - - def set_qs(self, vec): - """ - Set quantum state for this simulation. - - Args: - vec (numpy.ndarray): the quantum state that you want. - - Examples: - >>> from mindquantum import Simulator - >>> import numpy as np - >>> sim = Simulator('projectq', 1) - >>> sim.get_qs() - array([1.+0.j, 0.+0.j]) - >>> sim.set_qs(np.array([1, 1])) - >>> sim.get_qs() - array([0.70710678+0.j, 0.70710678+0.j]) - """ - if not isinstance(vec, np.ndarray): - raise TypeError( - f"quantum state must be a ndarray, but get {type(vec)}") - if len(vec.shape) != 1: - raise ValueError( - f"vec requires a 1-dimensional array, but get {vec.shape}") - n_qubits = np.log2(vec.shape[0]) - if n_qubits % 1 != 0: - raise ValueError(f"vec size {vec.shape[0]} is not power of 2") - n_qubits = int(n_qubits) - if self.n_qubits != n_qubits: - raise ValueError( - f"{n_qubits} qubits vec does not match with simulation qubits ({self.n_qubits})" - ) - self.sim.set_qs(vec / np.sqrt(np.sum(np.abs(vec)**2))) - - def get_expectation_with_grad(self, - hams: Hamiltonian, - circ_right: Circuit, - circ_left: Circuit = None, - encoder_params_name=None, - ansatz_params_name=None, - parallel_worker: int = None): - r""" - Get a function that return the forward value and gradient w.r.t circuit parameters. - This method is designed to calculate the expectation and its gradient shown as below. - - .. math:: - - E = \left<\psi\right|U_l^\dagger H U_r \left|\psi\right> - - where :math:`U_l` is circ_left, :math:`U_r` is circ_right, :math:`H` is hams - and :math:`\left|\psi\right>` is the current quantum state of this simulator. - - Args: - hams (Hamiltonian): The hamiltonian that need to get expectation. - circ_right (Circuit): The :math:`U_r` circuit described above. - circ_left (Circuit): The :math:`U_l` circuit described above. By default, this circuit - will be none, and in this situation, :math:`U_l` will be equals to - :math:`U_r`. Default: None. - encoder_params_name (list[str]): To specific which parameters belongs to encoder, - that will encoder the input data into quantum state. The encoder data - can be a batch. - ansatz_params_name (list[str]): To specific which parameters belongs to ansatz, - that will be trained during training. - parallel_worker (int): The parallel worker numbers. The parallel workers can handle - batch in parallel threads. - - Returns: - GradOpsWrapper, a grad ops wrapper than contains information to generate this grad ops. - - Examples: - >>> import numpy as np - >>> from mindquantum import Simulator, Hamiltonian - >>> from mindquantum import Circuit - >>> from mindquantum.core.operators import QubitOperator - >>> circ = Circuit().ry('a', 1) - >>> ham = Hamiltonian(QubitOperator('Z0')) - >>> sim = Simulator('projectq', 1) - >>> grad_ops = sim.get_expectation_with_grad(ham, circ) - >>> grad_ops(np.array([1.0])) - (array([[0.54030231+0.j]]), array([[[-0.84147098+0.j]]])) - """ - if isinstance(hams, Hamiltonian): - hams = [hams] - elif not isinstance(hams, list): - raise ValueError( - f"hams requires a Hamiltonian or a list of Hamiltonian, but get {type(hams)}" - ) - if not isinstance(circ_right, Circuit): - raise ValueError( - f"Quantum circuit need a Circuit, but get {type(circ_right)}") - if circ_left is not None and not isinstance(circ_left, Circuit): - raise ValueError( - f"Quantum circuit need a Circuit, but get {type(circ_left)}") - if circ_left is None: - circ_left = Circuit() - if circ_left.has_measure or circ_right.has_measure: - raise ValueError( - "circuit for variational algorithm cannot have measure gate") - if parallel_worker is not None and not isinstance( - parallel_worker, int): - raise ValueError( - f"parallel_worker need a integer, but get {type(parallel_worker)}" - ) - if encoder_params_name is None and ansatz_params_name is None: - encoder_params_name = [] - ansatz_params_name = [i for i in circ_right.params_name] - for i in circ_left.params_name: - if i not in ansatz_params_name: - ansatz_params_name.append(i) - if encoder_params_name is not None and not isinstance( - encoder_params_name, list): - raise ValueError( - f"encoder_params_name requires a list of str, but get {type(encoder_params_name)}" - ) - if ansatz_params_name is not None and not isinstance( - ansatz_params_name, list): - raise ValueError( - f"ansatz_params_name requires a list of str, but get {type(ansatz_params_name)}" - ) - if encoder_params_name is None: - encoder_params_name = [] - if ansatz_params_name is None: - ansatz_params_name = [] - s1 = set(circ_right.params_name) | set(circ_left.params_name) - s2 = set(encoder_params_name) | set(ansatz_params_name) - if s1 - s2 or s2 - s1: - raise ValueError( - "encoder_params_name and ansatz_params_name are different with circuit parameters" - ) - version = "both" - if not ansatz_params_name: - version = "encoder" - if not encoder_params_name: - version = "ansatz" - - def grad_ops(*inputs): - if version == "both" and len(inputs) != 2: - raise ValueError("Need two inputs!") - if version in ("encoder", "ansatz") and len(inputs) != 1: - raise ValueError("Need one input!") - if version == "both": - _check_encoder(inputs[0], len(encoder_params_name)) - _check_ansatz(inputs[1], len(ansatz_params_name)) - batch_threads, mea_threads = _thread_balance( - inputs[0].shape[0], len(hams), parallel_worker) - inputs0 = inputs[0] - inputs1 = inputs[1] - if version == "encoder": - _check_encoder(inputs[0], len(encoder_params_name)) - batch_threads, mea_threads = _thread_balance( - inputs[0].shape[0], len(hams), parallel_worker) - inputs0 = inputs[0] - inputs1 = np.array([]) - if version == "ansatz": - _check_ansatz(inputs[0], len(ansatz_params_name)) - batch_threads, mea_threads = _thread_balance( - 1, len(hams), parallel_worker) - inputs0 = np.array([[]]) - inputs1 = inputs[0] - if circ_left: - f_g1_g2 = self.sim.non_hermitian_measure_with_grad( - [i.get_cpp_obj() for i in hams], - [i.get_cpp_obj(hermitian=True) for i in hams], - circ_right.get_cpp_obj(), - circ_right.get_cpp_obj(hermitian=True), - circ_left.get_cpp_obj(), - circ_left.get_cpp_obj(hermitian=True), inputs0, inputs1, - encoder_params_name, ansatz_params_name, batch_threads, - mea_threads) - else: - f_g1_g2 = self.sim.hermitian_measure_with_grad( - [i.get_cpp_obj() for i in hams], circ_right.get_cpp_obj(), - circ_right.get_cpp_obj(hermitian=True), inputs0, inputs1, - encoder_params_name, ansatz_params_name, batch_threads, - mea_threads) - res = np.array(f_g1_g2) - if version == 'both': - f = res[:, :, 0] - g1 = res[:, :, 1:1 + len(encoder_params_name)] - g2 = res[:, :, 1 + len(encoder_params_name):] - return f, g1, g2 - f = res[:, :, 0] - g = res[:, :, 1:] - return f, g - - grad_wrapper = GradOpsWrapper(grad_ops, hams, circ_right, circ_left, - encoder_params_name, ansatz_params_name, - parallel_worker) - s = f'{self.n_qubits} qubit' + ('' if self.n_qubits == 1 else 's') - s += f' {self.backend} VQA Operator' - grad_wrapper.set_str(s) - return grad_wrapper - - -def _check_encoder(data, encoder_params_size): - if not isinstance(data, np.ndarray): - raise ValueError( - f"encoder parameters need numpy array, but get {type(data)}") - data_shape = data.shape - if len(data_shape) != 2: - raise ValueError("encoder data requires a two dimension numpy array") - if data_shape[1] != encoder_params_size: - raise ValueError( - f"encoder parameters size do not match with encoder parameters name,\ -need {encoder_params_size} but get {data_shape[1]}.") - - -def _check_ansatz(data, ansatz_params_size): - """check ansatz""" - if not isinstance(data, np.ndarray): - raise ValueError( - f"ansatz parameters need numpy array, but get {type(data)}") - data_shape = data.shape - if len(data_shape) != 1: - raise ValueError("ansatz data requires a one dimension numpy array") - if data_shape[0] != ansatz_params_size: - raise ValueError( - f"ansatz parameters size do not match with ansatz parameters name,\ -need {ansatz_params_size} but get {data_shape[0]}") - - -def _thread_balance(n_prs, n_meas, parallel_worker): - """threa balance""" - if parallel_worker is None: - parallel_worker = n_meas * n_prs - if n_meas * n_prs <= parallel_worker: - batch_threads = n_prs - mea_threads = n_meas - else: - if n_meas < n_prs: - batch_threads = min(n_prs, parallel_worker) - mea_threads = min(n_meas, max(1, parallel_worker // batch_threads)) - else: - mea_threads = min(n_meas, parallel_worker) - batch_threads = min(n_prs, max(1, parallel_worker // mea_threads)) - return batch_threads, mea_threads - - -class GradOpsWrapper: - """ - Wrapper the gradient operator that with the information that generate this - gradient operator. - - Args: - grad_ops (Union[FunctionType, MethodType])): A function or a method - that return forward value and gradient w.r.t parameters. - hams (Hamiltonian): The hamiltonian that generate this grad ops. - circ_right (Circuit): The right circuit that generate this grad ops. - circ_left (Circuit): The left circuit that generate this grad ops. - encoder_params_name (list[str]): The encoder parameters name. - ansatz_params_name (list[str]): The ansatz parameters name. - parallel_worker (int): The number of parallel worker to run the batch. - """ - def __init__(self, grad_ops, hams, circ_right, circ_left, - encoder_params_name, ansatz_params_name, parallel_worker): - self.grad_ops = grad_ops - self.hams = hams - self.circ_right = circ_right - self.circ_left = circ_left - self.encoder_params_name = encoder_params_name - self.ansatz_params_name = ansatz_params_name - self.parallel_worker = parallel_worker - self.str = '' - - def __call__(self, *args): - return self.grad_ops(*args) - - def set_str(self, s): - """Set expression for gradient operator.""" - self.str = s - - -__all__ = ['Simulator', 'get_supported_simulator', 'GradOpsWrapper'] diff --git a/mindquantum/src/CMakeLists.txt b/mindquantum/src/CMakeLists.txt deleted file mode 100644 index 5e44f0a50..000000000 --- a/mindquantum/src/CMakeLists.txt +++ /dev/null @@ -1,69 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -# lint_cmake: -whitespace/indent - -# for pybind -pybind11_add_module(mqbackend ${CMAKE_CURRENT_SOURCE_DIR}/binding.cc) -target_compile_definitions(mqbackend PUBLIC $<$:ENABLE_PROJECTQ> - $<$:ENABLE_QUEST>) -target_include_directories(mqbackend PUBLIC ${CMAKE_CURRENT_LIST_DIR}) - -target_link_libraries(mqbackend PUBLIC ${PARALLEL_LIBS} "$<$:mq_projectq>" - "$<$:QuEST;mq_quest>" mq_base) - -set_output_directory_auto(mqbackend mindquantum) -python_install_set_rpath(mqbackend "") - -set(MQ_BASE_HEADERS - gate/basic_gate.h - gate/gates.h - hamiltonian/hamiltonian.h - matrix/two_dim_matrix.h - core/popcnt.h - pr/parameter_resolver.h - projector/projector.h - sparse/algo.h - sparse/csrhdmatrix.h - sparse/paulimat.h - sparse/sparse_utils.h - core/type.h - core/utils.h) -set(MQ_BASE_SOURCES utils.cc) -add_subdirectory(backends) - -if(ENABLE_CUDA) - add_library(mq_base STATIC ${MQ_BASE_SOURCES} ${MQ_BASE_HEADERS}) - - target_compile_definitions(mq_base PUBLIC GPUACCELERATED) - target_link_libraries(mq_base PUBLIC $,CUDA::cudart_static,CUDA::cudart>) -else() - add_library(mq_base STATIC ${MQ_BASE_SOURCES} ${MQ_BASE_HEADERS}) -endif() -target_include_directories(mq_base PUBLIC ${CMAKE_CURRENT_LIST_DIR}) - -if(WIN32) - get_filename_component(CXX_DIR ${CMAKE_CXX_COMPILER} PATH) - file(GLOB LIB_LIST ${CXX_DIR}/libstdc++-6.dll ${CXX_DIR}/libwinpthread-1.dll - ${CXX_DIR}/libssp-0.dll ${CXX_DIR}/libgcc_s_*-1.dll ${CXX_DIR}/libgomp-1.dll) - # install(FILES ${LIB_LIST} DESTINATION ${CMAKE_BINARY_DIR}) - foreach(WIN_DEP_LIB ${LIB_LIST}) - MESSAGE("COPYING ${WIN_DEP_LIB}") - file(COPY ${WIN_DEP_LIB} DESTINATION ${MQBACKEND_OUTPUT_DIR} FOLLOW_SYMLINK_CHAIN) - endforeach() -endif() \ No newline at end of file diff --git a/mindquantum/src/backends/CMakeLists.txt b/mindquantum/src/backends/CMakeLists.txt deleted file mode 100644 index 607990e8d..000000000 --- a/mindquantum/src/backends/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -if(ENABLE_PROJECTQ) - add_subdirectory(projectq) -endif() - -if(ENABLE_QUEST) - add_subdirectory(quest) -endif() diff --git a/mindquantum/src/backends/projectq/CMakeLists.txt b/mindquantum/src/backends/projectq/CMakeLists.txt deleted file mode 100644 index d054138f1..000000000 --- a/mindquantum/src/backends/projectq/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -add_library(mq_projectq INTERFACE) -target_sources(mq_projectq INTERFACE projectq.h projectq_utils.h) - -target_compile_definitions(mq_projectq INTERFACE INTRIN) -target_compile_options(mq_projectq INTERFACE -ffast-math -mavx2) -target_include_directories(mq_projectq INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -target_link_libraries(mq_projectq INTERFACE projectq) diff --git a/mindquantum/src/backends/projectq/projectq.h b/mindquantum/src/backends/projectq/projectq.h deleted file mode 100644 index 63d0d140c..000000000 --- a/mindquantum/src/backends/projectq/projectq.h +++ /dev/null @@ -1,487 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_BACKENDS_PROJECTQ_PROJECTQ_H_ -#define MINDQUANTUM_BACKENDS_PROJECTQ_PROJECTQ_H_ - -#include - -#include -#include -#include -#include -#include - -#include "backends/projectq/projectq_utils.h" -#include "core/utils.h" -#include "gate/basic_gate.h" -#include "hamiltonian/hamiltonian.h" -#include "pr/parameter_resolver.h" -#include "projectq/backends/_sim/_cppkernels/simulator.hpp" - -namespace mindquantum { -namespace projectq { -template -class Projectq : public Simulator { - private: - unsigned n_qubits_; - VT ordering_; - unsigned len_; - RndEngine rnd_eng_; - std::function rng_; - - public: - Projectq() : Simulator(1, 1), n_qubits_(1), rnd_eng_(1) { - for (unsigned i = 0; i < n_qubits_; i++) { - ordering_.push_back(i); - } - len_ = (1UL << (n_qubits_ + 1)); - std::uniform_real_distribution dist(0., 1.); - rng_ = std::bind(dist, std::ref(rnd_eng_)); - } - - Projectq(unsigned seed, unsigned N) : Simulator(seed, N), n_qubits_(N), rnd_eng_(seed) { - for (unsigned i = 0; i < n_qubits_; i++) { - ordering_.push_back(i); - } - len_ = (1UL << (n_qubits_ + 1)); - std::uniform_real_distribution dist(0., 1.); - rng_ = std::bind(dist, std::ref(rnd_eng_)); - } - Projectq(unsigned seed, unsigned N, calc_type *vec) : Simulator(seed, N), n_qubits_(N), rnd_eng_(seed) { - for (unsigned i = 0; i < n_qubits_; i++) { - ordering_.push_back(i); - } - len_ = (1UL << (n_qubits_ + 1)); - set_wavefunction(vec, ordering_); - std::uniform_real_distribution dist(0., 1.); - rng_ = std::bind(dist, std::ref(rnd_eng_)); - } - void InitializeSimulator() { - if (vec_ != NULL) { - free(vec_); - } - vec_ = (StateVector) calloc(len_, sizeof(calc_type)); - vec_[0] = 1; - } - - void InitializeSimulator(const VT> &circ) { - Projectq::InitializeSimulator(); - Projectq::ApplyCircuit(circ); - } - - void InitializeSimulator(CTP vec) { - } - - void SetState(VT> vec) { - set_wavefunction(reinterpret_cast(vec.data()), ordering_); - } - - void ApplyGate(const BasicGate &gate) { - Projectq::apply_controlled_gate(MCast(gate.base_matrix_.matrix_), VCast(gate.obj_qubits_), - VCast(gate.ctrl_qubits_)); - } - - void ApplyGate(const BasicGate &gate, const ParameterResolver &pr, bool diff = false) { - T theta = LinearCombine(pr, gate.params_); - if (diff) { - Projectq::apply_controlled_gate(MCast(gate.param_diff_matrix_(theta).matrix_), VCast(gate.obj_qubits_), - VCast(gate.ctrl_qubits_)); - } else { - Projectq::apply_controlled_gate(MCast(gate.param_matrix_(theta).matrix_), VCast(gate.obj_qubits_), - VCast(gate.ctrl_qubits_)); - } - } - - unsigned ApplyMeasure(const BasicGate &gate) { - run(); - auto qubit = gate.obj_qubits_[0]; - auto mask = (1UL << qubit); - calc_type zero_amps = 0; - // #pragma omp parallel for schedule(static) reduction(+ : zero_amps) - for (unsigned i = 0; i < (len_ >> 1); i++) { - if ((i & mask) == 0) { - zero_amps += vec_[2 * i] * vec_[2 * i] + vec_[2 * i + 1] * vec_[2 * i + 1]; - } - } - unsigned collapse = (static_cast(rng_() > zero_amps) << qubit); - auto norm = (collapse == 0) ? sqrt(zero_amps) : sqrt(1 - zero_amps); -#pragma omp parallel for schedule(static) - for (unsigned i = 0; i < (len_ >> 1); i++) { - if ((i & mask) == collapse) { - vec_[2 * i] /= norm; - vec_[2 * i + 1] /= norm; - } else { - vec_[2 * i] = 0; - vec_[2 * i + 1] = 0; - } - } - return (collapse >> qubit); - } - - void ApplyCircuit(const VT> &circ) { - for (auto &gate : circ) { - Projectq::ApplyGate(gate); - } - Projectq::run(); - } - - void ApplyCircuit(const VT> &circ, const ParameterResolver &pr) { - for (auto &gate : circ) { - if (gate.parameterized_) { - Projectq::ApplyGate(gate, pr); - } else { - Projectq::ApplyGate(gate); - } - } - Projectq::run(); - } - - VT Sampling(const VT> &circ, const ParameterResolver &pr, size_t shots, - const MST &key_map, unsigned seed) { - auto key_size = key_map.size(); - VT res(shots * key_size); - for (size_t i = 0; i < shots; i++) { - Projectq sim = Projectq(seed + i, n_qubits_, vec_); - auto res0 = sim.ApplyCircuitWithMeasure(circ, pr, key_map); - for (size_t j = 0; j < key_size; j++) { - res[i * key_size + j] = res0[j]; - } - } - return res; - } - - VT ApplyCircuitWithMeasure(const VT> &circ, const ParameterResolver &pr, - const MST &key_map) { - auto key_size = key_map.size(); - VT res(key_size); - for (auto &gate : circ) { - if (gate.is_measure_) { - auto collapse = ApplyMeasure(gate); - res[key_map.at(gate.name_)] = collapse; - } else if (gate.parameterized_) { - ApplyGate(gate, pr); - } else { - ApplyGate(gate); - } - } - return res; - } - - void ApplyHamiltonian(const Hamiltonian &ham) { - Projectq::run(); - if (ham.how_to_ == ORIGIN) { - Projectq::apply_qubit_operator(HCast(ham.ham_), Projectq::ordering_); - } else if (ham.how_to_ == BACKEND) { - Projectq::vec_ = sparse::Csr_Dot_Vec(ham.ham_sparse_main_, ham.ham_sparse_second_, - Projectq::vec_); - } else { - Projectq::vec_ = sparse::Csr_Dot_Vec(ham.ham_sparse_main_, Projectq::vec_); - } - } - - VT> RightSizeGrad(calc_type *left_vec, calc_type *right_vec, const Hamiltonian &ham, - const VT> &circ, const VT> &herm_circ, - const ParameterResolver &pr, const MST &p_map) { - VT> f_g(p_map.size() + 1, 0); - Projectq sim_left = Projectq(1, n_qubits_, left_vec); - sim_left.ApplyHamiltonian(ham); - f_g[0] = ComplexInnerProduct(sim_left.vec_, right_vec, static_cast(len_)); - Projectq sim_right = Projectq(1, n_qubits_, right_vec); - Projectq sim_right_tmp = Projectq(1, n_qubits_); - for (size_t j = 0; j < circ.size(); j++) { - if ((!herm_circ[j].parameterized_) || (herm_circ[j].params_.requires_grad_parameters_.size() == 0)) { - if (herm_circ[j].parameterized_) { - sim_left.ApplyGate(herm_circ[j], pr, false); - sim_right.ApplyGate(herm_circ[j], pr, false); - } else { - sim_left.ApplyGate(herm_circ[j]); - sim_right.ApplyGate(herm_circ[j]); - } - } else { - sim_right.ApplyGate(herm_circ[j], pr, false); - sim_right.run(); - sim_right_tmp.set_wavefunction(sim_right.vec_, ordering_); - sim_right_tmp.ApplyGate(circ[circ.size() - j - 1], pr, true); - sim_right_tmp.run(); - sim_left.run(); - CT gi = 0; - if (herm_circ[j].ctrl_qubits_.size() == 0) { - gi = ComplexInnerProduct(sim_left.vec_, sim_right_tmp.vec_, static_cast(len_)); - } else { - gi = ComplexInnerProductWithControl(sim_left.vec_, sim_right_tmp.vec_, - static_cast(len_), - GetControlMask(herm_circ[j].ctrl_qubits_)); - } - for (auto &it : herm_circ[j].params_.requires_grad_parameters_) { - f_g[1 + p_map.at(it)] += circ[circ.size() - j - 1].params_.data_.at(it) * gi; - } - sim_left.ApplyGate(herm_circ[j], pr, false); - } - } - return f_g; - } - - CT GetExpectation(const Hamiltonian &ham) { - Projectq sim = Projectq(1, n_qubits_, vec_); - sim.ApplyHamiltonian(ham); - auto out = ComplexInnerProduct(sim.vec_, vec_, static_cast(len_)); - return out; - } - - VT>> HermitianMeasureWithGrad(const VT> &hams, const VT> &circ, - const VT> &herm_circ, const ParameterResolver &pr, - const MST &p_map, size_t mea_threads) { - auto n_hams = hams.size(); - auto n_params = pr.data_.size(); - VT>> output; - for (size_t i = 0; i < n_hams; i++) { - output.push_back({}); - for (size_t j = 0; j < n_params + 1; j++) { - output[i].push_back({0, 0}); - } - } - - Projectq sim = Projectq(1, n_qubits_, vec_); - sim.ApplyCircuit(circ, pr); - if (n_hams == 1) { - auto f_g = sim.RightSizeGrad(sim.vec_, sim.vec_, hams[0], circ, herm_circ, pr, p_map); - for (size_t g = 1; g < n_params + 1; g++) { - f_g[g] += std::conj(f_g[g]); - } - output[0] = f_g; - } else { - std::vector tasks; - tasks.reserve(mea_threads); - size_t end = 0; - size_t offset = n_hams / mea_threads; - size_t left = n_hams % mea_threads; - for (size_t i = 0; i < mea_threads; ++i) { - size_t start = end; - end = start + offset; - if (i < left) { - end += 1; - } - - auto task = [&, start, end]() { - for (size_t n = start; n < end; n++) { - auto f_g = sim.RightSizeGrad(sim.vec_, sim.vec_, hams[n], circ, herm_circ, pr, p_map); - for (size_t g = 1; g < n_params + 1; g++) { - f_g[g] += std::conj(f_g[g]); - } - output[n] = f_g; - } - }; - tasks.emplace_back(task); - } - for (auto &t : tasks) { - t.join(); - } - } - return output; - } - - VT>>> HermitianMeasureWithGrad(const VT> &hams, const VT> &circ, - const VT> &herm_circ, const VVT &enc_data, - const VT &ans_data, const VS &enc_name, const VS &ans_name, - size_t batch_threads, size_t mea_threads) { - auto n_hams = hams.size(); - auto n_prs = enc_data.size(); - auto n_params = enc_name.size() + ans_name.size(); - VT>>> output; - for (size_t i = 0; i < n_prs; i++) { - output.push_back({}); - for (size_t j = 0; j < n_hams; j++) { - output[i].push_back({}); - for (size_t k = 0; k < n_params + 1; k++) { - output[i][j].push_back({0, 0}); - } - } - } - MST p_map; - for (size_t i = 0; i < enc_name.size(); i++) { - p_map[enc_name[i]] = i; - } - for (size_t i = 0; i < ans_name.size(); i++) { - p_map[ans_name[i]] = i + enc_name.size(); - } - - if (n_prs == 1) { - ParameterResolver pr = ParameterResolver(); - pr.SetData(enc_data[0], enc_name); - pr.SetData(ans_data, ans_name); - output[0] = HermitianMeasureWithGrad(hams, circ, herm_circ, pr, p_map, mea_threads); - } else { - std::vector tasks; - tasks.reserve(batch_threads); - size_t end = 0; - size_t offset = n_prs / batch_threads; - size_t left = n_prs % batch_threads; - for (size_t i = 0; i < batch_threads; ++i) { - size_t start = end; - end = start + offset; - if (i < left) { - end += 1; - } - auto task = [&, start, end]() { - for (size_t n = start; n < end; n++) { - ParameterResolver pr = ParameterResolver(); - pr.SetData(enc_data[n], enc_name); - pr.SetData(ans_data, ans_name); - auto f_g = HermitianMeasureWithGrad(hams, circ, herm_circ, pr, p_map, mea_threads); - output[n] = f_g; - } - }; - tasks.emplace_back(task); - } - for (auto &t : tasks) { - t.join(); - } - } - return output; - } - - VT>> NonHermitianMeasureWithGrad(const VT> &hams, const VT> &herm_hams, - const VT> &left_circ, const VT> &herm_left_circ, - const VT> &right_circ, - const VT> &herm_right_circ, const ParameterResolver &pr, - const MST &p_map, size_t mea_threads) { - auto n_hams = hams.size(); - auto n_params = pr.data_.size(); - VT>> output; - for (size_t i = 0; i < n_hams; i++) { - output.push_back({}); - for (size_t j = 0; j < n_params + 1; j++) { - output[i].push_back({0, 0}); - } - } - Projectq sim = Projectq(1, n_qubits_, vec_); - sim.ApplyCircuit(right_circ, pr); - Projectq sim2 = Projectq(1, n_qubits_, vec_); - sim2.ApplyCircuit(left_circ, pr); - if (n_hams == 1) { - auto f_g1 = sim2.RightSizeGrad(sim.vec_, sim2.vec_, herm_hams[0], left_circ, herm_left_circ, pr, p_map); - auto f_g2 = sim.RightSizeGrad(sim2.vec_, sim.vec_, hams[0], right_circ, herm_right_circ, pr, p_map); - for (size_t g = 1; g < n_params + 1; g++) { - f_g2[g] += std::conj(f_g1[g]); - } - output[0] = f_g2; - } else { - std::vector tasks; - tasks.reserve(mea_threads); - size_t end = 0; - size_t offset = n_hams / mea_threads; - size_t left = n_hams % mea_threads; - for (size_t i = 0; i < mea_threads; i++) { - size_t start = end; - end = start + offset; - if (i < left) { - end += 1; - } - auto task = [&, start, end]() { - for (size_t n = start; n < end; n++) { - auto f_g1 = sim2.RightSizeGrad(sim.vec_, sim2.vec_, herm_hams[n], left_circ, herm_left_circ, pr, - p_map); - auto f_g2 = sim.RightSizeGrad(sim2.vec_, sim.vec_, hams[n], right_circ, herm_right_circ, pr, - p_map); - for (size_t g = 1; g < n_params + 1; g++) { - f_g2[g] += std::conj(f_g1[g]); - } - output[n] = f_g2; - } - }; - tasks.emplace_back(task); - } - for (auto &t : tasks) { - t.join(); - } - } - return output; - } - - VT>>> NonHermitianMeasureWithGrad(const VT> &hams, const VT> &herm_hams, - const VT> &left_circ, - const VT> &herm_left_circ, - const VT> &right_circ, - const VT> &herm_right_circ, const VVT &enc_data, - const VT &ans_data, const VS &enc_name, const VS &ans_name, - size_t batch_threads, size_t mea_threads) { - auto n_hams = hams.size(); - auto n_prs = enc_data.size(); - auto n_params = enc_name.size() + ans_name.size(); - VT>>> output; - for (size_t i = 0; i < n_prs; i++) { - output.push_back({}); - for (size_t j = 0; j < n_hams; j++) { - output[i].push_back({}); - for (size_t k = 0; k < n_params + 1; k++) { - output[i][j].push_back({0, 0}); - } - } - } - MST p_map; - for (size_t i = 0; i < enc_name.size(); i++) { - p_map[enc_name[i]] = i; - } - for (size_t i = 0; i < ans_name.size(); i++) { - p_map[ans_name[i]] = i + enc_name.size(); - } - if (n_prs == 1) { - ParameterResolver pr = ParameterResolver(); - pr.SetData(enc_data[0], enc_name); - pr.SetData(ans_data, ans_name); - output[0] = NonHermitianMeasureWithGrad(hams, herm_hams, left_circ, herm_left_circ, right_circ, - herm_right_circ, pr, p_map, mea_threads); - } else { - std::vector tasks; - tasks.reserve(batch_threads); - size_t end = 0; - size_t offset = n_prs / batch_threads; - size_t left = n_prs % batch_threads; - for (size_t i = 0; i < batch_threads; ++i) { - size_t start = end; - end = start + offset; - if (i < left) { - end += 1; - } - auto task = [&, start, end]() { - for (size_t n = start; n < end; n++) { - ParameterResolver pr = ParameterResolver(); - pr.SetData(enc_data[n], enc_name); - pr.SetData(ans_data, ans_name); - auto f_g = NonHermitianMeasureWithGrad(hams, herm_hams, left_circ, herm_left_circ, right_circ, - herm_right_circ, pr, p_map, mea_threads); - output[n] = f_g; - } - }; - tasks.emplace_back(task); - } - for (auto &t : tasks) { - t.join(); - } - } - return output; - } - - void PrintInfo() { - std::cout << n_qubits_ << " qubits simulator with currently quantum state at:" << std::endl; - for (unsigned i = 0; i < (len_ >> 1); i++) { - std::cout << "(" << vec_[2 * i] << ", " << vec_[2 * i + 1] << ")" << std::endl; - } - } -}; -} // namespace projectq -} // namespace mindquantum -#endif // MINDQUANTUM_BACKENDS_PROJECTQ_PROJECTQ_H_ diff --git a/mindquantum/src/backends/projectq/projectq_utils.h b/mindquantum/src/backends/projectq/projectq_utils.h deleted file mode 100644 index cf03bee51..000000000 --- a/mindquantum/src/backends/projectq/projectq_utils.h +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_BACKENDS_PROJECTQ_UTILS_H_ -#define MINDQUANTUM_BACKENDS_PROJECTQ_UTILS_H_ -#include -#include - -#include "core/utils.h" -#include "projectq/backends/_sim/_cppkernels/fusion.hpp" -#include "projectq/backends/_sim/_cppkernels/intrin/alignedallocator.hpp" -#include "projectq/backends/_sim/_cppkernels/simulator.hpp" - -namespace mindquantum { -namespace projectq { -inline VT VCast(const VT &a) { - return VT(a.begin(), a.end()); -} - -template -inline Fusion::Matrix MCast(const VVT> &m) { - Fusion::Matrix out; - for (size_t i = 0; i < m.size(); i++) { - std::vector> col; - for (auto &a : m[i]) { - // cppcheck-suppress useStlAlgorithm - col.push_back({a.real(), a.imag()}); - } - out.push_back(col); - } - return out; -} - -template -inline Simulator::ComplexTermsDict HCast(const VT> &ham_) { - Simulator::ComplexTermsDict res; - for (auto &pt : ham_) { - Simulator::Term term; - for (auto &pw : pt.first) { - // cppcheck-suppress useStlAlgorithm - term.push_back(std::make_pair(static_cast(pw.first), pw.second)); - } - res.push_back(std::make_pair(term, static_cast(pt.second))); - } - return res; -} -} // namespace projectq -} // namespace mindquantum -#endif // MINDQUANTUM_BACKENDS_PROJECTQ_H_ diff --git a/mindquantum/src/backends/quest/CMakeLists.txt b/mindquantum/src/backends/quest/CMakeLists.txt deleted file mode 100644 index 3a6f30965..000000000 --- a/mindquantum/src/backends/quest/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ============================================================================== - -add_library(mq_quest INTERFACE) -target_sources(mq_quest INTERFACE quest.h quest_utils.h) - -target_include_directories(mq_quest INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -target_link_libraries(mq_quest INTERFACE QuEST) diff --git a/mindquantum/src/backends/quest/quest.h b/mindquantum/src/backends/quest/quest.h deleted file mode 100644 index b65d2680e..000000000 --- a/mindquantum/src/backends/quest/quest.h +++ /dev/null @@ -1,323 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_BACKENDS_QUEST_QUEST_H_ -#define MINDQUANTUM_BACKENDS_QUEST_QUEST_H_ -#include - -#include "QuEST.h" -#include "QuEST_internal.h" -#include "backends/quest/quest_utils.h" -#include "core/utils.h" -#include "gate/basic_gate.h" -#include "hamiltonian/hamiltonian.h" -#include "pr/parameter_resolver.h" - -namespace mindquantum { -namespace quest { -template -struct Quest { - int n_qubits_; - QuESTEnv env; - Qureg qubits; - explicit Quest(int n) : n_qubits_(n), env(createQuESTEnv()), qubits(createQureg(n, env)) { - initZeroState(qubits); - } - Quest(int n, Qureg ref_qureg) : n_qubits_(n), env(createQuESTEnv()), qubits(createQureg(n, env)) { - cloneQureg(qubits, ref_qureg); - } - Quest() : n_qubits_(1), env(createQuESTEnv()), qubits(createQureg(n_qubits_, env)) { - initZeroState(qubits); - } - ~Quest() { - destroyQureg(qubits, env); - destroyQuESTEnv(env); - } - void InitializeSimulator() { - initZeroState(qubits); - } - void PrintInfo() { - reportQuregParams(qubits); - reportQuESTEnv(env); - } - VT> GetVec() { - VT> result; - for (size_t i = 0; i < (1UL << n_qubits_); i++) { - result.push_back({getRealAmp(qubits, i), getImagAmp(qubits, i)}); - } - return result; - } - void ApplyGate(const BasicGate &gate) { - if (gate.ctrl_qubits_.size() == 0) { // no control - if (gate.name_ == gX) { - pauliX(qubits, gate.obj_qubits_[0]); - } else if (gate.name_ == gY) { - pauliY(qubits, gate.obj_qubits_[0]); - } else if (gate.name_ == gZ) { - pauliZ(qubits, gate.obj_qubits_[0]); - } else if (gate.name_ == gH) { - hadamard(qubits, gate.obj_qubits_[0]); - } else if (gate.name_ == gT) { - tGate(qubits, gate.obj_qubits_[0]); - } else if (gate.name_ == gS) { - sGate(qubits, gate.obj_qubits_[0]); - } else if (gate.name_ == gCNOT) { - controlledNot(qubits, gate.obj_qubits_[1], gate.obj_qubits_[0]); - } else if (gate.name_ == gSWAP) { - swapGate(qubits, gate.obj_qubits_[0], gate.obj_qubits_[1]); - } else if (gate.name_ == gRX) { - rotateX(qubits, gate.obj_qubits_[0], static_cast(gate.applied_value_)); - } else if (gate.name_ == gRY) { - rotateY(qubits, gate.obj_qubits_[0], static_cast(gate.applied_value_)); - } else if (gate.name_ == gRZ) { - rotateZ(qubits, gate.obj_qubits_[0], static_cast(gate.applied_value_)); - } else if (gate.name_ == gPS) { - phaseShift(qubits, gate.obj_qubits_[0], static_cast(gate.applied_value_)); - } else { - auto obj = Vec2Intptr(gate.obj_qubits_); - auto m = Dim2Matrix2ComplexMatrixN(gate.base_matrix_, gate.obj_qubits_.size()); - multiQubitUnitary(qubits, obj, static_cast(gate.obj_qubits_.size()), m); - if (obj != nullptr) { - free(obj); - } - destroyComplexMatrixN(m); - } - } else if (gate.ctrl_qubits_.size() == 1) { - if (gate.name_ == gX) { - controlledNot(qubits, gate.ctrl_qubits_[0], gate.obj_qubits_[0]); - } else { - auto obj = Vec2Intptr(gate.obj_qubits_); - auto m = Dim2Matrix2ComplexMatrixN(gate.base_matrix_, gate.obj_qubits_.size()); - controlledMultiQubitUnitary(qubits, gate.ctrl_qubits_[0], obj, - static_cast(gate.obj_qubits_.size()), m); - if (obj != nullptr) { - free(obj); - } - destroyComplexMatrixN(m); - } - } else { - auto ctrl = Vec2Intptr(gate.ctrl_qubits_); - auto obj = Vec2Intptr(gate.obj_qubits_); - auto m = Dim2Matrix2ComplexMatrixN(gate.base_matrix_, gate.obj_qubits_.size()); - int nctrl = static_cast(gate.ctrl_qubits_.size()); - int nobj = static_cast(gate.obj_qubits_.size()); - multiControlledMultiQubitUnitary(qubits, ctrl, nctrl, obj, nobj, m); - destroyComplexMatrixN(m); - if (obj != nullptr) { - free(obj); - } - if (ctrl != nullptr) { - free(ctrl); - } - } - } - void ApplyGate(const BasicGate &gate, const ParameterResolver &pr, bool diff = false) { - T theta = LinearCombine(pr, gate.params_); - if (diff) { - auto ctrl = Vec2Intptr(gate.ctrl_qubits_); - auto obj = Vec2Intptr(gate.obj_qubits_); - int nctrl = static_cast(gate.ctrl_qubits_.size()); - int nobj = static_cast(gate.obj_qubits_.size()); - if (nctrl == 0 && nobj == 1) { - auto m = Dim2Matrix2ComplexMatrix2(gate.param_diff_matrix_(theta)); - applyMatrix2(qubits, gate.obj_qubits_[0], m); - } else if (nctrl == 0 && nobj == 2) { - auto m = Dim2Matrix2ComplexMatrix4(gate.param_diff_matrix_(theta)); - applyMatrix4(qubits, gate.obj_qubits_[0], gate.obj_qubits_[1], m); - } else if (nctrl != 0) { - auto m = Dim2Matrix2ComplexMatrixN(gate.param_diff_matrix_(theta), gate.obj_qubits_.size()); - applyMultiControlledMatrixN(qubits, ctrl, nctrl, obj, nobj, m); - destroyComplexMatrixN(m); - } else { - auto m = Dim2Matrix2ComplexMatrixN(gate.param_diff_matrix_(theta), gate.obj_qubits_.size()); - applyMatrixN(qubits, obj, nobj, m); - destroyComplexMatrixN(m); - } - if (obj != nullptr) { - free(obj); - } - if (ctrl != nullptr) { - free(ctrl); - } - } else { - BasicGate gate_tmp = gate; - gate_tmp.ApplyValue(theta); - ApplyGate(gate_tmp); - } - } - void ApplyCircuit(const VT> &circ) { - for (auto &gate : circ) { - Quest::ApplyGate(gate); - } - } - // TODO(unknown): - // Stuff.🔥🔥🔥🔥🔥《模拟器开发》↪️开发quest模拟器的测量算符操作逻辑,GPU和CPU都需通过 - unsigned ApplyMeasure(const BasicGate &gate) { - } - - // TODO(unknown): - // Stuff.🔥🔥🔥🔥🔥《模拟器开发》↪️开发quest模拟器的采样方法逻辑,GPU和CPU都需通过 - VT Sampling(const VT> &circ, const ParameterResolver &pr, size_t shots, - const MST &key_map, unsigned seed) { - } - - void ApplyCircuit(const VT> &circ, const ParameterResolver &pr) { - for (auto &gate : circ) { - if (gate.parameterized_) { - Quest::ApplyGate(gate, pr); - } else { - Quest::ApplyGate(gate); - } - } - } - - void ApplyHamiltonian(const Hamiltonian &ham) { - if (ham.how_to_ == ORIGIN) { - Qureg qureg = createQureg(n_qubits_, env); - Qureg ori = createQureg(n_qubits_, env); - cloneQureg(ori, qubits); - initBlankState(qubits); - for (size_t i = 0; i < ham.ham_.size(); i++) { - cloneQureg(qureg, ori); - for (size_t j = 0; j < ham.ham_[i].first.size(); j++) { - if (ham.ham_[i].first[j].second == 'X') { - statevec_pauliX(qureg, ham.ham_[i].first[j].first); - } - if (ham.ham_[i].first[j].second == 'Y') { - statevec_pauliY(qureg, ham.ham_[i].first[j].first); - } - if (ham.ham_[i].first[j].second == 'Z') { - statevec_pauliZ(qureg, ham.ham_[i].first[j].first); - } - } - Complex coef = (Complex){.real = ham.ham_[i].second, .imag = 0}; - Complex iden = (Complex){.real = 1, .imag = 0}; - Complex zero = (Complex){.real = 0, .imag = 0}; - setWeightedQureg(coef, qureg, iden, qubits, zero, qubits); - } - destroyQureg(qureg, env); - destroyQureg(ori, env); - } else if (ham.how_to_ == BACKEND) { - Csr_Dot_Vec(ham.ham_sparse_main_, ham.ham_sparse_second_, qubits); - } else { - Csr_Dot_Vec(ham.ham_sparse_main_, qubits); - } - } - - CT GetExpectation(const Hamiltonian &ham) { - // auto quest_ham = HCast(ham, n_qubits_); - Quest sim = Quest(n_qubits_); - cloneQureg(sim.qubits, qubits); - sim.ApplyHamiltonian(ham); - CT value; - if (qubits.isDensityMatrix) { - value = {calcTotalProb(sim.qubits), 0}; - } else { - value = Complex2Complex(calcInnerProduct(qubits, sim.qubits)); - } - - return value; - } - - VT> RightSizeGrad(Qureg left_vec, Qureg right_vec, const Hamiltonian &ham, const VT> &circ, - const VT> &herm_circ, const ParameterResolver &pr, - const MST &p_map) { - VT> f_g(p_map.size() + 1, 0); - Quest sim_left = Quest(n_qubits_, left_vec); - sim_left.ApplyHamiltonian(ham); - f_g[0] = Complex2Complex(calcInnerProduct(sim_left.qubits, right_vec)); - Quest sim_right = Quest(n_qubits_, right_vec); - Quest sim_right_tmp = Quest(n_qubits_); - for (size_t j = 0; j < circ.size(); j++) { - if ((!herm_circ[j].parameterized_) || (herm_circ[j].params_.requires_grad_parameters_.size() == 0)) { - if (herm_circ[j].parameterized_) { - sim_left.ApplyGate(herm_circ[j], pr, false); - sim_right.ApplyGate(herm_circ[j], pr, false); - } else { - sim_left.ApplyGate(herm_circ[j]); - sim_right.ApplyGate(herm_circ[j]); - } - } else { - sim_right.ApplyGate(herm_circ[j], pr, false); - cloneQureg(sim_right_tmp.qubits, sim_right.qubits); - sim_right_tmp.ApplyGate(circ[circ.size() - j - 1], pr, true); - CT gi = 0; - // if (herm_circ[j].ctrl_qubits_.size() == 0) { - gi = Complex2Complex(calcInnerProduct(sim_left.qubits, sim_right_tmp.qubits)); - // } else { - // TODO(unknown): - // Stuff.🔥🔥🔥🔥🔥《模拟器开发》↪️当含参门有控制位时如何进行内积运算 - // gi = ComplexInnerProductWithControl(sim_left.vec_, - // sim_right_tmp.vec_, static_cast(len_), - // GetControlMask(herm_circ[j].ctrl_qubits_)); - // } - for (auto &it : herm_circ[j].params_.requires_grad_parameters_) { - f_g[1 + p_map.at(it)] -= herm_circ[j].params_.data_.at(it) * gi; - } - sim_left.ApplyGate(herm_circ[j], pr, false); - } - } - return f_g; - } - - VT>> HermitianMeasureWithGrad(const VT> &hams, const VT> &circ, - const VT> &herm_circ, const ParameterResolver &pr, - const VT ¶ms_order, size_t mea_threads) { - auto n_hams = hams.size(); - auto n_params = pr.data_.size(); - MST p_map; - for (size_t i = 0; i < params_order.size(); i++) { - p_map[params_order[i]] = i; - } - VT>> output(n_hams); - Quest sim = Quest(n_qubits_, qubits); - auto t0 = NOW(); - sim.ApplyCircuit(circ, pr); - auto t1 = NOW(); - std::cout << "evolution time " << TimeDuration(t0, t1) << std::endl; - - // #pragma omp parallel for schedule(static) - for (size_t i = 0; i < n_hams; i++) { - auto t2 = NOW(); - auto f_g = sim.RightSizeGrad(sim.qubits, sim.qubits, hams[i], circ, herm_circ, pr, p_map); - auto t3 = NOW(); - std::cout << "grad " << TimeDuration(t2, t3) << std::endl; - for (size_t g = 1; g < n_params + 1; g++) { - f_g[g] += std::conj(f_g[g]); - } - output[i] = f_g; - } - return output; - } - - VT>>> HermitianMeasureWithGrad(const VT> &hams, const VT> &circ, - const VT> &herm_circ, const VT> &prs, - const VT ¶ms_order, size_t batch_threads, - size_t mea_threads) { - // auto n_hams = hams.size(); - auto n_prs = prs.size(); - // auto n_params = prs[0].data_.size(); - VT>>> output(n_prs); - // #pragma omp parallel for schedule(static) - for (size_t i = 0; i < n_prs; i++) { - auto f_g = HermitianMeasureWithGrad(hams, circ, herm_circ, prs[i], params_order, mea_threads); - output[i] = f_g; - } - return output; - } -}; -} // namespace quest -} // namespace mindquantum -#endif diff --git a/mindquantum/src/backends/quest/quest_utils.h b/mindquantum/src/backends/quest/quest_utils.h deleted file mode 100644 index fa9bfd419..000000000 --- a/mindquantum/src/backends/quest/quest_utils.h +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_BACKENDS_QUEST_UTILS_H_ -#define MINDQUANTUM_BACKENDS_QUEST_UTILS_H_ -#include - -#include "QuEST.h" -#include "QuEST_validation.h" -#include "core/utils.h" -#include "gate/basic_gate.h" -#include "sparse/csrhdmatrix.h" -#ifdef GPUACCELERATED -# include -#endif - -namespace mindquantum { -namespace quest { -using sparse::CsrHdMatrix; - -template -inline ComplexMatrixN Dim2Matrix2ComplexMatrixN(const Dim2Matrix &matrix, int n_qubits) { - auto m = createComplexMatrixN(n_qubits); - validateMatrixInit(m, __func__); - int dim = 1 << m.numQubits; - for (int i = 0; i < dim; i++) - for (int j = 0; j < dim; j++) { - m.real[i][j] = std::real(matrix.matrix_[i][j]); - m.imag[i][j] = std::imag(matrix.matrix_[i][j]); - } - return m; -} - -template -inline ComplexMatrix2 Dim2Matrix2ComplexMatrix2(const Dim2Matrix &matrix) { - ComplexMatrix2 m; - int dim = 1 << 1; - for (int i = 0; i < dim; i++) - for (int j = 0; j < dim; j++) { - m.real[i][j] = std::real(matrix.matrix_[i][j]); - m.imag[i][j] = std::imag(matrix.matrix_[i][j]); - } - return m; -} - -template -inline ComplexMatrix4 Dim2Matrix2ComplexMatrix4(const Dim2Matrix &matrix) { - ComplexMatrix4 m; - int dim = 1 << 2; - for (int i = 0; i < dim; i++) - for (int j = 0; j < dim; j++) { - m.real[i][j] = std::real(matrix.matrix_[i][j]); - m.imag[i][j] = std::imag(matrix.matrix_[i][j]); - } - return m; -} - -// void destroyComplexMatrix2(ComplexMatrix2 m) { -// int numRows = 1 << 1; -// for (int r = 0; r < numRows; r++) { -// free(m.real[r]); -// free(m.imag[r]); -// } -// free(m.real); -// free(m.imag); -// } - -// void destroyComplexMatrix4(ComplexMatrix4 m) { -// int numRows = 1 << 2; -// for (int r = 0; r < numRows; r++) { -// free(m.real[r]); -// free(m.imag[r]); -// } -// free(m.real); -// free(m.imag); -// } - -inline int *Vec2Intptr(const VT &vec) { - int *out = reinterpret_cast(malloc(sizeof(int) * vec.size())); - for (size_t i = 0; i < vec.size(); i++) { - out[i] = static_cast(vec[i]); - } - return out; -} -template -inline PauliHamil HCast(const Hamiltonian &ham, int n_qubits) { - PauliHamil quest_ham = createPauliHamil(n_qubits, ham.ham_.size()); - validateHamilParams(quest_ham.numQubits, quest_ham.numSumTerms, __func__); - - int i = 0; - for (int t = 0; t < quest_ham.numSumTerms; t++) { - quest_ham.termCoeffs[t] = static_cast(ham.ham_[t].second); - size_t curr = 0; - for (size_t q = 0; q < static_cast(quest_ham.numQubits); q++) { - if (curr < ham.ham_[t].first.size()) { - if (ham.ham_[t].first[curr].first == q) { - if (ham.ham_[t].first[curr].second == 'X') { - quest_ham.pauliCodes[i] = PAULI_X; - } else if (ham.ham_[t].first[curr].second == 'Y') { - quest_ham.pauliCodes[i] = PAULI_Y; - } else { - quest_ham.pauliCodes[i] = PAULI_Z; - } - curr++; - } else { - quest_ham.pauliCodes[i] = PAULI_I; - } - } else { - quest_ham.pauliCodes[i] = PAULI_I; - } - i++; - } - } - return quest_ham; -} - -template -inline CT Complex2Complex(Complex a) { - CT res = {a.real, a.imag}; - return res; -} - -template -inline void Csr_Dot_Vec(std::shared_ptr> a, Qureg qureg) { - auto dim = a->dim_; - auto vec_real = reinterpret_cast(malloc(sizeof(qreal) * dim)); - auto vec_imag = reinterpret_cast(malloc(sizeof(qreal) * dim)); - auto ori_real = reinterpret_cast(malloc(sizeof(qreal) * dim)); - auto ori_imag = reinterpret_cast(malloc(sizeof(qreal) * dim)); - // auto nnz = a->nnz_; - auto data = a->data_; - auto indptr = a->indptr_; - auto indices = a->indices_; -#ifdef GPUACCELERATED - cudaMemcpy(ori_real, qureg.deviceStateVec.real, dim * sizeof(qreal), cudaMemcpyDeviceToHost); - cudaMemcpy(ori_imag, qureg.deviceStateVec.imag, dim * sizeof(qreal), cudaMemcpyDeviceToHost); -#else -# pragma omp parallel for schedule(static) - for (Index i = 0; i < dim; ++i) { - ori_real[i] = statevec_getRealAmp(qureg, i); - ori_imag[i] = statevec_getImagAmp(qureg, i); - } -#endif -#pragma omp parallel for schedule(static) - for (Index i = 0; i < dim; i++) { - CT sum = {0.0, 0.0}; - for (Index j = indptr[i]; j < indptr[i + 1]; j++) { - sum += data[j] * CT(ori_real[indices[j]], ori_imag[indices[j]]); - } - vec_real[i] = std::real(sum); - vec_imag[i] = std::imag(sum); - } - initStateFromAmps(qureg, vec_real, vec_imag); - free(vec_real); - free(vec_imag); - free(ori_real); - free(ori_imag); -} - -template -inline void Csr_Dot_Vec(std::shared_ptr> a, std::shared_ptr> b, Qureg qureg) { - auto dim = a->dim_; - auto vec_real = reinterpret_cast(malloc(sizeof(qreal) * dim)); - auto vec_imag = reinterpret_cast(malloc(sizeof(qreal) * dim)); - auto ori_real = reinterpret_cast(malloc(sizeof(qreal) * dim)); - auto ori_imag = reinterpret_cast(malloc(sizeof(qreal) * dim)); - // auto nnz = a->nnz_; - auto data = a->data_; - auto indptr = a->indptr_; - auto indices = a->indices_; - auto data_b = b->data_; - auto indptr_b = b->indptr_; - auto indices_b = b->indices_; - -#ifdef GPUACCELERATED - cudaMemcpy(ori_real, qureg.deviceStateVec.real, dim * sizeof(qreal), cudaMemcpyDeviceToHost); - cudaMemcpy(ori_imag, qureg.deviceStateVec.imag, dim * sizeof(qreal), cudaMemcpyDeviceToHost); -#else -# pragma omp parallel for schedule(static) - for (Index i = 0; i < dim; ++i) { - ori_real[i] = statevec_getRealAmp(qureg, i); - ori_imag[i] = statevec_getImagAmp(qureg, i); - } -#endif -#pragma omp parallel for schedule(static) - for (Index i = 0; i < dim; i++) { - CT sum = {0.0, 0.0}; - for (Index j = indptr[i]; j < indptr[i + 1]; j++) { - sum += data[j] * CT(ori_real[indices[j]], ori_imag[indices[j]]); - } - for (Index j = indptr_b[i]; j < indptr_b[i + 1]; j++) { - sum += data_b[j] * CT(ori_real[indices_b[j]], ori_imag[indices_b[j]]); - } - vec_real[i] = std::real(sum); - vec_imag[i] = std::imag(sum); - } - initStateFromAmps(qureg, vec_real, vec_imag); - free(vec_real); - free(vec_imag); - free(ori_real); - free(ori_imag); -} - -} // namespace quest -} // namespace mindquantum -#endif diff --git a/mindquantum/src/binding.cc b/mindquantum/src/binding.cc deleted file mode 100644 index 88a11b23a..000000000 --- a/mindquantum/src/binding.cc +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include -#include -#include -#include - -#ifdef ENABLE_PROJECTQ -# include "backends/projectq/projectq.h" -#endif -#ifdef ENABLE_QUEST -# include "backends/quest/quest.h" -#endif -#include "core/type.h" -#include "gate/gates.h" -#include "hamiltonian/hamiltonian.h" -#include "matrix/two_dim_matrix.h" -#include "pr/parameter_resolver.h" -#include "sparse/algo.h" -#include "sparse/csrhdmatrix.h" -#include "sparse/paulimat.h" - -namespace py = pybind11; -namespace mindquantum { -using mindquantum::sparse::Csr_Plus_Csr; -using mindquantum::sparse::GetPauliMat; -using mindquantum::sparse::PauliMat; -using mindquantum::sparse::PauliMatToCsrHdMatrix; -using mindquantum::sparse::SparseHamiltonian; -using mindquantum::sparse::TransposeCsrHdMatrix; - -#ifdef ENABLE_PROJECTQ -using mindquantum::projectq::Projectq; -#endif - -#ifdef ENABLE_QUEST -using mindquantum::quest::Quest; -#endif - -// Interface with python -PYBIND11_MODULE(mqbackend, m) { - m.doc() = "MindQuantum c plugin"; - - // matrix - py::class_, std::shared_ptr>>(m, "dim2matrix") - .def(py::init<>()) - .def(py::init> &>()); - - // basic gate - py::class_, std::shared_ptr>>(m, "basic_gate") - .def(py::init<>()) - .def(py::init>()) - .def(py::init()) - .def("PrintInfo", &BasicGate::PrintInfo) - .def("apply_value", &BasicGate::ApplyValue) - .def_readwrite("obj_qubits", &BasicGate::obj_qubits_) - .def_readwrite("ctrl_qubits", &BasicGate::ctrl_qubits_) - .def_readwrite("params", &BasicGate::params_) - .def_readwrite("daggered", &BasicGate::daggered_) - .def_readwrite("applied_value", &BasicGate::applied_value_) - .def_readwrite("base_matrix", &BasicGate::base_matrix_) - .def_readwrite("hermitian_prop", &BasicGate::hermitian_prop_); - m.def("get_gate_by_name", &GetGateByName); - m.def("get_measure_gate", &GetMeasureGate); - // parameter resolver - py::class_, std::shared_ptr>>(m, "parameter_resolver") - .def(py::init &, const SS &, const SS &>()) - .def(py::init<>()) - .def(py::init &, const VT &, const VT &>()) - .def_readonly("data", &ParameterResolver::data_) - .def_readonly("no_grad_parameters", &ParameterResolver::no_grad_parameters_) - .def_readonly("requires_grad_parameters", &ParameterResolver::requires_grad_parameters_); - - m.def("linear_combine", &LinearCombine); - - // pauli mat - py::class_, std::shared_ptr>>(m, "pauli_mat") - .def(py::init<>()) - .def(py::init &, Index>()) - .def_readonly("n_qubits", &PauliMat::n_qubits_) - .def_readonly("dim", &PauliMat::dim_) - .def_readwrite("coeff", &PauliMat::p_) - .def("PrintInfo", &PauliMat::PrintInfo); - - m.def("get_pauli_mat", &GetPauliMat); - - // csr_hd_matrix - py::class_, std::shared_ptr>>(m, "csr_hd_matrix") - .def(py::init<>()) - .def(py::init, py::array_t, py::array_t>>()) - .def("PrintInfo", &CsrHdMatrix::PrintInfo); - m.def("csr_plus_csr", &Csr_Plus_Csr); - m.def("transpose_csr_hd_matrix", &TransposeCsrHdMatrix); - m.def("pauli_mat_to_csr_hd_matrix", &PauliMatToCsrHdMatrix); - - // hamiltonian - py::class_, std::shared_ptr>>(m, "hamiltonian") - .def(py::init<>()) - .def(py::init> &>()) - .def(py::init> &, Index>()) - .def(py::init>, Index>()) - .def_readwrite("how_to", &Hamiltonian::how_to_) - .def_readwrite("n_qubits", &Hamiltonian::n_qubits_) - .def_readwrite("ham", &Hamiltonian::ham_) - .def_readwrite("ham_sparse_main", &Hamiltonian::ham_sparse_main_) - .def_readwrite("ham_sparse_second", &Hamiltonian::ham_sparse_second_); - m.def("sparse_hamiltonian", &SparseHamiltonian); - -#ifdef ENABLE_PROJECTQ - // projectq simulator - py::class_, std::shared_ptr>>(m, "projectq") - .def(py::init<>()) - .def(py::init()) - .def("reset", py::overload_cast<>(&Projectq::InitializeSimulator)) - .def("apply_measure", &Projectq::ApplyMeasure) - .def("apply_gate", py::overload_cast &>(&Projectq::ApplyGate)) - .def("apply_gate", - py::overload_cast &, const ParameterResolver &, bool>(&Projectq::ApplyGate)) - .def("apply_circuit", py::overload_cast> &>(&Projectq::ApplyCircuit)) - .def("apply_circuit", - py::overload_cast> &, const ParameterResolver &>(&Projectq::ApplyCircuit)) - .def("apply_circuit_with_measure", &Projectq::ApplyCircuitWithMeasure) - .def("sampling", &Projectq::Sampling) - .def("apply_hamiltonian", &Projectq::ApplyHamiltonian) - .def("get_expectation", &Projectq::GetExpectation) - .def("PrintInfo", &Projectq::PrintInfo) - .def("run", &Projectq::run) - .def("get_qs", &Projectq::cheat) - .def("set_qs", &Projectq::SetState) - .def("hermitian_measure_with_grad", - py::overload_cast> &, const VT> &, const VT> &, - const VVT &, const VT &, const VS &, const VS &, size_t, size_t>( - &Projectq::HermitianMeasureWithGrad)) - .def("non_hermitian_measure_with_grad", - py::overload_cast> &, const VT> &, const VT> &, - const VT> &, const VT> &, const VT> &, - const VVT &, const VT &, const VS &, const VS &, size_t, size_t>( - &Projectq::NonHermitianMeasureWithGrad)); -#endif - -#ifdef ENABLE_QUEST - // quest simulator - py::class_, std::shared_ptr>>(m, "quest") - .def(py::init()) - .def(py::init<>()) - .def("reset", &Quest::InitializeSimulator) - .def("PrintInfo", &Quest::PrintInfo) - .def("get_qs", &Quest::GetVec) - .def("apply_gate", py::overload_cast &>(&Quest::ApplyGate)) - .def("apply_gate", - py::overload_cast &, const ParameterResolver &, bool>(&Quest::ApplyGate)) - .def("apply_circuit", py::overload_cast> &>(&Quest::ApplyCircuit)) - .def("apply_circuit", - py::overload_cast> &, const ParameterResolver &>(&Quest::ApplyCircuit)) - .def("apply_hamiltonian", &Quest::ApplyHamiltonian) - .def("get_expectation", &Quest::GetExpectation) - .def("hermitian_measure_with_grad", - py::overload_cast> &, const VT> &, const VT> &, - const VT> &, const VT &, size_t, size_t>( - &Quest::HermitianMeasureWithGrad)); -#endif -} -} // namespace mindquantum diff --git a/mindquantum/src/core/popcnt.h b/mindquantum/src/core/popcnt.h deleted file mode 100644 index fc292d539..000000000 --- a/mindquantum/src/core/popcnt.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_POPCNT_H_ -#define MINDQUANTUM_POPCNT_H_ - -namespace mindquantum { -static char POPCNTTABLE[256] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, - 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, - 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, - 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, - 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, - 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, - 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; -} // namespace mindquantum -#endif // MINDQUANTUM_POPCNT_H_ diff --git a/mindquantum/src/core/type.h b/mindquantum/src/core/type.h deleted file mode 100644 index 8c54432fc..000000000 --- a/mindquantum/src/core/type.h +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_TYPE_H_ -#define MINDQUANTUM_TYPE_H_ -// #ifndef MQONLY -// #include "backend/kernel_compiler/cpu/quantum/quantum_simulator/popcnt.h" -// #else -// #include "./popcnt.h" -// #endif - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -namespace mindquantum { -#define PRECISION 1e-8 -#define COS1_2(theta) static_cast(cos(theta / 2)) -#define SIN1_2(theta) static_cast(sin(theta / 2)) - -using Index = int64_t; -#ifdef FLOAT -using MT = float; -#else -using MT = double; -#endif - -template -using VT = std::vector; - -template -using VVT = VT>; - -template -using VVVT = VT>; - -template -using CT = std::complex; - -template -using CTP = CT *; - -template -using MST = std::map; - -using SS = std::set; -using VS = VT; -using PauliWord = std::pair; - -template -using PauliTerm = std::pair, T>; - -using TimePoint = std::chrono::steady_clock::time_point; - -TimePoint NOW(); - -int TimeDuration(TimePoint start, TimePoint end); - -// void Duration() -struct PauliMask { - Index mask_x = 0; - Index mask_y = 0; - Index mask_z = 0; - Index num_x = 0; - Index num_y = 0; - Index num_z = 0; -}; - -const char kNThreads[] = "n_threads"; -const char kNQubits[] = "n_qubits"; -const char kParamNames[] = "param_names"; -const char kEncoderParamsNames[] = "encoder_params_names"; -const char kAnsatzParamsNames[] = "ansatz_params_names"; -const char kGateNames[] = "gate_names"; -const char kGateMatrix[] = "gate_matrix"; -const char kGateObjQubits[] = "gate_obj_qubits"; -const char kGateCtrlQubits[] = "gate_ctrl_qubits"; -const char kGateThetas[] = "gate_thetas"; -const char kNPG[] = "npg"; -const char kGateParamsNames[] = "gate_params_names"; -const char kGateCoeff[] = "gate_coeff"; -const char kGateRequiresGrad[] = "gate_requires_grad"; -const char kHamsPauliCoeff[] = "hams_pauli_coeff"; -const char kHamsPauliWord[] = "hams_pauli_word"; -const char kHamsPauliQubit[] = "hams_pauli_qubit"; -const char kHowTo[] = "how_to"; -const char kHamsSparseData[] = "hams_sparse_data"; -const char kHamsSparseIndice[] = "hams_sparse_indice"; -const char kHamsSparseIndptr[] = "hams_sparse_indptr"; -const char kIsProjector[] = "is_projector"; -const char kProjectors[] = "projectors"; - -enum SparseHow : int64_t { - ORIGIN = 0, - BACKEND, - FRONTEND, -}; -enum HermitianProp : int64_t { - SELFHERMITIAN = 0, - DOHERMITIAN, - PARAMSOPPOSITE, -}; -const char gX[] = "X"; -const char gY[] = "Y"; -const char gZ[] = "Z"; -const char gI[] = "I"; -const char gH[] = "H"; -const char gT[] = "T"; -const char gS[] = "S"; -const char gCNOT[] = "CNOT"; -const char gCZ[] = "CZ"; -const char gSWAP[] = "SWAP"; -const char gISWAP[] = "ISWAP"; -const char gRX[] = "RX"; -const char gRY[] = "RY"; -const char gRZ[] = "RZ"; -const char gPS[] = "PS"; -const char gXX[] = "XX"; -const char gYY[] = "YY"; -const char gZZ[] = "ZZ"; -} // namespace mindquantum -#endif // MINDQUANTUM_TYPE_H_ diff --git a/mindquantum/src/core/utils.h b/mindquantum/src/core/utils.h deleted file mode 100644 index 30afc2313..000000000 --- a/mindquantum/src/core/utils.h +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINDQUANTUM_UTILS_H_ -#define MINDQUANTUM_UTILS_H_ - -#ifdef ENABLE_OPENMP -# include -#endif // ENABLE_OPENMP // NOLINT - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core/type.h" - -namespace mindquantum { -extern const VT> POLAR; -template -CT ComplexInnerProduct(const ST *v1, const ST *v2, Index len) { - // len is (1UL>>n_qubits)*2 - ST real_part = 0; - ST imag_part = 0; - auto size = len / 2; -#pragma omp parallel for reduction(+ : real_part, imag_part) - for (Index i = 0; i < size; i++) { - real_part += v1[2 * i] * v2[2 * i] + v1[2 * i + 1] * v2[2 * i + 1]; - imag_part += v1[2 * i] * v2[2 * i + 1] - v1[2 * i + 1] * v2[2 * i]; - } - - CT result = {static_cast(real_part), static_cast(imag_part)}; - return result; -} - -template -CT ComplexInnerProductWithControl(const ST *v1, const ST *v2, Index len, Index ctrlmask) { - // len is (1UL>>n_qubits)*2 - ST real_part = 0; - ST imag_part = 0; - auto size = len / 2; -#pragma omp parallel for reduction(+ : real_part, imag_part) - for (Index i = 0; i < size; i++) { - if ((i & ctrlmask) == ctrlmask) { - real_part += v1[2 * i] * v2[2 * i] + v1[2 * i + 1] * v2[2 * i + 1]; - imag_part += v1[2 * i] * v2[2 * i + 1] - v1[2 * i + 1] * v2[2 * i]; - } - } - CT result = {static_cast(real_part), static_cast(imag_part)}; - return result; -} - -Index GetControlMask(const VT &ctrls); - -PauliMask GetPauliMask(const VT &pws); - -// inline int CountOne(uint32_t n) { return __popcntd(n); } -// inline int CountOne(uint64_t n) { return __popcntq(n);} -inline int CountOne(uint32_t n) { - int result; - asm("popcnt %1,%0" : "=r"(result) : "r"(n)); - return result; -} - -inline int CountOne(int64_t n) { - uint32_t *p = reinterpret_cast(&n); - return CountOne(p[0]) + CountOne(p[1]); -} - -// inline int CountOne(uint64_t n) { -// uint8_t *p = reinterpret_cast(&n); -// return POPCNTTABLE[p[0]] + POPCNTTABLE[p[1]] + POPCNTTABLE[p[2]] + -// POPCNTTABLE[p[3]] + POPCNTTABLE[p[4]] + POPCNTTABLE[p[5]] + -// POPCNTTABLE[p[6]] + POPCNTTABLE[p[7]]; -// } - -// inline int CountOne(uint32_t n) { -// uint8_t *p = reinterpret_cast(&n); -// return POPCNTTABLE[p[0]] + POPCNTTABLE[p[1]] + POPCNTTABLE[p[2]] + -// POPCNTTABLE[p[3]]; -// } - -template -PauliTerm GenerateRandomPauliTerm(Index n_qubits) { - std::default_random_engine e(std::clock()); - std::uniform_real_distribution ut(-1.0, 1.0); - auto coeff = ut(e); - std::uniform_int_distribution uit(0, 3); - VT pws; - for (Index i = 0; i < n_qubits; i++) { - auto p = uit(e); - if (p != 3) { - pws.push_back(std::make_pair(i, (p + 'X'))); - } - } - return std::make_pair(pws, coeff); -} - -template -void ShowPauliTerm(const PauliTerm &pt) { - std::cout << pt.second << " ["; - for (Index i = 0; i < static_cast(pt.first.size()); i++) { - auto &pw = pt.first[i]; - std::cout << pw.second << pw.first; - if (i != static_cast(pt.first.size()) - 1) { - std::cout << " "; - } - } - std::cout << "]" << std::endl; -} - -TimePoint NOW(); -int TimeDuration(TimePoint start, TimePoint end); - -template -void PrintVec(T *vec, size_t len) { - auto cvec = reinterpret_cast>(vec); - for (size_t i = 0; i < len / 2; i++) { - std::cout << cvec[i] << std::endl; - } -} -} // namespace mindquantum -#endif // MINDQUANTUM_UTILS_H_ diff --git a/mindquantum/src/gate/basic_gate.h b/mindquantum/src/gate/basic_gate.h deleted file mode 100644 index 249d6c880..000000000 --- a/mindquantum/src/gate/basic_gate.h +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINDQUANTUM_GATE_basic_gate_H_ -#define MINDQUANTUM_GATE_basic_gate_H_ -#include -#include - -#include "core/utils.h" -#include "matrix/two_dim_matrix.h" -#include "pr/parameter_resolver.h" - -namespace mindquantum { -namespace py = pybind11; - -template -inline VVT> CastArray(const py::object& fun, T theta) { - py::array_t> a = fun(theta); - py::buffer_info buf = a.request(); - if (buf.ndim != 2) { - throw std::runtime_error("Gate matrix must be two dimension!"); - } - if (buf.shape[0] != buf.shape[1]) { - throw std::runtime_error("Gate matrix need a square matrix!"); - } - CTP ptr = static_cast>(buf.ptr); - - VVT> m; - for (size_t i = 0; i < buf.shape[0]; i++) { - m.push_back({}); - for (size_t j = 0; j < buf.shape[1]; j++) { - m[i].push_back(ptr[i * buf.shape[1] + j]); - } - } - return m; -} -template -struct BasicGate { - bool parameterized_ = false; - std::string name_; - VT obj_qubits_; - VT ctrl_qubits_; - ParameterResolver params_; - int64_t hermitian_prop_ = SELFHERMITIAN; - bool daggered_ = false; - T applied_value_ = 0; - bool is_measure_ = false; - Dim2Matrix base_matrix_; - std::function(T)> param_matrix_; - std::function(T)> param_diff_matrix_; - // Dim2Matrix (*param_matrix_)(T para); - // Dim2Matrix (*param_diff_matrix_)(T para); - void PrintInfo() { - if (!daggered_) { - std::cout << "Gate name: " << name_ << std::endl; - } else { - std::cout << "Gate name: " << name_ << " (daggered version)" << std::endl; - } - std::cout << "Parameterized: " << parameterized_ << std::endl; - if (!parameterized_) { - base_matrix_.PrintInfo(); - } - if (obj_qubits_.size() != 0) { - std::cout << "Obj qubits: "; - for (auto o : obj_qubits_) { - std::cout << o << " "; - } - std::cout << std::endl; - } - if (ctrl_qubits_.size() != 0) { - std::cout << "Control qubits: "; - for (auto o : ctrl_qubits_) { - std::cout << o << " "; - } - std::cout << std::endl; - } - } - void ApplyValue(T theta) { - if (parameterized_) { - parameterized_ = false; - applied_value_ = theta; - base_matrix_ = param_matrix_(theta); - } - } - BasicGate() { - } - BasicGate(bool parameterized, const std::string& name, int64_t hermitian_prop, Dim2Matrix base_matrix) - : parameterized_(parameterized), name_(name), hermitian_prop_(hermitian_prop), base_matrix_(base_matrix) { - } - BasicGate(bool parameterized, const std::string& name, int64_t hermitian_prop, - Dim2Matrix (*param_matrix)(T para), Dim2Matrix (*param_diff_matrix)(T para)) - : parameterized_(parameterized) - , name_(name) - , hermitian_prop_(hermitian_prop) - , param_matrix_(param_matrix) - , param_diff_matrix_(param_diff_matrix) { - } - BasicGate(const std::string& name, int64_t hermitian_prop, py::object matrix_fun, py::object diff_matrix_fun) - : parameterized_(true), name_(name), hermitian_prop_(hermitian_prop) { - param_matrix_ = [matrix_fun](T theta) { - auto matrix = CastArray(matrix_fun, theta); - Dim2Matrix res = Dim2Matrix(matrix); - return res; - }; - param_diff_matrix_ = [diff_matrix_fun](T theta) { - auto matirx = CastArray(diff_matrix_fun, theta); - Dim2Matrix res = Dim2Matrix(matirx); - return res; - }; - } -}; -} // namespace mindquantum -#endif // MINDQUANTUM_GATE_basic_gate_H_ diff --git a/mindquantum/src/gate/gates.h b/mindquantum/src/gate/gates.h deleted file mode 100644 index 93f04b2fe..000000000 --- a/mindquantum/src/gate/gates.h +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINDQUANTUM_GATE_GATES_H_ -#define MINDQUANTUM_GATE_GATES_H_ -#include - -#include - -#include "core/utils.h" -#include "gate/basic_gate.h" -#include "matrix/two_dim_matrix.h" - -namespace mindquantum { -template -BasicGate XGate = {false, gX, SELFHERMITIAN, Dim2Matrix{{{{0, 0}, {1, 0}}, {{1, 0}, {0, 0}}}}}; - -template -BasicGate YGate = {false, gY, SELFHERMITIAN, Dim2Matrix{{{{0, 0}, {0, -1}}, {{0, 1}, {0, 0}}}}}; - -template -BasicGate ZGate = {false, gZ, SELFHERMITIAN, Dim2Matrix{{{{1, 0}, {0, 0}}, {{0, 0}, {-1, 0}}}}}; - -template -BasicGate IGate = {false, gI, SELFHERMITIAN, Dim2Matrix{{{{1, 0}, {0, 0}}, {{0, 0}, {1, 0}}}}}; - -template -BasicGate HGate = {false, gH, SELFHERMITIAN, - Dim2Matrix{{{{static_cast(M_SQRT1_2), 0}, {static_cast(M_SQRT1_2), 0}}, - {{static_cast(M_SQRT1_2), 0}, {-static_cast(M_SQRT1_2), 0}}}}}; - -template -BasicGate TGate = { - false, gT, DOHERMITIAN, - Dim2Matrix{{{{1, 0}, {0, 0}}, {{0, 0}, {static_cast(M_SQRT1_2), static_cast(M_SQRT1_2)}}}}}; - -template -BasicGate SGate = {false, gS, DOHERMITIAN, Dim2Matrix{{{{1, 0}, {0, 0}}, {{0, 0}, {0, 1}}}}}; - -template -BasicGate CNOTGate = {false, gCNOT, DOHERMITIAN, - Dim2Matrix{{{{1, 0}, {0, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {1, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {0, 0}, {1, 0}}, - {{0, 0}, {0, 0}, {1, 0}, {0, 0}}}}}; - -template -BasicGate CZGate = {false, gCZ, SELFHERMITIAN, - Dim2Matrix{{{{1, 0}, {0, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {1, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {1, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {0, 0}, {-1, 0}}}}}; - -template -BasicGate SWAPGate = {false, gSWAP, SELFHERMITIAN, - Dim2Matrix{{{{1, 0}, {0, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {1, 0}, {0, 0}}, - {{0, 0}, {1, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {0, 0}, {1, 0}}}}}; - -template -BasicGate ISWAPGate = {false, gISWAP, DOHERMITIAN, - Dim2Matrix{{{{1, 0}, {0, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {0, 1}, {0, 0}}, - {{0, 0}, {0, 1}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {0, 0}, {1, 0}}}}}; - -template -BasicGate RXGate = { - true, gRX, PARAMSOPPOSITE, - [](T theta) { - return Dim2Matrix{{{{COS1_2(theta), 0}, {0, -SIN1_2(theta)}}, {{0, -SIN1_2(theta)}, {COS1_2(theta), 0}}}}; - }, - [](T theta) { - return Dim2Matrix{ - {{{-SIN1_2(theta) / 2, 0}, {0, -COS1_2(theta) / 2}}, {{0, -COS1_2(theta) / 2}, {-SIN1_2(theta) / 2, 0}}}}; - }}; - -template -BasicGate RYGate = { - true, gRY, PARAMSOPPOSITE, - [](T theta) { - return Dim2Matrix{{{{COS1_2(theta), 0}, {-SIN1_2(theta), 0}}, {{SIN1_2(theta), 0}, {COS1_2(theta), 0}}}}; - }, - [](T theta) { - return Dim2Matrix{ - {{{-SIN1_2(theta) / 2, 0}, {-COS1_2(theta) / 2, 0}}, {{COS1_2(theta) / 2, 0}, {-SIN1_2(theta) / 2, 0}}}}; - }}; - -template -BasicGate RZGate = { - true, gRZ, PARAMSOPPOSITE, - [](T theta) { - return Dim2Matrix{{{{COS1_2(theta), -SIN1_2(theta)}, {0, 0}}, {{0, 0}, {COS1_2(theta), SIN1_2(theta)}}}}; - }, - [](T theta) { - return Dim2Matrix{ - {{{-SIN1_2(theta) / 2, -COS1_2(theta) / 2}, {0, 0}}, {{0, 0}, {-SIN1_2(theta) / 2, COS1_2(theta) / 2}}}}; - }}; - -template -BasicGate PSGate = {true, gPS, PARAMSOPPOSITE, - [](T theta) { - return Dim2Matrix{{{{1, 0}, {0, 0}}, {{0, 0}, {COS1_2(2 * theta), SIN1_2(2 * theta)}}}}; - }, - [](T theta) { - return Dim2Matrix{{{{0, 0}, {0, 0}}, {{0, 0}, {-SIN1_2(2 * theta), COS1_2(2 * theta)}}}}; - }}; - -template -BasicGate XXGate = {true, gXX, PARAMSOPPOSITE, - [](T theta) { - return Dim2Matrix{{{{COS1_2(2 * theta), 0}, {0, 0}, {0, 0}, {0, -SIN1_2(2 * theta)}}, - {{0, 0}, {COS1_2(2 * theta), 0}, {0, -SIN1_2(2 * theta)}, {0, 0}}, - {{0, 0}, {0, -SIN1_2(2 * theta)}, {COS1_2(2 * theta), 0}, {0, 0}}, - {{0, -SIN1_2(2 * theta)}, {0, 0}, {0, 0}, {COS1_2(2 * theta), 0}}}}; - }, - [](T theta) { - return Dim2Matrix{{{{-SIN1_2(2 * theta), 0}, {0, 0}, {0, 0}, {0, -COS1_2(2 * theta)}}, - {{0, 0}, {-SIN1_2(2 * theta), 0}, {0, -COS1_2(2 * theta)}, {0, 0}}, - {{0, 0}, {0, -COS1_2(2 * theta)}, {-SIN1_2(2 * theta), 0}, {0, 0}}, - {{0, -COS1_2(2 * theta)}, {0, 0}, {0, 0}, {-SIN1_2(2 * theta), 0}}}}; - }}; - -template -BasicGate YYGate = {true, gYY, PARAMSOPPOSITE, - [](T theta) { - return Dim2Matrix{{{{COS1_2(2 * theta), 0}, {0, 0}, {0, 0}, {0, SIN1_2(2 * theta)}}, - {{0, 0}, {COS1_2(2 * theta), 0}, {0, -SIN1_2(2 * theta)}, {0, 0}}, - {{0, 0}, {0, -SIN1_2(2 * theta)}, {COS1_2(2 * theta), 0}, {0, 0}}, - {{0, SIN1_2(2 * theta)}, {0, 0}, {0, 0}, {COS1_2(2 * theta), 0}}}}; - }, - [](T theta) { - return Dim2Matrix{{{{-SIN1_2(2 * theta), 0}, {0, 0}, {0, 0}, {0, COS1_2(2 * theta)}}, - {{0, 0}, {-SIN1_2(2 * theta), 0}, {0, -COS1_2(2 * theta)}, {0, 0}}, - {{0, 0}, {0, -COS1_2(2 * theta)}, {-SIN1_2(2 * theta), 0}, {0, 0}}, - {{0, COS1_2(2 * theta)}, {0, 0}, {0, 0}, {-SIN1_2(2 * theta), 0}}}}; - }}; - -template -BasicGate ZZGate = {true, gZZ, PARAMSOPPOSITE, - [](T theta) { - return Dim2Matrix{{{{COS1_2(2 * theta), -SIN1_2(2 * theta)}, {0, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {COS1_2(2 * theta), SIN1_2(2 * theta)}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {COS1_2(2 * theta), SIN1_2(2 * theta)}, {0, 0}}, - {{0, 0}, {0, 0}, {0, 0}, {COS1_2(2 * theta), -SIN1_2(2 * theta)}}}}; - }, - [](T theta) { - return Dim2Matrix{{{{-SIN1_2(2 * theta), -COS1_2(2 * theta)}, {0, 0}, {0, 0}, {0, 0}}, - {{0, 0}, {-SIN1_2(2 * theta), COS1_2(2 * theta)}, {0, 0}, {0, 0}}, - {{0, 0}, {0, 0}, {-SIN1_2(2 * theta), COS1_2(2 * theta)}, {0, 0}}, - {{0, 0}, {0, 0}, {0, 0}, {-SIN1_2(2 * theta), -COS1_2(2 * theta)}}}}; - }}; - -template -BasicGate GetMeasureGate(const std::string& name) { - BasicGate out; - out.name_ = name; - out.is_measure_ = true; - return out; -} - -template -BasicGate GetGateByName(const std::string& name) { - BasicGate out; - if (name == gX) { - out = XGate; - } else if (name == gY) { - out = YGate; - } else if (name == gZ) { - out = ZGate; - } else if (name == gI) { - out = IGate; - } else if (name == gH) { - out = HGate; - } else if (name == gT) { - out = TGate; - } else if (name == gS) { - out = SGate; - } else if (name == gCNOT) { - out = CNOTGate; - } else if (name == gSWAP) { - out = SWAPGate; - } else if (name == gISWAP) { - out = ISWAPGate; - } else if (name == gCZ) { - out = CZGate; - } else if (name == gRX) { - out = RXGate; - } else if (name == gRY) { - out = RYGate; - } else if (name == gRZ) { - out = RZGate; - } else if (name == gPS) { - out = PSGate; - } else if (name == gXX) { - out = XXGate; - } else if (name == gYY) { - out = YYGate; - } else if (name == gZZ) { - out = ZZGate; - } else { - auto msg = name + " not implement in backend!"; - throw std::invalid_argument(msg); - } - return out; -} -} // namespace mindquantum -#endif // MINDQUANTUM_GATE_GATES_H_ diff --git a/mindquantum/src/hamiltonian/hamiltonian.h b/mindquantum/src/hamiltonian/hamiltonian.h deleted file mode 100644 index fe3eeca43..000000000 --- a/mindquantum/src/hamiltonian/hamiltonian.h +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINDQUANTUM_HAMILTONIAN_HAMILTONIAN_H_ -#define MINDQUANTUM_HAMILTONIAN_HAMILTONIAN_H_ -#include - -#include "core/utils.h" -#include "sparse/algo.h" - -namespace mindquantum { -using mindquantum::sparse::CsrHdMatrix; -using mindquantum::sparse::SparseHamiltonian; -using mindquantum::sparse::TransposeCsrHdMatrix; - -template -struct Hamiltonian { - int64_t how_to_; - Index n_qubits_; - VT> ham_; - std::shared_ptr> ham_sparse_main_; - std::shared_ptr> ham_sparse_second_; - - Hamiltonian() { - } - - explicit Hamiltonian(const VT> &ham) : how_to_(ORIGIN), ham_(ham) { - } - - Hamiltonian(const VT> &ham, Index n_qubits) : how_to_(BACKEND), n_qubits_(n_qubits), ham_(ham) { - if (n_qubits_ > 16) { - std::cout << "Sparsing hamiltonian ..." << std::endl; - } - ham_sparse_main_ = SparseHamiltonian(ham_, n_qubits_); - ham_sparse_second_ = TransposeCsrHdMatrix(ham_sparse_main_); - if (n_qubits_ > 16) { - std::cout << "Sparsing hamiltonian finished!" << std::endl; - } - } - - Hamiltonian(std::shared_ptr> csr_mat, Index n_qubits) - : n_qubits_(n_qubits), how_to_(FRONTEND), ham_sparse_main_(csr_mat) { - } -}; -} // namespace mindquantum -#endif // MINDQUANTUM_HAMILTONIAN_HAMILTONIAN_H_ diff --git a/mindquantum/src/matrix/two_dim_matrix.h b/mindquantum/src/matrix/two_dim_matrix.h deleted file mode 100644 index 4ff2b020b..000000000 --- a/mindquantum/src/matrix/two_dim_matrix.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINDQUANTUM_MATRIX_TWO_DIM_MATRIX_H_ -#define MINDQUANTUM_MATRIX_TWO_DIM_MATRIX_H_ -#include -#include -#include - -#include "core/utils.h" -namespace mindquantum { -template -struct Dim2Matrix { - VVT> matrix_; - Dim2Matrix() { - } - explicit Dim2Matrix(const VVT> &m) : matrix_(m) { - } - void PrintInfo() { - if (matrix_.size() > 0) { - if (matrix_[0].size() > 0) { - std::cout << "<--Matrix of " << matrix_.size() << " X " << matrix_[0].size() << std::endl; - for (auto &col : matrix_) { - for (Index i = 0; i < static_cast(col.size()); i++) { - std::cout << col[i]; - if (i != static_cast(col.size() - 1)) { - std::cout << ", "; - } - } - std::cout << std::endl; - } - std::cout << "-->" << std::endl; - } - } - } -}; - -template -Dim2Matrix Dim2MatrixFromRI(const VT &real, const VT &imag) { - Dim2Matrix out; - for (Index i = 0; i < static_cast(real.size()); i++) { - out.matrix_.push_back({}); - for (Index j = 0; j < static_cast(real[i].size()); j++) { - out.matrix_[i].push_back(CT(std::stod(real[i][j]), std::stod(imag[i][j]))); - } - } - return out; -} -} // namespace mindquantum -#endif // MINDQUANTUM_MATRIX_TWO_DIM_MATRIX_H_ diff --git a/mindquantum/src/pr/parameter_resolver.h b/mindquantum/src/pr/parameter_resolver.h deleted file mode 100644 index fdae8f4a1..000000000 --- a/mindquantum/src/pr/parameter_resolver.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINDQUANTUM_PR_PARAMETER_RESOLVER_H_ -#define MINDQUANTUM_PR_PARAMETER_RESOLVER_H_ -#include -#include -#include - -#include "core/utils.h" - -namespace mindquantum { -template -struct ParameterResolver { - MST data_; - SS no_grad_parameters_; - SS requires_grad_parameters_; - - ParameterResolver() { - } - ParameterResolver(const MST &data, const SS &ngp, const SS &rgp) - : data_(data), no_grad_parameters_(ngp), requires_grad_parameters_(rgp) { - } - ParameterResolver(const VT &names, const VT &coeffs, const VT &requires_grads) { - for (Index i = 0; i < static_cast(names.size()); ++i) { - data_[names[i]] = coeffs[i]; - if (requires_grads[i]) { - requires_grad_parameters_.insert(names[i]); - } else { - no_grad_parameters_.insert(names[i]); - } - } - } - void SetData(const VT &data, const VS &name) { - for (size_t i = 0; i < data.size(); i++) { - data_[name[i]] = data[i]; - } - } - void Times(T f) { - for (auto &it : data_) { - data_[it.first] = -it.second; - } - } -}; - -template -T LinearCombine(const ParameterResolver &pr_big, const ParameterResolver &pr_small) { - T res = 0; - for (typename MST::const_iterator i = pr_small.data_.begin(); i != pr_small.data_.end(); ++i) { - res += (pr_big.data_.at(i->first) * i->second); - } - return res; -} -} // namespace mindquantum -#endif // MINDQUANTUM_PR_PARAMETER_RESOLVER_H_ diff --git a/mindquantum/src/projector/projector.h b/mindquantum/src/projector/projector.h deleted file mode 100644 index c0aef2cf7..000000000 --- a/mindquantum/src/projector/projector.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINDQUANTUM_PROJECTOR_PROJECTOR_H_ -#define MINDQUANTUM_PROJECTOR_PROJECTOR_H_ -#include - -#include "core/utils.h" - -namespace mindquantum { -struct Projector { - std::string proj_str_; - Index n_qubits_; - Index mask1_; - Index mask2_; - - explicit Projector(const std::string &proj_str) : proj_str_(proj_str) { - n_qubits_ = static_cast(proj_str_.length()); - mask1_ = 0; - mask2_ = 0; - for (auto i : proj_str_) { - if (i == '1') { - mask1_ = mask1_ * 2 + 1; - } else { - mask1_ = mask1_ * 2; - } - if (i == '0') { - mask2_ = mask2_ * 2; - } else { - mask2_ = mask2_ * 2 + 1; - } - } - } -}; -} // namespace mindquantum -#endif // MINDQUANTUM_PROJECTOR_PROJECTOR_H_ diff --git a/mindquantum/src/sparse/algo.h b/mindquantum/src/sparse/algo.h deleted file mode 100644 index 5068932c2..000000000 --- a/mindquantum/src/sparse/algo.h +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_SPARSE_ALGO_H_ -#define MINDQUANTUM_SPARSE_ALGO_H_ -#include - -#include "sparse/csrhdmatrix.h" -#include "sparse/paulimat.h" -#include "sparse/sparse_utils.h" - -namespace mindquantum { -namespace sparse { -template -std::shared_ptr> TransposeCsrHdMatrix(std::shared_ptr> a) { - auto &dim = a->dim_; - auto &nnz = a->nnz_; - auto &a_indices = a->indices_; - auto &a_indptr = a->indptr_; - auto &a_data = a->data_; - Index *indices = reinterpret_cast(malloc(sizeof(Index) * nnz)); - Index *indptr = reinterpret_cast(malloc(sizeof(Index) * (dim + 1))); - CTP data = reinterpret_cast>(malloc(sizeof(CT) * nnz)); - std::fill(indptr, indptr + dim, 0); - for (Index n = 0; n < nnz; n++) { - indptr[a_indices[n]]++; - } - for (Index col = 0, sum = 0; col < dim; col++) { - Index t = indptr[col]; - indptr[col] = sum; - sum += t; - } - indptr[dim] = nnz; - for (Index row = 0; row < dim; row++) { - for (Index jj = a_indptr[row]; jj < a_indptr[row + 1]; jj++) { - Index col = a_indices[jj]; - Index dest = indptr[col]; - indices[dest] = row; - data[dest] = std::conj(a_data[jj]); - indptr[col]++; - } - } - for (Index col = 0, last = 0; col <= dim; col++) { - Index t = indptr[col]; - indptr[col] = last; - last = t; - } - auto c = std::make_shared>(dim, nnz, indptr, indices, data); - return c; -} - -template -std::shared_ptr> PauliMatToCsrHdMatrix(std::shared_ptr> a) { - Index nnz = 0; - auto &col = a->col_; - auto &dim = a->dim_; - auto &coeff = a->coeff_; - auto &p = a->p_; -#pragma omp parallel for schedule(static) reduction(+ : nnz) - for (Index i = 0; i < dim; i++) { - if (i <= col[i]) { - nnz++; - } - } - Index *indptr = reinterpret_cast(malloc(sizeof(Index) * (dim + 1))); - Index *indices = reinterpret_cast(malloc(sizeof(Index) * nnz)); - CTP data = reinterpret_cast>(malloc(sizeof(CT) * nnz)); - indptr[0] = 0; - for (Index i = 0, j = 0; i < dim; i++) { - if (i <= col[i]) { - indptr[i + 1] = indptr[i] + 1; - if (i == col[i]) { - data[j] = p * POLAR[coeff[i]] * (T) (0.5); - } else { - data[j] = p * POLAR[coeff[i]]; - } - indices[j] = col[i]; - j++; - } else { - indptr[i + 1] = indptr[i]; - } - } - auto c = std::make_shared>(dim, nnz, indptr, indices, data); - return c; -} - -template -std::shared_ptr> GetPauliMat(const PauliTerm &pt, Index n_qubits) { - auto c = std::make_shared>(pt, n_qubits); - return c; -} - -template -std::shared_ptr> Csr_Plus_Csr(std::shared_ptr> a, std::shared_ptr> b) { - auto a_nnz = a->nnz_; - auto b_nnz = b->nnz_; - auto dim = a->dim_; - auto maxnnz = a_nnz + b_nnz; - CTP data = reinterpret_cast>(malloc(sizeof(CT) * maxnnz)); - Index *indices = reinterpret_cast(malloc(sizeof(Index) * maxnnz)); - Index *indptr = reinterpret_cast(malloc(sizeof(Index) * (dim + 1))); - csr_plus_csr(dim, a->indptr_, a->indices_, a->data_, b->indptr_, b->indices_, b->data_, indptr, indices, data); - auto nnz = indptr[dim]; - - auto c = std::make_shared>(dim, nnz, indptr, indices, data); - return c; -} - -template -std::shared_ptr> SparseHamiltonian(const VT> &hams, Index n_qubits) { - VT>> sp_hams(hams.size()); - -#pragma omp parallel for schedule(static) - for (Index i = 0; i < static_cast(hams.size()); i++) { - auto pm = GetPauliMat(hams[i], n_qubits); - sp_hams[i] = PauliMatToCsrHdMatrix(pm); - pm->Reset(); - } - Index tot = static_cast(hams.size()); - while (tot > 1) { - Index half = tot / 2 + tot % 2; -#pragma omp parallel for schedule(static) num_threads(half) - for (Index i = half; i < tot; i++) { - sp_hams[i - half] = Csr_Plus_Csr(sp_hams[i - half], sp_hams[i]); - sp_hams[i]->Reset(); - } - tot = half; - } - return sp_hams[0]; -} - -template -T2 *Csr_Dot_Vec(std::shared_ptr> a, T2 *vec) { - auto dim = a->dim_; - auto c_vec = reinterpret_cast>(vec); - auto new_vec = reinterpret_cast>(malloc(sizeof(CT) * dim)); - // auto nnz = a->nnz_; - auto data = a->data_; - auto indptr = a->indptr_; - auto indices = a->indices_; -#pragma omp parallel for schedule(static) - for (Index i = 0; i < dim; i++) { - CT sum = {0.0, 0.0}; - for (Index j = indptr[i]; j < indptr[i + 1]; j++) { - sum += data[j] * c_vec[indices[j]]; - } - new_vec[i] = sum; - } - free(vec); - return reinterpret_cast(new_vec); -} - -template -T2 *Csr_Dot_Vec(std::shared_ptr> a, std::shared_ptr> b, T2 *vec) { - auto dim = a->dim_; - auto c_vec = reinterpret_cast>(vec); - auto new_vec = reinterpret_cast>(malloc(sizeof(CT) * dim)); - // auto nnz = a->nnz_; - auto data = a->data_; - auto indptr = a->indptr_; - auto indices = a->indices_; - auto data_b = b->data_; - auto indptr_b = b->indptr_; - auto indices_b = b->indices_; - -#pragma omp parallel for schedule(static) - for (Index i = 0; i < dim; i++) { - CT sum = {0.0, 0.0}; - for (Index j = indptr[i]; j < indptr[i + 1]; j++) { - sum += data[j] * c_vec[indices[j]]; - } - for (Index j = indptr_b[i]; j < indptr_b[i + 1]; j++) { - sum += data_b[j] * c_vec[indices_b[j]]; - } - new_vec[i] = sum; - } - free(vec); - return reinterpret_cast(new_vec); -} -} // namespace sparse -} // namespace mindquantum -#endif // MINDQUANTUM_SPARSE_ALGO_H_ diff --git a/mindquantum/src/sparse/csrhdmatrix.h b/mindquantum/src/sparse/csrhdmatrix.h deleted file mode 100644 index 59c994f72..000000000 --- a/mindquantum/src/sparse/csrhdmatrix.h +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_SPARSE_CSR_HD_MATRIX_H_ -#define MINDQUANTUM_SPARSE_CSR_HD_MATRIX_H_ -#include -#include -#include - -#include "core/utils.h" - -namespace mindquantum { -namespace sparse { -namespace py = pybind11; -template -struct CsrHdMatrix { - Index dim_; - Index nnz_; - Index *indptr_; - Index *indices_; - CTP data_; - - void FreeMemory() { - if (indptr_ != nullptr) { - free(indptr_); - } - if (indices_ != nullptr) { - free(indices_); - } - if (data_ != nullptr) { - free(data_); - } - indptr_ = nullptr; - indices_ = nullptr; - data_ = nullptr; - } - void Reset() { - FreeMemory(); - indptr_ = nullptr; - indices_ = nullptr; - data_ = nullptr; - } - ~CsrHdMatrix() { - FreeMemory(); - } - CsrHdMatrix() : dim_(0), nnz_(0), indptr_(nullptr), indices_(nullptr), data_(nullptr) { - } - CsrHdMatrix(Index dim, Index nnz, Index *indptr, Index *indices, CTP data) - : dim_(dim), nnz_(nnz), indptr_(indptr), indices_(indices), data_(data) { - } - CsrHdMatrix(Index dim, Index nnz, py::array_t indptr, py::array_t indices, py::array_t> data) - : dim_(dim), nnz_(nnz) { - indptr_ = reinterpret_cast(malloc(indptr.size() * sizeof(Index))); - indices_ = reinterpret_cast(malloc(indices.size() * sizeof(Index))); - data_ = (CTP) malloc(data.size() * sizeof(CT)); - Index *indptr_py = static_cast(indptr.request().ptr); - Index *indices_py = static_cast(indices.request().ptr); - CTP data_py = static_cast *>(data.request().ptr); - for (size_t i = 0; i < data.size(); i++) { - indices_[i] = indices_py[i]; - data_[i] = data_py[i]; - } - for (size_t i = 0; i < indptr.size(); i++) { - indptr_[i] = indptr_py[i]; - } - } - void PrintInfo() { - std::cout << "<--Csr Half Diag Matrix with Dimension: "; - std::cout << dim_ << " X " << dim_ << ", and nnz: " << nnz_ << std::endl; - std::cout << " Data:\n "; - for (Index i = 0; i < nnz_; ++i) { - std::cout << data_[i]; - if (i != nnz_ - 1) { - std::cout << ","; - } - } - - std::cout << "\n indptr:\n "; - for (Index i = 0; i < dim_ + 1; i++) { - std::cout << indptr_[i]; - if (i != dim_) { - std::cout << ","; - } - } - - std::cout << "\n indices:\n "; - for (Index i = 0; i < nnz_; i++) { - std::cout << indices_[i]; - if (i != nnz_ - 1) { - std::cout << ","; - } - } - - std::cout << "-->\n\n"; - } -}; -} // namespace sparse -} // namespace mindquantum -#endif // MINDQUANTUM_SPARSE_CSR_HD_MATRIX_H_ diff --git a/mindquantum/src/sparse/paulimat.h b/mindquantum/src/sparse/paulimat.h deleted file mode 100644 index 03c5ebea6..000000000 --- a/mindquantum/src/sparse/paulimat.h +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_SPARSE_PAULI_MAT_H_ -#define MINDQUANTUM_SPARSE_PAULI_MAT_H_ -#include "core/utils.h" -#include "pybind11/numpy.h" - -namespace mindquantum { -namespace sparse { -namespace py = pybind11; -template -struct PauliMat { - char *coeff_; - Index *col_; - Index n_qubits_; - Index dim_; - T p_; - - inline void FreeMemory() { - if (coeff_ != nullptr) { - free(coeff_); - } - if (col_ != nullptr) { - free(col_); - } - } - void Reset() { - FreeMemory(); - coeff_ = nullptr; - col_ = nullptr; - } - ~PauliMat() { - FreeMemory(); - } - PauliMat() : coeff_(nullptr), col_(nullptr), n_qubits_(0), dim_(0) { - } - PauliMat(const PauliTerm pt, Index n_qubits) : n_qubits_(n_qubits), p_(pt.second) { - dim_ = (1UL << n_qubits_); - coeff_ = reinterpret_cast(malloc(sizeof(char) * dim_)); - col_ = reinterpret_cast(malloc(sizeof(Index) * dim_)); - auto mask = GetPauliMask(pt.first); - auto mask_f = mask.mask_x | mask.mask_y; -#pragma omp parallel for schedule(static) - for (Index i = 0; i < dim_; i++) { - auto j = (i ^ mask_f); - col_[i] = j; - auto axis2power = CountOne(i & mask.mask_z); // -1 - auto axis3power = CountOne(i & mask.mask_y); // -1j - // (-1)^a2*(-1j)^a3*(1j)^a1=(1j)^2a2*(1j)^3a3*(1j)^a1=(1j)^(a1+2*a2+3*a3) - coeff_[j] = static_cast((mask.num_y + 2 * axis3power + 2 * axis2power) & 3); - } - } - void PrintInfo() { - std::cout << "<--Pauli Matrix with Dimension: "; - std::cout << dim_ << " X " << dim_ << std::endl; - std::cout << " Data:\n "; - for (Index i = 0; i < dim_; i++) { - std::cout << POLAR[coeff_[i]]; - if (i != dim_ - 1) { - std::cout << ","; - } - } - - std::cout << "\n Col:\n "; - for (Index i = 0; i < dim_; i++) { - std::cout << col_[i]; - if (i != dim_ - 1) { - std::cout << ","; - } - } - std::cout << "-->\n\n"; - } -}; -} // namespace sparse -} // namespace mindquantum -#endif // MINDQUANTUM_SPARSE_PAULI_MAT_H_ diff --git a/mindquantum/src/sparse/sparse_utils.h b/mindquantum/src/sparse/sparse_utils.h deleted file mode 100644 index 344f3d7b2..000000000 --- a/mindquantum/src/sparse/sparse_utils.h +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINDQUANTUM_SPARSE_SPARSE_UTILS_H_ -#define MINDQUANTUM_SPARSE_SPARSE_UTILS_H_ -#include -#include - -#include "core/utils.h" - -namespace mindquantum { -namespace sparse { -template -void csr_plus_csr(Index dim, const Index *a_indptr, const Index *aj, const T *ad, const Index *b_indptr, - const Index *bj, const T *bd, Index *cp, Index *cj, T *cd) { - cp[0] = 0; - Index nnz = 0; - for (Index i = 0; i < dim; i++) { - Index ap = a_indptr[i]; - Index bp = b_indptr[i]; - Index a_end = a_indptr[i + 1]; - Index b_end = b_indptr[i + 1]; - - while (ap < a_end && bp < b_end) { - Index a_j = aj[ap]; - Index b_j = bj[bp]; - - if (a_j == b_j) { - T result = ad[ap] + bd[bp]; - if (std::abs(result) > PRECISION) { - cj[nnz] = a_j; - cd[nnz] = result; - nnz++; - } - ap++; - bp++; - } else if (a_j < b_j) { - T result = ad[ap]; - if (std::abs(result) > PRECISION) { - cj[nnz] = a_j; - cd[nnz] = result; - nnz++; - } - ap++; - } else { - T result = bd[bp]; - if (std::abs(result) > PRECISION) { - cj[nnz] = b_j; - cd[nnz] = result; - nnz++; - } - bp++; - } - } - - while (ap < a_end) { - T result = ad[ap]; - if (std::abs(result) > PRECISION) { - cj[nnz] = aj[ap]; - cd[nnz] = result; - nnz++; - } - ap++; - } - - while (bp < b_end) { - T result = bd[bp]; - if (std::abs(result) > PRECISION) { - cj[nnz] = bj[bp]; - cd[nnz] = result; - nnz++; - } - bp++; - } - - cp[i + 1] = nnz; - } -} -} // namespace sparse -} // namespace mindquantum -#endif // MINDQUANTUM_SPARSE_SPARSE_UTILS_H_ diff --git a/mindquantum/src/utils.cc b/mindquantum/src/utils.cc deleted file mode 100644 index 0fe6e58ef..000000000 --- a/mindquantum/src/utils.cc +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2021 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "core/utils.h" - -namespace mindquantum { -const VT> POLAR = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; -TimePoint NOW() { - return std::chrono::steady_clock::now(); -} - -int TimeDuration(TimePoint start, TimePoint end) { - auto d = end - start; - return std::chrono::duration_cast(d).count(); -} - -Index GetControlMask(const VT &ctrls) { - Index ctrlmask = std::accumulate(ctrls.begin(), ctrls.end(), 0, [&](Index a, Index b) { return a | (1UL << b); }); - return ctrlmask; -} - -PauliMask GetPauliMask(const VT &pws) { - VT out = {0, 0, 0, 0, 0, 0}; - for (auto &pw : pws) { - for (Index i = 0; i < 3; i++) { - if (static_cast(pw.second - 'X') == i) { - out[i] += (1UL << pw.first); - out[3 + i] += 1; - } - } - } - PauliMask res = {out[0], out[1], out[2], out[3], out[4], out[5]}; - return res; -} -} // namespace mindquantum diff --git a/mindquantum/third_party/__init__.py b/mindquantum/third_party/__init__.py index ddc35f6bf..5e888335b 100644 --- a/mindquantum/third_party/__init__.py +++ b/mindquantum/third_party/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 The OpenFermion Developers # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,8 +12,3 @@ # See the License for the specific language governing permissions and # limitations under the License. # -"""Third-party modules for MindQuantum.""" - -# Allow extending this namespace. -from .unitary_cc import uccsd_singlet_get_packed_amplitudes -from .unitary_cc import uccsd_singlet_generator diff --git a/mindquantum/third_party/interaction_operator.py b/mindquantum/third_party/interaction_operator.py index 615df3ea6..4af3a7465 100644 --- a/mindquantum/third_party/interaction_operator.py +++ b/mindquantum/third_party/interaction_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 The OpenFermion Developers. # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +22,7 @@ # Note this module, we did not modify much of the OpenFermion file import itertools -from mindquantum.core.operators.polynomial_tensor import PolynomialTensor +from mindquantum.ops.polynomial_tensor import PolynomialTensor class InteractionOperator(PolynomialTensor): @@ -33,7 +32,7 @@ class InteractionOperator(PolynomialTensor): The Hamiltonian including one-body and two-body terms which conserve spin and parity. In this module, the stored coefficient could be represented the - molecular Hamiltonians through the FermionOperator class. + molecualr Hamiltonians througth the FermionOperator class. Note: The operators stored in this class has the form: diff --git a/mindquantum/third_party/unitary_cc.py b/mindquantum/third_party/unitary_cc.py index 958ce2bc5..972ef1d55 100644 --- a/mindquantum/third_party/unitary_cc.py +++ b/mindquantum/third_party/unitary_cc.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 The OpenFermion Developers. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,8 +15,9 @@ import itertools import numpy +from mindquantum.ops import FermionOperator from openfermion.utils.indexing import down_index, up_index -from mindquantum.core.parameterresolver import ParameterResolver as PR +from mindquantum.parameterresolver import ParameterResolver as PR def uccsd_singlet_get_packed_amplitudes(single_amplitudes, double_amplitudes, @@ -142,7 +142,6 @@ def uccsd_singlet_generator(n_qubits, n_electrons, anti_hermitian=True): s_0 [3^ 1] + d1_0 [3^ 1 2^ 0] """ - from mindquantum.core.operators import FermionOperator if n_qubits % 2 != 0: raise ValueError('The total number of spin-orbitals should be even.') diff --git a/mindquantum/utils/__init__.py b/mindquantum/utils/__init__.py index 8979da78f..61526bd06 100644 --- a/mindquantum/utils/__init__.py +++ b/mindquantum/utils/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +14,21 @@ # ============================================================================ """Utils""" +from .beauty_print import bprint from .f import mod from .f import normalize from .f import random_state from .f import ket_string -from .f import random_circuit +from .utils_operator import (number_operator, normal_ordered, count_qubits, + commutator, get_fermion_operator, + hermitian_conjugated, up_index, down_index, + sz_operator) + +__all__ = [ + 'bprint', 'mod', 'normalize', 'random_state', 'number_operator', + 'normal_ordered', 'commutator', 'up_index', 'down_index', 'sz_operator', + 'hermitian_conjugated', 'ket_string', 'get_fermion_operator', + 'count_qubits' +] -__all__ = ['mod', 'normalize', 'random_state', 'ket_string', 'random_circuit'] __all__.sort() diff --git a/mindquantum/io/beauty_print.py b/mindquantum/utils/beauty_print.py similarity index 98% rename from mindquantum/io/beauty_print.py rename to mindquantum/utils/beauty_print.py index 6a39dd4ae..0c90e6953 100644 --- a/mindquantum/io/beauty_print.py +++ b/mindquantum/utils/beauty_print.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -105,6 +104,3 @@ def bprint(strings: list, output.extend(strings) output.append(bot) return output - - -__all__ = ['bprint'] diff --git a/mindquantum/utils/f.py b/mindquantum/utils/f.py index 7a8a8b3ae..a6b563d90 100644 --- a/mindquantum/utils/f.py +++ b/mindquantum/utils/f.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,52 +14,9 @@ # ============================================================================ """Useful functions""" -import fractions import numpy as np -def random_circuit(n_qubits, gate_num, sd_rate=0.5, ctrl_rate=0.2, seed=42): - """Generate a random circuit""" - from mindquantum import Circuit - import mindquantum.core.gates as G - if n_qubits == 1: - sd_rate = 1 - ctrl_rate = 0 - single = { - 'param': [G.RX, G.RY, G.RZ, G.PhaseShift], - 'non_param': [G.X, G.Y, G.Z, G.H] - } - double = {'param': [G.XX, G.YY, G.ZZ], 'non_param': [G.SWAP]} - c = Circuit() - np.random.seed(seed) - qubits = range(n_qubits) - for _ in range(gate_num): - if n_qubits == 1: - q1, q2 = int(qubits[0]), None - else: - q1, q2 = np.random.choice(qubits, 2, replace=False) - q1, q2 = int(q1), int(q2) - if np.random.random() < sd_rate: - if np.random.random() > ctrl_rate: - q2 = None - if np.random.random() < 0.5: - gate = np.random.choice(single['param']) - p = np.random.uniform(-np.pi * 2, np.pi * 2) - c += gate(p).on(q1, q2) - else: - gate = np.random.choice(single['non_param']) - c += gate.on(q1, q2) - else: - if np.random.random() < 0.75: - gate = np.random.choice(double['param']) - p = np.random.uniform(-np.pi * 2, np.pi * 2) - c += gate(p).on([q1, q2]) - else: - gate = np.random.choice(double['non_param']) - c += gate.on([q1, q2]) - return c - - def _check_num_array(vec, name): if not isinstance(vec, (np.ndarray, list)): raise TypeError( @@ -164,35 +120,24 @@ def _index_to_bitstring(index, n, big_end=False): return s -def _common_exp(num, round_n=None): +def _common_exp(num, tol=1e-7): """common expressions.""" if num == 0: - return '0' - com = { - '': 1, - 'π': np.pi, - '√2': np.sqrt(2), - '√3': np.sqrt(3), - '√5': np.sqrt(5) - } - for k, v in com.items(): - left = str(fractions.Fraction(str(round(num / v, 9)))) - if len(left) < 5 or '/' not in left or left.startswith( - '1/') or left.startswith('-1/'): - tmp = left.split('/') - if not (len(tmp) == 2 and int(tmp[1]) > 5 and int(tmp[0]) > 5): - if tmp[0] == '1': - tmp[0] = k - if k == '': - tmp[0] = '1' - elif tmp[0] == '-1': - tmp[0] = f"-{k}" - if k == '': - tmp[0] = '-1' - else: - tmp[0] = f"{tmp[0]}{k}" - return '/'.join(tmp) - return str(num) if round_n is None else str(round(num, round_n)) + return num + s2 = np.sqrt(2) + s3 = np.sqrt(3) + s5 = np.sqrt(5) + com = {2: s2, 3: s3, 5: s5} + for i, j in com.items(): + tmp_num = (j / num) + ceil = np.ceil(tmp_num) + floor = np.floor(tmp_num) + if np.abs(tmp_num - ceil) < tol or np.abs(tmp_num - floor) < tol: + frac = int(1 / (num / j)) + if frac > 0: + return f'√{i}/{frac}' + return f'-√{i}/{-frac}' + return num def ket_string(state, tol=1e-7): @@ -214,8 +159,6 @@ def ket_string(state, tol=1e-7): √2/2¦0⟩ -√2/2j¦1⟩ """ - if not isinstance(state, np.ndarray) or len(state.shape) != 1: - raise TypeError(f"state need a 1-D ndarray.") n = int(np.log2(len(state))) if len(state) < 2 and len(state) != (1 << n): raise ValueError("Invalid state size!") @@ -225,15 +168,10 @@ def ket_string(state, tol=1e-7): if np.abs(i) < tol: continue if np.abs(np.real(i)) < tol: - s.append(f'{_common_exp(np.imag(i))}j¦{b}⟩') + s.append(f'{_common_exp(np.imag(i), tol)}j¦{b}⟩') continue if np.abs(np.imag(i)) < tol: - s.append(f'{_common_exp(np.real(i))}¦{b}⟩') + s.append(f'{_common_exp(np.real(i), tol)}¦{b}⟩') continue - i_real = _common_exp(np.real(i)) - i_imag = _common_exp(np.imag(i)) - if i_imag.startswith('-'): - s.append(f'({i_real}{i_imag}j)¦{b}⟩') - else: - s.append(f'({i_real}+{i_imag}j)¦{b}⟩') + s.append(f'{i}¦{b}⟩') return s diff --git a/mindquantum/core/operators/utils.py b/mindquantum/utils/utils_operator.py similarity index 93% rename from mindquantum/core/operators/utils.py rename to mindquantum/utils/utils_operator.py index 0f63ca5c6..3c16474bb 100644 --- a/mindquantum/core/operators/utils.py +++ b/mindquantum/utils/utils_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Portions Copyright (c) 2020 Huawei Technologies Co.,ltd. # Portions Copyright 2017 The OpenFermion Developers. # @@ -17,10 +16,10 @@ import projectq.ops as pjops import openfermion.ops as ofops -from mindquantum.core.operators.fermion_operator import FermionOperator -from mindquantum.core.operators.qubit_operator import QubitOperator -from mindquantum.core.operators.qubit_excitation_operator import QubitExcitationOperator -from mindquantum.core.operators.polynomial_tensor import PolynomialTensor +from mindquantum.ops.fermion_operator import FermionOperator +from mindquantum.ops.qubit_operator import QubitOperator +from mindquantum.ops.qubit_excitation_operator import QubitExcitationOperator +from mindquantum.ops.polynomial_tensor import PolynomialTensor def count_qubits(operator): @@ -42,8 +41,8 @@ def count_qubits(operator): TypeError: Operator of invalid type. Examples: - >>> from mindquantum.core.operators import QubitOperator,FermionOperator - >>> from mindquantum.core.operators.utils import count_qubits + >>> from mindquantum.ops import QubitOperator,FermionOperator + >>> from mindquantum.utils import count_qubits >>> qubit_op = QubitOperator("X1 Y2") >>> count_qubits(qubit_op) 3 @@ -60,7 +59,7 @@ def count_qubits(operator): for term in operator.terms: # a tuple compose of single (qubit_index,operator) subterms if term == (): - qubit_index = (0, ) + qubit_index = (0,) else: qubit_index, _ = zip(*term) num_qubits = max(max(qubit_index) + 1, @@ -87,7 +86,7 @@ def commutator(left_operator, right_operator): TypeError: operator_a and operator_b are not of the same type. Examples: - >>> from mindquantum.core.operators import QubitOperator,FermionOperator + >>> from mindquantum.ops import QubitOperator,FermionOperator >>> from mindquantum.utils import commutator >>> qub_op1 = QubitOperator("X1 Y2") >>> qub_op2 = QubitOperator("X1 Z2") @@ -163,7 +162,7 @@ def normal_ordered(fermion_operator): FermionOperator, the normal_ordered FermionOperator. Examples: - >>> from mindquantum.core.operators import FermionOperator + >>> from mindquantum.ops import FermionOperator >>> from mindquantum.utils import normal_ordered >>> op = FermionOperator("3 4^", 'a') >>> normal_ordered(op) @@ -207,7 +206,7 @@ def number_operator(n_modes=None, mode=None, coefficient=1.): FermionOperator, a fermionic number operator for the reverse_jordan_wigner transform. Examples: - >>> from mindquantum.core.operators import FermionOperator + >>> from mindquantum.ops import FermionOperator >>> from mindquantum.utils import number_operator >>> nmode = 3 >>> number_operator(nmode) @@ -241,7 +240,7 @@ def hermitian_conjugated(operator): the hermitian form of the input operator. Examples: - >>> from mindquantum.core.operators import QubitOperator + >>> from mindquantum.ops import QubitOperator >>> from mindquantum.utils import hermitian_conjugated >>> q = QubitOperator('X0', {'a' : 2j}) >>> hermitian_conjugated(q) diff --git a/mindquantum/version.py b/mindquantum/version.py new file mode 100644 index 000000000..c503470a8 --- /dev/null +++ b/mindquantum/version.py @@ -0,0 +1,16 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Version""" +__version__ = '0.3.1' diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 3b0b3e9f3..000000000 --- a/pyproject.toml +++ /dev/null @@ -1,98 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel", "pybind11>=2.6.0", "setuptools_scm[toml]>=3.4"] -build-backend = "setuptools.build_meta" - -# ============================================================================== - -[tool.black] - line-length = 120 - target-version = ['py36','py37','py38'] - skip-string-normalization = true - - -[tool.check-manifest] - ignore = [ - 'PKG-INFO', - '*.egg-info', - '*.egg-info/*', - 'setup.cfg', - '.hgtags', - '.hgsigs', - '.hgignore', - '.gitignore', - '.bzrignore', - '.gitattributes', - '.github/*', - '.travis.yml', - '*.mo', - '.clang-tidy', - '.clang-format', - '.gitmodules', - 'requirements.txt', - 'requirements_tests.txt', - 'VERSION.txt', - '.editorconfig', - '*.yml', - '*.yaml', - 'docs/*', - 'docs/images/*', - 'tests/*', - ] - - - -[tool.coverage] - [tool.coverage.run] - omit = [ - '*_test.py', - '*_fixtures.py' - ] - - -[tool.pylint] - [tool.pylint.master] - ignore-patterns = [ - '.*_test.py', - '.*_fixtures.py', - ] - - extension-pkg-whitelist = [ - ] - extension-pkg-allow-list = [ - ] - - [tool.pylint.basic] - good-names = [] - - [tool.pylint.format] - max-line-length = 120 - - [tool.pylint.messages_control] - disable = [ - 'no-name-in-module', # due to dynamic importing of symbols - 'fixme' - ] - - -# [tool.pytest.ini_options] - -# minversion = '6.0' -# addopts = '-pno:warnings' -# testpaths = ['mindquantum'] -# norecursedirs = 'third_party' -# mock_use_standalone_module = true - -[tool.isort] - -profile = "black" - - -[tool.setuptools_scm] -write_to = 'VERSION.txt' -write_to_template = '{version}' -local_scheme = 'no-local-version' -fallback_version = '0.3.0' - - -[tool.yapf] -column_limit = 120 diff --git a/requirements.txt b/requirements.txt index 1425f1be7..47f315a21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ -numpy>=1.17.0 -scipy>=1.5.3 -projectq>=0.5.1 +numpy >= 1.17.0 +scipy >= 1.5.3 +projectq >= 0.5.1 openfermion>=1.0.0 -sympy>=1.4 -matplotlib>=3.1.3 -rich>=10.9.0 - +sympy >= 1.4 +matplotlib >= 3.1.3 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3999f2925..000000000 --- a/setup.cfg +++ /dev/null @@ -1,81 +0,0 @@ -[metadata] - -name = mindquantum -version = file:VERSION.txt - -author = The MindSpore Authors -author_email = contact@mindspore.cn - -description = A hybrid quantum-classic framework for quantum computing -long_description = file:README.md -long_description_content_type = text/markdown; charset=UTF-8 - -license = Apache License Version 2.0 -license_file = LICENSE - -requires_dist = setuptools - -url = https://www.mindspore.cn/ -home_page = https://www.mindspore.cn/ -download_url = https://gitee.com/mindspore/mindquantum/tags -project_urls = - Download = https://gitee.com/mindspore/mindquantum/tags - Source = https://gitee.com/mindspore/mindquantum - Issue-Tracker = https://gitee.com/mindspore/mindquantum/issues - -classifier = - License :: OSI Approved :: Apache Software License - Topic :: Software Development :: Libraries :: Python Modules - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - - -[options] - -zip_safe = False -include_package_data = True -packages = find: -python_requires = >= 3 - -setup_requires = - setuptools >= 42 - setuptools_scm[toml] - pybind11 >= 2.6.0 - -install_requires = - numpy >= 1.17.0 - scipy >= 1.5.3 - projectq >= 0.5.1 - openfermion>=1.0.0 - sympy >= 1.4 - matplotlib >= 3.1.3 - rich >= 10.9.0 - -[options.extras_require] - -# test = -# pytest - -[options.package_data] - -* = *.so, *.so*, *.pyd - - -# ============================================================================== - -[flake8] - -max-line-length = 120 -exclude = - .git - __pycache__ - build - dist -docstring-quotes = """ -eradicate-whitelist = # yapf: disable# yapf: enable - -# ============================================================================== diff --git a/setup.py b/setup.py index 29c2b787f..a7c73e12d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,498 +12,85 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +"""Setup.""" -"""Setup.py file.""" - -import contextlib -import copy -import distutils.log -import errno -import hashlib -import itertools -import multiprocessing import os -import platform -import shutil import stat -import subprocess -import sys -from distutils.cmd import Command -from distutils.command.clean import clean -from distutils.file_util import copy_file - -import setuptools -from setuptools.command.build_ext import build_ext +from setuptools import setup +from setuptools import find_packages +from setuptools.command.egg_info import egg_info +from setuptools.command.build_py import build_py -# ============================================================================== -# Helper variables - -on_rtd = os.environ.get('READTHEDOCS') == 'True' cur_dir = os.path.dirname(os.path.realpath(__file__)) -ext_errors = (subprocess.CalledProcessError, FileNotFoundError) -cmake_extra_options = [] +pkg_dir = os.path.join(cur_dir, 'build') -# ============================================================================== -# Helper functions and classes +def read_version(): + """generate python file""" + version_file = os.path.join(cur_dir, 'mindquantum/', 'version.py') + with open(version_file, 'r') as f: + version_ = f.readlines()[-1].strip().split()[-1][1:-1] + return version_ -@contextlib.contextmanager -def fdopen(fname, mode, perms=0o644): # pragma: no cover - """ - Context manager for opening files with correct permissions. +version = read_version() - Args: - fname (str): Path to file to open for reading/writing - mode (str): Mode in which the file is opened (see help for builtin `open()`) - perms (int): Permission mask (see help for `os.open()`) +def update_permissions(path): """ - if 'r' in mode: - flags = os.O_RDONLY - elif 'w' in mode: - flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC - elif 'a' in mode: - flags = os.O_WRONLY | os.O_CREAT - else: - raise RuntimeError(f'Unsupported mode: {mode}') - - file_object = open(os.open(fname, flags, perms), mode=mode, encoding='utf-8') - - try: - yield file_object - finally: - file_object.close() - - -def remove_tree(directory): - """Remove a directory and its subdirectories.""" - - def remove_read_only(func, path, exc_info): - excvalue = exc_info[1] - if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES: - os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) - func(path) - else: - raise exc_info[0].with_traceback(exc_info[1], exc_info[2]) - - if os.path.exists(directory): - distutils.log.info(f'Removing {directory} (and everything under it)') - shutil.rmtree(directory, ignore_errors=False, onerror=remove_read_only) - - -def write_checksum(): - """Rename Python wheels on Windows.""" - if os.path.exists(os.path.join(cur_dir, 'output')): - whl = os.listdir(os.path.join(cur_dir, 'output')) - if whl: - whl_name = os.path.join(cur_dir, 'output', whl[0]) - with open(whl_name, 'rb') as f: - sha256obj = hashlib.sha256() - sha256obj.update(f.read()) - hash_value = sha256obj.hexdigest() - with open(whl_name + '.sha256', 'w') as f: - f.writelines(f'{hash_value} *{whl[0]}') + Update permissions. - -def important_msgs(*msgs): - """Print an important message.""" - print('*' * 75) - for msg in msgs: - print(msg) - print('*' * 75) - - -def get_extra_cmake_options(): - """ - Parse CMake options from python3 setup.py command line. - - Read --unset, --set, -A and -G options from the command line and add them as cmake switches. + Args: + path (str): Target directory path. """ - _cmake_extra_options = [] - - opt_key = None - - has_generator = False - - argv = copy.deepcopy(sys.argv) - # parse command line options and consume those we care about - for arg in argv: - if opt_key == 'G': - has_generator = True - _cmake_extra_options += ['-G', arg.strip()] - elif opt_key == 'A': - _cmake_extra_options += ['-A', arg.strip()] - elif opt_key == 'unset': - _cmake_extra_options.append(f'-D{arg.strip()}:BOOL=OFF') - elif opt_key == 'set': - _cmake_extra_options.append(f'-D{arg.strip()}:BOOL=ON') - - if opt_key: - sys.argv.remove(arg) - opt_key = None - continue - - if arg in ['--unset', '--set', '--compiler-flags']: - opt_key = arg[2:].lower() - sys.argv.remove(arg) - continue - if arg in ['-A']: - opt_key = arg[1:] - sys.argv.remove(arg) - continue - if arg in ['-G']: - opt_key = arg[1:] - sys.argv.remove(arg) - continue - - # If no explicit CMake Generator specification, prefer Ninja on Windows - if (not has_generator) and (platform.system() == "Windows") and shutil.which("ninja"): - _cmake_extra_options += ['-G', "Ninja"] - - return _cmake_extra_options - - -# ============================================================================== - - -def get_python_executable(): - """Retrieve the path to the Python executable.""" - try: - root_path = os.environ['VIRTUAL_ENV'] - python = os.path.basename(sys.executable) - python_path = os.path.join(root_path, python) - if os.path.exists(python_path): - return python_path - return os.path.join(root_path, 'bin', python) - except KeyError: - return sys.executable - - -def get_cmake_command(): - """Retrieve the path to the CMake executable.""" - with fdopen(os.devnull, 'w') as devnull: - try: - subprocess.check_call(['cmake', '--version'], stdout=devnull, stderr=devnull) - return ['cmake'] - except (OSError, subprocess.CalledProcessError): - pass - - # CMake not in PATH, should have installed Python CMake module - # -> try to find out where it is - try: - root_path = os.environ['VIRTUAL_ENV'] - python = os.path.basename(sys.executable) - except KeyError: - root_path, python = os.path.split(sys.executable) - - search_paths = [root_path, os.path.join(root_path, 'bin'), os.path.join(root_path, 'Scripts')] - - # First try executing CMake directly - for base_path in search_paths: - try: - cmake_cmd = os.path.join(base_path, 'cmake') - subprocess.check_call([cmake_cmd, '--version'], stdout=devnull, stderr=devnull) - return [cmake_cmd] - except (OSError, subprocess.CalledProcessError): - pass - - # That did not work: try calling it through Python - for base_path in search_paths: - try: - cmake_cmd = [python, os.path.join(base_path, 'cmake')] - subprocess.check_call(cmake_cmd + ['--version'], stdout=devnull, stderr=devnull) - return cmake_cmd - except (OSError, subprocess.CalledProcessError): - pass - - # Nothing worked -> give up! - return None - - -# ============================================================================== - - -class BuildFailed(Exception): - """Extension raised if the build fails for any reason.""" - - def __init__(self): - """Initialize a BuildFailed exception.""" - super().__init__() - self.cause = sys.exc_info()[1] # work around py 2/3 different syntax - - -# ============================================================================== - - -class CMakeExtension(setuptools.Extension): # pylint: disable=too-few-public-methods - """Class defining a C/C++ Python extension to be compiled using CMake.""" - - def __init__(self, pymod, target=None, optional=False): - """ - Initialize a CMakeExtension object. - - Args: - src_dir (string): Path to source directory - target (string): Name of target - pymod (string): Name of compiled Python module - optional (bool): (optional) If true, not building this extension is not considered an error - """ - # NB: the main source directory is the one containing the setup.py file - self.src_dir = os.path.realpath('') - self.pymod = pymod - self.target = target if target is not None else pymod.split('.')[-1] - - self.lib_filepath = os.path.join(*pymod.split('.')) - super().__init__(pymod, sources=[], optional=optional) - - -# ------------------------------------------------------------------------------ - - -class CMakeBuildExt(build_ext): - """Custom build_ext command class.""" - - user_options = build_ext.user_options + [ - ('no-arch-native', None, 'Do not use the -march=native flag when compiling'), - ('clean-build', None, 'Build in a clean build environment'), - ] - - boolean_options = build_ext.boolean_options + ['no-arch-native', 'clean-build'] - - def initialize_options(self): - """Initialize all options of this custom command.""" - build_ext.initialize_options(self) - self.no_arch_native = None - self.clean_build = None - - def build_extensions(self): - """Build a C/C++ extension using CMake.""" - # pylint: disable=attribute-defined-outside-init - if on_rtd: - important_msgs('skipping CMake build on ReadTheDocs') - return - self.cmake_cmd = get_cmake_command() - if self.cmake_cmd is None: - raise RuntimeError('Unable to locate the CMake command!') - distutils.log.info('using cmake command: ' + ' '.join(self.cmake_cmd)) - - self.configure_extensions() - build_ext.build_extensions(self) - - def configure_extensions(self): - """Run a CMake configuration and generation step for one extension.""" - # pylint: disable=attribute-defined-outside-init - - def _src_dir_pred(ext): - return ext.src_dir - - cmake_args = [ - '-DPython_EXECUTABLE:FILEPATH=' + get_python_executable(), - '-DBUILD_TESTING:BOOL=OFF', - '-DIN_PLACE_BUILD:BOOL=OFF', - '-DIS_PYTHON_BUILD:BOOL=ON', - '-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON', - '-DVERSION_INFO="{self.distribution.get_version()}"', - ] # yapf: disable - - if self.no_arch_native: - cmake_args += ['-DUSE_NATIVE_INTRINSICS=OFF'] - - cfg = 'Debug' if self.debug else 'Release' - self.build_args = ['--config', cfg] - - if platform.system() == "Windows": - # self.build_args += ['--', '/m'] - pass - else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - if platform.system() == "Darwin" and 'TRAVIS' in os.environ: - self.build_args += ['--'] - else: - self.build_args += [ - f'-j {self.parallel if self.parallel else multiprocessing.cpu_count()}', - '--', - ] - - cmake_args.extend(cmake_extra_options) - - env = os.environ.copy() - - # This can in principle handle the compilation of extensions outside the main CMake directory (ie. outside the - # one containing this setup.py file) - for src_dir, extensions in itertools.groupby(sorted(self.extensions, key=_src_dir_pred), key=_src_dir_pred): - self.cmake_configure_build(src_dir, extensions, cmake_args, env) - - def cmake_configure_build(self, src_dir, extensions, cmake_args, env): - """Run a CMake build command for a list of extensions.""" - args = cmake_args.copy() - for ext in extensions: - dest_path = os.path.realpath(os.path.dirname(self.get_ext_fullpath(ext.lib_filepath))) - args.append(f'-D{ext.target.upper()}_OUTPUT_DIR={dest_path}') - - build_temp = self._get_temp_dir(src_dir) - if self.clean_build: - remove_tree(build_temp) - if not os.path.exists(build_temp): - os.makedirs(build_temp) - - distutils.log.info(f' Configuring from {src_dir} '.center(80, '-')) - distutils.log.info(f'CMake command: {" ".join(self.cmake_cmd + [src_dir] + args)}') - distutils.log.info(f' cwd: {build_temp}') - try: - subprocess.check_call(self.cmake_cmd + [src_dir] + args, cwd=build_temp, env=env) - except ext_errors as err: - raise BuildFailed() from err - finally: - distutils.log.info(f' End configuring from {src_dir} '.center(80, '-')) - - def build_extension(self, ext): - """Build a single C/C++ extension using CMake.""" - distutils.log.info(f' Building {ext.pymod} '.center(80, '-')) - distutils.log.info( - 'CMake command: {" ".join(self.cmake_cmd + ["--build", ".", "--target", ext.target] + self.build_args)}' - ) - distutils.log.info(f' cwd: {self._get_temp_dir(ext.src_dir)}') - try: - subprocess.check_call( - self.cmake_cmd + ['--build', '.', '--target', ext.target] + self.build_args, - cwd=self._get_temp_dir(ext.src_dir), - ) - except ext_errors as err: - if not ext.optional: - raise BuildFailed() from err - distutils.log.info(f'Failed to compile optional extension {ext.target} (not an error)') - finally: - distutils.log.info(f' End building {ext.pymod} '.center(80, '-')) - - def copy_extensions_to_source(self): - """Copy the extensions.""" - # pylint: disable=protected-access - - build_py = self.get_finalized_command('build_py') - for ext in self.extensions: - fullname = self.get_ext_fullname(ext.name) - filename = self.get_ext_filename(fullname) - modpath = fullname.split('.') - package = '.'.join(modpath[:-1]) - package_dir = build_py.get_package_dir(package) - dest_filename = os.path.join(package_dir, os.path.basename(filename)) - src_filename = os.path.join(self.build_lib, filename) - - # Always copy, even if source is older than destination, to ensure - # that the right extensions for the current Python/platform are - # used. - if os.path.exists(src_filename) or not ext.optional: - copy_file(src_filename, dest_filename, verbose=self.verbose, dry_run=self.dry_run) - if ext._needs_stub: - self.write_stub(package_dir or os.curdir, ext, True) - - def get_outputs(self): - """ - Get the list of files generated during a build. - - Mainly defined to properly handle optional extensions. - """ - self.check_extensions_list(self.extensions) - outputs = [] - for ext in self.extensions: - if os.path.exists(self.get_ext_fullpath(ext.name)) or not ext.optional: - outputs.append(self.get_ext_fullpath(ext.name)) - return outputs - - def _get_temp_dir(self, src_dir): - return os.path.join(self.build_temp, os.path.basename(src_dir)) - - -# ============================================================================== - - -class Clean(clean): - """Custom clean command.""" - + for dirpath, dirnames, filenames in os.walk(path): + for dirname in dirnames: + dir_fullpath = os.path.join(dirpath, dirname) + os.chmod( + dir_fullpath, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC + | stat.S_IRGRP | stat.S_IXGRP) + for filename in filenames: + file_fullpath = os.path.join(dirpath, filename) + os.chmod(file_fullpath, stat.S_IREAD) + + +class EggInfo(egg_info): + """Egg info.""" def run(self): - """Run the clean command.""" - # Execute the classic clean command - clean.run(self) - import glob # pylint: disable=import-outside-toplevel - - pkg_name = self.distribution.get_name().replace('-', '_') - info = glob.glob(f'{pkg_name}.egg-info') - if info: - remove_tree(info[0]) - - -# ============================================================================== - - -class GenerateRequirementFile(Command): - """A custom command to list the dependencies of the current.""" + super().run() + egg_info_dir = os.path.join(cur_dir, 'mindquantum.egg-info') + update_permissions(egg_info_dir) - description = 'List the dependencies of the current package' - user_options = [ - ('include-all-extras', None, 'Include all "extras_require" into the list'), - ('include-extras=', None, 'Include some of extras_requires into the list (comma separated)'), - ] - - boolean_options = ['include-all-extras'] - - def initialize_options(self): - """Initialize this command's options.""" - self.include_extras = None - self.include_all_extras = None - self.extra_pkgs = [] - - def finalize_options(self): - """Finalize this command's options.""" - if self.include_extras: - include_extras = self.include_extras.split(',') - else: - include_extras = [] - - try: - for name, pkgs in self.distribution.extras_require.items(): - if self.include_all_extras or name in include_extras: - self.extra_pkgs.extend(pkgs) - - except TypeError: # Mostly for old setuptools (< 30.x) - for name, pkgs in self.distribution.command_options['options.extras_require'].items(): - if self.include_all_extras or name in include_extras: - self.extra_pkgs.extend(pkgs) +class BuildPy(build_py): + """BuildPy.""" def run(self): - """Execute this command.""" - with fdopen('requirements.txt', 'w') as req_file: - try: - for pkg in self.distribution.install_requires: - req_file.write(f'{pkg}\n') - except TypeError: # Mostly for old setuptools (< 30.x) - for pkg in self.distribution.command_options['options']['install_requires']: - req_file.write(f'{pkg}\n') - req_file.write('\n') - for pkg in self.extra_pkgs: - req_file.write(f'{pkg}\n') - - -# ============================================================================== - - -ext_modules = [ - CMakeExtension(pymod='mindquantum.libQuEST', target='QuEST', optional=True), - CMakeExtension(pymod='mindquantum.mqbackend'), -] - - -if __name__ == '__main__': - remove_tree(os.path.join(cur_dir, 'output')) - cmake_extra_options.extend(get_extra_cmake_options()) - setuptools.setup( - use_scm_version={'local_scheme': 'no-local-version'}, - setup_requires=['setuptools_scm'], - cmdclass={ - 'build_ext': CMakeBuildExt, - 'clean': Clean, - 'gen_reqfile': GenerateRequirementFile, - }, - ext_modules=ext_modules, - ) - - write_checksum() + super().run() + mindquantum_dir = os.path.join(pkg_dir, 'lib', 'mindquantum') + update_permissions(mindquantum_dir) + + +with open('requirements.txt', 'r') as f_requirements: + requirements = f_requirements.readlines() +requirements = [r.strip() for r in requirements] + +setup(name='mindquantum', + version=version, + author='The MindSpore Authors', + author_email='contact@mindspore.cn', + url='https://www.mindspore.cn/', + download_url='https://gitee.com/mindspore/mindquantum/tags', + project_urls={ + 'Sources': 'https://gitee.com/mindspore/mindquantum', + 'Issue Tracker': 'https://gitee.com/mindspore/mindquantum/issues', + }, + description= + "A hybrid quantum-classic framework for quantum machine learning", + license='Apache 2.0', + packages=find_packages(), + include_package_data=True, + cmdclass={ + 'egg_info': EggInfo, + 'build_py': BuildPy, + }, + install_requires=requirements, + classifiers=['License :: OSI Approved :: Apache Software License']) +print(find_packages()) diff --git a/tests/cmake-ldtest/.gitignore b/tests/cmake-ldtest/.gitignore deleted file mode 100644 index a0f00082c..000000000 --- a/tests/cmake-ldtest/.gitignore +++ /dev/null @@ -1 +0,0 @@ -CMakeLists.txt diff --git a/tests/cmake-ldtest/CMakeLists.txt.in b/tests/cmake-ldtest/CMakeLists.txt.in deleted file mode 100644 index eda010958..000000000 --- a/tests/cmake-ldtest/CMakeLists.txt.in +++ /dev/null @@ -1,46 +0,0 @@ -# ============================================================================== -# -# Copyright 2021 -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. -# -# ============================================================================== - -# lint_cmake: -whitespace/indent - -cmake_minimum_required(VERSION 3.14) -set(CMAKE_MODULE_PATH @CMAKE_MODULE_PATH@) - -@CMAKE_EXTRA_CONTENT@ - -project(cmake-ldtest @LANGS@) - -set(CMAKE_SUPPRESS_REGENERATION 1) -set(CMAKE_VERBOSE_MAKEFILE @CMAKE_VERBOSE_MAKEFILE@) - -foreach(_flag ${LINKER_FLAGS}) - add_link_options("LINKER:${_flag}") -endforeach() - -set(CMAKE_BUILD_SKIP_RPATH @CMAKE_BUILD_SKIP_RPATH@) -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH @CMAKE_INSTALL_RPATH_USE_LINK_PATH@) -set(CMAKE_BUILD_WITH_INSTALL_RPATH @CMAKE_BUILD_WITH_INSTALL_RPATH@) - -add_library(shared_lib_@LANG@ SHARED ${CMAKE_CURRENT_LIST_DIR}/shared_lib.cpp) -set_target_properties(shared_lib_@LANG@ PROPERTIES INSTALL_RPATH "$ORIGIN/.") -set_source_files_properties(${CMAKE_CURRENT_LIST_DIR}/shared_lib.cpp PROPERTIES LANGUAGE @LANG@) - -# ~~~ -# add_executable(shared_test ${CMAKE_CURRENT_LIST_DIR}/shared_test.cpp) -# target_link_libraries(shared_test PUBLIC shared_lib@LANG@) -# ~~~ diff --git a/tests/cmake-ldtest/shared_lib.cpp b/tests/cmake-ldtest/shared_lib.cpp deleted file mode 100644 index 31c806614..000000000 --- a/tests/cmake-ldtest/shared_lib.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2021 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "shared_lib.hpp" - -int foo() { - return 42; // NOLINT -} diff --git a/tests/cmake-ldtest/shared_lib.hpp b/tests/cmake-ldtest/shared_lib.hpp deleted file mode 100644 index e5edc79ca..000000000 --- a/tests/cmake-ldtest/shared_lib.hpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2021 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef SHARED_LIB_HPP -#define SHARED_LIB_HPP - -int foo(); - -#endif /* SHARED_LIB_HPP */ diff --git a/tests/cmake-ldtest/shared_test.cpp b/tests/cmake-ldtest/shared_test.cpp deleted file mode 100644 index ed9f02a1f..000000000 --- a/tests/cmake-ldtest/shared_test.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2021 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "shared_lib.hpp" - -int main() { - std::cout << foo() << std::endl; - return 0; -} diff --git a/tests/st/__init__.py b/tests/st/__init__.py index ed94b4aa0..6228b7132 100644 --- a/tests/st/__init__.py +++ b/tests/st/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/st/runtest.sh b/tests/st/runtest.sh index c735e8285..916f7a260 100644 --- a/tests/st/runtest.sh +++ b/tests/st/runtest.sh @@ -27,4 +27,4 @@ run_test() { echo "Test all use cases success." } -run_test +run_test \ No newline at end of file diff --git a/mindquantum/algorithm/nisq/qaoa/__init__.py b/tests/st/test_ansatz/__init__.py similarity index 75% rename from mindquantum/algorithm/nisq/qaoa/__init__.py rename to tests/st/test_ansatz/__init__.py index fc510396d..6228b7132 100644 --- a/mindquantum/algorithm/nisq/qaoa/__init__.py +++ b/tests/st/test_ansatz/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,8 +12,3 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""Algorithm for quantum approximation optimization algorithm""" -from .max_2_sat_ansatz import Max2SATAnsatz -from .max_cut_ansatz import MaxCutAnsatz - -__all__ = ['Max2SATAnsatz', 'MaxCutAnsatz'] diff --git a/tests/st/test_ansatz/test_hardware_efficient.py b/tests/st/test_ansatz/test_hardware_efficient.py new file mode 100644 index 000000000..0939c52c9 --- /dev/null +++ b/tests/st/test_ansatz/test_hardware_efficient.py @@ -0,0 +1,40 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test hardware efficient ansatz""" + +import os +os.environ['OMP_NUM_THREADS'] = '8' +import numpy as np +import mindspore as ms +from mindquantum.ansatz import HardwareEfficientAnsatz +from mindquantum.nn import MindQuantumAnsatzOnlyLayer as MAL +from mindquantum.gate import Hamiltonian, RX, RY, X +from mindquantum.ops import QubitOperator + +ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + + +def test_hardware_efficient(): + depth = 3 + n_qubits = 3 + hea = HardwareEfficientAnsatz(n_qubits, [RX, RY, RX], X, 'all', depth) + ham = QubitOperator('Z0 Z1 Z2') + ms.set_seed(42) + net = MAL(hea.circuit.para_name, hea.circuit, Hamiltonian(ham)) + opti = ms.nn.Adagrad(net.trainable_params(), learning_rate=4e-1) + train_net = ms.nn.TrainOneStepCell(net, opti) + for i in range(3): + res = train_net().asnumpy()[0, 0] + assert np.allclose(round(res, 4), -0.7588) diff --git a/tests/st/test_ansatz/test_max_2_sat.py b/tests/st/test_ansatz/test_max_2_sat.py new file mode 100644 index 000000000..1f773ca44 --- /dev/null +++ b/tests/st/test_ansatz/test_max_2_sat.py @@ -0,0 +1,40 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test max_2_sat""" + +import os +os.environ['OMP_NUM_THREADS'] = '8' +import numpy as np +import mindspore as ms +from mindquantum.ansatz import Max2SATAnsatz +from mindquantum.nn import MindQuantumAnsatzOnlyLayer as MAL +from mindquantum.gate import Hamiltonian + +ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + + +def test_max_2_sat(): + clauses = [(1, 2), (1, -2), (-1, 2), (-1, -2), (1, 3)] + depth = 3 + max2sat = Max2SATAnsatz(clauses, depth) + ms.set_seed(42) + net = MAL(max2sat.circuit.para_name, max2sat.circuit, + Hamiltonian(max2sat.hamiltonian)) + opt = ms.nn.Adagrad(net.trainable_params(), learning_rate=4e-1) + train_net = ms.nn.TrainOneStepCell(net, opt) + ret = 0 + for i in range(100): + ret = train_net().asnumpy()[0, 0] + assert np.allclose(round(ret, 3), 1) diff --git a/tests/st/test_ansatz/test_max_cut.py b/tests/st/test_ansatz/test_max_cut.py new file mode 100644 index 000000000..bb86b52f2 --- /dev/null +++ b/tests/st/test_ansatz/test_max_cut.py @@ -0,0 +1,39 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test max_cut""" + +import os +os.environ['OMP_NUM_THREADS'] = '8' +import numpy as np +import mindspore as ms +from mindquantum.ansatz import MaxCutAnsatz +from mindquantum.nn import MindQuantumAnsatzOnlyLayer as MAL +from mindquantum.gate import Hamiltonian + +ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + + +def test_max_cut(): + graph = [(0, 1), (1, 2), (2, 3), (3, 4), (1, 4)] + depth = 3 + maxcut = MaxCutAnsatz(graph, depth) + ms.set_seed(42) + net = MAL(maxcut.circuit.para_name, maxcut.circuit, + Hamiltonian(-maxcut.hamiltonian)) + opti = ms.nn.Adagrad(net.trainable_params(), learning_rate=4e-1) + train_net = ms.nn.TrainOneStepCell(net, opti) + for i in range(50): + cut = -train_net().asnumpy()[0, 0] + assert np.allclose(round(cut, 3), 4.831) diff --git a/tests/st/test_ansatz/test_qubit_ucc.py b/tests/st/test_ansatz/test_qubit_ucc.py new file mode 100644 index 000000000..71dad08df --- /dev/null +++ b/tests/st/test_ansatz/test_qubit_ucc.py @@ -0,0 +1,66 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test unitary coupled-cluster ansatz""" + +import os +os.environ['OMP_NUM_THREADS'] = '8' +import numpy as np +import mindspore as ms +from mindquantum.ansatz import QubitUCCAnsatz +from mindquantum.circuit import Circuit +from mindquantum.nn import MindQuantumAnsatzOnlyLayer as MAL +from mindquantum.gate import Hamiltonian, X +from mindquantum.ops import QubitOperator + +ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + + +def test_quccsd(): + # Hydrogen molecule + ham = QubitOperator("", (-0.5339363487727398+0j)) + \ + QubitOperator("X0 X1 Y2 Y3", (-0.0647846187202642+0j)) + \ + QubitOperator("X0 Y1 Y2 X3", (0.0647846187202642+0j)) + \ + QubitOperator("Y0 X1 X2 Y3", (0.0647846187202642+0j)) + \ + QubitOperator("Y0 Y1 X2 X3", (-0.0647846187202642+0j)) + \ + QubitOperator("Z0", (0.06727930458983417+0j)) + \ + QubitOperator("Z0 Z1", (0.12736570310657463+0j)) + \ + QubitOperator("Z0 Z2", (0.06501569581211997+0j)) + \ + QubitOperator("Z0 Z3", (0.12980031453238416+0j)) + \ + QubitOperator("Z1", (0.06727930458983417+0j)) + \ + QubitOperator("Z1 Z2", (0.12980031453238416+0j)) + \ + QubitOperator("Z1 Z3", (0.06501569581211997+0j)) + \ + QubitOperator("Z2", (0.006651295687574388+0j)) + \ + QubitOperator("Z2 Z3", (0.13366602988233994+0j)) + \ + QubitOperator("Z3", (0.006651295687574388+0j)) + n_qubits = 4 + n_electrons = 2 + occ_orb = [0] + vir_orb = [1] + generalized = False + trotter_step = 4 + ucc = QubitUCCAnsatz(n_qubits, n_electrons, + occ_orb, vir_orb, + generalized, trotter_step) + total_circuit = Circuit() + for i in range(n_electrons): + total_circuit += X.on(i) + total_circuit += ucc.circuit + net = MAL(total_circuit.para_name, total_circuit, Hamiltonian(ham.real)) + opti = ms.nn.Adagrad(net.trainable_params(), learning_rate=4e-2) + train_net = ms.nn.TrainOneStepCell(net, opti) + for i in range(100): + res = train_net().asnumpy()[0, 0] + print(res) + assert np.allclose(round(res, 4), -0.9486) diff --git a/tests/st/test_ansatz/test_ucc.py b/tests/st/test_ansatz/test_ucc.py new file mode 100644 index 000000000..598217137 --- /dev/null +++ b/tests/st/test_ansatz/test_ucc.py @@ -0,0 +1,65 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test unitary coupled-cluster ansatz""" + +import os +os.environ['OMP_NUM_THREADS'] = '8' +import numpy as np +import mindspore as ms +from mindquantum.ansatz import UCCAnsatz +from mindquantum.circuit import Circuit +from mindquantum.nn import MindQuantumAnsatzOnlyLayer as MAL +from mindquantum.gate import Hamiltonian, X +from mindquantum.ops import QubitOperator + +ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + + +def test_uccsd(): + # Hydrogen molecule + ham = QubitOperator("", (-0.5339363487727398+0j)) + \ + QubitOperator("X0 X1 Y2 Y3", (-0.0647846187202642+0j)) + \ + QubitOperator("X0 Y1 Y2 X3", (0.0647846187202642+0j)) + \ + QubitOperator("Y0 X1 X2 Y3", (0.0647846187202642+0j)) + \ + QubitOperator("Y0 Y1 X2 X3", (-0.0647846187202642+0j)) + \ + QubitOperator("Z0", (0.06727930458983417+0j)) + \ + QubitOperator("Z0 Z1", (0.12736570310657463+0j)) + \ + QubitOperator("Z0 Z2", (0.06501569581211997+0j)) + \ + QubitOperator("Z0 Z3", (0.12980031453238416+0j)) + \ + QubitOperator("Z1", (0.06727930458983417+0j)) + \ + QubitOperator("Z1 Z2", (0.12980031453238416+0j)) + \ + QubitOperator("Z1 Z3", (0.06501569581211997+0j)) + \ + QubitOperator("Z2", (0.006651295687574388+0j)) + \ + QubitOperator("Z2 Z3", (0.13366602988233994+0j)) + \ + QubitOperator("Z3", (0.006651295687574388+0j)) + n_qubits = 4 + n_electrons = 2 + occ_orb = [0] + vir_orb = [1] + generalized = True + trotter_step = 2 + ucc = UCCAnsatz(n_qubits, n_electrons, + occ_orb, vir_orb, + generalized, trotter_step) + total_circuit = Circuit() + for i in range(n_electrons): + total_circuit += X.on(i) + total_circuit += ucc.circuit + net = MAL(total_circuit.para_name, total_circuit, Hamiltonian(ham.real)) + opti = ms.nn.Adagrad(net.trainable_params(), learning_rate=4e-2) + train_net = ms.nn.TrainOneStepCell(net, opti) + for i in range(50): + res = train_net().asnumpy()[0, 0] + assert np.allclose(round(res, 4), -0.9486) diff --git a/tests/st/test_circuit/test_circuit.py b/tests/st/test_circuit/test_circuit.py new file mode 100644 index 000000000..2e6b90caf --- /dev/null +++ b/tests/st/test_circuit/test_circuit.py @@ -0,0 +1,107 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test circuit.""" + +from mindquantum.ops import QubitOperator +import numpy as np +from mindquantum import Circuit +import mindquantum.gate as G +from mindquantum.circuit import pauli_word_to_circuits +from mindquantum.circuit import decompose_single_term_time_evolution +from mindquantum.circuit import UN, SwapParts, generate_uccsd +from mindquantum.circuit import TimeEvolution + + +def test_time_evolution(): + h = QubitOperator('Z0 Z1', 'p') + circ = TimeEvolution(h).circuit + circ_exp = Circuit([G.X.on(1, 0), G.RZ({'p': 2}).on(1), G.X.on(1, 0)]) + assert circ == circ_exp + + +def test_circuit(): + circuit1 = Circuit() + circuit1 += G.RX('a').on(0) + circuit1 *= 2 + circuit2 = Circuit([G.X.on(0, 1)]) + circuit3 = circuit1 + circuit2 + assert len(circuit3) == 3 + assert circuit3.n_qubits == 2 + circuit3.insert(0, G.H.on(0)) + assert circuit3[0] == G.H(0) + circuit3.no_grad() + assert len(circuit3[1].coeff.requires_grad_parameters) == 0 + circuit3.requires_grad() + assert len(circuit3[1].coeff.requires_grad_parameters) == 1 + assert len(circuit3.parameter_resolver()) == 1 + assert circuit3.mindspore_data() == { + 'gate_names': ['npg', 'RX', 'RX', 'npg'], + 'gate_matrix': [[[['0.7071067811865475', '0.7071067811865475'], + ['0.7071067811865475', '-0.7071067811865475']], + [['0.0', '0.0'], ['0.0', '0.0']]], + [[['0.0', '0.0'], ['0.0', '0.0']], + [['0.0', '0.0'], ['0.0', '0.0']]], + [[['0.0', '0.0'], ['0.0', '0.0']], + [['0.0', '0.0'], ['0.0', '0.0']]], + [[['0.0', '1.0'], ['1.0', '0.0']], + [['0.0', '0.0'], ['0.0', '0.0']]]], + 'gate_obj_qubits': [[0], [0], [0], [0]], + 'gate_ctrl_qubits': [[], [], [], [1]], + 'gate_params_names': [[], ['a'], ['a'], []], + 'gate_coeff': [[], [1.0], [1.0], []], + 'gate_requires_grad': [[], [True], [True], []] + } + + +def test_circuit_apply(): + circuit = Circuit() + circuit += G.RX('a').on(0, 1) + circuit += G.H.on(0) + circuit = circuit.apply_value({'a': 0.2}) + circuit_exp = Circuit([G.RX(0.2).on(0, 1), G.H.on(0)]) + assert circuit == circuit_exp + + +def test_pauli_word_to_circuits(): + circ = pauli_word_to_circuits(QubitOperator('Z0 Y1')) + assert circ == Circuit([G.Z.on(0), G.Y.on(1)]) + + +def test_un(): + circ = UN(G.X, [3, 4, 5], maps_ctrl=[0, 1, 2]) + assert circ[-1] == G.X.on(5, 2) + + +def test_swappart(): + circ = SwapParts([1, 2, 3], [4, 5, 6], 0) + assert circ[-1] == G.SWAP([3, 6], 0) + + +def test_decompose_single_term_time_evolution(): + circ = decompose_single_term_time_evolution(QubitOperator('Z0 Z1'), + {'a': 1}) + assert circ == Circuit([G.X.on(1, 0), G.RZ({'a': 2}).on(1), G.X.on(1, 0)]) + + +def test_generate_uccsd(): + circ, init_amp, para_name, ham, n_q, n_e = generate_uccsd( + './tests/st/LiH.hdf5') + assert len(circ) == 4416 + assert circ[2000] == G.X.on(9, 8) + assert np.allclose(init_amp[-5], 0.001687182323430231) + assert len(para_name) == 20 + assert len(ham.terms) == 631 + assert n_q == 12 + assert n_e == 4 diff --git a/tests/st/test_circuit/test_high_level_ops.py b/tests/st/test_circuit/test_high_level_ops.py new file mode 100644 index 000000000..43a99d3b8 --- /dev/null +++ b/tests/st/test_circuit/test_high_level_ops.py @@ -0,0 +1,143 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test high level ops.""" + +import numpy as np +from mindquantum import Circuit, X, H, RX +from mindquantum.circuit import controlled as C +from mindquantum.circuit import dagger as D +from mindquantum.circuit import apply as A +from mindquantum.circuit import AP +from mindquantum.circuit import CPN +from mindquantum.circuit import StateEvolution +from mindquantum.circuit import qft + + +def test_apply(): + u = unit1([0]) + u2 = A(unit1, [1, 2]) + u2 = u2([0]) + u3 = A(u, [1, 2]) + u_exp = Circuit([X.on(1), H.on(2), RX('a_0').on(1)]) + assert u2 == u3 == u_exp + + +def test_add_prefix(): + u = unit1([0]) + u2 = AP(u, 'x') + u3 = AP(unit1, 'x') + u3 = u3([0]) + u_exp = Circuit([X.on(0), H.on(1), RX('x_a_0').on(0)]) + assert u2 == u3 == u_exp + + +def test_change_param_name(): + u = unit1([0]) + u2 = CPN(u, {'a_0': 'x'}) + u3 = CPN(unit1, {'a_0': 'x'}) + u3 = u3([0]) + u_exp = Circuit([X.on(0), H.on(1), RX('x').on(0)]) + assert u2 == u3 == u_exp + + +def unit1(rotate_qubits): + circuit = Circuit() + circuit += X.on(0) + circuit += H.on(1) + for q in rotate_qubits: + circuit += RX(f'a_{q}').on(q) + return circuit + + +def test_controlled_and_dagger(): + qubits = [0, 1, 2, 3] + c1 = C(unit1)(4, qubits) + c2 = C(unit1(qubits))(4) + assert c1 == c2 + + c3 = C(C(unit1))(4, 5, qubits) + c4 = C(C(unit1)(4, qubits))(5) + c5 = C(C(unit1(qubits)))(4, 5) + assert c3 == c4 == c5 + + c6 = D(unit1)(qubits) + c7 = D(unit1(qubits)) + assert c6 == c7 + + c8 = D(C(unit1))(4, qubits) + c9 = C(D(unit1))(4, qubits) + assert c8 == c9 + + +def test_state_evol(): + qubits = [0, 1, 2, 3] + circuit = X.on(4) + C(D(unit1(qubits)))(4) + circuit_exp = Circuit() + circuit_exp += X.on(4) + circuit_exp += RX({'a_3': -1}).on(3, 4) + circuit_exp += RX({'a_2': -1}).on(2, 4) + circuit_exp += RX({'a_1': -1}).on(1, 4) + circuit_exp += RX({'a_0': -1}).on(0, 4) + circuit_exp += H.on(1, 4) + circuit_exp += X.on(0, 4) + assert circuit_exp == circuit + pr = {'a_0': 1, 'a_1': 2, 'a_2': 3, 'a_3': 4} + fs1 = StateEvolution(circuit).final_state(pr) + fs2 = StateEvolution(circuit.apply_value(pr)).final_state() + assert np.allclose(fs1, fs2) + np.random.seed(42) + sampling = StateEvolution(circuit.apply_value(pr)).sampling(shots=100) + sampling_exp = { + '00000': 0, + '00001': 0, + '00010': 0, + '00011': 0, + '00100': 0, + '00101': 0, + '00110': 0, + '00111': 0, + '01000': 0, + '01001': 0, + '01010': 0, + '01011': 0, + '01100': 0, + '01101': 0, + '01110': 0, + '01111': 0, + '10000': 0, + '10001': 0, + '10010': 0, + '10011': 0, + '10100': 0, + '10101': 2, + '10110': 0, + '10111': 2, + '11000': 0, + '11001': 0, + '11010': 0, + '11011': 0, + '11100': 7, + '11101': 44, + '11110': 4, + '11111': 41 + } + assert sampling == sampling_exp + + +def test_qft(): + c = qft(range(4)) + s = StateEvolution(c).final_state() + s_exp = np.ones(2**4) * 0.25 + assert np.allclose(s, s_exp) diff --git a/tests/st/test_engine/test_circuitengine.py b/tests/st/test_engine/test_circuitengine.py new file mode 100755 index 000000000..ceeb1eaa6 --- /dev/null +++ b/tests/st/test_engine/test_circuitengine.py @@ -0,0 +1,40 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test circuitengine.""" + +import mindquantum.gate as G +from mindquantum.engine.circuitengine import CircuitEngine +from mindquantum.engine import circuit_generator +from mindquantum import Circuit + +def test_allocate_qureg(): + """Test allocate qureg.""" + eng = CircuitEngine() + qubits = eng.allocate_qureg(2) + G.H.__or__((eng.qubits[0],)) + G.X.__or__((eng.qubits[0], eng.qubits[1])) + assert qubits[0].qubit_id == 0 + assert qubits[1].qubit_id == 1 + + +def test_circuit_generator(): + """Test circuit generator.""" + @circuit_generator(3) + def encoder(qubits): + G.H.__or__((qubits[0],)) + G.X.__or__((qubits[0], qubits[1])) + G.RY('p1').__or__((qubits[2],)) + + assert isinstance(encoder, Circuit) diff --git a/tests/st/test_gate/test_gate.py b/tests/st/test_gate/test_gate.py new file mode 100644 index 000000000..72f4048c3 --- /dev/null +++ b/tests/st/test_gate/test_gate.py @@ -0,0 +1,180 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test gate.""" + +import pytest +from mindquantum.ops import QubitOperator +import numpy as np +from scipy.linalg import expm +from mindquantum.gate import Hamiltonian +import mindquantum.gate as G + + +def test_hamiltonian(): + """Test hamiltonian""" + ham = Hamiltonian(QubitOperator('Z0 Y1', 0.3)) + assert ham.ham_termlist == [(((0, 'Z'), (1, 'Y')), 0.3)] + assert ham.mindspore_data() == { + 'hams_pauli_coeff': [0.3], + 'hams_pauli_word': [['Z', 'Y']], + 'hams_pauli_qubit': [[0, 1]] + } + + +def test_rotate_pauli(): + gates = { + 'rx': [ + G.RX('angle').on(0), lambda phi: np.array([[ + np.cos(phi / 2), -1j * np.sin(phi / 2) + ], [-1j * np.sin(phi / 2), np.cos(phi / 2)]]) + ], + 'ry': [ + G.RY('angle').on(0), + lambda phi: np.array([[np.cos(phi / 2), -np.sin( + phi / 2)], [np.sin(phi / 2), np.cos(phi / 2)]]) + ], + 'rz': [ + G.RZ('angle').on(0), + lambda phi: np.array([[np.exp(-1j * phi / 2), 0], + [0, np.exp(1j * phi / 2)]]) + ] + } + angle = 0.5 + for name, rs in gates.items(): + assert np.allclose(rs[0].matrix({'angle': angle}), + rs[1](angle)) + assert np.allclose(rs[0].diff_matrix({'angle': angle}), + 0.5 * rs[1](angle + np.pi)) + assert np.allclose(rs[0].hermitian().matrix({'angle': angle}), + rs[1](-angle)) + assert np.allclose(rs[0].hermitian().diff_matrix({'angle': angle}), + 0.5 * rs[1](-angle - np.pi)) + + +def test_phase_shift(): + angle = 0.5 + f = lambda theta: np.array([[1, 0], [0, np.exp(1.0j * theta)]]) + assert np.allclose(G.PhaseShift(angle).matrix(), f(angle)) + + +def test_trap_ion_gate(): + angle = 0.5 + xx = [ + G.XX("angle").on((0, 1)), lambda angle: expm(-1j * angle * np.array( + [[0. + 0.j, 0. + 0.j, 0. + 0.j, 1. + 0.j], + [0. + 0.j, 0. + 0.j, 1. + 0.j, 0. + 0.j], + [0. + 0.j, 1. + 0.j, 0. + 0.j, 0. + 0.j], + [1. + 0.j, 0. + 0.j, 0. + 0.j, 0. + 0.j]])) + ] + yy = [ + G.YY("angle").on((0, 1)), lambda angle: expm(-1j * angle * np.array( + [[0. + 0.j, 0. + 0.j, 0. + 0.j, -1. + 0.j], + [0. + 0.j, 0. + 0.j, 1. + 0.j, 0. + 0.j], + [0. + 0.j, 1. + 0.j, 0. + 0.j, 0. + 0.j], + [-1. + 0.j, 0. + 0.j, 0. + 0.j, 0. + 0.j]])) + ] + zz = [ + G.ZZ("angle").on((0, 1)), lambda angle: expm(-1j * angle * np.array([[ + 1., 0., 0., 0. + ], [0., -1., 0., 0.], [0., 0., -1., 0.], [0., 0., 0., 1.]])) + ] + for g in [xx, yy, zz]: + assert np.allclose(g[0].matrix({'angle': angle}), + g[1](angle)) + assert np.allclose(g[0].diff_matrix({'angle': angle}), + g[1](angle + np.pi / 2)) + + +def test_pauli_gate(): + gates = { + 'X': [ + G.X, + np.array([[0. + 0.j, 1. + 0.j], [1. + 0.j, 0. + 0.j]]), + lambda phi: np.array([[np.cos(phi / 2), -1j * np.sin(phi / 2)], + [-1j * np.sin(phi / 2), + np.cos(phi / 2)]]) + ], + 'Y': [ + G.Y, + np.array([[0. + 0.j, 0. - 1.j], [0. + 1.j, 0. + 0.j]]), + lambda phi: np.array([[np.cos(phi / 2), -np.sin( + phi / 2)], [np.sin(phi / 2), np.cos(phi / 2)]]) + ], + 'Z': [ + G.Z, + np.array([[1. + 0.j, 0. + 0.j], [0. + 0.j, -1. + 0.j]]), + lambda phi: np.array([[np.exp(-1j * phi / 2), 0], + [0, np.exp(1j * phi / 2)]]) + ] + } + angle = 0.5 + for name, ps in gates.items(): + assert np.allclose(ps[0].matrix(), ps[1]) + assert np.allclose((ps[0]**angle).matrix(), + ps[2](angle * np.pi)) + + +def test_identity(): + assert np.allclose(G.I.matrix(), + np.array([[1. + 0.j, 0. + 0.j], [0. + 0.j, 1. + 0.j]])) + + +def test_hadamard(): + assert np.allclose( + G.H.matrix(), + np.array([[0.70710678 + 0.j, 0.70710678 + 0.j], + [0.70710678 + 0.j, -0.70710678 + 0.j]])) + + +def test_power(): + angle = 0.3 + frac = 0.4 + assert np.allclose( + G.Power(G.RX(angle), frac).matrix(), + G.RX(angle * frac).matrix()) + + +def test_swap(): + assert np.allclose( + G.SWAP.matrix(), + np.array([[1. + 0.j, 0. + 0.j, 0. + 0.j, 0. + 0.j], + [0. + 0.j, 0. + 0.j, 1. + 0.j, 0. + 0.j], + [0. + 0.j, 1. + 0.j, 0. + 0.j, 0. + 0.j], + [0. + 0.j, 0. + 0.j, 0. + 0.j, 1. + 0.j]])) + + +def test_univ_mat_gate(): + mat = np.random.uniform(size=(2, 2)) + assert np.allclose(G.UnivMathGate('univ', mat).matrix(), mat) + + +def test_gate_obj_mismatch(): + with pytest.raises(Exception, match=r"requires \d+ qubits"): + G.X((0, 1)) + with pytest.raises(Exception, match=r"requires \d+ qubits"): + G.RX('a').on((1, 2), 0) + with pytest.raises(Exception, match=r"requires \d+ qubits"): + G.RX(1).on((1, 2), 0) + with pytest.raises(Exception, match=r"requires \d+ qubits"): + G.ZZ('a').on(1, 0) + + +def test_gate_obj_ctrl_overlap(): + with pytest.raises(Exception, match=r"cannot have same qubits"): + G.X(1, 1) + with pytest.raises(Exception, match=r"cannot have same qubits"): + G.ZZ('a').on((0, 1), (1, 2)) + with pytest.raises(Exception, match=r"cannot have same qubits"): + G.RX('a').on(1, (1, 2)) diff --git a/tests/st/test_gate/test_projector.py b/tests/st/test_gate/test_projector.py new file mode 100644 index 000000000..11ca2ece1 --- /dev/null +++ b/tests/st/test_gate/test_projector.py @@ -0,0 +1,41 @@ +# Copyright (c) 2020 Huawei Technologies Co.,ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The test projector.""" + +from mindquantum.gate import Projector +from mindquantum.circuit import UN +from mindquantum import H, RX, RY, RZ +from mindquantum.nn import generate_evolution_operator +from mindquantum.nn import MindQuantumAnsatzOnlyOperator +import mindspore as ms +import numpy as np + + +def test_projector(): + pro = Projector('II010') + assert str(pro) == 'I2 ⊗ ¦010⟩⟨010¦' + + +def test_projector_checked_by_evo(): + circ = UN(H, 3) + RX('a').on(0) + RY('b').on(1) + RZ('c').on(2) + evo = generate_evolution_operator(circ) + a, b, c = 0.3, 0.5, 0.9 + data = ms.Tensor(np.array([a, b, c]).astype(np.float32)) + state = evo(data) + proj = [Projector('I10'), Projector('I10')] + poi = [int(i, 2) for i in ['010', '110']] + pqc = MindQuantumAnsatzOnlyOperator(circ.para_name, circ, proj) + pob = pqc(data) + pob_exp = np.sum(np.abs(state[poi])**2) + assert np.allclose(pob.asnumpy(), [[pob_exp], [pob_exp]]) diff --git a/tests/st/test_hiqfermion/test_quccsd.py b/tests/st/test_hiqfermion/test_quccsd.py new file mode 100644 index 000000000..f53477d34 --- /dev/null +++ b/tests/st/test_hiqfermion/test_quccsd.py @@ -0,0 +1,64 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Tests for the qUCCSD generator and related functions""" + +from mindquantum.circuit import TimeEvolution +from mindquantum.utils import count_qubits +from mindquantum.hiqfermion.ucc import quccsd_generator + + +def test_quccsd(): + h2_quccsd = quccsd_generator(4, 2) + h2_quccsd_terms = set(list(h2_quccsd.terms)) + h2_quccsd_terms_check = set([((3, 1), (0, 0)), + ((3, 1), (1, 0)), + ((1, 1), (2, 0)), + ((1, 1), (3, 0)), + ((2, 1), (0, 0)), + ((2, 1), (1, 0)), + ((0, 1), (2, 0)), + ((0, 1), (3, 0)), + ((3, 1), (2, 1), (1, 0), (0, 0)), + ((1, 1), (0, 1), (3, 0), (2, 0))]) + assert h2_quccsd_terms == h2_quccsd_terms_check + + lih_quccsd = quccsd_generator(12, 4) + lih_quccsd_circuit = TimeEvolution( + lih_quccsd.to_qubit_operator().imag, 1).circuit + n_params_lih = len(lih_quccsd_circuit.para_name) + assert n_params_lih == 200 + + lih_quccgsd_cas = quccsd_generator(12, 4, + occ_orb=[1], vir_orb=[2, 3], + generalized=True) + assert count_qubits(lih_quccgsd_cas) == 8 + lih_quccgsd_cas_circuit = TimeEvolution( + lih_quccgsd_cas.to_qubit_operator().imag, 1).circuit + n_params_lih = len(lih_quccgsd_cas_circuit.para_name) + assert n_params_lih == 135 + + # qUCCSD with fully occupied orbitals should lead to zero parameters + he2_quccsd = quccsd_generator(4, 4) + he2_quccsd_circuit = TimeEvolution( + he2_quccsd.to_qubit_operator().imag, 1).circuit + n_params_he2 = len(he2_quccsd_circuit.para_name) + assert n_params_he2 == 0 + + # qUCCGSD will not be affected by the occupancy numbers + he2_quccsd = quccsd_generator(4, 4, generalized=True) + he2_quccsd_circuit = TimeEvolution( + he2_quccsd.to_qubit_operator().imag, 1).circuit + n_params_he2 = len(he2_quccsd_circuit.para_name) + assert n_params_he2 == 27 diff --git a/tests/st/test_hiqfermion/test_transforms.py b/tests/st/test_hiqfermion/test_transforms.py new file mode 100644 index 000000000..1e9e9fbba --- /dev/null +++ b/tests/st/test_hiqfermion/test_transforms.py @@ -0,0 +1,39 @@ +# Copyright (c) 2020 Huawei Technologies Co.,ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Test the transforms in the hiqfermion module. +""" + +from mindquantum.hiqfermion.transforms.transform import Transform +from mindquantum.ops import FermionOperator + +def test_transform(): + """Test different transform. + Note we need add the pyscfcalculator to build the Hamiltonian for the + test of bravyi-kitaev superfast transform. + """ + op1 = FermionOperator('1^') + op_transform = Transform(op1) + op1_jordan_wigner = op_transform.jordan_wigner() + assert str(op1_jordan_wigner) == '0.5 [Z0 X1] +\n-0.5j [Z0 Y1] ' + + op1_parity = op_transform.parity() + assert str(op1_parity) == '0.5 [Z0 X1] +\n-0.5j [Y1] ' + + op1_bravyi_kitaev = op_transform.bravyi_kitaev() + assert str(op1_bravyi_kitaev) == '0.5 [Z0 X1] +\n-0.5j [Y1] ' + + op1_ternary_tree = op_transform.ternary_tree() + assert str(op1_ternary_tree) == '0.5 [X0 Z1] +\n-0.5j [Y0 X2] ' + #TODO bravyi_kitaev_superfast() diff --git a/tests/st/test_hiqfermion/test_uccsd0.py b/tests/st/test_hiqfermion/test_uccsd0.py new file mode 100644 index 000000000..0b1f72b9b --- /dev/null +++ b/tests/st/test_hiqfermion/test_uccsd0.py @@ -0,0 +1,94 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Tests for the UCCSD0 generator and related functions""" + +from mindquantum.circuit import TimeEvolution +from mindquantum.hiqfermion.transforms import Transform +from mindquantum.utils import count_qubits +from mindquantum.hiqfermion.ucc import uccsd0_singlet_generator +from mindquantum.hiqfermion.ucc.uccsd0 import spin_adapted_t1, spin_adapted_t2 + + +def test_spin_adapted_t1(): + t1_20 = spin_adapted_t1(2, 0)[0] + assert str(t1_20) == '1.0 [4^ 0] +\n1.0 [5^ 1] ' + t1_00 = spin_adapted_t1(0, 0)[0] + assert str(t1_00) == '1.0 [0^ 0] +\n1.0 [1^ 1] ' + + +def test_spin_adapted_t2(): + """Use a 8-qubit 4-electron system as an example.""" + t2_3210_list = spin_adapted_t2([3, 2], [1, 0]) + assert len(t2_3210_list) == 2 + term1 = set(list(t2_3210_list[0].terms)) + assert len(term1) == 4 + term1_check = set([((7, 1), (4, 1), (0, 0), (3, 0)), + ((7, 1), (4, 1), (2, 0), (1, 0)), + ((5, 1), (6, 1), (0, 0), (3, 0)), + ((5, 1), (6, 1), (2, 0), (1, 0))]) + assert term1 == term1_check + term2 = set(list(t2_3210_list[1].terms)) + assert len(term2) == 6 + term2_check = set([((0, 0), (2, 0), (6, 1), (4, 1)), + ((1, 0), (3, 0), (7, 1), (5, 1)), + ((0, 0), (3, 0), (7, 1), (4, 1)), + ((0, 0), (3, 0), (5, 1), (6, 1)), + ((2, 0), (1, 0), (7, 1), (4, 1)), + ((2, 0), (1, 0), (5, 1), (6, 1))]) + assert term2 == term2_check + + +def test_uccsd0(): + h2_uccsd0 = uccsd0_singlet_generator(4, 2) + h2_uccsd0_terms = set(list(h2_uccsd0.terms)) + h2_uccsd0_terms_check = set([((2, 1), (0, 0)), + ((3, 1), (1, 0)), + ((0, 1), (2, 0)), + ((1, 1), (3, 0)), + ((3, 1), (2, 1), (1, 0), (0, 0)), + ((1, 1), (0, 1), (3, 0), (2, 0))]) + assert h2_uccsd0_terms == h2_uccsd0_terms_check + + lih_uccsd0 = uccsd0_singlet_generator(12, 4) + lih_uccsd0_circuit = TimeEvolution( + Transform(lih_uccsd0).jordan_wigner().imag, 1).circuit + n_params_lih = len(lih_uccsd0_circuit.para_name) + assert n_params_lih == 44 + + # cas means complete active space + lih_uccgsd0_cas = uccsd0_singlet_generator(12, 4, + occ_orb=[1], vir_orb=[2, 3], + generalized=True) + # The max index of affected qubits in the ansatz is 7 = 8-1. + # Does not mean the number of qubits in Hamiltonian is reduced to 8. + assert count_qubits(lih_uccgsd0_cas) == 8 + lih_uccgsd0_cas_circuit = TimeEvolution( + Transform(lih_uccgsd0_cas).jordan_wigner().imag, 1).circuit + n_params_lih_cas = len(lih_uccgsd0_cas_circuit.para_name) + assert n_params_lih_cas == 24 + + # UCCSD with fully occupied orbitals should lead to 0 parameters + he2_uccsd = uccsd0_singlet_generator(4, 4) + he2_uccsd_circuit = TimeEvolution( + Transform(he2_uccsd).jordan_wigner().imag, 1).circuit + n_params_he2 = len(he2_uccsd_circuit.para_name) + assert n_params_he2 == 0 + + # UCCGSD will not be affected by the occupancy number + he2_uccgsd = uccsd0_singlet_generator(4, 4, generalized=True) + he2_uccgsd_circuit = TimeEvolution( + Transform(he2_uccgsd).jordan_wigner().imag, 1).circuit + n_params_he2_gsd = len(he2_uccgsd_circuit.para_name) + assert n_params_he2_gsd == 5 diff --git a/tests/st/test_nn/test_mindquantum.py b/tests/st/test_nn/test_mindquantum.py new file mode 100644 index 000000000..9cae5b8a7 --- /dev/null +++ b/tests/st/test_nn/test_mindquantum.py @@ -0,0 +1,70 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test mindquantum.""" + +import os +os.environ['OMP_NUM_THREADS'] = '8' +from mindquantum.ops import QubitOperator +import numpy as np +import mindspore as ms +import mindquantum.gate as G +from mindquantum.nn import MindQuantumLayer, MindQuantumAnsatzOnlyLayer +from mindquantum.circuit import generate_uccsd +from mindquantum import Circuit, Hamiltonian + + +def test_mindquantumlayer_forward(): + """Test mindquantum layer.""" + encoder = Circuit() + ansatz = Circuit() + encoder += G.RX('e1').on(0) + ansatz += G.RY('a').on(0) + ham = Hamiltonian(QubitOperator('Z0')) + ms.set_seed(42) + ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + net = MindQuantumLayer(['e1'], ['a'], encoder + ansatz, ham) + encoder_data = ms.Tensor(np.array([[0.5]]).astype(np.float32)) + res = net(encoder_data) + assert round(float(res.asnumpy()[0, 0]), 3) == 0.878 + + +def test_vqe_convergence(): + ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + ansatz_circuit, \ + init_amplitudes, \ + ansatz_parameter_names, \ + hamiltonian_qubitop, \ + n_qubits, n_electrons = generate_uccsd( + './tests/st/H4.hdf5', th=-1) + hf_circuit = Circuit([G.X.on(i) for i in range(n_electrons)]) + vqe_circuit = hf_circuit + ansatz_circuit + molecule_pqcnet = MindQuantumAnsatzOnlyLayer( + ansatz_parameter_names, vqe_circuit, + Hamiltonian(hamiltonian_qubitop.real)) + optimizer = ms.nn.Adagrad(molecule_pqcnet.trainable_params(), + learning_rate=4e-2) + train_pqcnet = ms.nn.TrainOneStepCell(molecule_pqcnet, optimizer) + eps = 1e-8 + energy_diff = 1. + energy_last = 1. + iter_idx = 0 + iter_max = 100 + while (abs(energy_diff) > eps) and (iter_idx < iter_max): + energy_i = train_pqcnet().asnumpy() + energy_diff = energy_last - energy_i + energy_last = energy_i + iter_idx += 1 + + assert round(energy_i.item(), 3) == -2.166 diff --git a/tests/st/test_nn/test_nn.py b/tests/st/test_nn/test_nn.py new file mode 100755 index 000000000..35bd9fe40 --- /dev/null +++ b/tests/st/test_nn/test_nn.py @@ -0,0 +1,147 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test nn.""" + +import numpy as np +import mindspore as ms +from mindquantum.ops import QubitOperator +import mindquantum.gate as G +import mindquantum as mq +from mindquantum import Hamiltonian +from mindquantum import Projector +from mindquantum.nn import MindQuantumLayer +from mindquantum.circuit import Circuit +from mindquantum.engine import circuit_generator +from mindquantum.nn import generate_evolution_operator +from mindquantum.nn import MindQuantumAnsatzOnlyOperator +from mindquantum.nn import MindQuantumAnsatzOnlyLayer + + +def test_mindquantumlayer(): + """Test mindquantumlayer forward and backward.""" + encoder = Circuit() + ansatz = Circuit() + encoder += G.RX('e1').on(0) + encoder += G.RY('e2').on(1) + ansatz += G.X.on(1, 0) + ansatz += G.RY('p1').on(0) + ham = Hamiltonian(QubitOperator('Z0')) + ms.set_seed(55) + ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + net = MindQuantumLayer(['e1', 'e2'], ['p1'], encoder + ansatz, ham) + encoder_data = ms.Tensor(np.array([[0.1, 0.2]]).astype(np.float32)) + res = net(encoder_data) + assert round(float(res.asnumpy()[0, 0]), 6) == round(float(0.9949919), 6) + state = net.final_state(encoder_data[0]) + assert np.allclose(state[0], 9.9375761e-01 + 1.2387493e-05j) + + +def test_generate_evolution_operator_state(): + a, b = 0.3, 0.5 + circ = Circuit([G.RX('a').on(0), G.RX('b').on(1)]) + data = ms.Tensor(np.array([a, b]).astype(np.float32)) + evol = generate_evolution_operator(circ) + state = evol(data) + + state_exp = [0.9580325796404553, -0.14479246283091116j, + -0.2446258794777393j, -0.036971585637570345] + assert np.allclose(state, state_exp) + + +def test_generate_pqc_operator(): + """Test generate pqc operator""" + @circuit_generator(2) + def encoder(qubits): + G.RY('a').__or__((qubits[0],)) + G.RY('b').__or__((qubits[1],)) + + @circuit_generator(2) + def ansatz(qubits): + G.X.__or__((qubits[0], qubits[1])) + G.RX('p1').__or__((qubits[0],)) + G.RX('p2').__or__((qubits[1],)) + + ham = mq.Hamiltonian(QubitOperator('Z1')) + encoder_names = ['a', 'b'] + ansatz_names = ['p1', 'p2'] + + ms.context.set_context(mode=ms.context.GRAPH_MODE, device_target="CPU") + + pqc = mq.nn.generate_pqc_operator(encoder_names, ansatz_names, + encoder + ansatz, ham) + encoder_data = ms.Tensor(np.array([[0.1, 0.2]]).astype(np.float32)) + ansatz_data = ms.Tensor(np.array([0.3, 0.4]).astype(np.float32)) + measure_result, encoder_grad, ansatz_grad = pqc(encoder_data, ansatz_data) + assert round(float(measure_result.asnumpy()[0, 0]), + 6) == round(float(0.89819133), 6) + assert round(float(encoder_grad.asnumpy()[0, 0, 0]), + 6) == round(float(-0.09011973), 6) + assert round(float(ansatz_grad.asnumpy()[0, 0, 1]), + 6) == round(float(-3.7974921e-01), 6) + + +def test_generate_evolution_operator(): + circ = Circuit(G.RX('a').on(0)) + data = ms.Tensor(np.array([0.5]).astype(np.float32)) + + evol = generate_evolution_operator(circ, ['a']) + state1 = evol(data) + + evol = generate_evolution_operator(circ) + state2 = evol(data) + + circ = circ.apply_value({'a': 0.5}) + evol = generate_evolution_operator(circ) + state3 = evol() + assert np.allclose(state1, G.RX(0.5).matrix()[:, 0]) + assert np.allclose(state2, G.RX(0.5).matrix()[:, 0]) + assert np.allclose(state3, G.RX(0.5).matrix()[:, 0]) + + +def test_mindquantum_ansatz_only_ops(): + circ = Circuit(G.RX('a').on(0)) + data = ms.Tensor(np.array([0.5]).astype(np.float32)) + ham = Hamiltonian(QubitOperator('Z0')) + evol = MindQuantumAnsatzOnlyOperator(circ.para_name, circ, ham) + output = evol(data) + assert np.allclose(output.asnumpy(), [[8.77582550e-01]]) + + +def test_mindquantum_ansatz_only_layer(): + circuit = Circuit( + [G.H.on(0), + G.RZ(0.4).on(0), + G.RX('a').on(0), + G.RY('b').on(0)]) + ham = Hamiltonian(QubitOperator('Z0')) + init = ms.Tensor(np.array([0, 0]).astype(np.float32)) + net = MindQuantumAnsatzOnlyLayer(circuit.para_name, circuit, ham, init) + opti = ms.nn.Adagrad(net.trainable_params(), learning_rate=0.8) + train_net = ms.nn.TrainOneStepCell(net, opti) + for i in range(1000): + train_net() + assert np.allclose(net().asnumpy(), [-1]) + + +def test_pqc_with_projector(): + circ = Circuit([G.RX('a').on(0), G.RX('b').on(0)]) + proj = Projector('0') + ansatz_data = np.array([0.1]).astype(np.float32) + encoder_data = np.array([[0]]).astype(np.float32) + net = MindQuantumLayer(['a'], ['b'], + circ, + proj, + weight_init=ms.Tensor(ansatz_data)) + assert np.allclose(net(ms.Tensor(encoder_data)).asnumpy(), [[0.99750208]]) diff --git a/tests/st/test_ops/test_fermion_ops.py b/tests/st/test_ops/test_fermion_ops.py new file mode 100644 index 000000000..2697b19c3 --- /dev/null +++ b/tests/st/test_ops/test_fermion_ops.py @@ -0,0 +1,112 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# wITHOUT wARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test fermion operator.""" + +from mindquantum.ops import FermionOperator + + +def test_fermion_ops_num_coeff(): + # check the creation operator + a_p_dagger = FermionOperator('1^') + assert str(a_p_dagger) == '1.0 [1^] ' + + # check the annihilation operator + a_q = FermionOperator('0') + assert str(a_q) == '1.0 [0] ' + + # check zero operator + zero = FermionOperator() + assert str(zero) == '0' + + # check identity operator + identity = FermionOperator('') + assert str(identity) == '1.0 [] ' + + +def test_power(): + # check power and multiply + w = (1 + 2j) * FermionOperator(' 4^ 3 9 3^ ') + 4 * FermionOperator(' 2 ') + w_2 = w * w + w_3 = w**2 + assert w_2 == w_3 + + +def test_normal_order(): + origin = FermionOperator('0 1^') + + normal_order = FermionOperator('1^ 0', -1) + + assert origin.normal_ordered() == normal_order + + +def test_multiplier(): + origin = FermionOperator('0 1^') + after_mul = FermionOperator('0 1^', 2) + assert after_mul == 2 * origin + + # Test in-place multiplier + origin *= 2 + assert after_mul == origin + + # Test right divide + new = origin / 2.0 + assert str(new) == '1.0 [0 1^] ' + + # Test in-place divide + origin /= 2 + assert str(origin) == '1.0 [0 1^] ' + + +def test_add_sub(): + # Test in place add + w1 = FermionOperator(' 4^ 3 9 3^ ') + 4 * FermionOperator(' 2 ') + w2 = 4 * FermionOperator(' 2 ') + w1 -= w2 + assert str(w1) == '1.0 [4^ 3 9 3^] ' + + +def test_compress(): + # test compress + w1 = FermionOperator('4^ 3') + FermionOperator('2', 1e-9) + w2 = FermionOperator('4^ 3') + assert w1.compress() == w2 + + a = FermionOperator('0 1^', 'x') + b = FermionOperator('1^ 0', 'x') + c = a + b + d = c.normal_ordered() + assert d.terms == {} + + +def test_constant(): + # test constant + w1 = FermionOperator('4^ 3 9 3^') + 6.0 * FermionOperator( + '2 3^') + 2.0 * FermionOperator('') + assert w1.constant == 2.0 + + +def test_para_operators(): + para_op = FermionOperator('0 1^', 'x') + assert str(para_op) == 'x [0 1^] ' + + # test the para with the value + para_dt = {'x': 2} + op = para_op.subs(para_dt) + assert str(op) == '2 [0 1^] ' + + +def test_eq(): + a = FermionOperator('0 1^', 'x') + assert a.subs({'x': 1}) == FermionOperator('0 1^') diff --git a/tests/st/test_ops/test_polynomial_tensor.py b/tests/st/test_ops/test_polynomial_tensor.py new file mode 100644 index 000000000..f22a2b56b --- /dev/null +++ b/tests/st/test_ops/test_polynomial_tensor.py @@ -0,0 +1,47 @@ +# Copyright (c) 2020 Huawei Technologies Co.,ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Test the polynomial_tensor in the ops module. +""" +import numpy as np + +from mindquantum.ops import PolynomialTensor + + +def test_polynomial_tensor(): + """"Test the polynomial_tensor class""" + constant = 1 + one_body_term = np.array([[1, 0], [0, 1]]) + two_body_term = np.array([[[[1, 0], [0, 1]], [[1, 0], [0, 1]]], + [[[1, 0], [0, 1]], [[1, 0], [0, 1]]]]) + n_body_tensors = { + (): 1, + (1, 0): one_body_term, + (1, 1, 0, 0): two_body_term + } + poly_op = PolynomialTensor(n_body_tensors) + + # test get function + assert poly_op.constant == 1 + + # test set function + poly_op.constant = 2 + assert poly_op.constant == 2 + + # test n_qubits + assert poly_op.n_qubits == 2 + + assert np.allclose(poly_op.one_body_tensor, one_body_term) + + assert np.allclose(poly_op.two_body_tensor, two_body_term) diff --git a/tests/st/test_ops/test_qubit_excitation_ops.py b/tests/st/test_ops/test_qubit_excitation_ops.py new file mode 100644 index 000000000..56d94b8b9 --- /dev/null +++ b/tests/st/test_ops/test_qubit_excitation_ops.py @@ -0,0 +1,134 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# wITHOUT wARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test qubit excitation operator.""" + +from mindquantum.ops import QubitExcitationOperator +from mindquantum.ops import QubitOperator, FermionOperator + + +def test_qubit_excitation_ops_num_coeff(): + # check the creation operator + a_p_dagger = QubitExcitationOperator('1^') + assert str(a_p_dagger) == '1.0 [Q1^] ' + + # check the annihilation operator + a_q = QubitExcitationOperator('0') + assert str(a_q) == '1.0 [Q0] ' + + # check zero operator + zero = QubitExcitationOperator() + assert str(zero) == '0' + + # check identity operator + identity = QubitExcitationOperator('') + assert str(identity) == '1.0 [] ' + + +def test_power(): + # check power and multiply + w = (1 + 2j) * QubitExcitationOperator(' 4^ 3 9 3^ ') + \ + 4 * QubitExcitationOperator(' 2 ') + w_2 = w * w + w_3 = w**2 + assert w_2 == w_3 + + +def test_normal_order(): + origin = QubitExcitationOperator('0 1^') + # Coefficient will not be affected for qubit-excitation operators + normal_order = QubitExcitationOperator('1^ 0', 1) + + assert origin.normal_ordered() == normal_order + + +def test_multiplier(): + origin = QubitExcitationOperator('0 1^') + after_mul = QubitExcitationOperator('0 1^', 2) + assert after_mul == 2 * origin + + # Test in-place multiplier + origin *= 2 + assert after_mul == origin + + # Test right divide + new = origin / 2.0 + assert str(new) == '1.0 [Q0 Q1^] ' + + # Test in-place divide + origin /= 2 + assert str(origin) == '1.0 [Q0 Q1^] ' + + +def test_add_sub(): + # Test in place add + w1 = QubitExcitationOperator(' 4^ 3 9 3^ ') + \ + 4 * QubitExcitationOperator(' 2 ') + w2 = 4 * QubitExcitationOperator(' 2 ') + w1 -= w2 + assert str(w1) == '1.0 [Q4^ Q3 Q9 Q3^] ' + + +def test_compress(): + # test compress + w1 = QubitExcitationOperator('4^ 3') + \ + QubitExcitationOperator('2', 1e-9) + w2 = QubitExcitationOperator('4^ 3') + assert w1.compress() == w2 + + a = QubitExcitationOperator('0 1^', 'x') + b = QubitExcitationOperator('1^ 0', 'x') + c = a + -b + d = c.normal_ordered() + assert d.terms == {} + + +def test_constant(): + # test constant + w1 = QubitExcitationOperator('4^ 3 9 3^') + 6.0 * QubitExcitationOperator( + '2 3^') + 2.0 * QubitExcitationOperator('') + assert w1.constant == 2.0 + + +def test_para_operators(): + para_op = QubitExcitationOperator('0 1^', 'x') + assert str(para_op) == 'x [Q0 Q1^] ' + + # test the para with the value + para_dt = {'x': 2} + op = para_op.subs(para_dt) + assert str(op) == '2 [Q0 Q1^] ' + + +def test_eq(): + a = QubitExcitationOperator('0 1^', 'x') + assert a.subs({'x': 1}) == QubitExcitationOperator('0 1^') + + +def test_convert_to_qubit_operator(): + # Check if the qubit excitation operator can correctly convert to + # the qubit operator correctly according to the definition. + op = QubitExcitationOperator(((4, 1), (1, 0)), 2.j) + qubit_op = QubitOperator("X1 X4", 0.5j) + QubitOperator("X1 Y4", 0.5) + \ + QubitOperator("Y1 X4", -0.5) + QubitOperator("Y1 Y4", 0.5j) + + assert op.to_qubit_operator().compress() == qubit_op + + +def test_fermion_op(): + # Test the "Fermion excitation version" of a qubit excitation operator + op = QubitExcitationOperator(((4, 1), (1, 0)), 2.j) + ferm_op = FermionOperator(((4, 1), (1, 0)), 2.j) + + assert op.fermion_operator == ferm_op diff --git a/tests/st/test_ops/test_qubit_ops.py b/tests/st/test_ops/test_qubit_ops.py new file mode 100644 index 000000000..a99c4897d --- /dev/null +++ b/tests/st/test_ops/test_qubit_ops.py @@ -0,0 +1,118 @@ +# Copyright (c) 2020 Huawei Technologies Co.,ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The test function for QubitOperator.""" + +from mindquantum.ops import QubitOperator + + +def test_qubit_ops_num_coeff(): + q1 = QubitOperator('Z1 Z2') + QubitOperator('X1') + assert str(q1) == '1.0 [X1] +\n1.0 [Z1 Z2] ' + q5 = QubitOperator('X1') * QubitOperator('Y1') + assert str(q5) == '1j [Z1] ' + q6 = QubitOperator('Y1') * QubitOperator('Z1') + assert str(q6) == '1j [X1] ' + q7 = QubitOperator('Z1') * QubitOperator('X1') + assert str(q7) == '1j [Y1] ' + + q8 = QubitOperator('Z1 z2') + q8 *= 2 + assert str(q8) == '2.0 [Z1 Z2] ' + + q9 = QubitOperator('Z1 z2') + q10 = QubitOperator('X1 X2') + q9 = q9 * q10 + assert str(q9) == '(-1+0j) [Y1 Y2] ' + + q9 = q9 * 2 + assert str(q9) == '(-2+0j) [Y1 Y2] ' + + q9 = 2 * q9 + assert str(q9) == '(-4+0j) [Y1 Y2] ' + + q9 = q9 / 2.0 + assert str(q9) == '(-2+0j) [Y1 Y2] ' + + q10 = QubitOperator('Z1 z2') + q10 *= q10 + assert str(q10) == '1.0 [] ' + + q11 = QubitOperator('Z1 z2') + 1e-9 * QubitOperator('X1 z2') + assert str(q11) == '1.0 [Z1 Z2] ' + + q12 = QubitOperator('Z1 Z2') + 1e-4 * QubitOperator('X1 Z2') + q13 = QubitOperator('Z3 X2') + 1e-5 * QubitOperator('X1 Y2') + q14 = q12 * q13 + assert str( + q14 + ) == '0.0001j [X1 Y2 Z3] +\n(1e-05-0j) [Y1 X2] +\n1j [Z1 Y2 Z3] +\n-1e-09j [X2] ' + assert str(q14.compress() + ) == '0.0001j [X1 Y2 Z3] +\n1e-05 [Y1 X2] +\n1j [Z1 Y2 Z3] ' + + iden = QubitOperator('') + assert str(iden) == '1.0 [] ' + + zero_op = QubitOperator() + assert str(zero_op) == '0' + + iden = -QubitOperator('') + assert str(iden) == '-1.0 [] ' + + ham = ((QubitOperator('X0 Y3', 0.5) + 0.6 * QubitOperator('X0 Y3'))) + assert str(ham) == '1.1 [X0 Y3] ' + + +def test_qubit_ops_symbol_coeff(): + q1 = QubitOperator('Z1 Z2', 'a') + QubitOperator('X1', 'b') + assert str(q1) == 'b [X1] +\na [Z1 Z2] ' + + q2 = QubitOperator('Z1 Z2', 'a') + 'a' * QubitOperator('Z2 Z1') + assert str(q2) == '2.0*a [Z1 Z2] ' + + q8 = QubitOperator('Z1 z2') + q8 *= 'a' + assert str(q8) == '1.0*a [Z1 Z2] ' + + q9 = QubitOperator('Z1 z2') + q10 = QubitOperator('X1 X2', 'a') + q9 = q9 * q10 + assert str(q9) == '-1.0*a [Y1 Y2] ' + + q9 = q9 / 2.0 + assert str(q9) == '-0.5*a [Y1 Y2] ' + + q12 = QubitOperator('Z1 Z2') + 1e-4 * QubitOperator('X1 Z2') + q13 = QubitOperator('Z3 X2') + 1e-5 * QubitOperator('X1 Y2', 'b') + q14 = q12 * q13 + assert str( + q14 + ) == '0.0001j [X1 Y2 Z3] +\n1.0e-5*b [Y1 X2] +\n1j [Z1 Y2 Z3] +\n-1.0e-9*I*b [X2] ' + assert str(q14.compress()) == str(q14) + + ham = ((QubitOperator('X0 Y3', 'a') + 'a' * QubitOperator('X0 Y3'))) + assert str(ham) == '2.0*a [X0 Y3] ' + assert ham == QubitOperator('X0 Y3', {'a': 2}) + + +def test_qubit_ops_subs(): + q = QubitOperator('X0', 'b') + QubitOperator('X0', 'a') + q = q.subs({'a': 1, 'b': 2}) + assert str(q) == '3 [X0] ' + + +def test_qubit_ops_sub(): + q1 = QubitOperator('X0') + q2 = QubitOperator('Y0') + q = QubitOperator('X0') + QubitOperator('Y0', -1) + assert str(q1 - q2) == '1.0 [X0] +\n-1.0 [Y0] ' diff --git a/tests/st/test_parameter_resolver/test_parameter_resolver.py b/tests/st/test_parameter_resolver/test_parameter_resolver.py new file mode 100644 index 000000000..7e4087d34 --- /dev/null +++ b/tests/st/test_parameter_resolver/test_parameter_resolver.py @@ -0,0 +1,51 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Test ParameterResolve.""" + +from mindquantum import ParameterResolver as PR + + +def test_parameter_resolve(): + """test parameter resolver.""" + pr = PR({'a': 1.0}) + pr['b'] = 2.0 + pr[['c', 'd']] = [3.0, 4.0] + pr *= 2 + pr = pr * 2 + pr = 1 * pr + pr_tmp = PR({'e': 5.0, 'f': 6.0}) + pr_tmp.no_grad() + pr.update(pr_tmp) + assert pr.para_name == ['a', 'b', 'c', 'd', 'e', 'f'] + assert pr.para_value == [4.0, 8.0, 12.0, 16.0, 5.0, 6.0] + pr.requires_grad_part('e') + pr.no_grad_part('b') + assert pr.requires_grad_parameters == {'a', 'c', 'd', 'e'} + assert pr.no_grad_parameters == {'b', 'f'} + pr.requires_grad() + assert not pr.no_grad_parameters + mindspore_data = pr.mindspore_data() + assert 'gate_params_names' in mindspore_data + assert 'gate_coeff' in mindspore_data + assert 'gate_requires_grad' in mindspore_data + assert sum(mindspore_data['gate_coeff']) == 51.0 + assert sum(mindspore_data['gate_requires_grad']) == 6 + assert ''.join(mindspore_data['gate_params_names']) == 'abcdef' + + +def test_parameter_resolve_combination(): + pr1 = PR({'a': 1}) + pr2 = PR({'a': 2, 'b': 3}) + assert pr1.combination(pr2) == 2 diff --git a/tests/st/test_utils/test_io.py b/tests/st/test_utils/test_io.py deleted file mode 100644 index c938a15e8..000000000 --- a/tests/st/test_utils/test_io.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""Test utils.""" - -from mindquantum.io import bprint - - -def test_beauty_print(): - """ - Description: Test beauty print - Expectation: xxx""" - o = bprint(['Age:17', 'Name:Bob'], title='Info of Bob') - o_exp = [ - '|===Info of Bob===|', '| Age :17 |', '| Name:Bob |', - '===================' - ] - assert o == o_exp diff --git a/tests/st/test_utils/test_utils.py b/tests/st/test_utils/test_utils.py index f8716ef4c..9bbfb1053 100755 --- a/tests/st/test_utils/test_utils.py +++ b/tests/st/test_utils/test_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,13 +15,21 @@ """Test utils.""" import numpy as np -from mindquantum.utils import f +from mindquantum.utils import bprint, f + + +def test_beauty_print(): + """Test beauty print""" + o = bprint(['Age:17', 'Name:Bob'], title='Info of Bob') + o_exp = [ + '|===Info of Bob===|', '| Age :17 |', '| Name:Bob |', + '===================' + ] + assert o == o_exp def test_mod(): - """ - Description: Test mod - Expectation: xxx""" + """Test mod""" mod_0 = f.mod([[1 + 1j, 0.5 + 0.5j], [0.5 + 1j, 0.5 + 0.5j]]) mod_1 = f.mod([[1 + 1j, 0.5 + 0.5j], [0.5 + 1j, 0.5 + 0.5j]], axis=1) assert round(np.real(mod_1[0, 0]), 5) == round(1.58113883, 5) @@ -30,17 +37,13 @@ def test_mod(): def test_normalize(): - """ - Description: Test normalize - Expectation: xxx""" + """Test normalize""" norm = np.real((f.normalize([[1 + 1j, 0.5 + 0.5j], [0.5 + 1j, 0.5 + 0.5j]]))[0, 0]) assert round(norm, 5) == round(0.5547002, 5) def test_random_state(): - """ - Description: Test random state - Expectation: xxx""" + """Test random state""" assert round(np.real(f.random_state((2, 4), seed=55)[0, 0]), 5) == round(0.16926417, 5) diff --git a/tests/st/test_utils/test_utils_operator.py b/tests/st/test_utils/test_utils_operator.py index 443be0e94..16ff4917e 100644 --- a/tests/st/test_utils/test_utils_operator.py +++ b/tests/st/test_utils/test_utils_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,19 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""Test operator_utils.""" +"""Test utils_operator.""" -from mindquantum.core.operators import QubitOperator, FermionOperator, QubitExcitationOperator -from mindquantum.core.operators.utils import (count_qubits, normal_ordered, - commutator, number_operator, - up_index, down_index, - hermitian_conjugated) +from mindquantum.ops import QubitOperator, FermionOperator, QubitExcitationOperator +from mindquantum.utils import (count_qubits, normal_ordered, commutator, + number_operator, up_index, down_index, + hermitian_conjugated) def test_count_qubits(): - """ - Description: Test count_qubits - Expectation: xxx""" + """Test count_qubits""" qubit_op = QubitOperator("X1 Y2") assert count_qubits(qubit_op) == 3 @@ -37,17 +33,13 @@ def test_count_qubits(): def test_normal_ordered(): - """ - Description: Test normal_ordered function - Expectation: xxx""" + """Test normal_ordered function""" op = FermionOperator("3 4^") assert str(normal_ordered(op)) == '-1.0 [4^ 3] ' def test_commutator(): - """ - Description: Test commutator - Expectation: xxx""" + """Test commutator""" qub_op1 = QubitOperator("X1 Y2") qub_op2 = QubitOperator("X1 Z2") qub_op3 = 2j * QubitOperator("X2") @@ -61,14 +53,11 @@ def test_commutator(): QubitExcitationOperator("4^ 1 3^ 2", -4.0) assert commutator(qubit_exc_op1, qubit_exc_op2).compress() == qubit_exc_op3 - assert commutator(qubit_exc_op1, - qubit_exc_op1) == QubitExcitationOperator() + assert commutator(qubit_exc_op1, qubit_exc_op1) == QubitExcitationOperator() def test_number_operator(): - """ - Description: Test number operator - Expectation: xxx""" + """Test number operator""" nmode = 3 # other parameters by default check_str = '1.0 [0^ 0] +\n1.0 [1^ 1] +\n1.0 [2^ 2] ' @@ -76,25 +65,19 @@ def test_number_operator(): def test_up_index(): - """ - Description: This is for labelling the spin-orbital index with spin alpha - Expectation: xxx""" + """This is for labelling the spin-orbital index with spin alpha""" alpha = 2 assert up_index(alpha) == 4 def test_down_index(): - """ - Description: This is for labelling the spin-orbital index with spin beta - Expectation: xxx""" + """This is for labelling the spin-orbital index with spin beta""" beta = 1 assert down_index(beta) == 3 def test_hermitian_conjugated(): - """ - Description: Test hermitian_conjugated for the QubitOperator and Fermion Operator - Expectation: xxx""" + """Test hermitian_conjugated for the QubitOperator and Fermion Operator""" qub_op1 = -1j * QubitOperator("X1 Y2") + QubitOperator("X1") qub_op2 = 1j * QubitOperator("X1 Y2") + QubitOperator("X1") @@ -104,8 +87,6 @@ def test_hermitian_conjugated(): fer_op2 = FermionOperator("2^ 1") assert hermitian_conjugated(fer_op1) == fer_op2 - qubit_exc_op1 = QubitExcitationOperator(((4, 1), (1, 0)), - 2.j).normal_ordered() - qubit_exc_op2 = QubitExcitationOperator(((4, 0), (1, 1)), - -2.j).normal_ordered() + qubit_exc_op1 = QubitExcitationOperator(((4, 1), (1, 0)), 2.j).normal_ordered() + qubit_exc_op2 = QubitExcitationOperator(((4, 0), (1, 1)), -2.j).normal_ordered() assert hermitian_conjugated(qubit_exc_op1) == qubit_exc_op2 diff --git a/tests/ut/__init__.py b/tests/ut/__init__.py index ed94b4aa0..6228b7132 100644 --- a/tests/ut/__init__.py +++ b/tests/ut/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/ut/runtest.sh b/tests/ut/runtest.sh index 3de026431..64f818740 100644 --- a/tests/ut/runtest.sh +++ b/tests/ut/runtest.sh @@ -26,4 +26,4 @@ run_test() { echo "Test all use cases success." } -run_test +run_test \ No newline at end of file diff --git a/tests/ut/test_mindquantum.py b/tests/ut/test_mindquantum.py index cd97c492b..b121da4de 100644 --- a/tests/ut/test_mindquantum.py +++ b/tests/ut/test_mindquantum.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/third_party/patch/projectq/projectq.patch001 b/third_party/patch/projectq/projectq.patch001 deleted file mode 100644 index 89c810c84..000000000 --- a/third_party/patch/projectq/projectq.patch001 +++ /dev/null @@ -1,969 +0,0 @@ -diff --color -aur ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp ---- ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp 2020-06-05 21:07:57.000000000 +0800 -+++ ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp 2021-04-19 18:04:05.541802882 +0800 -@@ -17,18 +17,18 @@ - { - __m256d v[2]; - -- v[0] = load2(&psi[I]); -- v[1] = load2(&psi[I + d0]); -+ v[0] = load2(psi + 2 * I); -+ v[1] = load2(psi + 2 * (I + d0)); - -- _mm256_storeu2_m128d((double*)&psi[I + d0], (double*)&psi[I], add(mul(v[0], m[0], mt[0]), mul(v[1], m[1], mt[1]))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0), psi + 2 * I, add(mul(v[0], m[0], mt[0]), mul(v[1], m[1], mt[1]))); - - } - - // bit indices id[.] are given from high to low (e.g. control first for CNOT) - template --void kernel(V &psi, unsigned id0, M const& m, std::size_t ctrlmask) -+void kernel(V &psi, unsigned id0, M const& m, std::size_t ctrlmask, unsigned len) - { -- std::size_t n = psi.size(); -+ std::size_t n = len; - std::size_t d0 = 1UL << id0; - - __m256d mm[] = {load(&m[0][0], &m[1][0]), load(&m[0][1], &m[1][1])}; -diff --color -aur ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp ---- ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp 2020-06-05 21:07:57.000000000 +0800 -+++ ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp 2021-04-19 18:04:05.541802882 +0800 -@@ -17,21 +17,21 @@ - { - __m256d v[4]; - -- v[0] = load2(&psi[I]); -- v[1] = load2(&psi[I + d0]); -- v[2] = load2(&psi[I + d1]); -- v[3] = load2(&psi[I + d0 + d1]); -+ v[0] = load2(psi + 2 * I); -+ v[1] = load2(psi + 2 * (I + d0)); -+ v[2] = load2(psi + 2 * (I + d1)); -+ v[3] = load2(psi + 2 * (I + d0 + d1)); - -- _mm256_storeu2_m128d((double*)&psi[I + d0], (double*)&psi[I], add(mul(v[0], m[0], mt[0]), add(mul(v[1], m[1], mt[1]), add(mul(v[2], m[2], mt[2]), mul(v[3], m[3], mt[3]))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1], (double*)&psi[I + d1], add(mul(v[0], m[4], mt[4]), add(mul(v[1], m[5], mt[5]), add(mul(v[2], m[6], mt[6]), mul(v[3], m[7], mt[7]))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0), psi + 2 * I, add(mul(v[0], m[0], mt[0]), add(mul(v[1], m[1], mt[1]), add(mul(v[2], m[2], mt[2]), mul(v[3], m[3], mt[3]))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1), psi + 2 * (I + d1), add(mul(v[0], m[4], mt[4]), add(mul(v[1], m[5], mt[5]), add(mul(v[2], m[6], mt[6]), mul(v[3], m[7], mt[7]))))); - - } - - // bit indices id[.] are given from high to low (e.g. control first for CNOT) - template --void kernel(V &psi, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask) -+void kernel(V &psi, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask, unsigned len) - { -- std::size_t n = psi.size(); -+ std::size_t n = len; - std::size_t d0 = 1UL << id0; - std::size_t d1 = 1UL << id1; - -diff --color -aur ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp ---- ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp 2020-06-05 21:07:57.000000000 +0800 -+++ ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp 2021-04-19 18:04:05.541802882 +0800 -@@ -17,10 +17,10 @@ - { - __m256d v[4]; - -- v[0] = load2(&psi[I]); -- v[1] = load2(&psi[I + d0]); -- v[2] = load2(&psi[I + d1]); -- v[3] = load2(&psi[I + d0 + d1]); -+ v[0] = load2(psi + 2 * I); -+ v[1] = load2(psi + 2 * (I + d0)); -+ v[2] = load2(psi + 2 * (I + d1)); -+ v[3] = load2(psi + 2 * (I + d0 + d1)); - - __m256d tmp[4]; - -@@ -29,23 +29,23 @@ - tmp[2] = add(mul(v[0], m[8], mt[8]), add(mul(v[1], m[9], mt[9]), add(mul(v[2], m[10], mt[10]), mul(v[3], m[11], mt[11])))); - tmp[3] = add(mul(v[0], m[12], mt[12]), add(mul(v[1], m[13], mt[13]), add(mul(v[2], m[14], mt[14]), mul(v[3], m[15], mt[15])))); - -- v[0] = load2(&psi[I + d2]); -- v[1] = load2(&psi[I + d0 + d2]); -- v[2] = load2(&psi[I + d1 + d2]); -- v[3] = load2(&psi[I + d0 + d1 + d2]); -- -- _mm256_storeu2_m128d((double*)&psi[I + d0], (double*)&psi[I], add(tmp[0], add(mul(v[0], m[16], mt[16]), add(mul(v[1], m[17], mt[17]), add(mul(v[2], m[18], mt[18]), mul(v[3], m[19], mt[19])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1], (double*)&psi[I + d1], add(tmp[1], add(mul(v[0], m[20], mt[20]), add(mul(v[1], m[21], mt[21]), add(mul(v[2], m[22], mt[22]), mul(v[3], m[23], mt[23])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d2], (double*)&psi[I + d2], add(tmp[2], add(mul(v[0], m[24], mt[24]), add(mul(v[1], m[25], mt[25]), add(mul(v[2], m[26], mt[26]), mul(v[3], m[27], mt[27])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d2], (double*)&psi[I + d1 + d2], add(tmp[3], add(mul(v[0], m[28], mt[28]), add(mul(v[1], m[29], mt[29]), add(mul(v[2], m[30], mt[30]), mul(v[3], m[31], mt[31])))))); -+ v[0] = load2(psi + 2 * (I + d2)); -+ v[1] = load2(psi + 2 * (I + d0 + d2)); -+ v[2] = load2(psi + 2 * (I + d1 + d2)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d2)); -+ -+ _mm256_storeu2_m128d(psi + 2 * (I + d0), psi + 2 * I, add(tmp[0], add(mul(v[0], m[16], mt[16]), add(mul(v[1], m[17], mt[17]), add(mul(v[2], m[18], mt[18]), mul(v[3], m[19], mt[19])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1), psi + 2 * (I + d1), add(tmp[1], add(mul(v[0], m[20], mt[20]), add(mul(v[1], m[21], mt[21]), add(mul(v[2], m[22], mt[22]), mul(v[3], m[23], mt[23])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d2), psi + 2 * (I + d2), add(tmp[2], add(mul(v[0], m[24], mt[24]), add(mul(v[1], m[25], mt[25]), add(mul(v[2], m[26], mt[26]), mul(v[3], m[27], mt[27])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d2), psi + 2 * (I + d1 + d2), add(tmp[3], add(mul(v[0], m[28], mt[28]), add(mul(v[1], m[29], mt[29]), add(mul(v[2], m[30], mt[30]), mul(v[3], m[31], mt[31])))))); - - } - - // bit indices id[.] are given from high to low (e.g. control first for CNOT) - template --void kernel(V &psi, unsigned id2, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask) -+void kernel(V &psi, unsigned id2, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask, unsigned len) - { -- std::size_t n = psi.size(); -+ std::size_t n = len; - std::size_t d0 = 1UL << id0; - std::size_t d1 = 1UL << id1; - std::size_t d2 = 1UL << id2; -diff --color -aur ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp ---- ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp 2020-06-05 21:07:57.000000000 +0800 -+++ ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp 2021-04-19 18:04:05.541802882 +0800 -@@ -17,10 +17,10 @@ - { - __m256d v[4]; - -- v[0] = load2(&psi[I]); -- v[1] = load2(&psi[I + d0]); -- v[2] = load2(&psi[I + d1]); -- v[3] = load2(&psi[I + d0 + d1]); -+ v[0] = load2(psi + 2 * I); -+ v[1] = load2(psi + 2 * (I + d0)); -+ v[2] = load2(psi + 2 * (I + d1)); -+ v[3] = load2(psi + 2 * (I + d0 + d1)); - - __m256d tmp[8]; - -@@ -33,10 +33,10 @@ - tmp[6] = add(mul(v[0], m[24], mt[24]), add(mul(v[1], m[25], mt[25]), add(mul(v[2], m[26], mt[26]), mul(v[3], m[27], mt[27])))); - tmp[7] = add(mul(v[0], m[28], mt[28]), add(mul(v[1], m[29], mt[29]), add(mul(v[2], m[30], mt[30]), mul(v[3], m[31], mt[31])))); - -- v[0] = load2(&psi[I + d2]); -- v[1] = load2(&psi[I + d0 + d2]); -- v[2] = load2(&psi[I + d1 + d2]); -- v[3] = load2(&psi[I + d0 + d1 + d2]); -+ v[0] = load2(psi + 2 * (I + d2)); -+ v[1] = load2(psi + 2 * (I + d0 + d2)); -+ v[2] = load2(psi + 2 * (I + d1 + d2)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d2)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[32], mt[32]), add(mul(v[1], m[33], mt[33]), add(mul(v[2], m[34], mt[34]), mul(v[3], m[35], mt[35]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[36], mt[36]), add(mul(v[1], m[37], mt[37]), add(mul(v[2], m[38], mt[38]), mul(v[3], m[39], mt[39]))))); -@@ -47,10 +47,10 @@ - tmp[6] = add(tmp[6], add(mul(v[0], m[56], mt[56]), add(mul(v[1], m[57], mt[57]), add(mul(v[2], m[58], mt[58]), mul(v[3], m[59], mt[59]))))); - tmp[7] = add(tmp[7], add(mul(v[0], m[60], mt[60]), add(mul(v[1], m[61], mt[61]), add(mul(v[2], m[62], mt[62]), mul(v[3], m[63], mt[63]))))); - -- v[0] = load2(&psi[I + d3]); -- v[1] = load2(&psi[I + d0 + d3]); -- v[2] = load2(&psi[I + d1 + d3]); -- v[3] = load2(&psi[I + d0 + d1 + d3]); -+ v[0] = load2(psi + 2 * (I + d3)); -+ v[1] = load2(psi + 2 * (I + d0 + d3)); -+ v[2] = load2(psi + 2 * (I + d1 + d3)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d3)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[64], mt[64]), add(mul(v[1], m[65], mt[65]), add(mul(v[2], m[66], mt[66]), mul(v[3], m[67], mt[67]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[68], mt[68]), add(mul(v[1], m[69], mt[69]), add(mul(v[2], m[70], mt[70]), mul(v[3], m[71], mt[71]))))); -@@ -61,27 +61,27 @@ - tmp[6] = add(tmp[6], add(mul(v[0], m[88], mt[88]), add(mul(v[1], m[89], mt[89]), add(mul(v[2], m[90], mt[90]), mul(v[3], m[91], mt[91]))))); - tmp[7] = add(tmp[7], add(mul(v[0], m[92], mt[92]), add(mul(v[1], m[93], mt[93]), add(mul(v[2], m[94], mt[94]), mul(v[3], m[95], mt[95]))))); - -- v[0] = load2(&psi[I + d2 + d3]); -- v[1] = load2(&psi[I + d0 + d2 + d3]); -- v[2] = load2(&psi[I + d1 + d2 + d3]); -- v[3] = load2(&psi[I + d0 + d1 + d2 + d3]); -- -- _mm256_storeu2_m128d((double*)&psi[I + d0], (double*)&psi[I], add(tmp[0], add(mul(v[0], m[96], mt[96]), add(mul(v[1], m[97], mt[97]), add(mul(v[2], m[98], mt[98]), mul(v[3], m[99], mt[99])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1], (double*)&psi[I + d1], add(tmp[1], add(mul(v[0], m[100], mt[100]), add(mul(v[1], m[101], mt[101]), add(mul(v[2], m[102], mt[102]), mul(v[3], m[103], mt[103])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d2], (double*)&psi[I + d2], add(tmp[2], add(mul(v[0], m[104], mt[104]), add(mul(v[1], m[105], mt[105]), add(mul(v[2], m[106], mt[106]), mul(v[3], m[107], mt[107])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d2], (double*)&psi[I + d1 + d2], add(tmp[3], add(mul(v[0], m[108], mt[108]), add(mul(v[1], m[109], mt[109]), add(mul(v[2], m[110], mt[110]), mul(v[3], m[111], mt[111])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d3], (double*)&psi[I + d3], add(tmp[4], add(mul(v[0], m[112], mt[112]), add(mul(v[1], m[113], mt[113]), add(mul(v[2], m[114], mt[114]), mul(v[3], m[115], mt[115])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d3], (double*)&psi[I + d1 + d3], add(tmp[5], add(mul(v[0], m[116], mt[116]), add(mul(v[1], m[117], mt[117]), add(mul(v[2], m[118], mt[118]), mul(v[3], m[119], mt[119])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d2 + d3], (double*)&psi[I + d2 + d3], add(tmp[6], add(mul(v[0], m[120], mt[120]), add(mul(v[1], m[121], mt[121]), add(mul(v[2], m[122], mt[122]), mul(v[3], m[123], mt[123])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d2 + d3], (double*)&psi[I + d1 + d2 + d3], add(tmp[7], add(mul(v[0], m[124], mt[124]), add(mul(v[1], m[125], mt[125]), add(mul(v[2], m[126], mt[126]), mul(v[3], m[127], mt[127])))))); -+ v[0] = load2(psi + 2 * (I + d2 + d3)); -+ v[1] = load2(psi + 2 * (I + d0 + d2 + d3)); -+ v[2] = load2(psi + 2 * (I + d1 + d2 + d3)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d2 + d3)); -+ -+ _mm256_storeu2_m128d(psi + 2 * (I + d0), psi + 2 * I, add(tmp[0], add(mul(v[0], m[96], mt[96]), add(mul(v[1], m[97], mt[97]), add(mul(v[2], m[98], mt[98]), mul(v[3], m[99], mt[99])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1), psi + 2 * (I + d1), add(tmp[1], add(mul(v[0], m[100], mt[100]), add(mul(v[1], m[101], mt[101]), add(mul(v[2], m[102], mt[102]), mul(v[3], m[103], mt[103])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d2), psi + 2 * (I + d2), add(tmp[2], add(mul(v[0], m[104], mt[104]), add(mul(v[1], m[105], mt[105]), add(mul(v[2], m[106], mt[106]), mul(v[3], m[107], mt[107])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d2), psi + 2 * (I + d1 + d2), add(tmp[3], add(mul(v[0], m[108], mt[108]), add(mul(v[1], m[109], mt[109]), add(mul(v[2], m[110], mt[110]), mul(v[3], m[111], mt[111])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d3), psi + 2 * (I + d3), add(tmp[4], add(mul(v[0], m[112], mt[112]), add(mul(v[1], m[113], mt[113]), add(mul(v[2], m[114], mt[114]), mul(v[3], m[115], mt[115])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d3), psi + 2 * (I + d1 + d3), add(tmp[5], add(mul(v[0], m[116], mt[116]), add(mul(v[1], m[117], mt[117]), add(mul(v[2], m[118], mt[118]), mul(v[3], m[119], mt[119])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d2 + d3), psi + 2 * (I + d2 + d3), add(tmp[6], add(mul(v[0], m[120], mt[120]), add(mul(v[1], m[121], mt[121]), add(mul(v[2], m[122], mt[122]), mul(v[3], m[123], mt[123])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d2 + d3), psi + 2 * (I + d1 + d2 + d3), add(tmp[7], add(mul(v[0], m[124], mt[124]), add(mul(v[1], m[125], mt[125]), add(mul(v[2], m[126], mt[126]), mul(v[3], m[127], mt[127])))))); - - } - - // bit indices id[.] are given from high to low (e.g. control first for CNOT) - template --void kernel(V &psi, unsigned id3, unsigned id2, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask) -+void kernel(V &psi, unsigned id3, unsigned id2, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask, unsigned len) - { -- std::size_t n = psi.size(); -+ std::size_t n = len; - std::size_t d0 = 1UL << id0; - std::size_t d1 = 1UL << id1; - std::size_t d2 = 1UL << id2; -diff --color -aur ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp ---- ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp 2020-06-05 21:07:57.000000000 +0800 -+++ ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp 2021-04-19 18:04:05.541802882 +0800 -@@ -17,10 +17,10 @@ - { - __m256d v[4]; - -- v[0] = load2(&psi[I]); -- v[1] = load2(&psi[I + d0]); -- v[2] = load2(&psi[I + d1]); -- v[3] = load2(&psi[I + d0 + d1]); -+ v[0] = load2(psi + 2 * I); -+ v[1] = load2(psi + 2 * (I + d0)); -+ v[2] = load2(psi + 2 * (I + d1)); -+ v[3] = load2(psi + 2 * (I + d0 + d1)); - - __m256d tmp[16]; - -@@ -41,10 +41,10 @@ - tmp[14] = add(mul(v[0], m[56], mt[56]), add(mul(v[1], m[57], mt[57]), add(mul(v[2], m[58], mt[58]), mul(v[3], m[59], mt[59])))); - tmp[15] = add(mul(v[0], m[60], mt[60]), add(mul(v[1], m[61], mt[61]), add(mul(v[2], m[62], mt[62]), mul(v[3], m[63], mt[63])))); - -- v[0] = load2(&psi[I + d2]); -- v[1] = load2(&psi[I + d0 + d2]); -- v[2] = load2(&psi[I + d1 + d2]); -- v[3] = load2(&psi[I + d0 + d1 + d2]); -+ v[0] = load2(psi + 2 * (I + d2)); -+ v[1] = load2(psi + 2 * (I + d0 + d2)); -+ v[2] = load2(psi + 2 * (I + d1 + d2)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d2)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[64], mt[64]), add(mul(v[1], m[65], mt[65]), add(mul(v[2], m[66], mt[66]), mul(v[3], m[67], mt[67]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[68], mt[68]), add(mul(v[1], m[69], mt[69]), add(mul(v[2], m[70], mt[70]), mul(v[3], m[71], mt[71]))))); -@@ -63,10 +63,10 @@ - tmp[14] = add(tmp[14], add(mul(v[0], m[120], mt[120]), add(mul(v[1], m[121], mt[121]), add(mul(v[2], m[122], mt[122]), mul(v[3], m[123], mt[123]))))); - tmp[15] = add(tmp[15], add(mul(v[0], m[124], mt[124]), add(mul(v[1], m[125], mt[125]), add(mul(v[2], m[126], mt[126]), mul(v[3], m[127], mt[127]))))); - -- v[0] = load2(&psi[I + d3]); -- v[1] = load2(&psi[I + d0 + d3]); -- v[2] = load2(&psi[I + d1 + d3]); -- v[3] = load2(&psi[I + d0 + d1 + d3]); -+ v[0] = load2(psi + 2 * (I + d3)); -+ v[1] = load2(psi + 2 * (I + d0 + d3)); -+ v[2] = load2(psi + 2 * (I + d1 + d3)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d3)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[128], mt[128]), add(mul(v[1], m[129], mt[129]), add(mul(v[2], m[130], mt[130]), mul(v[3], m[131], mt[131]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[132], mt[132]), add(mul(v[1], m[133], mt[133]), add(mul(v[2], m[134], mt[134]), mul(v[3], m[135], mt[135]))))); -@@ -85,10 +85,10 @@ - tmp[14] = add(tmp[14], add(mul(v[0], m[184], mt[184]), add(mul(v[1], m[185], mt[185]), add(mul(v[2], m[186], mt[186]), mul(v[3], m[187], mt[187]))))); - tmp[15] = add(tmp[15], add(mul(v[0], m[188], mt[188]), add(mul(v[1], m[189], mt[189]), add(mul(v[2], m[190], mt[190]), mul(v[3], m[191], mt[191]))))); - -- v[0] = load2(&psi[I + d2 + d3]); -- v[1] = load2(&psi[I + d0 + d2 + d3]); -- v[2] = load2(&psi[I + d1 + d2 + d3]); -- v[3] = load2(&psi[I + d0 + d1 + d2 + d3]); -+ v[0] = load2(psi + 2 * (I + d2 + d3)); -+ v[1] = load2(psi + 2 * (I + d0 + d2 + d3)); -+ v[2] = load2(psi + 2 * (I + d1 + d2 + d3)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d2 + d3)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[192], mt[192]), add(mul(v[1], m[193], mt[193]), add(mul(v[2], m[194], mt[194]), mul(v[3], m[195], mt[195]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[196], mt[196]), add(mul(v[1], m[197], mt[197]), add(mul(v[2], m[198], mt[198]), mul(v[3], m[199], mt[199]))))); -@@ -107,10 +107,10 @@ - tmp[14] = add(tmp[14], add(mul(v[0], m[248], mt[248]), add(mul(v[1], m[249], mt[249]), add(mul(v[2], m[250], mt[250]), mul(v[3], m[251], mt[251]))))); - tmp[15] = add(tmp[15], add(mul(v[0], m[252], mt[252]), add(mul(v[1], m[253], mt[253]), add(mul(v[2], m[254], mt[254]), mul(v[3], m[255], mt[255]))))); - -- v[0] = load2(&psi[I + d4]); -- v[1] = load2(&psi[I + d0 + d4]); -- v[2] = load2(&psi[I + d1 + d4]); -- v[3] = load2(&psi[I + d0 + d1 + d4]); -+ v[0] = load2(psi + 2 * (I + d4)); -+ v[1] = load2(psi + 2 * (I + d0 + d4)); -+ v[2] = load2(psi + 2 * (I + d1 + d4)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d4)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[256], mt[256]), add(mul(v[1], m[257], mt[257]), add(mul(v[2], m[258], mt[258]), mul(v[3], m[259], mt[259]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[260], mt[260]), add(mul(v[1], m[261], mt[261]), add(mul(v[2], m[262], mt[262]), mul(v[3], m[263], mt[263]))))); -@@ -129,10 +129,10 @@ - tmp[14] = add(tmp[14], add(mul(v[0], m[312], mt[312]), add(mul(v[1], m[313], mt[313]), add(mul(v[2], m[314], mt[314]), mul(v[3], m[315], mt[315]))))); - tmp[15] = add(tmp[15], add(mul(v[0], m[316], mt[316]), add(mul(v[1], m[317], mt[317]), add(mul(v[2], m[318], mt[318]), mul(v[3], m[319], mt[319]))))); - -- v[0] = load2(&psi[I + d2 + d4]); -- v[1] = load2(&psi[I + d0 + d2 + d4]); -- v[2] = load2(&psi[I + d1 + d2 + d4]); -- v[3] = load2(&psi[I + d0 + d1 + d2 + d4]); -+ v[0] = load2(psi + 2 * (I + d2 + d4)); -+ v[1] = load2(psi + 2 * (I + d0 + d2 + d4)); -+ v[2] = load2(psi + 2 * (I + d1 + d2 + d4)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d2 + d4)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[320], mt[320]), add(mul(v[1], m[321], mt[321]), add(mul(v[2], m[322], mt[322]), mul(v[3], m[323], mt[323]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[324], mt[324]), add(mul(v[1], m[325], mt[325]), add(mul(v[2], m[326], mt[326]), mul(v[3], m[327], mt[327]))))); -@@ -151,10 +151,10 @@ - tmp[14] = add(tmp[14], add(mul(v[0], m[376], mt[376]), add(mul(v[1], m[377], mt[377]), add(mul(v[2], m[378], mt[378]), mul(v[3], m[379], mt[379]))))); - tmp[15] = add(tmp[15], add(mul(v[0], m[380], mt[380]), add(mul(v[1], m[381], mt[381]), add(mul(v[2], m[382], mt[382]), mul(v[3], m[383], mt[383]))))); - -- v[0] = load2(&psi[I + d3 + d4]); -- v[1] = load2(&psi[I + d0 + d3 + d4]); -- v[2] = load2(&psi[I + d1 + d3 + d4]); -- v[3] = load2(&psi[I + d0 + d1 + d3 + d4]); -+ v[0] = load2(psi + 2 * (I + d3 + d4)); -+ v[1] = load2(psi + 2 * (I + d0 + d3 + d4)); -+ v[2] = load2(psi + 2 * (I + d1 + d3 + d4)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d3 + d4)); - - tmp[0] = add(tmp[0], add(mul(v[0], m[384], mt[384]), add(mul(v[1], m[385], mt[385]), add(mul(v[2], m[386], mt[386]), mul(v[3], m[387], mt[387]))))); - tmp[1] = add(tmp[1], add(mul(v[0], m[388], mt[388]), add(mul(v[1], m[389], mt[389]), add(mul(v[2], m[390], mt[390]), mul(v[3], m[391], mt[391]))))); -@@ -173,35 +173,35 @@ - tmp[14] = add(tmp[14], add(mul(v[0], m[440], mt[440]), add(mul(v[1], m[441], mt[441]), add(mul(v[2], m[442], mt[442]), mul(v[3], m[443], mt[443]))))); - tmp[15] = add(tmp[15], add(mul(v[0], m[444], mt[444]), add(mul(v[1], m[445], mt[445]), add(mul(v[2], m[446], mt[446]), mul(v[3], m[447], mt[447]))))); - -- v[0] = load2(&psi[I + d2 + d3 + d4]); -- v[1] = load2(&psi[I + d0 + d2 + d3 + d4]); -- v[2] = load2(&psi[I + d1 + d2 + d3 + d4]); -- v[3] = load2(&psi[I + d0 + d1 + d2 + d3 + d4]); -- -- _mm256_storeu2_m128d((double*)&psi[I + d0], (double*)&psi[I], add(tmp[0], add(mul(v[0], m[448], mt[448]), add(mul(v[1], m[449], mt[449]), add(mul(v[2], m[450], mt[450]), mul(v[3], m[451], mt[451])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1], (double*)&psi[I + d1], add(tmp[1], add(mul(v[0], m[452], mt[452]), add(mul(v[1], m[453], mt[453]), add(mul(v[2], m[454], mt[454]), mul(v[3], m[455], mt[455])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d2], (double*)&psi[I + d2], add(tmp[2], add(mul(v[0], m[456], mt[456]), add(mul(v[1], m[457], mt[457]), add(mul(v[2], m[458], mt[458]), mul(v[3], m[459], mt[459])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d2], (double*)&psi[I + d1 + d2], add(tmp[3], add(mul(v[0], m[460], mt[460]), add(mul(v[1], m[461], mt[461]), add(mul(v[2], m[462], mt[462]), mul(v[3], m[463], mt[463])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d3], (double*)&psi[I + d3], add(tmp[4], add(mul(v[0], m[464], mt[464]), add(mul(v[1], m[465], mt[465]), add(mul(v[2], m[466], mt[466]), mul(v[3], m[467], mt[467])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d3], (double*)&psi[I + d1 + d3], add(tmp[5], add(mul(v[0], m[468], mt[468]), add(mul(v[1], m[469], mt[469]), add(mul(v[2], m[470], mt[470]), mul(v[3], m[471], mt[471])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d2 + d3], (double*)&psi[I + d2 + d3], add(tmp[6], add(mul(v[0], m[472], mt[472]), add(mul(v[1], m[473], mt[473]), add(mul(v[2], m[474], mt[474]), mul(v[3], m[475], mt[475])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d2 + d3], (double*)&psi[I + d1 + d2 + d3], add(tmp[7], add(mul(v[0], m[476], mt[476]), add(mul(v[1], m[477], mt[477]), add(mul(v[2], m[478], mt[478]), mul(v[3], m[479], mt[479])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d4], (double*)&psi[I + d4], add(tmp[8], add(mul(v[0], m[480], mt[480]), add(mul(v[1], m[481], mt[481]), add(mul(v[2], m[482], mt[482]), mul(v[3], m[483], mt[483])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d4], (double*)&psi[I + d1 + d4], add(tmp[9], add(mul(v[0], m[484], mt[484]), add(mul(v[1], m[485], mt[485]), add(mul(v[2], m[486], mt[486]), mul(v[3], m[487], mt[487])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d2 + d4], (double*)&psi[I + d2 + d4], add(tmp[10], add(mul(v[0], m[488], mt[488]), add(mul(v[1], m[489], mt[489]), add(mul(v[2], m[490], mt[490]), mul(v[3], m[491], mt[491])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d2 + d4], (double*)&psi[I + d1 + d2 + d4], add(tmp[11], add(mul(v[0], m[492], mt[492]), add(mul(v[1], m[493], mt[493]), add(mul(v[2], m[494], mt[494]), mul(v[3], m[495], mt[495])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d3 + d4], (double*)&psi[I + d3 + d4], add(tmp[12], add(mul(v[0], m[496], mt[496]), add(mul(v[1], m[497], mt[497]), add(mul(v[2], m[498], mt[498]), mul(v[3], m[499], mt[499])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d3 + d4], (double*)&psi[I + d1 + d3 + d4], add(tmp[13], add(mul(v[0], m[500], mt[500]), add(mul(v[1], m[501], mt[501]), add(mul(v[2], m[502], mt[502]), mul(v[3], m[503], mt[503])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d2 + d3 + d4], (double*)&psi[I + d2 + d3 + d4], add(tmp[14], add(mul(v[0], m[504], mt[504]), add(mul(v[1], m[505], mt[505]), add(mul(v[2], m[506], mt[506]), mul(v[3], m[507], mt[507])))))); -- _mm256_storeu2_m128d((double*)&psi[I + d0 + d1 + d2 + d3 + d4], (double*)&psi[I + d1 + d2 + d3 + d4], add(tmp[15], add(mul(v[0], m[508], mt[508]), add(mul(v[1], m[509], mt[509]), add(mul(v[2], m[510], mt[510]), mul(v[3], m[511], mt[511])))))); -+ v[0] = load2(psi + 2 * (I + d2 + d3 + d4)); -+ v[1] = load2(psi + 2 * (I + d0 + d2 + d3 + d4)); -+ v[2] = load2(psi + 2 * (I + d1 + d2 + d3 + d4)); -+ v[3] = load2(psi + 2 * (I + d0 + d1 + d2 + d3 + d4)); -+ -+ _mm256_storeu2_m128d(psi + 2 * (I + d0), psi + 2 * I, add(tmp[0], add(mul(v[0], m[448], mt[448]), add(mul(v[1], m[449], mt[449]), add(mul(v[2], m[450], mt[450]), mul(v[3], m[451], mt[451])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1), psi + 2 * (I + d1), add(tmp[1], add(mul(v[0], m[452], mt[452]), add(mul(v[1], m[453], mt[453]), add(mul(v[2], m[454], mt[454]), mul(v[3], m[455], mt[455])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d2), psi + 2 * (I + d2), add(tmp[2], add(mul(v[0], m[456], mt[456]), add(mul(v[1], m[457], mt[457]), add(mul(v[2], m[458], mt[458]), mul(v[3], m[459], mt[459])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d2), psi + 2 * (I + d1 + d2), add(tmp[3], add(mul(v[0], m[460], mt[460]), add(mul(v[1], m[461], mt[461]), add(mul(v[2], m[462], mt[462]), mul(v[3], m[463], mt[463])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d3), psi + 2 * (I + d3), add(tmp[4], add(mul(v[0], m[464], mt[464]), add(mul(v[1], m[465], mt[465]), add(mul(v[2], m[466], mt[466]), mul(v[3], m[467], mt[467])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d3), psi + 2 * (I + d1 + d3), add(tmp[5], add(mul(v[0], m[468], mt[468]), add(mul(v[1], m[469], mt[469]), add(mul(v[2], m[470], mt[470]), mul(v[3], m[471], mt[471])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d2 + d3), psi + 2 * (I + d2 + d3), add(tmp[6], add(mul(v[0], m[472], mt[472]), add(mul(v[1], m[473], mt[473]), add(mul(v[2], m[474], mt[474]), mul(v[3], m[475], mt[475])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d2 + d3), psi + 2 * (I + d1 + d2 + d3), add(tmp[7], add(mul(v[0], m[476], mt[476]), add(mul(v[1], m[477], mt[477]), add(mul(v[2], m[478], mt[478]), mul(v[3], m[479], mt[479])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d4), psi + 2 * (I + d4), add(tmp[8], add(mul(v[0], m[480], mt[480]), add(mul(v[1], m[481], mt[481]), add(mul(v[2], m[482], mt[482]), mul(v[3], m[483], mt[483])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d4), psi + 2 * (I + d1 + d4), add(tmp[9], add(mul(v[0], m[484], mt[484]), add(mul(v[1], m[485], mt[485]), add(mul(v[2], m[486], mt[486]), mul(v[3], m[487], mt[487])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d2 + d4), psi + 2 * (I + d2 + d4), add(tmp[10], add(mul(v[0], m[488], mt[488]), add(mul(v[1], m[489], mt[489]), add(mul(v[2], m[490], mt[490]), mul(v[3], m[491], mt[491])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d2 + d4), psi + 2 * (I + d1 + d2 + d4), add(tmp[11], add(mul(v[0], m[492], mt[492]), add(mul(v[1], m[493], mt[493]), add(mul(v[2], m[494], mt[494]), mul(v[3], m[495], mt[495])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d3 + d4), psi + 2 * (I + d3 + d4), add(tmp[12], add(mul(v[0], m[496], mt[496]), add(mul(v[1], m[497], mt[497]), add(mul(v[2], m[498], mt[498]), mul(v[3], m[499], mt[499])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d3 + d4), psi + 2 * (I + d1 + d3 + d4), add(tmp[13], add(mul(v[0], m[500], mt[500]), add(mul(v[1], m[501], mt[501]), add(mul(v[2], m[502], mt[502]), mul(v[3], m[503], mt[503])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d2 + d3 + d4), psi + 2 * (I + d2 + d3 + d4), add(tmp[14], add(mul(v[0], m[504], mt[504]), add(mul(v[1], m[505], mt[505]), add(mul(v[2], m[506], mt[506]), mul(v[3], m[507], mt[507])))))); -+ _mm256_storeu2_m128d(psi + 2 * (I + d0 + d1 + d2 + d3 + d4), psi + 2 * (I + d1 + d2 + d3 + d4), add(tmp[15], add(mul(v[0], m[508], mt[508]), add(mul(v[1], m[509], mt[509]), add(mul(v[2], m[510], mt[510]), mul(v[3], m[511], mt[511])))))); - - } - - // bit indices id[.] are given from high to low (e.g. control first for CNOT) - template --void kernel(V &psi, unsigned id4, unsigned id3, unsigned id2, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask) -+void kernel(V &psi, unsigned id4, unsigned id3, unsigned id2, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask, unsigned len) - { -- std::size_t n = psi.size(); -+ std::size_t n = len; - std::size_t d0 = 1UL << id0; - std::size_t d1 = 1UL << id1; - std::size_t d2 = 1UL << id2; -diff --color -aur ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/simulator.hpp ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/simulator.hpp ---- ProjectQ-0.5.1/projectq/backends/_sim/_cppkernels/simulator.hpp 2020-06-05 21:07:57.000000000 +0800 -+++ ProjectQ-0.5.1_new/projectq/backends/_sim/_cppkernels/simulator.hpp 2021-04-20 11:46:27.115554725 +0800 -@@ -18,11 +18,7 @@ - #include - #include - --#if defined(NOINTRIN) || !defined(INTRIN) --#include "nointrin/kernels.hpp" --#else - #include "intrin/kernels.hpp" --#endif - - #include "intrin/alignedallocator.hpp" - #include "fusion.hpp" -@@ -32,173 +28,29 @@ - #include - #include - #include -- -+#include - - class Simulator{ - public: - using calc_type = double; - using complex_type = std::complex; -- using StateVector = std::vector>; -+ using StateVector = calc_type *; - using Map = std::map; - using RndEngine = std::mt19937; - using Term = std::vector>; - using TermsDict = std::vector>; - using ComplexTermsDict = std::vector>; -+ StateVector vec_; - -- Simulator(unsigned seed = 1) : N_(0), vec_(1,0.), fusion_qubits_min_(4), -+ Simulator(unsigned seed = 1, unsigned N = 0) : N_(N), fusion_qubits_min_(4), - fusion_qubits_max_(5), rnd_eng_(seed) { -+ len_ = 1UL << (N_ + 1); -+ vec_ = (StateVector)calloc(len_, sizeof(calc_type)); - vec_[0]=1.; // all-zero initial state - std::uniform_real_distribution dist(0., 1.); - rng_ = std::bind(dist, std::ref(rnd_eng_)); -- } -- -- void allocate_qubit(unsigned id){ -- if (map_.count(id) == 0){ -- map_[id] = N_++; -- StateVector newvec; // avoid large memory allocations -- if( tmpBuff1_.capacity() >= (1UL << N_) ) -- std::swap(newvec, tmpBuff1_); -- newvec.resize(1UL << N_); --#pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < newvec.size(); ++i) -- newvec[i] = (i < vec_.size())?vec_[i]:0.; -- std::swap(vec_, newvec); -- // recycle large memory -- std::swap(tmpBuff1_, newvec); -- if( tmpBuff1_.capacity() < tmpBuff2_.capacity() ) -- std::swap(tmpBuff1_, tmpBuff2_); -- } -- else -- throw(std::runtime_error( -- "AllocateQubit: ID already exists. Qubit IDs should be unique.")); -- } -- -- bool get_classical_value(unsigned id, calc_type tol = 1.e-12){ -- run(); -- unsigned pos = map_[id]; -- std::size_t delta = (1UL << pos); -- -- for (std::size_t i = 0; i < vec_.size(); i += 2*delta){ -- for (std::size_t j = 0; j < delta; ++j){ -- if (std::norm(vec_[i+j]) > tol) -- return false; -- if (std::norm(vec_[i+j+delta]) > tol) -- return true; -- } -- } -- assert(false); // this will never happen -- return false; // suppress 'control reaches end of non-void...' -- } -- -- bool is_classical(unsigned id, calc_type tol = 1.e-12){ -- run(); -- unsigned pos = map_[id]; -- std::size_t delta = (1UL << pos); -- -- short up = 0, down = 0; -- #pragma omp parallel for schedule(static) reduction(|:up,down) -- for (std::size_t i = 0; i < vec_.size(); i += 2*delta){ -- for (std::size_t j = 0; j < delta; ++j){ -- up = up | ((std::norm(vec_[i+j]) > tol)&1); -- down = down | ((std::norm(vec_[i+j+delta]) > tol)&1); -- } -- } -- -- return 1 == (up^down); -- } -- -- void collapse_vector(unsigned id, bool value = false, bool shrink = false){ -- run(); -- unsigned pos = map_[id]; -- std::size_t delta = (1UL << pos); -- -- if (!shrink){ -- #pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); i += 2*delta){ -- for (std::size_t j = 0; j < delta; ++j) -- vec_[i+j+static_cast(!value)*delta] = 0.; -- } -- } -- else{ -- StateVector newvec; // avoid costly memory reallocations -- if( tmpBuff1_.capacity() >= (1UL << (N_-1)) ) -- std::swap(tmpBuff1_, newvec); -- newvec.resize((1UL << (N_-1))); -- #pragma omp parallel for schedule(static) if(0) -- for (std::size_t i = 0; i < vec_.size(); i += 2*delta) -- std::copy_n(&vec_[i + static_cast(value)*delta], -- delta, &newvec[i/2]); -- std::swap(vec_, newvec); -- std::swap(tmpBuff1_, newvec); -- if( tmpBuff1_.capacity() < tmpBuff2_.capacity() ) -- std::swap(tmpBuff1_, tmpBuff2_); -- -- for (auto& p : map_){ -- if (p.second > pos) -- p.second--; -- } -- map_.erase(id); -- N_--; -- } -- } -- -- void measure_qubits(std::vector const& ids, std::vector &res){ -- run(); -- -- std::vector positions(ids.size()); -- for (unsigned i = 0; i < ids.size(); ++i) -- positions[i] = map_[ids[i]]; -- -- calc_type P = 0.; -- calc_type rnd = rng_(); -- -- // pick entry at random with probability |entry|^2 -- std::size_t pick = 0; -- while (P < rnd && pick < vec_.size()) -- P += std::norm(vec_[pick++]); -- -- pick--; -- // determine result vector (boolean values for each qubit) -- // and create mask to detect bad entries (i.e., entries that don't agree with measurement) -- res = std::vector(ids.size()); -- std::size_t mask = 0; -- std::size_t val = 0; -- for (unsigned i = 0; i < ids.size(); ++i){ -- bool r = ((pick >> positions[i]) & 1) == 1; -- res[i] = r; -- mask |= (1UL << positions[i]); -- val |= (static_cast(r&1) << positions[i]); -- } -- // set bad entries to 0 -- calc_type N = 0.; -- #pragma omp parallel for reduction(+:N) schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i){ -- if ((i & mask) != val) -- vec_[i] = 0.; -- else -- N += std::norm(vec_[i]); -- } -- // re-normalize -- N = 1./std::sqrt(N); -- #pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i) -- vec_[i] *= N; -- } -- -- std::vector measure_qubits_return(std::vector const& ids){ -- std::vector ret; -- measure_qubits(ids, ret); -- return ret; -- } -- -- void deallocate_qubit(unsigned id){ -- run(); -- assert(map_.count(id) == 1); -- if (!is_classical(id)) -- throw(std::runtime_error("Error: Qubit has not been measured / uncomputed! There is most likely a bug in your code.")); -- -- bool value = get_classical_value(id); -- collapse_vector(id, value, true); -+ for (unsigned i = 0; i < N_; i++) -+ map_[i] = i; - } - - template -@@ -221,84 +73,13 @@ - fused_gates_ = fused_gates; - } - -- template -- void emulate_math(F const& f, QuReg quregs, const std::vector& ctrl, -- bool parallelize = false){ -- run(); -- auto ctrlmask = get_control_mask(ctrl); -- -- for (unsigned i = 0; i < quregs.size(); ++i) -- for (unsigned j = 0; j < quregs[i].size(); ++j) -- quregs[i][j] = map_[quregs[i][j]]; -- -- StateVector newvec; // avoid costly memory reallocations -- if( tmpBuff1_.capacity() >= vec_.size() ) -- std::swap(newvec, tmpBuff1_); -- newvec.resize(vec_.size()); --#pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); i++) -- newvec[i] = 0; -- --//#pragma omp parallel reduction(+:newvec[:newvec.size()]) if(parallelize) // requires OpenMP 4.5 -- { -- std::vector res(quregs.size()); -- //#pragma omp for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i){ -- if ((ctrlmask&i) == ctrlmask){ -- for (unsigned qr_i = 0; qr_i < quregs.size(); ++qr_i){ -- res[qr_i] = 0; -- for (unsigned qb_i = 0; qb_i < quregs[qr_i].size(); ++qb_i) -- res[qr_i] |= ((i >> quregs[qr_i][qb_i])&1) << qb_i; -- } -- f(res); -- auto new_i = i; -- for (unsigned qr_i = 0; qr_i < quregs.size(); ++qr_i){ -- for (unsigned qb_i = 0; qb_i < quregs[qr_i].size(); ++qb_i){ -- if (!(((new_i >> quregs[qr_i][qb_i])&1) == ((res[qr_i] >> qb_i)&1))) -- new_i ^= (1UL << quregs[qr_i][qb_i]); -- } -- } -- newvec[new_i] += vec_[i]; -- } -- else -- newvec[i] += vec_[i]; -- } -- } -- std::swap(vec_, newvec); -- std::swap(tmpBuff1_, newvec); -- } -- -- // faster version without calling python -- template -- inline void emulate_math_addConstant(int a, const QuReg& quregs, const std::vector& ctrl) -- { -- emulate_math([a](std::vector &res){for(auto& x: res) x = x + a;}, quregs, ctrl, true); -- } -- -- // faster version without calling python -- template -- inline void emulate_math_addConstantModN(int a, int N, const QuReg& quregs, const std::vector& ctrl) -- { -- emulate_math([a,N](std::vector &res){for(auto& x: res) x = (x + a) % N;}, quregs, ctrl, true); -- } -- -- // faster version without calling python -- template -- inline void emulate_math_multiplyByConstantModN(int a, int N, const QuReg& quregs, const std::vector& ctrl) -- { -- emulate_math([a,N](std::vector &res){for(auto& x: res) x = (x * a) % N;}, quregs, ctrl, true); -- } -- - calc_type get_expectation_value(TermsDict const& td, std::vector const& ids){ - run(); - calc_type expectation = 0.; - -- StateVector current_state; // avoid costly memory reallocations -- if( tmpBuff1_.capacity() >= vec_.size() ) -- std::swap(tmpBuff1_, current_state); -- current_state.resize(vec_.size()); -+ StateVector current_state = (StateVector)malloc(len_ *sizeof(calc_type)); - #pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i) -+ for (std::size_t i = 0; i < len_; ++i) - current_state[i] = vec_[i]; - - for (auto const& term : td){ -@@ -306,81 +87,53 @@ - apply_term(term.first, ids, {}); - calc_type delta = 0.; - #pragma omp parallel for reduction(+:delta) schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i){ -- auto const a1 = std::real(current_state[i]); -- auto const b1 = -std::imag(current_state[i]); -- auto const a2 = std::real(vec_[i]); -- auto const b2 = std::imag(vec_[i]); -+ for (std::size_t i = 0; i < (len_ >> 1); ++i){ -+ auto const a1 = current_state[2 * i]; -+ auto const b1 = -current_state[2 * i + 1]; -+ auto const a2 = vec_[2 * i]; -+ auto const b2 = vec_[2 * i + 1]; - delta += a1 * a2 - b1 * b2; - // reset vec_ -- vec_[i] = current_state[i]; -+ vec_[2 * i] = current_state[2 * i]; -+ vec_[2 * i + 1] = current_state[2 * i + 1]; - } - expectation += coefficient * delta; - } -- std::swap(current_state, tmpBuff1_); -+ if (NULL != current_state){ -+ free(current_state); -+ current_state = NULL; -+ } - return expectation; - } - - void apply_qubit_operator(ComplexTermsDict const& td, std::vector const& ids){ - run(); -- StateVector new_state, current_state; // avoid costly memory reallocations -- if( tmpBuff1_.capacity() >= vec_.size() ) -- std::swap(tmpBuff1_, new_state); -- if( tmpBuff2_.capacity() >= vec_.size() ) -- std::swap(tmpBuff2_, current_state); -- new_state.resize(vec_.size()); -- current_state.resize(vec_.size()); -+ StateVector new_state = (StateVector)calloc(len_, sizeof(calc_type)); -+ StateVector current_state = (StateVector)malloc(len_ * sizeof(calc_type)); - #pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i){ -- new_state[i] = 0; -+ for (std::size_t i = 0; i < len_; ++i){ - current_state[i] = vec_[i]; - } - for (auto const& term : td){ - auto const& coefficient = term.second; - apply_term(term.first, ids, {}); - #pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i){ -- new_state[i] += coefficient * vec_[i]; -- vec_[i] = current_state[i]; -+ for (std::size_t i = 0; i < (len_ >> 1); ++i){ -+ new_state[2 * i] += coefficient.real() * vec_[2 * i] - coefficient.imag() * vec_[2 * i + 1]; -+ new_state[2 * i + 1] += coefficient.real() * vec_[2 * i + 1] + coefficient.imag() * vec_[2 * i]; -+ vec_[2 * i] = current_state[2 * i]; -+ vec_[2 * i + 1] = current_state[2 * i + 1]; - } - } -- std::swap(vec_, new_state); -- std::swap(tmpBuff1_, new_state); -- std::swap(tmpBuff2_, current_state); -- } -- -- calc_type get_probability(std::vector const& bit_string, -- std::vector const& ids){ -- run(); -- if (!check_ids(ids)) -- throw(std::runtime_error("get_probability(): Unknown qubit id. Please make sure you have called eng.flush().")); -- std::size_t mask = 0, bit_str = 0; -- for (unsigned i = 0; i < ids.size(); ++i){ -- mask |= 1UL << map_[ids[i]]; -- bit_str |= (bit_string[i]?1UL:0UL) << map_[ids[i]]; -- } -- calc_type probability = 0.; -- #pragma omp parallel for reduction(+:probability) schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i) -- if ((i & mask) == bit_str) -- probability += std::norm(vec_[i]); -- return probability; -- } -- -- complex_type const& get_amplitude(std::vector const& bit_string, -- std::vector const& ids){ -- run(); -- std::size_t chk = 0; -- std::size_t index = 0; -- for (unsigned i = 0; i < ids.size(); ++i){ -- if (map_.count(ids[i]) == 0) -- break; -- chk |= 1UL << map_[ids[i]]; -- index |= (bit_string[i]?1UL:0UL) << map_[ids[i]]; -+ if (NULL != vec_) -+ free(vec_); -+ vec_ = new_state; -+ if (NULL != new_state) -+ new_state = NULL; -+ if (NULL != current_state){ -+ free(current_state); -+ current_state = NULL; - } -- if (chk + 1 != vec_.size()) -- throw(std::runtime_error("The second argument to get_amplitude() must be a permutation of all allocated qubits. Please make sure you have called eng.flush().")); -- return vec_[index]; - } - - void emulate_time_evolution(TermsDict const& tdict, calc_type const& time, -@@ -400,87 +153,71 @@ - } - unsigned s = std::abs(time) * op_nrm + 1.; - complex_type correction = std::exp(-time * I * tr / (double)s); -- auto output_state = vec_; -+ auto output_state = copy(vec_, len_); - auto ctrlmask = get_control_mask(ctrl); - for (unsigned i = 0; i < s; ++i){ - calc_type nrm_change = 1.; - for (unsigned k = 0; nrm_change > 1.e-12; ++k){ - auto coeff = (-time * I) / double(s * (k + 1)); -- auto current_state = vec_; -- auto update = StateVector(vec_.size(), 0.); -+ auto current_state = copy(vec_, len_); -+ auto update = (StateVector)calloc(len_, sizeof(calc_type)); - for (auto const& tup : td){ - apply_term(tup.first, ids, {}); - #pragma omp parallel for schedule(static) -- for (std::size_t j = 0; j < vec_.size(); ++j){ -+ for (std::size_t j = 0; j < len_; ++j){ - update[j] += vec_[j] * tup.second; - vec_[j] = current_state[j]; - } - } - nrm_change = 0.; - #pragma omp parallel for reduction(+:nrm_change) schedule(static) -- for (std::size_t j = 0; j < vec_.size(); ++j){ -- update[j] *= coeff; -- vec_[j] = update[j]; -+ for (std::size_t j = 0; j < (len_ >> 1); ++j){ -+ complex_type tmp(update[2 * j], update[2 * j + 1]); -+ tmp *= coeff; -+ update[2 * j] *= std::real(tmp); -+ update[2 * j + 1] *= std::imag(tmp); -+ vec_[2 * j] = update[2 * j]; -+ vec_[2 * j + 1] = update[2 * j + 1]; - if ((j & ctrlmask) == ctrlmask){ -- output_state[j] += update[j]; -- nrm_change += std::norm(update[j]); -+ output_state[2 * j] += update[2 * j]; -+ output_state[2 * j + 1] += update[2 * j + 1]; -+ nrm_change += std::sqrt(update[2 * j] * update[2 * j] + update[2 * j + 1] * update[2 * j + 1]); - } - } - nrm_change = std::sqrt(nrm_change); -+ if (NULL != current_state){ -+ free(current_state); -+ current_state = NULL; -+ } -+ if (NULL != update){ -+ free(update); -+ update = NULL; -+ } - } - #pragma omp parallel for schedule(static) -- for (std::size_t j = 0; j < vec_.size(); ++j){ -- if ((j & ctrlmask) == ctrlmask) -- output_state[j] *= correction; -- vec_[j] = output_state[j]; -+ for (std::size_t j = 0; j < (len_ >>1); ++j){ -+ if ((j & ctrlmask) == ctrlmask){ -+ complex_type tmp(output_state[2 * j], output_state[2 * j + 1]); -+ tmp *= correction; -+ output_state[2 * j] = std::real(tmp); -+ output_state[2 * j + 1] =std::imag(tmp); -+ } -+ vec_[2 * j] = output_state[2 * j]; -+ vec_[2 * j + 1] = output_state[2 * j + 1]; - } - } -+ if (NULL != output_state){ -+ free(output_state); -+ output_state = NULL; -+ } - } - - void set_wavefunction(StateVector const& wavefunction, std::vector const& ordering){ - run(); -- // make sure there are 2^n amplitudes for n qubits -- assert(wavefunction.size() == (1UL << ordering.size())); -- // check that all qubits have been allocated previously -- if (map_.size() != ordering.size() || !check_ids(ordering)) -- throw(std::runtime_error("set_wavefunction(): Invalid mapping provided. Please make sure all qubits have been allocated previously (call eng.flush()).")); -- -- // set mapping and wavefunction -- for (unsigned i = 0; i < ordering.size(); ++i) -- map_[ordering[i]] = i; -- #pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < wavefunction.size(); ++i) -- vec_[i] = wavefunction[i]; -- } -- -- void collapse_wavefunction(std::vector const& ids, std::vector const& values){ -- run(); -- assert(ids.size() == values.size()); -- if (!check_ids(ids)) -- throw(std::runtime_error("collapse_wavefunction(): Unknown qubit id(s) provided. Try calling eng.flush() before invoking this function.")); -- std::size_t mask = 0, val = 0; -- for (unsigned i = 0; i < ids.size(); ++i){ -- mask |= (1UL << map_[ids[i]]); -- val |= ((values[i]?1UL:0UL) << map_[ids[i]]); -- } -- // set bad entries to 0 and compute probability of outcome to renormalize -- calc_type N = 0.; -- #pragma omp parallel for reduction(+:N) schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i){ -- if ((i & mask) == val) -- N += std::norm(vec_[i]); -- } -- if (N < 1.e-12) -- throw(std::runtime_error("collapse_wavefunction(): Invalid collapse! Probability is ~0.")); -- // re-normalize (if possible) -- N = 1./std::sqrt(N); -- #pragma omp parallel for schedule(static) -- for (std::size_t i = 0; i < vec_.size(); ++i){ -- if ((i & mask) != val) -- vec_[i] = 0.; -- else -- vec_[i] *= N; -+ if (NULL != vec_){ -+ free(vec_); - } -+ vec_ = copy(wavefunction, len_); - } - - void run(){ -@@ -500,23 +237,23 @@ - switch (ids.size()){ - case 1: - #pragma omp parallel -- kernel(vec_, ids[0], m, ctrlmask); -+ kernel(vec_, ids[0], m, ctrlmask, len_ >> 1); - break; - case 2: - #pragma omp parallel -- kernel(vec_, ids[1], ids[0], m, ctrlmask); -+ kernel(vec_, ids[1], ids[0], m, ctrlmask, len_ >> 1); - break; - case 3: - #pragma omp parallel -- kernel(vec_, ids[2], ids[1], ids[0], m, ctrlmask); -+ kernel(vec_, ids[2], ids[1], ids[0], m, ctrlmask, len_ >> 1); - break; - case 4: - #pragma omp parallel -- kernel(vec_, ids[3], ids[2], ids[1], ids[0], m, ctrlmask); -+ kernel(vec_, ids[3], ids[2], ids[1], ids[0], m, ctrlmask, len_ >> 1); - break; - case 5: - #pragma omp parallel -- kernel(vec_, ids[4], ids[3], ids[2], ids[1], ids[0], m, ctrlmask); -+ kernel(vec_, ids[4], ids[3], ids[2], ids[1], ids[0], m, ctrlmask, len_ >> 1); - break; - default: - throw std::invalid_argument("Gates with more than 5 qubits are not supported!"); -@@ -525,12 +262,27 @@ - fused_gates_ = Fusion(); - } - -- std::tuple cheat(){ -+ std::vector cheat(){ - run(); -- return make_tuple(map_, std::ref(vec_)); -+ std::vector result; -+ for (unsigned int i = 0; i < (len_ >> 1); i++){ -+ result.push_back({vec_[2 * i], vec_[2 * i + 1]}); -+ } -+ return result; -+ } -+ -+ inline StateVector copy(StateVector source, unsigned len){ -+ StateVector result = (StateVector)malloc(len * sizeof(calc_type)); -+#pragma omp parallel for schedule(static) -+ for (std::size_t i = 0; i < len; ++i) { -+ result[i] = source[i]; - } -+ return result; -+} - - ~Simulator(){ -+ if (NULL != vec_) -+ free(vec_); - } - - private: -@@ -562,18 +314,13 @@ - } - - unsigned N_; // #qubits -- StateVector vec_; -+ unsigned len_; - Map map_; - Fusion fused_gates_; - unsigned fusion_qubits_min_, fusion_qubits_max_; - RndEngine rnd_eng_; - std::function rng_; -- -- // large array buffers to avoid costly reallocations -- static StateVector tmpBuff1_, tmpBuff2_; - }; - --Simulator::StateVector Simulator::tmpBuff1_; --Simulator::StateVector Simulator::tmpBuff2_; - - #endif diff --git a/third_party/patch/projectq/projectq.patch002 b/third_party/patch/projectq/projectq.patch002 deleted file mode 100644 index 95dd03711..000000000 --- a/third_party/patch/projectq/projectq.patch002 +++ /dev/null @@ -1,322 +0,0 @@ ---- projectq-src/projectq/backends/_sim/_cppkernels/intrin/alignedallocator.hpp 2020-06-05 15:07:57.000000000 +0200 -+++ projectq-src-new/projectq/backends/_sim/_cppkernels/intrin/alignedallocator.hpp 2021-10-04 10:44:26.352241980 +0200 -@@ -1,120 +1,231 @@ --// Copyright (C) 2012 Andreas Hehn . -- --// Licensed under the Apache License, Version 2.0 (the "License"); --// you may not use this file except in compliance with the License. --// You may obtain a copy of the License at -+// Copyright 2021 -+// -+// Licensed under the Apache License, Version 2.0 (the "License"); -+// you may not use this file except in compliance with the License. -+// You may obtain a copy of the License at - // --// http://www.apache.org/licenses/LICENSE-2.0 -+// http://www.apache.org/licenses/LICENSE-2.0 - // --// Unless required by applicable law or agreed to in writing, software --// distributed under the License is distributed on an "AS IS" BASIS, --// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --// See the License for the specific language governing permissions and --// limitations under the License. -+// Unless required by applicable law or agreed to in writing, software -+// distributed under the License is distributed on an "AS IS" BASIS, -+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+// See the License for the specific language governing permissions and -+// limitations under the License. - --#pragma once -+#ifndef ALIGNED_ALLOCATOR_HPP -+#define ALIGNED_ALLOCATOR_HPP - --#ifdef _WIN32 --#include --#else --#include --#endif - #include -+#include - #include - #include - --#if __cplusplus < 201103L --#define noexcept -+// NOLINTNEXTLINE -+#define DEFAULT_ALIGNMENT 16U -+ -+#ifdef _WIN32 -+# include - #endif - -+#ifndef MALLOC_ALREADY_ALIGNED -+# if defined(__GLIBC__) && ((__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 8) || __GLIBC__ > 2) && defined(__LP64__) \ -+ && !defined(__SANITIZE_ADDRESS__) -+# define GLIBC_MALLOC_ALREADY_ALIGNED 1 // NOLINT -+# else -+# define GLIBC_MALLOC_ALREADY_ALIGNED 0 // NOLINT -+# endif -+ -+// FreeBSD 6 seems to have 16-byte aligned malloc -+// See http://svn.freebsd.org/viewvc/base/stable/6/lib/libc/stdlib/malloc.c?view=markup -+// FreeBSD 7 seems to have 16-byte aligned malloc except on ARM and MIPS architectures -+// See http://svn.freebsd.org/viewvc/base/stable/7/lib/libc/stdlib/malloc.c?view=markup -+# if defined(__FreeBSD__) && !(defined(__arm__) || defined(__mips__) || defined(__mips)) -+# define FREEBSD_MALLOC_ALREADY_ALIGNED 1 // NOLINT -+# else -+# define FREEBSD_MALLOC_ALREADY_ALIGNED 0 // NOLINT -+# endif -+ -+# if (defined(__APPLE__) || defined(_WIN64) || GLIBC_MALLOC_ALREADY_ALIGNED || FREEBSD_MALLOC_ALREADY_ALIGNED) -+# define MALLOC_ALREADY_ALIGNED 1 // NOLINT -+# else -+# define MALLOC_ALREADY_ALIGNED 0 // NOLINT -+# endif -+#endif // !MALLOC_ALREADY_ALIGNED -+ -+#if ((defined __QNXNTO__) || (defined _GNU_SOURCE) || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 600))) \ -+ && (defined _POSIX_ADVISORY_INFO) && (_POSIX_ADVISORY_INFO > 0) -+# define HAS_POSIX_MEMALIGN 1 // NOLINT -+#else -+# define HAS_POSIX_MEMALIGN 0 // NOLINT -+#endif -+ -+#if (defined(_M_AMD64) || defined(_M_X64) || defined(__amd64)) && !defined(__x86_64__) -+# define __x86_64__ 1 // NOLINT -+#endif - --template --class aligned_allocator -+// Find sse instruction set from compiler macros if SSE_INSTR_SET not defined -+// Note: Not all compilers define these macros automatically -+#ifndef SSE_INSTR_SET -+# if defined(__AVX2__) -+# define SSE_INSTR_SET 8 // NOLINT -+# elif defined(__AVX__) -+# define SSE_INSTR_SET 7 // NOLINT -+# elif defined(__SSE4_2__) -+# define SSE_INSTR_SET 6 // NOLINT -+# elif defined(__SSE4_1__) -+# define SSE_INSTR_SET 5 // NOLINT -+# elif defined(__SSSE3__) -+# define SSE_INSTR_SET 4 // NOLINT -+# elif defined(__SSE3__) -+# define SSE_INSTR_SET 3 // NOLINT -+# elif defined(__SSE2__) || defined(__x86_64__) -+# define SSE_INSTR_SET 2 // NOLINT -+# elif defined(__SSE__) -+# define SSE_INSTR_SET 1 // NOLINT -+# elif defined(_M_IX86_FP) // Defined in MS compiler on 32bits system. 1: SSE, 2: SSE2 -+# define SSE_INSTR_SET _M_IX86_FP -+# else -+# define SSE_INSTR_SET 0 // NOLINT -+# endif // instruction set defines -+#endif // SSE_INSTR_SET -+ -+#if SSE_INSTR_SET > 0 -+# define HAS_MM_MALLOC 1 // NOLINT -+# include -+#else -+# define HAS_MM_MALLOC 0 // NOLINT -+#endif -+ -+#if __cplusplus >= 201703 -+# define HAS_STD_ALIGNED_ALLOC 1 // NOLINT -+#else -+# define HAS_STD_ALIGNED_ALLOC 0 // NOLINT -+#endif -+ -+#if __cplusplus < 201103L -+# error aligned_allocator requires at least C++11 support enabled! -+#endif -+ -+template -+class aligned_allocator : public std::allocator - { -- public: -- typedef T* pointer; -- typedef T const* const_pointer; -- typedef T& reference; -- typedef T const& const_reference; -- typedef T value_type; -- typedef std::size_t size_type; -- typedef std::ptrdiff_t difference_type; -+public: -+ using value_type = T; -+ using reference = T&; -+ using const_reference = const T&; -+ using pointer = T*; -+ using const_pointer = const T*; -+ using size_type = std::size_t; -+ using difference_type = std::ptrdiff_t; -+ -+ static_assert(alignment >= DEFAULT_ALIGNMENT, "Alignment must be equal or greater than default alignment"); - -- template -+ template - struct rebind - { -- typedef aligned_allocator other; -+ using other = aligned_allocator; - }; - -- aligned_allocator() noexcept {} -- aligned_allocator(aligned_allocator const&) noexcept {} -- template -- aligned_allocator(aligned_allocator const&) noexcept -- { -- } -+ aligned_allocator() noexcept = default; -+ aligned_allocator(const aligned_allocator& /* unused */) noexcept = default; -+ aligned_allocator(aligned_allocator&& /* unused */) noexcept = default; -+ -+ template -+ explicit aligned_allocator(const aligned_allocator& /* unused */) noexcept -+ {} -+ -+ ~aligned_allocator() noexcept = default; -+ aligned_allocator& operator=(const aligned_allocator&) noexcept = default; -+ aligned_allocator& operator=(aligned_allocator&&) noexcept = default; -+ -+ // NOLINTNEXTLINE(huawei-force-type-void) -+ auto allocate(size_type n, const void* /* hint */ = nullptr) const; -+ void deallocate(pointer p, size_type /* unused */) const; -+}; - -- pointer allocate(size_type n) -+namespace detail -+{ -+ // NOLINTNEXTLINE(huawei-force-type-void) -+ inline void* _aligned_malloc(size_t size, size_t alignment) - { -- pointer p; -- -- --#ifdef _WIN32 -- p = reinterpret_cast(_aligned_malloc(n * sizeof(T), Alignment)); -- if (p == 0) throw std::bad_alloc(); --#else -- if (posix_memalign(reinterpret_cast(&p), Alignment, n * sizeof(T))) -- throw std::bad_alloc(); --#endif -- return p; -+ void* res = nullptr; -+ void* ptr = malloc(size + alignment); // NOLINT -+ if (ptr != nullptr) { -+ // NOLINTNEXTLINE(performance-no-int-to-ptr) -+ res = reinterpret_cast((reinterpret_cast(ptr) & ~(size_t(alignment - 1))) + alignment); -+ *(reinterpret_cast(res) - 1) = ptr; // NOLINT -+ } -+ return res; - } -+} // namespace detail - -- void deallocate(pointer p, size_type) noexcept -- { --#ifdef _WIN32 -- _aligned_free(p); -+// NOLINTNEXTLINE(huawei-force-type-void) -+inline void* aligned_malloc(size_t size, size_t alignment) // NOLINT(misc-unused-parameters) -+{ -+#if MALLOC_ALREADY_ALIGNED -+ return malloc(size); // NOLINT -+#elif HAS_MM_MALLOC -+ return _mm_malloc(size, alignment); // NOLINT -+#elif HAS_POSIX_MEMALIGN -+ void* res; -+ const int failed = posix_memalign(&res, alignment, size); -+ if (failed) { -+ res = nullptr; -+ } -+ return res; -+#elif (defined _MSC_VER) -+ return _aligned_malloc(size, alignment); -+#elif HAS_STD_ALIGNED_ALLOC -+ return std::aligned_alloc(alignment, size); - #else -- std::free(p); -+ return detail::_aligned_malloc(size, alignment); - #endif -- } -+} - -- size_type max_size() const noexcept -+namespace detail -+{ -+ // NOLINTNEXTLINE(huawei-force-type-void) -+ inline void _aligned_free(void* ptr) - { -- std::allocator a; -- return a.max_size(); -+ if (ptr != nullptr) { -+ free(*(reinterpret_cast(ptr) - 1)); // NOLINT -+ } - } -+} // namespace detail - --#if __cplusplus >= 201103L -- template -- void construct(C* c, Args&&... args) -- { -- new ((void*)c) C(std::forward(args)...); -- } -+// NOLINTNEXTLINE(huawei-force-type-void) -+inline void aligned_free(void* ptr) -+{ -+#if MALLOC_ALREADY_ALIGNED -+ free(ptr); // NOLINT -+#elif HAS_MM_MALLOC -+ _mm_free(ptr); // NOLINT -+#elif HAS_POSIX_MEMALIGN -+ free(ptr); // NOLINT -+#elif defined(_MSC_VER) -+ _aligned_free(ptr); // NOLINT -+#elif HAS_STD_ALIGNED_ALLOC -+ std::free(ptr); // NOLINT - #else -- void construct(pointer p, const_reference t) { new ((void*)p) T(t); } -+ detail::_aligned_free(ptr); // NOLINT - #endif -+} - -- template -- void destroy(C* c) -- { -- c->~C(); -- } -- -- bool operator==(aligned_allocator const&) const noexcept { return true; } -- bool operator!=(aligned_allocator const&) const noexcept { return false; } -- template -- bool operator==(aligned_allocator const&) const noexcept -- { -- return false; -- } -- -- template -- bool operator!=(aligned_allocator const&) const noexcept -- { -- return true; -+template -+// NOLINTNEXTLINE(huawei-force-type-void) -+auto aligned_allocator::allocate(size_type n, const void* /* hint */) const -+{ -+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -+ auto res = reinterpret_cast(aligned_malloc(sizeof(T) * n, alignment)); -+ if (res == nullptr) { -+ throw std::bad_alloc(); - } --}; -- --#if __cplusplus < 201103L --#undef noexcept --#endif -+ return res; -+} - -+template -+void aligned_allocator::deallocate(pointer p, size_type /* unused */) const -+{ -+ aligned_free(p); -+} -+#endif /* ALIGNED_ALLOCATOR_HPP */ diff --git a/third_party/patch/quest/quest.patch001 b/third_party/patch/quest/quest.patch001 deleted file mode 100644 index cd4d25ef3..000000000 --- a/third_party/patch/quest/quest.patch001 +++ /dev/null @@ -1,211 +0,0 @@ -diff -aur quest-src-old/QuEST/CMakeLists.txt quest-src/QuEST/CMakeLists.txt ---- quest-src-old/QuEST/CMakeLists.txt 2021-10-05 12:06:40.000000000 +0200 -+++ quest-src/QuEST/CMakeLists.txt 2021-10-05 12:10:45.000000000 +0200 -@@ -29,9 +29,11 @@ - - option(GPUACCELERATED "Whether to program will run on GPU. Set to 1 to enable" 0) - --set(GPU_COMPUTE_CAPABILITY 30 CACHE STRING "GPU hardware dependent, lookup at https://developer.nvidia.com/cuda-gpus. Write without fullstop") -- -- -+if(NOT DEFINED GPU_COMPUTE_CAPABILITY) -+ set(GPU_COMPUTE_CAPABILITY 30 CACHE STRING "GPU hardware dependent, lookup at https://developer.nvidia.com/cuda-gpus. Write without fullstop") -+else() -+ set(GPU_COMPUTE_CAPABILITY ${GPU_COMPUTE_CAPABILITY} CACHE STRING "GPU hardware dependent, lookup at https://developer.nvidia.com/cuda-gpus. Write without fullstop") -+endif() - - # ***************************************************************************** - # ***** NO CHANGES SHOULD BE REQUIRED FROM THE USER BEYOND THIS POINT ********* -@@ -40,6 +42,9 @@ - # Show the user their settings - message(STATUS "Precision is ${PRECISION}") - message(STATUS "GPU acceleration is ${GPUACCELERATED}") -+if(CUDA_STATIC) -+ message(STATUS "Using static CUDA") -+endif() - message(STATUS "OMP acceleration is ${MULTITHREADED}") - message(STATUS "MPI distribution is ${DISTRIBUTED}") - -@@ -50,7 +55,7 @@ - - # ----- FATAL ----------------------------------------------------------------- - --if (${DISTRIBUTED} AND ${GPUACCELERATED}) -+if (DISTRIBUTED AND GPUACCELERATED) - message(FATAL_ERROR "DISTRIBUTED=${DISTRIBUTED} and \ - GPUACCELERATED=${GPUACCELERATED} set but \ - distributed GPU acceleration not supported. Aborting") -@@ -65,14 +70,14 @@ - endif() - - if ( (${PRECISION} EQUAL 4) AND -- ${GPUACCELERATED} ) -+ GPUACCELERATED ) - message(FATAL_ERROR "PRECISION=${PRECISION} but quad precision is not \ - supported on GPU. Aborting") - endif() - - # ----- WARNINGS -------------------------------------------------------------- - --if (${GPUACCELERATED} AND ${MULTITHREADED}) -+if (GPUACCELERATED AND MULTITHREADED) - message(WARNING "MULTITHREADED=${MULTITHREADED} and \ - GPUACCELERATED=${GPUACCELERATED} set but GPU \ - version makes no use of multithreading. Ignoring multithreading settings") -@@ -80,13 +85,13 @@ - - #TODO Add other supported Clang versions if found - if ( ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") AND -- ${GPUACCELERATED} AND -+ GPUACCELERATED AND - NOT("${CMAKE_C_COMPILER_VERSION}" STREQUAL "3.7.0") ) - message(WARNING "Some versions of Clang are not NVIDIA-GPU compatible. \ - If compilation fails, try Clang 3.7.") - endif() - --if ( ${GPUACCELERATED} AND -+if ( GPUACCELERATED AND - ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") AND - ("${CMAKE_SYSTEM_NAME}" STREQUAL "DARWIN") ) # DARWIN means Mac OS X - message(WARNING "On some platforms (e.g. OSX), NVIDIA-GPUs are not \ -@@ -109,11 +114,47 @@ - endif() - - if (GPUACCELERATED) -- find_package(CUDA REQUIRED) -- # Stop nvcc sending c compile flags through using -Xcompiler and breaking -- # on compilation of a cpp file receiving -std=c99. In long term should figure -- # out why CMAKE_C_FLAGS and not CMAKE_CXX_FLAGS are being sent through to a cpp file -- set(CUDA_PROPAGATE_HOST_FLAGS FALSE) -+ enable_language(CUDA) -+ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17) -+ find_package(CUDAToolkit REQUIRED) -+ else() -+ find_package(CUDA REQUIRED) -+ -+ if(CUDA_LIBRARIES) -+ if(NOT TARGET CUDA::cudart) -+ add_library(CUDA::cudart IMPORTED INTERFACE) -+ target_include_directories(CUDA::cudart SYSTEM INTERFACE "${CUDA_INCLUDE_DIRS}") -+ target_link_libraries(CUDA::cudart INTERFACE "${CUDA_LIBRARIES}") -+ endif() -+ endif() -+ -+ if(CUDA_cudart_static_LIBRARY) -+ if(NOT TARGET CUDA::cudart_static) -+ add_library(CUDA::cudart_static IMPORTED INTERFACE) -+ target_include_directories(CUDA::cudart_static SYSTEM INTERFACE "${CUDA_INCLUDE_DIRS}") -+ target_link_libraries(CUDA::cudart_static INTERFACE "${CUDA_cudart_static_LIBRARY}" Threads::Threads) -+ endif() -+ endif() -+ find_library( -+ CUDA_driver_LIBRARY -+ NAMES cuda_driver cuda -+ HINTS ${CUDA_TOOLKIT_ROOT_DIR} ENV CUDA_PATH -+ PATH_SUFFIXES nvidia/current lib64 lib/x64 lib) -+ if(NOT CUDA_driver_LIBRARY) -+ # Don't try any stub directories until we have exhausted all other search locations. -+ find_library( -+ CUDA_driver_LIBRARY -+ NAMES cuda_driver cuda -+ HINTS ${CUDA_TOOLKIT_ROOT_DIR} ENV CUDA_PATH -+ PATH_SUFFIXES lib64/stubs lib/x64/stubs lib/stubs stubs) -+ endif() -+ mark_as_advanced(CUDA_driver_LIBRARY) -+ if(CUDA_driver_LIBRARY) -+ add_library(CUDA::cuda_driver IMPORTED INTERFACE) -+ target_include_directories(CUDA::cuda_driver SYSTEM INTERFACE "${CUDA_INCLUDE_DIRS}") -+ target_link_libraries(CUDA::cuda_driver INTERFACE "${CUDA_driver_LIBRARY}") -+ endif() -+ endif() - endif() - - -@@ -125,7 +166,7 @@ - - # ----- OPENMP ---------------------------------------------------------------- - --if (${MULTITHREADED} AND NOT ${GPUACCELERATED}) -+if (MULTITHREADED AND NOT GPUACCELERATED) - find_package(OpenMP) - - # If found, we must also check the version -@@ -149,21 +190,17 @@ - # ----- CUDA FLAGS ------------------------------------------------------------ - - if (GPUACCELERATED) -- set (CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} \ -- -arch=compute_${GPU_COMPUTE_CAPABILITY} -code=sm_${GPU_COMPUTE_CAPABILITY}" -- ) -+ if(CMAKE_VERSION VERSION_LESS 3.18) -+ set(CMAKE_CUDA_FLAGS -+ "${CMAKE_CUDA_FLAGS} -arch=compute_${GPU_COMPUTE_CAPABILITY} -code=sm_${GPU_COMPUTE_CAPABILITY}") -+ endif() -+ - if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") -- set (CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} \ -- -O2" -- ) -+ set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O2") - elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") -- set (CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} \ -- -G -g -lineinfo" -- ) -+ set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -G -g -lineinfo") - else() -- set (CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} \ -- -O2" -- ) -+ set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O2") - endif() - endif() - -@@ -215,11 +252,6 @@ - - # ----- C++ COMPILER FLAGS -------------------------------------------------- - --# set C++ flags that are common between compilers and build types --set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ -- -std=c++98 -Wall" --) -- - # Use -O2 for all but debug mode by default - if (NOT("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")) - set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} \ -@@ -284,9 +316,13 @@ - add_subdirectory(src) - - if (GPUACCELERATED) -- cuda_add_library(QuEST SHARED -+ add_library(QuEST SHARED - ${QuEST_SRC} -- ) -+ ) -+ -+ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18) -+ set_target_properties(QuEST PROPERTIES CUDA_ARCHITECTURES ${GPU_COMPUTE_CAPABILITY}) -+ endif() - elseif (WIN32) - add_library(QuEST STATIC - ${QuEST_SRC} -@@ -300,8 +336,7 @@ - # ----- Location of header files ---------------------------------------------- - - target_include_directories(QuEST -- PRIVATE src -- PUBLIC include -+ PUBLIC src include - ) - - -@@ -328,7 +363,9 @@ - - # ----- GPU ------------------------------------------------------------------- - --target_link_libraries(QuEST ${CUDA_LIBRARIES}) -+if (GPUACCELERATED) -+ target_link_libraries(QuEST PUBLIC $,CUDA::cudart_static,CUDA::cudart>) -+endif() - - - # ----- Coverage testing with GCC or Clang ------------------------------------ diff --git a/tutorials/0.frequently_asked_questions.ipynb b/tutorials/0.frequently_asked_questions.ipynb deleted file mode 100644 index 2a6bf4502..000000000 --- a/tutorials/0.frequently_asked_questions.ipynb +++ /dev/null @@ -1,427 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Frequently Asked Questions (FAQ)\n", - "\n", - "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", - "\n", - "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/mindquantum/blob/master/tutorials/0.frequently_asked_questions.ipynb) [![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/view_mindquantum_api.png)](https://mindspore.cn/mindquantum/api/zh-CN/master/index.html)\n", - "\n", - "## 1. 为什么量子模拟器的运算速度很慢?\n", - "\n", - "量子模拟器运算速度很慢的原因可能是设置的并行内核数太低了或者是并行内核数过高,特别是在大型服务器中,如果不设置内核数,默认会占用所有的CPU,速度反而会降低。\n", - "\n", - "在运行代码前,我们需要设置量子模拟器运行时的并行内核数,例如:如果需要设置并行内核数为4,可运行如下代码:\n", - "\n", - "export OMP_NUM_THREADS=4\n", - "\n", - "当发现量子模拟器运算速度很慢的时候,可以适当调整并行内核数。\n", - "\n", - "请根据模型规模合理设置并行内核数以达到最优效果。\n", - "\n", - "## 2. 关于双量子比特门——`CNOT`是如何实现的?\n", - "\n", - "对于`CNOT`门,其本质上是受控`X`门(`Controlled-X` gate),因此在MindQuantum中,如果我们需要执行`CNOT`门,只需设定`X`门的控制比特位和目标比特位即可(实际上,任意的量子门我们都可以设定控制比特位和所需执行量子门操作的目标比特位)。例如运行如下代码:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "X(1 <-: 0)" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import mindquantum as mq\n", - "from mindquantum import X\n", - "\n", - "X.on(1, 0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "运行结果为:X(1 <-: 0) ,其表示第1位量子比特位为目标比特位,第0位量子比特位为控制比特位,第1位量子比特受第0位量子比特控制,若第0位量子比特为1,则对第1位量子比特执行`X`门操作,否则不作任何操作。\n", - "\n", - "为了更加直观,我们将其量子线路打印出来,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──●──\n",
-       "\n",
-       "q1: ──X──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──●──\n", - " │ \n", - "q1: ──X──" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from mindquantum import Circuit\n", - "\n", - "circuit = Circuit()\n", - "circuit += X.on(1, 0)\n", - "circuit" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这就是MindQuantum中实现`CNOT`门的语法,大家需要注意。如打印线路错乱,请参考下面第四条,设置浏览器或者终端的等宽字体。\n", - "\n", - "## 3. 关于量子比特的读取顺序?\n", - "\n", - "在MindQuantum中,量子比特的读取顺序都是从右往左的。我们通过一个具体的例子来说明。\n", - "\n", - "首先,运行如下代码,得到一个3量子比特的均匀叠加态:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "√2/4¦000⟩\n", - "√2/4¦001⟩\n", - "√2/4¦010⟩\n", - "√2/4¦011⟩\n", - "√2/4¦100⟩\n", - "√2/4¦101⟩\n", - "√2/4¦110⟩\n", - "√2/4¦111⟩\n" - ] - } - ], - "source": [ - "from mindquantum.simulator import Simulator\n", - "from mindquantum import H, UN\n", - "\n", - "sim = Simulator('projectq', 3)\n", - "\n", - "circuit1 = Circuit()\n", - "circuit1 += UN(H, 3)\n", - "\n", - "sim.apply_circuit(circuit1)\n", - "print(sim.get_qs(True))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果可以看到,我们得到了3量子比特的均匀叠加态,需要说明的是,所呈现的量子态,最右位的表示是第0位量子比特,中间位表示第1位量子比特,最左位表示第2位量子比特。\n", - "\n", - "我们再举一个例子,运行如下代码,打印量子线路和此时的量子态:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "projectq simulator with 2 qubits.\n", - "Current quantum state:\n", - "1¦10⟩\n" - ] - }, - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ─────\n",
-       "         \n",
-       "q1: ──X──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ─────\n", - " \n", - "q1: ──X──" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sim1 = Simulator('projectq', 2)\n", - "\n", - "circuit2 = Circuit()\n", - "circuit2 += X.on(1)\n", - "\n", - "sim1.apply_circuit(circuit2)\n", - "\n", - "print(sim1)\n", - "circuit2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到此时的量子态为$|10\\rangle$​​态,量子态中的数字1表示的是第1位量子比特为$|1\\rangle$​​,数字0表示的是第0位量子比特为$|0\\rangle$​​。我们简单地验证一下,在第1位量子比特添加测量门,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──────────────\n",
-       "                  \n",
-       "q1: ──X────M(q1)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──────────────\n", - " \n", - "q1: ──X────M(q1)──" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from mindquantum import Measure\n", - "\n", - "circuit2 += Measure('q1').on(1)\n", - "circuit2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从打印的结果可以看到,我们已经在第1位量子比特上添加了测量门。从理论上讲,第1位的量子比特是$|1\\rangle$​​,所以测量的结果应该也是$|1\\rangle$​​​,我们运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
shots: 100\n",
-       "Keys: q1│0.00     0.2         0.4         0.6         0.8         1.0\n",
-       "────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n",
-       "       1│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n",
-       "\n",
-       "{'1': 100}\n",
-       "
\n" - ], - "text/plain": [ - "shots: 100\n", - "Keys: q1│0.00 0.2 0.4 0.6 0.8 1.0\n", - "────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n", - " 1│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n", - " │ \n", - "{'1': 100}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sim1.reset()\n", - "\n", - "result = sim1.sampling(circuit2, shots=100)\n", - "result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果可以看到,对存在测量门的量子比特进行重复采样100次,得到的就是100次1的结果,所以再次验证$|10\\rangle$态中的$|1\\rangle$​是第1位量子比特。\n", - "\n", - "因此,在MindQuantum中,量子比特的读取顺序都是从右往左的。\n", - "\n", - "## 4. 若打印量子线路时出现线路错乱,该如何解决?\n", - "\n", - "我们可以在MindQuantum中搭建各种各样的量子线路,最后我们还可以将搭建好的量子线路打印出来。例如,我们运行如下代码来搭建量子线路:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────●────RX(π/4)───────●───────M(q0)──\n",
-       "           │                  │              \n",
-       "q1: ───────X───────────────RY(π/2)────M(q1)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────●────RX(π/4)───────●───────M(q0)──\n", - " │ │ \n", - "q1: ───────X───────────────RY(π/2)────M(q1)──" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import numpy as np\n", - "from mindquantum import RX, RY\n", - "\n", - "circuit3 = Circuit()\n", - "circuit3 += H.on(0)\n", - "circuit3 += X.on(1, 0)\n", - "circuit3 += RX(np.pi/4).on(0)\n", - "circuit3 += RY(np.pi/2).on(1, 0)\n", - "circuit3 += Measure('q0').on(0)\n", - "circuit3 += Measure('q1').on(1)\n", - "circuit3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是,有时会出现如下图所示的线路错乱问题。\n", - "\n", - "![](https://gitee.com/mindspore/mindquantum/raw/dev/tutorials/images/error_circuit.png)\n", - "\n", - "这个时候,我们只需要打开浏览器的设置,找到“外观”,找到“自定义字体”,然后在“宽度固定的字体”(有的浏览器为“等宽字体”)下,选择“Consolas”字体即可。此外,用户还可以下载并安装开源的[Fira Code](https://github.com/tonsky/FiraCode)字体来获得更优质的输出。当我们设置好等宽字体后,就可以看到最开始打印的量子线路了。(如下网址提供了一些等宽字体供用户自行选择[https://zhuanlan.zhihu.com/p/116230037/](https://zhuanlan.zhihu.com/p/116230037/))\n", - "\n", - "若想查询更多关于MindQuantum的API,请点击:[https://mindspore.cn/mindquantum/](https://mindspore.cn/mindquantum/)。" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "d62cf896b9ca57de08105ce3983377439eacacf6f6599f9150bf400edf4fa4b8" - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/tutorials/1.parameterized_quantum_circuit.ipynb b/tutorials/1.parameterized_quantum_circuit.ipynb index f87e61cee..78a30fafe 100644 --- a/tutorials/1.parameterized_quantum_circuit.ipynb +++ b/tutorials/1.parameterized_quantum_circuit.ipynb @@ -33,12 +33,10 @@ "source": [ "import numpy as np #导入numpy库并简写为np\n", "import mindquantum as mq #导入mindquantum库并简写为mq\n", - "from mindquantum.core import X, Y, Z, H, RX, RY, RZ #导入量子门H, X, Y, Z, RX, RY, RZ" + "from mindquantum.gate import X, Y, Z, H, RX, RY, RZ #导入量子门H, X, Y, Z, RX, RY, RZ" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "说明:\n", "\n", @@ -46,7 +44,7 @@ "\n", "(2)mindquantum是量子-经典混合计算框架,支持多种量子神经网络的训练和推理;\n", "\n", - "(3)搭建的量子线路中所需执行的量子门需要从mindquantum.core模块中导入;\n", + "(3)搭建的量子线路中所需执行的量子门需要从mindquantum.gate模块中导入;\n", "\n", "## 3. 量子门\n", "\n", @@ -57,7 +55,9 @@ "$$\\text{X}=\\begin{pmatrix}0&1\\\\\\\\1&0\\end{pmatrix},\\text{Y}=\\begin{pmatrix}0&-i\\\\\\\\i&0\\end{pmatrix},\\text{Z}=\\begin{pmatrix}1&0\\\\\\\\0&-1\\end{pmatrix},\\text{H}=\\frac{1}{\\sqrt{2}}\\begin{pmatrix}1&1\\\\\\\\1&-1\\end{pmatrix},\\text{CNOT}=\\begin{pmatrix}1&0&0&0\\\\\\\\0&1&0&0\\\\\\\\0&0&0&1\\\\\\\\0&0&1&0\\end{pmatrix}.$$\n", "\n", "分别打印上述量子门的矩阵形式,可以得到:\n" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -67,22 +67,17 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Gate name: X\n" - ] + "name": "stdout", + "text": "Gate name: X\n" }, { + "output_type": "execute_result", "data": { - "text/plain": [ - "array([[0, 1],\n", - " [1, 0]])" - ] + "text/plain": "array([[0, 1],\n [1, 0]])" }, - "execution_count": 2, "metadata": {}, - "output_type": "execute_result" + "execution_count": 2 } ], "source": [ @@ -98,22 +93,17 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Gate name: Y\n" - ] + "name": "stdout", + "text": "Gate name: Y\n" }, { + "output_type": "execute_result", "data": { - "text/plain": [ - "array([[ 0.+0.j, -0.-1.j],\n", - " [ 0.+1.j, 0.+0.j]])" - ] + "text/plain": "array([[ 0.+0.j, -0.-1.j],\n [ 0.+1.j, 0.+0.j]])" }, - "execution_count": 3, "metadata": {}, - "output_type": "execute_result" + "execution_count": 3 } ], "source": [ @@ -122,11 +112,11 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "说明:矩阵里的每一项,左边的“0.”表示小数形式(浮点数)的实部(若实部为负,则在小数前显示“-”,否则默认为非负),右边的“0.”表示小数形式(浮点数)的虚部(若虚部为负,则在小数前会显示“-”,否则显示“+”),j表示虚数单位$i$)。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -136,22 +126,17 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Gate name: Z\n" - ] + "name": "stdout", + "text": "Gate name: Z\n" }, { + "output_type": "execute_result", "data": { - "text/plain": [ - "array([[ 1, 0],\n", - " [ 0, -1]])" - ] + "text/plain": "array([[ 1, 0],\n [ 0, -1]])" }, - "execution_count": 4, "metadata": {}, - "output_type": "execute_result" + "execution_count": 4 } ], "source": [ @@ -167,22 +152,17 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Gate name: H\n" - ] + "name": "stdout", + "text": "Gate name: H\n" }, { + "output_type": "execute_result", "data": { - "text/plain": [ - "array([[ 0.70710678, 0.70710678],\n", - " [ 0.70710678, -0.70710678]])" - ] + "text/plain": "array([[ 0.70710678, 0.70710678],\n [ 0.70710678, -0.70710678]])" }, - "execution_count": 5, "metadata": {}, - "output_type": "execute_result" + "execution_count": 5 } ], "source": [ @@ -191,11 +171,11 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "对于`CNOT`门,其本质上是受控`X`门(`Controlled-X` gate),因此在MindQuantum中,如果我们需要执行`CNOT`门,只需设定`X`门的控制比特位和目标比特位即可(实际上,任意的量子门我们都可以设定控制比特位和所需执行量子门操作的目标比特位)。例如:" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -205,11 +185,9 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "X(0 <-: 1)\n" - ] + "name": "stdout", + "text": "X(0 <-: 1)\n" } ], "source": [ @@ -218,8 +196,6 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "说明:\n", "\n", @@ -236,7 +212,9 @@ "$$\\text{RZ}(\\theta)= e^{-\\frac{i\\theta Z}{2}}=\\cos\\left(\\frac{\\theta}{2}\\right)\\cdot I-i\\sin\\left(\\frac{\\theta}{2}\\right)\\cdot Z=\\begin{pmatrix}e^{-\\frac{i\\theta}{2}}&0\\\\\\\\0&e^{\\frac{i\\theta}{2}}\\end{pmatrix}.$$\n", "\n", "我们令$\\theta$分别为$0、\\frac{\\pi}{2}$和$\\pi$,然后打印$\\text{RX}(0)$门、$\\text{RY}(\\frac{\\pi}{2}$)门和$\\text{RZ}(\\pi)$门的矩阵形式,可以得到:" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -246,22 +224,17 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Gate name: RX(theta)\n" - ] + "name": "stdout", + "text": "Gate name: RX(theta)\n" }, { + "output_type": "execute_result", "data": { - "text/plain": [ - "array([[1.+0.j, 0.-0.j],\n", - " [0.-0.j, 1.+0.j]])" - ] + "text/plain": "array([[1.+0.j, 0.-0.j],\n [0.-0.j, 1.+0.j]])" }, - "execution_count": 7, "metadata": {}, - "output_type": "execute_result" + "execution_count": 7 } ], "source": [ @@ -271,11 +244,11 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "当$\\theta=0$时,此时$\\text{RX}(0)$门就是我们熟悉的`I`门。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -285,22 +258,17 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Gate name: RY(theta)\n" - ] + "name": "stdout", + "text": "Gate name: RY(theta)\n" }, { + "output_type": "execute_result", "data": { - "text/plain": [ - "array([[ 0.70710678, -0.70710678],\n", - " [ 0.70710678, 0.70710678]])" - ] + "text/plain": "array([[ 0.70710678, -0.70710678],\n [ 0.70710678, 0.70710678]])" }, - "execution_count": 8, "metadata": {}, - "output_type": "execute_result" + "execution_count": 8 } ], "source": [ @@ -310,11 +278,11 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "当$\\theta=\\frac{\\pi}{2}$时,此时$\\text{RY}(\\frac{\\pi}{2})$门就是我们熟悉的`H`门。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -324,22 +292,17 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Gate name: RZ(theta)\n" - ] + "name": "stdout", + "text": "Gate name: RZ(theta)\n" }, { + "output_type": "execute_result", "data": { - "text/plain": [ - "array([[0.-1.j, 0.+0.j],\n", - " [0.+0.j, 0.+1.j]])" - ] + "text/plain": "array([[0.-1.j, 0.+0.j],\n [0.+0.j, 0.+1.j]])" }, - "execution_count": 9, "metadata": {}, - "output_type": "execute_result" + "execution_count": 9 } ], "source": [ @@ -349,8 +312,6 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "当$\\theta=\\pi$时,此时$\\text{RZ}(\\pi)$门就是我们熟悉的`Z`门。(相差一个全局相位$-i$)\n", "\n", @@ -361,7 +322,9 @@ "![quantum circuit](https://gitee.com/mindspore/docs/raw/master/docs/mindquantum/docs/source_zh_cn/images/quantum_circuit.png)\n", "\n", "通过在量子线路中添加作用在不同量子比特位上的量子门即可快速完成对量子线路的搭建。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -371,25 +334,13 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "q0: ──────H────────●──\n", - " │ \n", - "q1: ───────────────X──\n", - " \n", - "q2: ──RY(theta)───────\n", - "=========Circuit Summary=========\n", - "|Total number of gates : 3. |\n", - "|Parameter gates : 1. |\n", - "|with 1 parameters are : theta.|\n", - "|Number qubit of circuit: 3 |\n", - "=================================\n" - ] + "name": "stdout", + "text": "H(0)\nX(1 <-: 0)\nRY(theta|2)\n=========Circuit Summary=========\n|Total number of gates : 3. |\n|Parameter gates : 1. |\n|with 1 parameters are : theta.|\n|Number qubit of circuit: 3 |\n=================================\n" } ], "source": [ - "from mindquantum.core import Circuit #导入Circuit模块,用于搭建量子线路\n", + "from mindquantum import Circuit #导入Circuit模块,用于搭建量子线路\n", "\n", "encoder = Circuit() #初始化量子线路\n", "encoder += H.on(0) #H门作用在第0位量子比特\n", @@ -414,9 +365,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3.7.5 64-bit", "language": "python", - "name": "python3" + "name": "python37564bit6afae4a42a5941c0967cdcfc2650559a" }, "language_info": { "codemirror_mode": { @@ -428,9 +379,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.7.5-final" } }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/tutorials/2.initial_experience_of_quantum_neural_network.ipynb b/tutorials/2.initial_experience_of_quantum_neural_network.ipynb index f74e0abc0..9368ce5c9 100644 --- a/tutorials/2.initial_experience_of_quantum_neural_network.ipynb +++ b/tutorials/2.initial_experience_of_quantum_neural_network.ipynb @@ -1,8 +1,27 @@ { + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5-final" + }, + "orig_nbformat": 2, + "kernelspec": { + "name": "python37564bit6afae4a42a5941c0967cdcfc2650559a", + "display_name": "Python 3.7.5 64-bit" + } + }, + "nbformat": 4, + "nbformat_minor": 2, "cells": [ { - "cell_type": "markdown", - "metadata": {}, "source": [ "# 量子神经网络初体验\n", "\n", @@ -50,7 +69,9 @@ "## 3. 环境准备\n", "\n", "导入本教程所依赖的模块" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -60,18 +81,18 @@ "source": [ "import numpy as np #导入numpy库并简写为np\n", "import mindquantum as mq #导入mindquantum库并简写为mq\n", - "from mindquantum.core import Circuit #导入Circuit模块,用于搭建量子线路\n", - "from mindquantum.core import H, RX, RY, RZ #导入量子门H, RX, RY, RZ" + "from mindquantum import Circuit #导入Circuit模块,用于搭建量子线路\n", + "from mindquantum.gate import H, RX, RY, RZ #导入量子门H, RX, RY, RZ" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "## 4. 搭建Encoder\n", "\n", "根据图示的量子线路图,我们可以在MindQuantum中搭建Encoder。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -81,40 +102,9 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "==================Circuit Summary==================\n", - "|Total number of gates : 4. |\n", - "|Parameter gates : 3. |\n", - "|with 3 parameters are : alpha0, alpha1, alpha2. |\n", - "|Number qubit of circuit: 1 |\n", - "===================================================\n" - ] - }, - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────RX(alpha0)────RY(alpha1)────RZ(alpha2)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────RX(alpha0)────RY(alpha1)────RZ(alpha2)──" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "text": "==================Circuit Summary==================\n|Total number of gates : 4. |\n|Parameter gates : 3. |\n|with 3 parameters are : alpha0, alpha1, alpha2. |\n|Number qubit of circuit: 1 |\n===================================================\n" } ], "source": [ @@ -124,19 +114,18 @@ "encoder += RY(f'alpha{1}').on(0) #RY(alpha_1)门作用在第0位量子比特\n", "encoder += RZ(f'alpha{2}').on(0) #RZ(alpha_2)门作用在第0位量子比特\n", "encoder = encoder.no_grad() #Encoder作为整个量子神经网络的第一层,不用对编码线路中的梯度求导数,因此加入no_grad()\n", - "encoder.summary() #总结Encoder\n", - "encoder" + "encoder.summary() #总结Encoder" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从对Encoder的Summary中可以看到,该量子线路由4个量子门组成,其中有3个含参量子门且参数为$\\alpha_0,\\alpha_1,\\alpha_2$​​​​,该量子线路调控的量子比特数为1。\n", "\n", "\n", "然后,我们需要对Encoder中的参数进行赋值。由于Encoder中的参数$\\alpha_0, \\alpha_1$​和$\\alpha_2$​分别为已知值0.2, 0.3和0.4,因此可以直接对参数进行赋值,并打印此时的状态。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -146,37 +135,36 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "(0.5669903122552596-0.1753906567580312j)¦0⟩\n", - "(0.800814626197614+0.08034947292077024j)¦1⟩\n" - ] + "name": "stdout", + "text": "(0.566990315914154-0.17539066076278687j)¦0⟩\n(0.8008146286010742+0.08034947514533997j)¦1⟩\n[WARNING] DEBUG(16155,7ffab96a0740,python):2021-09-14-07:41:08.515.366 [mindspore/ccsrc/debug/debugger/debugger.cc:88] Debugger] Not enabling debugger. Debugger does not support CPU.\n" } ], "source": [ + "from mindquantum.circuit import StateEvolution #导入StateEvolution模块,用于演化量子线路,计算末态\n", "\n", "alpha0, alpha1, alpha2 = 0.2, 0.3, 0.4 #alpha0, alpha1, alpha2为已知的固定值,分别赋值0.2, 0.3 和0.4\n", - "state = encoder.get_qs(pr={'alpha0': alpha0, 'alpha1': alpha1, 'alpha2': alpha2}, ket=True)\n", + "\n", + "state = (StateEvolution(encoder)).final_state({'alpha0': alpha0, 'alpha1': alpha1, 'alpha2': alpha2}, ket=True) #量子线路中的参数必须赋值,ket=True表示是否展示为量子态 \n", "print(state)" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "上述步骤为了展示MindQuantum可以演化量子线路(若量子线路中的量子门带参数,则需要对参数赋值)并得到演化后的末态。从上述打印可以看到,演化后得到的末态为$|0\\rangle$​​​和$|1\\rangle$​​​组成的叠加态,各项对应的振幅为上述打印的状态左边对应的数值。 \n", "\n", "说明:\n", "\n", - "(1)通过调用量子线路的`get_qs`函数,我们能够得到该量子线路在全零态基础上演化出来的量子态。\n", + "(1)StateEvolution模块用于演化量子线路,计算末态,一般格式如下:StateEvolution(circuit),括号中的circuit就是我们搭建的需要演化的量子线路,在上面的例子中,此时的Encoder我们上述的量子线路;\n", "\n", - "(2)`get_qs`的`pr`参数代表参数化量子线路中的参数值,`ket`表示是否将量子态输出为右矢形式。\n", + "(2)final_state模块用于展现初态经过给定的量子线路(即明确参数)后的末态,一般格式如下:final_state(param=None, ket=False),线路中的参数param必须要明确给出,ket=True表示是否展示量子态;\n", "\n", "## 5. 搭建Ansatz\n", "\n", "同样地,我们也可以在MindQuantum中搭建Ansatz。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -186,45 +174,26 @@ }, "outputs": [ { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──RX(theta0)────RY(theta1)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──RX(theta0)────RY(theta1)──" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "output_type": "stream", + "name": "stdout", + "text": "==============Circuit Summary==============\n|Total number of gates : 2. |\n|Parameter gates : 2. |\n|with 2 parameters are : theta0, theta1. |\n|Number qubit of circuit: 1 |\n===========================================\n" } ], "source": [ "ansatz = Circuit() #初始化量子线路\n", "ansatz += RX(f'theta{0}').on(0) #RX(theta_0)门作用在第0位量子比特\n", "ansatz += RY(f'theta{1}').on(0) #RY(theta_1)门作用在第0位量子比特\n", - "ansatz #打印量子线路" + "ansatz.summary() #总结Ansatz量子线路" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从对Ansatz的Summary中可以看到,该量子线路由2个量子门组成,其中有2个含参量子门且参数为$\\theta_0,\\theta_1$​​,该量子线路调控的量子比特数为1。\n", "\n", - "然后,对Ansatz中的参数进行赋值。由于Ansatz为需要训练的量子线路,因此Ansatz中的参数$\\theta_0$​​和$\\theta_1$​​可以随机设定,通常默认设为初始值0。我们同样可以打印此时的量子态,不过这并不是必要的步骤,只是为了再次熟悉一下`get_qs`函数。" - ] + "然后,对Ansatz中的参数进行赋值。由于Ansatz为需要训练的量子线路,因此Ansatz中的参数$\\theta_0$​​和$\\theta_1$​​可以随机设定,通常默认设为初始值0。我们同样可以打印此时的量子态,不过这并不是必要的步骤,只是为了再次熟悉一下如何使用StateEvolution模块中的final_state。" + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -234,27 +203,26 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "1¦0⟩\n" - ] + "name": "stdout", + "text": "1.0¦0⟩\n" } ], "source": [ "theta0, theta1 = 0, 0 #对theta0, theta1进行赋值,设为初始值0, 0\n", - "state = ansatz.get_qs(pr=dict(zip(ansatz.params_name, [theta0, theta1])), ket=True)\n", + "\n", + "state = (StateEvolution(ansatz)).final_state({'theta0': theta0, 'theta1': theta1}, ket=True) \n", "print(state)" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从上述打印可以看到,此时的状态为$|0\\rangle$​​且振幅为1。这是因为对于Ansatz来说,默认的输入量子态为$|0\\rangle$​​,而且其中的参数$\\theta_0$​​和$\\theta_1$​​都为0,此时的`RX(0)`门和`RY(0)`门都相当于`I`门,因此整个线路演化的过程就是$|0\\rangle$​​经过$I\\cdot I$,那么最后输出的态当然就是$|0\\rangle$​​​了。\n", "\n", "那么完整的量子线路就是Encoder加上Ansatz。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -264,45 +232,26 @@ }, "outputs": [ { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────RX(alpha0)────RY(alpha1)────RZ(alpha2)────RX(theta0)────RY(theta1)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────RX(alpha0)────RY(alpha1)────RZ(alpha2)────RX(theta0)────RY(theta1)──" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" + "output_type": "stream", + "name": "stdout", + "text": "==========================Circuit Summary==========================\n|Total number of gates : 6. |\n|Parameter gates : 5. |\n|with 5 parameters are : alpha1, alpha0, theta0, theta1, alpha2. |\n|Number qubit of circuit: 1 |\n===================================================================\n" } ], "source": [ "circuit = encoder + ansatz #完整的量子线路由Encoder和Ansatz组成\n", - "circuit" + "circuit.summary()" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从对完整的量子线路的Summary中可以看到,该量子线路由6个量子门组成,其中有5个含参量子门且参数为$\\alpha_0,\\alpha_1,\\alpha_2,\\theta_0,\\theta_1$​​​,该量子线路调控的量子比特数为1。\n", "\n", "## 6. 构建哈密顿量\n", "\n", "我们对第0位量子比特执行泡利`Z`算符测量,构建对应的哈密顿量。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -312,24 +261,20 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "-1 [Z0] \n" - ] + "name": "stdout", + "text": "-1 [Z0] \n" } ], "source": [ - "from mindquantum.core import QubitOperator #导入QubitOperator模块,用于构造泡利算符\n", - "from mindquantum.core import Hamiltonian #导入Hamiltonian模块,用于构建哈密顿量\n", + "from mindquantum.ops import QubitOperator #导入QubitOperator模块,用于构造泡利算符\n", + "from mindquantum.gate import Hamiltonian #导入Hamiltonian模块,用于构建哈密顿量\n", "\n", - "ham = Hamiltonian(QubitOperator('Z0', -1)) #对第0位量子比特执行泡利Z算符测量,且将系数设置为-1,构建对应的哈密顿量\n", + "ham = Hamiltonian(QubitOperator('Z0', -1)) #对第0位量子比特执行泡利Z算符测量,且将系数设置为-1,构建对应的哈密顿量\n", "print(ham)" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从上述打印可以看到,此时构建的哈密顿量为对第0位量子比特执行泡利`Z`算符测量,且系数为-1。之所以将系数设为-1,是因为在量子神经网络的训练中,Ansatz中的参数的梯度会一直下降,同时测量值也会一直减少。如果最后收敛于-1,那么此时对应的量子态是$|1\\rangle$而不是$|0\\rangle$​,如下所示\n", "$$\n", @@ -354,7 +299,9 @@ "\n", "\n", "首先,为了方便,我们对Encoder和Ansatz中的参数数组分别命名为encoder_names和ansatz_names。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -364,30 +311,27 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "encoder_names = ['alpha0', 'alpha1', 'alpha2'] \n", - "ansatz_names = ['theta0', 'theta1']\n" - ] + "name": "stdout", + "text": "encoder_names = ['alpha0', 'alpha1', 'alpha2'] \nansatz_names = ['theta0', 'theta1']\n" } ], "source": [ - "encoder_names = encoder.params_name # Encoder中所有参数组成的数组,encoder.para_name系统会自动生成\n", - "ansatz_names = ansatz.params_name # Ansatz中所有参数组成的数组,ansatz.para_name系统会自动生成\n", + "encoder_names = encoder.para_name #Encoder中所有参数组成的数组,encoder.para_name系统会自动生成\n", + "ansatz_names = ansatz.para_name #Ansatz中所有参数组成的数组,ansatz.para_name系统会自动生成\n", "\n", - "print('encoder_names = ', encoder.params_name, '\\nansatz_names =', ansatz.params_name)" + "print('encoder_names = ', encoder.para_name, '\\nansatz_names =', ansatz.para_name)" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从上述打印可以看到,encoder_names为Encoder中所有参数$\\alpha_0, \\alpha_1, \\alpha_2$​组成的数组,ansatz_names为Ansatz中所有参数$\\theta_0,\\theta_1$​组成的数组,这两个数组会在生成参数化量子线路模拟算子时用到。\n", "\n", "\n", - "然后,我们通过`Simulator`模块得到参数化量子线路演化和梯度求解的算子。" - ] + "然后,我们通过模块generate_pqc_operator生成一个参数化量子线路模拟算子。" + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -397,76 +341,54 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Measurement result: [[0.29552022+0.j]]\n", - "Gradient of encoder parameters: [[[0.+0.j 0.+0.j 0.+0.j]]]\n", - "Gradient of ansatz parameters: [[[-0.37202556+0.j 0.87992317+0.j]]]\n" - ] + "name": "stdout", + "text": "Measurement result: [[0.29552022]]\nGradient of encoder parameters: [[[0. 0. 0.]]]\nGradient of ansatz parameters: [[[-0.37202555 0.87992316]]]\n" } ], "source": [ - "# 导入Simulator模块\n", - "from mindquantum.simulator import Simulator\n", + "from mindquantum.nn import generate_pqc_operator #导入generate_pqc_operator模块\n", "\n", - "# 生成一个基于projectq后端的模拟器,并设置模拟器的比特数为量子线路的比特数。\n", - "sim = Simulator('projectq', circuit.n_qubits)\n", + "pqc = generate_pqc_operator(encoder_names, ansatz_names, circuit, ham) #模块generate_pqc_operator可生成参数化量子线路模拟算子\n", "\n", - "# 获取模拟器基于当前量子态的量子线路演化以及期望、梯度求解算子\n", - "grad_ops = sim.get_expectation_with_grad(ham,\n", - " circuit,\n", - " encoder_params_name=encoder_names,\n", - " ansatz_params_name=ansatz_names)\n", + "import mindspore as ms #导入mindspore库并简写为ms\n", + "from mindspore import Tensor #导入Tensor模块,用于数据储存\n", + "from mindspore import context #导入context模块,用于配置当前运行环境\n", + "\n", + "context.set_context(mode=context.GRAPH_MODE, device_target=\"CPU\") #模式:搭建静态训练图;要运行的目标设备:CPU(目前量子-经典混合神经网络只支持CPU的运行模式)\n", "\n", - "# Encoder中的alpha0, alpha1, alpha2这三个参数组成的数组,\n", - "# 将其数据类型转换为float32,并储存在encoder_data中。\n", - "# MindQuantum支持多样本的batch训练,Encoder数组是两个维度,\n", - "# 第一个维度为样本,第二个维度为特征(即参数)\n", - "encoder_data = np.array([[alpha0, alpha1, alpha2]]).astype(np.float32)\n", + "encoder_data = Tensor(np.array([[alpha0, alpha1, alpha2]]).astype(np.float32))#Encoder中的alpha0, alpha1, alpha2这三个参数组成的数组,将其数据类型转换为float32,并利用Tensor储存在encoder_data中\n", + " #MindQuantum支持多样本的batch训练,Encoder数组是两个维度,第一个维度为样本,第二个维度为特征(即参数)\n", "\n", - "# Ansatz中的theta0, theta1这两个参数组成的数组,将其数据类型转换为float32,\n", - "# 并储存在ansatzr_data中,Ansatz数据只有一个维度,特征(即参数)\n", - "ansatz_data = np.array([theta0, theta1]).astype(np.float32)\n", + "ansatz_data = Tensor(np.array([theta0, theta1]).astype(np.float32)) #Ansatz中的theta0, theta1这两个参数组成的数组,将其数据类型转换为float32,并利用Tensor储存在ansatzr_data中,Ansatz数据只有一个维度,特征(即参数)\n", "\n", - "# 根据Encoder和Ansatz的数据,输出参数化量子线路的测量值,Encoder中的参数的导数和Ansatz中的参数的导数\n", - "measure_result, encoder_grad, ansatz_grad = grad_ops(encoder_data, ansatz_data)\n", + "measure_result, encoder_grad, ansatz_grad = pqc(encoder_data, ansatz_data) #根据Encoder和Ansatz的数据,输出参数化量子线路的测量值,Encoder中的参数的导数和Ansatz中的参数的导数\n", "\n", - "print('Measurement result: ', measure_result)\n", - "print('Gradient of encoder parameters: ', encoder_grad)\n", - "print('Gradient of ansatz parameters: ', ansatz_grad)" + "print('Measurement result: ', measure_result.asnumpy())\n", + "print('Gradient of encoder parameters: ', encoder_grad.asnumpy())\n", + "print('Gradient of ansatz parameters: ', ansatz_grad.asnumpy())" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ - "从上述打印可以看到,测量结果(期望值)为0.29552022,Encoder中的3个参数的导数为0,0,0(因为我们对Encoder设置了no_grad()),Ansatz中的2个参数的导数为-0.37202555,-0.87992316。\n", + "从上述打印可以看到,测量结果(期望值)为2.9552022,Encoder中的3个参数的导数为0,0,0(因为我们对Encoder设置了no_grad()),Ansatz中的2个参数的导数为-0.37202555,-0.87992316。\n", "\n", - "这里通过`get_expectation_with_grad`产生的只是一个算子,还不能进行训练,要把它放到量子神经网络里面才能进行训练。通过训练Ansatz中的参数,可以使得Ansatz中的参数的导数一直下降并接近于0,那么测量值也就会接近于-1。\n", + "这里通过generate_pqc_operator产生的只是一个算子,还不能进行训练,要把它放到量子神经网络里面才能进行训练。通过训练Ansatz中的参数,可以使得Ansatz中的参数的导数一直下降并接近于0,那么测量值也就会接近于-1。\n", "\n", "说明:\n", "\n", - "(1)`Simulator`的`get_expectation_with_grad`用于生成参数化量子线路来模拟算子,一般格式如下:\n", - "\n", - "```python\n", - "Simulator.get_expectation_with_grad(ham,\n", - " circ_right,\n", - " circ_left,\n", - " encoder_params_name,\n", - " ansatz_params_name,\n", - " parallel_worker=1)\n", - "```\n", - "此函数适用于计算如下模型:\n", + "(1)generate_pqc_operator模块用于生成参数化量子线路来模拟算子,一般格式如下:mindquantum.nn.generate_pqc_operator(encoder_params_names, ansatz_params_names, circuit, measurements, n_threads=1),通常circuit为我们搭建的Encoder和Ansatz,measurements为我们构建的哈密顿量ham,n_threads为用于数据并行的线程数,默认值:1;\n", "\n", - "$$E=\\left<0\\right|U^\\dagger_l(\\theta) H U_r(\\theta)\\left|0\\right>$$\n", + "(2)mindspore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景覆盖三大目标,提供支持异构加速的张量可微编程能力,支持云、服务器、边和端多种硬件平台;\n", "\n", - "其中`circ_right`是$U_r$,`circ_left`是$U_l$,当不提供时,默认跟`circ_right`是相同的线路,`encoder_params_name`指定整个体系中哪些参数是属于编码器中的参数,编码器可以将经典数据通过feature mapping映射到高位希尔伯特空间中,`ansatz_params_name`指定整个体系中哪些参数是属于待训练线路中的参数,`parallel_worker`指定并行数,当需要编码的经典数据是一个batch时,合理设置此参数可以提高计算效率。\n", + "(3)Tensor模块主要用于数据储存;\n", "\n", - "(2)mindspore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景覆盖三大目标,提供支持异构加速的张量可微编程能力,支持云、服务器、边和端多种硬件平台;\n", + "(4)context模块用于配置当前运行环境;\n", "\n", "## 8. 搭建量子神经网络" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -474,71 +396,75 @@ "metadata": {}, "outputs": [ { + "output_type": "execute_result", "data": { - "text/plain": [ - "MQLayer<\n", - " (evolution): MQOps<1 qubit projectq VQA Operator>\n", - " >" - ] + "text/plain": "MindQuantumLayer<>" }, - "execution_count": 10, "metadata": {}, - "output_type": "execute_result" + "execution_count": 10 } ], "source": [ - "from mindquantum.framework import MQLayer # 导入MQLayer\n", - "import mindspore as ms # 导入mindspore\n", + "from mindquantum.nn import MindQuantumLayer #导入MindQuantumLayer\n", "\n", "ms.set_seed(1) #设置生成随机数的种子\n", - "ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target=\"CPU\")\n", - "\n", - "QuantumNet = MQLayer(grad_ops)\n", - "QuantumNet" + "Quantumnet = MindQuantumLayer(encoder_names, ansatz_names, circuit, ham, n_threads=1)\n", + "Quantumnet" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "上述打印可以看到,我们已经成功搭建了量子机器学习层,其可以无缝地跟MindSpore中其它的算子构成一张更大的机器学习网络。\n", "\n", "说明:\n", "\n", - "(1)MindQuantum中的量子线路梯度计算算子都是在`PYNATIVE_MODE`下的,因此需要设置MindSpore的运行模式。\n", + "(1)MindQuantumLayer模块可以生成可训练的MindQuantum量子机器学习层,一般格式如下:MindQuantumLayer(encoder_params_names, ansatz_params_names, circuit, measurements, weight_init=\"normal\", n_threads=1);\n", "\n", - "(2)我们也可以通过如下代码方式搭建量子机器学习层,只是在MindQuantum中,已经将下述过程封装打包,这样我们就可以直接利用MQLayer模块搭建量子机器学习层。对于更复杂的量子-经典混合神经网络,如下搭建方式会展示它的优势(将在以后的tutorials中介绍);" - ] + "(2)我们也可以通过如下代码方式搭建量子机器学习层,只是在MindQuantum中,已经将下述过程封装打包,这样我们就可以直接利用MindQuantumLayer模块搭建量子机器学习层。对于更复杂的量子-经典混合神经网络,如下搭建方式会展示它的优势(将在以后的tutorials中介绍);" + ], + "cell_type": "markdown", + "metadata": {} }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "```python\n", - "class MQLayer(nn.Cell):\n", - " def __init__(self, expectation_with_grad, weight='normal'):\n", - " super(MQLayer, self).__init__()\n", - " self.evolution = MQOps(expectation_with_grad)\n", - " weight_size = len(\n", - " self.evolution.expectation_with_grad.ansatz_params_name)\n", - " self.weight = Parameter(initializer(weight,\n", - " weight_size,\n", - " dtype=ms.float32),\n", - " name='ansatz_weight')\n", - "\n", + "class MindQuantumLayer(nn.Cell):\n", + " def __init__(self,\n", + " encoder_params_names,\n", + " ansatz_params_names,\n", + " circuit,\n", + " measurements,\n", + " weight_init='normal',\n", + " n_threads=1):\n", + " super(MindQuantumLayer, self).__init__()\n", + " self.circuit = circuit\n", + " self.measurements = measurements\n", + " self.encoder_params_names = encoder_params_names\n", + " self.ansatz_params_names = ansatz_params_names\n", + " self.pqc = generate_pqc_operator(encoder_params_names,\n", + " ansatz_params_names,\n", + " circuit,\n", + " measurements,\n", + " n_threads=n_threads)\n", + " self.weight = Parameter(initializer(weight_init,\n", + " len(ansatz_params_names)),\n", + " name=\"weight\")\n", " def construct(self, x):\n", - " return self.evolution(x, self.weight)\n", + " x, _, _ = self.pqc(x, self.weight)\n", + " return x\n", "```" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "## 9. 训练\n", "\n", "我们采用Adam优化器优化Ansatz中的参数。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -548,48 +474,25 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "0 : [[0.2837115]]\n", - "10 : [[-0.8851233]]\n", - "20 : [[-0.97001773]]\n", - "30 : [[-0.9929431]]\n", - "40 : [[-0.9939507]]\n", - "50 : [[-0.9967015]]\n", - "60 : [[-0.99878186]]\n", - "70 : [[-0.9995535]]\n", - "80 : [[-0.9999011]]\n", - "90 : [[-0.99998033]]\n", - "100 : [[-0.9999989]]\n", - "110 : [[-0.99999785]]\n", - "120 : [[-0.999997]]\n", - "130 : [[-0.9999987]]\n", - "140 : [[-0.9999998]]\n", - "150 : [[-1.]]\n", - "160 : [[-0.99999994]]\n", - "170 : [[-1.]]\n", - "180 : [[-1.]]\n", - "190 : [[-1.]]\n" - ] + "name": "stdout", + "text": "0 : [[0.2837115]]\n10 : [[-0.8851233]]\n20 : [[-0.97001773]]\n30 : [[-0.9929431]]\n40 : [[-0.9939507]]\n50 : [[-0.9967015]]\n60 : [[-0.99878186]]\n70 : [[-0.9995535]]\n80 : [[-0.9999011]]\n90 : [[-0.99998033]]\n100 : [[-0.9999989]]\n110 : [[-0.99999785]]\n120 : [[-0.999997]]\n130 : [[-0.9999987]]\n140 : [[-0.9999998]]\n150 : [[-1.]]\n160 : [[-0.99999994]]\n170 : [[-1.]]\n180 : [[-1.]]\n190 : [[-1.]]\n" } ], "source": [ "from mindspore import nn #导入nn模块,nn即经典神经网络\n", "from mindspore.nn import Adam, TrainOneStepCell #导入Adam模块和TrainOneStepCell模块\n", "\n", - "opti = Adam(QuantumNet.trainable_params(), learning_rate=0.5) #需要优化的是Quantumnet中可训练的参数,学习率设为0.5 \n", - "net = TrainOneStepCell(QuantumNet, opti)\n", + "opti = Adam(Quantumnet.trainable_params(), learning_rate=0.5) #需要优化的是Quantumnet中可训练的参数,学习率设为0.5 \n", + "net = TrainOneStepCell(Quantumnet, opti)\n", "\n", "for i in range(200):\n", - " res = net(ms.Tensor(encoder_data))\n", + " res = net(Tensor(encoder_data))\n", " if i % 10 == 0:\n", " print(i, ': ', res)" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从上述打印可以看到,最后测量值收敛于-1。\n", "\n", @@ -602,7 +505,9 @@ "## 10. 结果呈现\n", "\n", "由于测量值已经收敛于-1,所以我们可以打印此时Ansatz中的参数。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -612,27 +517,25 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "[ 2.2420275 -1.0756909]\n" - ] + "name": "stdout", + "text": "[ 2.2420275 -1.0756909]\n" } ], "source": [ - "theta0, theta1 = QuantumNet.weight.asnumpy()\n", + "theta0, theta1 = Quantumnet.weight.asnumpy()\n", "\n", - "print(QuantumNet.weight.asnumpy())" + "print(Quantumnet.weight.asnumpy())" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从上述打印可以看到,此时Ansatz中的参数$\\theta_1, \\theta_2$分别为2.2420275和-1.0756909。\n", "\n", - "通过get_qs,可以输出量子线路在最优参数时的量子态。" - ] + "通过StateEvolution模块的final_state,可以输出量子线路在最优参数时的量子态。" + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -642,27 +545,24 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "(0.37129760050057437-0.9285139157007681j)¦0⟩\n", - "(1.4564552975271372e-05+6.455516706194153e-07j)¦1⟩\n" - ] + "name": "stdout", + "text": "(0.37129759788513184-0.9285139441490173j)¦0⟩\n(1.4565440324076917e-05+6.52097298825538e-07j)¦1⟩\n" } ], "source": [ "pr = {'alpha0': alpha0, 'alpha1': alpha1, 'alpha2': alpha2, 'theta0': theta0, 'theta1': theta1}\n", - "state = circuit.get_qs(pr=pr, ket=True)\n", + "state = StateEvolution(circuit).final_state(pr, ket=True)\n", "\n", "print(state)" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "从上述打印可以看到,这就是量子线路在最优参数时的量子态。从其数值表示可以看到,这是一个接近于目标态$|0\\rangle$​​​的态。最后,我们计算一下此量子态与目标态$|0\\rangle$​​​​​的保真度(用于验证两个量子态的相似程度),并将保真度打印。" - ] + ], + "cell_type": "markdown", + "metadata": {} }, { "cell_type": "code", @@ -672,23 +572,19 @@ }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "0.9999999997874571\n" - ] + "name": "stdout", + "text": "1.0000000506744333\n" } ], "source": [ - "state = circuit.get_qs(pr=pr)\n", + "state = StateEvolution(circuit).final_state(pr)\n", "fid = np.abs(np.vdot(state, [1, 0]))**2 #保真度fidelity为向量内积的绝对值的模平方,即计算此时量子态对应的向量与|0>态对应的向量[1,0]的内积的模平方\n", "\n", "print(fid)" ] }, { - "cell_type": "markdown", - "metadata": {}, "source": [ "\n", "可以看到,此时的保真度为100.00%,也就是说,该状态与目标态$|0\\rangle$​​的相似程度为100.00%。\n", @@ -698,28 +594,9 @@ "至此,我们通过MindQuantum完成了对量子神经网络的初体验!赶紧动手体验一下量子编程的乐趣吧!\n", "\n", "若想查询更多关于MindQuantum的API,请点击:[https://mindspore.cn/mindquantum/](https://mindspore.cn/mindquantum/)。" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" + ], + "cell_type": "markdown", + "metadata": {} } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + ] +} \ No newline at end of file diff --git a/tutorials/3.classification_of_iris__by_qnn.ipynb b/tutorials/3.classification_of_iris__by_qnn.ipynb new file mode 100644 index 000000000..c46613383 --- /dev/null +++ b/tutorials/3.classification_of_iris__by_qnn.ipynb @@ -0,0 +1,664 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 通过量子神经网络对鸢尾花进行分类\n", + "\n", + "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", + "\n", + "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/parameterized_quantum_circuit.ipynb) [![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/view_mindquantum_api.png)](https://mindspore.cn/mindquantum/api/zh-CN/master/index.html)\n", + "\n", + "## 1. 概述\n", + "\n", + "在之前的案例中,我们介绍了什么是参数化量子线路并通过一个简单的例子体验了如何搭建量子神经网络来解决一个小问题。在本教案中,我们将体验升级,将会介绍如何通过搭建量子神经网络来解决经典机器学习中的问题。我们选取的问题是:监督学习中的鸢尾花分类问题。\n", + "\n", + "问题描述:鸢尾花(iris)数据集是经典机器学习中常用的数据集,该数据集总共包含150个样本(分为3种不同的亚属:山鸢尾(setosa)、杂色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),每个亚属各有50个样本),每个样本包含4个特征,分别为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。\n", + "\n", + "我们选取前100个样本(山鸢尾(setosa)和杂色鸢尾(versicolor)),并随机抽取80个样本作为训练集,通过搭建量子神经网络对量子分类器(Ansatz)进行训练,学习完成后,对剩余的20个样本进行分类测试,期望预测的准确率尽可能高。\n", + "\n", + "思路:我们需要将100个样本进行划分,分成80个训练样本和20个测试样本,根据训练样本的经典数据计算搭建Encoder所需的参数,然后,搭建Encoder,将训练样本的经典数据编码到量子态上,接着,搭建Ansatz,通过搭建的量子神经网络层和MindSpore的算子对Ansatz中的参数进行训练,进而得到最终的分类器,最后,对剩余的20个测试样本进行分类测试,得到预测的准确率。\n", + "\n", + "## 2. 环境准备\n", + "\n", + "首先,我们需要导入鸢尾花的数据集,而在导入该数据集前,我们需要使用sklearn库中的datasets模块,因此读者需要检查是否安装了sklearn库,可执行如下代码检查。" + ] + }, + { + "source": [ + "pip show scikit-learn" + ], + "cell_type": "code", + "metadata": { + "tags": [] + }, + "execution_count": 1, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "Name: scikit-learn\nVersion: 0.24.2\nSummary: A set of python modules for machine learning and data mining\nHome-page: http://scikit-learn.org\nAuthor: \nAuthor-email: \nLicense: new BSD\nLocation: /usr/local/lib/python3.7/dist-packages\nRequires: numpy, scipy, joblib, threadpoolctl\nRequired-by: \nNote: you may need to restart the kernel to use updated packages.\n" + } + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "若无报错,则表明已安装。简单说明一下,sklearn是scikit-learn的简称,是一个基于Python的第三方模块。sklearn库集成了一些常用的机器学习方法,在进行机器学习任务时,并不需要实现算法,只需要简单的调用sklearn库中提供的模块就能完成大多数的机器学习任务。\n", + "\n", + "若未安装sklearn库,则可通过运行如下代码来安装。" + ] + }, + { + "source": [ + "```python\n", + "!pip install scikit-learn\n", + "```" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后,我们设置本教程所需的线程数。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os #导入os库\n", + "os.environ['OMP_NUM_THREADS'] = '2' #通过os.environ将量子线路模拟器的线程数设置为2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "说明:\n", + "\n", + "(1)os是一个标准库,里面包含许多操作文件和目录的函数;\n", + "\n", + "(2)os.environ()模块,可以获取并修改环境变量;一般来说,我们需要在一开始设置线程数;\n", + "\n", + "## 3. 导入鸢尾花数据集\n", + "\n", + "有了上述准备,现在我们就可以导入鸢尾花的数据集了。" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(150, 4)\n['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']\n['setosa' 'versicolor' 'virginica']\n[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2\n 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2\n 2 2]\n(150,)\n" + } + ], + "source": [ + "import numpy as np #导入numpy库并简写为np\n", + "from sklearn import datasets #导入datasets模块,用于加载鸢尾花的数据集\n", + "\n", + "iris_dataset = datasets.load_iris() #加载鸢尾花的数据集,并存在iris_dataset\n", + "\n", + "print(iris_dataset.data.shape) #打印iris_dataset的样本的数据维度\n", + "print(iris_dataset.feature_names) #打印iris_dataset的样本的特征名称\n", + "print(iris_dataset.target_names) #打印iris_dataset的样本包含的亚属名称\n", + "print(iris_dataset.target) #打印iris_dataset的样本的标签的数组\n", + "print(iris_dataset.target.shape) #打印iris_dataset的样本的标签的数据维度" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从上述打印可以看到,该数据集共有150个样本,每个样本均有4个特征,分别为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。同时样本包含3种不同的亚属:山鸢尾(setosa)、杂色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),每个样本有对应的分类编号,0表示样本属于setosa,1表示样本属于versicolor,2表示样本属于virginica,因此有一个由150个数字组成的数组来表示样本的亚属类型。\n", + "\n", + "由于我们只选取前100个样本,因此执行如下命令。" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(100, 4)\n['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']\n['setosa' 'versicolor']\n[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]\n(100,)\n" + } + ], + "source": [ + "X = iris_dataset.data[:100, :].astype(np.float32) #选取iris_dataset的data的前100个数据,将其数据类型转换为float32,并储存在X中\n", + "X_feature_names = iris_dataset.feature_names #将iris_dataset的特征名称储存在X_feature_names中\n", + "y = iris_dataset.target[:100].astype(int) #选取iris_dataset的target的前100个数据,将其数据类型转换为int,并储存在y中\n", + "y_target_names = iris_dataset.target_names[:2] #选取iris_dataset的target_names的前2个数据,并储存在y_target_names中\n", + "\n", + "print(X.shape) #打印样本的数据维度\n", + "print(X_feature_names) #打印样本的特征名称\n", + "print(y_target_names) #打印样本包含的亚属名称\n", + "print(y) #打印样本的标签的数组\n", + "print(y.shape) #打印样本的标签的数据维度" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从上述打印可以看到,此时的数据集`X`中只有100个样本,每个样本依然有4个特征,仍为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。此时只有2种不同的亚属:山鸢尾(setosa)和杂色鸢尾(versicolor),并且每一个样本有对应的分类编号,0表示它属于setosa,1表示它属于versicolor,因此有一个由100个数字组成的数组来表示样本的亚属类型。\n", + "\n", + "## 4. 数据图像化\n", + "\n", + "为了更加直观地了解这100个样本组成的数据集,我们画出所有样本不同特征之间组成的散点图,执行如下命令。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n \n \n \n \n 2021-09-14T08:04:12.218767\n image/svg+xml\n \n \n Matplotlib v3.4.3, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "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EREREam1lCAVERERERERERGRWksJUhEREREREREREam1lCAVERERERERERGRWksJUhEREREREREREam1lCAVERERERERERGRWksJUhGRasjMXjOzjWY2r4TjZmYjzGyZmc01s4PCHaOIiIiIiIhIdaAEqYhI9fQGMLCU4ycBnfNvw4GXwhCTiIiIiIiISLWjBKmISDXknJsCbC2lymnAW84zDahvZs3DE52IiIiIiIhI9aEEqYhIzdQS+KvI4zX5ZSIiIiIiIiJSREykAwiVxo0bu3bt2kU6DBGpYWbPnr3ZOdck0nFUJjMbjjcNn+Tk5IO7du0a4YhEpKapiX1nUfrcKSKhoL5TRKT8Ktp31tgEabt27Zg1a1akwxCRGsbMVkU6hjJaC7Qu8rhVflkA59woYBRA7969nfpOEals1ajvrBB97hSRUFDfKSJSfhXtOzXFXkSkZhoPXJy/m/1hwA7n3PpIByUiIiIiIiJS1dTYEaQiIjWZmY0F+gGNzWwNcB8QC+CcGwlMAE4GlgHpwKWRiVRERERERESkalOCVESkGnLODdnDcQdcG6ZwRERERERERKotTbEXERERERERERGRWksJUhEREREREREREam1lCAVERERERERERGRWktrkEqt5Zxj+trpTF09lWYpzTij2xkkxSZFOiwRERGRWi3Xl8uEpRNYsmUJ+zXdj+M7Hk+UaVyHiEhtsyltEx8v/JjM3ExO2fcUOjXsFOmQpAaLeILUzLoA7xUp6gD82zn3TJE6BjyLtyNzOjDUOfdrOOOUmiXXl8vp407nhz9/IMeXQ1x0HNdPvJ4fhv7A/vvsH+nwRERERGql9bvW0/e1vmxO30xmbiYJMQm0q9+OKZdOoX5C/UiHJyIiYfLxwo+56OOLMDN8zsc/v/sntx1xGw8e+2CkQ5MaKuKXYp1zi51zBzrnDgQOxkuAflKs2klA5/zbcOClsAYpNc7Ls19m8p+TSctJIzsvm9TsVLZlbuOs98/C2/xbRERERMLtyi+u5K8df7Erexc5vhx2Ze9i8ZbF3DnpzkiHJiIiYbI9czsXfXwRGbkZpOekk5mbSUZuBk/98hQz1s6IdHhSQ0U8QVrMccBy59yqYuWnAW85zzSgvpk1D394UlO8+uurpOekB5Sv27WOpVuXRiAiERERkdotz5fHxGUTyXW5fuXZedmMmz8u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ldzZObca6AtciddIdgWGA4fhNZIPAmeUOCYHOAmYDOwHjAIuAgb6t+f739+H1+ifABwJzAAO9dcTds65HcBxwPn+ujvidZ0/Cu//zdPAic657BBVeQleo7oI73pPBH4G+gNr/ftsCnLcGcBbeH9nI/F+lieHKCYRKR+1nXviVNspIiIiEh6659wTp+45JeTMudIW4pLawswcgHMunCvNSSWZ2ed4jf9w59y7e9tfRCJDbWfVprZTREREagLdc1ZtuuesOdSDVKQKMLOuZpZSoizezP6F19huxBseISIifmo7RURERCTcdM9ZO2gOUpGqYRxwupn9gDeZcwPgEKAV3jwlFxadU0ZERAC1nSIiIiISfrrnrAWUIBWpGl4H6uBNtN0b7//mOuBl4AHn3E9RjE1EpKpS2ykiIiIi4aZ7zlpAc5CKiIiIiIiIiIhIraU5SEVERERERERERKTWUoJUREREREREREREai0lSEVERERERERERKTWUoJUREREREREREREai0lSEVERERERERERKTWUoJUREREREREREREai0lSEVERERERERERKTWUoJUREREREREREREai0lSEVERERERERERKTWUoJURERERERERIIys4lmtsHMFpay3czsUTNbZmYLzKx3pGMUEdlXSpCKiIiIiIiISGleBE4qY/tgoJP/NQZ4MgIxiYiElBKkIiLVmJnFmtk8M/soyLYLzWyjmc33vy6ORowiIiIiUn0552YAW8rYZRjwsvPMBhqYWcvIRCciEhpx0Q4gXJo0aeI6dOgQ7TBEpIaZO3fuJudc02jHUcQ1wM9AvVK2v+mcu7K8J1PbKSLhUAXbzpBS2yki4VCN2s7WwB9FPq/2l60ruaOZjcHrZUpqauqhnTt3jkiAIlJ7VLbtrLEJ0g4dOpCenh7tMESkhjGzVdGOoZCZtQFOBu4Crg3FOdV2ikg4VKW2MxzUdopIONTEttM59wzwDEBaWppT2ykioVbZtlND7EVEqq+HgX8AvjL2OdM/Wf47ZtY22A5mNsbM0s0sfePGjeGIU0RERERqrjVA0fvMNv4yEZFqQwlSEZFqyMxOATY45+aWsduHQAfnXHfgM+ClYDs5555xzqU559KaNq0Oo7hEREREpAqZBJzvX82+P7DdORcwvF5EpCqrsUPsRURquAHAUDMbAiQB9czsVefcqMId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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ], + "source": [ + "import matplotlib.pyplot as plt #导入matplotlib.pyplot模块并简写为plt\n", + "\n", + "feature_name = {0: 'sepal length', 1: 'sepal width', 2: 'petal length', 3: 'petal width'} #将不同的特征名称分别标记为0,1,2,3\n", + "axes = plt.figure(figsize=(23, 23)).subplots(4, 4) #画出一个大小为23*23的图,包含4*4=16个子图\n", + "\n", + "colormap = {0: 'r', 1: 'g'} #将标签为0的样本设为红色,标签为1的样本设为绿色\n", + "cvalue = [colormap[i] for i in y] #将100个样本对应的标签设置相应的颜色\n", + "\n", + "for i in range(4):\n", + " for j in range(4):\n", + " if i!= j:\n", + " ax = axes[i][j] #在[i][j]的子图上开始画图\n", + " ax.scatter(X[:, i], X[:, j], c=cvalue) #画出第[i]个特征和第[j]个特征组成的散点图\n", + " ax.set_xlabel(feature_name[i], fontsize=22) #设置X轴的名称为第[i]个特征名称,字体大小为22\n", + " ax.set_ylabel(feature_name[j], fontsize=22) #设置Y轴的名称为第[j]个特征名称,字体大小为22\n", + "plt.show() #渲染图像,即呈现图像" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从上述呈现的图像可以看到,红色的点表示标签为“0”的样本,绿色的点表示标签为“1”的样本,另外,我们发现,这两类样本的不同特征还是比较容易区分的。\n", + "\n", + "## 5. 数据预处理\n", + "\n", + "接下来,我们需要计算生成搭建Encoder时所要用到的参数,然后将数据集划分为训练集和测试集,执行如下命令。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(100, 7)\n" + } + ], + "source": [ + "alpha = X[:, :3] * X[:, 1:] #每一个样本中,利用相邻两个特征值计算出一个参数,即每一个样本会多出3个参数(因为有4个特征值),并储存在alpha中\n", + "X = np.append(X, alpha, axis=1) #在axis=1的维度上,将alpha的数据值添加到X的特征值中\n", + "\n", + "print(X.shape) #打印此时X的样本的数据维度" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从上述打印可以看到,此时的数据集`X`中仍有100个样本,但此时每个样本却有7个特征,前4个特征值就是原来的特征值,后3个特征值就是通过上述预处理计算得到的特征值,其具体计算公式如下:\n", + "$$\n", + "X_{i+4}^{j} = X_{i}^{j} * X_{i+1}^{j}, i=0,1,2,j=1,2,...,100.\n", + "$$\n", + "最后,我们将此时的数据集分为训练集和测试集,执行如下命令。" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(80, 7)\n(20, 7)\n" + } + ], + "source": [ + "from sklearn.model_selection import train_test_split #导入train_test_split函数,用于对数据集进行划分\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, shuffle=True) #将数据集划分为训练集和测试集\n", + " \n", + "print(X_train.shape) #打印训练集中样本的数据类型\n", + "print(X_test.shape) #打印测试集中样本的数据类型" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从上述打印可以看到,此时的训练集有80个样本,测试集有20个样本,每个样本均有7个特征。\n", + "\n", + "说明:\n", + "\n", + "(1)append主要用于为原始数组添加一些值,一般格式如下:np.append(arr, values, axis=None),arr就是需要被添加值的数组,values就是添加到数组arr中的值,axis表示沿着那个方向;\n", + "\n", + "(2)shuffle=True表示将数据集打乱,每次都会以不同的顺序返回, shuffle就是为了避免数据投入的顺序对网络训练造成影响。增加随机性,提高网络的泛化性能,避免因为有规律的数据出现,导致权重更新时的梯度过于极端,避免最终模型过拟合或欠拟合。\n", + "\n", + "(3)train_test_split是交叉验证中常用的函数,主要用于是从样本中随机的按比例选取train data和test data,一般格式如下:\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size, random_state, shuffle=True),其中test_size表示测试样本的比例,random_state表示产生随机数的种子,shuffle=True表示将数据集打乱;\n", + "\n", + "## 6. 搭建Encoder\n", + "\n", + "根据图示的量子线路图,我们可以在MindQuantum中搭建Encoder,将经典数据编码到量子态上。\n", + "\n", + "![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/encoder_classification_of_iris_by_qnn.png)\n", + "\n", + "在这里,我们采用的编码方式是IQP编码(Instantaneous Quantum Polynomial encoding),一般来说Encoder的编码方式不固定,可根据问题需要选择不同的编码方式,有时也会根据最后的性能对Encoder进行调整。\n", + "\n", + "Encoder中的参数$\\alpha_0,\\alpha_1,...,\\alpha_6$​​的值,就是用上述数据预处理中得到的7个特征值代入。​" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "==================================Circuit Summary==================================\n|Total number of gates : 17. |\n|Parameter gates : 7. |\n|with 7 parameters are : alpha2, alpha1, alpha6, alpha3, alpha5, alpha4, alpha0. |\n|Number qubit of circuit: 4 |\n===================================================================================\n" + } + ], + "source": [ + "import mindquantum as mq #导入mindquantum库并简写为mq\n", + "from mindquantum import Circuit #导入Circuit模块,用于搭建量子线路\n", + "from mindquantum.circuit import UN #导入UN模块\n", + "from mindquantum.gate import H, X, RZ #导入量子门H, X, RZ\n", + "\n", + "encoder = Circuit() #初始化量子线路\n", + "\n", + "encoder += UN(H, 4) #H门作用在每1位量子比特 \n", + "for i in range(4): #i = 0, 1, 2, 3 \n", + " encoder += RZ(f'alpha{i}').on(i) #RZ(alpha_i)门作用在第i位量子比特\n", + "for j in range(3): #j = 0, 1, 2\n", + " encoder += X.on(j+1, j) #X门作用在第j+1位量子比特,受第j位量子比特控制\n", + " encoder += RZ(f'alpha{j+4}').on(j+1) #RZ(alpha_{j+4})门作用在第0位量子比特 \n", + " encoder += X.on(j+1, j) #X门作用在第j+1位量子比特,受第j位量子比特控制\n", + " \n", + "encoder = encoder.no_grad() #Encoder作为整个量子神经网络的第一层,不用对编码线路中的梯度求导数,因此加入no_grad()\n", + "encoder.summary() #总结Encoder" + ] + }, + { + "source": [ + "从对Encoder的Summary中可以看到,该量子线路由17个量子门组成,其中有7个含参量子门且参数为$\\alpha_0,\\alpha_1,...,\\alpha_6$,该量子线路调控的量子比特数为4。\n", + "\n", + "说明:\n", + "\n", + "(1)UN模块用于将量子门映射到不同的目标量子比特和控制量子比特,一般格式如下:mindquantum.circuit.UN(gate, maps_obj, maps_ctrl=None),括号中的gate就是我们需要执行的量子门,maps_obj就是需要执行该量子门的目标量子比特,maps_ctrl就是控制量子比特,若为None即无控制量子位。若每个量子比特位执行同一个非参数量子门,则可以直接写出UN(gate, N),N表示量子比特个数;\n", + "\n", + "## 7. 搭建Ansatz\n", + "\n", + "根据图示的量子线路图,我们可以在MindQuantum中搭建Ansatz。\n", + "\n", + "![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/ansatz_classification_of_iris_by_qnn.png)\n", + "\n", + "与Encoder一样,Ansatz的编码方式也不固定,我们可以尝试不同的编码方式来测试最后的结果。\n", + "\n", + "在这里,我们采用的是HardwareEfficientAnsatz,即上述量子线路图所示的编码方式。" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "====================================================Circuit Summary====================================================\n|Total number of gates : 25. |\n|Parameter gates : 16. |\n|with 16 parameters are : d0_n2_0, d1_n0_0, d2_n1_0, d3_n2_0, d2_n2_0, d3_n1_0, d0_n1_0, d1_n2_0, d2_n0_0, d3_n3_0... |\n|Number qubit of circuit: 4 |\n=======================================================================================================================\n" + } + ], + "source": [ + "from mindquantum.ansatz import HardwareEfficientAnsatz #导入HardwareEfficientAnsatz\n", + "from mindquantum.gate import RY #导入量子门RY\n", + "\n", + "ansatz = HardwareEfficientAnsatz(4, single_rot_gate_seq=[RY], entangle_gate=X, depth=3).circuit #通过HardwareEfficientAnsatz搭建Ansatz\n", + "ansatz.summary() #总结Ansatz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "source": [ + "从对Ansatz的Summary中可以看到,该量子线路由25个量子门组成,其中有16个含参量子门且参数为d2_n3_0, d1_n1_0, d0_n2_0, d1_n0_0, d3_n2_0, d2_n2_0, d0_n1_0, d3_n1_0, d2_n0_0, d3_n0_0...,该量子线路调控的量子比特数为4。\n", + "\n", + "说明:\n", + "\n", + "(1)HardwareEfficientAnsatz是一种容易在量子芯片上实现的Ansatz,其量子线路图由红色虚线框内的量子门组成,一般格式如下:mindquantum.ansatz.HardwareEfficientAnsatz(n_qubits, single_rot_gate_seq, entangle_gate=X, entangle_mapping=\"linear\", depth=1),括号中的n_qubits表示ansatz需要作用的量子比特总数,single_rot_gate_seq表示一开始每一位量子比特执行的参数门,同时后面需要执行的参数门也固定了,只是参数不同,entangle_gate=X表示执行的纠缠门为X,entangle_mapping=\"linear\"表示纠缠门将作用于每对相邻量子比特,depth表示黑色虚线框内的量子门需要重复的次数;\n", + "\n", + "那么完整的量子线路就是Encoder加上Ansatz。" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "==================================================Circuit Summary==================================================\n|Total number of gates : 42. |\n|Parameter gates : 23. |\n|with 23 parameters are : d2_n2_0, d3_n1_0, d3_n3_0, alpha3, alpha5, alpha4, d1_n1_0, d3_n2_0, d1_n2_0, d0_n3_0...|\n|Number qubit of circuit: 4 |\n===================================================================================================================\n" + } + ], + "source": [ + "circuit = encoder + ansatz #完整的量子线路由Encoder和Ansatz组成\n", + "circuit.summary()" + ] + }, + { + "source": [ + "从对完整的量子线路的Summary中可以看到,该量子线路由42个量子门组成,其中有23个含参量子门且参数为$\\alpha_0,\\alpha_1,...,\\alpha_6$和d2_n3_0, d1_n1_0, d0_n2_0, d1_n0_0, d3_n2_0, d2_n2_0, d0_n1_0, d3_n1_0, d2_n0_0, d3_n0_0...,该量子线路调控的量子比特数为4。\n", + "\n", + "## 8. 构建哈密顿量\n", + "\n", + "我们分别对第2位和第3位量子比特执行泡利`Z`算符测量,构建对应的哈密顿量。" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "[1.0 [Z2] , 1.0 [Z3] ]\n" + } + ], + "source": [ + "from mindquantum.ops import QubitOperator #导入QubitOperator模块,用于构造泡利算符\n", + "from mindquantum.gate import Hamiltonian #导入Hamiltonian模块,用于构建哈密顿量\n", + "\n", + "hams = [Hamiltonian(QubitOperator(f'Z{i}')) for i in [2, 3]] #分别对第2位和第3位量子比特执行泡利Z算符测量,且将系数都设为1,构建对应的哈密顿量\n", + "print(hams)" + ] + }, + { + "source": [ + "从上述打印可以看到,此时构建的哈密顿量有2个,分别为对第2位和第3位量子比特执行泡利`Z`算符,且将系数都设为1。通过泡利`Z`算符测量,我们可以得到2个哈密顿量测量值,若第1个测量值更大,则会将此样本归类到标签为“0”的类,同理,若第2个测量值更大,则会将此样本归类到标签为“1”的类。通过神经网络的训练,期望训练样本中标签为“0”的样本的第1个测量值更大,而标签为“1”的样本的第2个测量值更大,最后应用此模型来预测新样本的分类。\n", + "\n", + "## 9. 搭建量子神经网络" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "MindQuantumLayer<>" + }, + "metadata": {}, + "execution_count": 12 + } + ], + "source": [ + "import mindspore as ms #导入mindspore库并简写为ms\n", + "from mindquantum.nn import MindQuantumLayer #导入MindQuantumLayer\n", + "\n", + "ms.set_seed(1) #设置生成随机数的种子\n", + "Quantumnet = MindQuantumLayer(encoder.para_name, ansatz.para_name, circuit, hams, n_threads=5) #搭建量子神经网络\n", + "Quantumnet" + ] + }, + { + "source": [ + "从上述打印可以看到,我们已经成功搭建了量子机器学习层,其可以无缝地跟MindSpore中其它的算子构成一张更大的机器学习网络。\n", + "\n", + "说明:\n", + "\n", + "(1)mindspore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景覆盖三大目标,提供支持异构加速的张量可微编程能力,支持云、服务器、边和端多种硬件平台;\n", + "\n", + "(2)MindQuantumLayer模块可以生成可训练的MindQuantum量子机器学习层,一般格式如下:MindQuantumLayer(encoder_params_names, ansatz_params_names, circuit, measurements, weight_init=\"normal\", n_threads=5);\n", + "\n", + "## 10. 训练\n", + "\n", + "接下来,我们需要定义损失函数,设定需要优化的参数,然后将搭建好的量子机器学习层和MindSpore的算子组合,构成一张更大的机器学习网络,最后对该模型进行训练。" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stderr", + "text": "[WARNING] DEBUG(14679,7f6e696a1740,python):2021-09-14-08:04:15.835.881 [mindspore/ccsrc/debug/debugger/debugger.cc:88] Debugger] Not enabling debugger. Debugger does not support CPU.\nepoch: 1 step: 16, loss is 0.6600145\nepoch: 2 step: 16, loss is 0.40091023\nepoch: 3 step: 16, loss is 0.39099234\nepoch: 4 step: 16, loss is 0.3733629\nepoch: 5 step: 16, loss is 0.3705962\nepoch: 6 step: 16, loss is 0.3742624\nepoch: 7 step: 16, loss is 0.37181872\nepoch: 8 step: 16, loss is 0.37131247\nepoch: 9 step: 16, loss is 0.37142646\nepoch: 10 step: 16, loss is 0.3706742\nepoch: 11 step: 16, loss is 0.3701976\nepoch: 12 step: 16, loss is 0.36975253\nepoch: 13 step: 16, loss is 0.36923733\nepoch: 14 step: 16, loss is 0.36880013\nepoch: 15 step: 16, loss is 0.36840618\nepoch: 16 step: 16, loss is 0.36804128\nepoch: 17 step: 16, loss is 0.36773998\nepoch: 18 step: 16, loss is 0.36747772\nepoch: 19 step: 16, loss is 0.36726192\nepoch: 20 step: 16, loss is 0.36708587\n" + } + ], + "source": [ + "from mindspore.nn import SoftmaxCrossEntropyWithLogits #导入SoftmaxCrossEntropyWithLogits模块,用于定义损失函数\n", + "from mindspore.nn import Adam, Accuracy #导入Adam模块和Accuracy模块,分别用于定义优化参数,评估预测准确率\n", + "from mindspore import Model #导入Model模块,用于建立模型\n", + "from mindspore.dataset import NumpySlicesDataset #导入NumpySlicesDataset模块,用于创建模型可以识别的数据集\n", + "from mindspore.train.callback import Callback, LossMonitor #导入Callback模块和LossMonitor模块,分别用于定义回调函数和监控损失\n", + "\n", + "loss = SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean') #通过SoftmaxCrossEntropyWithLogits定义损失函数,sparse=True表示指定标签使用稀疏格式,reduction='mean'表示损失函数的降维方法为求平均值\n", + "opti = Adam(Quantumnet.trainable_params(), learning_rate=0.1) #通过Adam优化器优化Ansatz中的参数,需要优化的是Quantumnet中可训练的参数,学习率设为0.1 \n", + " \n", + "model = Model(Quantumnet, loss, opti, metrics={'Acc': Accuracy()}) #建立模型:将MindQuantum构建的量子机器学习层和MindSpore的算子组合,构成一张更大的机器学习网络\n", + " \n", + "train_loader = NumpySlicesDataset({'features': X_train, 'labels': y_train}, shuffle=False).batch(5) #通过NumpySlicesDataset创建训练样本的数据集,shuffle=False表示不打乱数据,batch(5)表示训练集每批次样本点有5个\n", + "test_loader = NumpySlicesDataset({'features': X_test, 'labels': y_test}).batch(5) #通过NumpySlicesDataset创建测试样本的数据集,batch(5)表示测试集每批次样本点有5个\n", + "\n", + "class StepAcc(Callback): #定义一个关于每一步准确率的回调函数\n", + " def __init__(self, model, test_loader):\n", + " self.model = model\n", + " self.test_loader = test_loader\n", + " self.acc = []\n", + "\n", + " def step_end(self, run_context):\n", + " self.acc.append(self.model.eval(self.test_loader, dataset_sink_mode=False)['Acc']) \n", + "\n", + "monitor = LossMonitor(16) #监控训练中的损失,每16步打印一次损失值\n", + "\n", + "acc = StepAcc(model, test_loader) #使用建立的模型和测试样本计算预测的准确率\n", + "\n", + "model.train(20, train_loader, callbacks=[monitor, acc], dataset_sink_mode=False)#将上述建立好的模型训练20次" + ] + }, + { + "source": [ + "从上述打印可以看到,20次迭代后,损失值不断下降并趋于稳定,最后收敛于约0.367。\n", + "\n", + "\n", + "\n", + "说明:\n", + "\n", + "(1)nn.SoftmaxCrossEntropyWithLogits可以计算数据和标签之间的softmax交叉熵。使用交叉熵损失测量输入(使用softmax函数计算)的概率和目标之间的分布误差,其中类是互斥的(只有一个类是正的),一般格式如下:mindspore.nn.SoftmaxCrossEntropyWithLogits(sparse=False, reduction=\"none\"),sparse=False表示指定标签是否使用稀疏格式,默认值:False;reduction=\"none\"表示适用于损失的减少类型。可选值为mean、sum和none。如果为none,则不执行减少,默认值:“没有”。\n", + "\n", + "(2)Adam模块通过自适应矩估计算法更新梯度,可以优化Ansazt中的参数,输入的是神经网络中可训练的参数;一般格式如下:nn.Adam(net.trainable_params(), learning_rate=0.1),学习率可以自己调节;\n", + "\n", + "(3)mindspore.Model是用于训练或测试的高级API,模型将层分组到具有训练和推理特征的对象中,一般格式如下:mindspore.Model(network, loss_fn=None, optimizer=None, metrics=None, eval_network=None, eval_indexes=None, amp_level=\"O0\", acc_level=\"O0\"),其中network就是我们要训练的网络即Quantumnet;loss_fn即目标函数,在这里就是定义的loss函数;optimizer即优化器,用于更新权重,在这里就是定义的opti;metrics就是模型在训练和测试期间需要评估的字典或一组度量,在这里就是评估准确率;\n", + "\n", + "(4)Accuracy用于计算分类和多标签数据的准确率,一般格式如下:mindspore.nn.Accuracy(eval_type=\"classification\"),用于分类(单标签)和多标签(多标签分类))的数据集上计算准确率的度量,默认值:“分类”;\n", + "\n", + "(5)NumpySlicesDataset使用给定的数据切片创建数据集,主要用于将Python数据加载到数据集中,一般格式如下:mindspore.dataset.NumpySlicesDataset(data, column_names=None, num_samples=None, num_parallel_workers=1, shuffle=None, sampler=None, num_shards=None, shard_id=None);\n", + "\n", + "(6)Callback用于构建回调类的抽象基类,回调是上下文管理器,在传递到模型时将输入和输出。你可以使用此机制自动初始化和释放资源。回调函数将执行当前步骤或数据轮回中的一些操作;\n", + "\n", + "(7)LossMonitor主要用于监控训练中的损失,如果损失是NAN或INF,它将终止训练,一般格式如下:mindspore.train.callback.LossMonitor(per_print_times=1),per_print_times=1表示每秒钟打印一次损失,默认值:1;\n", + "\n", + "(8)train模块用于训练模型,其中迭代由python前端控制;当设置pynative模式或CPU时,训练过程将在数据集不接收的情况下执行,一般格式如下:train(epoch, train_dataset, callbacks=None, dataset_sink_mode=True, sink_size=-1),其中epoch表示在数据上的总迭代次数;train_dataset就是我们定义的train_loader;callbacks就是我们需要回调的损失值和准确率;dataset_sink_mode表示确定是否通过数据集通道传递数据,教案中为否;\n", + "\n", + "## 11. 训练过程中的准确率\n", + "\n", + "我们已经看到损失值趋于稳定,那么我们还可以将模型在训练过程中的预测的准确率呈现出来,执行如下代码。" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "Text(0, 0.5, 'Accuracy')" + }, + "metadata": {}, + "execution_count": 14 + }, + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n \n \n \n \n 2021-09-14T08:05:20.164574\n image/svg+xml\n \n \n Matplotlib v3.4.3, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ], + "source": [ + "plt.plot(acc.acc)\n", + "plt.title('Statistics of accuracy', fontsize=20)\n", + "plt.xlabel('Steps', fontsize=20)\n", + "plt.ylabel('Accuracy', fontsize=20)" + ] + }, + { + "source": [ + "从上述打印的图像可以看到,在大约50步后,预测的准确率收敛于1,也就是说预测的准确率已经可以达到100%。\n", + "\n", + "## 12. 预测\n", + "\n", + "最后,我们测试一下训练好的模型,将其应用在测试集上。" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "from mindspore import ops, Tensor #导入ops模块和Tensor模块\n", + "\n", + "predict = np.argmax(ops.Softmax()(model.predict(Tensor(X_test))), axis=1) #使用建立的模型和测试样本,得到测试样本预测的分类\n", + "correct = model.eval(test_loader, dataset_sink_mode=False) #计算测试样本应用训练好的模型的预测准确率\n", + "\n", + "print(\"预测分类结果:\", predict) #对于测试样本,打印预测分类结果\n", + "print(\"实际分类结果:\", y_test) #对于测试样本,打印实际分类结果\n", + "\n", + "print(correct) #打印模型预测的准确率" + ], + "cell_type": "code", + "metadata": { + "tags": [] + }, + "execution_count": 15, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "预测分类结果: [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]\n实际分类结果: [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]\n{'Acc': 1.0}\n" + } + ] + }, + { + "source": [ + "从上述打印的可以看到,预测分类结果和实际分类结果完全一致,模型预测的准确率达到了100%。\n", + "\n", + "至此,我们体验了如何通过搭建量子神经网络来解决经典机器学习中的经典问题——鸢尾花分类问题。相信大家也对使用MindQuantum有了更进一步的了解!期待大家挖掘更多的问题,充分发挥MindQuantum强大的功能!\n", + "\n", + "若想查询更多关于MindQuantum的API,请点击:[https://mindspore.cn/mindquantum/](https://mindspore.cn/mindquantum/)。" + ], + "cell_type": "markdown", + "metadata": {} + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.5 64-bit", + "language": "python", + "name": "python37564bit6afae4a42a5941c0967cdcfc2650559a" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5-final" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/tutorials/3.classification_of_iris_by_qnn.ipynb b/tutorials/3.classification_of_iris_by_qnn.ipynb deleted file mode 100644 index 4c8748928..000000000 --- a/tutorials/3.classification_of_iris_by_qnn.ipynb +++ /dev/null @@ -1,867 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 通过量子神经网络对鸢尾花进行分类\n", - "\n", - "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", - "\n", - "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/parameterized_quantum_circuit.ipynb) [![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/view_mindquantum_api.png)](https://mindspore.cn/mindquantum/api/zh-CN/master/index.html)\n", - "\n", - "## 1. 概述\n", - "\n", - "在之前的案例中,我们介绍了什么是参数化量子线路并通过一个简单的例子体验了如何搭建量子神经网络来解决一个小问题。在本教案中,我们将体验升级,将会介绍如何通过搭建量子神经网络来解决经典机器学习中的问题。我们选取的问题是:监督学习中的鸢尾花分类问题。\n", - "\n", - "问题描述:鸢尾花(iris)数据集是经典机器学习中常用的数据集,该数据集总共包含150个样本(分为3种不同的亚属:山鸢尾(setosa)、杂色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),每个亚属各有50个样本),每个样本包含4个特征,分别为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。\n", - "\n", - "我们选取前100个样本(山鸢尾(setosa)和杂色鸢尾(versicolor)),并随机抽取80个样本作为训练集,通过搭建量子神经网络对量子分类器(Ansatz)进行训练,学习完成后,对剩余的20个样本进行分类测试,期望预测的准确率尽可能高。\n", - "\n", - "思路:我们需要将100个样本进行划分,分成80个训练样本和20个测试样本,根据训练样本的经典数据计算搭建Encoder所需的参数,然后,搭建Encoder,将训练样本的经典数据编码到量子态上,接着,搭建Ansatz,通过搭建的量子神经网络层和MindSpore的算子对Ansatz中的参数进行训练,进而得到最终的分类器,最后,对剩余的20个测试样本进行分类测试,得到预测的准确率。\n", - "\n", - "## 2. 环境准备\n", - "\n", - "首先,我们需要导入鸢尾花的数据集,而在导入该数据集前,我们需要使用sklearn库中的datasets模块,因此读者需要检查是否安装了sklearn库,可执行如下代码检查。" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Name: scikit-learn\n", - "Version: 0.23.2\n", - "Summary: A set of python modules for machine learning and data mining\n", - "Home-page: http://scikit-learn.org\n", - "Author: None\n", - "Author-email: None\n", - "License: new BSD\n", - "Location: /home/xuxs/anaconda3/lib/python3.8/site-packages\n", - "Requires: joblib, numpy, threadpoolctl, scipy\n", - "Required-by: qiskit-aqua\n", - "Note: you may need to restart the kernel to use updated packages.\n" - ] - } - ], - "source": [ - "pip show scikit-learn" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "若无报错,则表明已安装。简单说明一下,sklearn是scikit-learn的简称,是一个基于Python的第三方模块。sklearn库集成了一些常用的机器学习方法,在进行机器学习任务时,并不需要实现算法,只需要简单的调用sklearn库中提供的模块就能完成大多数的机器学习任务。\n", - "\n", - "若未安装sklearn库,则可通过运行如下代码来安装。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "!pip install scikit-learn\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后,我们设置本教程所需的线程数。" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import os #导入os库\n", - "os.environ['OMP_NUM_THREADS'] = '2' #通过os.environ将量子线路模拟器的线程数设置为2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "说明:\n", - "\n", - "(1)os是一个标准库,里面包含许多操作文件和目录的函数;\n", - "\n", - "(2)os.environ()模块,可以获取并修改环境变量;一般来说,我们需要在一开始设置线程数;\n", - "\n", - "## 3. 导入鸢尾花数据集\n", - "\n", - "有了上述准备,现在我们就可以导入鸢尾花的数据集了。" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(150, 4)\n", - "['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']\n", - "['setosa' 'versicolor' 'virginica']\n", - "[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n", - " 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2\n", - " 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2\n", - " 2 2]\n", - "(150,)\n" - ] - } - ], - "source": [ - "import numpy as np #导入numpy库并简写为np\n", - "from sklearn import datasets #导入datasets模块,用于加载鸢尾花的数据集\n", - "\n", - "iris_dataset = datasets.load_iris() #加载鸢尾花的数据集,并存在iris_dataset\n", - "\n", - "print(iris_dataset.data.shape) #打印iris_dataset的样本的数据维度\n", - "print(iris_dataset.feature_names) #打印iris_dataset的样本的特征名称\n", - "print(iris_dataset.target_names) #打印iris_dataset的样本包含的亚属名称\n", - "print(iris_dataset.target) #打印iris_dataset的样本的标签的数组\n", - "print(iris_dataset.target.shape) #打印iris_dataset的样本的标签的数据维度" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印可以看到,该数据集共有150个样本,每个样本均有4个特征,分别为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。同时样本包含3种不同的亚属:山鸢尾(setosa)、杂色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),每个样本有对应的分类编号,0表示样本属于setosa,1表示样本属于versicolor,2表示样本属于virginica,因此有一个由150个数字组成的数组来表示样本的亚属类型。\n", - "\n", - "由于我们只选取前100个样本,因此执行如下命令。" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(100, 4)\n", - "['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']\n", - "['setosa' 'versicolor']\n", - "[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n", - " 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]\n", - "(100,)\n" - ] - } - ], - "source": [ - "X = iris_dataset.data[:100, :].astype(np.float32) #选取iris_dataset的data的前100个数据,将其数据类型转换为float32,并储存在X中\n", - "X_feature_names = iris_dataset.feature_names #将iris_dataset的特征名称储存在X_feature_names中\n", - "y = iris_dataset.target[:100].astype(int) #选取iris_dataset的target的前100个数据,将其数据类型转换为int,并储存在y中\n", - "y_target_names = iris_dataset.target_names[:2] #选取iris_dataset的target_names的前2个数据,并储存在y_target_names中\n", - "\n", - "print(X.shape) #打印样本的数据维度\n", - "print(X_feature_names) #打印样本的特征名称\n", - "print(y_target_names) #打印样本包含的亚属名称\n", - "print(y) #打印样本的标签的数组\n", - "print(y.shape) #打印样本的标签的数据维度" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印可以看到,此时的数据集`X`中只有100个样本,每个样本依然有4个特征,仍为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。此时只有2种不同的亚属:山鸢尾(setosa)和杂色鸢尾(versicolor),并且每一个样本有对应的分类编号,0表示它属于setosa,1表示它属于versicolor,因此有一个由100个数字组成的数组来表示样本的亚属类型。\n", - "\n", - "## 4. 数据图像化\n", - "\n", - "为了更加直观地了解这100个样本组成的数据集,我们画出所有样本不同特征之间组成的散点图,执行如下命令。" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABUgAAAUdCAYAAAAqwanmAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAEAAElEQVR4nOzdd5xTxdrA8d+kbKez9N57F1CqIAiCgiCKqBRRsL22a+8N0GtHr6IiIipNEERABESaIF16772X7Wnz/nF2syXZZbMkmy3P937yYTNnMufJyj0kz5mZR2mtEUIIIYQQQgghhBBCiMLIFOwAhBBCCCGEEEIIIYQQIlgkQSqEEEIIIYQQQgghhCi0JEEqhBBCCCGEEEIIIYQotCRBKoQQQgghhBBCCCGEKLQkQSqEEEIIIYQQQgghhCi0JEEqhBBCCCGEEEIIIYQotIKeIFVKTVBKnVFKbcvkuFJKjVVK7VNKbVFKtcjtGIUQQgghhBBCCCGEEAVT0BOkwESgRxbHewK1kx8jgC9zISYhhBBCCCGEEEIIIUQhEPQEqdZ6OXAhiy59gEna8A9QXClVPneiE0KIvEspZVZKbVJKzfVyrLNS6rJS6t/kx2vBiFEIIYQQQgghhMjrLMEOIBsqAkfTPD+W3HYyOOEIIUSe8QSwEyiayfEVWuveuRiPEEIIIYQQQgiR7+SHBKny0qa9dlRqBMYyfCIjI1vWq1cvkHEJIQqhDRs2nNNaRwc7DqVUJaAXMAp42l/jli5dWlerVs1fwwkhBJB3rp2BItdOIUQgyLVTCCF8l9NrZ35IkB4DKqd5Xgk44a2j1vpr4GuAVq1a6fXr1wc+OiFEoaKUOhzsGJJ9AjwHFMmiz/VKqc0Y18xntNbbrzZotWrVkGunEMLf8tC1MyDk2imECAS5dgohhO9yeu0M+h6k2TAHGJxczb4tcFlrLcvrhRCFllKqN3BGa70hi24bgapa66bAZ8DsLMYboZRar5Raf/bsWf8GK4QQQgghhBBC5HFBT5AqpaYAq4G6SqljSqnhSqmHlFIPJXeZDxwA9gHfAI8EKVQhhMgr2gG3KaUOAVOBLkqpH9N20Fpf0VrHJv88H7AqpUp7G0xr/bXWupXWulV0dIFdxSWEEEIIIYQQQngV9CX2Wuu7r3JcA4/mUjhCCJHnaa1fBF4Eo1o9xvL5e9P2UUqVA05rrbVSqjXGDbHzuRyqEEIIIYQQQgiR5wU9QSqEEMI/Umbea63HAXcADyulHEACMDD5hpMQQgghhBBCCCHSkASpEELkY1rrpcDS5J/HpWn/HPg8OFEJIYQQQgghhBD5R9D3IBVCCCGEEEIIIYQQQohgkRmkQgghhLg227bB+PFw9iz06QP9+oFFPmIIIUResuX0Fr7d9C0X4i/Qp14f+tbri8Uk12ohROF0JekK3//7PX8f/ZsG0Q14sMWDlC9SPthhiSCSfxGFEEIIkXOTJsFDD4HNBk4n/PorfPYZ/PknhIQEOzohhBDAhE0TeGz+Y9icNpzayaxds2hdsTV/3PsHVrM12OEJIUSuOhlzkpZft+Ry0mXi7fGEWcL4YNUHLB26lBblWwQ7PBEkssReCCGEEDkTGwsPPwwJCUZyFCAuDjZtgp9+Cm5sQgghAGOW1GPzHyPBkYBTG9fqOHsca4+vZdr2aUGOTgghct8Li1/gbNxZ4u3xACQ6EomxxTDs12FBjkwEkyRIhRBCCJEzq1Z5X0ofFwfT5Eu3EELkBSsOr/A6SzTOHsf07dODEJEQQgTXb3t+w6EdHu07zu7gStKVIEQk8gJJkAohhBAiZyIiQGvvx4oWzd1YhBBCeBVhjUB7uVYrFEVCiwQhIiGECK5wS7jXdoXCapJtRworSZAKIYQQImeuvx6iojzbIyJg5Mjcj0cIIYSHDlU7EGYJ82gPt4YzosWIIEQkhBDB9WDLBz2SpFaTlZ61ehJu9Z48FQWfJEiFEEIIkTNmM8yfD6VLGzNGo6IgLAyefRa6dg12dEIIIQCLycLv9/xOyfCSFA0pSpGQIoSZw3ix/Yt0qtYp2OHlKq01fx38i0fmPcKTC55kw4kNwQ5JCBEEL3V4iS7VuxBuCScqJIqokCjqR9fn2z7fBjs0EURSxV4IIYQQOdesGZw4AYsXw8WL0LkzVKgQ7KiEuCZKqUNADOAEHFrrVsGNSIhr07JCS07+5ySL9i/ictJlulTvQrmocsEOK1dprXlgzgNM2z6NeHs8CsU3G7/h5Q4v81KHl4IdnhAiF4WYQ5g7aC7bzmzj31P/UqNEDa6vdD1KqWCHJoJIEqRCCCGEuDZWK/TsGewohPC3G7XW54IdhBD+EmIOoVedXsEOI2j+Pvo307ZPI84eB4BGE2+P5+3lb3Nvk3upUqxKkCMMLqXUBKA3cEZr3cjL8WeBe5KfWoD6QLTW+oLcVBL5VaMyjWhUxuOvuyikZIm9EEIIIYQQQogCbfau2cTb4z3aTZj4fe/vQYgoz5kI9MjsoNb6fa11M611M+BFYJnW+kKaLjcmH5fkqBAiX5IEqRBCCCGEEOlpYKFSaoNSymsVG6XUCKXUeqXU+rNnz+ZyeEIIX4VbwjGbzB7tSimvRawKG631cuDCVTsa7gamBDAcIYTIdZIgFUIIIYQQIr12WusWQE/gUaVUx4wdtNZfa61baa1bRUdH536EQgif3NvkXqwmq0e7RtOnXp8gRJQ/KaUiMGaazkzTfNWbSmleLzeXhBB5kiRIhRBCCCGESENrfSL5zzPALKB1cCMSIv85G3eWD1d9yKPzHmXy1snYnLagxlO3dF0+uvkjwixhRFmjKBJShAhrBNPvmE7xsOI+j+dwOZi1cxaPzX+M0StGc/zKcf8HnTfdCvydYXn9VW8qpZCbS0KIvEqKNAkhhBBCCJFMKRUJmLTWMck/dwfeCnJYQuQrG05s4Mbvb8ThcpDgSGDS5km8vfxt/hn+D8XCigUtrodaPUT/+v1ZsG8BIeYQbql9C0VCi/g8TqIjkc4TO7P97HZibbGEmkMZtWIUcwbOoWuNrgGIPE8ZSIbl9WlvKimlUm4qLQ9CbEIIkWMyg1QIIYQQQohUZYGVSqnNwFpgntZ6QZBjEiLf0Fpzzy/3EGOLIcGRAECsPZaDFw8yesXoIEcH0ZHR3Nf0Pu5qdFeOkqMA49aPY8vpLcTaYgFIciYRb49n0C+DcLqc/gw3T1FKFQM6Ab+maYtUShVJ+RnjptK24EQohAgUrTXn48+T6EgMdigBIwlSIYQQQgghkmmtD2itmyY/GmqtRwU7JiHyk5OxJzl86bBHe5IzianbpwYhIv/7ccuP7uRvWvH2eLae2RqEiK6dUmoKsBqoq5Q6ppQarpR6SCn1UJputwMLtdZxadrkppIQBdyCfQuo/ml1KnxUgeLvFmfY7GHE2+ODHZbfyRJ7IYQQQgghhBB+YTFZcOHyeizEHJLL0QRGZu9Da51v36PW+u5s9JkITMzQdgBoGpiohBDBtvHkRvpP758uITp1+1QuJV5i1sBZQYzM/2QGqRBCCCGEEEIIvygTWYbm5ZpjUum/aoZbwnmwxYNBisq/RrYcSaQ10qO9XFQ56peuH4SIhBAiMN77+z0S7OlnzCc6Elmwf0GBK04nM0iFEEIIIYQQIp/SWrPk4BIW7l9IqYhS3NP4HioWrRjUmKbeMZUO33XgcuJlHC4HSik6Vu3Ik22fDGpc2XU+/jw/bf2Jo1eO0r5ye3rV6YXFlPrV+b6m97HowCJ+2fkLYMyaDbWEMnvgbJRSwQpbCCH8bs+5PWi0R3uoOZSjV44G/d8bf5IEqRBCCCGEEELkQw6Xgz5T+7D88HJ3NfU3l73JjAEz6Fm7Z9Diqla8GgefOMiCfQs4evko11W8jlYVWgUtHl+sO76OrpO64nA5SHAkMC5kHHVL1WXZ0GVEhhizRk3KxI/9fmTbmW2sOLyCslFl6VW7F6GW0CBHL4QQ/nV95evZfnY7dpc9XXuSI4m6peoGKarAkASpEEIIUVhpDXY7hOTP/dKEEKKwm7J1CssOLSPObtTMSXImgRPunnk3Z549E9T9MC0mC73r9A7a+XNCa83AmQOJscW422JtsWw/u52PVn/Eq51eTde/UZlGNCrTKLfDFEKIXPNcu+f4aetPOJIc7pmkEdYIHm71MCXCSwQ5Ov+SPUiFEEKIwkZreP99KFUKwsKgWjWYMSPYUQkhhPDRD1t+cCdH09JoVh9dHYSI8rdDlw5xMuakR3uiI5Eft/4YhIiEECK4qhWvxtoH1tKnbh9KhJWgZomafNj9Q97v9n6wQ/M7mUEqhBBCFDajRsGYMRCfXI3y8GEYMgQiI6Fn8JZkCiGE8I3VZPXarrVOt2emyB6zyex1rz1Afp9CiEKrbum6Ba5ivTcyg1QIIYQoTBwO+O9/U5OjKeLj4ZVXghOTEEKIHLm/+f1eq6mHWcJoW6ltECLK36oUq0KtErVQpC+0FGGNYHjz4UGKSgghRG6Q22BCCCFEYXLpEths3o8dOJCroQghhLg2/er34/d9vzN562Rc2oXVbEWh+HXgr5hNZp/Hi7fF88ayN1h9dDX1o+szuutoSkeUzlFsMUkxTNs+jSOXj9C6Ymt61uqZo5j8bevprczeNZtQSygDGgygeonq6Y7/fOfPdPyuI4mORGxOGxaThQ5VOvB/rf8vR+c7evko07dPJ84eR+86vWlRvoU/3oYQQgg/kwSpEEIIUZiUKAEREZCU5Hmsfv3cj0cIIUSOKaUYf9t4nmz7JH8e+JOS4SXpW68vRUKL+DzWwYsHqfe/eticxk20lUdXMn7jeJYMWULnap19GmvbmW10/K4jNqeNOHscUSFRHpXgg+HFxS/y6ZpPsTltmJSJN5a+wSc9PmFEyxHuPvVK1+PoU0eZs3sOx2OOc32l62ldsTVKqSxG9m7KtikM/3U4Lu3C7rLz3t/vMaTpEP53y/9yNJ4QQojAkSX2QgghRGFiNsPrrxtJ0rQiImD06ODEJIQQ4po0KtOIJ9o+wX1N78tRchSg9+Te7uRoCo2m79S+Po9198y7uZh40V1AKtYWy/Yz23nv7/dyFJs/bDixgbFrx5LgSMCpndhddhIcCTyx4AlOxZ5K1zfUEsqAhgN4su2TtKnUJkfJzEuJlxj+63ASHAkkOZNwaRfx9ngmbZ7EssPL/PW2hBBC+IkkSIUQQojC5okn4LPPjOr1oaHQrBn8+it07hzkwIQQQgTLznM7vbZfTrrMufhz2R7nZMxJ9p7f69Ge6Ezkhy0/5Di+azV9+3QS7Yke7SZlYu6euX4/3x/7/vBa2CneHs/krZP9fj4hhBDXRpbYCyGEEIXR/fcbDyGEEOIqTD7MqzGpzPtmLH6Um0zKZMwEzVCkXiX/LxDny4wsrxdCiLxHZpAKIYQQQgghRCHXuGxjr+0lwkpQMqJktscpG1WWBtENPJKOYZYwhjUbdk0xXouBjQYSag71aHdqJ7fWvdXv57u51s04tdOjPdwazn1N7vP7+YQQgTFl6xRu+PYG2k9oz8wdMwN+voMXD/LO8nd4duGzLD20FK311V8k/EJmkAohhBBCCCFEITdv0Dxqja1FkjO1iJ9CMW/QPJ/HmtJ/Ch2+60CiI5EEewJh1jCalG3Cs+2ezVFsZ+LO8PP2n4mxxdCzVk+almvq0cfhcjB/73y2nt5KnVJ16FOvDyHmEPfxpuWa8kL7Fxi9cjQu7cKszGg0X/f+mjKRZXIUV1aKhhblx9t/5J5f7kEphcPpwGwy88h1j9C+Snu/n08I4X/tvm3HqmOr3M//Pvo33Wp0Y+F9CwNyvmnbpjHs12HGPslOO1+u/5Jbat/C1DumZjkrXfiHJEiFEEKIvGDJEvj0UzhzBvr0gYcfhmLFgh2VEEKIQqJISBFqlazF3gt7sTltWE1WSkWUokKRCj6PVbd0XQ4/eZhZu2Zx9PJRWldsTedqnXO0tHzunrnc+fOdANhddt5e/jaDmwzmi15fuMe7kHCBdhPacezKMeLt8URaI3l64dP8M/wfKhat6B7r1U6vcnfju5mzew4h5hD61e+Xo/eXXbfXv51DTx5i5o6ZxNnj6FW7F/Wj6wfsfEII/5m5Y2a65GiKRQcWseTgErpU7+LX88XaYrl/zv0kOBLcbXH2OObvnc+c3XPoW6+vX88nPEmCVAghhAi2Tz6Bl1+G+Hjj+b//wjffwKZNULRoMCMTQghRSLyy5BV3chSMZOTZuLMM+3UYS4Ys8Xm8cGs4gxoPuqaY4mxxDJwxMF3CwOFy8MOWH+hXvx/danYD4NlFz3LgwgFsLiP2GFsM8fZ4Rvw2gnn3pJ8BW6tkLZ6+/ulrissXZSLL8PB1D+fa+YQQ/vHFui8yPTZ2zVi/J0j/OvgXFuWZoouzx/HT1p8kQZoLZI6uEEIIEUxXrsBLL6UmRwESE+HkSRg3LnhxCSGEKFSmbJviTo6mcGonK46sIMGekMmrAuvPg39iNpk92uPscUzaMsn9/OftP7uToymc2snCAwtxuBwBj1MIUfCk3aIjI6vZ6vfzWUwWMqsX523/ZOF/kiAVQgghgmnDBrB6+ZCVkABz5uR+PEIIIQolnbG8ex6QVXESKVwihAik59s/n+mxF9u/6PfzdanexaO4HUCkNTKoBe4KE0mQCiGEEMFUujQ4vMxuUQrKl8/9eIQQQhRKdzW8y2PGlEmZaF+lPeHW8KDEdFONm3C6PCvBR1ojGdx0sPt5//r9sZrS32w0KzPdanQzZmUJIYSPOlfrzN0N7/Zof7D5g7Qo38Lv5wu1hDLrrllEWiOJskYRZgkjzBLGQ60e8vtyfuGd/GshhBBCBFOjRlCjBuzcCc40XwLDw+Hxx4MXlxBCFDLbzmxj0f5FFAsrRr/6/SgeVjxH42itWXpoKRtObqB68ercWvdWr0s1j105xuxds1Eo+tTrQ6Wila7xHWTt8KXDvLHsDS4mXGRYs2H0qdcn3fHRXUez7PAyjl4+SowthqiQKCKtkUy4bYLXsebsnoPZZKZvvb7XVOho48mNLD20lNIRpbm93u0UCS3iPhYZEsmP/X5k0MxBaDR2p51QSyiDGg+iW41u7n4fdP+Av4/+zcnYk8TaYikSUoQioUX4qvdXOY5LCCEm3zGZZ9o9w4erPsSszDzf/nkalmkYsPPdWP1GTvznBLN3zSYmKYbuNbtTu1TtgJ1PpKcK6tKEVq1a6fXr1wc7DCFEAaOU2qC1bhXsOAJFrp1BcvQo9OoF+/eDxWLMKH3/fXjkkWBHJoRfyLVT5GVaax6a9xA/bP4Bl3a5Zxz+dvdv3Fj9Rp/GirPF0XVSV7af3U6SI4kwSxhRIVGsGr6KasWruft9ue5Lnl74tHs5pUbz8c0f81Crh/z2vtL6cNWHPLPomXRtDUo3YOvDWzGZUhcVOlwO5u2Zx+bTm6lZoib9G/QnzBKW7nUfr/6Yl5a8BIBCodF8ccsXDGvu2xJQl3Zxz8x7mLNnDg6XgxBzCCZlYuG9C2lTqU26vqdiTzFt2zRibbH0rN3T6+wtu9PO3D1z2XJ6C3VK1aFf/X6EWvL3vn1y7RRCCN/l9NopCVIhhPCBfFAVAbV9O5w/Dy1bQmRksKMRwm/k2inysrl75jJwxkDi7HHp2ouFFuPMs2eyLNSR0YuLX+STNZ+Q6Eh0t5mUiRsq38CKYSsAOHjxIA2/aJiuMjtAmCWMXY/uomrxqtfwbjxdSbxCsfeKeT32Rqc3eL3z69kea/e53TT/qrnX2Pc/vt+nmaQ/bfmJkXNHevzey0eV59jTxzAp2Q1Orp1CCOG7nF475V8dIYQQIq9o2BA6dpTkqBBC5KLv/v3OI0kHxqzOFYdX+DTWpC2T0iVHwZgpuebYGi4nXgbgl52/4NSe+2pqrZm5c6ZP58uOL9Z/kemxbzZ+49NYP+/4GbvL7tGuUMzeNdunsb7Z+I3X33uMLYaNJzf6NJYQQghxrSRBKoQQQgghhCi0HC4vhfKSeUtkZsXlcmV+TLvcY3pbxae19lqQ6Fpl9f5SYsoupyuT2PE99szOrVAB+T0IIYQQWZEEqRBCCCECT2tYsQLGjoXffjP2WRVCiDxgcJPBRFo9Z+5rrelYtaNPY93VyLMSvELRtFxTSoSXAKBvvb5eK6ubTCb61uvr0/my45FWme9nfV+T+3waq1/9foSave/reVvd23waa0jTIV5/7yHmEFpVKLCryoUQQuRRkiAVQoh8SillVkptUkrN9XJMKaXGKqX2KaW2KKU8qxkIkVvi46F9e7jlFnjuObjnHqhVC44dC3ZkQgjB7fVv55bat7iTdaHmUMIt4fzU7yePAkVxtjgmb53M/9b+j51nd3qM9WbnN6lZoiZRIVEARFojKRFegkl9J7n71ClVh5c7vEy4JRyLsmBWZsIt4bzW6bWAVCsuGVGSlzq85NFeqWglRnUd5dNYjcs25unrnybCEoFZmbGYLIRbwhndZbTH3ql2p53Zu2bz2ZrP+OfYPx4zT4c0G0L7Ku3dv6swSxiR1kimD5iO2WT28V0aCe2/Dv7FZ2s+Y8G+BZnOQj125Rhfrf+KCZsmcD7+vM/nEUIIUTDliSJNSqkewKeAGRivtX43w/FiwI9AFcACfKC1/i6rMWXDZyFEIOSlzfKVUk8DrYCiWuveGY7dAvwfcAvQBvhUa93Gc5T05NopAuLFF+GTTyAxzb58ZjN07gyLFwcrKpGL8tK1MxDk2pn/aa35++jfLNi3gBJhJbi78d0eBYf+OfYPN/94M1pr97L1IU2H8EWvL1BKufvZnXZ+2/Mb60+sp0aJGtzV8C6KhBbxOOeOszuYsWMGCkX/Bv1pEN0goO9x/Yn1vLH0DS4lXuKexvcwsuXIdBXsfbH19FZ+2fkLZpOZAQ0GULd03XTHD148SLsJ7Yi1xWJ32TErMzdUvoG5g+amm2GrtWbJwSUsObiEMpFluLvx3ZSJLONzPFeSrtDl+y7sPr8bh8uB1WSlXFQ5/r7/b6Ijo939Plr9ES8veRmTMqFQuLSLH27/gf4N+ufo9xBocu0U+YVLu/xWWM2fY/nrfC7tQqHSXeuv5XxXG0trjUZnK668FrsvYwXqv3O+rWKvlDIDe4BuwDFgHXC31npHmj4vAcW01s8rpaKB3UA5rbUts3HlYiuECIS88kFVKVUJ+B4YBTztJUH6FbBUaz0l+fluoLPW+mRW48q1UwREhQpw0stfPasVLlyAqKjcj0nkqrxy7QwUuXYWfA6Xg/Ifludc/Ll07ZHWSCb3n+zz8vKCrs03bVh/cn26fUbDLeG83ul1nm//vN/P98i8R5iwaQJJziR3m8Vk4bY6tzHzLqPw1fYz27num+tIcCSke224JZyjTx2lVEQpv8d1rXLz2qmUmgD0Bs5orRt5Od4Z+BU4mNz0i9b6reRjWU54yoxcO/O/uXvm8uSCJ9l/cT8lw0vyfLvnefaGZ31OoGmt+eifjxizYgznE85TvXh1Prr5o4BsO5Lil52/8J+F/+HQpUOUjijNSx1e4sk2T6aL/diVYzw09yEW7FuAUorb6tzGF72+oGxUWZ/Pt/b4Wh6Z9wgbT24kwhrBAy0e4L2b3iPUkrptSZwtjqf+eIoftvyAzWnj+krXM673OBqVSf9/yanbpvL84uc5cvkIZSLL8FrH13jkukfSxX740mEemvsQiw4swqSMLVy+6PUFpSNK+xz7qqOreHTeo2w+vZnIkEgebvUwo7qMwmq2+jzWD1t+4KU/X+LYlWOUiyrHm53fZETLET6Pk5X8XMW+NbBPa30gOeE5FeiToY8Giijjv3YUcAGQzcuEEIXZJ8BzQGbVFSoCR9M8P5bc5kEpNUIptV4ptf7s2bN+DVIIAOyeFY/dnFKIQwiR9/1z7B+SHEke7XH2OJ8rwRd0Z+LOsPn0Zo8iTAmOBMZvGh+Qc07eOjldchSMpPacPXPcS+0nb52Mzek5v8akTMzZPScgceUzE4EeV+mzQmvdLPmRkhw1A/8DegINgLuVUoGdDi3yhCUHl3DXz3ex/+J+AC4kXODNZW/y1rK3fB5rzMoxvP7X65xPMLa9OHjpIPf8cg8L9y/0a8wpft/7O/fNuo9Dlw4BcC7+HK8ueZX/rvqvu0+iI5G249saW3Zop/uacv2312N3ZvHZ1ou95/fS5fsubDi5AY0mzh7H1xu+5t5f7k3X79YptzJp8yQSHYm4tIu/j/5NuwntOBmTOtFg9q7ZDJ8znCOXjwDGNfe5xc/x+drP3X3ibHG0Gd+GhQcW4tRO7C5jy5P2E9r7XARvx9kddPuhG/+e/heNJtYWy+drP2f4nOE+jQNGYvehuQ9x7Iqxzdap2FM89cdTfLMhb/w7mhcSpNn5Ev85UB84AWwFntDas+yhfMkXQhQGSqmUu/sbsurmpc3rkgGt9dda61Za61bR0dHeughxbQYMgJD0RUtQCpo1g2LFghKSEEL4wu60ZzojylvSrTBzuBworx9DAve7StnyICOttTtRa3PZPJK2ABqN3eVbsqMg0lovx5iI5KvsTHgSBdCrS14l3hGfri3eHs8Hqz/w6f/rDpeDd1e+S5w9zmOsV5e86pdYM3plySvE29PHHmePY8yKMe4E4swdM7mcdBmnTk0oOlwOzsWfY97eeT6d74PVH3jcZEtwJDB371x3snDr6a2sOb7G42ZPkiOJL9d/6X7+8p8ve8Qeb4/nzWVvuvd6nrptKrG22HTXPLvLzomYEyw6sMin2N9d+a7X2Kdvn87p2NM+jeXt9x5vj+f1pa/7NE6g5IUEaXa+xN8M/AtUAJoBnyulinq8SL7kCyEKh3bAbUqpQxgfQrsopX7M0OcYUDnN80oYN5mEyH3vvANVqqQupY+IgOLFYeLEYEYlhBDZdn3l673eZoy0RvpcCb6gq1CkAtWKV/NoDzWHcnejuwNyzj51+2AxWdK1mZSJTlU7uZeA3lH/DsKt4R6vdWkXvWr3CkhcBdD1SqnNSqnflVINk9uyvWoJZFJTQbLnwh6v7U6X02M7kqxcSLiQ6U2KvRf25ii2q9l3cZ/X9gRHApeTLgOw69wuYm2xnn3sCew+t9un820+tRmH9ryRE2oOZe954z3uPr/b4zoGkORMYtOpTe7nBy8d9OgDcDnpsjv5uP3sdo+EMxg3qXyNfcvpLemSxCnCLGEcuHjAp7EOXz7stf1k7EmfZ7YGQl5IkGbnS/wwjD1OtNZ6H8a+J/VyKT4hhMhTtNYvaq0raa2rAQOBJVrrezN0mwMMTq5m3xa4fLX9R4UImJIlYds2+OoreOop+O9/4eBBaCAr8IQQ+UOYJYwf+/1IhCXCXWQoyhpFhyodGNhoYJCjy31n4s7w/b/f8+OWH7mYcNHj+E/9f6JoaFHCLUZCMiokipola/JSh5c8+u45v4fxG8cza+csr9sYZMeHN39I+ajyRIUYN+IirZGUDC/J17d+7e7TplIb7m9+PxHWCBQKi7IQbglnTNcxVCyaaT5PpNoIVNVaNwU+A2Ynt2d71RLIpKaCJLPCchaTheiI7P+3LRleklBzqNdj9UoHJu2T2biR1kiKhRqrmxqVaeS+pqQVbg2nYZmGHu1ZaVWhlffkpyPJXeSuYXRDr0v3w8xhXFfhOvfz2iVrez1HibASRFgjAGhatqnX2K1mq8+xtyzfErMye8buTKJWyVo+jVWzRE2v7RWLVMRs8jxHbvP8L5T71gG1lVLVgeMYX/YHZehzBOgKrFBKlQXqAr6lqoUQooBTSj0EoLUeB8zHqGC/D4jHuNEkRPCEhsKgQcZDCCHyoVvr3squx3YxafMkziecp0etHtxU46ZcrbacF4zfOJ7/+/3/3F/2nS4nk26fxB0N7nD3aVG+BQceP8CPW37k4KWD3FD5BvrW6+tRwf7heQ8zafMklFKYlZkQcwhLhiyhSdkmPsVULqocux/bzfTt09l4aiMNSjdgUONBFAktkq7fZz0/497G9zJz50xCzCEMajwo0ySPSE9rfSXNz/OVUl8opUojq5YKrVFdRtH9h+7pCp9FWCN4qcNLPhXvsZgsvNbpNV7969V0y6/DLeGM7jrarzGnGN1lNLdNuS3dFgER1ghe7/y6O1F3e/3beWHxCyQ6Et3beFhNVioUqUDPWj19Ot9/rv8PkzZPIsYW424Lt4TTv35/KhSpAED96Pp0qtqJpYeXkuhIBEChCLOG8VCrh9yve/emd+k/vb/H7/2dLu+4t4K5s+GdvLzkZRLtie6ZqyHmEGqUqEGX6l18iv359s8zbfu0dDNSI6wRDGo8iOhI325yvHvTuwyaOcgj9kD9d/ZV0KvYAyilbsEoOGIGJmitR6X9oq+UqoCxaXR5jDtU72qtMy4nTUcq4gkhAkEqMQshhO/k2ilEwXDg4gEafdHIayX4Q08eokxkmWyP9fP2nxn26zCPZaBVilXh0BOHfK6CXRDl9rVTKVUNmJtJFftywGmttVZKtQZmAFUxvsPvwZjQdBxjAtQgrfX2q51Prp35358H/uQ/C//D9rPbKRtZllc6vsLIliNzVMX+m43f8M7ydzgVe4p6pevxQfcP6F6ze4AihwX7FvDswmfZfX435aLK8Xrn17m/2f3pYj8de5qn/niK2btmo5RiQIMBfHTzR5QML+nz+Taf2swTC55g1dFVFA0tyiPXPcKrHV9Nl0xOdCTy8p8v8+2mb0lwJHBjtRsZ23MsdUrVSTfW3D1zeW7Rc+y9sJeKRSry5o1vMqTpkHR9TsSc4MkFT/Lbnt8wKzN3NbqLD7t/SPGw4j7HvuHEBp5Y8ARrj6+lWFgxHm/zOC+1fylHsz5n7ZzFC3++wIGLB6hSrArv3PgOdzf27/YrOb125okEaSDIxVYIEQjyJV/kGxs3gssFrQrsX1eRj8i1U4iCYfSK0byx9A2P/QIjrBF81P0jRrYame2xbpp0E38e/NOjPSokiuVDl9O8fPNrjje/y81rp1JqCtAZKA2cBl4HrOCetPQY8DDgABKAp7XWq5Jf6zHhKTvnlGunECIQcnrtzAtL7IUQQgjhL/PnQ//+kGgszSE0FKZMgdtvD25cQggh8r0kR5LXYh0u7XIvCc2ujLNQU5iUyeexxLXTWmc5hUtr/TnweSbH5mNs7ySEEPlW4dowRwghhCjIzp2D3r1Tk6MASUlGwvSEbAcmhBDi2txW9zbCLGFej/Wu09unse5pfI+7oEhaJmWiVYUCO+FcCCFEHiUJUiGEEKKgePVV8LZ1jtbwkmflYCGEEIGx9fRWJv47kWWHlpHZlmaHLx3mid+f4Infn+DI5SPXdL4jl4/ww+YfmLtnLjan7ZrGykrLCi15oPkD7krwJkyEW8J5of0L1CzpvTpxZoY3H06zcs3clZZDzCFEWCL48fYffSrwIoQQQviDLLEXQgghCooDBzI/duhQroUhhBCFlc1po/+0/iw5tASFQilFpaKVWDpkKWWjyrr7PbHgCcauGet+PnbtWJ5q8xQf9fjIp/NprXl+8fN8tvYzLCYLCkWoJZTF9y2mabmmfntfaX3a81PuanQX07ZNw2KyMKjxIFpWaOnzOKGWUJYNXcZvu3/jj/1/UC6qHEObDaVa8Wr+D1oIIYS4CkmQCiGEEAVF9+6wcKH3Y1265G4sQghRCL3/9/v8efDPdPtr7ruwjyGzh7Dg3gUAbDy5MV1yNMXHaz5mcNPBNCvfLNvn+33f73yx7ot0e3bG2GLoNbkXR546gkkFZsHgDZVv4IbKN1zzOBaThdvr387t9WWfbCGEEMElS+yFEEKIguKpp6BIEc/2yEhZYi+EELng6w1fexQfcrgcLDm4hJikGADeWf5Opq9/e/nbPp1v3PpxxNnjPNovJ11m7fG1Po0lhBBCFGYyg1QIIYQoKEwmOHIE7rgDli0z2tq1gxkzwCL/5AshRKBlVpldodx7g8bZPBOaKeLt8T6dL9YW67XdpEwk2L3HIoQQ/576l4X7F1I0tCgDGgygVESpYIeUr12Iv8Arf73C/gv76VqjK09f/zQWU/747B1vj2fmjpkcjzlOm4pt6FytM0qpdH201vxz7B+WHV5Gmcgy3NHgDoqGFvUY63TsaWbsmEG8PZ6etXvSqEyjHMe1+9xuftvzG6HmUPo36E+FIhVyPFZ25Y//YkIIIYTInuLFYfHiYEchhBCFUt96fZn470TsLnu69tqlarsTEA+2fJCFB7xvhzKi5Qifzjeo8SDWHF/jkVh1aRdtK7X1aSwhRMGntWbEbyOYvG0yNqeNEHMI/1n4H2bdNYvuNbsHO7x86Y99f9Dzp55ojIJ8Cw8s5K1lb3HoyUOUjigd5OiytuPsDjp+15EkZxIJ9gTCreE0L9echfctJMwSBhirIPpP68+fB/8kyZFEqCWUpxY8xaLBi2hdsbV7rNm7ZjNo5iD3a15f+jojWo7g45s/9ki4Xs2rS17lw9Uf4nQ5MZlMPLf4Ob697VsGNR7kvzfvhSyxF0IIIYQQQgg/eKfLO5SLKke4JRwwKrMXCSnCxL4T3X3uaHAHzco283ht83LNPfbi1Fqz+uhqpmydwp7zezxeM7jpYJqXa06U1agEbzFZCLeEM/7W8YRbw/33xoQQBcL8vfOZsm0K8fZ4HC4H8fZ44u3xDJg+IN1exiL7+k3v506Opoizx9FvWr8gRZR9d/18FxcSLhBri8WpncTaYll/Yj0fr/7Y3WfS5kksPriYOHscDu0gzh7HFdsVbp92Oy7tAiAmKYZ7frmHBEcCCY4E7C47CY4Exm8cz7LDy3yKaf2J9Xz0z0ckOBKwuWwkOhJJdCQyfM5wzsef9+v7z0gSpEIIIYQQQgjhByXCStCifAvsTjsWkwWtNZWKVvKozP5qx1cJMYdgUiZMykSIOYRXO7yars/ZuLM0HdeU7j92Z+TckTQd15QBPw/A4XK4+4SYQ1g6dCnf9f2OwU0H82TbJ9k0chN3NborN96uECKfmbh5otd9i1Gw7JBviSwBO8/uzHRrlFVHV+VyNL45duUY+y7u80juJjgS+O7f79zPv930rdf3eCXpCltObwFg0YFFmJXZo0+8PZ4ftvzgU1xTtk4h0e6ZrLcoC3P3zPVpLF9JglQIIYQIpOXLoVQpUMrYI7R1a7DZgh2V/2gN334LTZpA5crw8MNw8mSwoxJCiKD4cPWHLNy/EId24HA5sLvsRhX7WUPcfU7GnOTeWfdic9pwaRcu7cLmtHHPrHs4HXva3W/I7CHsPLeTWFssMbYYEh2JzNszL93MHjBmjd7R4A6+7/s973d7n7ql6+ba+xVC5C9a68yPkfkx4V3KDMr8KKu/C9ntl3Issz4ane3zpHDhAm8r8lXg/45KglQIIYQIlG3boFMnuHDBeK41rFsH0dHBjcufHn/ceGzdCseOGcnS5s1T37MQQhQi49aP8yjUZHfZWXRgkbug0s87fs70S97PO34GjJk5fx78M91sUTBm9nyx/osARC6EKAwGNx1MpDXSo92lXXSq2ikIEeVvDcs0dG+pklGbim1yORrfVC5WmerFq3u0h1vCGdx0sPv5kKZDiLBGePSLComiabmmAHSr2Q2ny+nRJ9IayT2N7/EproENB7r3P03L4XLQu05vn8bylSRIhRBCiEC5807v7VeuwKxZuRtLIJw8Cd98A/Fplt3Y7XD5Mnz5ZfDiEoWKUipMKdVeKXWnUmpwZo9gxykKB69LVwGlFEmOJMCoPG932j362F12dxI1pa83vla6F0KIFLfWuZV+9fsRYY3ApEyEWcIIt4Qztf9U2bc4h6bfMR2VYcpjuCWcmXfNDFJE2Tf1jqkUDyvuTppHhUTRpGwT/nP9f9x9hrcYTocqHYi0RqJQRFgjiAqJYsaAGZiUkVIsGlqUiX0nEmYJI9QcilmZibBGcF+T++hSvYtPMbWp1IZHr3uUcEs4ZmUmxBxCmCWML2/5MuBFr6SKvRBCCBEo+/ZlfmzSJLj99syP5webNkFYGCRl+CKfmAh//QUvvxycuEShoZR6CngNKJqN7pMCHI4Q3FrnVn7Y8oPHzM8aJWq4q9j3rNWTUStGeSQ6rSYrPWv1BKB0RGmqF6/O7vO70/WxmCz0qdsngO9ACFGQKaX4vu/3PNb6Mf7Y9wdFQ4tyV6O7KBdVLtih5Vu96/bmxH9O8NKfL3Hg4gE6V+3MSx1eIsQSEuzQrqpJ2SYcefII07ZP4/iV47Sp1IbuNbu7E59g/Lvz+z2/s+zwMpYdWkaZyDLc1eguSoaXTDfWgIYDuKHyDUzfPp04exy31L6FFuVb5Ciu/3b7L/c1uY85u+cQagllQIMBVC1e9Zrea3ZIglQIIYQIlGLF4Nw578eaNcvVUAKicmVjxmhGZjPUrJn78YhCRSl1P/Bh8tOdwC7gSvAiEoWBzWHjy/Vfcir2FEObDfXY73NUl1Es2LeAy0mXibfHE2IOIcQcwsQ+E919mpdvzuAmg/lhyw/uGaeR1kiGNB3iXq6olGJi34ncNOkmkpxJOFwOwsxhlIwoyVs3vpWj2J0uJyuOrOBy4mU6VO3g8eU2xb4L+9hyegs1S9R0xyOEKDiUUrSu2JrWFVsHO5QCo1xUOSb0mRDsMHKkSGgRHmjxQJZ9lFJ0rtaZztU6Z9mvYtGKPHX9U36Jq3HZxjQu29gvY2WXJEiFECLAlFJm4E6gK1AB8NxUxaC11l1zLTAReO+/D8OGebYrBa+8kvvx+FvjxtCgAWzenD5RGhpq7EsqRGA9DmjgPq31ZH8PnnztXg8c11oHdtMrkS/M3zuf26bchlMb+6y9+/e7dK/RnT/u+8Pdp3yR8ux6bBcT/53I8sPLqVe6Hg+1eohKRSulG+uLXl/Qv0F/d3XfwU0GeyxDrF2yNjVK1GDXuV1YTBYc2kGHKh2IjvB9H+utp7fS/cfuxNniUEphc9oY1WUUT1//tLuP3Wln0C+DmLdnHlazFYfLQdOyTfn9nt8pFlbM53MKIYQQ+YkkSIUQIoCUUiWAhUALvNfjS0tKRxY0Q4fC33/D+PGpbRYLLFpkzLIsCH7/He6911hSbzZDiRJGoaaGDYMdmSj46gCrApEcTfYExszU7CzfFwWcw+VIlxxNsfDAQj5c9SH/uSF1v7aioUV5vM3jPN4m8xtFSiluqnETN9W4KdM+Q38dyu7zu7G7Um9A/bbnNz5d82m6xObVOF1Obv7xZk7FnkrX/upfr9K2UltuqHwDAGNWjmHennkkOBLchaY2nNzAyLkjmXrH1GyfTwghhMiPpEiTEEIE1iigJXAMeB7oA9yYycO3HaxF/vDNN+BwwIwZsHq1MdOyc+dgR+U/pUvDggVw4gTs2AFHj0KPHsGOShQO8cCRQAyslKoE9ALGX62vKBy+//d7j+Roio//+djv54tJimHh/oXYnLZ07fH2eP639n8+jfX30b/dxZ/SSrAnMG79OPfzcevHuROjKWxOG7N2zfKIQwSXUqqoUupFpdRipdQOpdSBTB77gx2rEELkFzKDVAghAus24CLQRmt96mqdRQFlNkP//sGOIrBKlTIeQuSeVUCjAI39CfAcUCSzDkqpEcAIgCpVqgQoDJFXnE84n+mxQFSVT3AkeFRFThFji/FprCtJV1DKcyyN5kLCBffzzN6HS7uwOW2EmPN+wZHCQClVGVgBVEZWJwkhhN/IDFIhhAis0sBKSY4KIYTfvQnUU0oN8eegSqnewBmt9Yas+mmtv9Zat9Jat4qO9n1PSJG/DG46ONNjN9e82e/ni46IpnKxyh7tFmWhdx3ftsRtX6W91xmgkdZI7mhwh/v5zTVvTle5OEX90vWJCony6ZwioEYDVYBNwF1AU6B6Jo8aQYpRCCHyHZlBKoQQgXUCcAQ7CBFEWsPSpTBvnlHV/t57oXr1nI1ls8GsWcZS/Zo1jbFKlPDst2ULTJtmnHvAAGje/JreghB5gVKqo5fmj4AJSqlbgHkYS+5d3l6vtV6ezVO1A25LHjMMKKqU+lFrfW8OwhYBZnPaWHlkJS7ton2V9oRZMquDeG3KRZXjvib3uYsqpQi3hPNl7y89+h+9fJQtp7dQrXg1GpbxfU9mpRTf9fmOHj/2SFfFvlhYMd6+8W2fxioeVpz3u73P84ufJ9GRiEu7iLRG0rhsYwY1HuTu999u/2XJoSXE2eJIcCQQYg4hxBzC+Ntkp4k8pjtwCrhRa+3bdGIhhBCZkgSpEEIE1kxgqFIqXGudcNXeomBxuYwE5R9/QFwchITAmDEwcSLceadvY12+DG3bwrFjEBsL4eHw6quwbBk0bZra7513YPRoI5mqNXzyCTz9tNEuRP62FO/LRRVwR/IjM5psfu7VWr8IvAiglOoMPCPJ0bxp6aGl3D7tdlzayIlrrZl6x1RuqX1LQM436fZJdK/RnbeXv83lpMvcUvsWPrn5E4qGpdbxcrqcPPjbg0zeOpkwSxh2l53m5Zozd9BciocV9+l8jco0om6pumw5vQWLsmB32elWoxvli5T3OfbHWj9G64qtGbd+HOcTznNH/Tu4q9Fd6ZbNVy1elV2P7mLc+nGsPraahtENebT1o1QpJltI5DFFgfmSHBX5xcGLB+n5U092n98NQO2StZk3aB61S9VO16/zxM4sO7wMAIXikese4fNbPk/X55sN3/DY/MewuYxZ8VWLVWXzyM0UCy+WC+/EO6013276llErRnEq9hRNyjbhw+4f0r5K+3T9dpzdwVN/PMXKIyspElKEx9s8zvPtnsdsSi3cmuRI4q1lb/H1xq+Jt8dzU42b+Pjmj6lRIv1k8IX7F/LcoufYfX43VYpW4a0b3+KuRnflKPZx68fx7t/vcjbuLM3KNeOjmz+ibaW2Oftl5HNK64K5LUmrVq30+vXrgx2GEKKAUUpt0Fq38qF/FPA3cBh4QGt9JmDB+YFcO/1s1iy47z4jOZpWRAScOQORkdkf65ln4PPPISkpfXvjxsaMUYC9e6FJE0hMTN8nPBzWrZPK8iJofL12ZjLGUq5hPz2t9Y05OGdnjARplmua5dqZ+y4lXqLSR5WIs6e/vkZYItj/xH7KRZULSlyf/PMJLy95Od1+niHmEHrX7s3Mu2b6NFa/af2Yt3deuuXxEdYIxnQdw+NtHvdbzCLv8nbtVErtBPZqrW8LUlh+I9fOgs/msFHk3SIe23xYTVYuPX+JiJAIAJqNa8bm05s9Xv9Emyf4pMcnAPyx7w96/ORZCDTCGkHcS3Ee7bnlv3//l7eWvZXu36MIawRLBi+hTaU2ABy+dJjGXzYm1haLTv4oE2GN4K6GdzGhzwT36/pM6cOiA4vcBfNMykTxsOLsenQX0ZHGdj4L9y+k79S+6YrqRVgj+F/P/zG0+VCfYn9z2Zu8//f7HrGvHLaS5uXz7wq0nH7ulD1IhRDCj5RSE9I+gLHAfqA3sFcptUQpNTFjv+THt8GNXvjdTz95JkcBLBb46y/fxpoyxTM5CrBnj5FsBZgzx5i1mpHNBr/+6tv5hMhjtNadtdY35vSRw3MuvVpyVATHjB0zvLa7tIspW6fkcjSpxq4Z61HsyOa0MXfvXOJs2f8CH2uL9UiOglFIaeyasX6JVeRbPwKdlFJSGVHkeW8vf9vrHsh2l503l70JQIItwWtyFODztakzSIf9Osxrn3h7PLN2zvJDtL6zOW28s/wdj5t18fZ4XvnrFffzD1d/SKIj0Z0cTekzeetkTsUapSp2n9udLjkKxr9p8fZ4vt7wtbvthcUvpOuTMtaLf76ILxMgE+wJHsnRlPbXl76e7XEKElliL4QQ/jU0i2NFgM5ZHNfAcH8GI4IsJIuKv1arb2Nl1l9rI+Ga0sfk5d6n2ez7+YQQIg+7lHgJu8vu0Z7kTOJi4sUgRGSIScp81XO8PZ7IkOytHIizxWVaxf5K0pUcxSYKjPcwPk/OV0oN01rvCHI8QmRqzfE1mR5be3wtAOtOrMu0j1M73T+fjTubab9fdv7C7fVvz0GE1+Z07Ol0Maa19fRW98/rjq/z+m9WmCWMnWd3Ui6qHFvPbMVqtnokPxMdifxz/B/3813ndnk937mEcz79O3PsyjGU8vx3RqP599S/2RqjoJEEqRBC+Jf3W5uicLr/fmNWZ8ZZpEpB586+jTV8uLF/aUKaD01mM1x3HZQsaTzv1w+ef97ztWazsReqEAWIUmoJsEBr/d+r9HsGuEVr3SV3IhO5oVuNbry+9HWPmUkR1oiAVJXPrptr3czUbVM9vjBXLlqZ0hGlsz1OmcgyVCxSkQOXDqRrNyszPWp5LjEVBVfytS4jK3AdsEUpdYTMC9RprXXXQMYnRFYaRjdk0YFFXo81KNMAgKZlm3o9DqS7UVQsrBjnE8577detRrdriDLnoiOjM938p1bJWu6fG5ZpyPqT63G40tfuTXImUbNkTcDYmzXjcYBQcyhNyjRxP69SrIp7P9e0ioQUIdwanu3Yyxcp7/V8AHVL1c32OAWJLLEXQgg/0lp/fy2PYMcv/KxrVxg5EsLCjH1Ao6KMx+zZEBrq21jPPQdt2hj7loaGQpEiUK6csYw/RaVKMG6ccb6ICOMRFmYUaqpWzY9vTIg8oTNQLxv96gKdAhuKyG1NyzXl7kZ3E2lNnSkTaY3kltq3cEPlGwJ2Xq01m05uYsnBJV5ni47uOpoSYSUIM4cBYFEWIq2RjL9tvNeZOplRSvFtn2+JtEZiUcacljBzGCXCSzCqyyj/vBmRX3T28miXfMwEVAM6ZtKvc24EKERm3u7yNmZl9mg3KZP7WlYsvBhVi1X1+vq7G93t/nlsT+/bi1hNVgY3G+yHaH0XZgnjsdaPEWGNSNceYY3gzc5vup8/c8MzhJpDPV57c82b3YXwmpZrSovyLTz6hZhDePi6h93P3+nyjtfzvdzxZUwq+ym+qJAoHmzxoNexXu8sS+yFEEL4mVKqChCrtb5wlX4lgCJa6yO5E5nIFUrBhx/CQw/BwoVQtCj07WskN30VGgpLlsA//xgFl6pWhVtu8Vw6P2QI9OhhzFzVGm69Fcr7XvFYiAIkFPC+/k3ka9/c+g231rmV7/79DqfLyeCmg+nfoL9PiUhfpFRiPnblGGaTGbvTzgfdPuCR1o+4+1QpVoUdj+7gy/VfsvzwcuqVrscTbZ7wqNacHZ2rdWbjyI18+s+n7D6/m45VO/LIdY/4NBNVFAg52kNZiLwgKiSK1cNXc8vkWzgXfw6AUuGl+O3u3ygeVtzdb8//7aH+5/XTzZrvUasHP/VPnQgwqPEgtp/ZzpiVY9x7eRYLLcamkZty581kYsxNY4iwRvDR6o+Is8dRqWglPr75Y7rWSJ28Xa90Pf649w8emvcQO87uIMQcwtCmQ/no5o/SjTV/0Hwenf8o07ZPw+Fy0KJ8C77u/TWVilZy97mjwR3E2+J54c8XOBN3hqKhRXml4ys81fYpn2P/+OaPKRJShLFrjf2zqxarytieY2lfpX3OfyH5mFSxF0IIH+Sgir0TmKi1znJvUaXUN8AwrXVQb1zJtVMIEQj+qGLvZUwXxvX1/iz6mICtQAmtdQV/nj8tuXYWfFpr6n5el/0X9+PSqSuZI6wRLLx3Ie2qtMvi1ULkTCCunVmcawJGUdEzWutGXo7fA6Ts4xMLPKy13px87BAQg3EzypHdmOXaWbjE24wCdimV671xOp2cTThLdHg0ZrPnzNMUZ2PPEhUSRXhI9peUB5pLu0hyJBFmCcvyRl2SIwmr2ZrlbE+ny4nD5SDUkvmKM601iY7Eq54vu7HbnDbCLGHXNE5ekdNrp8wgFUKIwFLJj+z2FUIIkQkve/H1yGR/PjA+59YCygLTAxqYKPA2ntzIydiT6ZKjYFT7/WztZ5IgFblGKdUROKW13nOVfrWB8lrr5dkceiLwOTApk+MHgU5a64tKqZ7A10CbNMdv1Fqfy+a5RCGUVWI0hdlsplxUuav2i46K9kdIfmVSpmztAZpV0jOF2WTGbMo8QQzGViy+7DmaFZMyFZjk6LWQBKkQQuQNxYGkYAeRpyUmwvr1xh6czZoZy9cDyeWCDRvA4TAKIVky+SfzyBE4cADq14eyZQMbkxCic5qfNVAu+ZGVTaTOehIiRy4kXPA620ejORN3JggRiUJsKfAdkOXqJOA54H4g6yxLMq31cqVUtSyOr0rz9B+gUmZ9hRAiP5IEqRBC+FnyvqNpRXlpS2EB6gPdMe7MC2+mT4cHHjCSoi4XlCkD8+ZBvezUZ8mBtWuNvUJjY41zWiwwdSp0S1MhMyEBBg409hYNDTUSuEOGwBdfGFXjhRCBkLIXnwKWAAuA9zLpawOOy97Owh9aV2yNzWnzaA+3hNOnbp8gRCQKuWCvOhoO/J7muQYWKqU08JXW+uvghCWEEDknCVIhhPC/Q0DaDZ77Jz+yooCfrtKncNqxA4YONRKSKeLioEsXOHrU/8nI2Fjo3h0uX07f3rcv7N9vVI4HeOIJIzmamGg8AH78EWrVgmef9W9MQggAtNbLUn5WSi0DlqZtEyJQioUVY0zXMby85GXi7cY+emHmMKoWr8oDLR4IcnRCeFUGSLhqLx8ppW7ESJCmreLSTmt9QilVBliklNqV2dJ+pdQIYARAlSqZzR8QQojcl/musEIIIXLqSJqHBuIztKV97AOWAU8AY4IRbJ731VdgyzBrR2sjkfnXX/4/3+zZ4PRS8NrpNBKgYCy7/+GH1MRoivh4+PRT/8ckhPCgtb5Ra/3fYMchCo9+9ftRNrIsZmXGrMy4cPHYdY8RGRIZ7NBEAaeU6pjySG4ql7Ytw6OLUupRjNVJWe5TmoM4mgDjgT5a6/Mp7VrrE8l/ngFmAa0zG0Nr/bXWupXWulV0dN7bR1IIUXjJDFIhhPAzrXW1lJ+Tqyz/nFWVZXEVp055T1gCnAtALYDz58Fu92xPSoKzZ42fbTbvfcBz5qkQQoh8T2tNjx97cOTyEZza+DfJ6XTy3OLnaFmhJW0rtQ1yhKKAW0r61Uk3Jz+yooCv/BVA8nZRvwD3pS0QpZSKBExa65jkn7sDb/nrvEIIkVskQSqEEIE1DGOWqMip3r2N/Ubj4tK32+3QoYP/z9e5s/dl+1FR0LWr8XNEBNSuDbt2pe+jVGBiEkJ4UEpNyGZXG3AO2ADM11pLQTzhs02nNqVLjqZIsCfw2ZrPJEEqAm05qQnSTsAZYFcmfW3AcWCW1vq37J5AKTUFoxBeaaXUMeB1wAqgtR4HvAaUAr5QRqFMh9a6FVAWmJXcZgEma60X+PLmRMF3Pv488/bOQ2tNrzq9KB1ROsdj7b+wnyUHl1A8rDi96/T2Wsn9XPw5Rq8Yzbm4c9zf4n46V+t8DdFfndPlZOH+hRy5fITrKl5Hi/ItAno+ERiSIBVCiADSWn8f7Bjyvbvugk8+MZKR8ca+b0RGwmOPQcWK/j9f06bQrx/MmpWalI2MhBtugJtuSu331VfQs6cxs9TpBKsVwsPhww/9H5MQwpuhyX+mJA0yFi3J2K6BM0qpoVrrPwIcmyhgzsefx2Ly/Oqk0ZyMPRmEiERhorXunPJz8uqk3/29OklrffdVjj8AeGy4q7U+ADT1ZyyiYPlp6088MOcBLMoCCh6a9xDjeo1jSLMhPo2jtebpP55m3IZxmJTJvd3JwvsWcl3F69z9xm8cz4O/Peh+/sPWH2hZviVrH1iLyeT/XSaPXj5Kh+86cCHhAg6XA6UUHat25NeBvxJiDvH7+UTgyB6kQggh8raQEFi5Et57z0hS9uxpVJQfE8AtW7//HsaPN2aMduwIY8fC3LmQ9kNVx46wfj0MHgytW8PDD8PWrVC/fuDiEkKkNQz4H0YC9BjwCfAUxp7OHwNHk499AbwK/EXqTKeGQYhX5GOtKrQiyek5+TjcEk6v2r2CEJEoxG4E3gt2EEJkx/Erx3lgzgMkOhKJtccSa4sl0ZHIw/Me5sjlIz6N9fu+3/lm4zckOhKJt8cTY4vhUtIlek/pjdNlzO5PdCQy4rcRHq/dcHIDY1YG5rvDoF8GcezKMWJsMSQ4Eoi3x7Ps0DI+XCWTJvIbSZAKIYQfKaWc1/BwBDv+PCs83Jgx+vffMH++sexeZZws5kcmEwwcCIsXw7JlcP/9xgzRjOrXhwkTYM0aoziTVGMVIjdtwEiSvg/U1Fo/rbX+VGv9mdb6P0Ct5GPDgDla65swloiGAf8JVtAifyoRXoI3Or9BhDXC3RZuCadi0YqMaOn5Zfxy4mXWHFvDyRiZXSr8S2u9TGu9O9hxCJEdM3fO9Nru0i5m7Jjh01hfb/iaOHucR3uCPYHVx1YDMG79OHS67XpTfbn+S5/Olx3n48+z9vhaz+1XHAl8s/Ebv59PBJYssRdCCP+6lqxdADN+QghR4LwJHNdaP+/toNbaoZR6Aeib3Lcf8C7wEMY+e0L45Pl2z9O8XHPGrhnL2biz9Kvfj4eve5gioUXcfbTWvPTnS3yy5hNCzaEkOZPoWasnP/b7MV1yVYjsSi6OlGNaa9+m6QnhR0mOJPfszrSc2kmiI9GnsRIcCV7blVLuseJsngnUFDanzafzZYfdZUdl8hUuEOcTgSUzSIUQwo+01qaMD4ylnvHAR0BzoARQPPnnD4E44KPkvkIIIbKnA7A+qw5aa53cp0PycwewFSgf8OhEgdS9ZnfmDprLmgfX8Hz75ykaWjTd8W83fcvYtWNJdCRyOekyiY5Eft/3Ow/PezhIEYsC4BBwMIePA7kfrhCpbq17q9f9m60mK7fVvc2nse5tfC+R1kiPdpd20b5KewBGthqZ6esHNBzg0/myo1xUOWqUqOHRHmIO4c6Gd/r9fCKw5Mu4EEIEkFJqOPA40FNr/YzWerPW+rLW+kryz88CPYEnlFIPZj1aunHDlFJrlVKblVLblVJveunTWSl1WSn1b/LjNf+9MwEY+45arcZy/7AweOcdzz6LFkGZMkYfk8nYr/TKlfR9HA54802IjjbG6doVtm0LXNxaG1sDVKsGoaHQrJmxnYAQ+UsUEJ2NftFA2m9UlwDZ0kQExPur3ifeHp+uLdGRyLRt00iwe5/9JMRVHMnkodI8riQ/0rYdwdiLWYigqVe6Hk9f/zQR1ghMmFAoIqwRPNb6MRqVaeTTWHc3vpsbKt9AVEgUYCRZwy3hfNfnO8IsYQCUjijNk22e9HhtqfBSfNg9MHuC/nD7DxQJKUK4JRyAqJAoqharyqsdXw3I+UTgyBJ7IYQIrEeAFVrrFZl10FqvVEqtAB4GsrtZTRLQRWsdq5SyAiuVUr9rrf/J0G+F1rp3jiIXWbv3Xvjpp9TnSUnw6qtgNsOLLxpt27fDzTcbCUkw/ly3DmrXhtOnU197//0wYwYkJH95XrLEKEi1dStUrer/2MeOhZdegvjkL/GbN8Nttxn7u3bu7P/zCREYu4FOSqmmWuvN3joopZpiLKdPe8ehInA+8OGJwuh8fOZ/tWJsMYRbw3MxGlEQaK2rpX2ulDID04AI4G3gB6315eRjRYH7gFcw9mmWKWwi6N7p8g596vZh6rapuHAxsOFA2lRq4/M4FpOF3+/5nQX7FjBv7zxKR5RmaLOhHjM4P+7xMbfWvZU3lr7BxYSLDGg4gJc6vOR1Jqs/tKzQkv2P72fivxPZd3Ef7Su3Z0DDAe6krcg/JEEqhBCBVRf4NRv9TgKtszto8rLR2OSn1uSH9x3Jhf+5XDB5svdjb7+dmiB99NHU5GhaZ87AH38YydPjx+HnnyExwz5MiYnw8cfwySd+DR2n05itGp9+hhMJCUbSdNUq/55PiMD5EhgHLFFKfQBMwZgtpYHKwN3AM4A5uR9KqXCgBbAwGAGLgq9j1Y7M3jXbo0hIdGQ00RHZmfAsxFU9DfQCWmitd6Y9oLW+AvxPKbUE2AQ8i1S8F3nAdRWv47qK113zOGaTmV51etGrTq8s+3Wp3oUu1btc8/myKzoymmfbPZtr5xOBIUvshRAisJIw9hq9mubJfbNNKWVWSv0LnAEWaa3XeOl2ffIy/N+VUg19GV9k4cwZ74lPSJ0FCrBjR+Zj/Pmn8eeuXcYy94zsdli7NucxZubiRc/kaIqdO723C5EHaa2/BsZj7Ov8DrAfSMS4lh4ARgElgQnJfQGqA7PI/mx9ESDHrhxjy+ktBa6Ixbs3vUuR0CLumUopy0m/7PUlSqUv5KG1Zte5Xew+txud2b8pQngaCizNmBxNK/nYX8CQ3ApKCCHyuzyRIFVK9VBK7VZK7UuuNuqtT+fkPfS2K6WW5XaMQgiRQ8uBukqpt1XGb0aAMrwF1Evum21aa6fWuhlQCWitlMq4kc9GoKrWuinwGTDb2zhKqRFKqfVKqfVnz571JYTCq3RpY09Rb9ImO2vVynyMdu1S+yR5yY1bLNC0ac5jzEzx4hAS4v1YDc9N5oXIy7TWIzCq0y8DbBizRc3JPy8H7tBaP5im/w6t9X1a69+DEa+As3Fn6fhdR2p/Vpv2E9pT5v0y/LDlh2CH5Td1StVh80ObebDFgzQu05h+9fuxdMhSetdJv9vNhhMbqPFpDVp93YoWX7eg5tiabDy5MUhRi3ymOnAxG/0uAdUCGokQQhQgKth3K5P3UNkDdAOOAeuAu7XWO9L0KQ6sAnporY8opcporc9kNW6rVq30+vVZFjYVQgifKaU2aK1b+dC/EbAGCMOY3TQVo6ooGB9aBwK1MGY9tdVab81hXK8DcVrrD7LocwhopbU+l1kfuXb64NZbYe5cz/YXXoAxY4yf16+H67wsJypWDC5dSn1+++2wYEH6ZfaRkbBpk7Ffqb+NGmXEGBeX2hYRYeyD2rOn/88nCj1fr505PIcZKJ389HxyxfpcIdfO7Gs7vi0bT27E7rK72yKsESy+bzHXV74+iJHlnsuJl6nySRWuJKUv2FcstBhHnzpKkdAiQYpM5DXerp1KqVMYnxtrZXadU0pZgH1AmNa6XOAjzRm5dgohAiGnnzvzwgzS1sA+rfUBrbUNI3nQJ0OfQcAvWusjAFdLjgohRF6htd4G3IKxx2gt4GWMJaHjMTbQrw2cAnr7khxVSkUn3zxK2VPvJmBXhj7lUmatKqVaY1zzpTCJv/z6q5HYTJlJajbDY4+lJkcBWrWCadMgKiq1rU4dY1l9WlOmwAMPQHi4MV7z5kZV+UAkR8HYa/T116FECeN8VavC999LclTka8mz6k8nP6RKfR605/wetpzeki45CpBgT+Djfz4OUlS57+cdP+N0OT3aHS4HP+/4OQgRiXxmIcY+y98opTyy6UqpKOCr5D5/5HJsQgiRb+WFIk0VMTbUT3EMyFjSrA5gVUotBYoAn2qtJ2UcSCk1AhgBUKVKlYAEK4QQvtJaL1NK1QLuADphLIkHOI6xLHSG1johs9dnojzwffKMKRMwXWs9Vyn1UPI5xyWf72GllANIAAbqYC8bKEhMJvjlF6NgU2KiMQPTmzvvNB6JicbSdpOXe5NhYfDZZ0Z1eafTWF4fSErBs8/CM8+AwwFWa2DPJ4QQwKnYU4SYQ0hwpP8nT6M5cvlIkKLKfadiTxFv99wLOsGRwMmYk0GISOQzrwA9gcFAH6XUXNKvTroVKAZcAF4LRoBCCJEf5YUEqbdN3DJ+gbcALYGuQDiwWin1j9Z6T7oXGRvwfw3GdP0AxCqEEDmitU4Efkx++GO8LXgp/pScGE35+XPgc3+cT2TBZMo8OZpWWNjV+ygV+ORoxvNJclTkY8k3ie7E+IxYAWM7E2+01rprrgUmvGpatilJTs89l8PMYdxc8+YgRBQc7Sq3IzIkklhbbLr2CGsE7au0D1JUIr9I3nKuE/ADxmfBe0n9/pzy3fpf4D6t9eHcj1AIIfKnvJAgPYYx/T9FJeCElz7ntNZxQJxSajnQFGPvUiGEEEIIUcgopUpgLDVtgfcb7mnJjfM8oFhYMV7t+CqjV4wmzm7sgRxiCqFEeAkeb/N4kKPLPZ2rdaZ1xdb8c+wf90zSCGsEbSu2pWPVjkGOTuQHyfU6Wiql2uNldZLWekXQghNBtWDfAt5Y+gb7L+6nWblmjOoyitYVWwc1pjhbHKNWjHIX5Lun8T280vEVokKi0vWbu2cubyx9g8OXD9OifAtGdxlNywotgxGyKKTyQoJ0HVBbKVUd44I+EGPP0bR+BT5P3mw6BGMJfuHZqEgIIQo7ux2mT4eff4aiRWHECGifw1k2R47Ao4/CmjVQvjz8979wcw5nLq1eDU88AYcOQaNG8L//Qf366fvYbDB1KsycaVSQf+ghuN5LIZKNG+GLL+D0aaMA1ODB2Zt1mpHLBW+/DePHg9YwZIjx3NvS/nxq/4X9fL72c/ac30Onap14sMWDlAgvEeywRO4bhbHC6CjGbPldwJUsXyGC7qUOL9GoTCM+XPUhZ+LP0Kt2L55r9xylIkoFO7Rco5Ti93t+Z9z6cUzYNAGA4c2HM7LVSJS6Wq5fiFRa65XAymDHIfKGqdumMnzOcPeNl8UHFrPq6KqgFsFzaRc3fn8jW89sJdFhFCP95J9PWLh/IeseXIfZZAbg+3+/55H5j7hjX7h/ISuPrGTZ0GW0qhDQGo9CuAW9ij2AUuoW4BPADEzQWo/KsI8eSqlngWGACxivtf4kqzGlIp4QIhCuVhFPKXUAY6bSTVrrg8nPs0trrWtec5DXIE9eOx0OuOkmoyJ8XJyxLDw8HF591agY74vt26FJEyOJmNaYMb6PNWmSkXxMSylYuhQ6Js8Astmgc2fYsiV97KNGwZNPpr5u4kQjaZuYaMQWEQE1a8I//2Rv+X5a9erB7t3p22rUgP37fRsnj1p2aBm3TL4Fu9OO3WUn3BJOsbBibByxkfJFygc7PJGJQFSxV0odw9h6qaHW+pQ/x/ZVnrx2CiHyvUBcO/MSuXb6j9aaSh9V4kRsxsW4xrYeK+8PTh79j31/cMfPd3hsKRIVEsXU/lPpVacXLu2i7AdlORd/zuP1Xat3ZfHgxbkVrigg8nMVe7TW87XWdbTWNbXWo5LbxmXYS+99rXUDrXWjqyVHhRAiiKolP6wZnmf3ITL65ZfU5CgYsyLj4+HNN+HMGd/GGjTIMzkK8PLL3tuzMnKkZ5vWcPfdqc+nTk1NjqYcj4+HF1+EixeNtvh4eOwx48+UGOLjjYTmt9/6FtO0aZ7JUYADB+C773wbKw/SWjPs12HE2+PdVbATHAmciz/H60tfD3J0IghKAyuDnRwVQgghgi3GFsOZeO+fi/899W/uBpPGhpMbvBali7XFsv6EkRw/F3+OmKQYr6/feHJjQOMTIq08kSAVQogCpDpQAziQ5nl2HzVyO9h8Ydas1ARjWlYr/PWXb2Nt3+693eWClT7cWT93zpjt6c2JNHfuf/nFe+whIbB8ufHz2rVgNnv2iY83thXwxfffZ37shx98GysPOhV7ipOxnhWeHS4Hv+3+LQgRiSA7ATiCHYQQQgSSUsqplHIopeqkeZ7dh1wjC4lIayRhFu9bM1UoUiGXo0lVtVhVIqyeq6GiQqKoVrwaAMXDiruX2mdUqWglr+1CBIIkSIUQwo+01oeTH44Mz7P1CHb8eVKJEt4TiEpBsWK+jZVVhfiyZbM/TlbL3tPu9VmypBFnRlqnxl6sGDid3scq5eOefMWL5+xYPhFhjcClvc/0LRJaJJejEXnATKCjUio82IEIIUQAKdJ/b1c+POT7fiFhNpl5os0THsnICGsEr3V6LUhRQb/6/Qi3hKPS1FJUKELNodzR4A4AQswhPNzq4TwXuyh85IIphBAibxsxAkJDPdutVuja1bex7rjDe3uJElC3bvbHiYiAatW8H+uYpgLxyJHGnqPeXt+hg/Fzs2ZGsaiMidTISHjkkezHBPB6FsvM33jDt7HyoGJhxbip+k1YTdZ07RHWiEJVAVu4vYkxi3SaUqpMsIMRBq01hy8d9rqXXFoXEy5y6NKhTG96CCEMWmtT8mNPhufZegQ7fpF73uz8Jo9d9xgR1ghjj/bQYozuOpp7m9wbtJjCreH8ff/fXFfhOkLMIYSYQ2hZviUr719JZEiku9+7N73LyJYjCbeEE24Jp3hYcT7o9oE7iSpEbsgTRZoCQTZ8FkIEgq8bPiulZgGLgSVa652Bi8w/8uy186uv4KmnjKQoGAnTBQugRQvfxnE4oHFj2LUrtS0sDNatM6rQ++LYMWjYEK6kKZpdsSLs2ZN+hunYsfD888ayeq2NxOcffxjFolLs3WsUorp40UiUJiXBK68YD1+NGuX5uldfhbfe8n2sPOhc/Dl6/NiDXed2YTaZsTls3NXoLib0mYBJyffAvCpARZomAMWA24EYYANwBKOgZ0Zaaz3cn+dPK89eO3PZXwf/YvDswZyPP49Lu7i+8vVM6T+FclHl3H0uJV7ivln3sWj/IswmM1EhUYzrNY7b698exMiFyJukSJPIiURHIhcSLhAdEY3VbL36C3LJ+fjzAJSKyHyFVErsZSLLYDFlsfJLiCzk9NopCVIhhPBBDhKkLoyq9gCngD9THlrrYwEI8Zrk6Wvn5cvGvp1RUcbsy6yWy1/NmjUwezbUrw/33pt+WbyvZs409hHt3j3zGa0XL8KKFVC0qBG7ty0DXC5YtQrOn4cbboDo6JzHdOkSfP65MeZjjxlL/QsQrTWbTm3i8KXDtCjfgqrFqwY7JHEVAUqQplxfvexj4UFrrb1vcOYHeframUv2X9hPk3FN0hXjsJgs1C1Vl60Pb0Ulz5LvPLEzq4+txua0uftFWCNYPnQ5LSu0zPW4hcjLJEEqhBC+y+m1U1LyQggRWL2ArsmPJsC9wD0ASqm9GMnSxcBfWutLQYoxfyhWDG691T9jtWljPPyhf3/jkZUSJeC227LuYzJB+/b+ial48ZzNPs0nlFK0KN+CFuV9nEEsCpphwQ5ApPpy/ZfYnfZ0bQ6Xg8OXD7Pm+BraVmrL/gv7WXt8bbrkKBgzhj5a/RE/9f8pN0MWIl9SSm0ieXUSsExr7VkiXAghhM8kQSqEEAGktf4d+B1AKVUK6ALchJEwrZP8eAhwKaU2aa1bBytWIYTIT7TW3wc7BpFq34V92F12j3aTMnHsirFg4tiVY4SYQ0hwJKTr49Iu9l/cnytxClEANMW46f404FBKrSH1hvs/WutMKj8KIYTIimzWJYQQuURrfV5r/bPWeqTWuhZQDfgASALMgKwtzG+0htWr4YcfYMuWzPudOgWTJ8Pcucb+oiLf2XJ6Cz9s/oHVR1dTULcnEuJa3FjtRiIsER7tNqeNluWNf94al21MktPzGhhqDqVztc6BDlGIgqIh8ATwGxAHtAdeB5YDF5VS85RSTymlmmQxhhBCiAxkBqkQQuQipVRZjBmkKbNIK2Lsn+fEKDAi8osLF4w9R/fuNQoruVzGEvlffzUKP6UYNQreeSe1wJTFYhRpuu664MQtfJLoSKTPlD6sPLoSkzKhtaZOqTosHryYkuEFa2/X/Ewp1RC4HogGtmut5yS3mwCL1tqW1evFtRvWfBgfrv4Qe6zdPZM0whrBnQ3upHqJ6gCUDC/JU22f4tM1n7r3KrUoC0VCivBU26eCFrsQ+Uly0c+dwOfJ17iWpH6uvAHoCfQAUEqd1VqXy2wsIYQQqWQGqRBCBJBSKlIp1Usp9bFSaitwAvgBGIJx1/9LoB9QWmvdNoihCl+NHAnbt0NcHMTGQny8UUTqzTdT+6xcCaNHQ2IixMQYj4sX4ZZbwO65FFXkPW8ufZPlR5YTb48n1hZLnD2ObWe2MfK3kcEOTQBKqSpKqSXAFuAr4B2gb5ou/wckKKUyqaAm/KVoaFE2jNjAw60epnLRyjSIbsAH3T7g2z7fpus3qssovur9FU3LNqVS0UoMbjaYjSM3UjaqbJAiFyL/0lq7tNbrtNZjtNY3AZVIXZ2kMG4aCSGEyAaZQSqEEIF1gdRr7SngJ4w9ov7UWh8PWlTi2tjtxkzRjEnOxESYMAHGjDGef/01JCR4vt5mM5KpmVW9F3nGt5u+JdGRmK7N7rLz6+5fsTvtWM3WIEUmlFKlMZaUVsFIkK4EHsnQbTrwIdAHY48+EUDRkdF82vNTPu35aaZ9lFLc2+Re7m1yby5GJkTBpJRSQGtSVye1BUIwkqPngL+CF53wxcGLB1l3Yh2Vi1ambaW2GP9p07sQf4FP1nyCw+Xgseseo0LRCkGINGfi7fH8eeBPNJqu1bsSGRKZ47HOxJ1h+eHllAgrQedqnTGbzB59HC4HSw8t5XLiZTpW7Uh0ZM7vFcTaYllycAkmZaJL9S5EWD23kxEFgyRIhRAisKyABrYCnwOLtdaHghqRuHYOh7Gk3pu0CdGYGGOfUm/i4vwfl/C7jMnRFC7twuFySII0uF7ESI6+B7yktdZKqXQJUq31SaXUTow9+oQQIt9TStUlNSHaGSiKkRCNB5aSXLBJa/2vj+NOAHoDZ7TWjbwcV8CnwC3J5xqqtd6YfKxH8jEzMF5r/W4O3lqh5NIuhv86nKnbp2I1WdFoqharyuLBiykXlbo7wrsr3+XFP190Px+zcgxPtXmKj3p8FIywfTJvzzwGzhyISRkLmJ0uJ5P7T+a2urf5PNao5aN4Z8U7WE3G56/IkEgW37eYhmUauvtsPb2Vbj90c2/lYnfZeb3T67zQ/gWfz/fLzl+4b9Z9WJSROnPhYvod0+lZu6fPY4m8T5bYCyFEYH2CkRxtjLH8c79Sar9SapxSakByZXuR34SHQ/Pmnu1mM/Tqlfr8zjsh0ssdcrsdOnUKXHzCb3rW7olZec5MaFG+BeHW8CBEJNK4FThIcnI0i35HgfwzzUYIIbK2ExiLkajcCYzCSJSW0Fr31Fp/4GtyNNlEkvcuzURPoHbyYwTGNlEopczA/5KPNwDuVko1yMH5C6Vx68cxfcd0Eh2JxNhiiLXFsvvcbgbNHOTuc/DiwXTJ0RQfr/mY1UdX52a4PjsTd4Y7Z9xJrC2WK0lXuJJ0hTh7HANnDORkzEmfxlpycAmjV452/65ibDGcij1Fj5964NLGxAWny0mPn3pwOu60u0+iI5G3l7/N8sPLfTrf8SvHufeXe4m3x3PFdoUrtivE2mK54+c7OB9/3qexRP4gCVIhhAggrfXTWutmQFlgEPAtxozSEcA04LRSaqNS6n2l1M3Bi1T47NtvoWjR1IJMERFQujS8/35qnwEDoG3b1CSp2WwkVz/9FIoVy/2Yhc8+7P4hpSNKu5dThVnCKBpalPG3jQ9yZAKoDGy8SnIU4ApQIhfiEUKI3LQHWJL8WK21vqbNzbXWyzG2hspMH2CSNvwDFFdKlcdY4r9Pa30guSDe1OS+Ihs+X/u5e6ZjCod2sOroKs7FnwPgzWVvenvpVY/lBTN2zMDbP9MazfTt030a68t1X3r8rgAuJ15m7fG1AKw+tpqYpBiPPgn2BL5c96VP55u2fZo78ZrRjB0zfBpL5A+yxF4IIXKB1vocRkJ0GoBSqipGtdGuwO1AU+Ap5LqcfzRpAnv2wPjxsG2bkQgdOjR94jOlYv2cOTBrFpQoAcOHG68V+UKlopXY/dhuJv47kX+O/0Oj6EY80OIBKSiTNyQAxbPRrypwKaCRCCFE7nkC4/NjJ+AljO1GEpVSK0nd535jAM5bEWNGfopjyW3e2ttkNohSagTGRAGqVKni/yjzmVhbrNd2kzK5k4GXEy9n+npvycC8JCYpBrvLM3dvd9ozfe+ZuZh40Wu7Usr9e4hJivG6f6tGcynxkk/nu5J0BbvTe+wxtrz9exc5IzNIhRAilyXfbe8AdEx+hGLsHeX5r7nI28qWhZdfhilT4IknvM8KNZvh9tth0iRj5qgkR/OdYmHFeKLtE0zpP4WXO74sydG8YxvQUimV6XRspVRFjBtQgUgWCCFErtNaf6a17guUwijK9CrwD8Zny/eAdUqpc0qpn5OTkf7i7XOqzqLdK63111rrVlrrVtHROS+cU1D0qdfHvZ9mWtGR0VQuWhmAB1s+mOnr83rBu5tr3UyIOcSjPdQSSo9aWe3o4OnOhnd6LZDkcDm4vvL1ALSr0s5rUjPSGsmdDe/06Xw9a/UkzBrm0W4xWXyOXeQPkiAVQogAU0oVVUrdppQaq5TajnFn/XtgMMZd95S9pPoGL0ohhMh3JmPMIP1KKeXx7UspZcK4toYCP+ZuaEIIEVhaa5fWeq3WerTWuivGViI3AZ8BEUA/4As/nvIYxtYmKSoBJ7JoF9nwWsfXKBdVzp34s5qsRFgjmNhnonsm5C21b6F5Oc+976sVr8bIliNzNV5fNSvXjMFNBhNpTd2TP9Iayd2N7qZlhZY+jTWk6RAalWnkHsuszIRbwvms52dEhUQBUDS0KJ/0+IQIS4S7KFSkNZLGZRtzT5N7fDpf64qtubPBnR6xD28+nEZlPOqYiQJAlnIKIUQAKaVWAy0xqnqm3GE/SnKFUYxlUKeDFF7uiI83Kr5HRV37WFeuQEhI6r6f3sTGwvnzULkymHLhPqDDYVSrL1Ysd85XCMTZ4gCjMml+E2uLxaRMXmc4FAR2p504exzFQot5XcKWy8YD9wB3AtcppeYltzdSSr2HcdOpNkZV58nBCFAIIQIt+WZQG4xl9zdhzCq14v+VSXOAx5RSU5PPd1lrfVIpdRaorZSqDhwHBmLsuy+yIToymm2PbGPivxP56+Bf1CpZi4eve5gaJWqk67f+wfV8uPpDvlz/JU6XkyHNhvBGpzcw5YPPnl/0+oLb69/OpM2T0FpzX9P7uLmm76UXQi2hrBi2gmnbpjF712yiI6MZ2XIkzcunTx6PaDmCVhVa8fWGrzkXf47b693OgIYDvM5kzYpSigl9JjCg4QB+2vITJmVicNPB3FTjJp9jF/mDuvq+9vlTq1at9Pr164MdhhCigFFKbdBat/Khvwtjw/u/SE6Kaq33BSq+a+XXa+fJk3D//bB4sfG8eXP47jto2ND3sTZsMMbaudN43quXsfdnqVKpfS5cMPYB3bvXeG6xwNtvwwsvXNv7yIzLBaNGGUWZkpKMgk2jRsEIf65mK1z2X9jP0NlD+ef4PwC0r9KeiX0mUrV41SBHdnW7z+1m2K/DWHdiHQCdqnZiYt+JVCpaKciR+YfNaeM/C//Dtxu/xeFyUDaqLJ/1/Iy+9fpm6/W+XjuzSylVBPgGI0nqzWxgiNY6oJuFyedOIUQgZHbtVEo1JDUh2hEoQmpCNBZYTuqN+K3ZPNcUoDNQGjgNvI6RaEVrPU4Zd8U+x6h0Hw8M01qvT37tLcAnGBMCJmitR2XnnHLtFEIEQk4/d0qCVAghfJCDBGkLYFM2qiznCX67djqdULcuHD5szLAEUMqYZXnggFGsKLtOnIB69YxZmimsVmjUyEicpsxiK18eTp3yfP20aXCnb3sOZcuoUTB6tDFDNkVEBEyYAHfd5f/zFXDx9niqf1qdc/Hn3BVDzcpMmcgyHHziIKGW0CBHmLmYpBiqf1qdCwkX0MnbrpmVmYpFK7Lv//ZhNXvuLZbf3P/r/UzdNpUER4K7LcISwcL7FtKuSrurvj5QCdI049cHegI1ML6gHwV+11pvCtQ505LPnUKIQPB27VRKnQBSNsNWgB1jD9KU1UlrtNbOXA00h+TaKYQIhJx+7sz787GFECIf01pvzC/JUb9avBjOnElNjgJoDTabUazIF199ZbwuLbvdmCm6dq3xfP1678lRgOee8+182eFywX//mz45Csbz11/3//kKgRk7ZhBvj3cnRwGc2kmsLZZfd/8axMiubsq2KSQ6Et3JUTBiv5hwkfl75wcxMv+4mHCRyVsnp0uOAsQ74nl7+dtBiio9rfVOrfVHWuvHtNYPJ+/JlyvJURE4WmuuJF3B6coXuR4hcks5YDPwEXALUEJr3Ulr/ZbWelV+SY4KIUReIwlSIYQQ/nfgQPrkaIr4eNizx7exduwwlrBnpJRxHoB16zJ//Zkzvp0vO+LjPZOjKY4d8//5CoEDFw8Qa4v1aI+3x3Pg4oEgRJR9e8/vJc4e59Ge5Ezi4KWDQYjIv07EnMh03659F/LsjiE5ppQKU0qtVUptVkptV0q9GeyYCqOft/9M5Y8rU+q/pSj+XnFe+vMlSZQKYYjWWrfQWj+rtV6gtc7kA4kQQghfSIJUCCGE/zVr5r1gUVQUXHedb2PdcIOxdD0jhwOaNjV+vimLzdJr1Mj8WE5FRkLp0t6PNWjg//MVAs3KNXNXIE0rwhpBs3LNcj8gH7Ss0NJr7CHmEJqWbRqEiPyrWvFqOL1MSDIpE9dV8PH/z/lDEtBFa90UaAb0UEq1DW5IhcviA4sZMnsIx2OO43A5iLXF8umaT3luUQBWBAiRz2itzwc7BiGEKIgkQSqEEML/2raFFi3SV5u3Wo2koq/7c95/v1EAyWxObQsPN5KiKcnI2rWNpKw3X33l2/myQyljiX3GxG14uNEufNa7Tm8qF62cbqZiqDmUGiVq0L1m9yBGdnX96vejXFQ5QkzpY29QugGdq3UOXmB+EhkSyXPtniPCmv7ve7glnNc6vZZrcSilnNfw8DKl3TttSJnObE1+FL6tUoLojaVveG7pYI/ny/VfkmBPyORVQgghhBA5JwlSIYQQ/qcU/PEHPPEElC0LJUvCkCHGnqHh4b6NVayYscfo3XcbxZ0qVIDnn4cZM9L3W7cO+vdPTaSWKQPz5kG7qxeQyZH77oPJk6FxYyOB27YtLFgAnTsH5nwFnMVkYdXwVYxoMYLSEaWJjojmoVYPsWLYCkwqb39cCTGH8M/wfxjWfBilwktRJrIMj7V+jCVDlqBSiojlc691fI2xPcZSq2QtioYWpVuNbvx9/9/Uj66fm2Goa3j49JdIKWVWSv0LnAEWaa3XeOkzQim1Xim1/uzZszl9T8KLzLZuMCkTZ+Pldy2EEEII/5Mq9kII4YNAV2IONrl2CiECIb9eO5VSxYFZwP9prbdl1k+unf7V86eeLNi3wKO9SEgRzj13LtM9cYUoaPLrtTO75NophAgEqWIvhBBCCCGEH2mtLwFLgR7BjaRweefGdzy2dIiwRvB6p9clOSqEEEKIgJAEqRBCCCGEEMmUUtHJM0dRSoUDNwG7ghpUIdOyQkuWDF5ChyodiAqJolbJWnzZ60v+c8N/gh2aEEIIIQooSZAKIYTI+2JiYNQoo2p9u3YwZQrkdIuYo0fh0UehYUPo1QtWrPDsY7PB4MEQFWUUYrr9drhy5dregxB+5HK5+L/5/0fRMUUJfyecbpO6ceLKiRyNlWBP4INVH9D8q+a0Hd+Wif9OxKVdfo44XykP/KWU2gKsw9iDdG6QYyp02lRqw/Jhy4l5MYa9/7eXwU0HBzskIYQQQhRglmAHIIQQBYlSasI1vFxrrYf7LZiCIjHRKIB04IDxM8DmzbBqFXz2mW9jHToELVpAbCzY7bBjByxdCt98A4MGpfarVg1Onkx9Pns2VK4M58+DRf7pFMHX7KtmbD2z1f188cHFVB9bndPPnKZ4WPFsj+NwOeg0sRPbzmxzVw3fdmYbi/Yv4qf+P/k77HxBa70FaB7sOIQQQgghRO6Rb3lCCOFfQ6/htRqQBGlGkyfD4cOpyVGAuDgYPx6eeQaqVs3+WG+8YcwEdTpT2+Lj4fHH4c47jeTnpEnpk6MprlwxZrG+/nqO34oQ/vD3kb/TJUdT2Jw2nv7jaSb0yf59ml93/crOczvdyVGAOHscs3bNYtuZbTQq08gvMQshhBBCCJGXSYJUCCH8a1iwAyhwFiwwEqIZWa3GLFJfEqR//pk+OZoiIQGOHIEaNWDWrMxfP2+eJEhF0E3dNjXTY4v2L/JprCUHlxBri/V6bOWRlZIgFUKIIFNKLbmGl2utdVe/BSOEEAWYJEiFEMKPtNbfBzuGAqdKFWNmp8PheaxcOd/GKlsWjh3zbHc6oWRJ4+dKlTJ/fYUKvp1PiACoVrxapsfKRpX1aayKRSsSag4lyZmUrt1islA20rexhBBCBETna3htDjdsF0KIwkeKNAkhhMjbRo6EkJD0bSYTlCgBnTr5Ntbzz0NkZPq20FDo3RuKFzeev/46KOX99WPG+HY+IQLg/1r/H2Zl9npsTFff/o4OaToEsyn9WApFmCWMXnV65ThGIYQQfnPjNTy6BCFeIYTIl2QGqRBCiLytdm2YNg2GDDEKKzmdxlL4X381EqW+GDAA9u2Dt982lujbbNC1K3z3XWqf0qVh6lS4557UWasmE3z6KdSv77/3JUQOhVhCWDx4MT1+7OGe+alQvNLxFbrV7ObTWBWLVuS3u39j0MxBxNnjcGkXFYtUZPbA2YSYQ64+gBBCiIDSWi8LdgxCCFEYSIJUCCFygVIqDONOfh2gKOBtiqLWWr+dq4HlF717w+nTsG2bMQO0du2cj/Xii/B//we7d0P58t6Xzd95J9xxB8yfD0lJ0KePVK8XeUrnap1JfCWRRfsXcSHhAn3q9SHMEpajsbpU78KJ/5xg25lthJhDqFuqLiqzWdRCCCGEEEIUQPJtTwghAkwp1R8YB5TMqhvGPlGSIM2MxQLNmvlnrKgoaNky6z4mk5GYFSIP83XGaGZMykSTsk38MlagSKESIYQQQggRKJIgFUKIAFJKtQGmAi5gCtAIaAy8C9QCugHFgG8BL9WDhBBCJOt8Da+VQiVCiAJFKVUB6MPVVycNz9XAhBAin5IEqRBCBNYzGAXx+mqt5ymlvgMaa61fBlBKlQa+A24BWgQvTB9pDcuWwerVxhL1/v2NWZkZHT8OM2cae33eeivUrZv7sWbkcsGHH8LixVC9OrzzjrHvaEZHj8Ivvxj7kPbpA7VqefZJSjL2Qt23D5o0gZ49wey9eE5uOnTpELN2zkKj6VuvLzVK1MjxWD9u/pEft/5IqfBSvNH5DWqX8tze4Hz8eWbsmMHFxIt0q9GNlhWuMjs3CwcuHmD2rtkoFLfXvz3Liu1Z0Vqz9vha/jr0FyXDSzKgwQBKhJfIcVy5SWvN6mOrWXZoGdGR0QxoMIBiYcU8+p2OPc2MHTOIs8fRs1ZPGpdtHIRoc9WNwQ5ACCHyAqXUkxg3261pm5P/1Gmea0ASpEIIkQ1K64J5Q71Vq1Z6/fr1wQ5DCFHAKKU2aK1b+dD/OHBOa900+fl3wGCttTlNnyLAQWCG1vqhbI4bBiwHQjFuds3QWr+eoY8CPsVIvsYDQ7XWG7MaN1vXzqQkIxG4bh0kJEB4uLH8fdkyI0mY4ocfYMQI42eXy0gcPvMMvPVWdt5iYFy4ANWqQUxMaptSMHs23HZbatv48cY+pWDEbjLBq6/CSy+l9jl6FK6/Hq5cgfh4iIiAKlVg5UooXjwX3ox3/1v7P55Z9AxaazQakzIxqssonr7+aZ/Gcblc1P6sNgcuHUjX/t+b/suz7Z51P//zwJ/0mdoHjcbmtBFiDqF//f583/d7n/ex/Gj1R7y85GVc2oVCoZTig24f8GjrR32LXbsYOGMg8/bOw+awEWIJwaRMzB80nw5VO/g0Vm5zupz0m96PPw/8SZIjiVBLKCZlYuF9C2lbqa2735zdcxg4YyAADpcDi8nC8BbDGdtjbJ7cP9TXa2d+I587hRCB4O3aqZS6GfgduAJ8jjG7/nrgIYzVSf2B6sBY4F+t9fe5GbMv5NophAiEnH7u9LH8rxBCCB+VBnanee4AUEqFpzRorWMwkp09fRg3CeiSnHhtBvRQSrXN0KcnUDv5MQL40tfgvfr8c/jnH4iNNSrKx8bCpUtGhfiUm27nzhnJ0cRE42GzGcnUDz+EDRv8EkaO3Hln+uQoGDHfdVfq8xMnjORo2tgTE42Zptu3p/YbPhxOnTLGczqNP/fuTZ9EzWWHLx3mmUXPkOhIJMmZhM1pI9GRyMtLXmbv+b0+jfXCny94JEcBnlv8HLG2WABsThv9p/cnzh5HvD0eh8tBvD2eX3b+wuxds306397ze3l5ycskOhKxOW0kOZNIdCTyzKJnOHzpsE9jTd02lfl75xsxaSOmWFss/ab3w+ly+jRWbpu0eRJ/HviTOHscDu0gzh5HjC2GftP64dIuAGJtsQyaOYgERwIJjgTsLjsJjgS+2/QdSw8tDe4bEEIIEWiPY8wM7aa1fgXYC6C1/kZr/TzQAGPrpuHAqqBFKYQQ+YwkSIUQIrAuYszyTHEp+c9KGfppoEx2B9WG2OSn1uRHxiUBfYBJyX3/AYorpcpn9xyZ+u47I9mZ0dGjcPCg8fPcud6XmicmwtSp1xxCji1b5r09MRFSZjDMmWPMGM3IZoPp042fk5Lgr7+MxGjGPkF8f7N2zcLbyhCny8kvO3/xaaxJmydleuzrDV8D8PeRv9FetnaMs8fx3b/f+XS+mTtnek1eaq2ZtWuWT2N9t+k74uxxHu1JjiTWHl/r01i57dtN33qNPcYWw7+n/gVg0f5FmJXn/7/i7HFZ/ncTQghRIFwHrNdar/N2UGttAx7FmGH6urc+QgghPMkepEIIEVhHgSppnm/D2BOqN/AxgFIqEmgPHPdlYKWUGdiAsZzqf1rrNRm6VEw+f4pjyW0nM4wzAmOGKVWqpA01E5ltzaJU6rGstm/J61u7ZDf2zPoF8f1ltm2OTv6fT2Nl0T/lPFn2yUFNnMxe4+t2QP6OKzdl+/eeySp6F65AhJWnSaESIUQhUwxIu8TDBsbnSa11HIDW2q6U+hvZu1kIIbJNZpAKIURgLQUaKqWik5/PxdgPdIxS6j2l1P8l9ykNLPJlYK21U2vdDGM2amulVKMMXbwmCbyM87XWupXWulV0dLSXl2QwdKix72hGFSpAjeRiQL16ec6uBAgLS7+cPbe1b++9PTQUWiVvU3Pbbca+o976DBiQ+nOnTp4zTa3WoL6/vvX6et1/0mqy0q9+P5/GurfJvZkeG9lqJADtKrdDeflrFmmNZGjToT6dr1/9flhNVo92pYxiTb4Y2mwokdZIj/YQcwitK7b2aazcdn+z+73GHmWNonn55gB0q9ENh8vh0SfSGsngJoMDHmNeklyo5ADGPnyPA0PTPIYkP1KeCyFEQXAO42ZQigvJf1bL0C8MyB/VCYUQIg+QBKkQQgTWz8AyoDmA1vo88B+MJfHPAJ8ALTFmd76akxNorS9hJFl7ZDh0DKic5nkl4EROzpHO//0ftGyZWrU+MhKKFTOWn6ck58qUgS++MBKiISHGcvvwcHj8cbjuumsOIcd+/tmINy2lYMqU1OcVK8LHHxvxhoQYBajCw+G556Bxmirh335rvM+U30NUlJEgHjMm8O8jE9VLVGdM1zGEW8KxmqxYTVbCLeG81uk16pSq49NY7930HlWKes4oHt1lNFEhxnsOtYQyfcB0IqwRhFvCMWEiwhrBrXVu9TmpWadUHV7r9JpH7GO6jvG5kv3dje6mW81uRFojUSjCLeFEWiP5ecDPWEx5e/HMkGZD6Fi1ozv2CEsEUSFRzLhzBiZlfGwrElqESX0nEW4JJ9QcilmZibBGcG+Te+lSvUuQ30HuSS5U8hGQCIwBVicfGgm8j1H8DoxidffneoBCCBEYh4CqaZ7/i3FT/O6UBqVUGYziTb5t4i2EEIVYnqhir5TqgfHh1QyM11q/m0m/64B/gLu01jOyGlMq4gkhAsFflZiVUi2BO4CSwC7gu+REZ3ZfHw3YtdaXkgs+LQTe01rPTdOnF/AYRhX7NsBYrXWW0+eyfe10ueDPP2HVKiOheOedULSoZ78jR4ykZFKSMTOzUcZJrkHgcMC77xrxV6tmJDTLlfPsd/AgzJhh9O/bF+rX9+yTkAC//AL79kGTJnDrrUZCNcj2XdjHLzt/waVd9Kvfz+fkaAqXy8V3/37H5K2TKRlRkrc6v0X9aM/fw5m4M0zfPp2LCRfpXrM7rSu2znEl9T3n9/DLzl8wKRP96/enZsmaORpHa83fR/9mycEllAovxcBGAykVUSpHY+U2rTXLDy9n2eFlREdEc1ejuygZXtKj34mYE0zfPp1YWyy31L6FFuVbBCHa7AlEFXul1DyMG0NttdbrlFLfAYO11ubk4yEYM0sHAi211r5VKvOBfO4UQgRCJlXs3wJeBqprrY8opaIwEqHFgRkYN8j7Y9wk/6/W+sXcjTr75NophAiEnH7uDHqCNHkPvT1AN4yL+Trgbq31Di/9FmHMEpggCVIhRDAE4kt+DuNoAnyPcWPJBEzXWr+llHoIQGs9ThkZqs8xEgjxwDCtdZYXRrl2CiECIUAJ0jPAQa11m+Tn6RKkyW1WjJmkS7XWme8bcY3k2imECIRMEqT1gacxCnGuSG7rA0wG0u6BtAnomLIvaV4k104hRCDk9HNn8Ke5QGtgn9b6AIBSairGRvs7MvT7P2AmRtU+IYQo1LTWW0hetp+hfVyanzVGFVMhhCiIpFCJEKLQ0VrvBB7M0ParUqoORhHQlNVJc7TWXjaEz9zVVnYqpZ4F7kl+agHqA9Fa6wtKqUNADOAEHHlhQoEQQvgiLyRIvVVZbpO2g1KqInA70AVJkAoh8qHkpZ79MfaDqoRRLOkExt6hM7XWSUELLtAOHDCW2Net61nUSOR5DpeDXed2USy0GJWLVc6039HLR7mcdJl6pevlyj6fZ+POcjL2JLVK1iLCGuG1j91pZ/f53ZQIK0HFohUzHevI5SPEJMVQr3Q9zCZzpv2uRmvNnvN7MJvM1CxRM8fbDADYnDZ2n9tNqYhSVChSIcfjFHBZFSrZnqZdCpUIIQo8rfVx4Kucvj55xeb/SLOyUyk1J+3KTq31+xh7PKOUuhV4Smt9Ic0wN2qtz+U0BiGECKa8kCDNTpXlT4DntdbOrL5sKKVGACMAqlTxLCwhhBDBoJS6AWPZU2U8r3nDMSra36O1XpnrwQXS7t3Qr5+xl6fJBMWLG8WQOnQIdmQim2bumMmDvz2I3WXH4XLQonwLZgyYQfki5d19TsacpP/0/mw6tQmLyUKIOYSve39N/wb9AxJTgj2Bob8O5dddvxJiDsGpnbzS4RVe7JB+i7XJWyfz6LxHcWgHDpeD1hVbM2PADKIjo919jl05Rr9p/dh6ZisWk4UwSxgTbpvArXVv9Tmu1UdXM3DGQM4nnEejqVS0Er/c+QsNyzT0eazv//2exxc8jtYau9NOuyrtmD5gutd9SAu5Q2ReqOQVkEIlQoiCRyk1AViptZ5wlX5DMZbYZ7dIXXZXdqa4G5iSyTEhhMh38sJUnuxUWW4FTE2etn8H8IVSqm/GgbTWX2utW2mtW0VHR2c8LIQQuU4p1RCjgFIVjH3wRmEsi3ow+ef9GNfABcl9CwabDTp2hJ07jUJGcXFw/Dj07AknTwY7OpENm09tZvCswVxMvEisLZZERyJrj62lx089SNm/XGtNjx97sO74OhIdicTaYrmQcIHBswez+dTmgMT18LyHmbN7DknOJGJsMcTb43lnxTtM3TbV3Wft8bU8+NuDXEq65I599dHV9J7S291Ha81Nk25i48mN7tjPxZ9j4MyB7Dy706eYzsWfo/uP3Tly5Qhx9jji7fHsPb+XThM7kWBP8GmslUdW8sj8R7iSdIUYWwyJzkRWHFlB36l9fRqnkPgTqK+USrkrPg+4CLyolJqmlPoQWAtEAbODE6IQQvjdUKB9Nvq1A4b4MK63lZ1el18opSIw9rifmaZZAwuVUhuSJy55pZQaoZRar5Raf/bsWR/CE0KIwMoLCdJ1QG2lVPXkJagDgTlpO2itq2utq2mtq2FU5ntEaz071yMVQgjfvQVEAGOAOlrrV7XW3yY/XgXqAaOT+7wZxDj9a/58IzGasRCg0wnffx+cmIRPPl3zKYnOxHRtDu1g/4X9bD5tJD//PfUv+y/ux6Ed6folOZIYu2as32OKs8UxddtUEh3p44q3xzN6xWj3849Xf+yRmLS77Gw7s41d53YBsOb4Go7HHMeZYXu2JEcS/1v3P5/i+nHLjzhd6cfRaGxOG3N2z8nkVd59sOoD4u3x6dpsThvrT6znwMUDmbyq0JoCTCB5FqnWOha4H6Og5wDgKYybU/8C7wQnRCGECBor4PKhf3ZWdqa4Ffg7w/L6dlrrFkBP4FGlVEdvL5RJTUKIvCroCVKttQN4DPgD2IlRiXm7UuqhlGrMQgiRj3UCdmutX9Zae3xI1Vq7tNavALsxloEWDKdOgcPh2Z6YCEePeraLPOfI5SO4PP/KYjaZORljzAI+FXvK656dTu3k8GX/r2i+lHgJk/L+0eV03Gn3z0euHEF7+U5nNVk5EWMsUjkZc9LrWE7t5PAl32I/HnOcBIfnTFGb08bJWN9mTB+97P3/H1azlVOxp3waq6DTWu/UWj+YUsU5ue1XoA7wMPAyxt7PrfNyFWchhAiQhsAlH/pnZ2VnioFkWF6vtT6R/OcZYBbGkn0hhMg38sIepGit5wPzM7SNy6Tv0NyISQgh/CQc2JiNfhsx9nkqGG64AbztGR0VBZ0753o4wnfda3Zn1dFVHok/m9NGywotAWhZoSU2p83jteGWcLrX7O73mMoXKU+R0CIeMZmUiQ5VUve27V6ju3vpfFpJziSal2sOQOuKrbE5PGOPsEb4HHvHKh0Zt34csbbYdO1mk5kbKt/g01jdanZj29ltHr9Xu9NO4zKNfRqrsLrWQiVCCJHXJO87mlZ7L20pUqrLt8DYeiS73Cs7geMYSdBBXmIphjEB4N40bZGASWsdk/xzd4xVVEIIkW8EfQapEEIUcLuB8lftZfTZG+BYck+TJtCrF0SkqS4eFga1a0PfvkELS2TfyJYjKR1RmhBziLst0hrJk22epExkGQDKRJbh8daPE2mNdPcJMYcQHRnNyJYj/R6TSZn4rOdn6arWm5WZSGsko7qMcrc91voxSoSVIMSUPvYX2r9AiXCjmHnFohUZ0XJEuthDzaGUiyzHsObDfIrrltq30DC6IeGWcHdbhDWCrtW70rqibxNonr7+aYqHFcdqsqYb643Ob/D/7N13fNPV/sfx16d7sfcegkxFFHHgQBQBFffCPRD39qrXq9f1c+tVcSGKEwX3FvdAUaYisqcgS/bobprz++Pb0qZJoS1N0vF++sijyfmefM8noZ4mn+8ZdRLrlOtcNZ2ZvWRmu9x8xMwu2EkiQUSkOrig2M0BnUqUFb+dA+wH/IM3kr5MyjGz8yTgqxIj85sBP5vZH3hrP3/mnPuiHK9PRCTqzJVcH66G6NOnj5s+fXq0wxCRGsbMZjjn+pSj/gjgWeBw59ykUur0A34Eript9HykVGrfmZ8PL74Io0d7U+vPPhuuuy4waSpV2sbMjTzyyyN8OP9DGiY35LoDr+O07qdhxUYHO+d4Z+47PDH5CTZlbeLErifyr4P/RaOURmGLa+Lyidz/0/0s3byUfm36cftht7NHwz0C6qzLWMfDkx7m04Wf0jilMTccdAMndzs5oI5zjnGzxzFyyki25mzllG6ncONBN+5IopZHVl4WT019itdnvU5cTBzDew/n0j6XEhdT/sk6a9PX8sBPD/DFki9oltqMmw6+ieO7HF/u81Ql5e07y3hOP/DKrnZoNrMXgIucc8HrQVQSfe4UkXAo7DvNrHCzJcNbe/lnYEwpT8vFGwE62TkXPFWiClHfKSLhUNHPnUqQioiUQ0U6WzP7H96u9c8Cb+DtZg/QHjgbuAJ4wTl3YyWGWiHqO0UkHKKcIH0FONs5F7+zertDfaeIhEOovtPM/sIb3XlzdKKqPOo7RSQcKvq5s0qsQSoiUlOZWfGtrW8quIVynZldV6LMOefUT4uI7J7yblQiIlJlOefaRzsGEZGaSF+8RUTCK8RORRF5rohIjROhjUpERKqFgg2T9geaAMudc79EOSQRkWpLCVIRkTByzmkzPKnRfH4fp79zOp8s/IR8fz7t6rfjzZPf5KA2B0U1rol/TeScD85h5baVxMbEclLXk3jzlDcrtCZoZXpr9lvcO/Fe1qSv4YBWB/DAkQ/Qq3mvsLU3cflEbvv2Nuaun0vnhp25d8C9HL3H0WFrLwIuKHa/cKOSTrt4zlrKsVGJiEhVV5AYfRxvqabCP2yvAr8UHL8CuB042Tk3OSpBiohUM0qQioiISIV1f6Y7izYt2vH4ry1/0e+lfvx5+Z/0aNojKjH9tuY3+r/aH4e3zrrP7+Odue8wb8M8/rz8z6jEBPD4r49z+/e3k5mXCcCExROYuHwik4dPpmfTnpXe3rdLv+X48cfvaG/q6qmcOP5E3jj5DU7qdlKltxchFxb8rFEblYiIlJWZpQI/AL2AdcB04JgS1b4AngZOBJQgFREpA41sEhERkQqZtmpaQHK0kMNxxWdXRCEiz+WfXr4jOVrc7HWzmbNuThQighxfDnf+cOeOZGWhzLxM7vz+zrC0edNXNwW1l+XL4oavbghLe5HgnHu14PYKsAIv+flqKbdxzrmJSo6KSA1zE15ydCzQ0Tl3XMkKzrmlwEJgQIRjExGptpQgFRGJADPrZGaPmNnPZrbAzB4uduxAMxthZvWjGKJIuX2+6PNSj/25LnojNedumFvqsS8WfxHBSIr8ve1v/M4fVO5wTF09NSxtzlkfOhm8fMtyfH5fWNqMJOdc+5qwi7OISDmdBqwGLnHOZe6k3gqgVWRCEhGp/pQgFREJMzO7GJgN3AgcjLdeXuNiVZoAzwHVds6r1E69W/Qu9VjLOi0jGEmg5mnNSz22s5jDqVlqM/JdfshjHep3CEubLeq0CFneILlB1NdirWxmVs/MjjKzYWZ2cLTjEREJo47ANOdczi7qbQAaRSAeEZEaQQlSEZEwMrN+wPNANvAv4ACCd6f/AtgGHB/Z6ER2z/FdjqduQt2Qxx47+rEIR1Pk4aMeDlneMLkhAzpEZ7ZhncQ6nLf3eSTHJQeUp8SncMdhd4SlzdsPu52U+JSg9m7td2tY2ouGgsToS3jr8H2JN+V0eLHjV5jZajM7MFoxiohUsjwgqQz1WgPpYY5FRKTGUIJURCS8bsbbaXmIc+4x59y0khWcc3nAAqBbpIMT2V1/XvEnreoUzeCLi4nj8UGPM6jToKjFdFK3k3jgyAeIs6JRkm3qtuHPy6I37R/g6WOe5uLeF5Mcl0xibCJNU5vywtAXGLjHwLC0N7z3cO494l7qJdYjKS6JOgl1uLXfrdx08E1haS/Sim1UcgGwGZhA6AtQzfE2KhERqQkWAL3NrNQkqZk1wFunNLp/+EREqpGaNb9KRKTqOQiY6pz7dRf1/kYJUqmG2tZry8obVrIufR0bszbSpVEXYmKif/311kNu5eaDb2bBxgU0SW1C45TGu35SmMXHxvPUMU/x6NGPsjVnK41TGhNj4XuvzIwbDrqBaw64ho2ZG2mY3JD42PiwtRcFxTcqucw5l2lmAQu9OueWmpk2KhGRmuRd4MGC23Wl1LkfSAPejlBMIiLVnhKkIiLhVQ9YWYZ6CahPlmqsaVpTmqY1jXYYAWJiYujWpOpdd0iMS6RpXOTeq7iYOJqlNYtYexFUfKOSna3FtwLoEZmQRETC7mngfOBqM+sDvF9Q3t7MLsfrGw/HGz06JjohiohUP/oyLiISXuuAsuzA0gVYFeZYRERqko7Al9qoRERqk4LR8kcD7+Bt/nlQwaHDC24GzABOdM7lRidKCQvnYMECyM+Hbt2gCszYEalJlCAVEQmvScCpZtbHOTc9VAUzGwjsCbwY0cikSlm8aTFjfhvDuox1HNP5GE7oekLUdxrP9+fzycJP+GThJzRJacKF+1xIl8ZdKnSudenruOGrG5i8cjKdGnTisUGP0aNpxQb1rU1fy5jfxrBo0yIObXsow/YaFrQZkdQK2qhERGol59wq4GAzGwwcg3fBKBZvyaYJwIfOORfFEKWyzZoFJ50E//zjPa5fH95+Gw4+OKphidQkSpCKiITX43hTnd43s+HAN8UPmtlhwEuAD3gq8uFJVfD+vPc55/1z8Pl95PnzeHvu2+w9eW++O+87EuMSoxJTXn4eg8YOYtrqaaTnphMXE8fIKSN58fgXOWuvs8p1rjnr5tBrVC/yXT4ASzYv4cvnvmT8KeM5o+cZ5TrXtFXTGPDaAHz5PrLzs3l37rvc99N9TLtkGo1SNEiwltmxUYlzLjtUhWIblfwW0chERCLAOfcF3mZ0UpNlZsIRR8CmTUVlGRkwaBAsWwaNo7/OukhNoDHZIiJh5JybgreTfWu8K/ob8Xa1P9HM/gG+B1oBNzvntNNoLZTjy+GCDy8gy5dFnj8PgPTcdGaunckrM1+JWlzjZo9j6qqppOd6A+98fh9ZvixGfDKCzLzMcp3r5LdO3pEcLe6Cjy4od1znfXge6bnpZOd7+bCMvAxWbVvF3T/eXe5zSbX3LtAUb6OS0mijEhERqd4+/BByQ6yWkJ8Pb74Z8XBEaiolSEVEwsw59xje9KfpQF28taHqA02A2XhrRD0RrfgkuqasmoKZBZVn5mUybva4KETkGffnODLyMoLKY2NimbRiUrnOtWjTopDl2b5slm9ZXubz/JP+D8s2Lwsqz/Xn8u7cd8sVk9QITwPz8DYq+dnMbigob29ml5vZd8AItFGJiNRAZpZgZsPM7Hkz+8zMPjWz0WZ2lplFZ/qJhMfataETpFlZsEpbGIhUFk2xFxGJgMIpUGbWCG/Tpljgb+fc6uhGJtGWHJeM3/lDHkuNT41wNEVSEkKv6emcIzk+uVznMjNKWwotOa7s50qITcAR+jxJcWVZilJqEm1UIiK1lZkdDLwJtMHr64q7GHjAzM52zv0c8eCk8h16KMTFBSdJ09Kgf/+ohCRSE2kEqYhIBDnnNjrnpjvnpig5KgD7tdyP+kn1g8pT41O5tM+lkQ+owIh9R4RM0CbHJ3NQ64NCPKN0h7Q5JGR545TGNE1rWubzNEhuwMFtDibWYgNjikvmsj6XlSsmqRmcc6uccwfjjdJ/Bvgc+ApvxOgpQN+CzUxERGoEM+uB18+1BZYB9wGXFNzuA5bgJU6/KKgr1d3++8NRR0FKsYvXycmwzz7eOqQiUimUIBURiRIz62xmp5hZn2jHItETYzF8OuxTGiU3ok5CHVLjU0mKS+KSfS9h6J5DoxbX0XsczVV9ryIpLonU+FTqJNShQVIDPj/rc2JjYnd9gmI+GfYJjZIDN1BKiE3g+/O+L3dcb5z8Bu3rt9/xXqXEp3BUx6O4/sDry30uqTmcc184565xzh3nnBvinBvhnPtAuziLSA10D5ACPADs6Zy7wzk3puB2B9AVb/3lFEALdNcU770Hjz0G++3nJUbvuw+++QZilNIRqSxWUz839unTx02fPj3aYYhIDWNmM5xzZU5omtnJwHDg7oINmwrL7wDupGha1Djn3DmVGmwFqO+Mntz8XL5c/CUbszZyeLvD6dCgQ7RDAmDF1hV8t+w7GiQ1YHCnwSTGVXxZs3F/juPrpV/Ts2lPrjngGuJiKrbSj9/5+X7Z9yzfupw+Lfuwd7O9KxyTREZ5+87qRn2niIRDqL7TzDYA651z3Xbx3HlAE+dcld3iXH2niIRDRT93ag1SEZHwOgc4DG+TEADMrCfeFX0fMBnoAQwzs/edc+9HJUqJuoTYBIZ2id6I0dK0rdeWC/a5oFLONWyvYQzba9hunyfGYjiy45GVEJHUBGaWgDedvj/QGnDAauAH4D3nXE7UghMRqXzJwG9lqPcbcEKYYxERqTGUIBURCa/ewB/OucxiZefgfYEf7px7zcw6AnPx1o5SglREpIy0UYmI1EILgBZlqNcCWBTmWEREagwlSEVEwqsRMK1E2eFAOt6XepxzS83sZ2CnU6VEKtvc9XNZtnkZezfbmzb12lT4PH7nZ8rKKWzJ3sJBbQ4KuekUwN9b/2bWP7Po0KAD3Zt0r3B7IhCwUUkKsBQYB/xVcLg9cAbQCW+jkgOcc3OiEKaISGUbBTxrZv2cc5NCVTCzfngzmK6KaGQiItWYEqQiIuGVSLFRTQVTQfcBfnTO+YrVWwv0i2xoUlttzd7K0HFDmbFmBvEx8eTk53BGjzMYc/yYcm/AtHDjQo5+/Wg2Zm0kxmLIzc/lwaMe5NoDrt1RJ9+fz8UfX8xbc94iMTaRPH8e+7XYj0+GfUK9pHqV/fKk9ii+Uckdzjl/8YNmdmdBndvwljU5NeIRiohUMufcaDPrinfx51ngDbzd7MG7OHQ2cAXwpHNuVHSiFBGpfrTlmYhIeK0Big+VOwwvaVryin8asC1SQUntdsknlzB11VQy8zLZmrOVbF8278x9h8cnP16u8/idn6NfP5oVW1eQnpvOtpxtZPuyue3b2/h5RdGM5scnP847c98h25fN1pytZOZlMnXVVEZ8OqKyX5rULocDC5xz/ymZHAVwzvmdc7fjTUftX9aTmlkbM/vezOaZ2Rwzu3bXzxIRiQwzyweuxbtAdBPwO7Cl4DYT+BeQClxnZvklbr6QJxURESVIRUTC7Eegq5ndbGZ7A/firT/6RYl6PYGVkQ5Oap+svCw+WvAROfmB+9Zk5mXy9NSny3WuqaumsilrEw4X1Maz057d8fjpqU+TmZcZUCcnP4cP539IVl5WOV+ByA7l2agkqRzn9QE3FuwQfSBwpZlpTQgRqSpsN276/i8iUgp1kCIi4XUf3nqjD+Bd4T8A+NY5t2NdUjPbE+gITIlKhFKrZPmyKJHP3GFbTvkGMW/J3oJZyX1xwOFYn7l+1+d1kO3LLlebIsWEZaMS59wa59xvBfe3A/OAVhWKUESkkjnnYnbnFu34JcyWL4d//QuOOQbuvRfWr9/1c0QEUIJURCSsnHML8dYWfRWYANwFnFCi2pHAH8CnEQ1OaqUGSQ1oV79dUHmMxTC40+Byneug1geRl58XVJ4Sn8Ip3U7Z8XjQHoOIseCPHO0btKdBcoNytSlSzCjgsILNSEIqtlHJ8xVpwMzaA73RBSwRqQXMbLCZLTCzxWZ2a4jj/c1sq5nNLLj9t6zPlQiYOhV69oQnn4QJE+D++6FbN1i2bNfPFRElSEVEws05N9s5d5Fz7jjn3D3OuawSx59zzvV2zn0erRil9jAzxhw/hpT4FOJivL0ak2KTaJDUgAeOfKBc56qXVI+HjnqIlPgUrGAvspT4FDo37Mz5vc7fUe/Box6kQVIDkmK9Wc5xMXGkxqfy4tAXK+lVSW3knBsNjMTbqOQhM9vbzOoU3PYyswfxLkxVaKMSM0sD3gOuc84FDYM2sxFmNt3Mpq/XCB0RqebMLBZ4BhiCt37+sFKWF/nJObdPwe2ecj5Xwmn4cEhPh7yCi9fZ2bB5M9x8c3TjEqkmtIu9iIhILXNou0OZeelMRk4dyfz18+nXth9X7H8FTVOblvtcVx9wNfu13I9npz3L+oz1nNztZM7rdR7J8ck76rSr3465V87l2WnPMmnFJLo26cq1B1xLp4adKvNlSS1TsFFJoZsKbqFcZ2bXlShzzrlSPwebWTxecvQN59z7oeoUJGhHA/Tp06eUhStERKqNvsBi59xSADMbjzfraW6YnyuVISMD5s0LLvf74csvIx+PSDWkBKmIiEgt1LlRZ54a8lSlnOvgNgdzcJuDd1qnaWpT7up/V6W0J1IgeAHcSniueQvrjgHmOef+txtt1Cx//AEPPABz50KfPvDvf0PnzuFrb8UKePhhmDgROnaEW26Bgw4KX3si0gr4u9jjlXhr55d0kJn9AawGbnLOzSnHczGzEcAIgLZt21ZC2AJAfDzElDJBOC0tsrGIVFNKkIqIiIhItRPGzUb6AecCf5rZzIKy22r1Mijffw/HHedN1/T7vSTpO+/Azz9Dr16V397SpbDfft6IqLw8mD0bvv4aXn0VTj218tsTEQh94ajk6PjfgHbOuXQzOwb4EOhcxud6hRp9Hx4JCV7/+O67kJtbVJ6cDJdfHr24RKoRrUEqIiISAc45nKt63wPKElNVjT3S9B7UDs65n51z5pzbu9g6e7U3OQrel+vMTC85CpCf761zd8MN4Wnvjjtg27aidfSc89q/8sqiGESksq0E2hR73BpvlOgOzrltzrn0gvufA/Fm1rgsz5UIeO456NsXUlKgbl1ISvIubt2qPbNEykIJUhGRasjM2pjZ92Y2z8zmmNm1IeqUutOoRM7sdbM5/OXDibs3jtT7U7nisyvIzMuMakzOOUZOGUmzR5sRc08MnZ/qzMcLPg6qN3PtTPq91I+4e+Oo80Adrp1wLdm+7ChEHF3vzX2Pjk92JOaeGFo82oLnpj+nZKnUHtnZsGhR6GO//hqeNr//PnQiND0dVq4MT5siMg3obGYdzCwBOBMI+HBgZs0LliHBzPri5RM2luW5EgF168JPP8HkyfDaa97o+7ff9qbfi8guaYq9iEj15ANudM79ZmZ1gBlm9rVzruRi+D85546LQnwCrN6+mn4v9WNbjrcBdpYvi5dnvsyijYv4+ryvoxbXQ5Me4t6J9+5I1C7etJgz3z2TD8/8kKP3OBqA5VuWc+jLh5Kemw5ARl4Go38bzZLNS/j0rE+jFnukfbLgE8774Dwyfd57tTZjLTd9dRO5+blce0DQdQmRmichARITISsr+FjDhuFps0kTWLMmuDw/H+rXD0+bIrWcc85nZlcBXwKxwEvOuTlmdlnB8VHAqcDlZuYDsoAznXfFMORzo/JCBPbay7uJSLloBKmISDXknFvjnPut4P52YB7eAvlShTw77VlyfDkBZdm+bCb9PYm566OzsavP7+OBnx8IGsWa5cviP9/9Z8fjkVNGhoz922XfsnjT4ojEWhXc9u1tO5KjhTLzMrnnx3vwO031lVogJgYuucRbx664lBS4/vrwtHnzzd75i0tKghNO8EZIiUhYOOc+d87t6Zzbwzl3X0HZqILkKM65p51zPZxzvZxzBzrnftnZc0VEqhMlSEVEqjkzaw/0BqaEOHyQmf1hZhPMrEcpzx9hZtPNbPr69evDGWqt8/va38nJzwkqj4+NZ/6G+VGICLZkbwlKfBYqnvj8fe3v5PnzguokxiaycOPCsMVX1SzdsjRk+bacbVFfKkEkYh55BE46yRtJWriu3cUXhy9BetZZXpI0ObmovaOOgjFjwtOeiIiI1HpKkIqIVGNmlga8B1znnNtW4nDhTqO9gKfwdhoN4pwb7Zzr45zr06RJk7DGW9v0adGHxNjEoPK8/Dy6N+kehYigQVIDkuOSQx7r0qjLjvt9WvYhITYhqE5Ofg5dG3cNW3xVTaeGnUKW10+qT2p8aoSjEYmShAR44w1YscLbTX7VKhg50htdGg5mcOedsHat197ixfDJJ5CWFp72REREpNZTglREpJoys3i85Ogbzrn3Sx7fyU6jEiGX7385yfHJGLajLCkuicPbHR61JGNsTCy3H3Y7KfGB01eT45K5b0DRjLhrDrgmKLmbHJfMkE5D6NigY0RirQoeOPKBoIRySnwK9/S/h4J9KkRqj6ZNvR2Sw7X2aEl163rttdIKMiIiIhJeSpCKiFRDBTuIjgHmOef+V0qd0nYalQhpntacXy/+laM6HkVcTBx1E+pyeZ/L+eDMD6Ia1w0H3cD/jv4freq0ItZi6d64O++f8T5HdjxyR53WdVvzy8W/MKD9AOJi4qiXWI+r+l7FuFPGRTHyyDum8zG8depbdGnUhViLpU3dNjw15Cku3//yaIcmIiIiIiKVRLvYi4hUT/2Ac4E/zWxmQdltQFvY5U6jEkFdG3flq3O/inYYAcyMS/tcyqV9Lt1pvZ5Ne/Lt+d9GKKqqa2iXoQztMjTaYYiIiEht988/4PdDixal19mwAebPh332iczSJFu3wldfwb77wh57hL89vx9WroQ6daBBg/C3J7WGEqQiItWQc+5nYKfze51zTwNPRyYiEREREREJi8WL4cwzYfZs7/Eee8Cbb0KvXkV1cnPhgANg5syisiFD4NNPw7dm9L77wu+/Fz2uXx+WLAnfUixffOFtErh5s5coHTAAxo6N3NIvUqNpir2IiIiIiFQt69bB7bfDIYfAhRfCrFnhbW/DBjj5ZO/LfcuW8Nhj4W3P74dx4+Doo73buHFemYhISTk5Xl/422/e/ZwcmDsXDj8ctmwpqnf44YHJUYAJE+DSnc8YqrATTghMjoIXT6fQG1zutjlz4JRTYPVqyMry3odvv4Vjjw1Pe1LrVIkEqZkNNrMFZrbYzG4NcfxsM5tVcPvFzHqFOo+IiEhlcM7x3tz3OOaNYzj69aN5/Y/X8fl90Q6rUn264FO6Pt2Veg/W48AXD2TmmpnRDklExLNyJfToAY8+CpMmwWuvwUEHweefh6e9TZugdWv44ANvquiaNXDTTd7Iq3BwDs44Ay65BL7+2rtdcok3Okwr4YhISR9/DJmZwf1DXp53cQW80aOTJ4d+/quvhieuTz4JXb55M6xaVfntPfGElxQtLjfXu4A2Z07ltye1TtQTpGYWCzwDDAG6A8PMrHuJasuAw51zewP3AqMjG6WIiNQmwz8ezvkfns+ExRP4eunXXP7Z5Zww/gRqyhKu//v1fwwdP5QFGxewLWcbU1ZNYd/R+/LDXz9EOzQREfjvf70v2IVfhP1+LzlwySXhSSBeeWXwl27wpnIuWVL57U2e7I3qysgoKsvI8BLAU6dWfnsiUr2tWBG6j8rMhL/+8u6vW1f68/PywhLWTvvjuXMrv73FiyE/P7g8Ph7+/rvy25NaJ+oJUqAvsNg5t9Q5lwuMB04oXsE594tzbnPBw8lA6wjHKCIitcSf//zJuNnjyMgr+uKakZfBxOUT+W7Zd1GMrHL4/X5u+eaWoHKH49z3z41CRCIiJXzxRegvwZs3e6NLK9s335R+7PXXK7+9777zpoeWlJ3tTRcVESmub18vCVhSWpo3uh68pUFiY0M/v27d8MSVkFD6sUMOqfz2jjgCkpKCy7OzvQ2pRHZTVUiQtgKKp/tXFpSV5mJgQqgDZjbCzKab2fT169dXYogiIlJbfLfsO/wueB249Nx0vlpStXajr4hlW5aVulzAqu1hmA4lIlJepW224feH54v+zs7ZsmXlt9ewISQmBpcnJkKjRpXfnohUb4ccAvvvD8nJRWWJid5GTccd5z2OiYFrrw39/P/9Lzxx3X9/6PJDDw2MtbJceaXXX8cV22s8JcWbXdC8eeW3J7VOVUiQhtqFOeRYbTM7Ai9BGjz0BXDOjXbO9XHO9WnSpEklhigiIrVFw+SGxMcGX6VPjE2kcUrjKERUuRokNSj1WGxMKSMPREQi6frrITU1sCwhAQYNgnr1Kr+9228PXR4bC8OHV357p58eekdpMzjttMpvT0SqNzNvZP1tt0HHjtCuHdx4I/z8c2Cy8LHH4OGHvSRiTAw0aeLt8H7xxeGJ68YbvTYLL/jExMA558DEieFpr1Ejb1Ooiy6CVq28taqffBJGjgxPe1LrVIUE6UqgTbHHrYHVJSuZ2d7Ai8AJzrmNEYpNRERqmRO7noiFuHYXGxPL2XufHYWIKlfDlIa0rdc25LGhew6NcDQiEnXOwWefeTu4H3ssjB8fenr7xInQu7c3+vHgg4N3Si6PO+6AOnW8KaP77APLlgUev+gi70t2bGzRba+9wrfRyIUXwtkl+vf4ePjyy9CJzN3VqJG3uUnDhl4io04dr+zTT0sfPSsitVtioncxZ8kSb93R++7zptiX9K9/eZvN5ed765KW7Nsq2w03eFPcnfPaDMeyJMW1bAnPP+8ttzJ7tncRy0KNuRMpv6qQIJ0GdDazDmaWAJwJfFy8gpm1Bd4HznXOLYxCjCIiUkvUSazDl+d8SeOUxtRJqEPdxLrUS6zHu6e9S8s6YZhqGQW/XvRr0EjSro268vZpb0cpIhGJmquv9nZU/+ADb5Og4cPhxBMDN994+WU4/HAvKbp5M/z6K+y7r5dALK9+/eD//g/S08Hngz/+8EZELSz2EX/NGi+euLiiZO28eeHdwGjsWK/dxx7z7mdnw5FHhq+9I46AtWu95PSECd79/v3D156IiIjsVNyuq4SXc85nZlcBXwKxwEvOuTlmdlnB8VHAf4FGwLPmXR3wOef6RCtmERGp2Q5qcxBrb1zLlFVT8Pl9HNj6QBJid7IQfTXTsm5LNt2yia+XfM3va3/n6I5Hs0+LfaIdlohE2vz58NJLgRsGZWTADz94GwkVJgivuCL4uc7BeefBP/+Uvb0VK+CXX0IfO+EELwkKcNddsGmTl0AFL0mameklb5cvD99ooebNvdFQkRIfH56NTERERKTcop4gBXDOfQ58XqJsVLH7w4EwLAAkIiISWmxMLAe3OTjaYYTVwD0GMnCPgdEOQ0Si5ZtvAkeKFkpP90aTHnkkbNvmjaYMZd268rW3s41Cio8g/eyzouRocRs2wKpV0Lp1+doVERER2YWqMMVeREREREQirX79wA0+CiUkFK2FmZRU+vPLO5Kzbej1j4HAOErbiMnvD73mnoiIiMhuUoJURESiy+/3pnKOGgU//RR6NFMV5Xd+vl7yNaOmj+KXv3/BVaPY8/35fLn4S0ZNH8WUlVOqVeyR5pzjl79/YdT0UXyz9Bv8zh/tkEQqx4knhk5yxsbCued69xMSoHPn0M8/7LDytXfttaUfO+ecovvXXAMpKYHHExK8Ea3165evTREREZEyqBJT7EVEpJbauNHb+GP5cm+NudhY6NYNvv3W29W3CluXsY7DXj6M1dtX4/P7iLEY9mm+D1+e8yWpCanRDm+nVm9fzaEvH8r6jPU7Yt+/5f5MOGcCSXE7GS1WC2XkZnD02KP5Y+0f+J2fuJg4WtZpycQLJ9I0tWm0wxPZuW3bYMwYr0/t0MHbkGnPPYuOp6V5GwQdf3zRlHa/39uFuPhoz59/9vrmTZuKytq08abhF5eZCTfe6G2wlJoKt9wCI0YUHY+NhfHj4cwzA5/XsaMXZ6ERI7wLZ+++W3TRrHVreO21wOf5/XD//TB6tHf/vPO8DaBK7jy/fDk8/TTMnettEnXppd6u8cWlp8P113u7y9epA7fd5u1uHy7Oee/fq696SerzzoNjjtFuzCIiIlGiBKmIiETPFVd4687l5RWVzZrlfal+9tnoxVUGl3x8CUs2L8HnL1onb8aaGfz3h//y2NGPRTGyXbvgwwtYsWUFPlcU++RVk7lv4n3cO+DeKEZW9dzx/R3MWD2DnPycHWVLNi/hkk8u4aMzP4piZCK7sGED7LcfrF/vbcIUF+dtyPT++zBoUFG9fv28HdQnTfL64kMPDZ5W37Spd0Hryy+9HeyPOip4c6HMTGjRwkvKFrr0Um890Y+K/b+Sm+uNBs3N9R4nJnqjRTMyvKQqeJtEvfNO4PmXLoU77gj827D33jBnTtHjBx6AceNgyZKiJOmUKV68OTne6/v+e3j8cZgxoygJvG0btGzpxQDexlMXXeS93vHjd/lWV8jFF8Pbbxe1+dlnXuL4xRfD056IeKZP9y625ObCaafBgAHBFyY2bYKXX4Y//4Q+fbwLGHXrVqy9pUu9C0Br1sDgwXDSSd4GbRUxZQqMHetd0DrjDG+QQcnY583zLlQtXAj77w+PPeb1bzVFfj58/DF8+ql3oevii6FLl2hHJTWE1dQpdX369HHTp0+PdhgiUsOY2QznXJ9oxxEuEe07/X7vS3jx5GihunVh69bIxFEBufm5pN2fRp4/OPZGyY3YcPOGKERVNum56TR8qGHI2FuktWD1jaujEFXV1fjhxmzM2hhUHh8TT/pt6STEJkQhqupHfWcUXH+9l0wsTEQWatECVq4MHmW5u664Ap57LvSx+fO9L7BZWV6yNT098HhysjcS9LrrvMcNGsCWLaHP5fN5I1Hfew9OPTV0neefLxq52rNnYBIVvNd++uleMhW8kaKvvBL6XMuX73zt1IqYPt1LbGRmBpanpHhLzey7b+W2J9WW+s5Kdv/9cN993sZzfr93Uea007yLR4WJxvnz4eCDvTpZWd7/l2lp3v+3bdqUr71PPvEufOTlebe0NG80/sSJO1/fOZT//tdLdmZneyPQU1K8pUlGjSqq88EHcPLJgc+LifEuCO2zT/naq4ry8mDgQO/1pKd7F/7i471/v5IzE6RWq2jfqTVIRUQkOpzzPpyGEippWoX4nb/UdShDJR6rknx/fqnHqnrs0VDae7Kz3wGRKuGjj4KTo+BdfFq2rPLb+/jj0o8VTp+fMSN0YjYryxtNWai05Ch4SwJA6QlN8EZYgTcydMGC4ON+P3zxRdHjzz4r/Vwvv1z6sYr66itvRGtJubneqFURqXwrVsC993oXJgo/f2ZkeKPVf/65qN6ll3p9UFaW9zgz0xtBf/315WsvL88beZqZWfS5Nj3du2DzwgvlO9eSJfDII0WxO+fF/vrrMHVqUb3CtaOL8/uDk6bV1ZtveonqwotsPp/37zR8eNG/l8huUIJURESiIzbWG0FT8stybCwce2x0YiqjpLgkDmh1AEbgtKa4mDhO6HJClKIqm3pJ9dir6V5B5fEx8ZzcrYZ8gK5EJ3Q5gbiYwBWJDOOA1gdovVap2kqbDpqfH56d4FN3svZy48bezzp1vPZDadCgbO20br3r+vXqeT8TEkofKVs83p3F3qRJ2eIqjzp1Qk+xjY+v+DReEdm5L74I3R9kZnojL8FLuE2aFLxhaH5+0cWZsvrtt9ADATIzvURfeXz2WehNTLOziy5ObdhQtGRHSeG4KBYNb74Z+jXGxnr/biK7SQlSERGJnuef977kFu5WnJrqfRl94omohlUWY04YQ/2k+qTEebGnxqfSPK05Dw98OMqR7dorJ75CvcR6JMclA5AWn0aruq24b8B9UY6s6nl44MM0T2tOaryXQEmJT6F+Un3GHD9mF88UibKrrw7eCT4uDg44AJo1q/z2brstdLmZtys9eGuGtmwZvGZeaipcdVXR4969Q58rPr5omuidd5Yey913ez+TkuCEE7xEaXHJyd6SAIX+9a/Q54mJCdxkqrKcfnroRI2Zd0xEKl9SUuj/72Jji/rKmJjSL6qU7EfK0l5pM6V2dlEmlORkL86S4uKKYt9ZfDVl87fS3jfnvPdIZDcpQSoiItHTqZM3bejhh70pTf/7HyxaBK1aRTuyXerauCtLr13Kg0c9yKX7XcrIISNZcNUCmqc1j3Zou7RXs71Yeu1S7j/yfi7b7zKePuZp5l4xl8YpjaMdWpXTPK05C65awMghI7l0v0t58MgHWXrtUro27hrt0KSmys/3NlIaNsxLzk2ZUrHzXHiht0anWdGtdWt4662KnW/mTOjf3+ufjznG67uLO//80NM4x40rWmvPzNu5vVGjophiYrzk6JAhRc+ZPDn0KNfvvy+6v8ce3nqCJd16a+AanqNHe49TU73RmUlJcNxxcPPNRXWuuMIrKy4mxvt3iCuxp+2sWV68p58Ob7wRehmDXWnWzJvWm5bmxVS3rnf/3XfDM2JVROD440MnLOPjvbU8wfv//pRTgpONiYleH1cee+8d+v/n1FS47LLyneukk0KPII2NLVp7s25daF7KZ9ADDihfe1XVpZeGTpKmpsKBB0Y+HqlxtEmTiEg5aLF8EZHyU99ZRvn5MHSot4FHRob3ZT0pyRsRedNN5TvXkiXQtas3ZbS4Rx/1djguj3ff9TYyKc7Mi7NwN/vsbG906ObNgfVOPTVwR/prr4WRIwPrJCd7G0c1bOg9njcP9toreDr+U08FjjS9/npvJkLh2nMpKd7Ozi+9FPwa/vjD201677295GooS5Z466U2beolTUsmSV57zUts5OZ6saWmeptA/fijl0Apr6ws+OEH737//hoBJUHUd1ayCRO8viwmxks4+nzw+OOBCcvNm72d7RcvLkpK9u7tTdEv78jPOXO8c2Vne31Gfr538eqZZ8o/qvPDD+Hss72kaGHszz7rna/QggXQq1fgGscNGnh9X/365WuvKnIObrnF+1sQG+v9O8bFwTffaHM7CVDRvlMJUhGRctAHVRGR8lPfWUYffuhtslFyl/ekJG839aZNy36u/ff3NrMoKSbG2zCkPLvYp6YG77gOXkJ01Srv/jXXeF9aQ1m82EtKbtlS+tqhp5ziJWLBS47Onh1cJy7O++IfEwNz50KfPsEbc6SkeCNN+/Yt00srs4wM7/0PtfP844+HZyq+1HrqO8Ng+3YvUZqbC4MGhR7l6Zy3puXChd5FkP33r/g09bw8b/O1DRvg0ENLv0BTFlu3erHn58Pgwd5o/JL8fq8v/uMPLzlbODq2Jlm+3OvnGzTw3oeKXKCSGq2ifWfcrquIiIiIVC2ZeZlszNxIizotgjZRKq8t2VvIzMukRVoLrKas0yXV0/vvBydHwZsC+u233rT7spo5M3S53++NWhwwoGzn2bYtdHIUYPXqovvvv1/6OUaPhoce2vnGJF99VXR/7tzQdXw+b+OTPn280VyhNnzKyvI2NKnsBOmvvwZPtwfvvXn7bSVIpUYws8HAk0As8KJz7sESx88Gbil4mA5c7pz7o+DYX8B2IB/wVdnEbp06u17r18wbHV84Qn53xMcHL+FRUfXqFU2pL01MjDdSvyZr1w4uuCDaUUgNpDVIRUREpNrIy8/jis+uoPHDjen6dFeaPNKE0TNGV+hc6zPWM2TsEJo92ow9Ru5Bx5Ed+eGvHyo3YJHyqFev9M17yrvzfKgNPQo1Lsd6w2Xd+KPkhlDFFU7tDDXaqVDxEUA7i71wBGpqauiEZUKClwCpbGlppW+4op3npQYws1jgGWAI0B0YZmbdS1RbBhzunNsbuBco+Qf4COfcPlU2OSoishNKkIqIiEi1cd0X1/HKzFfI8mWR6ctkS/YWrv/yej6a/1G5zuOc4+ixR/Ptsm/Jzc8l25fNX1v+4tg3j2XJpiW7PoFIOFx0UeipgjExcPTR5TvXKaeELq9Tx1uHs6ySkrzROqEUH6VZfNOj4syKRjOddlropCbA5ZcX3S9ttFWDBkXTU089NXSdmJhdj7CqiL59Q6/hl5oaGLtI9dUXWOycW+qcywXGAycUr+Cc+8U5V7jQ8GSgdYRjFBEJGyVIRUREpFrIzMvk5Zkvk+XLCiq/Z+I95TrXb2t+Y9HGReT58wLK8/LzeGbaM7sdq0iF9O4NDzzgjaAs3OU9Odnb+b1k4nTJEvj3v+G880Lvpv7qq9CpU2BZfDx8911wu5Mnw5VXwiWXeJtdlNyjYOLE4M1JGjcOnBY/fLi3S3RxZvDKK0WjS2Ni4OOPg0fJ9u0Ld91V9Hj8+OCkbEJC0YZG4I1Gfeedot3p69b12nn1VWhdgZxNfr63TMAFF3ibWJWc5h8T463916iR9z7GxXk/b7gBBg4sf3siVU8r4O9ij1cWlJXmYmBCsccO+MrMZphZqWtOmNkIM5tuZtPXr1+/WwGLiFQmrUEqIiIi1cKmrE2lHvt769+lHgtl+dblxMYET+PN8+excOPCcscmUil8PnjwwcC1NbOy4LHHijYwAm+NzdNP9zb/yMuDDz7wdqefNKkoGRkXB4sWeUnF99/3drS/7LLg5OTdd8PDD3u7LPv9MG6cN9LzpZeKptC3beutRTp2LEybBkcdBSecQJCPPvJ2oB892ksk3nBD8NT7rCwv2Zmb67WXlOSV5+YW3U9IgL/+8tZd/egj6NHDS96WjP2YY+Cff7ykbn6+l6isyPT6vDxvs5Zp07w1YOPi4LnnYNQoLwFdaNYsb81Rv79oF/vvv/di39lSBCLVQ6hFuEPu6GxmR+AlSIsv0tnPObfazJoCX5vZfOfcxKATOjeagqn5ffr0qZk7RotItaQEqYiIiFQLzdOakxyfHDSC1DD6tirfhiz7ttiX3PzcoPLkuGT6t++/O2GKVNzdd8PatcHl773nJR67dfOSqOeeG7hxUno6LFjgJfVuvDHwuf37e7dQli3zErLZ2UVlGRneyMzhw6Ffv6LymBgvWVg8YRhKt27eru6h5OTAhRcGtped7e1YP2aMN4q1uCOP9G47k5oaOllbHm+9BVOneq8dvPfY5/Omzp98srf+aGamtxFTVrH+JyPD2zRq7FhveQSR6m0l0KbY49bA6pKVzGxv4EVgiHNuY2G5c251wc91ZvYB3pT9oASpiEhVpSn2IiIiUi3ExcTx0FEPkRJfNCLNMFLiU7j/yPvLda729dtzRo8zAs4VFxNH/aT6DN93eKXFLFIuO9vl/dlnvZ8zZ3rJu5Kysnb+/FAmTAjcaKlQZiZ8+GH5zlUW06eHLs/M9EauRsv48UXJ0eLi4uCnn7z7v/4aevOoaMcuUnmmAZ3NrIOZJQBnAh8Xr2BmbYH3gXOdcwuLlaeaWZ3C+8DRwOyIRV4oPx8WLvQuGu2u3FzvwlTJ5UtKWrdu1+1t3erFtTN+P6xcuev2FizwLirtTFaWF3vx2QihlCX2vLxd1ylr7Fu2wKbSZwNVutzcyvldkFpDCVIRERGpNobvO5zxp4ynT8s+NE1tyrF7HssvF//C3s3KselMgTHHj+Ghox5iz4Z70iKtBRf3vpjfLv2N+kn1Kz9wkbLY2U7whVPHU1JK3029vDvdp6SETvrFxZX/XGVtr7Jir0ylte1c0b9JSkrw2qy7er5INeKc8wFXAV8C84C3nXNzzOwyM7usoNp/gUbAs2Y208wKr3o0A342sz+AqcBnzrkvIvoCzjjD67u6dPH6y6ZNvcRkeeXne0uSJCZC9+7ez74hZqm88YbXLzRr5rXXurU3Kr+4Vau8zd3q1/fiMvOWOinpX//ylulo08Zr74ADAkfaAzzxhPf8rl1hr728+7fcEhz7Hnt4cXXv7r0fhxxCkJdf9pY0KYy9XTtYsSKwTkaGN+K/Th1vg7xu3YouGBV39dXeesyFsR9ySHCidNIkaNjQO0+jRt77MTGMg4u3b/dmWhTG3rOnd5FLZBfMlfaHvprr06ePm17aVWoRkQoysxnOuT7RjiNc1HeKSDio7yyjsWO9L3WhbN7sfal0zvuCvGhRYMIuNdWbpn7GGWVvb/Nm70t98en64G0MNWtW8CZPu8s56NjRW1+0uNRU77WfeGLltldW333nbTBVchRp06awerWXRPb7vQTA6hIzjlNTvfVhBw+OXLxSa6jvLKMbbgi9tEf9+l4/Vx49e8KcOcHlAwZ46yKDNxp+//2D66SkeMm5wvWSk5K8pUVKeuwxL+bC+zfdFFxn773hjz+8+2vWQMuWoeP99Vc48EDv/h57wNKlwXWOOw4++cS7P2lS6KRp3bqBCeXBg+HHHwMTtSkp3rIiXbp4j++5B+68M/hcffvClCne/W3bvCRlyYtjMTGwfr2XOK1sRxzhvS/F3/vUVO/vWseOld+eVDkV7Ts1glREREREpCo455zgneABRo70vuiDN2ro44+heXNvdExamvcl/NxzvY2byqNBAy+5l5rqnatOHe9cTz9d+cnRwtg//dQbQZSQULQT/MUX7/46ortjwABv7dakJO/9LBx19PnnRSNsY2K8zbEaN/YSCWlp3mipq68Ob3J0/Xp45BFv/dOXXw5cA1VEPE8/Hbp8y5ZdT0cvLj8/dHIUvAsphQqTmyVlZsLrr3v3P/00dHIU4L//Lbp/772h68ya5U2Bh9LXkYaivxlZWaGTo4WxFLruutB1tm3z1rsGWLIkODkK3ut57LGixw8/HPpcU6d65wO4447QMwf8frj99tDP3x3z5nntl3zvc3O9v6UiO6FNmkREREREqoqPPvK+0D/7LNSr5029LDnCpksXbzrkN994X6APOaTio2KGDPE2hvrii6Ld3MMxoqfQsmXeSE2/31tLtXBEUm6ul3CMlrvv9pKQ33/vve9HHx0czz77eFNmv/rKG5XWv783qjRcfv8dDj/ce58K15i9916YNs1LMouIJy+v9GM//eSNCi2Lsk7JX7Kk9GO//Qbnnx+YUC2p+Kj97dtLrzd/ftFI9tIUjpAtOTK/NDurN2MGnHKK108nJgYnSPPzvQRkoZKzD4pbutTrM4vXL2n+/LJEXD5LlngX3krKyys9+S1SQAlSEREREZGqpGfPok2ZShMXV3kjF9PS4NRTK+dcO5Ob642SLf6lOzPTSyi8/HLotfkiqVUrL76dSUjwpqtGwnnnBSZPMjK8BO2dd5Y+Yk6kNkpOLn109bHHlv08O7s4VHxDu969S09aDhzo/Tz99NDT/qFoRgB4o9ILR4qWtM8+3s/u3b1RkaEUTr3fc8/Qx6Foyj9465d+/33oekcdVdReqNGvCQlF0/lh50sYdO3q/Tz4YPj669B1ip+rsuy1V+jYExPhoIMqvz2pUTTFXkREREREwm/69NAbHWVmehueSJENG0Lvep2b6y2LICJFHnoodHn79tC2bfnOdfTRocsvuKDo/lNPBSZMCzVtWnQB5cADS0+4Pv980f0nnghdZ/BgbzkPKD2hCUUbJ8XGwqGHhq5z+eVF9595JnTsrVt7y42Al3Q966zAjQPNvER08Sn6jzwSur2TT/aWLAG47bbQswMSEgKXGqgs7dp57ScnF5XFxHhLyVx5ZeW3JzWKEqQiIiIiIhJ+iYml72Jf/MushJ4iWiiaSxGIVEVXX+0tPxFXbIJs376weHH5z/Xll94088IkohkMHw4vvVRUp0MHbxOgwuRrTAwcdljw1Ps1a6BHj6LHCQne7IDTTisqGzbMO3e9et7jwnWZP/usqE5Kirfre+GayIVxvftuYAJ44sTAdaxjYrykYPER5926wQ8/eCPmC+sMGAALFgTGPno03HWXlzitUweGDvVGsRY+D7w4R43yjhfGfvnlRWuZFr7mhQu9kZ1m3q1HD296fWEStbK9+qq3vmnLll6S+aSTvKVJmjULT3tSY2gXexGRctBuoiIi5ae+UwAvOdq+Pfz9d2B5aqr3hfaUU6ISVpU1YICX8MjPLypLToZ//9vb+ERqPPWdIiLlp13sRURERESqsvR0eOEFuOYab8TQzja4qIliYuCTT7xpp3XqeKOikpLg3HO9KZESaOxYb3RY4XuVkuJNob3llmhHJiIiUuNokyYRERERkXD76y844ABvo52MDG/U5B13BE9ZrOl69fI2N/n8c9i40dulvXPnaEdVNbVsCYsWwbffwvLlsO++sN9+0Y5KRESkRlKCVEREREQk3C67zNt4p3ANzowMbzf3a6+tfZvuJCZ6a8LJrsXGlr5pjIiIiFQaTbEXEREREQkn5+Cbb4I3KMrPh08/jU5MIiIiIrKDEqQiIiIiIuFWfPfh4uI0oUtEREQk2pQgFREREREJJzNvSnl8fGB5QgKceWZ0YhIRERGRHXTJWkREpIIycjN4a85bLNy4kN7Ne3NSt5NIiE2IdlgiUhU98wzMmgUrV0JenjdydI894LHHwtdmfr63GdIvv0CbNjBsGDRoEL72RETCYcMGePNNWLvW29ht4ECIqSZjvdatg3Hj4J9/YMAA71Yy9uxsuOce+P576NABHngA2rULX0zOwaRJMGEC1K8PZ51VuzYLFCmFEqQiItWQmbUBXgOaA35gtHPuyRJ1DHgSOAbIBC5wzv0W6VhrqiWblnDwmIPJzMskPS+dtIQ0/vPdf5g8fDKNUxpHOzwR2Q1m9hJwHLDOOdezUk7aqBHMnu3tSD5/PvToAUcc4Y0uDYfMTC+RMH8+pKdDSgr8+9/eF/B99w1PmyIilW3SJBg82Lvgk5UFTz3l9WFffeVt+FaV/fADHHecF3t2NowcCQcfDJ99VjSjYO1a6NjRe20Akyd7CdW33oLTT6/8mPx+LyH66afe34mEBLjzTi8BfeKJld+eSDVSTS67iIhICT7gRudcN+BA4Eoz616izhCgc8FtBPBcZEOs2S7++GI2ZG0gPS8dgPTcdFZsXcGt39wa5chEpBK8Agyu9LPGxHgjn66+2htFFK7kKHgjU2fP9pKj4H0R3rbNG0XqXPjaFRGpLH4/nHaa148VJhDT02H6dBg9Orqx7Up+vhd7RoaXHAXv/qRJ8OqrRfVOO63otRV37rnhievjj73kaEaG97cgJ8dr/5xzvL8TIrWYEqQiItWQc25N4WhQ59x2YB5Qcm7MCcBrzjMZqG9mLSIcao2U7cvm5xU/43eBO1Ln+fN4d+67UYpKRCqLc24isCnaceyW118v+lJe3N9/w4oVkY9HRKS85syB7duDyzMz4ZVXIh5Oufz2m5d8LKlk7JMnh35+bq6XCK5sr7/uJUdLio2FH3+s/PZEqhElSEVEqjkzaw/0BqaUONQK+LvY45UEJ1ExsxFmNt3Mpq9fvz5scdYkhmGljPyKjSllp2oRqVGqfN8ZW0pf5Fz1WbtPRGq3mJjSR7zHVfHVAmNjS4+9tP65pIQwrGu/s/etrHGJ1FD6dCQiUo2ZWRrwHnCdc25bycMhnhL0Sc05N9o518c516dJkybhCLPGSYxL5KgORxFrgR8kE2MTOavnWVGKSkQiqcr3nRddBMnJgWVm0Lmzt2GTiEhV1707hOpfU1Jg+PDIx1Me++wDdesGl6emBsZ+xBGhn5+cDHvvXflxXXCBF0NJZt661SK1mBKkIiLVlJnF4yVH33DOvR+iykqg+Lfg1sDqSMRWG7x4/Iu0rtuaOgl1iI+JJy0hjW5NunHfkfdFOzQREbj2Wm8zkNRUbzOQtDRvo6i33452ZCIiZWMG77/v7bSeluaNqExJ8dZyvvDCaEe3czEx8MEHUK9eYOzHHgtnn11U7913vTrFmXnPDYfBg+H8870EbGKi9zciNRXee6/qb3olEmZVfFy6iIiEUrBD/RhgnnPuf6VU+xi4yszGAwcAW51zayIVY03Xqm4rFl29iM8Xfc6iTYvo1awXR3Y8khjTtUcRqQISEuDrr+GXX7w17lq18nYoTkqKdmQiImXXuzesXAkffujt+H7YYbD//tGOqmz69vVi/+ADWL8e+veHffcNrFO3LmzaBM8+C19+CZ06wd13hx59WhnM4Jln4Ior4KuvvOTsySd7SWiRWk4JUhGR6qkfcC7wp5nNLCi7DWgL4JwbBXwOHAMsBjKBKn6pvfqJj43nhK4nRDsMEalkZjYO6A80NrOVwJ3OuTHRjaoCzKBfP+8mIlJdpaYGjrqsTtLSdr0jfUwMXHWVd4uUHj28m4jsoASpiEg15Jz7mdBrjBav44ArIxORiEjN4ZwbFu0YRERERCRyNA9QREREREREREREai0lSEVERERERERERKTWUoJUREREREREREREaq0qkSA1s8FmtsDMFpvZrSGOm5mNLDg+y8z2DXUeERERERERERERkfKIeoLUzGKBZ4AhQHdgmJl1L1FtCNC54DYCeC6iQYqIiIiIiIiIiEiNFPUEKdAXWOycW+qcywXGAyeUqHMC8JrzTAbqm1mLSAcqIiIiIiIiIiIiNUtctAMAWgF/F3u8EjigDHVaAWuKVzKzEXgjTAFyzGx25YZapTQGNkQ7iDDS66u+avJrA+gS7QDCacaMGRvMbHmUmq/OvzuKPToUe3RUJPZ24QikqlDfWWGKPToUe3So7yxBfWeFKfboUOzREbG+syokSC1EmatAHZxzo4HRAGY23TnXZ/fDq5r0+qq3mvz6avJrA+/1RTuGcHLONYlW29X5d0exR4dij47qHHu4qO+sGMUeHYo9Oqpz7OGivrNiFHt0KPboiGTsVWGK/UqgTbHHrYHVFagjIiIiIiIiIiIiUi5VIUE6DehsZh3MLAE4E/i4RJ2PgfMKdrM/ENjqnFtT8kQiIiIiIiIiIiIi5RH1KfbOOZ+ZXQV8CcQCLznn5pjZZQXHRwGfA8cAi4FM4MIynHp0mEKuKvT6qrea/Ppq8muDmv/6oqk6v7eKPToUe3RU59hrour876HYo0OxR0d1jr0mqs7/Hoo9OhR7dEQsdnMuaClPERERERERERERkVqhKkyxFxEREREREREREYkKJUhFRERERERERESk1qr2CVIzG2xmC8xssZndGuK4mdnIguOzzGzfaMRZUWV4fWcXvK5ZZvaLmfWKRpwVsavXVqze/maWb2anRjK+3VWW12dm/c1sppnNMbMfIx3j7ijD72Y9M/vEzP4oeH1lWTu4SjCzl8xsnZnNLuV4te5XqgIzizWz383s0xDH+pvZ1oL/N2aa2X+jEWMoZvaXmf1ZENf0EMer7O9GGWKvyu97fTN718zmm9k8MzuoxPGq/L7vKvYq+b6bWZdiMc00s21mdl2JOlX2fa+p1HdGnvrO6FDfKZVJfWfkqe+MDvWduyfqmzTtDjOLBZ4BBgIrgWlm9rFzbm6xakOAzgW3A4DnCn5WeWV8fcuAw51zm81sCN4CtlX+9ZXxtRXWewhvE69qoyyvz8zqA88Cg51zK8ysaVSCrYAy/vtdCcx1zg01sybAAjN7wzmXG4WQy+sV4GngtVKOV9t+pQq5FpgH1C3l+E/OueMiGE95HOGc21DKsar+u7Gz2KHqvu9PAl845041swQgpcTxqvy+7yp2qILvu3NuAbAP7OjzVwEflKhWld/3mkp9Z3So74w89Z1SmdR3Rof6zshT37kbqvsI0r7AYufc0oKky3jghBJ1TgBec57JQH0zaxHpQCtol6/POfeLc25zwcPJQOsIx1hRZfm3A7gaeA9YF8ngKkFZXt9ZwPvOuRUAzrnq9BrL8vocUMfMDEgDNgG+yIZZMc65iXjxlqY69ytRZ2atgWOBF6MdSxjod6OSmVld4DBgDIBzLtc5t6VEtSr5vpcx9urgSGCJc255ifIq+b7XVOo7pTzUd1YJ6jurAPWdUh7qO6uEqPWd1T1B2gr4u9jjlQVl5a1TVZU39ouBCWGNqPLs8rWZWSvgJGBUBOOqLGX5t9sTaGBmP5jZDDM7L2LR7b6yvL6ngW7AauBP4FrnnD8y4YVdde5XqoIngJuBnf0+HGTe8gwTzKxHZMIqEwd8VfD/7IgQx6vy78auYoeq+b53BNYDL5s3Pe5FM0stUaeqvu9liR2q5vte3JnAuBDlVfV9r6meQH1nNKjvjDz1nVKZnkB9ZzSo74w89Z27qbonSC1EmatAnaqqzLGb2RF4CdJbwhpR5SnLa3sCuMU5lx/+cCpdWV5fHLAf3hXNQcAdZrZnuAOrJGV5fYOAmUBLvOHyTxdc1aoJqnO/ElVmdhywzjk3YyfVfgPaOed6AU8BH0YitjLq55zbF2+Kx5VmdliJ41X5d2NXsVfV9z0O2Bd4zjnXG8gASq57XFXf97LEXlXfdwAKpmcdD7wT6nCIsqrwvtc46jujSn1n5KnvlEqhvjOq1HdGnvrO3VTdE6QrgTbFHrfGG61W3jpVVZliN7O98aYMnOCc2xih2HZXWV5bH2C8mf0FnAo8a2YnRiS63VfW380vnHMZzlubZSJQXTbZKsvruxBvCQHnnFuMt15u1wjFF27VuV+Jtn7A8QX/X48HBpjZ2OIVnHPbnHPpBfc/B+LNrHHEIw3BObe64Oc6vHVx+paoUmV/N3YVexV+31cCK51zUwoev4v34a9knar4vu8y9ir8vhcaAvzmnPsnxLGq+r7XROo7o0R9Z1So75TKor4zStR3RoX6zt1U3ROk04DOZtahINN8JvBxiTofA+eZ50Bgq3NuTaQDraBdvj4zawu8D5zrnFsYhRgrapevzTnXwTnX3jnXHu9/7iuccx9GPNKKKcvv5kfAoWYWZ2YpeAsMz4twnBVVlte3Am/9EMysGdAFWBrRKMOnOvcrUeWc+7dzrnXB/9dnAt85584pXsfMmpuZFdzvi/e3KuoXf8ws1czqFN4HjgZml6hWJX83yhJ7VX3fnXNrgb/NrEtB0ZHA3BLVquT7XpbYq+r7XswwQk9zgir6vtdE6jujQ31ndKjvlMqivjM61HdGh/rO3Vetd7F3zvnM7Cq8Hc5jgZecc3PM7LKC46OAz4FjgMVAJt6otmqhjK/vv0AjvNGVAD7nXJ9oxVxWZXxt1VZZXp9zbp6ZfQHMwlsT50XnXMk/elVSGf/97gVeMbM/8YbD3+J2vothlWFm44D+QGMzWwncCcRD9e9XqqoSvzunApebmQ/IAs50zlWFaSvNgA8K+to44E3n3BfV5G9OWWKvqu87eBv2vWHeBZmlwIXV5H2HXcdeZd938y7eDQQuLVZWXd73WqGa/C6p74we9Z1RoL6z6qsmv0vqO6NHfWcUVIW+06rIeyEiIiIiIiIiIiIScdV9ir2IiIiIiIiIiIhIhSlBKiIiIiIiIiIiIrWWEqQiIiIiIiIiIiJSaylBKiIiIiIiIiIiIrWWEqQiIiIiIiIiIiJSaylBKiIiUouZ2QVm5szslXI8p33Bc/4KX2TRZWZ/FbzG9tGORUSqHvWdoanvFJGdUd8ZmvrOqkEJUhEREalVKvLhXESktlPfKSJSfuo7qw8lSEVERERERERERKTWUoJUREREREREREREai0lSEVERMrBzLqY2atmttzMcs1se8G6QR+Y2SmlPOcAMxtvZisLnrPezD42s0NKqe/MzBXcH2Fmv5tZppltNLP3zaznTtp5xMymm9k/BW2tNrN3zezAynsXds7MUs3sZjObZmbbzCzLzOaY2V1mlhai/l0Fr/kuM2tmZs8XvFc5ZrbMzB40s6RS2oo3s1vMbJ6ZZZvZWjN7zczaFj9vsfp/AS8XPDy/8L3e2dQnMxtoZt+a2daCf4fJZnb8br9RIrWI+s5dU98pIiWp79w19Z1SWZQgFRERKSMz2wuYBpwHZAKfAF8Ca4BBwCUhnnMj8CtwOrAW+AhYDBwL/GhmQc8p9tzHgeeArQXP2wCcBEwp5UPufcD1QDwwFfgY2AicAvxsZqeV+0WXk5m1Lmj7IaAd3mv/CmgA3AlMMrMGpTy9DTADOK7geT8ATYFbgLdDtBWL9xofLGjrO+BHYEDBedqFaONdYFLB/SXAq8VuP4eofzHev3Ea8DkwHzgA+NDMTi3ldYhIMeo7d019p4iUpL5z19R3SqVyzummm2666aabbmW4AS8BDvh3iGNpwEElygYX1F8FHFDiWD+8D6C5wJ4ljrmCWwZwWLFyAx4oOLYCSArRXrMQsQ0taGcjkFLi2AUF53ulHO9D+4Ln/FWi3IBfCo49VbwtIBl4PVRbwF3FXvMLQEKxY92A7QXH+pV43nWFcQAdipUnAuOKnfOu8r7mgnM6IAcYXOLY7QXHFkX7d1I33arDTX3njueo71TfqZtuZb6p79zxHPWd6jsjctMIUhERkbJrVvBzQskDzrl059yvJYrvLvg53Dk3pUT9ScC9eFfdLy2lveeccxOLPcfhfUhainfVO2BqlXPuC+fcPyFi+wR4B2gIHFFKW5VhMHAQMBm41jmXWSyGLOAyYB1wdilX8/8GrnHO5RZ73jy8D7gAR5aof03Bz9udc8uKPScHuArvg/7ueso590WJsofxvmR0MrO2ldCGSE2nvnPn1HeKSCjqO3dOfadUKiVIRUREym5qwc9RBesDJZZW0cwaA/sD2/Cm+oTyY8HPg0o5PrZkgXMuH+8qNUD/UO2a2QVm9qiZvWhmrxSscVS4ftSepcVcCY4p+Pmec85f8qBzLgOYDsThvTclfVfwgbak+QU/WxYWmFkboAOQD7wVoq2NwNflij60T0OcOxfvy0JATCJSKvWdO6e+U0RCUd+5c+o7pVLFRTsAERGRauQR4FC8K8pfATlmNhPvA+dY59yfxep2wJv6UxfwmdnOztuklPJlpZT/VfCzdfFCM7sU+B+QspO26u4skN3UseDnI2b2yC7qhnrNK0qpu63gZ/EF81sV/FzjnMsr5XnLdxFDWZQnJhEJTX3nzqnvFJFQ1HfunPpOqVRKkIqIiJRRwdSdo8zsALxpPf3wrsIfANxsZnc65+4pqB5b8HMr8OEuTr2hoiEV3jGzPngL6/uAf+Et5L8SyHTOOTO7H/g33ofncCl8zT9S9GG6NKE+RAZd/S8Dt5NjFTlfOM4hUqup79wl9Z0iEkR95y6p75RKpQSpiIhIORWs6zQFwMwSgLPwFnm/y8zecs4twFvXCCDPOXdBBZtqD/xRSjnA6mJlp+J9CB3pnHs0xHM6VTCG8ih8ze84554Jc1uFr72lmcWXcjW/fZhjEJFyUN9ZKvWdIlIq9Z2lUt8plUprkIqIiOwG51yuc+4VvAXiDdi7oHwV8CfQ2Mz6V/D0Z5csMLNY4IyChz8UO9Sw4OfflGBmTYCBFYyhPAo3ETgt3A0551bgjQaIDdWemTWk9NdcuBi/LhSLRIn6zgDqO0WkTNR3BlDfKZVKCVIREZEyMrMrzKxLiPKOQI+Ch8Wn8NxR8HOsmR0d4nkJZna8mZW2WP4VZnZIsfqGt0NpJ2AV8F6xuoULyp9nZmnFnlMHeAmov7PXVkk+BGYAh5vZqIIPiwHMrKOZXVlJ7T1V8PM+M2tXrI0EYCSQFvJZ3nsH0K2S4hCRnVDfuUsfor5TREpQ37lLH6K+UyqRMtgiIiJlNwJ4xsyWArOBdKA5cAiQAIx3zhXuOIpz7iMzuxF4GPjSzBYCC/CuJLcBugD1gMuBX0O09wLwo5lNBNYA+xY8Jws4u8TOmy8D1xXUWWpmP+ONLDisoL2XgIsq4T0olXPOb2YnAp8DlwJnmdkfeGtSNQba4u1m+g9QGVOhngSOLrjNM7PvgAzgYCAZeA04j6Ir94UmA2uBfc1sOjAHyAMmOederoS4RCSQ+s6dUN8pIqVQ37kT6julsmkEqYiISNndDjyPt5PkwXjrL3XGWxz+dEJMTXLO/Q/YDxiDNy1nIDAIaFDwvEuAt0tp7wbgarxpTCcCTfGulh/gnPuxRDubgT7AaLwP0McWPH4f78Nr0BSocHDOrQT6AlcBv+ONcDgF6AlsBx4FTq6ktnzAUOA2vF0/BwL9gYl4r71wfagNJZ6Xg7fZwWd4u76eA1wMHF4ZcYlIEPWdu6C+U0RCUN+5C+o7pTKZczvbhEtEREQizcwcgHMunDt/1mhmFoc32qIL0Mc5NyPKIYlImKnv3H3qO0VqH/Wdu099Z82gEaQiIiJSbZnZPmYWX6IsBW8tqC7AbH1IFREJpL5TRKT81HfWbBpBKiIiUsXoSn7ZFax51QP4A2+9rCZAL7y1p7YAR+mDqkjtoL6z7NR3ikgh9Z1lp76zZlOCVEREpIrRB9WyM7PzgLOAvfDWzAJv3auvgUecc39FKTQRiTD1nWWnvlNECqnvLDv1nTWbEqQiIiIiIiIiIiJSa2kNUhEREREREREREam1lCAVERERERERERGRWksJUhEREREREREREam1lCAVERERERERERGRWksJUhEREREREREREam1lCAVERERERERERGRWksJUhEREREREREREam1lCAVERERERERERGRWksJUhGRasjMXjKzdWY2u5TjZmYjzWyxmc0ys30jHaOIiIiIiIhIdaAEqYhI9fQKMHgnx4cAnQtuI4DnIhCTiIiIiIiISLWjBKmISDXknJsIbNpJlROA15xnMlDfzFpEJjoRERERERGR6kMJUhGRmqkV8HexxysLykRERERERESkmLhoBxAujRs3du3bt492GCJSw8yYMWODc65JtOMoAwtR5kJWNBuBNw2f1NTU/bp27RrOuESkFqpGfWeF6HOniISD+k4RkfKraN9ZYxOk7du3Z/r06dEOQ0RqGDNbHu0Yymgl0KbY49bA6lAVnXOjgdEAffr0ceo7RaSyVaO+s0L0uVNEwkF9p4hI+VW079QUexGRmulj4LyC3ewPBLY659ZEOygRERERERGRqqbGjiAVEanJzGwc0B9obGYrgTuBeADn3Cjgc+AYYDGQCVwYnUhFREREREREqjYlSEVEqiHn3LBdHHfAlREKR0RERERERKTa0hR7ERERERERERERqbWUIBUREREREREREZFaSwlSERERERERERERqbW0BqnUWs45pqyawqQVk2ie1pyTup1ESnxKtMMSERERqdV8fh+fL/qchRsXslfTvRi4x0BiTOM6RERqm/UZ63l/3vtk+7I5ds9j6dSwU7RDkhos6glSM+sCvFWsqCPwX+fcE8XqGPAk3o7MmcAFzrnfIhmn1Cw+v48Tx5/ID3/9QJ4/j4TYBK6ecDU/XPADezfbO9rhiYiIiNRKa7avod9L/diQuYFsXzZJcUm0r9+eiRdOpH5S/WiHJyIiEfL+vPc55/1zMDP8zs+/v/03Nx18E/cccU+0Q5MaKuqXYp1zC5xz+zjn9gH2w0uAflCi2hCgc8FtBPBcRIOUGuf5Gc/z/V/fk5GXQW5+Lum56WzO3swpb5+Ct/m3iIiIiETapZ9eyt9b/2Z77nby/Hlsz93Ogo0LuOXrW6IdmoiIRMiW7C2c8/45ZPmyyMzLJNuXTZYvi8d+fYypq6ZGOzypoaKeIC3hSGCJc255ifITgNecZzJQ38xaRD48qSle/O1FMvMyg8pXb1/Nok2LohCRiIiISO2W789nwuIJ+JwvoDw3P5fxc8ZHKSoREYm0CYsmEBcTPOE525fN2FljoxCR1AZVLUF6JjAuRHkr4O9ij1cWlAUwsxFmNt3Mpq9fvz5MIUpN4Pf7Q5YbRr4/P8LRiIiIiIjDlTqTx+9Cf3YTEZGaJ9/l4wj+e+Cc0/d1CZsqkyA1swTgeOCdUIdDlAX93+KcG+2c6+Oc69OkSZPKDlFqkPN6nUdyXHJQecPkhnRt3DUKEYmIiIjUbnExcQzoMCBoQ6a4mDhO6npSlKISEZFIG9xpMD6/L6g8JT6FM3ueGYWIpDaoMglSvHVGf3PO/RPi2EqgTbHHrYHVEYlKaqQr+17Jvi32JS0hDYDkuGTqJNTh7dPextsTTEREREQibfTQ0TRJaUJavPcZLS0hjTZ12/Do0Y9GLIZtOdsYO2ssz057lsWbFkesXRER8TROacxzxzxHclwy8THxxBBDSnwK5/c6n0PaHhLt8KSGivou9sUMI/T0eoCPgavMbDxwALDVObcmYpFJjZMUl8TECyfy5eIvmbh8Iq3qtmJYz2E0SmkU7dBEREQkyszsL2A7kA/4nHN9ohtR7dG+fnuWXruUt+e8zYINC+jVvBcndT2JxLjEiLT/w18/MHTcUMBbE9XhuKrvVTwy8JGItC8iIp4Lel/A4e0PZ/zs8WTmZXJC1xPo01J/jiV8qkSC1MxSgIHApcXKLgNwzo0CPgeOARbj7XJ/YRTClBomxmIY0nkIQzoPiXYoIiIiUvUc4ZzbEO0gaqOU+BQu2OeCiLeb48vhxPEnkp6bHlD+3LTnGNJpCAM6DIh4TCKRYmYvAccB65xzPUMc/xdwdsHDOKAb0MQ5t0kXlSRcOjTowL8P/Xe0w5BaokokSJ1zmUCjEmWjit13wJWRjkuqp3x/Pj+v+JnMvEwOaXsIdRLrRDskEREREanivv/r+5CbgmTkZfDS7y8pQSo13SvA08BroQ465x4BHgEws6HA9c65TcWq6KKSiFRrVSJBKlJZfl/zO0PeGEJmXiZmRl5+Hk8f8zQX9b4o2qGJiIhI9eGAr8zMAc8750aXrGBmI4ARAG3bto1weBIOefl5pR7L8eVEMBKRyHPOTTSz9mWsvrPl8UREqqWqtEmTyG7Jy8/j6LFH80/GP2zP3c62nG1k+bK4+vOrmfXPrGiHJyIiItVHP+fcvnibiF5pZoeVrOCcG+2c6+Oc69OkSZPIRyiV7ogOR4TcNTk1PpWz9z47xDNEap+C5fEGA+8VKy68qDSj4OLRzp4/wsymm9n09evXhzNUEZFyUYJUaoxvln5Dbn5uUHlOfg4v/vZiFCISERGR6sg5t7rg5zrgA6BvdCOqunx+H58t/IwXZrzAn//8Ge1wdktaQhovHf8SyXHJJMQmAF5ydFCnQRzf5fiQz5m/YT4vzHiBj+Z/FPJzqEgNNBSYVGJ6/S4vKhXSxSURqao0xV5qjK05W/GWqw2U7/LZmLkxChGJiIhIdWNmqUCMc257wf2jgXuiHFaVtGTTEg575TDSc9Px5ftwOI7d81jGnzKe2JjYaIdXIWf0PIO+rfoydtZYtmRv4bg9j6N/+/6YWUA9v/Mz/OPhjJ89HjMj1mJJikvi+/O/p0fTHlGKXiQizqTE9PriF5XMrPCi0sQoxCYiUmFKkEqN0b99/5BrR6XGp3Ji1xMjH5CIiIhUR82ADwoSYnHAm865L6IbUtV0ytunsDZ9LX7n31H2+aLPGT1jNJfvf3kUI9s9HRp04I7D79hpnXF/juPtOW+T5cvaUZaem84J409g0dWLghKqIjWBmdUDDgfOKVami0oiUiNoir3UGM3TmnPbobeREp+yoyw1PpV9W+zLSd1OimJkIiIiUl0455Y653oV3Ho45+6LdkxV0YqtK1iwcUFAchQgMy+TUTNGRSmqyBk1YxQZeRkBZQ7H2vS1zF0/N0pRiVScmY0DfgW6mNlKM7vYzC4zs8uKVTsJ+Mo5V/yXvxnws5n9AUwFPtNFpdpnY+ZGRnwygoYPNaTxw425dsK1bM/ZHu2wRMpFI0ilRrnj8Ds4tN2hPD/9ebblbuOMHmcwrOcw4mL0qy4iIiJSWXJ8OcRY6LEW2b7sCEcTedl5oV9jjMXUitcvNY9zblgZ6rwCvFKibCnQKzxRSXWQm5/LgS8eyPKty8nzezM6n5/xPD///TPTLplW6t8KkapGWSOpcfq370//9v2jHYaIiIhIjdWpYScaJTciMy8zoDwpLomzep4Vpagi5+y9z2bO+jkBU+wBEmIT2Kf5PtEJSkQkCj6Y9wFrM9buSI6Ct1Hywo0L+W7ZdxzV8agoRidSdkrlS1hsz9nOwo0LycrL2nVlEREREalWzIw3Tn6D1PhUEmMTAUiLT6NTw07ccNANEYtjW8423p7zNuNnj2dz1uaItXtZn8vYq9lepMWnAV5iNCU+hTdOfiPkBlX5/ny+WvIVr/3xGos2LopYnCIi4fb72t9Jz00PKs/x5TDrn1lRiEikYjSCVCpVXn4e10y4hlf+eIW4mDicc9x26G38+5B/a7F6ERERkRrk0HaHsujqRbw882WWb11O/3b9OaX7KSTEJkSk/Q/nf8jZ759NrHkJSZ/fx+ihozln73N28czdlxSXxKSLJvHh/A/5eunXtExryYW9L6RtvbZBdZdsWkL/V/uzNXsrDofP72NYz2G8ePyLmnoqItVe54adSY1PDVqXOSkuiY4NOkYpKpHyU4JUKtWt397Ka3+8FrD20v0/3U/ztOZc1PuiKEYmIiIiIpWtRZ0W3HbobRFvd33Ges5676ygKe4jPhnBYe0OC5morGxxMXGc2v1UTu1+6k7rnfTWSazevjpgQ6u357zN4e0O5/x9zg93mCIiYXVGzzO49dtbyfJl7ejnYi2WBskNOLbzsVGOTqTsdMlSKo3P72PU9FFk+gLXosrIy+CBnx6IUlQiIiIiUtO8N++9kLOT8l0+b81+KwoRhbZ081IWb1ockBwF7/Pxs9OejVJUIiKVJy0hjV8v/pV+bfoRFxNHXEwcR3Y4kl8u+oX42PhohydSZhpBKpUmMy+TvPy8kMf+yfgnwtGIiIiISE2VmZdJvj8/qNyX7wu5Fl60ZOZlhlyTFAiajioiUl11atiJiRdOJCsvCzMjKS4p2iGJlJtGkEqlqZNQh2ZpzUIe69OyT4SjEREREZGa6tjOx4ZcvzMpPonjuxwfhYhC69a4GynxKUHlSXFJnNnzzChEJCISPsnxyUqOSrWlBKlUGjNj5OCRAR8CDSM1PpVHBj4SxchEREREpCbp0rgL1x5wLSnxKVjBfynxKZy393ns13K/kM+Zv2E+4/4cx5SVU3DOVUocf2/9m/Gzx/Pdsu9CjmiNjYll7EljSYlPIS7Gm7yXEp/CHg324LoDrwt5zo2ZG3lnzjt8vuhzcvNzKyVOERER2TlNsZdKdVK3k/g8+XPunXgvizctZt8W+3J3/7vZq9le0Q5NRERERGqQQ9sdyhOTn9iReMzPz+eI9kcE1cvLz+PMd89kwuIJxMXE4Xd+OjfszNfnfU3jlMYVats5xw1f3sCoGaOIj/HW2GuQ1IBvz/+WTg07BdTt3KgzDZMa8k/GP8TFxJHny6Nfm36kxqcGnXfklJHc8s0txMfEYxixMbF8fvbnHNj6wArFKSIiImWjBKlUusPbH87h7Q+PdhgiIiIiUkNtyNzAae+cRnZ+dkD5BR9dwMFtD6Z13dY7yh755REmLJ4QsOP9nPVzuPCjC/lk2CcVav+9ee/xwm8vkO3LJhsvhvTcdIaOG8rcK+YGbCB10lsnsTo9cBf7N/58g0PbHco5e5+zo2zG6hn8+5t/B5wT4Jg3jmHNjWtIjEusUKwiIiKya5piL9VKWadD+f1+/H7/riuKiIiISLXz3tz3Qpb7nZ/xs8cHlI2aPiogOQqQ58/jy8VfkpFbsY2Snpn6TNAmSw7Hiq0rWLBxwY6yZZuXsWDDgpC72D899emAsjG/jwlK+ALku3y+WfpNheIUERGRslGCVKqF9+a+xx4j9yD2nlhaPNqCZ6c9GzJZunrbaro+3ZXYe2OJvTeWBg81YMKiCVGIWERERETCJSMvA5/fF1Sem5/L9pztAWWZeZmlnqeia3xuy9kWsjzWYknPTQ+Is7Rd7LfnBsa5NWdrUCIVvAECxc8pIiIilU8JUqnyPl34Ked9eB5LNy/F4VibsZZ/ff0vRk4dGVS36zNdA67ab8newrFvHsuCDQuC6oqIiIhI9TS402BiLTjxmByfzLF7HhtQdtyexxFnwSuLdW7UmQbJDSrU/uk9Tic5LjmoPDYmll7Neu143K1xt5A7OifFJnF699MDyk7pdkrIdUnz/HkM6DCgQnGKSO2Rl5/Hpws/5eXfX2bxpsXRDifi5q6fy8u/v8yXi78MuWleodz8XD5Z8AmvzHyFpZuXRjBCqeqUIJUq77Zvbwu68p+Zl8k9P94TcJV97B9jg67Egzfd6ZoJ14Q9ThERERGJjO5NunNpn0tJiU/ZUZYan8qZPc6kb6u+AXUfOPIBGqc23pGoTIhNIC0hjZdPeDnkuX1+H98v+56P5n/E5qzNIetc1fcq9mi4x46EZpzFkRKfwssnvEx8bPyOerExsbx64qukxKfs2MwpNT6VdvXbcf1B1wec84QuJ3BI20NIi08DIMZiSIlP4b4B99EktUl53h4RqWXmrp9L6/+15uz3zubqCVez13N7cfmnl5d5ibrqLN+fzxnvnkGf0X24esLVnPbOaXQc2ZG/tvwVVPfPf/6k1f9acfb7Z3PV51fR49keXDPhmlrxPsmuaZMmqfJKu6qzLWcbGbkZ1EmsA8AvK38p9RzzNswLS2wiIiIiEh1D9hjC89Of37GLvc/v47g9jwuq1yS1Cf3a9OPD+R8SFxOHc45ODTrRuWHnoLqz/pnFwNcHku3z1gLNzc/l4aMe5uoDrg6ol5qQyrRLpjHuz3F8vuhzWtdrzaX7XUrXxl2DznlM52OYeelMnp/xPMu3LGdQp0GctddZAcld8JKpn531GR8t+Ih35rxD3aS6XNz74qCEr4hIcc45jh93POsz1+MoSvS9Put1juhwBKf3OH0nz67+np/xPJ8u/DRgremMvAxOf+d0pl4ydUeZc45j3zyWDZkbAp7/0u8vMaDDAE7semKkQpYqSglSqfI6NezEH//8EVReP7E+qQlF05AObXsoz01/LuQ5ejbtGbb4RERERCSyNmVt4qS3Twr4QuzDx9nvn83iaxbTsk7LHeWP/fIYExZNIN/lU5g7mLt+Lhd/fDHvn/F+0fP9Pga+PpB1GesC2rr121vp26ovB7Q+IKA8KS6JC3tfyIW9L9xlvJ0bdebRox/dZb3YmFhO7nYyJ3c7eZd1RUQAZq+bzdr0tQHJUfCShM9Nf67GJ0ifm/5c0IxTv/Pz57o/WbVtFa3qtgLgtzW/sTk7eFZARl4Go6aPUoJUNMVeqr4HjnwgaI2nlPgU7up/FzFW9Cs8bK9h1E2sG/R8w3hy8JNhj1NEREREImNnu9iP+3NcQNmz058l0xf45TnXn8tnCz8L+FI9cflEsvICd7sHyPZlM3rG6EqIWkSk8mX7sgO+Fxe3s03qaopQ/TZ4y5QUzgaAnb9PGXkZYYlNqhclSKXKG9J5CG+d+hZdGnUh1mJpU7cNI4eM5Mq+VwbVXXTVIno06bHjceOUxnx97td0bhQ8hUpEREREqqdtOdvw5YfexX5rztaAsozc0r/4Fv/yvC1nG2YWVMfv/GzK2rQb0YqIhM8+zffZsdRIcclxyZzV86woRBRZZ/Y8k8TYxKDyximN6dig447HfVr2Cfn8lPiUWvE+ya4pQSrVwtAuQ5l/1Xx8//Wx4voVXNz74pD1mqY1ZfYVs3F3OtydjvX/Ws+RHY+McLQiIiIiEk6DOg0iNiZ4F/uU+BSGdBoSUDak05CQO953aNCBhskNdzw+tO2h5ObnBtVLjU/llO6nVELUIiKVLz42ntdPej1gM7i0hDR6NO3BiP1GRDm68Lu53810aNBhx6Z5ibGJpManMvaksQEXvRLjEnnlhFdIiSv2PsWn0atZLy7qfVFUYpeqRWuQioiIiIhUY6u2rWLm2pm0q9+u1qy73rNpTy7Y5wJenfnqjunzKXEpnNj1RA5sfWBA3QeOeoAvl3zJ9pztZOdnE2dxJMYl8tIJLwXUa5TSiAeOfID/fPcfsvKycDhS4lPYp/k+nNHjjN2K1znH72t/Z236Wvq07EPT1Ka7db5C8zfMZ8mmJfRs2pN29dtVyjlFpPo5ds9jmX35bMb8PobV21czaI9BnNztZOJj46MdWtjVTazL75f+zttz3ub7Zd/ToUEHLu598Y61R4s7qdtJzLp8FmN+H8Pa9LUc0/kYTux6YsgRuFL76LdAoiYzL5OHfn6I12a9hmGc3+t8bu53M8nxyUF1f1vzG3d8dwe/r/2dTg07cVf/uxjQYUAUohYRERGpGvzOz2WfXsbrf7xOYlwief48ejXrxWdnfUaD5AbRDi/sTut+Gq//8fqOL7YOx7Cew4KmybdIa8FRHY/inbnvEGdxYNCtSbeAZZkKdajfgey87B2bnWTmZdKmXpvdSjKs3r6aQWMHsWzzMuJi4sjJz+G6A67j/iPvDzmlvyy252znhPEnMHnlZBJiE8jJz+Hkrifz6kmv6ou+SC3VoUEH/m/A/0U7jKhIikvivF7ncV6v83ZZd4+Ge3D/kfdHICqpbjTFXqLC7/z0f6U/D//yMH9t+YtlW5bx4KQHGfDaAPzOH1B3ysopHPryoUxYPIE16Wv4acVPDB03tNTF+UVERERqg+emPccbf75Bdn42W3O2kpmXyYzVM7jgowuiHVrYbcnewtBxQ0nPS8fn9+Hz+8jyZXH6O6ezNn1tQN0npjzBRws+8uo5r+6sf2Yx/OPhAfVyfbmc/PbJ+An8LDp+9nhe++O1Csd68lsnM2/9PDLyMtias5VsXzZPTX2Kd+e+W+FzXvn5lfzy9y9k+bJ2nPPDBR/y0M8PVficIiIitZkSpBIVXy35inkb5gXtKjd73Wy+XfptQN0bv7qRzLzMHVfywbuaf/2X1+OcQ0RERKQ2enLKk0E7FOf6c/li8Rdsy9kWpagio7TkYqhd7J+e8nTw+5Sfy8cLPw4of3ra00EX6gvdN/G+CsW5YusK/vjnD/JdfkB5Rl4GT0x5okLnzMvP4+05b5OTnxNQnpmXyTPTnqnQOUVERGo7JUglKqaumhpyR9HM3EymrpoaUDZz7cyQ51iTvoaMvNJ3JRURERGpyUpLgsZYTFBCsKbZkr2FPH9eUHlOfg6bszcHlG3LLT1ZnJWXteP+P+n/lFqvognnLdlbdmwGUtLmrM0hy3clNz83KOFaaHvu9gqdU0REpLZTglSiom29tjt2mSsuJSGFtvXaBpQ1T2se8hxJcUkkxwWvVyoiIiJSG5S2O3vztOY0S20WhYgiZ2DHgSHX2kyJT2HQHoMCygbtMYgYC/7a065eu4Bd7He2i/HQLkMrFGf3Jt1DxpkYm8iJXU+s0DlTE1Lp2rhrUHmMxTCw48AKnVNERKS2U4JUouLU7qeSEJeAUbQwvWEkxiZySvdTAureduhtpMSnBJSlxKdwdd+riY0J/lIgIiIiUhvcO+BeGiY3JCkuCYA4iyM1PpUXh74YcvMf5xy/rfmN75d9T3pueqTDJduXzQ9//cCUlVNKncpeVr2a92JYz2EBF9xT41M5pvMxHNzm4IC6Dxz5AA2SGpAYmwhAXEzB+3R84PvUpXEXhnQaEtRWanwq/xv0v5BxbMnewrdLv+XPf/4MufRTXEwco4eOJiUuZUeSNjkumeZpzbnp4JvK/8ILjD5uNKnxqTtGpybGJlIvsR6PDHykwucUERGpzbTFoURFWkIaP134E8PeG8aCDQsA6Nq4K+NOGReUDL1wnwvZkLmB/5v4f/idH7/zc+l+l3LvEfdGI3SRKsPMBgNPArHAi865B0scrweMBdri9fePOudejnigIiISFq3rtmbulXN5btpz/Lj8R/ZstCfXHnAtXRp3Caq7eNNiBo8dzNr0tcTFxJHnz+PxQY8zYr8REYn1nTnvcNHHFxFjMfidn3qJ9fjsrM/o1bxXhc/5wtAXGLrnUF6e+TL5/nzO63Uep3Q/JSg53K5+O+ZdOY9npj3Dzyt+pmvjrlx7wLV0btQ56JyPD3qcKaumsDlrMw5HrMXy8FEPk5aQFlT3gZ8f4J4f7yExNhGf38ceDfdgwtkTaFmnZUC9U7ufSueGnRk5ZSQrtq5gUKdBjNhvBHUT61b4tR/U5iBmXT6LkVNGMnvdbA5qfRBX9r2y1JlXIiIisnNWUze56dOnj5s+fXq0w5AyWLN9DWa2yw90Ob4cVm9fTbO0ZkFJVJFIMbMZzrk+VSCOWGAhMBBYCUwDhjnn5harcxtQzzl3i5k1ARYAzZ1zuaWdV32niIRDVek7w6Wq953OOTqO7MjyLcsDNr1MiU/h+/O/p2+rvmFtf+HGhewzah+yfFkB5Y1TGrPqhlUkxCaEtf2y8js/7R5vx6rtq4Lep58v/JneLXrvKJuwaAKnvnNqwFqvsRZL7xa9mXbJtIjGLTVXJPtOM3sJOA5Y55zrGeJ4f+AjYFlB0fvOuXsKju30on1potl3fr3ka+7+8W6Wbl7Kvi325d4j7g34fzycfH4fT099muemPUeWL4uTup3Efw/7L41SGkWk/X/S/+GuH+7i04WfUiexDlf1vYrL+lwWcimSLxZ/wT0/3sNfW/6iT8s+/N+A/2PvZnsH1VuyaQmHvXIYq7evBqBj/Y5MunhSyO/4Y34bw83f3MyW7C3USajD3f3v5toDrw2ql5efx8gpI3l+xvNk+7I5rftp3H7Y7TRIblAJ74LUZBXtOzXFXqKuRZ0WZbranRiXSIcGHZQcFfH0BRY755YWJDzHAyeUqOOAOuYNpUkDNgG+yIYpIiLRNnnlZDZkbghI+oG3QVEkdj1/8bcXQ2+o5MvhqyVfhb39svpp+U9szdka9D5l+7IZNX1UQNnjkx8P2ggr3+UzZ90cFm9aHPZYRcLgFWDwLur85Jzbp+BWmByNBZ4BhgDdgWFm1j2ske6m8bPHc+JbJzLp70msSV/D54s+55CXD2H66sgka8967yz+8+1/WLhpIX9v+5tR00ax/wv7h9zEuLJtyd5C7+d7M+b3MazcvpJ5G+bxr6//xYhPgmcTvP7H65zy9in8uvJX1qSv4dOFn3LwmIODNlFOz02n81OddyRHAZZuWUrr/7UmPz9wQ7mnpjzF8E+GsylrE37nZ2vOVq778jru+v6uoPZPe+c0/vvDf1m0aRF/b/ubp6c9Td8X+wZsridSmapEgtTM6pvZu2Y238zmmdlBJY73N7OtZjaz4PbfaMUqu7Zw40Ku+OwKDn/lcP79zb9Zs31NtEMSqYlaAX8Xe7yyoKy4p4FuwGrgT+Ba54IXfTOzEWY23cymr1+/PlzxiohIlGzK2hRyZJDDsS59XdjbX5exDp8/+Pqcc46NmRvD3n5ZbcraFHLtVr/z809G4A736zND/72Mj41nU9amsMQnEk7OuYl4F9PLqywX7asM5xw3fHlDwAUOhyMzL5Nbvr4l7O3PWz+PTxd+SqavqP1cfy7rMtYxdtbYsLf/wowX2JK9JeCiVWZeJm/8+QYrtq7YUeZ3fm74KvT7dNu3twWc8/wPzg+6sATeRaMbv74xoOzWb24NGdf9P98f8HjWP7P4eunXAe3n5ueydvta3przVhleqUj5VYkEKd5w/C+cc12BXsC8EHWCrlZJ1fPzip/Z9/l9eWHGC0xcPpHHJz9O92e760q6SOUL/gZH0CeTQcBMoCWwD/C0mQUteOacG+2c6+Oc69OkSZPKjlNERKLsoDYHkZsfvLpKSnwKx3c5PuztH7fncaTFB6/h6XM++rfvH/b2y6pf237k+HKCylPjU4Pep+P3PH7Hpk/FOedCTj8VqSEOMrM/zGyCmfUoKCvLRfsqY3P2ZjZmhb4wM31N+EeQTl89nVgL3mg4Iy+Dicsnhr397//6Pmi5E4CE2AR+X/P7jsfrM9aH3MzP4Zi6ampA2a8rfy21vW+WfhPwuHhiuLg8fx6ZuUXHpq2aFrChc6H0vPSIvE9SO0U9QVrwZf0wYAyAcy7XObclqkFJhV3yySVk5GXgc94ogZz8HLblbOPmr2+OcmQiNc5KoE2xx63xRooWdyHe+lDOObcYb82orhGKT0REqoiGyQ25u//dAcsUpcSl0LFBRy7Y54Kwt39i1xPp1bxXQPup8alcuf+VtKvfLuztF1qwYQFPT32aX/8O/WW+aWpTbj/sdlLjU3eUpcSlsGejPTlrr7MC6l534HU0T2tOclwyAIaREp/Ck4OfJCkuKXwvQiR6fgPaOed6AU8BHxaUl+Wi/Q7RnrlUJ6EOcTGh96pukdYi7O23qdcm5Ej1xNhE9mi4R9jb79ywc8jXn+/Pp029oq8W9ZPqh0xQAkEb0bWqW3o+vH399gGPQyWHCxXvO9vUaxNy5kNSXBKdGnYq9RwiuyPqCVKgI7AeeNnMfjezF80sNUS9UFerpArZnrM95EhRv/Pz7bJvoxCRSI02DehsZh3MLAE4E/i4RJ0VwJEAZtYM6AIsjWiUIiJSJdzc72Y+GfYJJ3U9icPaHcaDRz3IlOFTSI5PDnvbcTFxfHf+dzwx6AmOaH8Ex+15HONPHc8jAx8Je9sAfr+f3qN60/WZrlw94WoOfulgmj7SNOTyArcfdjsfnPEBJ3Y5kcPbHc7DAx9m0kWTgpKeDZIb8Mdlf/Dfw//LIW0P4fQep/Pted9yYe8LI/KaRCLNObfNOZdecP9zIN7MGlO2i/bFzxPVmUvxsfFcsf8VQftapMSncMdhd4S9/cPaHUbztOZBicK4mDhG7Be8Dmhlu7LvlUEb48XFxNG5UWd6Ny/apCoxLpHh+w4v0/s05vgxpbb38vEvBzw+rftpIesNaD+AmJii9NSRHY6kUUqjkO/Thfuon5XwiPou9mbWB5gM9HPOTTGzJ4Ftzrk7itWpC/idc+lmdgzwpHOuc4hzjQBGALRt23a/5cuXR+ZFCOCtCVLngTohp3C1qtOKlTesjEJUIpWrKu3EXNAfPoG3Y+hLzrn7zOwyAOfcKDNribfgfgu8q/sPOud2urhRVd+JWUSqp6rUd4aD+s6q7cx3zwy5Zl2H+h1Yeq2uG0rVFem+08zaA5+Wsot9c+Af55wzs77Au0A7vM+hC/Euyq/Cu4h/lnNuzq7ai1bf6fP7uOHLG3jxtxcxM2Itlrv638UNB90QkfZXb1/NsPeGMXnlZGIshpZ1WvLaia/Rr22/iLT/7dJvufCjC9mQuYF8l0//dv0Ze/JYmqQGJqzz8vO49otreXnmy8RYDPEx8dxzxD1cc8A1Qed87JfH+NfX/9qxFmmMxfDy8S9z3j7nBdTz+/0cPfbogAFUfVv1ZdJFk4JGtv699W+GvTeM6aunY2a0rtua1096nQNbH1hZb4XUUBXtO6tCgrQ5MNk5177g8aHArc65Y3fynL+APs65DaXV0QfV6Djvg/N4e87b5OQXrd+UEpfCnf3v5OZ+mmYv1Z++5IuIlJ/6Tomm+HvjQ24SBbDxXxtpmNIwwhGJlE0k+04zGwf0BxoD/wB3AvGw48L7VcDlgA/IAm5wzv1S8Nygi/ZlaTPafWdmXibrM9bTok6LoFGVkbAhcwPZvmxa1WkVctp9ODnnWLltJakJqTRM3nkfmJGbwYbMDbSs05L42Pid1v16ydckxiZyWPvDdlpvW/Y2Zq2bRffG3XfZB2/I3ECOL4eWdVpG/H2S6qmifWfoxTciyDm31sz+NrMuzrkFeFee5havE+JqVQxQdba8lB2ePfZZ1mxfw6S/J5EQm0C2L5vTe5zOjQfduOsni4iIiIhUsnx/fqnHNmYpQSoC4JwbtovjTwNPl3Lsc+DzcMQVTinxKRFdB7mkximNo9a2mQWsObozqQmppCaEWgUx2MA9BpapXt2kuhzS9pAy1Y3m+yS1S9QTpAWuBt4oWEdvKXBh8WmiwKnA5WZWeLXqTBftoa8SUlpCGl+f9zWLNi5i6eal9Gjag9Z1W0c7LBERERGppVqktWB1evCSiLEWyx4Nwr8pioiIiFR9VSJB6pybCZQc/jqq2PFSr1ZJ1dS5UWc6NwpaJlZEREREZJfWZaxj2eZldG7UeZfTP3fllRNf4eixRweV33vEvQGbghT35eIvWZ+xnhO7nUhaQtputS8iIiJVX5VIkErtNXPtTN6e8zYAZ/Q4g17Ne0WsbZ/fx2cLP+PH5T/Sqk4rzu11Lk1Tm0asfREREREJlJufy0UfXcS7c98lKS6JnPwchvcezpNDniTGQiczd+Wojkdx7t7n8vqs13eU9W3Zl+sOvC6o7s8rfuao147asZ6+fWhcf+D1PDbosQq1LSIiItVDxT5liFSCO7+/k4PHHMxDkx7i4UkPc9CYg7j7x7sj0nZWXhb9XurHOR+cw+OTH+f2729njyf34OcVP0ekfREREREJdss3t/D+vPfJyc9ha85Wsn3ZvDTzJR77peIJytG/jea9ee8FlM1aN4srP78yoMzn93HEq0cEbDbqcPxv8v/4aP5HFW5fREREqj4lSCUq5q2fxyO/PEKWLwu/85Pv8snyZfHQzw+xcOPCsLf/9NSn+fOfP0nPTQcg25dNel46Z757JlreVkRERCTy/M7P6BmjyfJlBZRn5mXy+OTHK3zeR395lMy8zICybF82b/75Jtm+7B1lL8x4odTd7v/z3X8q3L6IiIhUfUqQSlR8tOCjkB9AfX5fRK7Qvz7r9aAP3wBbsrcwb8O8sLcvIiIiIoF8fl9AwrK4zdmbK3zejZkbQ5Y73I6L5QDLtiwr9RwbMjdUuH0RkUhwzvHL37/w0fyP+Cf9n53W3Zy1mU8WfMKPf/1Ivj9/p3Vnrp3Jh/M/5K8tf+203obMDdzz4z088NMDbMvettO6Czcu5MP5HzJ3/dyd1svLz+Pbpd/y2cLP2J6zfad1RXaX1iCVqIiPicfMgspjLIb42Piwtx8XE/pX3+GIjwl/+yIiIiISKCE2gS6NuoS8WH1g6wMrfN5D2x3KJws+wRE4S6h5anMaJTfa8fisnmfxyC+PhDzHgA4DKty+iEi4Ldu8jKNeP4p1GeuIsRhy83O56aCbuHfAvUF1n5zyJLd+cysJsQk450hLSOOrc7+iZ9OeAfU2ZW1i8NjBzF0/l9iYWHLzczm126m8cuIrxMbEBtS95etbePiXh3c8vu2723h0oG94uQABAABJREFU4KPcePCNAfVyfDmc/s7pfL30a+Jj48nLz+Og1gfx8bCPSU1IDag7eeVkjnvzOPLy88C8ZOmoY0dx3j7n7e7bJRKSRpBKVJza/dSQC+3HWAyndDsl7O1fut+lpMSnBJQZRuu6renUsFPY2xcRERGRYM8e+ywp8Sk7PifGWixpCWk8Pij0FHvnHAs3LmTe+nmlLpP08FEPUyexzo4L5IaREp/Cs8c+G3DBfp8W+9C3Zd+g58fHxDNyyMjdfWkiImHhnOO4ccfx15a/SM9NZ1vONrJ92Tw++XE+WfBJQN3JKydz27e3ke3LZlvONrbnbmdN+hqOfv3ooJGkF350ITPXziQjL2PHOd+f/z5PTHkioN60VdMCkqOFbvr6JpZvWR5Q9t/v/8vXS78my5fFtpxtZPmymPT3JK774rqAell5WQweO5iNWRvZlrttR93LPruM+RvmV/zNEtkJJUglKtrVb8fIISNJiksiJT6FlPgUkuKSeOaYZ2hTr03Y2x++73AG7TGIlPgUEmMTqZNQh0YpjfjgjA9CjmwVERERkfDr16Yfg/YYBHgXzh2O07qfxt7N9g6qO+ufWXR+qjO9n+/N/i/sT/sn2zNl5ZSgel0ad2HmpTO5uPfF7NV0L07pdgo/XvAjx+55bFDdt099m2apzXY8jouJ4/njnqdxSuNKfJUiIpVn/ob5/LXlL/zOH1CekZfBU1OfCih7bvpzZOUFLzWXnpsesGFxem46Xyz+gjx/XkC9zLxMnpn6TEDZnT/cWWpsd/14V8DjF39/MWipu5z8HF6f9XrARa4JiycEvR6APH8eL//+cqntieyOCk2xN7PWQEsgqbQ6zrmJFQ1KaodL9r2EoXsO5eMFH2MYQ7sMpXla84i0HRsTy/tnvM9va35j0opJNE9rztAuQ0mKK/VXWkRERETC7LbvbuOLxV8EfDF+a85b9GzakxsOumFHWUZuBv1f6R+wNmnG1gwGvj6Q5dctp0Fyg4DzdmjQgVHHjdpp2845Bo4dGLDeqM/v46oJV3Fg6wPp1qTb7r48EZFKty1nW6lLyG3O2hz0uORyIwBmxraconVDS1sPGmB7buBaoFuzt5Zad0vWloDHGbkZIevl5ueS7/KJM+91bMvZFjJB6vP7dmtNapGdKdcIUjM72cwWAMuBX4HvS7l9V8lxSg3VPK05I/YbwSX7XRKx5Ghx+7bYl6sPuJrTepym5KiIiIhIFDnneG7acyF3sX/s18cCyt6f937QyCaAfH8+42aPq1D7v678lTXpa8h3gdNMc3w5PDvt2QqdU0Qk3PZpvk/IJUaS4pI4tfupAWUndzuZ1PjUoLq5+bkc0vaQHY8bJTeibb22QfXiLI5jOweOvh+217BSY7uw94UBjwd0GIARPGOzb6u+AUneAR0GhNzUOS0+jeO7HF9qeyK7o8wJUjMbCrwNdAa2ATOBiaXcfqrsQGuy6aun8+JvL/Lt0m9DXiWpiAUbFjDmtzF8suATb1Fj2S25+bl8suATxvw2hoUbF0Y7HBERESlgZklmdoiZnW5m55V2i3acsmt5/ryg5GihTVmbAh6vTV9Lji8nqF6mL5PV21dXqP216WtDrpGf7/L5a+tfFTqniEi4JcYlMuq4USTHJRNr3uZJKfEptK/fniv2vyKg7ll7nUXPpj13JEkL12R+8KgHA0bemxkvHf8SqfGpOzYxTo5LpmFyQ/5vwP8FnPOKPlfQum7roLh6NukZlMx8cvCT1Euqt2NwUkJsAnUS6gSN8G9bry3/OvhfAfuGpMan0q9tP47pfEy53h+RsirPFPvbAANuBx5xzinrtpuyfdkc9+ZxTF45GeccMTExtKzTkokXTKRZWrNdnyAEv/Mz/OPhjJ89HjMj1mJJjk/mxwt+pGvjrpX8CmqHuevncsQrR5DlyyLf5eOc4+y9zmb00NFar1RERCSKzOx64L9A3TJUfy3M4chuSohNoHPDzizYuCDo2P4t9w94fEjbQ0iITQgaRZqWkMahbQ+tUPsHtDqAXF9uUHlKfAqD9xhcoXOKiETCWXudRY8mPXh22rOs3L6SYzsfywX7XBC0MXFCbAITL5zIuD/H8d6892iQ3IDL+1zOga0PDDrnoe0OZdbls3h66tMs2LCAQ9sdyoj9RtAwuWFAvZiYGJZdu4z/fPsfxv45lliLZfi+w7n90NuDztm5UWfmXzmfZ6c9y7TV09i72d5c1feqkAnWewfcy4AOA3jxtxfJyMvgzJ5nlrrZs0hlsNJ2ewyqaJYBzHPO9QlvSJWjT58+bvr06dEOY6fu+O4OHv310YD1PeJi4hjYcSCfn/15hc45dtZYLvv0MjLyitb2MGxHR6SEXvk45+j8VGeWbl4asFZLanwqLwx9YafTCaRmMrMZ1aUfrIjq0HeKSPUTjr7TzC4CXix4OA+YjzfLKSTn3IWlHdtd0e47s33ZLNu8jOZpzYPW3ixpzfY1bM/dzh4N9iA2JrZS2t+YuZH1mevp2KAjCbEJpdZzzrFk8xKS4pJCfhkG+GbpN5ww7gSyfFk4HDEWQ3JcMj9c8AN9WvYJONdxbx7HD8t/IDMvE/BGN+3Xcj9+vODHCn+Bvu6L63Z8GQdIjE2kdd3W/HHZH6QmBE9LFQknfe4UESm/ivad5RlBmgcEX86VChvz+5igxY99fh/fLP2GzLzMoKs9ZfHc9OcCkqMADseqbatYsHGBRpGW09z1c1mbvjZoIeuMvAyem/6cEqQiIiLRcw3ggHOdc29W9snNLBaYDqxyzh1X2eevLI/+8ih3/3g3hpGbn8vpPU5n9NDRQWurr01fy+nvnM7UVVOJi4kjJT6Fl054ieP2rPhLS89N54IPL+DThZ8SHxtPjMXw0FEPcVmfy4Lq/vjXj5zzwTlsytqE3/np0aQH757+Lu3rtw+od1THo5h44UT+76f/Y+76ufRp0Yf/HPYfujfpHlDPzLhvwH0MfmPwjh2ZU+JSeGTgI7s1uujxQY9zQKsDGDllJFtztnJq91O54aAblBwVERGp4cqTIJ0BdAxXILVRTn7wukmFQi1IXBaFHxBLirGYUo9J6bJ92aV+yNb7KSIiElV7Ar+EIzla4Fq8kallmb4fFW/Nfos7f7hzxwhKgHfnvktCbAIvHv/ijjLnHIPGDmLu+rn4/D5y8nPIyMvgjHfPYOrwqfRo2qNC7Z//wfl8tugzcvJzdnyuvfGrG2lfvz2DOxVNSf97698c++axARfxf1/7O4e/cjhLr1kaNJJ1v5b78cEZH+y07cy8TI56/Sg2ZW3acSF7Y/ZGBo0dxPLrllM/qX6FXpOZMWyvYboILiIiUsuU5/Lqg0BfMxsYrmBqmxO7nhiwU1uhvZruRd3Ein0WP2uvs0iOSw4qT4xLZO9me1fonLVZr+a9iI+NDypPjkvm7L3PjkJEIiIiUiATWBGOE5tZa+BYiqbwV0n3/3x/QHIUIMuXxRt/vhFQ/vva31myaUnQBfgcXw4jp46sUNsbMjfsSI4Wl5mXyQM/PxBQ9uJvLwatFep3fjZnbea7Zd9VqP0P5n1ATn5O0Cwfn9/H+NnjK3ROkerCzOqa2b/N7Bszm2tmS0u5/T979x3mVPE1cPw72yu9N+mIgKgsSJMmIFgRFeSnoiii2Ptr7yiIDQsiKoqKCopiR2yISBFQBJEuvRcp20vO+8dk2WSTLLvZTbLlfJ4nD5u5s3NPgl6Sc2fmbAx1rEopVVb4nEFqjGmUr2ktMAb4whjzEvA19kOp17LrIhKQD6zlydNnPs33G7/nYNpBUrJSiI2IJTI8krcHve33mDd2vJGP/v6INfvXkJKVQlR4FBFhEUwbPK3E9pmqSHLfu4tmXES2I5vMnEziI+NpXbM113W4LtThKaWUUhXZAqBtgMZ+EbgHSPTVwRgzChgF0KhR/o/NwbH76G6v7QbDofRDx7Zr2nl0p9fPgTmSw+ZDm/06976UfUSGR3pdEbX9yHa355sPbSYzx7P4kUMc7Di6w6/z7zy602OrKrAJ2vznV6o8McY0BH4FGmKLKBekcAVHlFJKFbjEfjPeL6gGuMv58EWOM7YCasXXYvWNq/lg5Qcs3rGYVtVbMeLUEdSIq+H3mLGRsSy8ZiGfrfmMORvn0KBSA64+9WoaVQ7NB/fyYEDzAfxzwz+8vfxtdhzZQf9m/Rl04iCvM0uVUkopFTSPAQuMMVeKyNSSGtQYcy6wV0SWGWN6+eonIpOByWALjZTU+Yuia8OufL72c49ZlAlRCdRJqHPseYe6HcjI9kxkxkbE0rdJX7/O3bRqU4yX3Ey4CafnCT3d2vo06cPM1TM99sl3iMNr5eTC6NKwC9Hh0R6zYhOiEujasKtfYypVRjwFNAL+AMZxnAJ1SimlCqegJOZW9I5TwMVHxXNth2u5tsO1JTZmZHgkQ9oMYUibISU2ZkV3QpUTeLTXo6EOQymllKqwjDE9vDQ/D0wxxpzN8Vc3zSvkqboB5zvHjAEqGWPeF5HL/Qg7oMacOYYfNtning6xLzsuMo4XBrzgtod63cS6XJ90vVt19qiwKKrHVWdUh1F+nTs6Ippxfcdx1/d3HVvOH27CSYxK5KEeD7n1Hdp2KGN/G8um/zYdm3EaFxnHoFaDfBYQ3Z+6n7/3/s0pdU7xup9ot4bd6NKgC79t+420bLsvfGxELG1qtnHb/1Spcqg/sBvoLSJHQx2MUkqVFz4TpCLSOIhxlDs5jhz+3P0nDnHQoW4HXd7uQ44jhz92/QHAaXVP0/dJKaWUUr7MxffqpoudD18KvbpJRO4D7gNwziC9qzQmRwFOqnkSS69dyuO/PM6C7QtoUqUJD/Z4kD5N+nj0feGsF+hQtwMTFk/gUPohBp04iHu730vlmMp+n//KU65k+qrpzNtic8/hJpz7u99Pk6pN3PrFRMSw6JpFjF8wnhmrZhAbGcvopNFce5rnBIHM7Ey6vNWFP3b/caytT5M+fH/594SF5SV9jTF8fdnXvLz4ZaYsn0KOI4fh7Ydze+fbi1XFXqkyoBLwjSZHgyM5M5nhnw1n9obZAJzf8nymXDCFuKg4v8cUEaatnMarv79KSlYKQ9oM4bbOt5EQlVCsWL9Z/w3PLniWPSl7GNh8IPd0u4da8bU8+q3Ys4JzPziX7Ue2Y4xhQLMBfHHpF4SHe34XP+u9s5jz7xzAbt9y/xn382SfJz36ZWRnMHHJRN79613Cw8IZedpIRp420mvNFaVKKyNSPieJJiUlydKlS0Ny7oXbFjJ4+mCSs5IxGGIiYvhkyCf0OMHbxIeKa/7W+Vw046Jj1eDjIuOYOWQm3Rp1C3FkSvlmjFkmIkmhjiNQQnntVEqVXyVx7TTGzKUYq5tEpLcf5+yFTZCeW1C/inrtHPrJUL5Y+4XbXqBxkXHMGjqLfs38q+va8+2ezNvqOdl3WNthfHDRB37HqlRZ5O3aaYxZDawXkfNDFFaJKe3XTofDQfVnqnMo45Bbe43YGuy5a4/bTZuiuO7L65i2ctqxGf0xETE0q9qMpaOWEhMR49eYzy14jkfmPuK2SqBqbFVWjl5Jzfiax/ptPLiR5i839/j92vG12X2X+77WLV9qyfr/1nv0vbHjjbxy9ivHnjvEQc+3e7Js17JjM/rjIuPo26Qvnw/73K/Xo1Rx+Pu5s9D/Rxtjphhjri5Ev6uMMVOKGkh5cTj9MGe9fxa7U3aTnJnM0cyj7EvdxznTzuFA6oFQh1dqHEw7yMBpA9mbspejmUc5mnnU3umaNpBD6YdCHZ5SSimlShkR6SUivf19+HnOucdLjlZUB1IP8Pmazz0KJaVmpfLU/Kf8GtPhcHhNjgLMWDXDrzGVKofeB3oaY6qHOpDy7rmFz3kkRwH2p+1n0rJJfo258eBG3l3xrtuezOnZ6Ww+tJnpf0/3a8zkzGQe+vkhtzEzHZkcSj/Ei4tedOt70fSLvI6xJ2UP32/8/tjzrKwsr8lRgFeXvOr2/LsN37F8z/JjyVGw/xb8uOlHft/xe1FfjlIhU5RbHlcB3QvRrxtwpV/RlAMzV888tgeUqxzJ4aO/PwpBRKXTjFUzfL5PH6/6OAQRKaWUUkqpwtqTsoeo8Civx7Ye3urXmMmZyT6P5UiOX2MqVQ6NA34HvjHGnBTqYMqzb9Z/4/PYF2u/8GvMhdsXel12npKVwpyNc/wac+WelV4LCGfkZBzbGiDXP/v/8TnO68teP/bzB38Xfsb+vC3zvF6/sxxZzN86v9DjKBVqgdgQIhIfm+NXBPtT9x/bfN5VWnYa+1L3hSCi0ml/6n7Ss9I92jOyM/R9UkoppdRxGWN+AmaLyDPH6XcXcLaIeG7MqfzWtGpTxMuOB+EmnDManeHXmJViKhERFuFRmR4gPjLerzGVKuuc17r8IoGOwApjzFZ8F6gTETkzkPGVZ3UT6/o81qBSA7/GrB1fG4PxaI8Mi6Rh5Yb+jZlQm6ycLK/H8seZEJnAfxn/ee3bqnqrYz93qNeh0Oevl1iP2IhYtxmkAFHhUdRJqFPocZQKtUDsYN4GOBSAccuEXo17eb2bHh8ZT6/GvYIfUCnVq3EvYiNjPdpjImL0fVJKKaVUYfQCvJdAd9cK6BnYUMofEWHX0V0+Z3XGRMTwZJ8niYvMK1QSZsKIj4r3qGJfFPd1v89r+/h+4/0eU6kyrpeXR27RhjCgMdDDR79ewQiwvBrTZ4zPY4/3etyvMfs06UOVmCoexeQiwyMZ1WGUX2M2rdqU0+qeRmSY+yzSuMg47ux6p1vbw70e9jmO62tqW7ut10QuQK0498JPw9oN81psOTIskkEnDjpe+EqVGgXOIPWyl2j3AvYXjQBaA6cBX5dAbGVSx3odGdB8AN9t+O7YHiBxkXH0bNyTnifoZ/Nc3Rp2o0+TPvy06adj71N8ZDz9mvWjS4MuIY5OKaWUUuVINKDrs4vg63VfM+qrURxMO4hDHFzQ6gLeOv8tEqMT3frdevqtnFD5BJ769Sl2Ht1JjxN68Fivx2hWrZnf53689+PUjKvJwz8/zJHMI1SPrc6z/Z9lePvhxX1ZSpVVfu2hrIqvSdUmTDl/Ctd+ee2xbT7CTThTL5xKvUr1/BozPCycn6/8mUHTB7Hx4EbCw8KJjYjl3QvfpWnVpn7HOuvSWVw842IW71h8LFH6wlkveBSKvq3zbczdNJfP1+UVTzIYZg2d5VHFfsX1K2g3qZ1bW1RYFNtv2+7WViOuBt9d/h1DPh7CofRDCEKdhDp8NvQzt5toSpV2BVaxN8a4TtMX8HELwd1u4CwRWVnM2IollBXxchw5fLDyA978801EhBGnjOCK9ld43WukIst2ZPP+iveZ8ucUjDFcfcrVXH7y5V7vPilVWmgVe6WUKrpAXDudn1PfERGfRUSNMWHASqCqiPj3bbYQytO1889df9L97e6kZqUea4sOj6Zn4558d/l3IYxMqYpHP3eWDg6Hg6/Wf0UYYZzd4my/q9fnt/HgRlKyUmhTs02JfQfedngbB9IO0LpGa6Ijon32S85MZuKSiZxQ6QSGthta4JhT/5zKtxu+5caON3JGY99bqIgI/+z7h4iwCFpWb4kxhUkfKVXy/L12Hi9BmltsyQBTgPnAWz66ZwI7gEUiklnUQEpaWbnYKqXKFv2gqpRSRVdS1858e/H1wt6YX+OjewTQHKgNzBCRYcU9vy/l6dp52czL+GjVRx7FNGMiYlh942oaV2kcmsCUqoC8XTuNMT2A3SKy7ji/2wKoKyLzCnmuKcC5wF4Raevl+GXA/zmfJgOjReQv57HNwFHsbP3swl7vy9O1UylVevj7ubPAKY0iMtXlBI9ik59Tff+GUlZyZjKb/9tMy+otiYrwXuE016H0QwBUialSYL8cRw77U/dTNbaqz6qpSimllCrXern8LEAd56Mgf5L3pV4dx7oD6zySo2BnkW49vFUTpEqF3lzgbeCa4/S7B7gaKOzUxHeAV4B3fRzfBPQUkf+MMQOBycDpLsd7i8j+Qp5LKaVKnUKv+RaRxgGMQ5UT2Y5serzdg4XbFwJ2P5OhbYby4cUfevRdf2A9wz8bzrJdywBIqpfEexe+53Xfqld/f5WHfn6ItOw0wk04t3W+jcd7P+6xubVSSimlyrXcvfgM8BMwGxjno28msENEtgYjsPKiR+MerNi7gswc9wVhGTkZtK3lMalMKRUaJb52WUTmGWMaF3B8gcvTRYB/ZdyVUqqU0k0xVYnq/U7vY8lRAEH4aNVH1IqvxYSBE461p2al0m1KN/an7kew2zws3rGYrlO6svnWzW4V7qetmMY9P9zjthfWC4teIDIskkd6PRKEV6WUUkqp0kBEfsn92RjzCzDXtU0V351d7uTtP98mKyfr2Ge0+Mh4RnccTbXYaiGOTilVBLWAtACNfQ3wrctzAeYYYwR4XUQm+/pFY8woYBRAo0aNAhSeUkoVXaETpMaYhwvZNRPYDywTkT/9ikqVSZnZmczfNt/rsUlLJ7klSGf+M5O07LRjH7wBHOIgNSuVz9Z8xv/a/e9Y+2O/POaWHAWbYH1u4XM81PMhnUWqlFJKVUAiopWdA6BeYj1eHPAiN3x9AylZKRgM7Wq345EeelNaqVBx7jvqqo6XtlwRQGugP7A6ALH0xiZIu7s0dxORncaYWsD3xpg1vvY+dSZPJ4Pdg7Sk41NKKX8VZQbpo0BhLmAmt58xZiVwlYgsL3JkqszZnbLb57FMh/syrU2HNpGSmeLRLzUzlU3/bXJr23l0p9cx07LTSM1KJSEqwY9olVJKKaVUfiv2rGD016OP3ZwWhOW7ljN05lC+/t/XIY5OqQprLu7fxc9yPgpigNdLMghjzMnAm8BAETmQ2y4iO51/7jXGfAZ0AgpVHEoppUqLoiRIHwcaAVcBKcD3wBbAATQG+gHxwFQgG3tH6WTgB2PMabr/U/nXILEBYYThwHNj/8SoRLfnHep2ID4qnuTMZLf2uKg4Tqt7mltbu9rtWLR9kceYteJqER8ZXwKRK6WUUqqscVZcLoxjq5uAb0QkI3BRlX3jfxtPena6W1t6Tjo/bfqJrYe30qiyLolVKgTmkZcg7QnsBdb46JsJ7AA+E5EvSyoAY0wj4FPgChFZ59IeD4SJyFHnz/2xuQNVQrYe3soXa7+gVfVW9GvWr8C+a/avYV/KPk6te2qBE4nSstL4Y9cfVIquRNtabTGm+NvaZjuyWbZzGeFh4ZxW9zRd6anKnKIkSN/CfrD8ELhZRA66HjTGVAVeBs4BkrAX5ZeB64G7gFtKImBVeoWFhTGqwygmLZvkcWxcX/f6CQOaD6BZ1Was2b+GjBz7PSU6PJoW1VpwVnP3m6Hj+43nrPfOIjU7b5l9XGQc4/uPL5ELuVJKKaXKpKucf+YmDfJ/KMjfLsBeY8xVIvJdgGMrs9bsX+Oziv2WQ1s0QapUCIhIr9yfjTEO4FsRubokz2GM+RDoBdQwxmwHHgEineefBDwMVAcmOr+DZYtIElAb+MzZFgF8ICKzSzK2iqzfu/34YdMPx54nRiWybNQyWlRv4dZv19FdnPvhuazZv4aIsAiycrJ46synuK3zbR5jvrfiPW74+gbCTBg5jhwaVGrA1//72mux5ML6adNPDPl4CJk5mQhCYlQisy6dRaf6nfweU6lgK0pK/0kgHbtk/mD+gyLyHzDC2edJEckB7sTese9fArGqMuC1c1/joR4PERsRi8FQOboyb5z3BqM7jnbrFx4Wzq8jfuXmTjdTJ6EOdRPqcsvptzBvxDyPO03dG3Xn++Hf0/OEnlSNqcppdU/j40s+dtunVCmllFIVzgjgVWwCdDvwInA7cCvwArDNeWwi8BDwM3lf5NuEIN4yoVujbkSGRXq0Z+Rk0Lpm6xBEpJTKpzcw7ri9ikhEholIXRGJFJEGIvKWiExyJkcRkZEiUlVETnE+kpzt/4pIe+ejjYiMKenYKqr/+/7/3JKjAEczj9LpTc+k4/kfnc+K3StIzUrlSMYR0rLTeOCnB/jhX/ff/3PXn1z35XUkZyZzJOMIKVkprD+4nr7v9fV6c6ww9iTv4fwPz+dA2gGOZh4lOTOZXcm76PdeP48Vo0qVZkWZQdofWyk0y1cHEckyxizALrdHRNKMMX8BXYsXpipLHu/9OI/3Pv6qisToRMb3H8/4/uOP27drw67MvWpuCUSnlFJKqXJiGTZBOh54QESyXQ8aY/4PGAPcBHQWkTHGmAexSz/vBEp09lV5cWeXO3ln+TsczTx67MtyXGQc15x6DTXiaoQ4uuIREY5mHiU+Mp7wsPBQh6OUX0Tkl1DHoILjtaWveW0/lH6IP3b9cWxruvUH1rNq7yqy3f8ZJDUrlRcWvkDfpn2PtU1cOvHYCs5cDnGwP3U/C7ctpFujbkWO88O/PyRHcjzaHeLg09WfMrz98CKPqVQoFGUGaRUg8XidsPuQVnF5vq8I51BKKaWUUqowHgN2iMj/5U+OAjjb7sXOLn3M2TwW2IldRqq8aFi5IUuuXcKgVoOoGlOVplWb8ky/Z3hxwIuhDq1Ypq2cRr3n61HjmRpUHVeVx+Y+5vdsKaWCyRjTqDiPUMev/Jd/P2hXWw5tOfbzgbQDRIZ7zvwH2JOyx+35rqO7vF77wkwYB9IOeLQXxt6UvV5jzczO5ECqf2MqFQpFSZBuAnoVdJF1Huvj7JurLuCxJD/f71UxxnxijFljjFltjOmS77gxxrxkjNlgjFlhjDnN11ilwaH0Q1z31XVUGVuFymMrc/XnV5f5C8PmQ5u5aPpFJDyVQI1nanDvD/cWeMEujINpBxn5xUgqj61MlbFVGPXlKP5L+6+EIg6Nf//7lws/upCEpxKoNb4WD/z4AJk5maEOSymllCqPzgCWFtRBRMTZ5wzn82xgJfbzqfKhRfUWzBw6k4P/d5CNt2zkxo43luliG1+u/ZJRX45id/JushxZHM08yjMLnuGRnx8JdWhKFcZm7Pdrfx7/Bj9cVVLa1PS+G4zBcFazvLodJ9c+mRyH5wzO6PBozmt5nlvbuS3PJS4yzqNvZk4mXRp08WgvjD5N+pAQ6VkQKiI8gl6Ne/k1plKhUJRPOlOBOOBnY8wwY8yxdSnGmHBjzKXYvZ1inH0xxkQA7bEfRAsyAZgtIic6+6/Od3wg0ML5GAV4n2teCuQ4cug+pTvvLH+HwxmHOZJxhPdXvE/ntzqTleNzd4JS7WDaQTq+0ZFZa2eRkpXCgbQDTFg8gQunX+j3mNmObLq81YX3/nqPIxlHOJxxmKnLp9J1SleyHR6TQMqEfSn76PhGR75Y9wUpWSnsS93HC4teYMjHQ0IdmlJKKVUeJQA1C9GvJnaFU65DQNn8sKH88vDch0nNSnVrS81K5cXFL5bZz+eqQtnq42FcHkecD9e2rdi9mFUZ9d6F7xHmJWVzXYfriIvKS3LGRcbxbP9n3RKfMREx1Iqvxc2n3+z2u8PbD6dxlcbERsQea4uPjOfebvdSM74w/6R66tOkD10bdSU+Mu+f2vjIeC5odQGn1j3VrzGVCoWiJEifA74DmgDvA2nGmC3GmM1AGjDNeWyOsy9AG2AV8IGvQY0xlYAewFsAIpIpIofydbsAeFesRUAVY0ypvPM/e8Nsthze4jZrMMuRxe7k3Xy+9vMQRua/N/94k5TMFLep+OnZ6fyy+RdW7V3l15hfrv2SXUd3kenIe58yHZlsP7Kdb9Z/U+yYQ+H1Za+TmpXq9j6lZacxZ+Mc1h1YF8LIlFJKqXJpLdDTGNPeVwfnsV7AGpfm+kDZXtqjisR1KaqrrJwsDmccDnI0ShWNiDQWkSa5D6A5dg/mfcAtQFVn8aSq2K3ubgb2OPv4X5ZchVzb2m35+4a/6d6wO/GR8dRPrM8b573Ba+d6zhe7Pul6vr3sWwadOIjT65/O/d3v56/r/6JabDW3fnGRcSweuZgnej9B5wadObv52Xx8ycc80sv/GfVhJoyvhn3FhAETOKPRGfRp3IfJ503m/cHv+z2mUqFQ6CJNIpJtjDkHexG+BWgMNHTpsgV4GZjgrGCPiPyFc0lTAZpiL+5vOz/ELgNuFZEUlz71cb/7td3Ztquw8QfLyr0rSc/yXHqenJnMij0ruPiki0MQVfEs3rGYtOw0j/aIsAhW7FlBm1pFLwS7cu9KrxXtUjNTWblnJee3Ot+vWENp0fZFXrcdiAyPZOWelbSs3jIEUSmllFLl1mvAJOAnY8yzwIfYz4uC/Yw6DLgLCHf2wxgTC5yGvaGvKoi2tdry69ZfPdrjo+KpGlM1BBEpVSx3AOcAp4mI28pLETkCvGqM+Qn4E7ibAFS8V8HTumZrfr3a8/rlTY8TetDjhB7H7ZcQlcCdXe/kzq53Fje8YyLDI7nmtGu45rRrSmxMpYKtSJsJiYhDRF4UkaZAI6CL83GC847W87nJ0SKIwH5QfU1ETgVSsBvquzLewsnfYIwZZYxZaoxZum9faGpDtajWgtjIWI/2hKiEMpsga1erHTERMR7tDnHQonoLv8ZsUa0F8VHxHu3xUfF+jxlq7Wq1Iyo8yqM925FdZv/ulVJKqdJKRCYDbwJVgSeBjUA6kIHdd28MUA2Y4uwLdrXTZ8AbQQ/YSUTIyM7Abo9a8WTlZHndKy+Qnj7zabflpGBnUT3Z+0mtZq/KoquAufmTo66cx34GrgxWUEopVdb5vdu6iGwXkcXOR3H2NtkObBeRxc7nn2ATpvn7uM5WbYCtQJo/pskikiQiSTVr+rd/RnGd1+o8qsRUITxvi1bCTTgJUQlc1PqikMRUXNd1uM4j8RcVHkXbWm3pULeDX2Ne2PpCKkVX8nifKkVX4oJWFxQr3lC5sdONRIdHu7VFh0dzWt3TaFe7XYiiUuWZMWaAMWats4Bd/htLuX16GWOWG2NWGWN+CXaMSikVSCIyChgM/AJkYmeLhjt/ngdcLCLXuvT/R0SuEJFvQxArE5dMpPaztYl7Ko56z9fjrT/eCnYYIbN632rOmHIGMWNiiB0Ty7BPhgWtOGe3Rt2Yc8UcujToQkJUAq2qt+Kt899idMfRQTm/UiWsCVCY/3kOYVd9KqWUKoSQl6MUkd3ANmNMK2fTmcA/+bp9AQx3VrPvDBwWkVK3vB5s4nDhNQvp36w/EWERRJgI+jTpw6JrFnmdWVoW1E2sy68jfqVT/U6EmTAiwyK5uPXFfHf5dxjjbXLv8cVExLDwmoX0bdqXCBNBRFgE/Zr2Y9HIRURHRB9/gFKoQaUG/HLVLyTVTSLMhBEVHsWQNkP45n9lc09VVbo5C+W9ii1idxIwzBhzUr4+VYCJwPki0ga4JNhxKqVUoInILBHpgy3aVNf5SBSR3iLyaWijy/P6ste5+/u72Ze6D4c42J28m1tm38J7K94LdWgBdyD1AF2ndOW3bb/hEAdZjiw+Xf0pfd7tE7SZtN0bdWfBNQs4et9R1ty0hkvbXhqU8yoVAEeArs6CyF45j3Vx9lVKKVUIhd6DNJcxpgs2iVkPW7HeGxGRomw+cTMwzRgThV0SNcIYc71zoEnAN8DZwAYgFRhR1LiDqX6l+nxz2TfHqmJGhkeGOKLiO7n2ySweuZiM7AwiwiJKZDlSo8qNmH357HL1Pp1a91SWjFpSou+TUj50AjaIyL8AxpiPsAXtXG8w/Q/4VES2AojI3qBHqZRSQeLc5mlPqOPw5bG5j3mtpP7wzw9zxclXhCiq4Jjy5xS7rYDLDlmZjkw2HNzAgm0L6NaoWwijU6rMmQNcBrxhjLlFRI66HjTGJAATsCswtUqOUkoVUqETpMaYaGA6cF5uUwHdBSh0glRElgNJ+ZonuRwX4MbCjldalIeEX36BmN2p75NSfvFWvO70fH1aApHGmLlAIraI3rv5BzLGjAJGATRq1CggwSqlVEXmEAe7U3Z7Pbb9yPYgRxN8K/eu9FrwU0RYd2CdJkiVKpoHsSuIhgMXGGO+AjY5jzXGfl+vDBwEHg5FgEopVRYVZQbpo8D5QDLwHrAGnbKvgmTN/jWM+XUMS3YsoVX1VjzQ4wE61e9UrDF3HNnB2Plj+WHTD9RPrM/dXe/mrOZnlVDESgVcYYrXRQAdsLP+Y4GFxphFIrLO7Zds8ZLJAElJSRWzaohSqkxybjcyhMKtbjozaIHlE2bCaFSpEVuPbPU41qxqsxBEFFwd63Vk5uqZHjNoAd2nXakiEpGtxpie2O/kpwKXk/cZMPfz4XLgChHZEvwIlVKqbCpKgnQotsJ8RxFZG6B4lPKwfPdyzphyBmnZaeRIDusOrOOHTT8wc8hMBjQf4NeYO47soP2k9hzJOEKWI4s1+9ewcPtCnu33rG7Yr8qKwhSv2w7sF5EUIMUYMw9oD6xDKaXKOGNMVexS09MoeGUTeN5ACrqx/cYy8ouRbknC2IhYxvUdF8KoguPKU67kyV+fJD07HYc4ALsffYd6HUiql38RmVLqeETkH6CDMaY70BP7ORBgB/CLiPwasuAqsG2Ht/HCohdYvGMxbWu15c4ud9KyekuPfg6HgyfmPcHEpRPJzMlkYPOBTDpnEpViKoUgaqVUrqIkSOsBP2tyVAXbXXPuIjkr+dhzQUjNSuXGb25kw80b/CoUNXb+2GPJ0VypWan83w//x4hTRxAT4WsCilKlxhKghTGmCfbD8KXYPUddfQ684tyoPwq7BP+FoEaplFKBMwY7S34b8AqlfHXTsLbDiA6P5v4f72fzoc00q9aMsWeO5bxW5x3/l8u4StGVWHrtUm7/7na+3fAtUeFRXNX+KsacOSbUoSlVponIfGB+qONQdsVj5zc7k5adRmZOJou3L2baimnMuWIOXRt2devbZUoXft/x+7HnH/79IV+u/ZI9d+0hLiou2KErpZyKkiDdRyn+0KnKr8U7Fntt33p4KylZKSREJRR5zO///d4tOepq7f61tK/TvshjKhVMIpJtjLkJ+A4IB6aIyCrXAncistoYMxtYATiAN0Xk79BFrZRSJep84D/gdBHxvsFnKTO49WAGtx4c6jBComHlhnwy5JNQh6GUUgFx53d3ciTjyLFidDmSQ0pWCtd9dR0rR6881m/x9sVuydFcyVnJ3Pvjvbw08KWgxayUchdWhL7fAF2dM5GUCprqsdW9tkeFR/k907N+pfpe27McWdSKr+XXmEoFm4h8IyItRaSZiIxxtk0SEdcid+NF5CQRaSsiL4YsWKWUKnk1gPllJTmqlFKq/Jq7Ze6x5Kir1ftWk5aVV6Ru6l9TfY7x5dovAxKbUqpwipIgfcj55yvOivZKBcXdXe8mLtJ9qUFsRCzXnnYtEWH+5evv6XqPx5hR4VH0OqEXdRPr+h2rUkoppYJmJ5Ad6iCUUiqQjDE5xphsY0xLl+eFfeg1MkgqRXvfPzQyPJKo8Khjz+sk1PE5RrW4aiUel1Kq8IqSIL0eu5TzWmCtMWaKMeZRY8zDXh4PHWcspQrtho43cHOnm4mNiKVSdCViImIY0mYIz/R7xu8xz2p+FuP7jichKoHEqESiw6Pp3bg3H138UQlGrpRSSqkAmgn0MMbEhjoQpZQKIIP793ZThEdRvu+rYri5080eE3BiImK44uQrCA8LP9Z2V5e7MD7qCj7R+4mAxqiUKpgRKVxRT2OMA1sBtKCKOLnHRUTCC+gXcElJSbJ06dJQhqBK2JGMI/z73780rNSQ6nHel90XVXp2Omv3r6VWfC2dOaoKxRizTETKbcldvXYqpQIhENdOY0wC8BuwBRgpIntLcvyi0GunUioQ9HNn2ZHjyOGaL65h+t/TiY6IJiMngzObnMnHl3xMbKT7fbyZ/8xk6CdDyZGcY223dLqFCQMnBDtspcolf6+dRVmf/FhRB1eqJFWKrsQpdU4p0TFjImK0IJNSSilVNr0EbAAuBNYbY5YBW7FF6fITEbkmmMGVNhnZGTzw0wO8+cebpGalckajM3j57Jc5qeZJHn3/2PUHt3x7C4t3LCYxKpEbO97II70e8XtrI6WUKu/Cw8J5Z9A7PHXmU/yz7x+aVW1Gk6pNvPa96KSLSH8wnWkrpvFf+n9cdcpVVImpEtyAlVIeCv0pR0Q0QVoOrd2/lteWvsa2I9s4q9lZXH7y5R5LA8DOtLzvh/v45J9PiI+K5/+6/R8jTh0RgohVqbZqFUyaBLt2wTnnwLBhEONfIS2llFLqOK6CYxUxEoFeBfQVoEInSId8PITv//2etGxbLOTnzT/T5a0urL5xNfUS6x3rt+HgBnq+05PkzGQA/kv/j+cXPs/WI1uZOsh3cRGllFJQL7Ge2zXVl4iwCK485cogRKSUKiy9DVyBfbn2Sy6deSmZ2ZlkSzazN8zm+YXP8/u1v7ttMp2enU795+pzMP3gsbarv7iaz9d+zqxLZ4UgclUqTZ8OV18NGRmQkwOzZ8OECbBgAcR5Jt2VUkqpYtI7tYW04eAGt+QogCCkZ6fz8uKXebrv08faxy8YT3pWutvvp2anMmPVDMb1HVdggRGlVOAZY/4EfgB+An4RkdQQh6SUUuWCXwlSY0xloCNQE9giIgtKNCoVcNmObK76/CpSs/L+PU3NSmXL4S1MWDSBh3rm1dm6/8f73ZKjuT5f+zlr96+lVY1WQYlZlWIZGXDttZDq8vksJQXWrYM33oBbbw1dbEoppcolEdHpjIW0et9qIsMj3RKkAJk5mSzbtcytbdnOZWSLZ+Hr6PBo1h1YpwlSpUKvPXAycAeQbYxZDPyITZouEnHZ2FIppVShFamqnTGmsjFmCrAXW9H+fWCky/EbjDE7jTGdSzZMVdJW7V1FZk6mR3t6djoz/pnh1jbzn5k+x3l92eslHpsqg5YtA+OlfltaGnz0UfDjUUoppdQxrWq0Iisny6M9KjyKU+uc6tZ2Sp1TCDeetVYzsjNoXq15wGJUShVaG+BW4EsgBegOPALMA/4zxnxtjLndGHNyCGNUSqkyp9AJUmNMPDAXu9/Tf8C3eFa0nw3UAQaVSHQqYBKiEshxeL+56Lq8HiA+Kt7nOLqZtAIgIcEuq/emcuXgxqKUUqrCMca0McaMNMbcZ4w536U9zBgTFcrYSoOW1VvSs3FPYiLc9wWPDo/m5tNvdmu7p9s9Hv1iI2K5sPWFhdpXTykVWCKyWkReEZFBQA3gdOAB4GfsCtGBwLPAn8aY3SELVCmlypiizCC9Czud/32gqYicm7+DiPwLrAP6lEx4KlCaVWtGy+otCTPu/wnER8ZzU8eb3Nru7X6v1zEMhju63BGwGFUZ0q4d1KvnOYs0Ph5uvDE0MSmllCr3jDGNjDE/ASuA14Encb9RfzOQZow5MwThlSqfDvmUa069hrjIOMJMGN0aduPXEb/SoFIDt34tq7fkpyt/IqluEgZjq9h3upF3Br0TmsCVUj6JiENElojI0yLSF2iATY5mYCcz1QxpgEopVYYUZQ/SS4CdwLUiklFAv63Yaf+qlJt16Sz6TO3D/tT9AGQ5shhx6ggubXupW7/h7Yfz+ZrP+XTNp8faDIa3L3ibhKiEoMasSilj4Kuv4Mwz4fBh25aZCTffDOd63EtRSimlis0YUwO7pLQRNkE6H7ghX7cZwHPABdg9+iqs2MhYXjn7FV45+xVEBONtaxynTvU7sWTUkuP2U0qFlrH/g3YC+jofnYEobHJ0P3ZWabmwO3k3+1P307J6S6LCS2ZhwMG0g+w4soOmVZsWuGpSRNhwcAPGGJpVbVbgdXF38m5+2/obp9U9jSZVm5RInBnZGaw/uJ5a8bWoFV+rwL67ju7iQNoBWlVvRWR4ZImcX6mKoigJ0qbAd8dJjoK9EFf3PyQVLI2rNGbDLRuYv3U+u5N306VBFxpWbui178yhM1l/YD2Tl02mSkwVbu98O3FRWplcuWjZEjZvhnnzYP9+6NbNzipVSimlAuM+bHJ0HHC/iIgxxi1BKiK7jDGrsXv0KafCJj01OapU6WOMaUVeQrQXUAmbEE3Fbon3I/CDiCwv4rhTgHOBvSLS1stxA0wAznae6yoR+cN5bIDzWDjwpoiM9eOleXUo/RDDPhnGz5t/Jio8CmMML5z1AleferXfY2bmZDLqy1FMXzWdqPAosnKyuKfbPTzS8xGP694fu/7gko8vYXey3a2gfmJ9PhnyCSfXdt/i1eFwcMbbZ7Bge1796lbVW/HHqD+K9b150pJJ3PPDPQBk5WTRt1lfPhj8AYnRiW79DqYd5NJPLuXXrb8SGRZJmAnjpYEvMbz9cL/PrVRFU5QEaRYQc9xedlp/sn/hqGALM2H0OKFHofq2qN6C8f3HBzgiVaaFh0Pv3qGOQimlVMVwHrAJZ3K0gH7bgNOCE5JSSgXcakCAbGAZtnr9D8BCEfGsxlZ47wCvAO/6OD4QaOF8nA68BpxujAkHXgX6AduBJcaYL0Tkn2LEcsyQj4fwy5ZfyMzJJCPHztW6+dubaVKlCb2b+Pe9447v7mDGqhmkZ6eTnp0OwPgF42lYqSHXnHbNsX6H0w/Te2pvjmQcOda2/uB6er7Tk223b3NbTXnpzEvdkqMAaw+spdfUXvx+7e9+xTl7w2zu/P5OUrNSj7V9v/F7Lvv0Mr4Y9oVb3wunX8jCbQvJcmSRjn1No78eTdOqTeneSO8RKlUYRdmDdC1wqjHGZ5LUGFMVu0/pyuIGpoLjSMYR3vrjLcbMG8O8LfMo+PtF4RxKP8TN39xM33f78vDPD5OZnemz77///cuzC57luQXPsem/TT775Thy+HLtlzw570k++vsjMrKPN5FZHdeuXfDSSzBuHPz9d6ijCa6sLPjsM3jySfjkE7sdgFJKqbKmIfDHcZKjAEeAqkGIRymlgmkd8JPzUdzkKCIyDzhYQJcLgHfFWgRUMcbUxS7x3yAi/4pIJvCRs2+xbT+ynV+3/kpmjvtn9dSsVMYv8G/iTmZOJlP+nEJadprHmGN/c5/4On3VdK+FjbMd2cz8Z6Zb26erP/XoB7Bk55ICvw8XZNxv49ySowAZORnM2TiHvSl7j7Vt+m8TS3YsIcvh/p9AalYqzy14zq9zK1URFWUG6SfAWOfjNh99ngISsPs9qVJu2c5l9Hm3DzmOHNKy04iNiKVbw2589b+v/N6v5Letv9HjnR44xAHAj5t+5JnfnmHdzetoVLmRW98XFr7A/T/df6zvgz8/yNi+Y7n19Fvd+h1KP0T3Kd3ZengrKZkpxEXFceecO1l4zUKPMVUhzZgBV10FIpCdDY89BjfcAM8+G+rIAm/fPujaFfbsgeRkW0iqenVYuBDq1g11dEoppQovDahSiH4nAIcCGolSSgXPrcCZQE/gfux2I+nGmPnYmaQ/5i59L2H1sTPyc213tnlrP93XIMaYUcAogEaNCv4utzt5N1HhUcdmebradnibl984vpTMFK9JT8At6Qh2P8+UrBSPfmlZaexK3uXWliPexwQ4knmEGhE1ihzrjiM7vLZHhUexJ3nPsf1IdyfvJjI80iPpC7D18NYin1epiqooM0hfwU7nv9kYM98Yk1u+vLExZrSzgugo7OzRt0o4TlXCRISLZlzEkYwjpGSl4BAHKVkpzN82n8nLJvs97jkfnHMs4ZkrIyeDcz9wL9Sz4eAG7v/pftKz08nMySQzJ5P07HTu/eFej5mk9/1wH+sPrudo5lEcOEjOTGZP8h6u/tz/fWcqtEOHbHI0LQ3S022CNC0NXnsNfvst1NEF3u23w5YtcPSoTRAnJ8OOHXDjjaGOTCmlVNH8DXQwxlT21cEYUx+7uikQyQKllAo6EXlZRAZh6350Bh4CFgFnYPdkXmKM2W+M+diZjCwp3jYllgLavRKRySKSJCJJNWvWLPCEJ9U8iWxHtkd7ZFgkfZv1PU643lWJqULthNpej3Vp0MX9ecMuXosSx0TE0LVhV7e2ytHe/ymKCIugRlzRk6MAfZr0ISLM+5y2ltVbHvu5ba22ZOV4TiCOCo+iX7N+fp1bqYqo0AlSEUkF+gOLga5A7pz2ntjkaS/sh89znFPrVSm2ev/qY9XrXaVmpTJl+RS/xjyYepDDGYe9Hvt7r/sS7s9Wf+b1zp1DHB7LE6avmu6xrCJHcvhlyy9e7yaq45g9GyK8/EOblgbTpgU/nmD79FO7xN5VdjZ8+aVNmCqllCorPsDOIH3dGONR0tgYEwa8BEQD7wc3NKWUCiwRcYjI7yLylIicid1KpC/wMhAHDAYmluApt2O3NsnVANhZQHuxxUXG8WTvJ4mPzKswHxEWQeXoytzd9W6/xjTG8PLAl4mLzCucFGbCSIhK4Jl+z7j17du0L6fUOYXYiNi8mCLi6NKgC2c0OsOt7ysDX/F6vkd6PuJXnAD3n3E/iVGJbknSuMg4xvUdR3RE9LG2xOhEHu75sNtrigyLpHJ0ZW7vfLvf51eqoinKEntEZAfQ1Vml7mxsZftw7JT6b4FZhdgHSpVywfgrFN83FVWgFfT363D4PqaUUkqVLm8ClwFDgI7GmK+d7W2NMeOAQdhiInOxyVSllCp3nDeDTscuu++LnVUaifeZncXxBXCTMeYj5/kOi8guY8w+oIUxpgmwA7gU+F9JnfT2LrfbYsG/jWdn8k7OanYW93W/j3qJ9fwe84ITL+D7K77nyXlPsv7gejrV68RDPR/ixBonuvULM2H8cMUPvPL7K7y9/G2MMYw8dSSjO472qHZ/efvLSYxO5OZvb2ZX8i6qxVTjqTOfciv6VFSNKjfir+v/YsyvY/hx04/UT6zPvd3vZUDzAR597+1+L61rtGb8gvHsTdnLgOYDuK/7fT5nyyqlPJnyms9MSkqSpUuXhjqMUktEaDyhsceeJHERcYzrN46bOt3k17hVxlbxOou0ba22rBydV7trw8ENtHutnccM0JiIGFbdsIqmVZsea7v+q+t5e/nbbrNIw004PRv35MfhP/oVZ4V26BDUq2dnjLqKj7ezS7uX8yqHl11m92DNdlmuExEB55wDs2Yd99eNMctEJClwAYaWXjuVUoEQqGunMSYReAObJPVmFnCliBwt6XO70munUioQfF07jTFtyEuI9gASyUuIJgPzyNuPtFAFlI0xH2JXhdYA9gCPYBOtiMgkYzOCrwADgFRghIgsdf7u2cCL2MlTU0RkTGHOqddOpVQg+Pu5s0gzSFX5YYzhk0s+oe97fclx5JCalUp8VDyd63dmVAf/t6r56n9f0fPtnjjIm4kYFR7FV8O+cuvXvFpznuz9JA/9/BDZjmwMhrCwMJ7q85RbchRgbN+xzNsyj+1HtpOcmUx8VDwJUQlMOd+/rQAqvCpVYMoUGDHCPs/KgqgouPba8p8cBXjxRVi0yBZrSk6GhASoWtXuwaqUUqpMcSY+LzXGPAYMJN/qJhH5M5TxKaVUSTPG7ARypwUaIAuYD/yITYouFimgYpAPIjLsOMcF8Lppv4h8A3xT1HMqpVRpognSCqxj/Y5svW0rM1bNYHfybs444Qx6ntDTY7lAUXRv1J19d+/jwZ8fZO3+tXRu2JmHejxETESMR987u97JBSdewKerP8VgGNx6MM2qNfPoVyWmCitGr+DrdV/z156/aF6tOYNbD/Y6piqkSy+FM86Ajz+2M0nPPRfatQt1VMFRsyasWQNffAGrVsGJJ8KgQTZJrJRSqkwSkdXYYqJKKVXe1QGWYxOiPwLznPVClFJKFYPPJfbGmH+LMa6IiGemK4h0ur5SKhB0ib1SShVdWbp2GmNisMtTo7GTCT4RkQKrbFTUa6eI8O5f7/L0/KfZm7KXzg06M7bvWE6ufXKoQ1OqXPB27TTGVBeRA6GKqSRV1GunUiqwArHEvrH/4WgFnlDbcWQHC7cvpHZ8bbo16kaYCSv2mAfTDvLL5l+Ij4qnd+PeRIZHFnvMlMwUftr0E8YY+jTp41Z5L79v13/L9/9+T8d6HRnWrsAVIEoppZRS/soA+ohIsjEmEphvjPlWRBaFOrDS5qlfn+Kp+U+RmmUnr83eMJtft/zK79f+TuuarUMcnVLlU3lJjiqlVGlTUIK0SdCiUCVGRLjr+7uY+PtEosKjEITqcdX5+cqfaVylsd/jvvr7q9z1/V1EhdtlyJFhkcy+fDZJ9fyfDPLl2i8ZNnMY4WHhAOQ4cph+8XTOaXmOW7/kzGRavNSC3Sm7j7Vd++W1/HPjPzSq3Mjv8yullFKq7DDGFHlPPRciIoXaWsq5z16y82mk86E3//NJzUp1S44CCEJqdiqP//I4H178YQijU0oppZQqGp8fFEVkSzADUSVj5uqZvL70ddJz0knPsRXiU7JSOP/D81kxeoVfYy7buYx7vr+H9Ox0t6rzA94fwK47d/k1k3R38m6GfjKUtGz3SupDPhnCpls3USu+1rG2c6ad45YcBfuaznj7DLbcpv+ZKqWUUhWE/5ukF/F3jTHhwDKgOfCqiCwuxrnLpc2HNhNuwj3aHeLg952/hyAipZRSSin/FX/dtSpVXlr8EilZKW5tDnGw8b+NrD+w3q8x3/jjjWPJVldZOVn8uOlHv8acsWoG4mUyhojw8aqP3drmb53vdYyth7eSmqn7kSullFIVgYiEFedRxHPliMgpQAOgkzGmbf4+xphRxpilxpil+/btK6FXWXbUTahLZk6m12PNqoa0FIFSSimlVJFpgrScOZJxxGt7uAn3eex4DqUfwiEOj3ZB/B7zaMZRsnKyPNqzHFkczTzq1ubA89y5UrM1QaqUUkqpwBCRQ8BcYICXY5NFJElEkmrWrBns0EKuamxVhrUdRmxErFt7XEQcD/Z4MERRKaWUUkr5RxOk5cwlJ11CTESMR3tEWITfFUUHtx5MfGS8R3uWI4s+Tfr4NeaA5gOIjoj2aI8Kj2JAc/fvIPUT63sdIzYilhpxNfw6v1JKKaWUN8aYmsaYKs6fY4G+wJqQBlVKvX7e64w4ZQSxEbFEhUdRL7Ee7174Lj1O6BHq0JRSSimlikQTpOXMLaffQuMqjY9Vgw834cRFxPHm+W/6XXV+cOvBnF7/9GNJUoMhLjKOMX3G+J2g7FCvA8PaDnNLvMZHxnP5yZdzSp1T3PpOv3g6xsvWYZPPnezXuZVSSimlClAX+NkYswJYAnwvIl+FOKZSKSo8ilfPeZX//u8/dtyxg+23b+eiky4KdVhKKaWUUkVWqGqequxIjE5k2ahlvPfXe8zeMJsGlRswOmk0J9U8ye8xI8Ii+O6K75j5z0w+/udjKkdX5toO19K5QedixfrGeW9w8UkX8+5f72IwDG8/nP7N+nv069aoG+tvXs+N39zIX7v/omnVprw44EU61u9YrPMrpZRSSuUnIiuAU0MdR1kSHRHtdWWQUkoppVRZoQnSciguMo7rkq7juqTrSmzMiLAIhrYdytC2Q0tsTGMMA5oP8FhS702zas2YffnsEju3UkoppZRSSimllFKgCdJSITkzmX//+5f6ifWpHle9wL4Lty1kb8peBjYfSFRElM9+mTmZrN2/lhpxNaibWLfAMXcn72Zfyj5aVm+pd/+D7cAB2LEDmjWDeM99XpXT/v2wcyc0bw5xcaGORimllFJKqaAwxvxUjF8XETmzxIJRSqlyrFQkSI0xm4GjQA6QLSJJ+Y73Aj4HNjmbPhWRx4MYYkCICI/OfZTxC8YTGR5JZk4ml5x0CW+c94ZHonLx9sX0ebcPqVm2arvB8HCPh3m096Me4775x5vc8d0dgE2U9jyhJx9d/BFVY6u69TuUfohhnwzj580/ExVuk63P9n+WUR1GBeDVKjcZGXD11fDppxAVBVlZ8H//Bw8/DMZzv9UKKy0NRoyAzz+HyEjIyYEHHoD77tP3SSmllFJKVQS9ivG7UlJBKKVUeVcqEqROvUVkfwHHfxWRc4MWTRC89edbPLfwOdKy00jLTgPgk38+ISEqgYnnTDzWz+FwcMbbZ5DlyDrWJgiPzXuM0xuczsAWA4+1z908l1tn33oskQowd8tcLvn4En4Y/oPb+Yd+PJS5W+aSmZNJRk4GALd/dztNqzalb9O+AXnNyumWW+CzzyA93T4AnnkGTjgBrroqpKGVKqNH2+So6/v01FPQpAkMGxba2JRSSimllAq83qEOQCmlKgKfCVJjzJRijCsick0xfr9CGPfbOFKyUtza0rLTeGf5O7w44MVjszonLZvklhx1df+P97slSJ/57Rm35CjYWaS/bfuNbYe30bByQwB2HNnBvC3zyMzJdOubmpXK+N/Ga4I0kNLT4d138xJ+uVJTYexYTZDmSkmBjz6ys23ztz/9tCZIlVJKKaVUuSciv4Q6BqWUqggKmkF6VTHGFaAoCVIB5hhjBHhdRCZ76dPFGPMXsBO4S0RW5e9gjBkFjAJo1KhR0aMOsn0p+7y250gOyZnJVIutBsDG/zb6HGNP8h6359uPbPfaLyo8it3Ju48lSPem7CUqPIr0nHSPvtuPeh9DlZDkZBAfq1327g1uLKXZ4cMQFub92J493tuVUkoppZRSSimliqigBOmIoEUB3URkpzGmFvC9MWaNiMxzOf4HcIKIJBtjzgZmAS3yD+JMrE4GSEpKKvX7rXRt2JXZG2Yj+baGqR1fm6oxefuFDjlpCM8vfN7rGD1O6OH2vG/Tvqw9sNZjZmiOI4c2tdoce35ijRPJkRyP8SLDIjmzie7jHVDVq9vHzp3u7cZA166hiak0qlMHEhPtPqSuwsKge/fQxKSUUipktFCJUkoppZQKFJ8JUhGZGqwgRGSn88+9xpjPgE7APJfjR1x+/sYYM9EYU+M4e5aWeuP6juPXrb+SlpV2LFkZFxnHK2e/gnEpQHN6g9NpU7MNq/a5T5qNCIvgpYEvubXd0+0e3lvxHofTDx9blh8fGc8TfZ4gLjKv+ndsZCxPnfkU9/1437El+RFhEVSKrsS93e8NyOtVTsbAK6/A5ZfbZfUA4eEQGwvjxoU2ttIkLAxeftkWaXJ9n+Li7D6kSimlKppexfjdUn/jXCmlisIYUw+4AGgJVAK8VTDVre+UUqqQQl6kyRgTD4SJyFHnz/2Bx/P1qQPsERExxnQCwoADwY+2ZLWr3Y6l1y7lyXlP8vuO32lerTkP9HiArg09ZxGuuH4FN35zI++teI8sRxad6nXi/cHvUyuhllu/Ogl1+Ov6v3j616eZs3EOdRPrcnfXuzmn5TkeY95y+i20qNaCZ357hp1Hd9KvWT/u634f9RLrBew1K6cLL4Q5c2DMGNi4EU4/HR58EFq2DHVkpcuQIXYm6VNPwb//Qrdutop98+ahjkwppVTwaaESpZQCjDG3AWOBSNdm55/i8ryoW98ppVSFZcTXXojBCsCYpsBnzqcRwAciMsYYcz2AiEwyxtwEjAaygTTgDhFZUNC4SUlJsnTp0gBGrpSqiIwxy0QkKdRxBIpeO5VSgaDXTqWUKjpv105jzFnAt8AR4BXs7PouwPVAc+AioAnwErA8mCtDi0qvnUqpQPD3c2eRZ5AaY2Kwd/CPN5X/icKMJyL/Au29tE9y+fkV7MVfVWAiQmZOJlHhUW5bEJRKqakQFQURIZikLQKZmfb8Jfk+JSfb5e2+CicppZRSSimlAu0W7MzQfiKyxBjzNtBFRN4AMMY8hP3ufA3QIXRhKqVU2VKkTIcx5iJgG/AV8DzwKPBIvsejzodSJWbaymk0fKEhcU/FUWN8DZ5b+Byhnv3s1fTpEB9vH5GRdil4/mJMgTR1KtSvbxOZNWvCSy/ZhGlxjBljk62JiXYP0K5dIT29ZOJVSimllFJKFUVHYKmILPF2UEQygRuxM0wfCWZgSilVlhV6epsx5nTgI8ABfAi0Bdph9z5pDvQDKgNvAdtLPFJVYX22+jNGfTnqWDGpg2kHeeTnRxAR7up6V4ijc7F0KVx6qXvbxo3QujUcPhz480+fDjfckFfQ6MABuO8+O4v05pv9G/PNN+3eqK4WLoSkJPj77+LFq5RSSpUALVSilKpgKgP/ujzPBFvbQ0RSAEQkyxjzG7p3s1JKFVpRZpDe5ew/WEQuB/4EEJEHRGQo9kPpN8DZwCSfoyhVRA/+/OCx5GiulKwUnvr1KRziCFFUXtxyi/f2I0dg5szAn/+hh/KSo7lSU+Hxx/2fRXrffd7bV62CrVv9G1MppZQqIc5CJf9il5PeAlzl8rjS+ch9rpRS5cF+7M2gXAedfzbO1y8GqBqMgJRSqjwoSoK0K/C3iHzt7aCI7Af+B0QDj5VAbEoBsOXQFq/tRzOPkpKZEuRoCrBxo+9jixYF/vy+EpYHD9o9Sf1x6JDvY8uX+zemUkopVQKchUqeB9KBp4GFzkPXAeOBTc7nE4Crgx6gUkoFxmbgBJfny7Ez54flNhhjamGLN3n/IqWUUspDURKkNYC1Ls+zAYwxsbkNInIUmAcMLJHolAJaVW/ltb1qTFUSohKCHE0B2rTxfaxPn8Cfv0UL7+21a9s9RP1Rq5bvY507+zemKjHGmAHGmLXGmA3GmHsL6NfRGJNjjLk4mPEppVSAuRYqeRBYDyAib4jI/wEnYbd+ugZYELIolVKqZP0ItDbGNHI+/xr4D7jPGDPdGPMc8DuQAMwKTYhKKVX2FCVB+h92dmiuQ84/G+TrJ0ABWRWlimZcv3HERsS6tcVFxjHmzDGlq5r9q696rxpfqxYMDMI9g3HjbHEmV3FxMHas/9XsJ0zw3t61a8HJUxVwxphw4FXsDamTgGHGmJN89BsHfBfcCJVSKuC0UIlSqiL6EJiCcxapiCRjZ8mnA5cAtwONsDNLnwxNiEopVfYUJUG6DXuhzfU3dir/ubkNxph4oDuwo0SiUwro27Qvsy6dRfva7YkOj6Z51ea8cd4bXHvataEOzV3r1vDDD3bGJtikZOfOsH59cM5/9tnw8cfQrh1ER9sZpVOmwPDh/o958cW2UFNion0eHg6DB8Ovv5ZMzKo4OgEbRORfZxLgI2yRkvxuBmYCe4MZnFJKBYHPQiW5DSKSBWihEqVUuSEiq0XkWhH51aXtc2xNkNHAA8BFQKfcok2FdbzVScaYu40xy52Pv50rlKo5j202xqx0HltarBeplFIhUOgq9sBc4FZjTE0R2Qd8BaQCTxtj6mAr1w/HLsX/tKQDVRVb/2b96d+sf6jDOL4+fWD37tCd/+yz7aMkXXONfajSpj72xlWu7cDprh2MMfWBC4E+2JlWSilVnhRUqGSVS7sWKlFKlXsisgN43d/fd1md1A/7uXKJMeYLEfnH5RzjsXs8Y4w5D7hdRA66DNPbWZtEKaXKnKLMIP0Y+AU4FUBEDgB3ApHYCvcvAh2wF9OHSjRKpZRS+XnbN0HyPX8R+D8RySlwIGNGGWOWGmOW7tu3r6TiU0qpQNuMFipRSlUwxpgpxpjjFp4zxlxljJlShKELuzop1zDscn+llCoXCp0gFZHfRaSfiMxxaXsdeyF9BngTmzBtr3eNlAqR336DXr2gRg27vH/OnOP+iiqztgMNXZ43AHbm65MEfGSM2QxcDEw0xgzKP5CITBaRJBFJqlmzZoDCVUqpEqeFSpRSFdFV2G3tjqcbcGURxvW2Oqm+t47GmDhgAHYbp1wCzDHGLDPGjCrCeZVSqlQoyhJ7r0RkGbCsBGJRShXH3LlwzjmQmmqfHzgAgwbB++/bfUNVebMEaGGMaYLd9/lS4H+uHUSkSe7Pxph3gK9EZFYQY1RKqUD6EKiLnUW6VUSSnbOqPsAWKsn1J1qoRClV8UQCjiL0L8zqpFznAb/lW17fTUR2Omfuf2+MWSMi8zxOYpOnowAaNWqU/7BSSoVMUZbYK6VKs7vuykuO5kpLgzvvDE08KqBEJBu4CVudfjUwQ0RWGWOuN8ZcH9rolFIq8AJZqEQppcqBNsChIvQvzOqkXJeSb3m9iOx0/rkX+Ay70tSDrlxSSpVWRZ5BaoyJwn7Y7IW9aAr2wjkXmCkiGSUYn1KqsP7+23v71q2QmQlRUcGNRwWciHwDfJOvbZKPvlcFIyallAq14hYqUUqp0sbLXqLdC9hfNAJoDZyG3XqksI67OskZS2WgJ3C5S1s8ECYiR50/9wceL8K5lVIq5IqUIDXGdMUuW2qI5xT8a7AV7S8TkfklFJ9SqrDq1oXNmz3bK1WCyMigh6OUUkoFkjM5MF9ECixCYoy5CughIsctaqKUUqXUVS4/C9Dc+SjIbuxM+kIRkWxjTO7qpHBgSu7qJOfx3JvwFwJz8s3Mrw18ZowBm2P4QERmF/bcSilVGhQ6QWqMaQPMAeKAf7FT6jc7DzcGhmIv0rONMaeLyKoSjVQpVbAHH4RbbnFfZh8XZ5feG29bCimllFJl2lXOP49XpTm3UIkmSJVSZdUI558Ge82bD7zlo28mdgboImc1+kIrzOokEXkHeCdf279A+6KcSymlSpuizCB9HJscfRp4SETcNnw2xjzi7HM/8Bi2YrJSKliuvhoOH4bHH7dL6sPD4fbb4b77Qh2ZUkopFUpFLVSilFKliohMzf3ZGPMoNvk51fdvKKWUKqqiJEh7AmtFxOs0fWfC9EFjTO7+pEqpYDIG7rjDziLdtw+qV9d9R5VSSqmiFypRSqlSS0QahzoGpZQqj4qSII0F/ihEvz+AC/wLRylVbBERdj9SpZRSqpwJUqESpZQqE5wFkzoCNYEtIrIgxCEppVSZVZQE6VqgMFmXusB6/8JRpVVaVhpv/PEG01dNp1JUJW7sdCPntDgHo3tbuktNhddfh48/hqpV4aabYOBA731XroRnn4U1a6BbN7jzTqhfP7jxhkpysn2fZs6EatXsrNf+/UMdlVJKqdLvKpefA1KoRCmlSjtnYvQF4DLyvtNPBRY4j98APAgMFpFFIQlSKaXKmKIkSCcBE40x3UTkN28djDHdgB7ATSURnCodMrIz6DalG2v2ryEtOw2AX7f+ys2dbubpvk+HOLpSJD0dOneGDRsgzb5PzJ0Ld98Njz7q3vf772HQIPs7DgcsXw5vvw1LlkDz433PK+NSU6FTJ9i8Oe99+vlnuP9+eEC/vyqllCpQUAqVKKVUaWWMiQfmYosi7QWWAmfn6zYbeAUYBGiCVCmlCiGssB1FZDLwErZK/ThjzMnGmETno50xZizwLTAhf6U7VbZ99PdHrDuw7lhyFCAlK4UXFr3AjiM7QhhZKfPee/Dvv3lJP7DJwLFjYc+evDYRGDXKHnM4a0ZkZsKRI3DvvcGNORTefhu2bPF8n558Eg4cCF1cSimlSj0Rmep8vANsxVmoxMfjQxGZp8lRpVQ5cxc2Ofo+0FREzs3fwVlVfh3QJ8ixKaVUmVXoBKkxJge4FVvJ/i7gT+yG94eA5cDdQDxwmzEmJ98ju4TjVkH09fqvSclK8WiPCo9i/tb5IYiolPrqK0jxfJ+IjoYFLtsBHTwIO3d69nM47EzK8u7LL21CNL+oKFikN7iVUkoVjog0FpF7Qh2HUkoF2SXATuBaEfHyofqYrUAF2b9LKaWKryhL7Iuz2aRuVFmG1UmoQ7gJJ0dyPI7ViKsRgohKqTp1IDwccvK9TyJQw+V9iouzFee9qVIlYOGVGnXqQFhY3uzZXA6H+/uklFJKFZIWKlFKVSBNge9EJOM4/fYD1YMQj1JKlQtFWWIfVpxHIF+ECqzrOlxHVHiUW5vBkBidSK/GvUITVGl0ww12tqgrY2yxpm7d8tpiY+Giizz7xsXBHXcEPs5Qu+kmiIlxbwsLg1q17N6kSimlVCEZYyo7q9jvBb7DLjkd6XL8BmPMTmNM51DFqJRSJSwLiDluL2gAJAc4FqWUKjc0camOq02tNrx9wdskRiVSKboS8ZHxNKnahJ+G/0R4WHiowys92re3ldkTEqBSJYiPtwWXfvzRJgBdvf469O5tk6WVK9uE4TXXwOjRoYk9mJKS4JVX7PuT+z61bGkLV/maWauUUkrl41Ko5CrgP+xe+Pn/IZkN1MEWKlFKqfJgLXCqMcZnktQYUxW7T+nKoEWllFJlXFGW2KsKbGjboVxw4gUs3bmUhKgE2tduj9FklqfLL7ezQ5cutcm/k0/2nvRLSIBvv7WV3LdsgZNOgpo1gx5uyIwYAZdeat+nypWhXTtNjiqllCoq10Il14tIqjHGbf8WEfnXGKOFSpRS5cknwFjn4zYffZ4CEoAZQYpJKaXKvCInSI0xzYHrgC7YfZ4+z90g37l86WRghogcKsE4VSkQExFD90bdQx1G6RcbC2ecUbi+jRvbR0VUlPdJKaWU8uRaqKSgvfi2Am2CE5JSSgXcK8CVwM3GmCTgU2d7Y2PMaOy1sSd29uhboQlRKaXKniIlSI0x1wCvArkbUgrgWlWlJvAadl+Ut0siQFV0WTlZzFozix82/UD9xPqMOGUEDSs3DHVYZdvWrfD227b6fP/+cMEFEOHlfx+HA+bMgS++sAWXrrwSWrXyPubatXDnnbBhA3TtCs88U/wiRcnJMG0aLFsGbdvCFVfYPVC9efFFGDcOMjPtrNfXXrNFppRSSqmyQQuVKKUqHOds+f7Ax0BX7MQlsEnRntitRpYBg0QkMzRRKlWOZGTY7/76XbncK3SC1BjTDXgdu9HzA8A8YHG+brOBI8D5aII0JNKy0ujxTg/W7F9DcmYy0eHRjPttHLOGzqJfs36hDq9s+u47GDwYsrNtMvGDD+yS+F9+cS82lJMDgwbB3Lk2URkZaZOQkybB8OHuY86YAUOH5j1fuxbeew9WrIDWrf2Lc8cO6NgRjhyBlBRb9Omxx2DhQrvHp6vTToM//8x7/sYbNrF65Ihe+JVSSpUVWqhEKVUhicgOoKsxZgBwNvaGUTiwDbsf8ywRkRCGqFTZ9/vvcN119jt6ZCT873/w8su2hoYql4pSpOke7IzRgSLynIgsyd9BRLKwm0b7meFRxTVxyURW7V1Fcqb9HpCRk0FqViqXfXoZOY6cEEdXBmVnw2WXQWqqTY6CTX6uXGkLLbn67DP4+Wd7HCArC9LS4Prr4ehR975XXun9XBdd5H+sd9wBe/fa5CjYmP/7D0aNcu83b557cjRXaqpnX6WUUqr00kIlSqkKTURmi8gtInKuiAwUkVEi8pkmR5Uqpk2b4MwzYflyu0o0IwM+/BAuvDDUkakAKkqCtAvwu4gsPE6/bUBd/0NSxTFt5TTSstM82tOz01mxZ0UIIirj/vorLzHqKi0N3n/fve2jj/KSk64iI23iNNeWLZCe7v18a9b4H+vXX9tZrK5EYP58m6zN9dhjvseYOdP/8yullFLB9QlQC1uoxBctVKKUUkqpopkwwSZFXaWn2+/W69aFJiYVcEXZg7QysL0Q/aKKOK4qQTER3idROMTh85gqQHS0vWPkTUy+9zM21vc4rn2jo333K04l98hI7+1hYfaRq6A4ve2rqpRSSpVOWqhEKVVhGWOigIuAXtitRARbuG4uMLMQ+zMrpXxZudJ9klGuqChYv95zCztVLhRlBuleoEkh+rUCdvgXjiqu0UmjiY903xPDYKiXWI8Ta5wYoqjKsDZtoE4dz8RlfDyMHu3eds01dt/P/MLDoVevvOd16kC1at7P1727/7Fefrln8jUy0u6L6rqv6IQJvse47Tb/z6+UUkoFkYikAv2xe+J3BcY7D/XEJk97AX8A52ihEqVUeWKM6QqsA94HrgUGYvciHQm8B6wzxhTji4VSFdzpp3uf2JSRYYshq3KpKAnS34DTnHfovTLG9ANaYu9aqRC47OTLuPiki4mNiCUuIo7EqERqxtXk80s/xxRndmJFZQx8/rmtLp+YaBOgsbEwZAgMG+bet1cvuP12O1s0Ls72r1QJvvzS3mly9eOPnjM+q1a1ff319NO2+FJ8vD1/QoK9s/Xaa+79mjWDq6/2/P1WreDBB/0/v1JKKRVkIrJDRLpiEwOvAt8Ac7AzRi8COjmLmSilVLlgjGmDvc41AjYBY7BJ0mudP28EGgKznX2VUkV18832e79rDiU2Fi64AE44IXRxqYAyhd2/2RhzOrAAOzt0JPADkA28IyJXG2N6ANOA2kAHEQnpZvhJSUmydOnSUIYQUqv3rWb+1vnUTqjNgOYDiAqPOv4vKd8yM+Hbb20RpDPOgBMLmI27ZQv88INNjp5zjvdZpWCLMj37LPz9N/Tv71np3h8isGiRXRLQooVN2vpKjK9bZy/8KSlw//1w9tnFP38FYIxZJiI+bxSVdRX92qmUCgy9diqlVNF5u3YaY2YCFwJPAw+JiCPf8TDgceB+4FMRuThY8RaVXjtVqbZuHdx5J/z0k518dMMN9nuzr63tVKnh7+fOQidInSe5E7t8SYAjQCXgMJAF1AAMcIeIvFjUQEqaXmyVUoGgX/KVUqro9NqplFJF5yNBuh/YJyKtj/O7q4GaIlIjkDEWh147lVKB4O/nziJVZBGR54wxq4DHgCRsQrSK8/BK7B2sL4oaRKhtPrSZ2RtmExcZx/mtzqdKTJWgnVtE+G3bbyzfvZymVZtyVrOzCA8LP/4vVjQi8OuvsGKFXSLev7/7vpr+Wr4cfvvN7gt67rm+Cyilp9vl73v3Qo8e0K6d7zF//BHefBOqVIGHHoJ69XyPOXYsrFoFffrAdde5F1NytWsXfPWVPX7++VCzZlFepVJKKVVuaaESpVQFE4vdX/l4/gAuCHAsSilVbhS5ZLWIzMbuZ1IdW7QpHNgmIjv9DcIYsxk4CuQA2V7ukhlgAnZ/qVTgKhEpzD8Kx/XYL48xdv5YwggjLCyM0V+P5tMhn3JW87NKYvgCpWal0v+9/izfvZwcySEyLJIacTWYf/V86iX6SKpVRCkpcOaZNpGYnW2ntNeqBfPn28SmP3JyYOhQu2ze4bBjxsTA3Llw0knufVesgN69bRW7rCy7ZH3wYHj3Xc+EZqdOsGRJ3vNJk+DFF+HWW937LVkCXbva1wPwySdw772waZNnAadJk+zeprkJ4ZtusgnYyy7z77UrpZRS5YSzUMkH2P328u8pcw3wtDHmMhGZH/TglFIqMNYCdQvRry6wPsCxKKVUuVGUIk1uROSAiCwVkcXFSY666C0ip/iYBjsQaOF8jAJe89KnyBZtX8Qzvz1DenY6qdmpJGcmk5qVykUzLiIlM6UkTlGgx395nGW7lpGSlUJ6djpHM4+y9fBWrpp1VcDPXaY8+KCd6ZmcbGddHj1q9/kcOdL/Md980yZHU1Pzxty/3yY+XbedELEbMR88aPukp0NaGsyaBdOmuY85frx7cjTXbbfBkSPubf375yVHcx05YmeHutq40SZH09Ntojglxf587bWwe7e/r14ppZQq87RQiVKqgpoE9DDGdPPVwXmsB/B60KJSSqkyzu8EqStjTAtjzEUFVbgvpguAd8VaBFQxxhTmrlmB3v3rXdKz0z3aw0wYszfMLu7wxzX1r6ke58+RHOZunhuUBG2Z8d57kJFvdVx2NsyZ49leWJMn2+SoKxHYts0mJXP9/Tfs2+f5+ykp8Hq+zxuvvur7fC+/nPfzzp1w6JD3fgsXuj+fPt3OdvXm0099n08ppZQq/x4H4rCFSlqKyEMi8pbz8RBwIvCUs89jIYxTKaVKjIhMBl7C3vwZZ4w52RiT6Hy0M8aMBb4FJojIpNBGq5RSZUehE6TGmMHGmG+c1exd2x8CVgMzgMXGmPf9iEOAOcaYZcaYUV6O1we2uTzf7mzLH+MoY8xSY8zSfd6SWvlk5GTgcC/6d0yWI6tQgRdHVo7vc+SIj6RYRZR/pmUuEbs83h+Zmd7bjXE/lpXle1/Q/GP4ihPsrE/XMX3JXzQtK8v7a3Q4fL8GpZRSqmLoCawVkQfyV3EGEBGHiDyIXY7aK9jBKaVUIBhjcoBbsTd/7gL+BA45H8uBu4F44DZjTE6+RwFfWJRSqmIrygzSy7HT9FfmNhhj2mLvyDuA37AX5WHGmMFFjKObiJyGXUp/ozGmR77j+feUAptUdW8QmSwiSSKSVLMQRWyGthlKfGS8R3uWI4v+zfoXLvJiuPiki4kMi3RrMxhOrXMqlaIrBfz8ZcaFF0JEvu1yjYHOnSE21r8xr7jC++9WqQKtXQpCtm9v9ybNLy7OjuGqoD1Bb7wx7+cTTvAdd5t8KwAHDfJeOMoYz+X4SimlVMVSlEIlXv4x984Y09AY87MxZrUxZpUx5tbj/5ZSSgWNKcajRFaQKqVUeVSUC+SpwF8i4rou+XJsonKkiPQAOgJZ2L2fCi13D1MR2Qt8BnTK12U7dg+pXA2w1UmLpV/TfgxuPZj4yHgMhoiwCGIjYnl54MtUi612/AGKaUyfMTSo1ICEqAQA4iLjqBJThbcHvR3wc5cp48ZB/fqQYN8n4uKgalW7j6i/br7ZJiNzx4yJgfh4+PBDm3zMFR5u2+Lj8xKlCQlwyikwKt9k5zFjvFesv+kmz2JS06a5nwdsEjj/svn27W1yNS7OzmQND7fJ1YcegqZNi/yylVJKqXIkUIVKsoE7RaQ10Bl78/6k4/yOUkoFhYiEFecR6viVCgiHA2bPhscegzfe8KwBolQhFKWKfXUgfwWankAytnooIvKvMWY+0JpCMsbEA2EictT5c3/snlKuvgBuMsZ8BJwOHBaRXUWI3de5mTpoKiNPG8nnaz8nITKBy06+jJbVWxZ36EKpHledf278h0/++YTfd/xOy+otufzky6kSUyUo5y8zatWC1avh449h6VI48UQ7W7NyZf/HjI21+31++aWtXN+gAQwfDrVre/Y980xYt85Wrd+1yz4/55y8qvK5IiLsHqYvvAAffACVKsEjj0CvXp5jXnihrVh/552wfj106wZjx9rfye+ZZ2DIEJgxw55j6FCbOFVKKaUqtknARGNMNxH5zVsHl0IlNxV2UOdnzF3On48aY1Zjt3b6p/ghK6VU6WWMGQBMAMKBN0VkbL7jvYDPsYXxAD4VkccL87tKBUxamv2OvnKlLewcHw93322/559ySqijU2WIkfx7HvrqaEwG8IWIXOJ8HgUcBn4RkQEu/d4HLhKRQq19NsY0xc4aBZuw/UBExhhjrgcQkUnGGAO8AgwAUoERIrK0oHGTkpJk6dICuyilVJEZY5aJSKAK0oWcXjuVUoEQqGunMeZ57MqlicA08r60NwYuA24A3hCRO/0cvzEwD2grIkfyHRsFjAJo1KhRhy1btvhzCqWU8imYnzuNMeHAOqAfdgXnEmCYiPzj0qcXcJeInFvU3/VGP3eqEvHUU/DkkzZR6qpVKzvRKv/KTVXu+XvtLMoM0l2A6/KiHkA0du9RVwlAoeczi8i/gMd0ONeKe2KzuDfm71PRiAhLdy7laOZRTq9/OvFRnvunlms7dsA//9il5c2a+e6XnQ1Tp8Lhw3ZWaI0awYsxUDIzYdEiu8y+c2fPPVmVUkqpCsZZqCTXXc6HN7cZY27L1yYiUuA/psaYBGAmcFv+5KhzgMnAZLBf8gsbt1JKlVKdgA3O7+c4V29eQOFmzxfnd5Uqnnff9UyOAmzdClu2QOPGQQ9JlU1FybL8AlxujLkHmA08gd1/dHa+fm2xd41UCVq9bzUDpw3kQNoBwkwY2Y5sJp49kStPuTLUoQVeTg6MHAkffWQLFmVmQo8eMHOmnT7v6rPP4JJL7O+AXcI+ejRMnBj8uEvKnDl2Wb3DYavcR0XBrFnQvXuoI1NKKaVCqThTQgr8XWNMJDY5Ok1EPi2or1Ihs3cvTJkCf/9tb6APH+59u6ZQy8iwW0V9/z00bGg/1zdpEuqolKf6wDaX59ux29vl18UY8xe2JshdIrKqCL+bf/Z9CYStKryCZojq7FFVBEXZpHkMdr/Rp4E/sRe8H0Xk2L6kxpiWQFNgcUkGWdHlOHLo+15fth7eSnJmMkcyjpCalcror0fz1+6/Qh1e4D3zjP1QlZ5uZ4WmpcEvv9hCS67S0+Gii/KSo7leew2++CJ48ZakPXvsfqWHDtmNpo8ehQMHYOBA3XhaKaVUhRaoQiXOrZ3eAlaLyPPBe0VKFcHKldCihS1IMm0a/N//2eWkO3aEOjJ3ycmQlGQnLLz3Hjz7LLRtC999F+rIlCdvmaT8s+P/AE4QkfbAy8CsIvyubRSZLCJJIpJUs2ZNf2NVKs/VV9saI66MsTdiTjghNDGpMqnQCVIRWQd0A6YC3wKPYqfNuzoT+Av4qoTiU8C8LfM4mnEUyfdvTGZOJpOWTvLxW+XIK69Aaqp7W3q6LYSUnZ3X9txzdoalN488Erj4AunDD+3M0fxE7AxapZRSSpW0bsAVQB9jzHLn4+xQB6WUm5Ej7c3y9HT7PDUV9u2De+4JbVz5TZgAGzZASop9nplpY73iCs9JDSrUtgMNXZ43wM4SPUZEjohIsvPnb4BIY0yNwvyuUgFzyy3QqRMkJNhCygkJULUqTJ8e6shUGVOkjQxF5G/g6gKOvwa8VtyglLuDaQcxXm7K5UgOe1L2hCCiIPM1UzInxy7Zyd2Pc/du32McPFjycQXDgQN5H3xdZWXZY0oppZQqUSIyn+It31cqsFJTYdkyz/acHPiqlM1TmT7d+2fZtDRYtQpOPjn4MSlflgAtjDFNgB3ApcD/XDsYY+oAe0REjDGdsBOuDgCHjve7SgVMdDT8/LNdZbpoEdSvD4MHe27Hp9RxFGWJvQqR7o26k+nI9GiPj4znglb5J/GWQ717e987pFUr94velQXsx3r++SUfVzD06+f9wh4RAX37Bj8epZRSSikVWhERtnCnN9HRwY3leHwlKHJyIC4uuLGoAolINnAT8B2wGpghIquMMdcbY653drsY+Nu5B+lLwKVief3d4L8KVWEZA716wb332hnqmhxVftAEaRlQO6E293W/j/jIvP/J4yLjOLHGiVza9tIQRhYkzz5rN5yPirLPIyLsBW9Svu0FkpKga1fP309IgHHjAh9nIJxxhk2Eul7g4+PtXqunnBKysJRSSimlVIhERcF55+V9Ns4VEwPXXBOamHy54QbPRIUx0LQpNG8empiUTyLyjYi0FJFmIjLG2TZJRCY5f35FRNqISHsR6SwiCwr6XaWUKkuKtMRehc7DPR+mS4MuTFw6kUPphxhy0hBGnDqC6IhSdpc4EFq2tEtwJkyAhQvtxu63327b8/vtN3jySXj1Vbuc5+yzbZGmsnqH2hi71+hHH8HUqTY5PGIEXHxxqCNTSimllFKhMnky9OkD//5r96YXsRMFStu++5ddBvPmwfvv28+xxtiJD7NmhToypZRSyo0RX0VtyrikpCRZunRpqMNQSpUzxphlIpIU6jgCRa+dSqlA0GunUgEgAgsW2CJI7duX7tVFGzbYWOvUgTPPtIVU1HHptVMppYrO32unziBVZUdyMvz1F7RuDdWqFdz34EG7+Xu9et73Lw0khwP++AOqV4cmTYJ7bqWUUkopVTEYA9262Udp17y5LqlXSilVqukepKr0czjsPkuJidC9u008dugAmZ6Fq9i1yy43qlvXfghr0cIuuw+WF1+0+0F17Gj3VqpeHVavDt75lVJKKaWUUkoppVSRaIJUlX7XXQdffeXe9scftrq9KxHb9uuvNnmang4bN8KAAbBtW+Dj/OknuzdqTk5e28GDcNppNsmrlFJKKaWUUkoppUodTZCq0m/qVO/tCxa4zyKdPx927IDsbPd+mZnw+uuBiy/X3Xd7b09PtxvTK6WUUkoppZRSKjRSU6FHDwgLs9uUVKkCM2YE7/w7dtiCy7GxkJAAI0fC4cPe+37yiS1MHRlpV8dOn+693/btcOKJ9vUYY7cZXLIkcK8hGLZtg8GDISbGriS+7jo4ciTgp9U9SFXpl5Xl+9jBg3azd4CtW73vN5qZaTeGD7Tt230f+/vvwJ9fKaWUUkqVbSJ2NdQXX9gvz5df7nvvzsOHYdo0WLsWkpLgkkvsl8nSJifHrgb7+WeoXx+uuCLv83t+u3bBe+/Bzp1226xzztGCTkqpktO2LWzalPf88GEYOtRek3r0COy5U1OhUyfYsydv1el778HSpfDnn+65jI8/hquusr8DdmXs1Vfb3/vf//L6ORzQqlVeP7DX0c6dbTLW17W2NEtOtu/Tvn329WZk2Elzf/wBv/8e0BozOoNUlX6Jid7bw8OhVq2850lJ3pOpcXGBv9iB/Z/Yl/PPD/z5VYVjjBlgjFlrjNlgjLnXy/HLjDErnI8Fxpj2oYhTKaWUUoUgYhOiZ58Nzz8PTz0FJ5/sfTXVunXQrJldwfTSS3DDDbaQ6d69wY+7IOnptojU5ZfDhAnw8MM24TtvnmffuXNt/YCHH7Z9L7sMzjjDfjlWSqni+uMP9+SoqxtuCPz5p0+3CVnXLfkyM23y8+ef3fved5970hPs8/vvd2979VXPfmATp3fdVTJxB9v778PRo+7vU0YGrFkT8PoymiBVpd/zz3tvv/VWOzU+V6tWNhEZF5fXFhkJNWrA8OGBjRHsxSnMy/9SzZrZ4lJKlSBjTDjwKjAQOAkYZow5KV+3TUBPETkZeAKYHNwolVJKKVVos2fD559DSopNlmZlQVoajB4Nhw65973mGruSKveLcXKynS10r8f90tB65RVYscLGBzZhmpICl17qvkd/To5tS0nJS4gmJ8NffwVnqyylVPnn7cZMrs2bA3/+5cvtNS6/rCxYtapw8Wzdav99yLVgge/z/flnUSMsHf780/v7lJMT8JW5miBVpd/IkfbOefXqNgGZmAjjxsFzz3n2nTYNnnzS3n2uXx+uv95OWU9ICHycjRrZ/5nbtLFxRkba/UXWrAn8uVVF1AnYICL/ikgm8BFwgWsHEVkgIv85ny4CGgQ5RqWUUkoV1kcfef9SGBEB33+f9zwtDRYtcv+SDPZL9qefBjbGonr/fRtvfkePwj//5D1fudL7a09N9V2PQCmliqJbN9/HGjYM/PnbtoX4eM/2qCg72ctVAx9f2+rXd19inpTk+3zt2hU9xtKgXTv3SW+5IiLsXqsBpAlSVTYMHw7799u7BkeOwD33eO8XEWErya9bZ/cEfeklqFkzeHGefLK9q5GTY6fLf/yxjUmpklcf2ObyfLuzzZdrgG+9HTDGjDLGLDXGLN23b18JhqiUUkqpQouO9r63mjH2C3Su3OIi3pS2z52ucbtyOOxkglyRkZ4J31zR0SUfl1Kq4unY0Xfi8aWXAn/+YcNsgtR11WlkpI2pb1/3vk8+6ZkkjIuDxx93b7v1Vu/XSGPg2WdLJu5gu+IK+1pd36eoKGjcGHr2DOipNUGqlFJlk7dvRl6/WRhjemMTpP/n7biITBaRJBFJqhnMGwpKKaWUynPllbaycX4OB/Trl/c8Oto+z58MjY4OzrZSRXHttZ5f8o2xs6BatsxrO+kkqF3b8/fj4231YqWUKgmrVsGpp+Y9j4uDN990v8YGSkICLF5szxUebpOjF15oC/Pl36rv8sth4kR7rQSoWxdefhlGjHDvFxFhX9MJJ+S1VasGP/3kOxlc2lWubFdJnHmmfZ+iouzK3F9+CWiBJtAq9qoie/NNuO02u5zHGOjf31bYLG133pXybjvguhakAbAzfydjzMnAm8BAETkQpNiUUkqpsmv7dvjgA7tqaeBA6NrV+5cyhwN++MEWF6pTx84O8nWj8cAB+PBDW539jDPgrLM8vxB36wZ33GFn/Rhjvxg6HDBzpmeS8a238qoUZ2fbz69t28ITT3g//z//wCef2J8vushuCRUMV18Nc+bAl1/mxRkfD5995v6eGgOzZkGvXnargKws+/6cd56dTaSUUiWhUiVbrMnhsI9gf/dv3NjuN+1w2OteQQm/K6+0D4fDe62TXM2a2T1LQ/WaAqFZM/tvR2HepxJUDt45pfzw2Wf2jnYuEfjuO7uHx/LlIQtLqSJYArQwxjQBdgCXAv9z7WCMaQR8ClwhIuuCH6JSSilVxnz6qZ25k5Njk3QvvggXXADvvef+BTUzEwYMgCVLbDGhmBh44AH4+mvo0cN9zEWL7I347Gy7H+fLL0P79ja5GhPj3veJJ2xScfZsm0i84AI7mya/f/5xL9aRnW0LGu3caffid/X003bcrCzbf+xYG+sDDxT77Tqu7GzYvdsmezMy7J+ZmbaSc37t2tmE75dfwp49NpHcvn3gY1RKVTxhYQUnHYNx/pLuG+rXFAhBfj3l7N1TqpBuvdV7+19/2Q+bSpVyIpIN3AR8B6wGZojIKmPM9caY653dHgaqAxONMcuNMUtDFK5SSilV+qWk2CXqaWk2iSdi2z7/3CY+Xb35pl0q6VqdPTkZhgxxr84uYtuOHs0rVpScbGcwTZzoPY4mTWzl+uHDvSdHAQYP9tyzMzvbzrh0tX693bMuLc0ez8mxP48ZY/fsD7TXXoNly2yxJbDva3IyDB3qfc/RmBi45BK46SZNjiqllAoqTZCqimn3bt/HFi4MXhxKFYOIfCMiLUWkmYiMcbZNEpFJzp9HikhVETnF+SigzKFSSilVwf38s53hmF9Kip1B6mrq1LykX/6+f/2V93zNGjh40LNfWpr/1dkPHfI+AxNg7Vr3559/7p6wzZWdbVdUBdrUqd6r2P/3n3sVe6WUUirENEGqKqaCCtF06hS8OJRSSimlVOngLTmaK/+ebgXt8eY6Tni47+rsBZ2vIAUtOcy/T5uvive5e5wGmq9ziATn/EoppVQhaYJUVUzPPuu9vXVru6xJKaWUUkpVLL17e2+Pj4errnJvGznStudXtardSzNXixa2+nB+cXF2DH9UqgTVq3s/5npusAWZvCVIw8LssUAbOdKzwBTYolatWgX+/EoppVQhaYJUVUzDhtlN96Oj89q6d7f7QSmllFJKqYonJiavYnx8vP2cGBsLI0ZAv37ufYcPt4WXcmeShoVBQoL36uyffmoTpwkJEBVlx+7dG0aN8j/W2bM9E59RUfDtt+5tJ5wAL7xgX0tEhH1ER8NzzxV/UsD339uEbPPm8OST3vuMHAlnnmnf2/Bwe+4qVex7EqSqxKSl2S0SHnsMvvjC7sMaTDt22L+DMWPct19QSilVqmgVe1Vx3XqrfWRnF7xMSimllFJKVQx9+8L27TaBd+QInHUWnHSSZ7/kZPjqK/s5Euw+n8nJMGMGdOjg3vfkk+2Ys2bBrl32pnynTsVLEG7a5Ll0PzMT9u6FevXc29PS7Lly9yIND/e+f2pRXHihfT25HnrIJl3373dfOu9w2H1ZjbGJyfBw+2dmZvHOX1gbN0LXrvb1JidDYqJNGs+f77sAVkmaPt0m2B0O+9/KU0/B1VfDSy8FL0Gsim/XLvjyS/t3dv75ULt2qCMqnXbssNfF8HD7PtWqFeqIvEtNtfszHzgAvXpB27a++37zDbz9NtSoYa9z+a+vgZSdDc8/bwsCJiXB3Xdr3iLAjPjaE6eMS0pKkqVLtWCzUqpkGWOWlediR3rtVEoFgl47VbnTqRMsWeL9WEaGnc0ZSBER3mdCVq3qXhRq40b75T893b1fTAysWGG3ACiqjRvtrFFvrr4a3nor7/nLL8O993omZBs1gs2bA58k7N7dFmB1LVQVHQ3XX29XkwXS4cN2e4X8Rari4+Hrr6Fnz+MOodfOUuD11+G22/L2/nU44LXXPLfdqOheecUm8IyxDxF44w247LJQR+ZuyRK7IiAnxyYgjYFLL7XXLdfrkcNhb26tWuX++xMnwujRgY9z0ya7/V9GRl5bVBT8/bd/1+0Kxt9rpy6xV+WLwwGTJ8Mpp9gPbvfea6tkerNlC1xzDTRtCt262SU3xXXggP2HoXlzOO00e7epuDch0tLskpwTT7QzGJ59Nnh33ZVSSimllKdly3wfe+GFwJ57927fy8Tzf+6dNct7FfucHP+r2N91l+9jM2a4P3/7be+zVQ8cgNWr/Tt/YR09Cr//7vn6MzLggw8Ce26A777zPtsrNRWmTQv8+VXxbdpkk6Pp6fbvLTXV/jx6tJ0tqax16+Cee+x7k5Zm36e0NLj2WtizJ9TR5XE44IIL7M2L5OS8eGfMgI8/du/7+OOeyVGAG2/0vOEUCP37uydHweYA8m/3okqUJkhV+XL11XDHHXZ/n40b7Z3hjh09P5ht3Qqnngrvvmv/4VuwIG9fUn8dPWqXVL30kj33n3/CTTfBDTf4P2ZOjp32P2YMrF1rP0g+/DCcc07xE69KKaWUUqrklYWl07mzvPwRVoSvkAWdI9DvU6j/HkL52lXJ+OQT7zcYwO5XrKwZMyAry7PdGP9vxATCsmU2MZpfSoqd7epq8mTvY4jYWcWBtmGD9/YtW3z/N6mKTROkqvzYuNHu85OSkteWkWHvsr//vnvfp5+2Cc3cfaPAJlEfeshzGUxhvf027NvnPrszNRXeeQe2bfNvzO++g3/+cY8pLc0uFfrtN//GVEoppZRSxdOxo+9jt90W2HPXqeN7H7qqVd2fDx7sPaEZFmaP+eOZZ3wfu/RS9+dXX+29in2NGnZ1VCAlJEDnzp6vPzo6OMt++/f3PtM3Lq70LTtW3mVleU9GORzeE4IVVVl5nwqKJf8KTdc8QX7+5gtUqacJUlV+LF7s/cNiSgr89JN7288/e7/ohYXZJQL++P5770uIoqJ871F1PAsWeL/LlZlpk6RKKaWUUur4HA6YMwfGjrU31PMvXXS1aJHdY7R1a1t4yJsffvC+z+j993tvT06GqVNh3DhbIKiglUCrVtktlV57zRZd8ib/Unaws7XmzXNva9LEnjMqyn7ODQuzPz/1FDRr5jlGZqZdWjpggF2V5e1zaLNmMGSIZ3u1ajBpknvbqFFwxhl2383wcPtn5cp29l0wZlFOnWoLxSQk2NeekGATs48/7r3/pk12RdmECXbFWXFUrmxXq8XG2kdkpP0z9z1Rpd8FF9i/t/zCwuwxZV14ob3x4M155wU3loJ07Og9XxAfD8OHu7flv9nj6vrrSzYub+rW9d5eq1bRZvGrItF3VpUf9et7/6AVFWU/HLpq1Mj7GJmZ/lclbNrU+wXX4bCx+aNBA+933aOj/R9TKaWUUqoiSU62Cc+LLrKrha69Fho3tsmw/K66Crp0sTe316yx+21Wq+Y5EzAhwd4Yv/NOu/d8166wcqXdFim/5cuhYUO79dKDD9rk48CBnrOZROD22+2X+Pvvt+du3Nj7PvneKilHREClSp7t8+fbz7gOh31kZtq2/LZvt0m9Rx6xq5heeME+93ZTfvp0O+Hg1FOhZUub0D1wwL2CPdjk0rff2sfjj9utqLZts9tSBUOTJvbvefJkeOIJG/eyZbaafX4vvGD3+7/3Xvto1coz4VtUF10E//5rZ90+8YTdE/X553WJfVnRpo29URAXZ5NS4eE2yf3AA74LlVVEp5xir2+571NEhH2fHnvMXsNKi8hI+PBDG2duQjd3pnn+BOmzz9pkZH733QdVqgQ8VD7/3DMRGhZm95VWAaNV7FX54XDYim5btrh/iI2Ls3fiXS/OP/4I55/vPuMzOtpuevzll/6df906+yHRdczwcPvh6u+//fsgdOgQnHACHDmS12aM/aC+bZv9h0cFlVYTVUqpotNrpwqpu++21dRdZ42Ghdmk5q+/5rXt2GFvTnszfLidjVhUIjaR8u+/7u1xcXZm50035bXNnQvnnuu+XRTY2U27d9sv8rmqV3evVp+rbVubqM01b57vauk//2z3us/Vvr2tbJ9f1arez1WerFtnX3/+4isxMbYOgK/JFQGm185SYvlyO2s7LAyGDoV27UIdUem0bJndtzUy0r5PbdqEOiLvdu60W/Dt2WO///fv731WpsNhVxHMmGGvg489Zm+gBcv+/bb41fLl9vo0bpz3pK3y4O+108fmNUqVQWFh9oPlJZfYi0h4uL2Qvfuu552rM8+EV16xd+lzcuwd/LPP9u+Db66WLe1yoREj8vY3TUqyFfH8vUtcpYr98Dp0aF6lxKZN7UVak6NKKaWUUsf3/vueS+odDrs905EjebMu77nH9xiffOLf58R162xyM7/UVHjrLfcE6Xvved+uKTzcbg+Qu2fooUO+E5b5qy772iIAYPx49wSpa2LV1X//2fNVq+Z7rLLu00+97xcKtsjMrbcGN54QMcYMACYA4cCbIjI23/HLgP9zPk0GRovIX85jm4GjQA6QXa4Su6ecYh+qYB06BG92eHHUq1fw9T5XWJi9wXb33YGPyZsaNWDKlNCcu4LSBKkqXxo2tPtG7dxpP2A2a+Y7OTliBFx+ub2jX6OGvRNfXAMG2ETmxo32Lr+vvUOK4rTT7IfrzZvtB+QQ3cFWSimllCqTCqr467qarqCVdf6uuhPx/Vk0f1wOh/fziPh/fl9Jv9xxC6u8V00OxHtfxhhjwoFXgX7AdmCJMeYLEfnHpdsmoKeI/GeMGQhMBk53Od5bRPYHLWillCpBugepKp/q1bPLmY43czMy0i6BL4nkaK6wMLvUvySSo7mMsXsoaXJUKaWUUqpohg3zLCBijJ3pVLlyXtvYsfh0/vn+nbtVK3sjPr+4OHuz3tVll9nl9Pnl5NhloLmqVPG9B16rVu7Pb7/dd2z5j7Vu7b1f5creX0N5cuGF3ovxGAODBgU9nBDpBGwQkX9FJBP4CHCrRCQiC0TkP+fTRYCPPSmUUqrs0QRpqO3fbzfsHjDAfkjJvz+RUkoppZRSJeHIEbtM/KWXYPXqUEcTPI89Zm+c5+7hmZBgE37vvuver1EjuPhiz99PSIBp0/w7tzF2a6TExLxintHRthDT6NHufc880yZJcwudREfbLZXeftuz+JK3Ah6RkfDVV55jnnOOZ1xnneWedAX7u1FRnvEHsyhIaqotovLii/DHH8E7b+vWtvBObKz9e4qMtPuPPvVU6SoyE1j1gW0uz7c723y5BvjW5bkAc4wxy4wxo3z9kjFmlDFmqTFm6b59+4oVsFJKlaRSs8TeOaV/KbBDRM7Nd6wX8Dl2Sj/ApyLyeFADDIStW+2d6+RkuyH4Tz/BG2/YPYa6dg11dEoppZRSqrz45RdbAAjsPun33msrtr/6avmvqF25st2f/uuvbRGRpk3tnvXeZmtef71NFGZk2KXV4eEwcqT3Ah6FtWOH3Z8+V0aGTVDnX7ptDLz+OowaZWNITIQhQ7wXjurRA/bts9Xu//nHVmF+9FGbXM3P296h3lZPNWli9xt99FG7ZdWJJ9oEYbBmjy5fDn362NoAWVn2vR840FaeDw8P/PkfeMBWnf/0U/v3fdFFdlVYxeHtQuB1fwFjTG9sgrS7S3M3EdlpjKkFfG+MWSMi8zwGFJmMXZpPUlJSxdi/QClVJpSaKvbGmDuAJKCSjwTpXfnbC1ImKuL973/2H/z8e/q0bm0/6CilSh2tJqqUUkWn184Qy8yE2rVtcR9X8fH2s6i3GYYVUXq6fZ+OHHFvj4+3hXryz7gsDIfDzkTMyvI8dumldrZkIC1c6Hvixa+/Qvfu3o8Fm4hNXG/e7N4eH29nPF99dUjCCrVgXjuNMV2AR0XkLOfz+wBE5Ol8/U4GPgMGisg6H2M9CiSLyLMFnbPUXzuVUmWSv9fOUrHE3hjTADgHeDPUsQTV7NneNzzfsMHzA6xSSimllFL+mD/f+2fOlBS7fFtZc+d6b09JgXfe8W/MH3/0nhwFu0w+0MaN8+9YsP3zj50Rm19Kil1hp4JhCdDCGNPEGBMFXAp84drBGNMI+BS4wjU5aoyJN8Yk5v4M9Af+DlrkSilVAkpFghR4EbgHKKg8YhdjzF/GmG+NMW28dShz+5nk7oOUnzGeG8krpZRSSinlj+xs38d8Je8qooLep8xM/8bMyPB9LBiV4Qv6+y3o9QZbdrbvrR70v9GgEJFs4CbgO2A1MENEVhljrjfGXO/s9jBQHZhojFlujMmd/lkbmG+M+Qv4HfhaRGYH+SWoQJg5E9q1g6pVoXdvWLzYe7/ly+3WHcbYR506sGmT97433WS3zTDGbmdxxRXe+23caPMiuWOGhdl9tL356y+7t3LVqnZ7kHff9dzGBOz2hjExeWMaY7c18ebjj+0s9tx+rVtDWpr3vrfemhdr1aqee1znWr8e2rSxryU8HLp0gYMHvff98Ue7fUrVqnbf6jlzvPcrij/+sKshqlaFk06CDz4o/pj//mu3ralWzW7VMmFCcP59CwQRCekDOBeY6Py5F/CVlz6VgATnz2cD6483bocOHaTUGztWJC5OxP6vax9RUSKXXBLqyJRSPgBLJcTXzUA+ysS1UylV5ui1M8RSU0USEtw/c4JIfLzIjBmhjq70SE72/Gye+z7NmuXfmDk5ImFhnmOCyDnnlGz83sye7f3cIPLNN4E/f2Hl5IjUqeMZY1ycyEsvhTq6kNFrpwqp11/3vCbGxYksXuze79Ah79eYsDCR7Gz3viNHeu977rme5/d17fr9d/d+q1bZ63T+OJ9+uvBjPvSQe79ffvHeLzHRc8yLLvLe9/333fsdPiwSEeF9zJwc977ffCMSG+v5mr74wvP8hfXXX55/n/HxIs895/+YO3eKVK3q/u9cXJzIqFH+j1kC/L12loYZpN2A840xm4GPgD7GmPddO4jIERFJdv78DRBpjAnSbuEBdOedMGiQvYNRqZLdVD0pSZeRKKWUUkqpkhMbC++/b//MrVIeH29n21x0UWhjK03i4+3spPzv07nnwnnn+TdmWBhMnOjZnpBg/04C7ayzoG9fz/ZevWwBpNIiLMzuhxsfb78bgX2POnTwPbtr0yZb1OqDD9yLYCmlii8nB+67D1JT3dtTU21xOFeXX+59DIcDbrzRve2tt7z3/eor9+eXXOI7tgED3J8/+qjnzM7UVHjySff2l1/2PeYTT7g/9zWr9ehR+MJl54nUVDvL1pvbbnN/ft993mfuHz0Kkya5t915p/fXdOed3s9VGA895DlmSop9//xdJTFhgh3DdcZoaqr9t3T3br9DDZWQV7EXkfuA+8CtGJPb/2HGmDrAHhERY0wn7NYAB4IcasmLiIBp0+w/7itX2unI7dqFOiqllFJKKVXeXHABrFtnP3v+959NjvXoUf4r2BfVxRdDp0426XbokC1g1b178d6nHj3s0sPDh23SISLCJi4TE0ss7AJ9/70tMvXii3Z+z623ls7EeI8edqnmtGmwa5dN4g4YYJOn+T3wADz/vD0WFgbXXQdffml/RylVfPv3eyZHcy1f7v582TLf48yf7/5cCigSfvgwVK5sf/7lF9/9/vvP/fnvv3tf0h0WBlu3QqtW9vnkyb7HzG/nTt/Hpk+H88+3P69c6btf/qXzv/3mu+9PP8ENN+Q9X+e1/pmtVyPi379JS5d6f/8dDtixw+ajimr+fO/J1ZgY+Ptvu9VCGRLyBKkvufuciMgk4GJgtDEmG0gDLnVOmy0fmjTx7z9GpZRSSimlCqtBA/i//wt1FKVfo0Zw770lM5YIDB5sv9Dnfn3JzoZvv4UpU+Daa0vmPMdz4YX2UdrVqgW3315wn7lzbbI3Pd29fdAg2LNHazkoVRKqVvV+cwLsNdLVCSfYmxreNG9e+HO61mhp1sx74TbIm2Xueo4tWzz7ZWW5J+j69rVJu8KoVMn33qDdu+f93KKF7zFiY92ft2hh90r15qST3J/Xru09SVurlv837Jo29T5mTg7UrOnfmK1bw6JFdgxXmZnQuLF/Y4ZQaVhif4yIzBWRc50/T3ImRxGRV0SkjYi0F5HOIrIgtJGqErN6tZ36/uSTsGaN735ZWfDJJ/Dgg7aKqK+7WUXhcMB339mp5hMn+r4AKqWUUkop5Y+NG+0MpvxzO1JT7fJwVXRTpngvlCJiZ2EppYovKsouj4+Lc2+Pi7NLsl35Kpzk7Vi3bt77nXiiLVqU68cffY+Zf0vCBx/0jDM2Fi67LG9GKsALL/gec9Ag9+fPPee9X3g4jB6d97xaNWjf3nvfe+5xf/7ss96Tm+HhntsWeHtN8fGe/Yri4Ye9/32OGOG7gPjx3HGH502p6Gjo2rVoyfFSolQlSFUF8/TTdl+hRx+Fxx6D006DceM8+x04YCu9jRgBY8bYqndNmtglOP7KyICePe0yqiefhLvusnc4fFXlU0oppZRSqqgyM33P9imowr3yLTPT9zJdf/fRU0p5evppuPlmm5iLjrazDF99NW95ea6WLe3yddcZp+Hh8Omn7glKsEvn828r2LSp58zOuDjv+5UOG2YTn6569bKJ2Hr1bGI3Nhauucb7/s/eEq8tW9ptSFxddRXccot7W2ys5/YCYGdQduiQ9zwsDK6/3iYkXZ1wAsya5Z6krFrV/n7+WbHXXw+PPw5Vqtj3vnJlO97NN3uev7D69YM337SzaqOjbRyjRtl9RP3VurXdP7Z5c/veR0fbLVzyv59lhClPK9VdJSUlydKlS0MdhvJl3To45RTPu78xMXYfD9e7DaNG2VmjWVl5bWFhdp+in3/27/zPP2/vyuQ/f6NGsHmz7selfDLGLBORpFDHESh67VRKBYJeO1WF5XBAw4aeyxpjY+GRR3TLA3/MmmWLwqSkuLfHxtqiIJUqhSSsQNBrpyoVsrLgyJGCl93nWr3aJsqaNSu4X1qaXW7etu3xZy8uWWL3hO7Xr+B+InZVaGJiXqE9X1assOcfOrTgvjk5Nj9RsybUr1/wmMnJdi/PZs3sXtMF2brVnvd4e3Tm5NgtWqpUOf6YheVw2DEL8z4VlogdMy7OM9kbAv5eO3UGqQqNWbO8V3BzOOwxVx9/7J4cze03f77n3kOFNXWq96U5Bw7A2rX+jamUUkoppcq+tDRblfidd2DbtuKNFRYGH37oWZ39pJOKNxOoIjv/fFvkKj7ePo+MtMnRyZPLVXJUqVIjMhKqVz9+chTsjMLjJUfB/j/buXPhlnZ37Hj85CjYSU7Vqxcu6XfyybZS/fH6hofbiV3HS46CfS2tWhUukdmoUeEKGIWHQ40aJZccBfv3WNj3qbCMsdsNlILkaHGU2iJNqpwLC/M+S9MYzwtvQRdif2d6+hpTpHAXfqWUUkopVf4sXAgDB9qb8Q6Hnb1zzz12Oyh/9egB69fbG/Tbt0Pv3nDBBSX7hbciCQuztQl++gm++MIuPR0+vEzud6eUUqr00H+VVWgMHmyLI+VnjN2zwtX//mc3Ynbdpyk8HM480/8qlSNH2g+7+Ys91a1bcCU6pZRSSilVPmVmwjnnwOHD7u3PPms/d/bo4f/YdevCvfcWLz6Vxxj7d3LmmaGORCmlVDmhU+VUaDRtCuPH2ynYro/nn7ebF7saM8ZWtUtIsHfaExPtBsxvvun/+UeNskWa4uPtkoGEBLuvx8yZuv+oUkoppVRFNHeunTGaX1pa8T53KqWUUqrU0xmkKnRuusnuITRrlk1KDhpkN7HPr1Il+OMP+P57u5Fy8+Zw3nk2semvyEj4+mtbMW7+fHtX/8IL8/YyUkoppZRSFYuvve1FPAsCKaWUUqpc0QSpCq1GjeCWW47fLyzMbsZ+1lkld25joEsX+1BKKaWUUhVbr16ehUHB3kC/9NKgh6OUUkqp4NEl9qrsWLEC3nvPbp4vEupolFJKKaVUeVKpEkycaKsr5xZQSkiwe48OHhza2IJp5Ur9zK2UKrotW+D2222tj927C+7755/2OvP77+XjOiMC8+bB++/DmjWhjqZkZGXBt9/CBx/YAoMVgM4gVaVfRoat9Pnrr3YmqQi0bAk//ADVqoU6OqWUUkqVM8aYKcC5wF4RaRvqeFQQXXUVnH46vP02HDpkt4AaMMB+Bi3vMjLs6503L+/1NmsGP/4I1auHNDSlVCl3ww3w2mt5z8ePh0cegUcfde+XmgrnnguLF+d9t2/TBubMgcqVgxpyidm1C3r3hh077POcHFvw78MP8262lTUrVkC/fnbrGYcDsrPhjjtsfZhyrAL8S6/KvMceg19+sRfT5GS7B9Tff8N114U6MqWUUkqVT+8AA0IdhAqR1q3hmWdg8mQ4++yKkRwFeOIJW6gq9zN3cjL88w9ce22oI1NKlWbz57snR3M99hisX+/e9sADdna663f75cvh5puDEmpAXHYZbNyYd91MS4NvvoEJE0IdmX8cDvtv3969cOSIfU3p6fb1fPddqKMLqAryr70q095803PT/Kws+PxzyMwMTUxKKaWUKrdEZB5wMNRxKBVUvj5zf/WVnV2qlFLePPGE72NPPun+/O23Pa8zmZkwfbpNzJU1//0Hv/1mZ1i6Sk2FSZNCE1Nx/f47HD7s2Z6S4j0RXo5oglSVfr4+kOVO9VZKKaWUCjJjzChjzFJjzNJ9+/aFOhylii9/0iKXiPfiVUopBTZx5svRo+7PfX23z84umwnS9HRb/Nmb1NTgxlJSUlN9r5zI//dZzmiCVJV+Z58N4eGe7aeeCnFxwY9HKaWUUhWeiEwWkSQRSapZs2aow1Gq+M45x/tn7nbtbLEqpZTy5qqrfB/Lvy1e//6eyTdjoFu3srlfZ5060LChZ3tkJFx4YfDjKQmdO9t9VPOLi4OhQ4MfTxBpglSVfuPH243hc5OhMTG2yuibb4Y2LqWUUkoppcqLZ56BGjXcP3MnJsKUKaGNSylVul19NbRo4dneuTOcdZZ724QJttBybKx9Hhtrv9uX1eXoxsC779qbSNHRti0+HurV8yxQVVbExcHrr9u/m9ykdXw8tG8PV14Z2tgCrAym6FWF06ABrFsH77xjN3Ru29ZuFl+7dqgjU0oppZRSqnyoXx/WroWpU+1n7pNOsp+569QJdWRKqdIsLAzWrLE3Wd56yybVbr7ZVrbPr3Fj+91+yhRYssQm3UaOhLK8EqNLF1i92hb2W7cOevaEK64o2zPvL7vMrth9803YswfOPx8GD7YzY8sxIyKhjiEgkpKSZOnSpaEOQylVzhhjlolIUqjjCBS9diqlAqGsXTuNMR/y/+zdd3xUVfrH8c+TZFIhdJAOKqCAgohUFQQrYu9lbSusrr3r7tp1bb91xYq9rF1ERcWKBStSBEQBRYr0XkJ6Zs7vjzvBJDOBmWSSyYTv+/WaVzLnnrn3uVw43HnuKTAUaA6sBm52zj1dWX21nSJSExKt7YyW2k4RqQlVbTvVg1REREREpAzn3GnxjkFEREREao/mIBUREREREREREZGdlhKkIiIiIiIiIiIistNSglRERERERERERER2WkqQiogkKDM73Mzmm9kCM7s+zHYzsweD22ebWZ94xCkiIiIiIiJSlylBKiKSgMwsGXgEOALoDpxmZt0rVDsC6BJ8jQYeq9UgRURERERERBKAEqQiIompH7DAObfQOVcEvAocU6HOMcALzvM90NjMWtd2oCIiIiIiIiJ1WUq8A6gp06dPX2dmS6L8WHNgXU3EE0c6p7qvvp0P1O9z6hjvQILaAkvLvF8G9I+gTltgZdlKZjYar4cpQKGZzYltqHVKffy7WVZ9Pr/6fG5Q/8+vW7wDqElVuO+sj9db55QYdE6Joa7dd9YItZ2AzilR6JzqvrLnU6W2s94mSJ1zLaL9jJlNc871rYl44kXnVPfVt/MBnVMtsTBlrgp1cM49ATwBdfI8Y0rnl7jq87nBznF+8Y6hJkV731kfr7fOKTHonBJDfTyncNR26pwShc6p7ovF+WiIvYhIYloGtC/zvh2wogp1RERERERERHZqSpCKiCSmqUAXM+tsZqnAqcCECnUmAGcFV7MfAGx2zq2suCMRERERERGRnVm9HWJfRU/EO4AaoHOq++rb+YDOqcY550rM7GLgIyAZeMY597OZXRDcPhaYCIwAFgB5wLkR7LpOnWcN0Pklrvp8bqDz29nUxz8PnVNi0Dklhvp4TrFQH/9cdE6JQedU91X7fMy5kOnoRERERERERERERHYKGmIvIiIiIiIiIiIiOy0lSEVERERERERERGSntdMlSM2svZl9bmZzzexnM7ssTB0zswfNbIGZzTazPvGINRIRns9QM9tsZjODr5viEWukzCzdzH4ws1nBc7o1TJ2EuUYQ8Tkl1HUqZWbJZvajmb0XZltCXadSOzinhLxOFZnZ4WY2P3htrg+zPSGvHUR0bmcEz2m2mX1rZr3iEWdV7ej8ytTbz8z8ZnZibcZXXZGcX/Df4cxge/plbcdYHRH8/WxkZu+W+f8ikrmD6wQze8bM1pjZnEq2J2y7UhX17Z4TdN+ZQNepXt536p6z7l+jWFDbmRjXW21nYlwnUNsZ1TVyzu1UL6A10Cf4e0PgV6B7hTojgA8AAwYAU+IddzXPZyjwXrxjjeKcDGgQ/N0HTAEGJOo1iuKcEuo6lYn7SuDlcLEn2nWK8JwS8jpVOIdk4HdgVyAVmJXI7WAVzm0Q0CT4+xGJcm6Rnl+Zep/hLdR1YrzjjvH1awz8AnQIvm8Z77hjfH7/AO4J/t4C2ACkxjv2CM/vQKAPMKeS7QnZrlTjz6Ne3XNGcU4J9f8kuu+Me7xRnJfuOXeCl9rO+Mcb4Tmp7UyQl9rOyF87XQ9S59xK59yM4O85wFygbYVqxwAvOM/3QGMza13LoUYkwvNJKME/963Bt77gq+JqYglzjSDic0o4ZtYOOBJ4qpIqCXWdIKJzqg/6AQuccwudc0XAq3jXqqyEu3ZBOzw359y3zrmNwbffA+1qOcbqiOTaAVwCvAmsqc3gYiCS8zsdGO+c+wPAOZdI5xjJ+TmgoZkZ0AAvQVpSu2FWjXNuMl68lUnUdqVK6ts9J+i+M4GuU72779Q9585DbWdiUNuZGNR2RmenS5CWZWadgH3wngyU1RZYWub9MhKgAdvO+QAMDHYV/8DMetRuZNELdpmeiffl/hPnXMJfowjOCRLsOgEPANcCgUq2J9x1YsfnBIl3nSqK5Lok4rWD6OP+K95T00Sxw/Mzs7bAccDYWowrViK5fl2BJmb2hZlNN7Ozai266ovk/B4G9gRWAD8BlznnttceJZJEbVeqrb7dc4LuO6nj16ke3nc+gO456/o1ijm1nXWb2s6EuE4PoLYz4mu00yZIzawBXu+ay51zWypuDvOROv3kYAfnMwPo6JzrBTwEvF3L4UXNOed3zvXG69nVz8x6VqiScNcognNKqOtkZiOBNc656durFqaszl6nCM8poa5TJSK5Lgl17cqIOG4zOwgvQXpdjUYUW5Gc3wPAdc45f82HE3ORnF8KsC/ek+PDgBvNrGtNBxYjkZzfYcBMoA3QG3jYzLJrNqxak6jtSrXUt3tO0H1n6cdqPLBqqE/3nbrnrPvXqCao7az711ttZ92+Tmo7o79GO2WC1Mx8eA3TS8658WGqLAPal3nfDq8nR520o/Nxzm0p7SrunJsI+MyseS2HWSXOuU3AF8DhFTYl1DUqq7JzSsDrNBg42swW4w0THWZmL1aok2jXaYfnlIDXKZxIrkuiXbtSEcVtZnvjDcs4xjm3vpZii4VIzq8v8Grw7/GJwKNmdmytRFd9kf7d/NA5l+ucWwdMBhJloa1Izu9cvCkEnHNuAbAI2KOW4qtpidquVFl9u+cE3XcG1fnrVKqe3HfqnrPuX6OYUtuZWNdbbWedvU5qO6O8RjtdgjQ4p9fTwFzn3P2VVJsAnGWeAcBm59zKWgsyCpGcj5ntEqyHmfXDu+51NiFgZi3MrHHw9wzgYGBehWoJc40gsnNKtOvknLvBOdfOOdcJOBX4zDl3ZoVqCXWdIjmnRLtOlZgKdDGzzmaWineuEyrUSahrV8YOz83MOgDjgb84536NQ4zVscPzc851ds51Cv49Hgf83Tn3dq1HWjWR/N18BzjAzFLMLBPojzeXVyKI5Pz+AIYDmFkroBuwsFajrDmJ2q5USX275wTddybQdapX952656z71yiW1HYmxvVW21n3r5PazuivUUqM400Eg4G/AD+ZN7cEeCvGdgBwzo3FW/V3BLAAyMPrzVFXRXI+JwIXmlkJkA+c6pyrs92m8Vb5e97MkvH+Mr/unHvPzC6AhLxGENk5Jdp1CivBr1NY9e06OedKzOxi4CO8VbWfcc79XB+uXYTndhPQDK9nJUCJc65vvGKORoTnl7AiOT/n3Fwz+xCYjTf30FPOuTnxizpyEV6/24HnzOwnvGFP1wV7ytZ5ZvYK3sqhzc1sGXAz3gIHCd2uVEN9u+cE3XcmynXaKe47E/wahVXfrlEVqe1MjOuttjMxrlOIBL9GYcXqGlmCXUsRERERERERERGRmNnphtiLiIiIiIiIiIiIlFKCVERERERERERERHZaSpCKiIiIiIiIiIjITksJUhEREREREREREdlpKUEqIiIiIiIiIiIiOy0lSKVeM7NzzMyZ2XNxOHan4LEXV+GzzsxcFT4Xt/MVERER2ZnpvlNEJHpqO6WuUIJUJIGY2eJgY9op3rGIiIiISP2l+04Rkeip7UxcKfEOQKQeWw7sCRTHOxARERERqdd03ykiEj21nbKNEqQiNcQ5VwzMi3ccIiIiIlK/6b5TRCR6ajulLA2xl3LMrJuZPW9mS8ysyMxygl3E3zKzEyr5TH8ze9XMlgU/s9bMJpjZ/pXU3zZXh5mNNrMfzSzPzNab2Xgz67md49xnZtPMbHXwWCvMbJyZDYjR+e8bjG9KmG33B7cVm1nDCttGBLe9U6Zsu/OZmNlewT/XDWaWa2YzzOz8SuqeE/wz6xgsWlT651hZ930zaxj881pkZoVmttzMHjOzppH/iYiIiIjUDN136r5TRKKntlNtp9QM9SCVbcxsL+AboCHeU5R3AQe0BQ4DMoA3K3zmKuC+4NsZwHdAO+BI4Egzu8A592Qlx/svcCnwFfAO0Ac4DjjMzA5zzn1d4SN3AkOBn4EfgEKgG3ACcKyZneace6Oq5x/0I7AB2NfMGjvnNpXZNjz4MyUYx7thtk2K5CBmNgT4AO/PdH7wuK2Bx82se5iPLACeB04EsvCuw9Yy27dWqN8I71q2BSYDc4D9gQuAfmY2IPi0TERERKTW6b4T0H2niERJbSegtlNqinNOL71wzgE8g9e43hBmWwNgYIWyw4P1lwP9K2wbDGwGioCuFba54CsXOLBMuQF3Bbf9AaSHOV6rMLEdFTzOeiCzwrZzgvt7Loo/h3HBzxxbpqwFEABmB7eNqfCZmcHyHmXKOgXLFleomwEsC277N2Bltg0J/rk4759nSGyLg9s6VRJ76fk64H2gQZltbYJ/rg44I95/3/TSSy+99NJLr533pfvObZ/RfadeeukV8Utt57bPqO3UK+YvDbGXsloFf35QcYNzbqtz7rsKxbcGf57vnJtSof43wO2AD/hbJcd7zDk3ucxnHPAvYCHQHu8pU9l9fuicWx0mtneBN4CmwEGVHCsapU+UDi5TNgzvP4OHgZVlt5lZc2BvYJVz7ucI9n8i3lOi34Ebg+cNgHPuS2BstaL3bAX+6pzb9pTKObciGD/8+fRMREREJB503+nRfaeIRENtp0dtp8ScEqRS1g/Bn2PN7BAzS6usYrCB2Q/YAnxcSbUvgz8HVrL9xYoFzjk/8Erw7dBwxw3O7fF/ZvaUmT1nZs8BpXOgdK0s5ih8GvxZtkEaXmbbJKC7mbUOlpU2xBF11cd74gTwavB8K/pfFLFWZrpzblWY8tIJqNvE4BgiIiIiVaX7To/uO0UkGmo7PWo7JeY0B6mUdR9wAF7D8jFQaGYz8RrNF51zP5Wp2xmvgckGSsxse/ttUUn5okrKFwd/titbaGZ/A+4HMrdzrOztBRIJ59xvZrYU2MPM2jrnluP9mSx2zi00s0+BM/GeSP2PKOcy4c/z2tH5V8cflZRvCf5Mj8ExRERERKpK953ovlNEoqa2E7WdUjOUIJVtnHN5wMFm1h9v7pDBeE+S+gPXmtnNzrnbgtWTgz83A2/vYNfrqhpS6S9m1hd4DCgBrsGbbHkZkOecc2b2b+AGvP8AYmES3twgw81sMrAr8FSZbVD1xnZH3I6r7FAgBvsQERERqRG67yxH950iEhG1neWo7ZSYUoJUQgTnJpkCYGapwOnAk8AtZvaac24+sDRYvdg5d04VD9UJmFVJOcCKMmUn4jWkDzrn/i/MZ3avYgyV+RSvsT0Yb06W0jKcc8vMbD5eQ9wR2A34zTlX2ROgipYHf3aqZHvnqgQsIiIikmh03wnovlNEoqS2E1DbKTGmOUhlu5xzRc6554Dv8Rq7vYPly4GfgOZmNrSKuz+jYoGZJQOnBN9+UWZT0+DPpVRgZi2AQ6oYQ2VKnywND74c8FmF7W2BiyvUj0TpPC+nBs+3opA/lzKKgj/1cENERETqFd136r5TRKKntlNtp8SGEqSyjZn93cy6hSnfFegRfLukzKYbgz9fNLNDw3wu1cyONrPKJnz+u5ntX6a+4a2ytzveE5s3y9Qtnaj4LDNrUOYzDYFngMbbO7doBSdL/gVvYuTjgNnOubVlqpROCn1xhfeRGIe3qt7ueE/4tg0xCP55XLidz5Y+ydoziuOJiIiI1Cm67/yT7jtFJFJqO/+ktlNiTQlSKWs0MM/Mfjezd8zsJTObBMwFmuCt4Fa6ah7OuXeAq4BdgI/MbL6ZTTCzcWY2BVgDvAP0quR4TwJfmtnnZvZy8Dj/BPKBM5xz+WXqPov3JKoPsNDMxpvZW3iTI/fFa3BjrbQBTSf0adPngD+4LRB8H5HgvDFnAgXAv4BfzOxlM/sc70nVE9v5+FvBny8F/5yfCr6aRXp8ERERkTpA953l6b5TRCKhtrM8tZ0SM0qQSln/Ah7HWzVtEN4cIl3wGoCTCdON3Dl3P7Av8DTeJNCHAIfhNc5fAqOA1ys53pXAJXhd8Y8FWuJNHt3fOfdl2YrOuY14jeoTwFbgyOD78XgNcEg3/hgo28CWe9rknNsEzAi+nemc2xDNjp1znwEDgAl4/1kdi/dndpFz7srtfPRhvKeAy4GRwF+Dr4bRHF9EREQkznTfWZ7uO0UkEmo7y1PbKTFjzsVi8S2RyJmZA3DOxWr1OhERERGRELrvFBGJntpO2RmpB6mIiIiIiIiIiIjstJQgFRERERERERERkZ2WEqQiIiIiIiIiIiKy09IcpCIiIiIiIiIiIrLTUg9SERERERERERER2WkpQSoiIiIiIiIiIiI7LSVIRUREREREREREZKelBKmIiIiIiIiIiIjstJQgFRERERERERERkZ2WEqQiIiIiIiIiIiKy01KCVERERERERERERHZaSpCKiIiIiIiIiIjITksJUhEREREREREREdlpKUEqIiIiIjsFM3vGzNaY2ZxKtpuZPWhmC8xstpn1qe0YRURERKT2KUEqIiIiIjuL54DDt7P9CKBL8DUaeKwWYhIRERGROFOCVEQkQZlZspn9aGbvhdk21Mw2m9nM4OumeMQoIlKXOOcmAxu2U+UY4AXn+R5obGatayc6EREREYmXlHgHUFOaN2/uOnXqFO8wRKSemT59+jrnXIt4xxF0GTAXyK5k+1fOuZHR7FBtp4jUhDrWdm5PW2BpmffLgmUrK1Y0s9F4vUzJysrad4899qiVAEVk55FAbWeV6L5TRGpCVdvOepsg7dSpE9OmTYt3GCJSz5jZknjHAGBm7YAjgTuBK2O1X7WdIlIT6krbGQELU+bCVXTOPQE8AdC3b1+ntlNEYi2B2s4q0X2niNSEqradGmIvIpKYHgCuBQLbqTPQzGaZ2Qdm1qOySmY22symmdm0tWvXxjpOEZFEsgxoX+Z9O2BFnGIRERERkVqiBKmISIIxs5HAGufc9O1UmwF0dM71Ah4C3q6sonPuCedcX+dc3xYt6u0oLhGRSEwAzgquZj8A2OycCxleLyIiIiL1S70dYi8iUo8NBo42sxFAOpBtZi86584sreCc21Lm94lm9qiZNXfOrYtDvCIidYKZvQIMBZqb2TLgZsAH4JwbC0wERgALgDzg3PhEKiIiIiK1SQlSEZEE45y7AbgBvNXqgavLJkeD5bsAq51zzsz64Y0YWF/LoYqI1CnOudN2sN0BF9VSOCIiIiJSRyhBKiJST5jZBbCtF9SJwIVmVgLkA6cGv/iLiIiIiIiISBlKkIqIJDDn3BfAF8Hfx5Ypfxh4OD5RiYiIiIiIiCQOLdIkInXOxvyNfPDbB0xZNgV1epSwli+H99+Hn3+OdyQiIiIiIiIC/LT6J97/9X1W5iTeGpdx70FqZt2A18oU7Qrc5Jx7oEwdA8bgTZqfB5zjnJtRm3GKSO2475v7uOmLm0hNTiXgArTKasXHf/mYXZvsGu/QpC4IBOBvf4P//Q/S06G4GHr18pKlTZrEOzoREREREZGdzvq89Rzx0hH8vPZnfEk+CkoK+Os+f+XhEQ/jpfTqvrj3IHXOzXfO9XbO9Qb2xUuAvlWh2hFAl+BrNPBYrQYpIrVi0sJJ3PLlLRSUFLClcAtbi7ayaNMijnjpCPUkFc8jj8DLL0NhIWzeDHl5MH06nHdevCMTERERERHZKZ05/kxmrppJXnEemws3U+gv5LlZz/HUjKfiHVrE4p4grWA48LtzbkmF8mOAF5zne6CxmbWu/fBEpCY9/MPD5BXnlSsLuADLtyxn1upZcYpK6pQHH/SSomUVFcHEiZCTE5+YREREREREdlIb8jfw2eLPKA4UlyvPK85jzJQxcYoqenUtQXoq8EqY8rbA0jLvlwXLyjGz0WY2zcymrV27toZCFJGasjYv/L/b5KRkNhVsqt1gpG7avDl8uVlo4lRERERERERqVE5hDsmWHHZbIn2PrzMJUjNLBY4G3gi3OUxZyHhb59wTzrm+zrm+LVq0iHWIIlLDjt/zeDJSMkLKSwIl7NdmvzhEJHXOiBGQHOY/37ZtoWXL2o9HRERERERkJ9ahUQeaZjQNKU9JSuGorkfFIaKqqTMJUrx5Rmc451aH2bYMaF/mfTtgRa1EJSK15m/7/o3OTTqT6csEwDAyfZn897D/kpWaFefopE64/XZvMab0dO99SgpkZcFTT3m9SEVERERERKTWmBlPH/00mb7MbT1J01PSaZbRjJuG3BTn6CIX91XsyziN8MPrASYAF5vZq0B/YLNzbmWtRSYitSIrNYupo6by7I/P8va8t9mlwS5c3O9i+rfrH+/QpK5o3x7mzoXHHoMvv4SuXeGyy6Bbt3hHJiIiIiIislM6bPfDmDZqGmOmjGHBhgUc1OkgLtzvwrA9S+uqOpEgNbNM4BDgb2XKLgBwzo0FJgIjgAV4q9yfG4cwRaQWZPoyuajfRVzU76J4hyJ1VfPmcOON3ktEREREqs3MngFGAmuccz3DbL8GOCP4NgXYE2jhnNtgZouBHMAPlDjn+tZO1CJSl+zZYk/Gjhwb7zCqrE4kSJ1zeUCzCmVjy/zuAGVLRKRSa3LXkFuUS6fGnTANtRYRERERicZzwMPAC+E2OufuA+4DMLOjgCuccxvKVDnIObeupoMUEakpdSJBKiJSVStyVnDquFP5YfkPJFkSzTKb8fyxzzOs87B4hyYiIiIikhCcc5PNrFOE1bc3PZ6ISEKqS4s0iYhExTnH8BeG8+3Sbyn0F5Jfks+yLcs46pWjWLhxYbzDExERERGpV4LT4x0OvFmm2AEfm9l0Mxu9g8+PNrNpZjZt7dq1NRmqiEhUlCAVkYT17dJvWbZlGX7nL1de7C9m7LTEnftERERERKSOOgr4psLw+sHOuT7AEcBFZnZgZR92zj3hnOvrnOvbokWLmo5VRCRiSpCKSMJanrMcI3S+0eJAMb9v/D0OEYmIiIiI1GunUmF4vXNuRfDnGuAtoF8c4hIRqRYlSEUkYfVt05fiQHFIeaYvk2GdNAepiIiIiEismFkjYAjwTpmyLDNrWPo7cCgwJz4RiohUnRKkIpKwdm2yK6f0OIVMX+a2stTkVFpktuDs3mfHMTIRERERkcRhZq8A3wHdzGyZmf3VzC4wswvKVDsO+Ng5l1umrBXwtZnNAn4A3nfOfVh7kYtIolqbu5YbPr2B3mN7c8RLR/Dpwk/jGo9WsReRhPbMMc8woN0AHvnhEXKKcjhhzxP4xwH/oEFqg3iHJiIiIiKSEJxzp0VQ5znguQplC4FeNROViNRXa3PX0mtsLzbkb6DQX8is1bOYvGQy9xx8Dxf3uzguMSlBKiIJLcmSuKDvBVzQ94IdVxYRERERERGRuLr/u/u3JUdL5RXncd2n13HePueVGyVaWzTEXkRERERERERERGrFBws+KJccLZWSlMJPq3+KQ0RKkIpIgli+ZTnHvHoMqbenkn5HOme8eQbr8tbFOywRERERERERiUKbhm3Clhf7i2mZ1bKWo/EoQSoidV5+cT79nurH+7++T3GgmEJ/IW/88gb7P7M//oA/3uGJiIiIiIiISISuGnhVyDB6X5KPfVrvQ+cmneMSkxKkIlLnvf7z62wp3ILf/ZkMLQ4UsyJnBR///nEcI5MaNX8+/O1vcMABcN11sGJFvCMSERERERGRahq+63DuO/g+snxZZKdlk5GSQb+2/Xj7lLfjFpMWaRKROu/ntT+ztWhrSHmhv5C56+ZyRJcj4hCV1KivvoLDD4fCQvD74Ycf4IknvJ9dusQ7OhEREREREamGv/f7O+fscw5z1syhRWaLuPUcLaUepCJS5/Vs2ZMGqQ1CytOS09iz+Z5xiEhq3OjRkJfnJUcBiopgyxavJ6mIiIiIiIgkvExfJv3a9ot7chSUIBWRBHBS95NolNaIZEveVuZL8tE2uy2H7nZoHCOTGpGTAwsWhJYHAjBpUu3HIyIiIiIiIvWaEqQiUudl+DKYcv4Uju52NKlJqaQnp3Nyj5P5+tyvSU5K3vEOJLGkpUFSJf89ZWfXbiwiIiIiIiJS72kOUhFJCG2z2zL+lPHxDkNqQ2oqnHoqvPaaNwdpqcxMuPTS+MUlIiIiIiIi9ZJ6kIqISN3zyCMwZAhkZECjRpCeDqecAldeGe/IREREREREpJ5RglREqs05x/Mzn6fXY73o8N8OXPDeBazIWVGlfS3fspzR746m/X/b03tsb16Y9QLOuSrta8L8CfR/qj/t7m/HaeNO47f1v1VpPxIHDRrARx/B7Nnwxhvw++/wzDOQrCkVREREREREJLY0xF5Equ3qT67m8WmPk1ucC8DTPz7NW/Pe4ue//0zzzOYR72dN7hr2eXwfNhZspCRQwrIty/j7+3/n5zU/c88h90QV0yM/PMK1n15LXnEeAK//8joTF0xk+ujp7N5096j2JXG0++7eS0RERERERGKuoKSAWatm0Ti9Md2ad4voM/PWzWNL4RZ6tepFWkpazGMqCZQwc9VM0pLT6NmyJ2YW82NUpB6kIlIta3LX8OjUR7clR8FrzLYUbOHhHx6Oal8PTnmQLYVbKAmUbCvLLc7lwR8eZF3euoj3U1hSyA2TbtiWHAUIuAC5Rbnc+uWtUcUkIiIiIiIiUh+9MOsFWt7XkkNfPJR9Ht+H3mN7s3Tz0krrL960mJ6P9mTfJ/blkP8dQov7WvDqnFdjGtPHv39Mq/9rxbDnhzHw6YHs9uBuzFkzJ6bHCEcJUhGpllmrZpGWHPrEqMBfwGeLPotqX58v+pxCf2FIeVpyGrNXz454P4s3LcYROizf7/x8veTrqGKSOmraNBg7Fj78EPz+eEcjIiIiIiKSUKatmMaF711ITlEOWwq3kF+Sz5w1czjsxcPCTnPnnOPgFw5m7rq55BXnsaVwCzlFOfx1wl+ZtWpWTGJaunkpx712HBvyN5BTlENucS6LNi3ioOcPoshfFJNjVEYJUhGplnbZ7SgOFIeUJ1ty1EPZd22yK0kW2iwV+Ytol90u4v20zGpJsT80JoAOjTtEFZPUMYWFcOih3gJOV14JJ5/sDcFftizekYmIiIiIiCSMB6c8SIG/oFyZ3/lZumUpM1bOCKn//bLvWZ27moALlCsvLCnk0amPxiSmZ2c+W25EadljvP/r+zE5RmWUIBWRatmzxZ7s3WpvfEm+cuVpKWlcPuDyqPZ15cArSU9JL1eWmpTKvm32pWuzrhHvp0lGE47f8/iQfWX6MvnH/v+IKiapY+69F77+GvLyID8fcnJg6VL4y1/iHZmIiIiIiEjCWJ6zPCTZCV5npzW5a0LK1+SuCduhye/8LMuJTYeVFTkrwvYU9Qf8YWOKJSVIRaTa3j/9fQ7e9WDSktPI9GWyS4NdeO3E19i71d5R7Wef1vvwygmv0CqrFZm+TNKS0zh4t4OZcOqEqGN6+uinOX7P40lLTiPLl0Xj9MaMOXwMh+1+WNT7kjrkqae8xGhZfj98+y1s2hSXkERERERERBLNyC4jyUjJCCkv9BfSv13/kPIB7QZQVBKavMz0ZTKyy8iYxHTYbofRILVBSLnDMaTTkJgcozJaxV5Eqq1pRlMmnjGRDfkb2FywmY6NO4Z9shSJo7sdzciuI1myaQmN0hvRNKNplfaT4cvgpeNf4tERj7I+fz3ts9vjS/bt+INStxVtZ96Z4vDTKoiIiIiIiEh55/c5n0emPsLynOUUlHhD7bN8WVy///Vhv4e3atCKKwdeyZgpY7Yt0pyekk777Pac1eusmMR0VLej6NmyJ7NWzSK/JH9bTCf3OJk9mu8Rk2NURglSEYmZphlNq5zQLCvJkujcpHMMIoJG6Y1olN4oJvuSOuDEE+GJJ0ITpV27QosW8YlJREREREQkwTRMa8j00dN56IeHeGvuWzTLbMZl/S/jyK5HVvqZO4bdQf92/XlwyoNsKtjEid1P5KL9LiIrNSsmMaUkpfD52Z/zxPQneHH2i6SnpHNB3ws4redpMdn/9li4lanqg759+7pp06bFOwyRnca3S7/lkR8eYXXuao7b4zjO3edcMn2ZldZfvXU1j0x9hK/++Iruzbtz+YDL6dKsSy1GXDVmNt051zfecdSUOt92btgA/frBqlWQmwsZGeDzwZdfQu/e8Y5ORCqhtlNEJHpqO0VEolfVtlM9SEWk2h794VGu+fQa8ovzcTi+W/Ydj017jCnnTwn7JGnRxkX0fbIvuUW5FPoL+fqPr3lu1nN8eMaHHNDxgDicgSSMpk1hzhx44w345htvBftzzoHmzeMdmYiIiIiIiCQoLdIkItWSU5jD1Z9cTV5xHg6vR3pecR6LNi7imR+fCfuZ6z+9nk35myj0FwJQEighrziPUe+OqrW4JYGlp3ur1o8dC1dfreSoiIiIiIiIVIsSpCJSLVOWTwm7+FFeSR7j544P+5lPFn5CgEBI+cKNC9mYvzHmMYqIiIiIiIiIVEYJUhGplibpTQi40GQnQPOs8D37GqY1DFtuZmT4MmIWm9RB69fDmjXxjkJERERERERkGyVIRaRa+rTuQ6usViRZ+eYk05fJJf0uCfuZS/pdErKAU1pyGsftcRzpKek1Fmt9Y2bJZvajmb0XZpuZ2YNmtsDMZptZn3jEuM3ixTBoELRpAx06wF57wezZcQ1JREREREREBJQgFZFqMjM+OvMjOjfuTIPUBmSnZZOeks4dw+7gwI4Hhv3MFQOu4OTuJ5Oekk6jtEZk+jIZ2G4gTxz1RC1Hn/AuA+ZWsu0IoEvwNRp4rLaCClFcDIMHw5QpUFQEhYXeQksHHggbNaWCiNQeMzvczOYHHx5dH2Z7IzN718xmmdnPZnZuPOIUERERkdqlBKmIVNtuTXfjt0t+Y9JZk3jtxNdYedVKrhhwRaX1k5OSefbYZ/ntkt945YRXmD56Op+f8znZadm1GHViM7N2wJHAU5VUOQZ4wXm+BxqbWetaC7Cs99+HnBwIVJiKobgYXnopLiGJyM7HzJKBR/AeIHUHTjOz7hWqXQT84pzrBQwF/mNmqbUaqIhIHJjZM2a2xszmVLJ9qJltNrOZwddNZbZt9+GTiMTelGVTOHP8mRz8wsGM+X4MW4u2Vmk/M1fOpPFdjbFbDbvVGPb8MAACLsDrP7/OyJdHctQrR/HmL29um1rvy8Vfcsq4UzjkhUMYO20sBSUF2z3GjJUz6P9kf5rc04TeY3vzxeIvqhRrTUuJdwAiUj+YGf3a9ovqM+2y29Euu10NRVTvPQBcC4Sf0BXaAkvLvF8WLFtZsaKZjcbrZUqHDh1iGiQAS5Z4PUcrysuD33+P/fFERMLrByxwzi0EMLNX8R4m/VKmjgMampkBDYANQEltByoiEgfPAQ8DL2ynzlfOuZFlC8o8fDoE735zqplNcM79Em4HIlJ9T894mks/uJT8knwcjm+Xfsuj0x5l2qhpla73Ec6c1XPY54l9ypV9vvhzGt3ViMO7HM77v75PbnGuV77oc96Z/w49W/bk1i9vJa84D4Bvl33LkzOe5Jvzvgk7Xd4Hv33AiJdHbHu/qWATBz1/EM8c/Qzn7lO3BuqoB6mIxEWRv4hX57zKVR9fxePTHmdL4ZZ4h5QwzGwksMY5N3171cKUuXAVnXNPOOf6Ouf6tmjRIiYxlrPffpAS5nlcgwYwcGDsjyciEl5lD47KehjYE1gB/ARc5lz4lQjNbLSZTTOzaWvXrq2JeEVEao1zbjLeQ6FobXv45JwrAkofPolIDcgrzuOyDy8jryQPF/x6l1+Sz9LNSxk7bWxU+9r/2f3Dlm8p2sLbv7y9LTkKkFucy7hfxnHj5zduS46WxjN/3Xxe+emVsPs6++2zw5ZfNPGiqGKtDXUiQWpmjc1snJnNM7O5ZjawwvZKu/OLSOLZkL+Bno/2ZNS7o7j/u/u56uOr6DymM/PWzYt3aIliMHC0mS3GuwkdZmYvVqizDGhf5n07vC/8tW/gQC9Jml7miWJaGrRvD8cdF5eQRGSnFMmDo8OAmUAboDfwsJmFnf+lxh8uiYjUPQODczR/YGY9gmWRPHwSkRiZvmI6yUnJIeX5Jfm8OffNqPa1uXBzpduKXOgIwEJ/IRbmdiq3OLfSY6/NC/8QOb8kv8rTAtSUOpEgBcYAHzrn9gB6EX7Rka+cc72Dr9tqNzwRiaV/TvonSzYt2dYg5hbnsjF/I+e8fU58A0sQzrkbnHPtnHOdgFOBz5xzZ1aoNgE4K7ia/QBgs3MuZHh9rTCDDz6Af/wDOnaEtm3h4ovhu+/A54tLSCKyU4rkwdG5wPjg/M0LgEXAHrUUn4hIXTYD6Bico/kh4O1gecSjlkC970Wqq0lGE/wBf9htzTObx+w44RKhviRf2PIkS6JlVsuI91Mq3JD8eIp7gjT4VP5A4GkA51yRc25TXIMSkRo1bu44igLln0g5HDNWziCnMCdOUSU+M7vAzC4Ivp0ILAQWAE8Cf49bYOD1Hr3xRli8GJYtg//7P2jUKK4hichOZyrQxcw6BxdeOhXvYVJZfwDDAcysFdANry0VEdmpOee2OOe2Bn+fCPjMrDlRjlpS73uR6unRogcdG3ckycqn8zJ9mVzW/7Ko9jW88/BKt/kstCNLsiXTNKNpSNIzPTmdC/teGHY/B3Y8MGx5t2bdSEmqW8sixT1BCuwKrAWeNbMfzewpM8sKUy9cd/5y9DRKJDEkW+iQgFIVG3rZPufcF6WT5TvnxjrnxgZ/d865i5xzuznn9nLOTYtvpCIi8eWcKwEuBj7CG630unPu5woPl24HBpnZT8Ak4Drn3Lr4RCwiUneY2S7BBewws354uYT1RPbwSURixMyYePpEdm+6Ow1SG5Cdlk16Sjo3Hngjh+x2SFT7+vSsT8lODZ1J6KoBV/HuGe/SKK0R2WnZZKdl0zi9Me+d/h6Tzp5E+0btaZjakOy0bDJSMvjPYf9hv7b7hT3GxNMn0iG7/ELAzTKa8fV5X0cVa22oC+naFKAPcIlzboqZjQGuB24sU6e0O/9WMxuB152/S8UdOeeeAJ4A6Nu3b6Xd+kUkvs7udTYP/vAgBSUF28qSLZkDOx5IVmq45yMiIiLVF+z1NLFC2dgyv68ADq3tuERE4s3MXgGGAs3NbBlwM+CDbe3kicCFZlYC5AOnOuccUGJmpQ+fkoFnnHM/x+EURHYaHRt3ZN5F85i+cjrr89bTr20/mmQ0qdK+Nt+wmU9+/4TrPrmOttltGX/SeHzBadDWXLOGb/74BjNjcPvB+JK98kWXLeKH5T+wpXALA9sNpGFaw0r3n5mayZIrlvDNH98wadEkBrYbGHUit7aY16bFMQCzXYDvg3PpYWYHANc7547czmcWA32390S/b9++bto0dZgSqYtyi3I5+H8HM2fNHIr8RaQlp9EkownfnPcN7bLbxTu87TKz6c65vvGOo6bUetvpHLzxhjfkft06OOII+Ne/oHXr2B0jPx8eeACef96bD/W88+DSS72FokSkVqjtFBGJntpOEZHoVbXtjHsPUufcKjNbambdnHPz8eZ9+qVsnWASdbVzzlXozi8iCSgrNYtvz/uWyUsmM3PVTDo36cyILiPq3BwkUgtuuQX+8x/IzfXeP/mklzCdMwdahp/oOyqBAAwfDjNneolSgJtvhokT4bPPvISpiIiIiIiI7NTqSjbiEuCl4JwlC4FzS+eC2kF3fhFJUGbGkE5DGNJpSLxDkXjZuBHuvRcK/pxqgeJi2LIFxoyBO++s/jE++QR++unP5Ch4v0+dCpMnwxD9/RMREREREdnZ1YkEqXNuJlCx+2vZ+aAeBh6uzZhEJLzNBZv5YMEH+AN+Dt/9cJplNttu/YAL8MXiL1i8aTF9Wveh9y69aydQqft++skb5l42QQpQWAiTJsUmQfrdd7B1a2h5YaG3TQlSERERERGRnV6dSJCKSGIYP3c8Z44/c9tQ+OJAMWOPHMvZvc8OW3/V1lUMeW4IK3NWEnABHI4DOx7IO6e+Q2pyam2GLnVR69ZQVBRabgYdO8bmGG3bQmYm5OWVL09P97aJiIiIiIjITi8p3gGISGJYk7uGM8efSX5JPjlFOeQU5VBQUsAF71/A4k2Lw37mnLfPYeGGheQU5ZBbnEtecR5fLv6Se76+p3aDl7qpSxfYd18IrpK4TUYGXHVVbI5xyimh+wev7PjjY3MMERERERERSWhKkIpIRMbPHY+FWdAmEAjw+s+vh5TnFObw2aLPKHEl5crzS/J5csaTNRanJJh33oFhw7yh9llZ0KwZPPss9OsXm/1nZ8MXX0DXrl7iNSMD9tzTm380Kys2xxAREREREYlAkb+IX9f/yob8DbV+7E0Fm/jgtw9YsmlJRPWdcyzZtIQ/Nv8Rsm326tlMWjiJkkD57/sb8zcyf918CksKYxJzbdIQexGJSH5xfkjjB1DiSsgrzgspLw4Uh02oAhSUFIQtl51Q06bw4YewZo23aNNuu0FKjP9r6t0b5s2DxYu94fudOsV2/yIiIiIiIjvw5Iwnuebja/A7P8X+Yo7qehTPHfscWak133HjqJeP4r3f3tv2frcmuzFj9Ayy07PD1p+1ahanjDtlW3K0U+NOvH7S6wRcgAOePYAthVsASLIkbh16K1cPupq/Tvgrb/7yJr5kH4Zxx7A7uLT/pTV+brGiHqQiEpEjux5JkoU2Gekp6RzV9aiQ8qYZTenWrFtIuS/Jx3F7HlcjMUoCa9kSunWLfXK0lBl07qzkqIiIiIiI1LqPFnzE5R9ezubCzWwt2kqhv5B3f32Xs98Ov55HLF32wWXlkqMAv2/8nf5P9Q9bf0vhFoY8N4T56+eTX5JPfkk+89bNY8hzQ9jvif22JUfBW5T5xs9v5MiXj2T83PEU+gvZWrSVnKIcbph0A2/Pe7smTy2mlCAVkYh0bdaVqwZeRaYvkyRLwjCyfFmc2/tc9m2zb9jPPH/s82SnZZOekg5Ali+L1g1bc8dBd9Rm6CIiIiIiIiJxc/c3d4eMvCz0F/Ler++xLm9djR778emPhy2ft34eG/JCh/q//vPrIaNHHY7colyKAmEW2QU+X/R5yEjRvOI87vzqzipGXfuq1FXHzNoBbYD0yuo45yZXNSgRqZvuGHYHR3U9ihdnv4jf+Tm156kc0OGASuvv03offrvkN5758Rnmr5vPwPYDOWOvM2plCIHUYyUlMGECTJ3q9Qo99VRvrlHnvLlFP/7YG7p/+unQurX3mTlzYPx4ryfpiSd685Buz+bN8MorsGQJDBgARx5Zc71bRURERESkXlu6eWnY8tTkVFZvXU3zzOY1duwif/ikJsAfm/+gaWbTcmXLtywntzg3qv04XNjyFTkrIowy/qL6tmdmxwN3AbvvoKqLdt8ikhj6t+tP/3bhu+KH0zKrJdfvf30NRiQ7lS1bYNAgL3G5dau30NL118OXX8LNN3vJ0dxcb9GnG2+EceNg2jS4+24oKvISpP/+N9x6K1x7bfhjzJ4NBx4IxcWQlwcNGsDuu8NXX3m/i4iIiIiIRGFIxyEs3rQYv/OHbNu96Y5SbNXTMqslq3NXh5QbRs9WPUPKB7QbQIPUBmwt2lquPD0lnfyS/LDH8CX5KA4UlytLsiT277B/NSKvXREPsTezo4DXgS7AFmAmMLmS11exDlRE6r556+bx3dLvyC8O32hGKq84j++Wfsf8dfMjqu+cY86aOXy/7PvtPtWSeuC222DBAi85Cl4ydNMmOProP5OjAIWFkJ8PJ58Md93l/e73e71PCwq8ZOrCheGPcdppXg/SvOAQmK1bvUWe7rmnxk9PRERERETqnxuH3EiD1AYkW/K2skxfJncNv4u0lLQaPfbjI8MPsb+g7wWkJIX2bTxkt0Po2bInGSkZ28oyUjLo17Yf3Zt3D6mfbMncffDdZPoyy5Vl+bK4behtMTiD2hFNL89/AAb8C7jPOVe8g/oispNYtmUZI18eyW8bfiMlKYWACzDm8DGct895Ue/rielPcOVHV5KclExJoIRuzbrx7mnv0ja7bdj6v63/jZGvjGT5luUkJ3n/2Tx99NOc2P3Eap2T1FEvv+wlP8tyzutR6sIM6ygp8XqCVuQcvP02XHll+fIVK8InTgsK4MUX4fbbqxy6iIiIiIjsnDo17sSPf/uRW7+8lS8Xf0mbhm244YAbGNl1ZI0f+5g9juH909/nwvcuZHnOchqkNuCfB/yTawZfE7Z+kiXx2Vmf8d/v/8sLs17AMM7d51wu638ZviQfo98bzcs/vUxxoJi9W+7Nyye8TLfm3ejRogf//vrf/LHpDwa1H8TNQ2+ma7OuNX5+sWIu3BfKcBXNcoG5zrm+NRtSbPTt29dNmzYt3mGI1HvOOfZ6bC/mrZtXbrhApi+TSWdNYkC7ARHv6+s/vuawFw8rN3l1siXTo2UPZl0wK6S+P+Cn85jOLNuyrNycJ5kpmUwdPZXuLUKfblWXmU1PlHawKup829m+PSxbFlpuFj5BmpbmJUn9FYaypKd7w+4vu6x8+erV0LFjaBIWYLfdvN6rIhI1tZ0iItFT2ykiEr2qtp3RrGJfDEQ23lVEdhqzVs8KO5dKfnE+Y74fE9W+xnw/JmR4vt/5WbBhAXPWzAmpP3nJZDYVbAqZELrIX8Tj08IPI5AEd845XnKzrKQk6NrVm4+0ovR08PnC7+v440PLWrWCHj28hGtZGRlw7rlVCllERERERETqtmiG2E8Hdq2pQEQkMa3NXRt23hKHY8XW6FasW7F1RdjV73xJPtbmrg09dl5oGUCJK6lTq+WZWTJwMjAcaAOkV1LVOeeG11pgieiGG2DSJPjpJ6+XZ3q6lxidONFbfOmVV7weoz6fl+ScMAFmzfpzQabSnqZjxni9UcN55RU44ABv3tKCAkhNhX33hauvrr3zFBERERERkVoTTYL0buBDMzvEOfdJTQUkIollv7b7UegPHY6ckZLBUV2PimpfR3U9ih9X/hiyMl6Rv4h92+wbUn9w+8EU+0Pnl8zyZTGiy4iojl1TzKwJ8DHQB28e5+2JbM6TnVlmJnzzDXzxBUyfDp06eQs0pabCU0/BpZfCp59CkyZeD9FGjbwV6Y89Ft55x0uQHnsstA0/py3g9UZdssSrv2wZ7LeflzCt2KtUREREJA7MLBu4iMgevu9Wa4GJiCSwShOkZtahQtF84E5ggpk9CLwP/AEEwn3eOfdHrIIUkbqrcXpjbhl6C7d/eTu5xd4K4ukp6bRu2JrR+46Oal8X9r2QJ6Y/wcqclRT4CwBvLtPbht5Gdlp2SP222W25uP/FPDb1sW3HzkjJYNcmu3LaXqdV88xi5k5gX2Ap8DAwD9gS14gSnRkcdJD3qmjvvb1XRe3bw8UXR36M9HQ45ZSqxygiIiJSA8ysPfAV0B49fBcRiZnt9SBdTPgG1YCrg6/KuB3sW0TqkesGX0fvVr0ZM2UMa3PXcuwex3Jxv4vDJjW3p1F6I2b8bQYPTXmICfMn0DKrJZcPuJxDdjuk0s/ce/C9DG4/mEemPsKWgi2c3ONkLuh7AekplT1Ir3VHAxuB/s65VfEORkREREQS2r+BDsAM4B708F1EJCa2l8T8Az1xEpEIHbb7YRy2+2HV3k/j9MbcOORGbhxyY0T1zYxj9ziWY/c4ttrHriHNgY+UHI2x2bPhpZe8oe8jR8Y7GhEREZHaciiwCjjIOZcT72BEROqLShOkzrlOtRiHiCSQnMIcHC7iHqLF/mI2FWyiWWYzkiyphqOrc1YAJfEOot7w+73h8itXeu/vvdebf3TePOjcOb6xiYiIiNS8bGCikqMitWfaimk8PeNpcopyOLH7iRzV9SiSk5Jjtv/8onzOfOtMPvr9I1KTUrlswGXcPPRmAB6b+hhjpoyhOFDMub3P5R/7/4OkpCTem/8eN31xE+vz1zNi9xHcd+h9NEhtwMyVM7ni4yv4fcPv9GndhwePeJAOjTqQU5jD87Oe56slX9G1WVf+1vdvtMtuR7G/mDfnvsmE+RNontmc0fuOpmfLnjjnmLRoEi//9DKGcebeZzK001CsHq/LYM7Vz06iffv2ddOmTYt3GCL1ypJNSzjrrbP4btl3AOzbel+eP+55ujbrGra+P+Dnn5/9k4d+eAh/wE/DtIbce/C9nLvPubUZdkyZ2XTnXN8o6t8HnAN0cM7l76B63NX5tnPwYPj229DyrCzYurX24xGRiETbdiaaOt92ikhCCtd2mtlc4Dfn3NFxCitm1HZKIrj/u/u58fMbKSgpIOACZPmyGNppKBNOmxCTzj/5Rfk0uqcRxYHyiw/v1WIvstOz+WbpN+XKOzXuxDFdj2HMD2PKlWekZDDmsDGMfr/8OiBJJPH+6e8z6r1RbMjfQF5xHmnJafiSfUw8fSLXT7qeWatmkVucS7Ilk5qcytiRY/l26be8OPtFcotzMYxMXybn9zmfBw5/oNrnXNOqet8ZcYLUzJ4BvnbOPbODeucABzrnzos2mFhSYysSW0X+IjqP6cyqrasIOG9tNsNomtGUxZcvpkFqg5DPXPfJdTw89WHyivO2lWX6Mnn5+Jc5Zo9jai32WKpCgrQB8A2wBDjfObemxoKLgTrfdm7vieXChepFKlJHKUEqIhK9ShKk/wSuBXZ1zq2PT2SxobZT6ro1uWvo+EBHCkoKypU38DXgpRNe4uhu1X9OccJrJzB+3vhq7we8ZGggzDrqDVIbUFhSGJKE3aXBLuQU5mxb8LhUeko6hpFfUr5/T0ZKBlNHTaVHyx4xibemVPW+M5p09znA/hHUGwycHW0gIlK3TZg/gZzCnG3JUQCHo6CkgNd/fj2kfpG/KCQ5CpBXnMfNX9xc4/HGi5k9U/YFPAj8DowEfjOzz8zsuYr1gq+n4xt9gluwIN4RiIiIiNS0e4AfgIlm1j3ewYjUZ5MWTsKX5Asp31q8lXG/jIvJMT76/aOY7AcImxwF2Fq0NSQ5CrB66+qQ5CiAc47CksKQ8pJACRN/m1j9QOuomlhp3geVXBURSVgLNy4MeYIEkFucy8KNC0PKNxVsKpdMLWvplqUxj68OOWc72xoCQ7ez3QF/jWUw9U5GBuRXMlPBsGG1G4uIiIhIDTOzz8IU+4D9gNlm9gfeAsvhbrydc254TcYnUp9lpWZhhI5gS7ZkGqU1iskxUpNTwyYpa4NhuErWZk9JSqEoUBRSlpWaVRuhxUVNJEh7AJtqYL8iEkd9WvchPSWdrUXl53lskNqAPq37hNRvltGMTF9myHAEgF6tetVYnHVA4k6wmgieeAL+8pfQ8lNOgeTYTZQuIiIiUkcM3c62JKBT8BVO/VxwRKSWHLrboWEXJUpLTuO8fWIzq+Rl/S/jli9vieozlQ2lz/JlhU22dmrUiTV5a8qN7vQl+di71d7MWzcv5DON0xqzuWhz2McuJ3Y/MapYE8l2E6TB4aFl7R+mrOy+9gT6AO/HIDYRqUOGdR7GHs334KfVP1Ho97rbpyan0j67PUd1PSqkfnJSMncffDeXf3h5+TlIUzK5++C7ay3u2uacez7eMdRrZ57pLcg0ahRs2ADp6XDddXBz/Z22QURERHZqB8U7AJGdVXpKOu+f/j4jXx5JwAVwOIr9xdx98N3s03qfmBzj5qE38+Yvb/LT2p/KlV87+FqyU7P51+f/Klc+qs8oju56NMe8dky5EZv7tdmPl457iV6P9yo38rNFZgumj57O6PdGM/G3iaQkpeBwdGjUgfdPf5/Hpj3G3V/fTWpyKmZGWnIaH5/1MfPXzeect88hJSkFzBte//LxL9Myq2VMzrsu2u4iTWZWNl/sIEzf4lCrgMOccz/tsGYN0oTPIrG3tWgrt315G/+b/T/8AT+n9jyV2w66jcbpjSv9zPi547nli1tYtmUZvXfpzV3D76J/u/61F3SMVWGRpg7AVufchh3UawI0dM79Ud0Yq0Ntp4jUBC3SJCISPbWdInVDYUkhnyz8hNyiXIbvOpzmmc1jfozvl37PrV/eSqP0Rjx0+EO0aNACgHV56/jPt/+h0F/IZf0vo2PjjgAUlBTwwPcPsHzLcs7qdRb7td0PgEAgwDMzn+HHVT9y6K6Hllsced66eUxfMZ1OjTsxqP2gbb1jV+as5IvFX9AkownDOw/Hl+zNu5pTmMPHv3+MmXHoboeGXZi5LqqRVezNrHSxJQOeAb4GKltEpAhYDnzvnCuqpE6tUWMrIjWhCglSP/Ccc267c4ua2ZPAuc65mpj6JGJqO0WkJuhLvohI9CpZxf5AYJVz7tcdfLYL0No5NznCYz2Dt6joGudczzDbzwCuC77dClzonJsV3LYYyAH8QEmk7b3aThGpCVW979zuF/GyQ0XN7Ba85KeGj4qIRM6IrPd9aV0RERERkcp8ATzLjhf2vBY4D4h0kvbngIeBFyrZvggY4pzbaGZHAE8AZYeFHeScWxfhsURE6pykSCs65zo5566tyWBEpGa8Pe9tejzag6x/Z7HP2H34cMGH8Q6JLxZ/Qb8n+5H17yy6PdSNl396GYCJv02k5X0tsVuN5NuSOezFwygqiXun9NrQGCiMdxARmz4dhg715gPt1AkefRS2MyKhSi69FFJTwcw7zv33x3b/IiIiIokp5g/Vgz1NK50Syjn3rXNuY/Dt90C7WMcgIhJPcR3KKSI175U5r3D+hPO3LZQ0c/VMjn/teMadPI4RXUbEJaavlnzFkS8dSV6JF9OvG35l1LujmLVqFvd+e++2egEX4OPfP6bnYz359ZLtjiKqU4LzjpbVIExZqdIF7g7FezJf982ZA0OGQG5wtcMlS+Caa2DlSrj99tgc4+yz4YUyHRjy8uCqq7zfr7wyNscQERERqb9aAvk7rFU1fwU+KPPeAR+bmQMed849UdkHzWw0MBqgQ4fKbo9FRGpfxAlSM7spwqpFwDpgunPuxypFJSIxc90n15VbRR4gvySf6z65Lm4J0us/vX5bcrRUXnEe//nuP2Hr/7bhN2aunEnv1r1rIbqYWIx3o1jqhOBrewx4qaYCiqnbboP8CvfbeXnwn//A9dd7vT2ro6QE/ve/8NtuukkJUhEREdmpBOcdLWuXMGWlyj58n1sDsRyElyDdv0zxYOfcCjNrCXxiZvMqm/s0mDx9Arw5SGMdn4hIVUXTg/QWyn/hr4yV1jOzn4BznHMzo45MRKqt2F/Msi3Lwm6bv35+LUfzp5/X/hy23O/8lX7mk4WfJFKC9A/+bC87AHl4D47CKV3g7i28eZ/qvunTIRAILU9JgcWLoUeP6u1/xYrKh+uX9loVERER2Xl8Qfnv4ocFX9tjwOOxDMLM9gaeAo5wzq0vLXfOrQj+XGNmbwH9gIgWhxIRqSuiSZDehvdF/xwgF/gEWAIEgE7AIUAW8DxQgvdEaW/gUzPr45z7I2ZRi0hEUpJSaJbZjHV5obm59o3axyEiT8fGHZm9enZIeRJJBAiTeAMGth9Y02HFjHOuU+nvZhYA3nDOnRe/iGKsa1dYuDC0vLgY2rat/v532aXybWlp1d+/iIiISGKZzJ8J0iHAGmBeJXW3PXx3zr0bqwCC00WNB/7inPu1THkWkOScywn+fihe7kBEwnDOsS5vHdlp2aSllP9usyF/A6nJqTRIbRDRvvKL88ktzqVZRjPMdjw1ccAFWJe3jsbpjUlNTq1S/KXyivPIL86naUbTiI6dCCJepAl4GhgJvAJ0dM4d75y7wjl3lXPuBKBjcNuRwO1AT2As0BS4OrZhi0gkzIwbD7yRTF9mufJMXya3Dr01TlHB7QfdHjam0/Y6LWz9llkt2b/D/mG3JYBz8drP+uOmmyCz/PUjIwP+8hdo3Lj6+09NhYMPDr/tssuqv38RERGRBOKcG+qcO8g5d1Cw6IPS92Fehznnzos2OWpmrwDfAd3MbJmZ/dXMLjCzC4JVbgKaAY+a2UwzmxYsbwV8bWazgB+A951z8V8RVqQOevOXN2l7f1va/7c9je9pzIXvXUhhSSFTl0+lx6M9aP1/rWl6T1NGvjyStblrK91PblEuZ44/kyb3NKHt/W3ZdcyufPz7x9s99vMzn6fV/7Wiw3870OSeJlz98dWUBEqiPoeN+Rs5/rXjaXJPE9rc34Y9HtmDb/74Jur91EXmIlx12MyeBw4CdnPOFVdSxwf8DnzhnDvLzDLweplucM7tEaOYI9K3b183bdq0HVcUqeecczww5QFu//J2copyaJLehDuH38moPqPiGtdLP73ENR9fw9q8tWT5srhm8DXcsP8NjJ02lis/upJCv7ege7dm3fj6vK9pntk8rvGWMrPpzrm+8Y6jpkTcdn7wAVx0ESxd6vXqvPBC+Pe/weeLTSCBABxxBHzyiTfcPikJzj8fHo/pSDERqSVqO0VEoheu7TSzIcAq51z85suKEbWdsjOZvGQyR7x0RLn1QTJSMjiy65F8uOBDthZt3VbuS/KxR/M9mHXBrLC9M4948Qg+X/z5tu/M4HU4+u6v37F3q71D6r87/11OffPUcsfO9GVyQd8L+M+h4dcBqcyApwbw48ofKQoUbSvL8mXx04U/0blJ56j2VVOqet8ZTYJ0JV7iM3wXrz/rvQoMcc61Dr7/BBjknKvmqh3RUWMrUp5zjrziPDJ9mXWmC3xpTBm+DJKsfIf2dXnraJDagPSU9DhFF56+5JfhnLc4U3o6JCfXTEAlJbBhAzRv7iVJRSQhqe0UEYme2k6R+uOw/x3GxwtDe3kmWzIpSSnlkp0ADVIb8NGZHzGo/aBy5Ys3Lab7I93JLym/aG6SJXHGXmfwwnEvhBxj3yf2ZcbKGSHlmSmZrL9ufcTfuWeumsngZwaHLALtS/JxSf9Lok621pSqtp3RzEHaGGgYQb2sYN1SlfcLFpFaY2Zkpdbqc4od2l5MdaXHaLTMrPKVpnbMOeeiaZfjy6z6K9bvSEoKtGxZs8cQERERqaOCc39WmdYCEakbFmxcELbcsJDkaKnFmxaHJEiXbFpCanJqSII04ALMWxd+auI/NodvBhyOjfkbad2w9Y7C3xZPSlLo19XiQDHz1lY2LXLiiKY7ziJg6PYa6OC2YcG6pVoDG7a3YzNrbGbjzGyemc01s4EVtpuZPWhmC8xstpn1iSJukZ3ezFUzOfmNk+n+SHfOHH8mv6z9pdaO/dSMp9jl/3Yh7Y40uj3Ujc8WfVZrx67Mz2t+5ow3z6D7I905ZdwpzFo1K5a7t2q81EVSRERERMpajPf9uiqvMKtqikg8DGg7IGTUJHidhjJTMkPK/QE/vXfpHVLeo2WPsAnV1OTUStft2GeXfcKWp6ek0yKrxQ4i/1OvVr0o8heFlKenpDO4w+CI91NXRfNl/HkgE/jczE4zs23jKc0s2cxOBT4H0oN1MbMUoBfw0w72PQb4MDhPaS9gboXtRwBdgq/RwGNRxC2yU/ti8RcMfmYw434Zx9x1c3llziv0e7IfU5dPrfFj/+PTfzDq3VGszl1Nkb+IXzf8yvAXhvPe/Pdq/NiVmbJsCv2e6serP7/K3HVzGffLOAY9M4ivlnwVk/0755IqvoD/AnnA/cA+QBO8nvb7AP8BcoH7g3V3Pm+9BXvvDe3aefOMbtnilU+bBgccAG3awJFHwqLgs7fNm+GBB+D44+H662HJEq+8qAhefBFOOsmbE3XmzHicjYiIiEgs/VHJq+xD9i3BV9myP4ClcYhXRMK4cYi3eLLx53R3mb5Mrht8HY0zGpfrmZmRksGhux1K9xbdQ/bTPLM5o/qMKrfocZIlkenL5KqBV4U99l3D7wq7SPK/h/07bI/QynRu0pkT9jyh3L6SLZmGqQ35275/i3g/dVU0c5CmAO8ChwEO8AMrg7+3AZLxGuKPgKOccyVm1gt4GHjcOfdiJfvNBmYBu7pKgjGzx/HmP30l+H4+MNQ5t7KyeDWfiYin56M9+XntzyHlg9sP5uvzvq6x4wYCAXx3+Ai4QMi2XbJ2YeXVlf7zrVEDnhrAlOVTQsr3brk3sy7ccU/SaOczMbO/AmOBYc65sFlYM9sf7wHT351zT0a433RgMpCGN13KOOfczRXqDAXe4c9e/eOdc7dtb7+13nZeeik89FD5sowM+L//8xaBKssM3nsPRo2CTZu8+U9TU72Fod57z0uWzpkDubnenKipqfDII3DuubV2OiISnubRExGJXiWLNCUDrwEHALcD/3PObQ5uywb+AvwL+AY42bkwN+N1hNpO2dnMWTOH6z+9nm+XfkurBq24fvD1nNXrLFZtXcW/Pv8XE+ZPICMlg7/t+zeuHXwtvuTwC+AGXIDHpj3Gf7/7LxsLNjKs8zDuGn4XuzfdvdJjT10+lRsm3cCMlTNol92Om4fczAndT4j6HEoCJfz3+//y6A+PklOUw4guI7hz2J20b9Q+6n3VlBpfpCl4kCTg0uCrU4XNS4CHgDHOuYjn4DOz3sATwC94vUenA5c553LL1HkPuNs593Xw/STgOudcpa2pGlsRr/FKvT0VR+i/89TkVAr/FX6uk1iYu3Yu3R8NfeIF3jwrgZvjc6/mu91HSaAk7Db/Tf6wwx7KqkKCdDqw2Tk3bAf1PgMaO+cimkLEvJW2spxzW83MB3yN13Z+X6bOUOBq59zISOOt1bZz0yZo0iT8NjNvEaiKsrKgsNBbvKmsli1h61YvaVpWZiasXg0NGsQkZBGpGiVIRUSiV0mC9BrgNqCPc67iyMvSOnsCPwI3O+fuqflIq0Ztp4jUhKred0Y1nNM5F3DOPeCc2xXoAAwMvjo65zo75+6PJjkalAL0AR5zzu2DN9T0+gp1wi25HfLN2cxGm9k0M5u2dq3WhhJJtuRKF0Fqkl5JYipGtjfRczTd+GOtsvPOTsveYXK0irrh9bbfkZVA10h36jxbg299wVfkT7zqgpdfrnxbZQ/vcnNDk6MAa9eGJkfBW+Tp22+rFp+IiIhI3XMO3ujKsMlRgOC2z4GzaysoEZFEV+VsgHNumXNuSvBVnblNlgHLnHOlY17H4SVMK9Yp21+3HbAiTExPOOf6Ouf6tmgR+USzIvWVmXHRfheRkZJRrjzTl8mVA6+s0WM3Tm9Ml6Zdwm47Y68zavTY23P5gMvDzr9ySb9LauqQhXhzje7IPsG6EQvO/zwTWAN8UqYdLWugmc0ysw/MrEcl+4nPw6VmzWr+GM5Bw4Y1fxwRSQhmdriZzQ8u/FnxgXxpnaFmNtPMfjazL2s7RhGRHegMbIyg3iZCR32KiEgl4r4giHNuFbDUzLoFi4bjDbcvawJwVnA1+wF4w1XjM4GhSIK5/aDbOa3naaSnpJOdlk16Sjrn9zmfqwddXePH/v6v39M+u/xcJAd0OICnj366xo9dmesGX8d5vc8r9+dx+l6nc8vQW2rqkJOBbmZ2e3BYfDnBdu02YI9g3Yg55/zOud54D436mVnPClVm4PXw74U3BcrblewnPg+XTjrJ6+EZTmVD4nfd1Rs2X5bPB/vtF1oOkJ0N/ftXL04RqReC8/Y9grf4Z3fgNDPrXqFOY+BR4GjnXA/gpNqOU0RkB7YAg4JrhIQV3DYwWFdERCIQ9ThXMxuIl8Rsg7difTjOOffXKHZ7CfCSmaUCC4FzzeyC4I7GAhOBEcACvJWgteKGSIR8yT6ePuZp7j3kXpZsXsKuTXalcXrjWjl208ym/HHFH/y85mdmr57NkI5DaJPdplaOXZnkpGQeGvEQtx10G4s2LaJT4040zWhak4e8ETgU+Adwipm9yp+LJnUCTgV2B/KBm6pyAOfcJjP7AjgcmFOmfEuZ3yea2aNm1tw5t64qx4m5pCSYMAFGjoRAmTlpBw6El16Cnj3LD5vfZReYPh3+9jfvcz6f10N01129RZoefxzuvNMrBy9h+uGH3nFERKAfsMA5txAg2B4fQ/kH86fjLWj3B4Bzbk2tRykisn0fA2cAT5rZpc65nLIbzawBMAZvBGbYhZJFRCRUxAlSM0vDWy3vqNKi7VR3QMQJUufcTKDiBKpjy2x3QIXljEUkGs0ym9EssxaGNIfRo2UPerQMO7o7bppkNKFJRs3OwwrgnJtjZiOAl/ASof+sUMXw5h890zn3U6T7NbMWQHEwOZoBHAzcU6HOLsBq55wzs354owbWV/1sasARR0B+vreS/fLlcOaZ0Cc4y0pODrz4Ivz4IxxyCIwY4ZW/9hr89hvMmAEdO3o9RM3gX//yVrifPNlb/Gno0Mp7qIrIzqgtUHZaqGVAxS7mXQFf8KFTQ7zFR1+onfBERCLyL7ye8GcBxwQXNC778P0ooBGwgSo+fBcR2RlF883xFuBoYCvwP2Ae6rIvUi+VBEp4a+5bjJ87nsbpjRm17yj6tI5ocfUa45zjwwUf8sqcV0hJSuHsXmczpNOQuMYUKefcl2a2O3AiMARvSDzAcuBLYJxzLj/K3bYGng8OGU0CXnfOvVeh9/2JwIVmVoLXQ/XU4AOnuiU1Fa66KrQ8KQnOOst7VdSli/eqqFUrb+i+iEioSBb9TAH2xRstlQF8Z2bfO+d+DdmZ2WhgNECHDh1iHKqISHjOuT/MbAjed/J9gDP5sy0rbedmAn9xzi2p/QhFRBJTNAnSU/BWmN/POTe/huIRkTgrCZRw6P8O5YflP5BbnEuSJfHCrBe475D7+Hu/v8clJuccZ799NuPnjie3OBfDeP3n17lwvwu575D74hJTtJxzBXjDnGIy1Mk5N5swiz8FE6Olvz8MPByL44mI1AORLPq5DFjnnMsFcs1sMtALCEmQOueeAJ4A6Nu3b917+CQi9ZZz7hdgXzPbnzAP351zX8UtOJFqmrlqJq/NeQ2H45Qep7BP60jWu61bcotyeWXOK8xaPYverXpzas9TyUrNoiRQwoT5E/hyyZd0yO7AX3r9hZZZLeMdrgRFkyBtA3yu5KhI/Tbul3HbkqMAARcgrySPqz65itP2Oq1WhqVX9N2y77YlRwEcjtziXB754RFG9RlF12Zdaz0mqQWBALz8MkydCocf7g3HL/XCC/DKK95w/Ntug+Rkr3zOHHj2Wa8n6aWXQnpwquytW2HSJK9X6vDh4Rd0Kss5+P57WLoU+vb15jkVqUUlgRK+WPwFmwo2cUCHA2jVoFW8Q6oPpgJdzKwzXhLhVLw5R8t6B3g4uMBJKt4Q/P/WapQiIhFyzn0NfB3vOERi5dYvbuWeb+6h0F8IwINTHuSqgVdx+7Db4xxZ5P7Y/Af9nuzH1qKt5BbnkuXL4l+f/4svzv6C08efzq/rf2Vr0VbSU9K55ctb+PjMjxnYfmC8wxaiS5CuRUPqReq9cb+M25aILCs1OZUvl3zJsXscW+sxvf/r++QV54WUB1yADxd8qARpfbRoUflFmh580Fuk6ccfvWRlfnBGgg8/hLvugm+/9eYgnTTpz33ccAOMG+f9fuaZXhLVDPx+bx7TI48Mf+xVq+Dgg2HJEq9+cTGcfDI888yfiViRGjRnzRwOfuHgbe1ecaCYfx7wT/514L/iHFlic86VmNnFwEdAMvCMc+7nslOTOOfmmtmHwGwgADzlnJtT+V5FREQkFuavm8/d39xNQUnBtrL8knz+891/OG2v0+jeonsco4vcRRMvYl3eOvzOD0BucS4FJQUc++qxLN68eNv5lf489c1TWXzZYsy2t8yP1IZoEqQTgRFmluKcK6mpgEQkvrLTsjEMV3FaNgdZvqy4xNQgtQG+JB9FgaJy5SlJKXGLqTJmthBvHqiDnXOLgu8j5Zxzu9VQaInlgAPKr2APXuKybHK0lHMweLDX47SsQABOPNGb47SgoPy2k07yEqAtWoQe+7TTYP58KCnzX924cdCvH1yk9QKlZgVcgCNePILVuavLld/19V0Maj+IYZ2HxSmy+sE5NxHvnrZs2dgK7+8DEmP+FhERkXpiwvwJ+AP+kPLiQDET5k9IiARp6boZpcnRUn7nZ976eWE/sy5vHQs2LKBLszDrK0itSoqi7o3Bnw8HV7QXkXpoVJ9RZPgyQspTU1IZ2mlo7QcEnLbXaSQnhfbccziO3/P4OES0XZ2CL1+F95G+ZMsWb0X7cComR0tVTI6WLS8J80zP7M/epWWtWwfffRf6mbw8eOSRymMWiZHvl33P5sLNIeV5xXmMnTY2zCdERKQ+MzO/mZWYWdcy7yN9qWOTJAxfso9kC/3Ol2RJ+JJ8YT5RN4U7BwALu1ak93Dcl5w451efRdOD9AK8IUmjgMPN7DPgD7zhRxU551ziTBIhItsMbD+QW4feyo2f34gvyYdhpCSn8MEZH8St4e7UuBNPHf0U5084n5SkFMwMf8DPuJPHxWVO1B3oHPy5vMJ7iVTFnqPV5Q99Ek1xsTcvabhjJ1Xy7DAnJ7ZxiYSxtWhrpUOsNhVsqt1gRESkLjAol1mJZhyuxuxKwjix+4ncMOmGkPIkS+LE7ifGIaLomRkn9TiJ1+e8Xm70Y2pyKr1b9WbO2jnlpo4zjN2b7E6nxp3iEK1UFE2C9Ba8YaMGdADOCVOndLsDlCAVSVBXD7qas3udzeeLPyc7LZvhnYfH/anW6XudzsiuI/l04ackWzKH7HYImb4dLLQTB865Jdt7LxHYZRdvcaWKw+LB6/npolwsOj09tOdpSoq38FNF7dt7w+7/+KN8uc8Hx9e53spSDw1qP4gSf2iHn0xfJqf0OCUOEYmISDw555K2916kvmiX3Y7HRjzGhRMvJNmScTgCLsDDIx6mY+OO8Q4vYg8e/iAzV81k8abFlARKSElKoVPjTrx3+nuMencUnyz8xOs1muQjIyWDcSeHGdUmcRFNgvTWGotCROqcFlktOLnHyfEOo5zstOy6OKReasLjj8PZZ5cvM/MWa7rkktD6l1ziLaKUW2GBsXPO8XqEvvban9uysrzyvfYK3Y8ZPP88jBwJRUVeT9PMTGjWzFsESqSGNUhtwEMjHuLiiRdT5C/C7/xk+bLo2bInZ+59ZrzDExEREakx5+xzDkd0OYJ3f30X5xxHdzuaVg1axTusqDTJaMKsC2bx2aLPmLt2Lt1bdOegzgeRZEm8ferbzFg5g2+Xfkubhm0Y2XUkqcmp8Q5ZgsxF2xMnQfTt29dNmzatZna+ebM3d93atTBkCAwY4H2pFomznMIcxv0yjtW5qzmgwwEMaj8IM6PY701s/ev6X+nRsgcjuowgJSkF5xyfL/6cH5b/QNuGbTmh+wl1sldmXWJm051zfaOo/xbwKfCZc25uzUUWGzXadkZr6lS44gpYuBD22Qcefhg6d4Y5c7yFlH77DZo2hYceghNO8IbMX3stTJgAjRrBTTfBKad4PU4/+ghefNFrq886y1ulfnvt9qJF8NhjsGABHHSQl1Bt2LDWTl1k5qqZPD7tcdbmreW4PY7jpB4nJfQNdLRtZ6KpU22niNQbajtFRKJX1bZTCdJoff89HHqot/BHYSGkpcEhh3gJ0+Twk/GK1IZpK6Zx8AsHUxIooaCkgPSUdIZ0HMLYkWM54NkD2JC/gdziXLJ8WbRp2IZJZ03i1DdPZebKmeSX5JPhyyAtOY3J505OiBUC46UKCdIA3rQjAKuASaUv59yyGgixWnSjKiI1QV/yRUSiF67tNLMfCT58B750zsV48vbao7ZTRGpCVe87qzR/iZk1MrODzew0MxtUlX0kpEDAm4MuJ8cbqllS4v385BP43//iHZ3sxJxznPD6CWwu3ExucS5+5ye3OJcvlnzByJdHsnzLcnKKcgi4ADlFOSzcuJCjXzma6Sums7V4K37nZ2vRVjbkb+DUcafG+3TqmyOB/wKzgV2AM4FngCVmNs/MHjGz48yscRxjFBEREZHE0Au4EngP2Ghmk83sZjMbbFbJ8tkiIrJDUSVIg4nRZ4A1eCvavwicX2b7381shZkNiG2YdcTMmeFXMc7NhaefrvVwREr9svYX1uetDynPK85j9prZlLjyC34UB4r5cdWP5JeUX7jG4fht/W8s37IciQ3n3AfOuaudc/sALYFTgKeARUBX4EJgHLDWzH6IX6R1VCDgtbGxGO1QWOjNKypSTznnyC3KJeAC8Q5FRERqTg/gMuBdIBfYH7gZmIyXMH3fzK4ws73jGKOISMKJOEFqZlnAF3ir128EPsBbsb6sD/F6SB0bk+jqmkCg8jnr6ulUBZIYHDH8+2cx3p9s45xb75x7wzn3N+fc7kAn4P+AQiAZ2Dee8dUpzsFdd3mLIzVuDG3bVr2n/uLF3pyjWVne6/DDYVmdm91ApFr+N/t/tL2/LY3vaUyze5px19d3UV+nURIR2Zk55+Y65x52zh0LNAf6A/8EPsdbhPkIvPvLH81sVdwCFRFJMNH0IL0arzv/i8CuzrmRFSs45xYCvwLDYhNeHbPPPt5qxhVlZsK559Z+PCJB3Vt0p0lGk5DyTF8m3Vt0J7nCaBtfko+9W+1Nekp6yGd2bbwr7bLb1VisOzsza2VmZ5jZs8DXwFVAOuAHpsY1uLrkrrvgjjtg0yZvOpOVK+GCC+Ctt6LbT34+DBwIX3wBfr+3r08/9crUm1TqibfmvsUF713Ayq0rKQmUsKlwE3dMvoO7vr4r3qGJiEgNcs4FnHNTnXN3OecOBtrx58N3A1rENUARkQQSTYL0JGAFMGoHE0H/AbStVlR1VXKytxhTgwZeUtTM6400ZAicfXa8o5OdWJIl8cZJb9AwtSGZvkwMo0FqAwa2G8h7p73HLg12oUFqAwAapDagQ6MOvHPqO/Rq1WtbeZYvi8bpjXn1xFfjeSr1jpllmdmRZvZfM/sJrx39H3A23rCox4DjgebOufo5PUm0/H645x7Iq/BfTV6etzJ9NN5801vd3u8vv//Nm73V7kXqgZs+v4m84vL/XvKK87jnm3vwB/yVfEpERBKdefqb2T/N7HNgOX8+fF+HN42TyE6toKSAQCDy6YeKSoooCZTsuGKQcy6q+tvjD/jDjgAKuIDu6WpBShR1dwU+cs4V7qDeOqBZ1UOq4/bfH5Ysgddeg7VrveTogQdWPvRepJYMaDeAJZcv4fWfX2fV1lUc2PFAhnYaipnx+6W/M37ueOavn0/Plj05ptsx+JJ9fPvXb/n494+ZsmwK7bLbcXKPk2mY1jDep1LfbODPtnYV8BLeyqOTnHOa7DWcrVu9np/hLF4c3b5++83bX0V5ed42kXpg8ebFYcvzi/PZWrSVRumNajcgERGpMWbWDTg4+BoKZOP1Fs3DmxJvEvCpc25mlPt9BhgJrHHO9Qyz3YAxwIjgsc5xzs0Ibjs8uC0ZeMo5d3cVTk0kpq748Aoe+uEh/M6PYRy3x3G8cdIbJCWF7yc4efFkjnv9ODbkbwCgXcN2fHrWp3Rr3i1s/ZJACbd+cSsPTnmQnKIc9my+Jw+NeIhhnaMfUP3L2l+48L0L+Xrp1/iSfJzS8xQeOuIhCksKuWjiRbw9720CLsDwXYfz+MjH6dS4U9THkB2LJkFajPckakfaAWG+jdYjTZvChRfGOwqREE0ymvC3vn8LKU9LSeO0vU4LKU+yJA7f/XAO3/3w2ghvZ+UDHPAT8DDeDeviuEZU1zVsCI0awbp1odt69IhuX716eb3+KyZJMzO9bSL1QPcW3flheegab43SG+mhl4hI/TMX796yBJiO9+D9U+A751xxNfb7HN696guVbD8C6BJ89ccbBdXfzJKBR4BDgGXAVDOb4Jz7pRqxiFTLPz/7Jw9MeWDbe4dj/LzxHPfacbxz2jsh9VdsWcHQ54eWW4tjWc4y9h67N7n/yCUlKTR1dukHl/L8rOe3jeL5Zd0vHPXyUXx57pf0bdM34ljX5K5h0NOD2FK4BYej0F/Iq3NeZf7a+Wwq3MTCjQspDnj/tD9d+Cn9n+zP75f9vm0kqMRONEPs5wP7mFmlSVIza4I3T+lP1Q1sp7F+Pbz9Nnz+efkhoJXx+726b7/tfVYSyvx183nzlzf5aXXd+Sfyx+Y/GD93PD8s/6Fcd/5FGxdxw6c38J9v/0NBSUGV9++cY8bKGbz5y5ss3LgwFiEnmgfw2sS9gMeB383sdzMba2YnmVn97XFfVUlJ8O9/h875nJHhzU0ajaOOgjZtwOf7syw1FTp0gMMOq36sInXA3cPvJjOl/L+XTF8m/x7+b5Ismls9ERFJIL8CnwVf1U2O4pybjDfyqTLHAC84z/dAYzNrDfQDFjjnFjrnioBXg3VF4uY/3/4nbPm7v74bdjj8NZ9cE3ah4iJ/Efd9c19I+aaCTTz747MhUxzll+Rzx+Q7oor1yelPUugvLHf8In8RM1fPZOmWpduSo+ANtc8tzuXVOZoWryZE04N0HHB38HV5JXX+DTQAXq9eWDuJ//s/uPFG78u6c14vp48/hp4hIxo8c+bAoYd6PaHMvAVG7rgDrrqqduOWqBWWFHLSGyfx6cJP8SX7KAmUsF+b/Xjv9Pfi9uQn4AJc+N6FvDDrBVJTUgm4AJ0ad+KTv3zCRe9fxPh547fVveaTa3jlhFc4pecpUR1jQ/4GDnvxMOaunUtyUjJF/iKO3+N4nj/u+bBP4eoj59yVAGbWHBgefA0DRgdfATObzZ/DoT6KV6x1yqhRXk/SW26BpUthzz3h3nvhoIOi24/PB999B9ddB2+84bWdp57qJVqTk3f8eZEEcFDng3j39He59pNrmbtuLu2z23Pr0FujbrNFRCQhXIZ3PzkE+AdwA1BgZl/z5zROM2rguG2BpWXeLwuWhSvvX9lOzKz0HpgOHTrEPkoRoNAffmZIh2PN1jW0yW5TrvynNZV3YJq+cnpI2dLNS/El+yjwl+9I5HD8vPbnqGKdvXp22A5JDkeRP3RR2dziXH5eE90xJDLRZCgexltU5BIz6wuUZk86mdmFeIs4DcHrKfV0TKOsj776Cm6+GQoKvBdATo7Xo2npUq8HVVl+v7dt5cry5TfdBP37e3OjSp118xc38+nCT8kvySe/xJtb8ftl33PpB5fyzDHPxCWmp2Y8xYs/vUiBv2Bbwz5v7TyGPjeU+evnl6vrcJz25mkcs8cxYVe+r8y575zLrFWzyj31enve2/z3u/9yzeBrYnMiCcI5tw54LfjCzDryZ8L0OLze91cQXbtcv516qveqrqZN4cknvZdIPTWs8zCmjZ4W7zBERKSGOeceAh4ysySgL95cpMOBA/CGuTsz2wh8DnzinHsiRocOt+iG2055WMF4ngDo27dvpfVEqiMjJWPb9+6yDGOXBruElPdt07fSJOmg9oNCyjo27ljuO26pJEui9y69o4q1b5u+vPvruyHxGkZqcmpIj9cGqQ2iPoZEJuJxV8GV6w8FpgCDgNJ+xkPwkqdDgRnAkcGu9bI9Y8eGX4QkJwe+/Ta0/NtvvW0V5ed7+5I67akZT4U0eIX+Ql7+6WUCLvIV9WLp4R8eDhkSUOJK+HX9r2HrOxwPTXko4v3nFuXy4YIPQ/7jyCvJ49Gpj0YfcD0SHI50AHBg8JWGd3Op1d5EREREZIeccwHn3A/OuX8754YDTfCSpQ8BmcDxQCxvupcB7cu8bwes2E65SNzceOCNYctP7Xlq2EWa7j3k3rDTEmWkZHB5/8tDyrPTsrlov4vI9JWf4ig9Jb3SY1fmr33+SpYvq9zx01PSGdhuIF2adiE1OXVbeYql0CitESf3ODmqY0hkopqYyjm33Dk3CG/lukeAicDHeD1GTwD6aVXmCG3c6A2rr8gMtmwJLc/J8bZV5Bxs2N5UMVIXVExElioOFMctQbqlMMzfMwg790qpdXlhFs2pxPbmLd1aVL/XcavIzLLN7Ggze9DMfsa7kXweOAtvWNJc4EHg2PhFKSIiIiKJxsySzGwgcA1wE3ABNfPwfQJwlnkGAJudcyuBqUAXM+tsZqnAqcG6InFzwwE3cOvQW0lLTgMg2ZI5v8/5vHzCy2HrN89sztRRU2mf/Weuf49me/DrJb9Wuur9vYfcy21Db2OXBrvgS/IxoN0APjvrM/ZutXdUsTbNaMoPo37gyC5HkpacRqO0Rlyw7wVMPGMiX57zJef0OoeGqQ3JSMng+O7HM3XUVDJ8GVEdQyJTpaGczrkPgQ9jHMvO5cQT4csvIa9C4qy4OPxw+cGDvW0VZWXBSSfVTIwSM8M6D+ODBR+EJEP3a7Nf3ObiPKbbMYydNpaiQPkO31m+LHKLc8N+5vw+50e8/6YZTenUuFNIj9RkS+bIrkdGH3CCMrPvgH2BZP68SV1KcM5RvHmiVscpvNoTCMBLL3nD3IuK4Kyz4PzzvTmY774b/vMfrz3s2xeeeQZ22w1mzfLmHf3lFxgwAK69Fjp3rvwYmzfDQw95i9g1awaXXQYjRtTaKYrE0sTfJjLm+zGsz1/PsXscyyX9LqFReqNK6y/auIh7v7mX75d/T/fm3bl28LX02qUXG/M38sD3D/D+b+/TMqslVwy4gkN2O6QWz0RERGLNzHrgDas/GG80UkP+vM/cyp8r20+KYp+v4I0KbW5my4CbAR+Ac24sXueoEcACIA84N7itxMwuBj7Cu999xjmnCRIl7m4achM3Dbkp4vp9Wvfhjyv+iLh+kiVx1aCruGpQ9deE6dykMxNOC32ukOHL4PGjHufxox6v9jFkx8yF68VYD/Tt29dNm1aH5+IqLIQhQ7yFl3JzvTlH09O9hZsuvDD8Zx57DK6+2puzNBDwkqM9e3qJ1rS02o1forJgwwL6PdmP/JJ8CkoKSEtOIzU5la/O/Ypeu/SKS0xrc9ey7xP7si5vHfkl+fiSfPiSfbx2wmuc8dYZIT1Mj+12LG+d+lZUx/h26bcc+r9DKfIXURwoJiMlg4ZpDZkxegZts9vG8nRqjZlNd871jaJ+AG9F0M/5cyGmBTUVX3XVWNt5xhnwzjteewfeCvV9+0KjRvDuu+XrJifDs8/CBRf82d6lpHir2H//PXTvHrr/nBzYZx9YvvzPeZ2zsuD66+Ff/4r9+YjUoDsm38HdX9+97WFVeko6bRu25ce//YCRobcAAQAASURBVEjDtIYh9X9Z+wsDnhpAfkk+JYESkiyJ9JR0XjzuRS7/6HJWb129bbGCTF8mdwy7gysGXFGr5xRt25lo6vx9p4gkpHBtp5mtAFqVvgWKge/58+H7FOecv1YDrSK1nSJSE6p636kEaTwVFcGrr8L48V5vpwsugP322/5npk715hxdvx5OOAFOOcXrgSV13trctYydNpYpy6fQq1Uv/r7f3+OeJNxSuIWnZzzNZ4s+Y7emu3HRfhfRpVkX8oryuObTaxg/dzwNUhtw3eDrouo9WtbiTYt55IdHmLduHgd0PIBRfUbRJKNJjM+k9lQhQdoH+NElSGNbI23nrFkwaFBoj/nMzNCyUhkZofM0m3k9Qt97L7T+/fd7idCKn0lP95KmTZtWPX6RWrQhfwNt728bMk1JRkoGdw67kysGhiY2j3z5SD747YOQKVKapDfZ9mCu4r5WX706bLK1pihBKiISvUoSpAFgJl5CdBIwObheSMJR2ykiNSHmCVIzW1iNeJxzbrdqfL7a1NiKSE3Ql/wqePBBuO66P3t2Vkd2tjeUvqLhw+Gzz8LXf+01OPzw6h9bpBZ8uOBDThl3Sth5ood1Hsaks0JHS2bflU1OUZiFHCvRKK0RE06bwIEdD6xWrNFQ2ykiEr1KEqTNnHPr4xVTLKntFJGaUNX7zu1Nftip6uFsZ5UXEQlR5C/it/W/0TyzOa0atNrxB7Zj9dbVrMtbR5dm5Ve8q0xBSQG/b/idVg1a0Tyz+bZyf8DPr+t/pWFaQ9plt6tWTBvyN7AiZwW7Ntk1ZKU/2Qm0bAk+X2iC1OcLP7fy9jSppPdxmzbeVCWBCoue+f3e8UUSRIvMFvgDoSMjkyyJtg3DjzpomtE0bII0yZLCLgRYHCimZZb+XYiIJKL6khwVEalrtreKfedqvHatuZBF6pdnf3yWlve1ZODTA+n4QEeOePEINhVsino/mwo2MeKlEXR6oBMDnx5Iy/ta8syPz2z3Mw98/wAt7mvBwKcH0u7+dpz4+onkFuUy8beJtPlPG/o92Y8uD3VhwFMDWLZlWdQxFZYUctZbZ9HmP20Y9PQgWtzXgjsn30mCjDaXWDn6aG9e0YpSU70h8OEMHBi6LTMTrrwyfP1LLw2tn5wMHTp4c5OKJIg+rfvQoVEHkq38v5n0lHQu6XdJ2M9cOfDKkIdP6SnpHLn7kSHlKZbCHs33YI/me8Q2cBERERGRBFZpgtQ5t6Q6r9o8CZFE9eXiL7n4g4vZXLiZnKIcCv2FfLb4M056/aSo93XyGyczadEkCvwF5BTlsLlwM5d8cAmfL/o8bP23573NPz/7J1uLtm479vu/vs9Jb5zESa+fxJq8NWwt3kpBSQHTVkxj+AvDo05sXvrBpYz7ZRyF/kJyinLIK87jrq/v4n+z/xf1+UkCy8z0hr+3bw8NGkDDhtC8OUyYAN99520v67TTYNIkb77R9HRvIae0NG/V+4svDn+M/faDRx/19p2d7e2zRw/46CNv7lKRBGFmfHTmR/Ro2YNMXybZadk0TG3Io0c+yn5tw89TfnG/izm/z/mkJafRKK0R6SnpjNh9BK+d9Br3H3Y/Wb4sstOyyUjJoHfr3rx3Wph5fEVEREREdmJapEkkjo58+Ugm/jYxpDw9JZ1fL/6V9o3aR7SfZVuW0eWhLiELcQAcvvvhfHDGByHl/Z/qzw/LfwgpTyKJJEuixJWUK2+Q2oCPzvyIQe0HRRRTQUkBTe5pEjamPZrvwdyL5ka0n7pG8+hVg3Mwe7Y3rH6ffcr3Kv3kE1iyBI491kuellq+HP74A7p29Raz25GCApg50xuK361brM9ApFbNXzefjQUb6b1Lb9JTKultXcb6vPX8uv5XOjTqUG4RwLziPGavnk2zjGZ0adalJkOulNpOEZHoqe0UEYleTcxBKiI1bOnmpWHLU5NTWbV1VcQJ0tVbV5OanBo2Gblsc/ih8StzVoYtN7OQ5Ch4idPKPhNOTmFOpT1O1+SuiXg/Uo+YQa9e4bcdckj48rZtvVek0tNhwIDoYxOpg7o1jy7J3yyzGQMzB4aUZ/oyGdBO/y5ERERERCqzvTlIRaSGDd91eNiFlEr8JXRv0T3i/ezRfA9KAqFJTV+Sj2G7Dgv7mSGdhpBkoU2AL9lHZkroQkpFgSL6te0XcUzNMpvRNKNpSLlhDGwX+gVeRERERERERCQelCAViaNrB11Ldmo2KUl/dubO9GVy20G3kZWaFfF+slKzuP2g28stxpGSlEJ2WjbXDb4u7GduGXILDVMbllsIJNOXyf2H3k+rBq1IS077c/++LM7rfV7EPVrBWz35oSMeKhdTsiWTlZrF3QffHfF+REL4/d5cpHvuCUcdBRs2/Lntp5/g/vvh2Wdh8+b4xSgSoeVblvPID4/w8A8PVzqqIBIBF+DThZ/yf9/+H2/NfYtif/EOP7No4yIenPIgj019jFVbV1X52CIiIiIiiU5zkIrE2fIty/n3V//m44Uf07pBa64ZdA1HdTuqSvt679f3uPebe1m5dSWH7HoI/zzgn+Xmoato0cZF3DH5Dr5c8iUdGnXgHwf8g4N3PZiN+Ru579v7eHPum2SnZnNp/0s5c+8zsSosdvPVkq+486s7WbBhAQPaDeCmITfRtVnXKp1fXaC5oOLsjz+gc2cIBMqXv/IKfPopvPyyl0D1+bwh/RMnwgEHxCdWkR14csaTXPrBpRiGw7sf+79D/o+L+l0U1X5yCnM46PmDmL9+PoUlhaSnpNMkownfnPcN7bLbhf3Mvd/cy81f3AyObW37k0c/yRl7nVG9k6qE2k4Rkeip7RQRiV5V204lSEVEorCjxtbMnqnG7p1z7q/V+Hy11fm2s0MHWBqml52Zt3J9bm758qZNYdUqL2EqUocs3byUrg93DZk7Oj0lnTkXzmG3prtFvK/LP7ycsdPGUugv3FaWbMkM6zyMj//ycUj9OWvm0O/JfuSX5Icce8nlS2iZ1TLKs9kxfckXEYme2k4Rkehpkaa6wjkoKQn/Zby4GFJSvC/yIhHyB/yYWch8oQEXwDlHclJyJZ8sLxAIUFBSQGZq6PyiseKcw+/85aYM2AmdU43POiCuCdI6L1xyFLy2t2JyFLz2+NtvYciQmo1LJErj546HMM+o/QE/b859k2sHXxvxvl766aVyyVEAv/Pz+eLPKSgpID0lvdy2V+e8SpG/KGQ/SZbEhPkTOL/P+REfW0REapaZfVaNjzvn3PCYBSMiUo/ViSyGmS0GcgA/UFIx02tmQ4F3gEXBovHOudtqMcQdcw7GjIE774T1671Vl++5B04/HSZMgMsvh8WLoVEjuOoq+Mc/IElTwErlflv/G6PfG83kJZNJtmSO2/M4HjvyMQAuev8ixs8dT4kr4YAOB/DEUU9UOmw9EAhw+pun8/ovr+NwJFkS5/U+jyePfjJmseYX53PVx1fx3MznKCgpYJ/W+/DYkY9FtahTPXJuvAOQCvz+eEcgEsLv/AQIhJQ7HP5AdH9nAxWnnCi7zYVu8wf8VDaCKNyCfyIiEldDq/HZ+jlcVESkBtSJBGnQQc65ddvZ/pVzbmStRROt//4XbrwR8vK898uWwahR8PvvcPfdf5Zv2gR33QX5+V4yVSSMTQWbGPj0QDbkb8DhCLgAb819i7lr5wIwf918igJe75/JSyYz8OmBLLhkAU0ymoTs64zxZ/DaL69tex9wAZ768SkyfBk8eMSDMYn35HEn8+nCT7cNFZ2xcgbDnx/OzAtmRjVMtD5wzj0f7xjqtZYtYc2a8NuyssL3Ih08uGZjEqmCY7odwz8/+2dIuS/Jx7F7HBvVvk7scSLP/fjctv8XwOsNOqDtgHIL5ZU6qcdJPPjDg+QV55UrD7gAR3Wt2hzYIiJSYw6KdwAiIjuDShOkiT6PXq0KBOCOO/5MgpbKy/szGVqx/IEHvIRqevlhbyIAL8x6gfzi/G2LdgAUB4pZsGEBQLkvwQ5HQUkBz818jisGXhGyr9d+fi2kDGDstLExSZAu2rioXHK0VIG/gP9+/18eHvFwtY8hss0XX0CPHl6v/bLGjPG2ffyxlyRNS/N66b/yive7SB2zW9PduGXoLdz6xa3bhrunJqdy3f7XsWeLPaPa113D7+LzRZ+zcutKthZtJcuXRYYvg2ePfTZs/T6t+3Dxfhfz8NSHKSwpxMzwJfm475D7truwn4iI1D7n3JfxjkFEZGewvR6k51Rjv9HOo+eAj83MAY87554IU2egmc0CVgBXO+d+rljBzEYDowE6dOgQfdRVlZcHOTnht1VMjpa1di20b18zMUlCm716NnkleSHlfhd+2GVecR4/rfkptLwor1yStaziQHH1ggz6bcNvpCWnhSRISwIlzF49OybHENlmzz1h40Y4+2z44QevDX3uOa/8kkvgq6/gww+hWTNvipPWreMdsUilrht8HUd3PZo3fnkD5xwndD+Bni17Rr2fphlNmfP3Obw9721+XPkjuzfdnVN6nkKD1AaVfuaeQ+7htL1O4625b+FL9nFKj1Po0qxLdU5HRERERCRhbS9BWpvz6A12zq0ws5bAJ2Y2zzk3ucz2GUBH59xWMxsBvA2E3MUHE6tPgLciXi3E7cnKgiZNvIRnRQ0awNatoeXJydCqVc3HJglp39b78uqcV8ktLj9cONmSw84bl+XLom+b0EXaMlMzSbKksHPQpSanxiTWPZvvGbI4SOn+92u7X0yOUR+YWTreEKmuQDYQbrU255y7vVYDS0SNGsHbb4eWm8GBB3ovkQSxZ4s9uWnITdXeT2pyKif3OJmTe5wc8Wd679Kb3rv0rvaxRUREREQSXaUJ0tqcR885tyL4c42ZvQX0AyaX2b6lzO8TzexRM2u+gzlLa4+ZN8T+iivKD7PPzITbb/cWZCrbkzQzE264AVJjk6CS+ufMvc/ktsm3UVBSsK3XaFpyGr1a9QLgx1U/bktKJlsyDVIb8Je9/xJ2X+f3OZ8npod2yr52UOQrJG9P+0btOW6P43h73tvkl3h/zw0jPSWdy/tfHpNjJDozOwEYCzTdXjW83vRKkIqIiIjIdplZG+AYdvzwfeeZ+k5EpBrivoy6mWWZWcPS34FDgTkV6uxiZhb8vR9e3OtrO9btGj0aHn8cdt3VS3z27AlvvumtXv/BB7Dvvl55+/Zw//1w/fXxjljqsIZpDZk6airH7XEcGSkZNEprxPl9zueTsz7hk7M+YdS+o2iU1oiMlAyO3eNYpo6aSsO0hmH39fjIx7m036X4knyA18voXwf8i9uHxS4P9/yxz3P1oKtpntmctOQ0hu86nO/++h3tG2kKCTPrD7yKd+P6ClA6F8LdwDhgc/D908BttR5gTXAOFiyAFSsi/8yaNfDrr1BSYQXtuXO9tnX58sj24/d7+1m9OvJji1RBTmEO89bNC1noaEvBFib+NpFFGxdFvK8lm5aweNPikBECc1bP4aMFH1FUUlSufFPBJuatmxcytUksrchZwYINCypd7V5EROLHzC4HFgIPA5fiTY9X+jo7+Cp9LyIiEbB43/ia2a7AW8G3KcDLzrk7zewCAOfcWDO7GLgQKAHygSudc99ub799+/Z106ZNq8HIRWRnZGbTnXOh8xlUXv8N4HjgaOfc+2b2LHCWcy45uL058CzQB+jjnIsosxccsj8ZSMNrO8c5526uUMeAMcAIIA84xzk3Y3v7rXbb+dlncNZZ3jyhfj/07g1vvFH5fMsbNnhzhX7xBfh83oJKjz4Khx0GnTrBpk1/1t11Vy/5mZwcfl8TJsD553s9+UtKYNAgePVVb+V7kRgpCZRw+YeX8/SPT+NL8uF3fq4ccCW3HXQbJ75+IuPnjd9Wt2OjjswYPYOmmeE7j89ZM4eT3ziZxZsWA16P/NdOfA1fko/9n92fTQWbAK9X/g0H3MBNB97E6PdG89qc1/Alew+9bh5yM1cPujpm57d081JOeuMkZq6aSXJSMk3Sm/DCcS8wrPOwau032rYz0ei+U0RqQri208wOAz4AtuAlSIcCA4ELgN2BE4DOwIPAzNocGRottZ0iUhOqet8ZdYI0UebRU2MrIjWhCgnS5cA651yv4PtyCdJgWUNgEV6S84II92tAVnBuZh/wNXCZc+77MnVGAJfgJUj7A2Occ/23t99qtZ2LF3srzJedaiQ52Ut0/vqrt6p8RQcc4C22VFSmh1xmptfjvmxytNTAgfBtmOdjs2d728oe2+eDvfaC6dOrdj4iYfxj0j8YM2VMuZ6jmb5MBrcfzCcLPwmp37lxZxZetjCkPLcolw4PdGBD/oZy5Y3SGlHkL9o2ZUlZB3c+mG+WflNuW6Yvk2eOfoZTep5SndMCIOACdHmoC0s2LSm3KGCmL5Of//4znRp3qvK+lSAVEYleJQnS94HDgQHOualhHr6n4iVOTwX2dc79VttxR0ptp4jUhKred0Y1xD44j95S4D3gfuAW4OYKr1uCr53PRx/BgAFeb6WDD4YpU7Zff9o06NLFSxqkpsJJJ4UOLy2rpMSrk5rqJR26dPH2IVKHvffre/R9oi8t72vJ4S8ezoyV2+3AWB81B+aXeV8CYGYZpQXOuRy83qBHRLpT5yldAc4XfFV84nUM8EKw7vdAYzOruWXdx46F4uLyZX6/N3z+q69C6y9Y4CUvi8oPHyYvL3xyFOC778KXjxkDhRUWCysuhnnzvOSpSAwEXICHfngoZFh9XnEeny78NOxnFm1axIotodNNjJ87niJ/UUh5QUlB2OQowKRFk0K25RXncedXd0Z6Ctv11ZKvWJu7tlxyFLxes49PezwmxxARkWrbD5jmnJsabqNzrgi4CK+H6c3h6oiISKiIE6Q75Tx60Xj9dTj+eC8punYtTJoEw4bBN9+Er//bb9Cvn5cgcM77Ij9unNf7qjLdu3t1ioshEPA+26+fty+ROui5H5/jlHGnMH3ldNbmreWj3z/igGcPYNqKnSqxvxFvGHypTcGf7SrUc0BUY8HNLNnMZgJrgE+ccxWfyrTFe6hValmwrOJ+RpvZNDObtnbt2mhCKG/x4tAEaalwc4iuWBG7xeoWLfKSsRX5fNHNhSqyHcX+4pDkaCkX8nziTws3hfYgXZ6znPzi0ERo6QJ80Rxj5daVlX4mGstzloc9RpG/iEWbIp9TVUREalQjvPlHSxXBtvU8AHDOFQPf4I38FBGRCETTg/TqYP3jnXNnAj8COOf+6Zw7BW/I/US8oZxjYx1oneYcXHVV+aGd4L2/5prwn7nwQu9zFf36K0wN8zBwypTwiVDn4O9/jz5mkRoWcAGu+fSasD2tbph0Q5yiioulQIcy7+fgTU0ysrQgeEO7PxDhSkQe55zfOdcbL9naz8x6VqgSdgqUMPt5wjnX1znXt0WLFtGEUN6wYZCVFVpeXAz9w4zs32uv0F6fsP2kaUpK+PKDD4b09NDywkLo06fy/YlEIS0ljc6NO4fdlpoc/u+tYfRtEzrCZ0C7AWT4MkLKM1MyKz1+6WJ7Ffc/qP2gSj8Tjf5t+1MSCB3JkuXLqvYcpCIiEjP/z959x0dVpX8c/zyZ9NB7LyKKgDRDUVTsChYsuIK9sthddddFV9eO9WdvqNjXjoqCFUUEG0GKIIIIogjSa3oy5/fHnUCSmYQkTEn5vl+veSVzbjnPDeHkznNPWY/XaalI0VwtnUrtlww0jkZAIiK1QWUSpAcAC5xzk0NtdM6tB07H6yl1SxhiqzkyM+Gvv0JvmzcvdPmcOWWfb9Kk4LIPPih7/x/q3JBlqQHWZ61nW+62kNtmr6pTc0JOA3qYWVHm8QO8BZPGmdndZnZ5YJ9mQPAEhhXgnNscOMcxpTatBIqvjtQOiFx3yjPPhFatvIWWiqSmwmmnQZcuwfs3buw9RCqeVI2Ph4YNYdiw0HX8t4yRYhdfDE2aeD1Gi6SlwaWXapEmCatHhj5CasLOJKZhpCakcs8R94Tc/9w+55IcH5y8H9JxCPu13o+U+J1J0pT4FHq36k2/VsFJ/TiL494j7y1Rd5zFkZaYxp2H3bk7l7RDlyZd+FuPv5WoI8mXRKt6rTiz15lhqUNERHbbb0DHYu/n4j0UH1VUYGYt8BZvWhHFuEREarTKJEgjMo9erZCS4r1CadOmcuXgrfpcWu/eZe/fNmjErEjMNUxqiC8u9Grj7RqUHl1eq70JfAn0BXDObQCuwZsz9FrgQWA/vGTmjRU9qZk1N7NGge9TgCOAn0vtNgk42zyDgC3OufCMxQ0lNdXrAX/VVV5CtHdveOABmDCh7GNuucXb3r8/dO4MF10Ec+fC5Mlw5ZU7e4wmJ3vn+s9/Qp+ncWPvwdMll3jn2W8/b07Ue+8N91VKHTe061A+PetTju5yNJ0aduLEbify9flfc+WgK/nkzE/o1KgTPvPRMKkhdx52JxOGh/79NzM+PvNjbjnkFro168beTffmpiE3MfXsqcy6aBZj9htDanwq8XHx9GnZh/lj5nPloCt5f9T7HNLpEDo17MRpPU5j1kWz6NGinOl5Kum54c/xwNEP0Ltlb7o07sJVg64iY3RGiaSpiIjE1FRgHzMrGqE0GW9Kp7Fm9rqZ3Q98D9QD3o1NiCIiNU+FV7E3s7+A75xzwwPv7wWuBroVXxnPzN4GhjnnysgYRkfUV8T773/hvvtKDrNPTYUnnoCzzw7e/+OP4ZjSnb3wejxt3x5cDlCvntdbtbSPPoKjj65a3CIRdM0n1/BkxpNBqz2/eOKLnNL9lBhGVnXhWonZzPYDRgBN8BKbzwV6glb0+F7AC4AP72HXG865W81sDIBz7snASveP4vUszQLOc86V2zBqNVERiYTqsoq9mR0DPITXdj7jnLurjP36A98Cpznn3trVedV2ikgklLGK/T54n8NfdM59FSgbDvwPKP4ZfA5wsHMuxAfI6kFtp4hEQlXvO8uYzC2k8ubReyAQRJXm0asVbroJcnLg0Ue9eUETE72kaajkKHgJzfvvh+uu27lyfYsW8PXXZdcxbx4ccIC3IjR4PavuvlvJUam27j7ibvx+P0/N9lY/TopPYtxh42pscjScnHOzgSrPNeCcm0+gV2qp8ieLfe/wVjEVEanzzMwHPAYciddrf5aZTXLO/RRiv7uBj6MfpYhI+Zxzi4CLSpW9Z2Z74X02L3r4Psk5F2IFy7Lt6iGSmf0TOCPwNh7YB2junNtoZr8B24BCoKA6PBQTEamMyiRIpwFXmllz59w6Ss6j1wrvRvNsvKH4E8MdaLXn83nJyltugQ0bvGRnQvBiCiVcfbU3FHXBAm/uvHa7GHbcpQusWQMrV8LGjdCzJ8RVZpYEkeiKj4vngWMeYNwR49iYvZEWaS2Ij6tMsyMiIhI2A4ClzrllAGb2GjAc+KnUfpcDbwP9oxueiEjVOef+BJ6q6vEVeYjknLsXuDew//HAP5xzG4ud5tDA2iQiIjVOZbJrEZlHr9ZJTvbmBN1VcrRIXBz06rXr5Ghx7dp5xyg5KjVEcnwybeq3qdPJUTNLNLNRZvaUmU02sw/MbLyZnW5mSbs+Qx308cew//7QsqXXU15DsKSYr//4msNfOJyW97XkoOcOYtpv02IdEgvWLmD4a8NpeV9L9hu/H+/+/C4Ac1fPpduj3fDd6iPxtkROfePUkKvFF/E7P09kPEG3R7vR+v7WnP/e+fy51Ruc88GSD+j/dH9a3teS4/53HPP+8haDzFiVwdEvH03L+1qy/7P78/FSdX4MoS3eiKgiKwNlO5hZW+Ak4El2wcxGm1mGmWWsW7curIGKiJTFzCaY2fkV2O9cMytnIvggOx4iOefygKKHSGUZBbxaifOLiFRrFZ6DtMwT7OY8epFSI+YzycyEp56CiROhaVO4/HI44ohYRyUi5ajKfCZmdgDevFDt8aYmKc7hfUg/wzk3IzxRVl21aTtfew0uuCB4XuepU2HQoNjFJdXCF8u/4LhXjys5v3F8Km+c+gbH7nVsTGJauHYhg54ZRGZ+Jg7v3io1IZXrBl/HLV/egt/5S+zftUlXlly+JOS5Lpl8CS/Me2HH9cVbPI1TGvOfg/7D2M/H7ig3jNSEVB4/9nEunnxx0HzPz57wLCN7jozE5VZadZiD1MxOBY52zl0YeH8WMMA5d3mxfd4E7nfOfWtmzwMfaA5SEYmVMuYg9QPPO+fKTZKa2dPA+c650KumBu8/AjimVBs50Dl3WYh9U/HuX/cs6kFqZsvxFotywFPOufG7qlNtp4hEQjTmIA1pd+fRq7OysmDAAFi+HLKzvbLPPvNWaB47NraxiUjYmFkP4BMgFViG96T9t8DmTsBpwJ7AR2Y20Dm3MAZhVi/OeVOQFE+Ogvf+uuvgyy9jE5dUG1d/fHWJZCBAVkEWV318VcwSpDd+cWOJ5ChAVn4Wt355a1ByFOCXjb8w8/eZDO4wuET5qm2reG7uc+QU5OwoK3AFbMvbxr+n/pvsguwd5Q5HVn4WV310VfDPIz+Laz6+htN6nIa3XpvgfZhvX+x9O2BVqX3SgdcCP7NmwDAzK3DOvRuVCEVEwicBCP4DVLZQfyzK6k11PDCz1PD6wc65VWbWAvjUzH52zk0PqsRsNDAaoEOHDqU3i4jEjMZox8rzz8Nvv+1MjoL34f/WW735RUWktrgVLzk6DtjLOXejc+7ZwOtGoBtwZ2CfW2IYZ/WxdSusL2P6qh9+iG4sUi0tXBf6OcLSjUsp9FdqPYqw+W7ldyWSo0UKy1kfY9LiSUFl8/6aR6IvMag8pyCH3ILcoHKHY1POppDnX5e1jq25W8sLu66ZBXQ1s85mlgiMBEr8IzjnOjvnOjnnOgFvAZcoOSoiNVQPYHMl9q/IQ6QiIyk1vN45tyrwdS3wDt6Q/SDOufHOuXTnXHrz5s0rEZ6ISGRVugdp4IbyFOAQvEbT4TWc04C3nXPBd+8S7L33gntHASQmwjffwLGx6QEjImE3BFjsnLsh1EbnnB/4j5kVtauSlua1hfn5wdvatg0ukzqnRVoL/tz2Z1B54+TG+OIqNJIw7Do07MCq7WV9jgytV6teIc8Tan5SnwWuK0RfniRfErmFwbdfib5E0hLTKhVTbeacKzCzy/BWp/cBE5xzC81sTGD7LucdFRGJhRBziR5YzvyiRavL9wMmV6KaHQ+RgD/xkqCnh4ilId797ZnFytKAOOfctsD3R+F1EhARqTEq1YM0MI/eEuBl4CJgKDAMuBB4CVhiZgeGO8haqVWr0Iss+f3efKQiUlukABXp9vgDkBzhWGqG+HhvTubU1JLlaWneNCRS511/0PWkJZRM/KUmpPKvwf+KUURw45AbSU0o+TubEp/C0D2Hhtw/NT6VUT1GBZX3aNGDXi17kRhXshdpUnwSp/U8jZT4lJLnSUhlzH5jQv48rhh4RZ1eHC8U59wU59xezrkuzrk7AmVPhkqOOufOrcj8oyIiUXBusZfDm57p3DJeZ+ItnrwGCPmAPhTnXAFQ9BBpEfBG0UOkogdJAScBnzjnMouVtQRmmNk84HtgsnPuo0pcn4hIzFX4rlnz6IXZZZfBW2+V7EUaFwfNm8PAgbGLS0TCbTHQugL7tQZ+iXAsNcftt0NeHjwZyFkkJMDNN8OZZ5Z7mNQNF6dfzOaczdw14y4KXSGGcdWgq2KaIB3WdRgPD32Yf336L3Lyc/Dj5+zeZ/Pw0Id59PtHue7T6yhwXs/QZqnNmHHeDOJCPSgFppw+hbPfPZtPfv2EOIujRVoLnjn+GQ7pdAiNkxszYc4EDCMxPpFxh41jTP8xdGrUiZu/vJl8fz44GJM+htsOvS2aPwIREYmc8wJfDZgAzACeLWPfPLweoN8GVqOvMOfcFGBKqbInS71/Hni+VNkyoHdl6hIRqW4qvIq9mb2N97RoHHBjYFho8e1xeN3orwcmOudGhDnWSqkRK+I9+yxccYXXW6qw0Bs6OmUKdOkS68hEpAyVXREvMBH948AQ59zMMvYZDHwJXBbrIZ7Vru3MzoYNG6BlSy9JKlJMXmEea7avoUVaC5Lik2IdDgAF/gL+2v4XTVKalOhR6vf7mbtmLs1Sm9GhYcUWpdiSs4XtedtpU79NiYWWsvKz2JC1gdb1W5foIZpfmM+azDU0TWlKSkJKqFPGTHVYxT6Sql3bKSK1Qhmr2P+G17szdk8Fw0Rtp4hEQjRWsdc8euF2wQUwciRkZEDDhtC7N2ilWZFaxTk33sy64fWufxx4BVge2NwJOAO4BHgo1snRaiklBdq1i3UUUk0l+hJp37D9rneMovi4eNo1CP6djYuLo1/rfpU6V8PkhjRMbhhUnpqQSmrD1KDyBF9CyLpFRKT2CCwiJyIiYVaZBGll5tEbXrVw6qC0NBgyJNZRiEiEmFnxJayvDbxCucrMripV5pxzmkBQRERERIIEFkzqDzQHVjjnvo5xSCIiNVZlPnhrHr2qys2FN9+EGTNgzz3h3HOhWTMoKIA77/S2NW7sza932GGxjlZqsD+3/skL815g5daVHLHHEZyw9wlaoCP2dqdbuLqUi8TQyq0reX7u86zatooj9ziS4/c+nvi4eLbkbOGl+S+xYO0C0tukM6rnqLCvFr9803Ken/s867PWc+xex3LMnscQZ3Gsz1rPi/NeZMmGJQxuP5hTe5xKcnwyWflZvL7gdb778zv2abYPZ/c+m8YpjSn0FzL5l8l8vPRjWqS14Ly+51V4iL+IiFRPgcToA3gjkYpu9l8Avg5svwT4D3Cyc+7bmAQpIlLDVGYOUs2jVxWbNnmLLq1eDdu3e8NF4+Phk0/g+ONh/fqS+191FTzwQExClZrt8+Wfc8KrJ1DgLyC3MJd6ifXYp9k+fHnul9VuLrqaTPPoidQNn/76KSe+fiKF/kKvTU2oR48WPXj6+Kc59IVDyS7IJis/i7SENBomN2TWRbNoU79NWOp++6e3OeudsyjwF5Dvz6deYj0OaH8Adxx2B4e/eDj5hflkF2RTL6EeLeu1ZPLpkznq5aPYkLWBzPxMUhNSSfQl8sU5X3DFh1cw5685bM/bTqIvkfi4eN489U2GdR0WllgrSm2niEjllTEHaRreIk29gbVABjAMeN45d35gnz2ApcA9zrl/RzfqilPbKSKRUNX7ztDLp4bgnBsPPIw3j97dZtbLzOoHXvua2V3Ah2gevZJuvRVWrPCSo+AtOLJtGwwdGpwcBXjwwdDlIuUo9Bcy6u1RZOZnkluYC8D2vO0sWLuAx2Y9FuPoRERqlkJ/IadPPJ2s/KydbWr+duavmc9Jr5/ExuyNZOVnAZCZn8na7Wu55uNrwlJ3dn425753LtkF2d6K9Hjt+czfZ3LiayeyNXcr2QXZO2L6Y+sfnPj6iazatorM/EzAW8RpS84WTnrtJGavns32PO8eJK8wj6z8LM6YeAb5hflhiVdERKLuWrzk6MvAHs6540rvEFhVfgmg4YkiIhVU4QRpYB69K4FUvEZ5DrA58JoL/BNIw5tHr7DUqyDMcdccb74JeXnB5Zs3l33MU09FLBypnRasXbDjw3px2QXZvPLjKzGISESk5pq3Zh65BblB5dkF2fy66VccJUffFLgC3l/yfljqnvnHTOIs+PYsMz+TVdtWBZXnFeaxeP1iCvwlb7Ucjt+2/Bbyb4Pf+Zm1alZY4hURkag7FVgFXOScC27kd/odaBudkEREar4KJ0jx5sKr6qsy9dQuiYmVPyZFw6GlcpLik/A7f8htyb7kKEcjoZjZnmZ2r5nNMLPFZnZPsW2DzGy0mTWKYYgiEpDoSyyzTbUypgZO8CWEpe4kXxIVnf6oiFnomMqK1e/8JPmSKh2biIhUC3sAs5xzwU/ySloPNI1CPCIitUJlhtjH7c4rkhdRrV14YXDC0+eDjh1D728Gl1wS+bikVtm76d60rd826MNwWkIaY9LHxCgqKWJmFwALgGuAA4A9gWbFdmkOPAGcFP3oRKS0Hs170LJey6DytIQ09muzHwlxJZOhSb4kzup1Vljq3r/9/iTFBycv0xLS6NGiBz7zlShPiU9h/3b7Bz0Mi4+Lp2+rvqQlBC8e1Si5EX1b9w1LvCIiEnX5QEV6QLQDtkc4FhGRWqPuJi6j5dprYcgQSEvzEqX160O7djBtGuy3X/D+Tz4JyerxJ5VjZrw78l2apTajfmJ9UuJTSIlP4cRuJ3JW7/B8aJeqCSxe9xSQgzcVyUCCV6f/CNgKnBDd6EQkFDPjvZHv7WhTUxNSSYlP4ZTupzD59Mns2WTPHW1tvcR69GnVhzsPvzMsdcfHxfPBqA9omNTQqzveq/uifhcx5fQptG/QfkfdaQlpDO4wmPdHvU9623TSEtJIiU+hfmJ9OjfqzOTTJ3P6vqeTEp9CakIq9RPr0ySlCe+Pej/kMH4REakRFgN9zazMD41m1hhvntIfoxaViEgNFx/rAGq9xET48EOYPRu+/97rOXr00V4v0owM+PxzmDABmjaFG2+EZs12fU6RELo3787Kq1cy5Zcp/LX9Lw7qcBA9WvSIdVgC/wIcMNQ59w0ED4d1zuWb2WJgn+iHJyKh9GzRk5X/8NrUNZlrOLjjwXRv3h2ABZcs4IvlX7BkwxJ6tezFAe0PKHOYe1UMbDeQ1des5v0l77MpexNH7HEEXZp0AWDpFUv55NdP+G3zb6S3Sad/2/4ATD93Ot+u/JZ5a+bRpXEXDt/jcOIsjvHHj+ea/a9h2m/TaJbajGP3OpbkeD2IFRGpwd4C7gq8ripjnzuBesAbUYpJRKTGsyrMc7Un8Hdgf7xhoe855/4V2DYI6AW84ZzbHN5QKyc9Pd1lZGTEMgQRqYXMbLZzLr0S+68FfnHODS5W5geed86dX6zsTeAo51zDsAZcSWo7RSQSKtt21jRqO0UkEkK1nWaWCswCugHfABOB+4BpwJt4izgNwes9OsA5F2LF4OpBbaeIREJV7zsr1YM0MI/eY0DRykOO0PPo5QPPVTaYWsvvh5de8nqSdusG//73zmH0L74Ijz4KTZrAE09A585e+dq1MHmy9/1xx0Hz5t73mZnw/vuwZQsccQR06VJ+3c7BzJkwfz7suad3TFzdHVbnnOP7P79n9urZdGrUiaO7HI0vzrfrA0WqriGwsgL7JaJe/SIiIiJSDudclpkdhZcMPQCv4xJ4SdEheFM5zQZOrM7JURGR6qbCH8aLzaO3HbgBmA58V2q34vPoKUEKsHWrl/TcuHFn2e23e3OQnnwyrFu3s3yPPeCf/4Tu3eHii71h+OAt2vTkk9C1Kwwd6iU9Cwu9xOvFF8P993uLO5WWmeklRBcs8PaPj4fWreGrr6BFi4hednWUU5DDsFeG8f2f3+N3fuLj4mma0pSvzv+Kdg3axTo8qb3WAp0rsN/ewJ8RjkVEQsgryOP5ec+TlZ/FuX3OpVFyo10es2zTMn7d+Cv7NN+nxN+QtZlrmffXPNo3bE+3Zt0iGHVsLFy7kNXbV9O3VV+apmpxZBGRWHDO/QkcYGbHAMPwVrb3AX8AHwLvusoOFZXKmTQJbr4Z/vgD+vaFceNCrzEiIjVGZXoraR69qjjllJLJUfCSlYceCgUFwfvfe6/XuzQnp2T5mDGQlOQlXIsbP96b0/Too4PP9Z//wJw5kJu7syw7Gy66CN57r2rXU4ON+2oc36z8hpyCnT/brPwsznrnLL4454sYRia13ExghJmlO+dCjiEysyOBvYBnohqZiPDyvJc5571z8Ds/AP/4+B/8e/C/GXfEuJD7Z+Vn8bc3/8bU5VNJ8iWRU5DDqd1PZcLwCVz32XU8kfEESb4k8v359GnVhw9GfUDjlMbRvKSIWJe5jmH/G8ZP634iIS6BnIIcrtn/Gm4/7Pawzr8qIiIV55z7CK+TkkTTc8/BZZdBVpb3/tNPvVGbX34J6bV2NhmRWq8yY633B74vSo6W4w+gddVDqmWmTQtdHio5WiQ/P7issDA4aQpeL9FnysipvPhiyeRoUb0ffgh5dW+0xYS5E0okRwEKXSEzf5/JlpwtMYpK6oAH8IY6TTSzo8xKLh1tZgcDE4AC4JEYxCdSZ23O2czZ7569Izla5K6ZdzH9t+khj/nHx/9g6vKp5BTksCV3C7mFuUz8eSJ/e/NvjJ89fkd5Vn4WGX9mcNY7Z0XjUiLu1DdPZd5f88jKz9px3Q999xBv/vRmrEMTERGJnsJC+Ne/diZHi2RlwdixsYlJRMKiMglSzaNXFX7/rvepyDHlnad0ErRIWUlYv79qcdVwBYWhfx5mRoG/nIS1yG5wzn2H1wO/Hd6Qpw14vfFPNLM1wBdAW+BfzrkfYxaoSB10x/Q7cIQegXjjFzcGlfmdnxfnvRj0sC0rP4v3l7xPZn5mifI8fx6fLfuMTdmbwhd0DKzetprvVn5Hvr/kA9zM/Ez+75v/i1FUIiJ1m5klmtkoM3vKzCab2QdmNt7MTjezpFjHV2tt2ADbtoXeNnt2dGMRkbCqTIJU8+hVRZ8+lT8mKcTfs8TE0IsrpaXB6aeHPs/w4d68o8WZwf7771wkqg4Z0X0EiXGJQeXdmnXTPGoSUc65+/Hmh8oAGuD1KG2Et7DdArxJ9B+MVXwiddWa7WvK3LYhe0NQWX5hPnmFoUdgFLrCkOU+87E1d2vIbTXF5pzNxPtCP/sO9XMSEZHIMrMDgCXAy8BFwFC8e80LgZeAJWZ2YOwirMUaNdq5Vkhp7dtHNRQRCa/KJEhnAv3MrMxJNYrNozdtN+OqPSZOhISE4PIJE0KXH3ssXH01pKZ6CdG4OO/7q6+GF16AlJSdx9WrB4ccAqeeGrrue+7xFmVKS/Pep6ZC48ZlD8mv5W459BbaN2xPvcR6AKTEp9AwqSEvnvhijCOTusA595FzbiDQAhiAN21JO+dcb+fcpNhGJ1I3ndPnnDK3nbzPyUFlSfFJ7Nti36Byw2hXvx3xccFJxIbJDWnfsGZ/YOratCuJvuAHjAlxCRy/1/ExiEhEpO4ysx7AJ0AHYDlwB16S9KLA978C7YGPAvtKOCUmegslp6aWLE9Nhf/+NzYxiUhYVGYo/APAqXjz6F0IfFZ8o+bRK0PHjrB+vTcfyfTp3kr199wDe+8NZ5wBo0fDBx94yc4779zZG/Tkk+G117zvR47cuSLefvt5c4tu2uQlU484InTPUoBWreDnn+H11yEjA/bZB846Cxo2jPx1V0NNUpqw4JIFvPXTW3y78lv2bLInZ/c+myYpTWIdmtQhzrkNeMPsRSTGDt/jcHq16MX8tfNLlDdKasR/DvpPyGOePO5JjnjxCHILcilwBST6EkmOT+alk17ib2/9ja25W8ktzMVnPpLik3j6+KeJs8o8j65+4uPieeq4pzjn3XPIKcjB7/wkxyfTOLkx/z7w37EOT0SkrrkVSAXGATc6V3IibTP7b2Cf64FbgBFRj7C2u+sucA6efNKbui4lxVvF/uTgh6siUnOYc6Hn3gq5s9k1wL148+dtxRsqugXIB5rhDRu9ujoMFU1PT3cZGSEXjBYRqTIzm+2cC8vylGbWFegFrChrhftoU9spdY3f7+fGaTfy9OynKfAXcGK3E3l06KOkJqaWeczSjUv5v2/+jx/X/MiAdgO4auBVtG/YnnWZ63jk+0eY9ts0ujTuwj/2/we9WvaK4tVE1g+rf+DBbx/kt82/ceQeR3LpgEsr/JAxnG1ndaS2U0QiIVTbaWbrgXXOuX12cewioLlzrlkkY9wdNb7tzM31Oi41b172sHsRibqq3ndWKkEaqOgYvCdR6XgJ0SI/4j3BqhZDRSPa2DoHCxbAunVej87iPTKXL4dly6B7d294e5FFi7yeon36wJFH7izfvh1mzfLmMunTx5sjtKj85pu972++2ethKtWKc44fVv/AtrxtDGg7gNSEsj9MF/lz65/8vP5n9myyJx0bdYxClBJulW1szexkvPmgbgks2FRUfiPwX3a2o686584Ma7BVUONvVEWkWlKCVESk8spIkGYC7zrnztjFsa8Aw51z1faDpNpOEYmEqt53Vnq1eefcR3jzmTTFW7TJB/zhnFtV2XMVMbPfgG1AIVAQ4o+AAQ/hTTydBZzrnPuhqvXtlj//hKFDvSRofDzk5cFtt3nzkJx6Knz+ubfIUm4ujBoFTz0FAwfCnDk7z9GwIcyfDx9/DFdd5Z2nsBDatIEPP4QHHoDHHtu5//33w2WXwSOauaC6WLx+McP+N4y1mWuJszgK/YU8OvRRzu17bsj9C/wFnP/e+byx8A2S45PJLczlqC5H8fqI10mOr3sLZtUxZwIH4z1EAsDMeuI9aCoAvgV6AKPMbKJzbmJMohQRERGRmmAx0HqXe3n7/BLhWEREao1KJ0iLRGAevUOdc+vL2DYU6Bp4DQSeCHyNvuOOg59+8hKaRW66CaZOhS++gJwc7wXe3J9z5sDcuSXPsWUL9OsH2dmQlbWz/Ndf4cAD4a+/gut99FE499ydc5FKzPidnyNfOpKVW1fi2NkD+9IPL6V3q970bd036Jg7v7qTtxe9TW5hLrmFuQB88usnXPvJtTw67NGoxS4x0ReY55wr9p+dM/GmKrnQOfeime0B/IQ3ub4SpCIiIiJSlieBx81ssHNuZqgdzGww3gP6y6IamYhIDRaWVQPMrKuZnVLeCve7aTjwovN8CzQys4o8NQuvxYthyZKSyVHwkpwff7wzMVq8vHRytMiGDSWTo+BN8Lx2bdn1jxlT6ZAl/Gb8PoPNOZtLJEcBcgpyeCLjiZDHPPr9o2TlZwXtP2HOBCo7zYXUOE2BP0uVDQG2A/8DcM4tA2YA5c4lJVLX+J2f+76+j1b3tSLhtgT6PdWP6SumR6XuAn8Bt355K83uaUbCbQns/+z+zPpzVljrWJ+1njMnnknqHamk3JHCaW+dxprta8Jah4iI1C7OufHAw3ijOu82s15mVj/w2tfM7gI+BB5yzj0Z22hFRGqOCidIzexkM5tiZgNLld8ILALeAL4zs5erEIcDPjGz2WY2OsT2tsAfxd6vDJSVjnG0mWWYWca6deuqEMYubNjgDYcPxe8PXV5Z5SXLNm0KTx2yWzZmb8TMgsr9zs+azNAfbLflbQtZnlOQQ4G/IKzxSbWTRLH5ms0sEegDfOOcK/6P/xfQMrqhiVRvN0y9gf9O+y9rMtdQ4C9gzl9zGPrKUDJWRX6+sosnX8zdM+9mQ/YGCvwFfLvyWw594VB+Xv9zWM5f4C/ggGcP4I2Fb5BdkE1OQQ4TF01k0DODyCvMC0sdIiJS+5hZIXAl3kr21wJzgM2B11zgn0AacJWZFZZ66YOHiEgZKtODtLx59PzATLxGeVRgUZLKGOyc64c3lP5SMzu41PbgbBQEZRKdc+Odc+nOufTmzZtXMoQK6NMnuPcoQHKyt3JdaWaQWsbCPWaQkhJcHlfOP8mpp1YoTImsA9ofEPLDa1pCGifsdULIYw7scCAW4te4d6veJPgSwh6jVCurge7F3h+MlzQtPSSqHrA1WkGJVHeZeZk89N1DQb3vs/OzuXnazRGte33Wel6e/3LInv93zbgrLHVMXjKZv7b/Rb4/f0dZgb+A9dnreffnd8NSh4iI1Eq2G6+wjCAVEamNKtNA7moevYOB/kA+3jx6FVa0wJNzbi3wDjCg1C4rgfbF3rcDqrwoVJWlpnoLKKWm7lxtPjnZW63+hRe8cp/PK09MhPr14dlnd+5b3A03wB57lEySpqbCLbd45ywtORluvz381ySV1iKtBdcfeD1pCWk7ylITUtmzyZ6c0Sv0YpIPHv0g9RLrkRDnJUPjLZ60hDSeODb0kHypVb4EupnZv8ysF3AbXrv5Uan9euK1dSIC/LntT3xxvqByh+PHtT+GOCJ8lm5cSpIvKai80BUyZ/WcEEdU3k/rfgpKwAJsz9vOT+t+CksdIiJS+zjn4nbnFev464Sff/bWI/n++/JHiIpItVKZBjIi8+iZWZqZ1S/6HjgKWFBqt0nA2eYZBGxxzq2uROzhc9FF8MknMGKEt6DSzTd7CzENHQo//AAXXAAHHACXXgoLF8LIkd6K9Qcc4CVMu3SBN9+E226D776DcePgoIPgxBNh0iQvcbpxIxxxhDecPz7e+37Llp3JV4m5G4fcyMTTJjJ87+Ec3OFg7j7ibr654JsyV6Tv0aIHCy5ZwCX9L+GAdgdwYb8LmfP3OQxqNyjKkUsM3IHXTo7DGwI1EJjqnNsxmaGZ7QXsAXwXkwhFqqG29dtS6A8xagPo3rx7yPJw2aPxHuQW5AaV+8xH71a9w1JHt2bdSE0IHmVSL7Ee3Zp1C0sdIiIilWFmx5jZYjNbamb/DrH9EDPbYmZzA6+bKnpsnZCfDyef7C3IfNFFcNhhkJ7ufb4XkWqvMqvYlzWP3pch5tEbXInztgTeCczpGA/8zzn3kZmNAQhMLD0FGAYsBbKA8ypx/vAbPNh7lbb33vDUU8HlPXvCzBALDKalwZVXeq/iUlLg00/DE6tEzFFdjuKoLkdVeP8ODTvw4DEPRi4gqZacc0sCK4leDbQAvgfuLbXb4cA84IMohydSbaUlpnFJ/0t4IuOJEj0tUxNSuXnIzRGtu0VaC0b2HMnrC18nuyB7R3lSfBL/PjA8n/mO2+s4mqU2I7sge8dc1PEWT+PkxpzU7aSw1CEiIlJRZuYDHgOOxBvVNMvMJjnnSg9r+Mo5d1wVj63d7r4bPvoIsnfeO/Djj3DhhTBxYuziEpEKqUyCNCLz6AV6nQZ1xyi+4p7zlvm+tBKxVj85OV7j2LkzNGu2s9zv91a6b9YMOnSIWXhSNVtytpCVn0Wreq1CLtwkAuCcWwCcX872JwDNtyBSyj1H3kPj5Mb837f/x6bsTXRv3p2HjnmIge0G7vrg3TT++PG0qteKxzMeZ1vuNvq26ssjwx4JW+/VBF8C31zwDZdOuZRJiyfhcBzX9TgeO/YxkuKDh/eLiIhE2ABgaeDzOWb2GjAcqEiSc3eOrT2efLJkchS8XqWTJ3vlodYgEZFqozIJ0i+BM83sX3hz52kevYo6+2x4+eWd84/06OHNR/LEE3DddTsXfmrRAr7+2huGL9Xa+qz1nDXxLD7/7XPiiKN1/dY8N/w5hnQaEuvQRERqjTiL44aDb+CGg2/AORfVB1EJvgTGHTGOcUeMi1jdLeu15K2/vYUL3B/oQZuIiMRQW+CPYu9X4k0NVdr+ZjYPb02Qa51zCytxLGY2GhgN0KG2dRDKCp5bHPDyALm5SpCKVHOVmYNU8+hVxQ03wEsvlZyceeFC2GcfuPbanclRgLVroXd45jaTyHHOceRLRzJ1+VTyCvPIKcxh+eblHPu/Y1m2aVmswxMRqZVimTyMdN1mpuSoiIjEWqg/RKVXGPoB6Oic6w08ArxbiWO9QufGO+fSnXPpzZs3r2qs1dOwYaHXDenWDRo1ino4IlI5FU6QOueW4M0t+gLwIXAzXrf54jSPXmkPPhi6/PffQ5dnZsJbb0UsHNl9P6z+gV82/EK+P79EeV5hHo9+/2iMohIREREREamylUD7Yu/b4fUS3cE5t9U5tz3w/RQgwcyaVeTYOuHuu72p84p6iiYmQr168OyzsY1LRCqkMkPsNY9eVZSeg6Qi5s2DESPCH4uExYotK/DFBT8ZzPfns2TDkhhEJHWRmbUHXgRaAX5gvHPuoVL7HAK8BywPFE10zt0axTCllvhlwy88/N3DLFq/iAM7HMgl/S+hRVqLWIcVFs45Pv71Y57+4Wmy87M5Y98zOK3nacTHxfPD6h945LtHWLl1JcfudSwX9L2A+kn1Yx1yWBT6C3nzpzd5ef7LJPoSOb/v+Rzb9Vj1ZBURqbtmAV3NrDPwJzASOL34DmbWCljjnHNmNgCvw9UGYPOujq0T2raFn3/2EqJff+2NGh0zBtq1i3VkIlIBlUqQShU0b+4Nna+ME06ITCwSFv1a9yOvMC+oPCU+hUM6HRL9gKSuKgCucc79YGb1gdlm9mlFVhoVqYyvVnzFMa8cQ15hHgX+Amb+PpPHvn+MjNEZdGzUMdbh7bZ/fvpPnsx4ksz8TACmr5jOS/Nf4sxeZzL6/dHkFubid35m/jGTR79/lNmjZ9MwuWGMo949zjlOfuNkpi6buuO6P/n1E87rcx6PDHskxtGJiEgsOOcKzOwy4GPAB0xwzi00szGB7U8CI4CLzawAyAZGBhZUDnlsTC4k1ho1gmuu8V4iUqNUZg5SqYrHHgtdfsIJEKqXxt57Q//+kY1JdkunRp34W4+/kZqQuqMsPi6eRsmNuLDfhTGMTOoS59xq59wPge+3AYvwJsgXCRvnHOdPOp+s/CwK/AUA5BTmsDFnI2Onjo1xdLvv142/8tisx3YkCQEy8zOZ8fsMRr8/muyCbPzOD0B2QTZ/bvuTR76v+QnEz5d/XiI5Ct51PzvnWX5e/3MMIxMRkVhyzk1xzu3lnOvinLsjUPZkIDmKc+5R51wP51xv59wg59zX5R0rIlKTKEEaaSNGwJtveivUm0FqKowdC++9561kv9deEBcHCQlw6qmwYEGsI5YKmHDCBMYdPo6uTbrSql4rzu9zPrNHz6ZRcqNYhyZ1kJl1AvoSeoG8/c1snpl9aGY9ohuZ1HSbcjbx++bgObP9zs/Hv34cg4jC6/PlnxNnwbdCmfmZOxLCxeUU5DBx0cRohBZRHy39qERytIjD8dmyz2IQkYiIiIhIbGmIfTSMGBF6TtH0dFi8OPrxyG7zxfm4YuAVXDHwiliHInWcmdUD3gaucs5tLbW5aKXR7WY2DG+l0a4hzjEaGA3QoUOHyAYsNUpyfHLodWmBBokNohtMBDRKboTPgueUjrd4XOjFd2mS0iTSYUVco+RGJPoSg6aLKRoNISIiIiJS16gHqYhIDWVmCXjJ0Vecc0Hd2spZabT0fuOdc+nOufTmzZtHPG6pOVITUhm+93ASfYlB5ZcOuDRGUYXPsXsdG7IHaYIvga5NugYlT9MS0rhy4JXRCi9izup9VsjEsGEM33t4DCISEREREYktJUij4cEHvaH1ZhAfD6efDn5/2fsvXgxdunj7m3nfV7Wn6ZQp0LMnJCbCHnvASy9V7TwiUq2Yt9T0s8Ai59z/lbFPq8B+lFppVKTCnj7+aQa2HUhqQioNkxqSHJ/Mqd1P5R+D/hHr0HZbakIqH5/5Mc1Sm9EgqQENkhqQlpDG8yc+z8dnfkzXJl2pl1iPBkkNSI5P5p8H/JPj9z4+1mHvtg4NO/C/U/6349rqJ9anSUoTPjzjQ+on1Y91eCIiIiIiUach9pE2fjz8o9iHyMJCePVV2LABPg4xf1tWFuy7L+Tn7yxbtswr27oVkpMrXvdHH3lD+7OzvffLl8OYMd770aOrdj0iUl0MBs4CfjSzuYGy64EOsMuVRkUqrGFyQ6afN52f1v3E8k3L6dWyF+0bto91WGEzsN1AVl+zmpm/zyS3MJcDOxy4YxG+ny79idmrZ7M2cy0D2g6gWWpQB+wa68RuJ7L22rXM+H0GCb4EBrcfTIIvIdZhiYiIiIjEhHqQRtp114Uu/+QT2L49uPyWW0omR4vk53vbKmPs2J3J0SJZWfCf/4ByJCI1mnNuhnPOnHO9nHN9Aq8pFV1pVKSyujfvzrF7HVurkqNF4uPiGdJpCEd1OWpHchTAzEhvk86wrsNqVXK0SEpCCkd2OZJDOh2i5KiIiNR+Gzd6n8/32stbD+SFF8r/XFxYCEOHgs/njexs0QKmT/e2TZ8ORx3ljfY844ydIz4XLYKRI73yo4+Gr74qP6bsbLjzTuje3esU9fDDofMBIhJx6kEaaVu2lL1t4UIYOLBkWUZG2fvPmlW5upcsCV2+aZOXKE1Lq9z5RERERERERGqa7du9pOiff0JeYJHCSy+F776Dxx8PfUyHDrBq1c7369bBkCFw111w663eZ2qA336DSZPg+efh3HO9cr/fGwk6Ywa8/DKcdFLw+QsLvfMtWLCzY9PYsd5I0MmTvaSsiESNepBGWv1y5vLae+/gsl69yt6/vG2hdOoUurxBA29OVBERkSjbkLWBV+a/wmsLXmNLTjkPEXfDkg1LeG7Oc0z5ZQoF/oId5X9t/4uX5r3EmwvfJDMvMyJ1S/VmZseY2WIzW2pm/w6x/Qwzmx94fW1mvWMRp4iIhNnzz8OaNTuTowCZmfDcc/DHH8H7f/ttyeRocddfvzM5Cl4yNDMTLr7YS8QWX28kKwuuuCJ0T9UpU7wep8VHfWZleb1Tv/++UpcnIrtPPUgj7b//hWuuCS4fPBgaNQouv+UWeOQR72lScT6f95SqMu64w+vuX7zxTk2FG2/U0ygREYm65+c8z8VTLiY+Lh7DKPQX8vLJL3PSPiF6VVSB3/m54L0LeH3h68RZHHEWR73Eenx57pdM/mUyY6eO3VG3w/Huae9y+B6Hh6Vuqf7MzAc8BhwJrARmmdkk59xPxXZbDgxxzm0ys6HAeGBg8NlERKRG+fTTkp+LiyQmesnI9qWmEHr66bLPFWrBZedg/frQ+69d643ibNKkZPmMGaGn3cvPh6+/Dh5tKiIRpR6kkXb11V5CMj6Qizbz5iqZNi30/g0aeE+rWrbcWdaypddo16tXubpPPBGeecYbGmAGzZt7wwGuvLIqVyIiIlJlyzct55Ipl5BTkMP2vO1sy9tGVkEWZ0w8g3WZ68JSx8vzX+bNn94kuyCbzPxMtuVt46/tf3HMK8dw/dTrS9S9PW87J75+onqS1i0DgKXOuWXOuTzgNWB48R2cc1875zYF3n4LtItyjCIiEgmdOu38TF6c3w9t2gSX77tv5esIdX7wOjuF+izfrh2kpASXJyWFjklEIkoJ0mi49VbvKVB2NhQUeKvXl9V4gjc3yl9/ecfk53vf9+tXtbpHjYIVK7yhBGvXwuWXq/eoiIhE3WsLXisx3L2ImfHOz++EpY7HZz1OZn7JhKfD8fuW38kpyAmuG+PDpR+GpW6pEdoCxcdRrgyUleUCoMxfEDMbbWYZZpaxbl14kvwiIhIhl1zi9RYtLj4e2raFQYOC97/qqrI/Nw8YEJzYTEuDc84JnsouJQUuuCC4boDTTw/OC5h5CdLhw4P3F5GIUoI0mpKTIa4SP/L4+PITqZURrvOIiIhUQVZ+FoX+wqByv/OTnZ8d4ojKK+s8RUPqS3O4sNUtNUKoT7ohly82s0PxEqTXlXUy59x451y6cy69efPmYQpRREQiYu+94a23vJXo09K8z+bp6TB1atmJ0GnTvN6fxZ1wgjdH6KmneonM+vW9pOj118NTT8F113nv69f3tp92Gtx/f+jzN20Kn33m9W5NTfWSqd26wZdfevGJSFQpQRpOX38NQ4fCHnt4DeaCBVU7T24u3Hsv9Ojhve691ysTERGpoYZ3G05yQuib/WP3OjYsdYzadxQp8cFD1VITUkmND16csMBfwFFdjgpL3VIjrASKTzLXDghagcPMegHPAMOdcxuiFJuIiETa0KHewksZGbB0KXzzjdeDtCwHH+yNAP3kE2+l+23b4L33vMTnCy/A6tXe9Hjr1nkJ0rg4uOkm7/2333rbn3sudO/RIgMGeKvdz5sHCxfCTz9B9+7hv3YR2SUlSMNl8mQ48kj46CNYvhwmTvS66s+eXbnzOOfNUfrf/3qN408/ed8ffXTole9ERERqgPQ26ZzT+xzSEtIwjDiLIzUhlWv3v5Y9m+wZljouH3A5ezXdi3oJ3jxfib5EUhNSeWPEGxy/9/HUS/TKfeYjJT6FcYePo2W9luWdUmqXWUBXM+tsZonASGBS8R3MrAMwETjLObckBjGKiEgk+XxeL83yEqOlHXmkt0J96XlEGzf2kpmlh9WnpnrljRtX7PxmsOee0LlzxWMSkbDTuOtwcM6b27P4qnh+P2RmwrXXwhdfVPxcn38OP/zgzVdaJDvbS7R+8QUcdlj44hYREYmix4Y9xsieI3ltwWv44nycue+ZDGwXvhVa0xLT+P6i73nrp7f4bNlntG/QnvP7nk/HRh05ssuRfLrsU95e9DZpCWmc0/scerfqHba6pfpzzhWY2WXAx4APmOCcW2hmYwLbnwRuApoCj5s35LLAOZceq5hFREREJDqUIA2HrCz444/Q277/vnLn+uabkonW4nV8/bUSpCIiUmOZGQd3PJiDOx4csToSfYmcvu/pnL7v6UF1H9XlKA2pr+Occ1OAKaXKniz2/YXAhdGOS0RERERiS0PswyE52ZuHJJQWLSp3rjZtglfEA6+sMsMAREREREREREREZJeUIA0Hnw/GjAk998i//lW5c516augV5xMSYMSIqscoIlKdZWV5E9prruU6YXPOZrbmbo11GBWSnZ/Nusx1uAj+bm7J2cLmnM0RO7+IiIiIiJRPCdJwGTcOzjzT601av76XHL3mGi9xWhn168OXX0LXrl6v0ZQU7/tp07xtIiK1yfbtcMYZ0KQJtG/vTU7/ySexjkoi5Of1P9P/6f60uLcFze5pxsHPHcxvm3+LdVghZeVnce6759L47sa0f6A9HR7swOQlk8Nax7JNyzhwwoE0v7c5Le5twaBnBrFkg9YFEhERERGJNiVIwyUhAZ56Clav9uYRXbsWbr3VW5Gusnr3hsWLYcEC77V4sVcmIlLbjBgBb78Nubnea8UKOOkkmD8/1pFJmG3L3cbgCYOZvWo2+f588v35zPxjJoMnDCavMC/W4QU54+0zeH3h6+QW5pJbmMvKrSv521t/Y/aq2WE5f05BDgc8ewDfrPxmx8/j+z+/Z/CEwWTmZYalDhERESll7Vq4/3544QVvYeUi27fD1VfDlVfCxo07y53zFlGeOhW2VnD0y+rV8OmnsHRpeGMXkYhSgjTcGjWCHj0gLW33zmMGe+zhvaqSZBURqe5++83rMZ+bW7I8Jwfuuy8mIUnkvLbgNXILcnHsHKrud3625W7j/cXvxzCyYKu2reKjXz8ipyCnRHl2fjZ3zbgrLHW8+/O7ZOZn4nc7P5w5HNn52bz101thqUNERESKufBCaNkSrr0Wzj0XEhNhyhQvMVq/PjzwADz8MDRtChdcAMuXQ7duMGQInHwytGoFjz5a9vkLC+Gii7zP8KeeCr16wZFHeslXEan2lCAtKIBXX4UTT4SzzoLp08Nfx4oVcOyx3oJN/fp5T5/ASwo8/zwMHw7nn1/5Fe9FRGqyFStCL3Dn93s956VW+XXTr2TmB/eMzCnIqXbD7H/f8jtJvuDfTYdjycbwDIFfvmk52fnZQeWZ+ZnV7uchIiJS473+Ojz7bMmywkI47jgvMVrahAlw0EFeL9Dt273eo9nZcN11MGNG6DoefBD+9z/vYf+WLd7+X30FF18c9ssRkfALsRpQHVJYCMccA99+C5mZXk/NiRNh7Fj4z3/CU8fcubDffju7769bB0ccAffcA2++CT/95NUdF+c12vfeC5dcEp66RUSqs+7dvRvI0hIT4cADox+PRFT/Nv2pl1iP7Xkle1EkxSfRr3W/GEUVWrdm3cgtzA0qT4hLYHD7wWGpY782+5GSkBL086iXWI++rfuGpQ4REREJuPnm0OXlLcK4alXw9uxseOSR0PeqjzziLTxaXG6u97n/6ae99UpEpNqq2z1IJ02C777zEpTgNX5ZWXD77d68IeEwcmTJuU2K/Pvf8OOPO+v2+726r73We9okIlLbNW8Of/+7t6hdkbg47/3VV8cuLomIE/Y+gfYN2pPoS9xRlhyfTI/mPTik0yGxCyyERsmNuGLAFaQm7PzdjLM4UhJSuG7wdWGp44g9jmCvpnuV6Kma5Euic6POHNv12LDUISIiIgGbN1f+mFDJU+e8eUxDKetzvN/vJVZFpFqr2wnSd98NPR9IQgJ8/nl46vjll9Dlfn/onlMJCTBzZnjqFhGp7h54wOs5v+ee3nxPI0ZARga0bRvryCTMEnwJfH3B11ycfjEt01rSpl4brhp4FVPPnopVw7m27zriLh48+kH2aroXTVKacOLeJzLroll0bNQxLOePszi+PPdLrhh4Ba3rtaZVvVZc2v9SZpw/A1+cLyx1iIiISMCxVXj46Avx9zglxZueL5TDD/ce9pfWqZO3VomIVGvmyutSXoOlp6e7jIyM8ne66ipvkuXCwpLl9evDK6/A8cfvfiCJiZCfX/H969f3JorW8FKRasnMZjvn0mMdR6RUqO0UEakktZ0iIpWntjOMtm71Flkq3ZPzsMNKjiotkpQE48Z5U+9lZ3s9R1NSoGNH72F+qEWZf/0V+vf3Robm5noJ1qQk7/P9kCGRuzYRKaGqbWfd7kF6wQVeArO0+Hg46qjw1DF8eOjyhg1LDist0qABHHBAeOoWERERERERqesaNICVK70p8Bo1gtat4e67vQWUN270FmtKSPByAUccAZs2wT/+AR9+6I1wGjLES5iWlRwF6NIFFi70OmIddJCXb5g9W8lRkRqibi/StO++3kTKl1/uNYbgJUynTAm9snJVvPKKt1DT0qU7y1JSvKdUH3zgPZFKTPSeSNWvDx9/HLpbvoiIiIiIiEiEmNkxwEOAD3jGOXdXqe1nAEWTcW8HLnbOzQts+w3YBhQCBdWy52uTJvDqq8HliYnw/vuhjzn4YO9VUa1bw1137Xo/Eal26naCFLynOqeeCl9+6fXoHDLEe2oULomJ3jykM2d6i0L16gWjRnlJ0L33hnPP9bY1bOgNqw81z4mIiIhE3QdLPuDer+/lr+1/cXSXoxl74Fha128d8Xqdc7z505s8+O2DbMzeyPC9h/Ovwf+iaWrTiNctIiJ1k5n5gMeAI4GVwCwzm+Sc+6nYbsuBIc65TWY2FBgPDCy2/VDn3PqoBS0iEkZKkILX3T4c842WZ/Bg71Va06ZwwgmRrVtEREQq5f6v7+emaTeRlZ8FwPJNy3ltwWv8ePGPtKzXMqJ1Xz/1eh75/hEy87350B767iFeXfAq8y+eT6PkRhGtW0RE6qwBwFLn3DIAM3sNGA7sSJA6574utv+3QLuoRigiEkEayx1OWVnw4ovw3//C229XbnEmERERqRa2520vkRwFyPfnsyVnC/d9c19E616XuY4Hvn1gR3IUILcwl/VZ63kq46mI1i0iInVaW+CPYu9XBsrKcgHwYbH3DvjEzGab2eiyDjKz0WaWYWYZ69at262ARUTCqdr0IA106c8A/nTOHVdq2yHAe3hd+gEmOudujWqAu/LbbzBokLf63fbt3nyirVrBt996c52IiIhIjbBw7ULi44JvkfL8eXz666fe4MMImb16NsnxyeQW5pYozy7I5pNfP+G6A68r40gREZHdYiHKXMgdzQ7FS5AeWKx4sHNulZm1AD41s5+dc9ODTujceLyh+aSnp4c8v4hILFSnHqRXAovK2f6Vc65P4FW9kqMA558P69Z5yVGAbdtgxQoYOza2cYmIiEiltKrXirzCvJDb2jdoH/G6C/wFQeVxFkfHRh0jWreIiNRpK4Hif+TaAatK72RmvYBngOHOuQ1F5c65VYGva4F38Ibsi4jUGNUiQWpm7YBj8Rramic3F6ZPB7+/ZHleHrz+emxiEhERkSrp2Kgj+7fbn8S4xBLlqQmp/HPwPyNad++WvenSpAvxVrIHa7IvmSsGXhHRukVEpE6bBXQ1s85mlgiMBCYV38HMOgATgbOcc0uKlaeZWf2i74GjgAVRi1xEJAyqRYIUeBD4F+AvZ5/9zWyemX1oZj1C7RDT+Uws1IgEvNXqRUREpEZ5+29vM6TTEJJ8SdRPrE+DpAY8OvRRDu54cETrNTM+PvNjBrQbQHJ8MvUS69E4uTEvnPQCfVr1iWjdIiJSdznnCoDLgI/xRna+4ZxbaGZjzGxMYLebgKbA42Y218wyAuUtgRlmNg/4HpjsnPsobMF99RWceCL07w833QQbNpS///z5MGAApKVB+/bw3HNe+cKF0KKF99k9Lg4OOsgr37wZkpK8cjNISPCm0ANo23ZneVwcvPGGV3766TvL4+Ph3nu98t9/h8svh/R0OPNML5by5OfD+PFw4IFwyCHwyivBHa9EJCrMudhO+2FmxwHDnHOXBOYavTbEHKQNAL9zbruZDQMecs51Le+86enpLiMjo7xdwuvoo2HqVCgs3FmWmAgXXACPPx69OEQkosxstnMuPdZxRErU206Ram71ttWsz1rP3s32JtGXuOsDwmjl1pVsztlMt2bdQs6JWpOo7RQRqTy1ncAzz8CVV3oLIoOXyGzaFObOhebNg/f/7jvYf38onec4/3yYMCF4/3r1dk6TV1EdO3rT6ZV2/vneYs1ZWV7iMy4OkpPhvffgiCOC9/f7vTzC11/vvL60NDjhBPjf/yoXk4jsUNW2szp0bxwMnGBmvwGvAYeZ2cvFd3DObXXObQ98PwVIMLNmUY+0PM8+C61be4szxcd7De1ee8G4cbGOTERERKqodf3W7Nty36gnRwHaNWhHzxY9a3xyVEREpEpycuDqq3cmD8Gb3m79erj//tDHnH12cHIUQidHofLJUQidHC2qY+tWLzkKXgI0Kwv+/vfQMX32mbeoc/Hry8z0Eqpz51Y+LhHZLTFPkDrnxjrn2jnnOuHNc/K5c+7M4vuYWSszbwy7mQ3Ai3sX/eqjrF07+PVXr1G87TZv7tG5c6Fhw1hHJiIiIiIiIlKzLFwYeiq7vDyYMiX0Mb/+GtmYdiVUInTlSm8Yf2lTp4ZO0BYWwrRp4Y5MRHah2nZJKJrnxDn3JDACuNjMCoBsYKSL9dwAoSQmwogRsY5CREREREREpGZr1sxLhobSokXo8sREyM6OXExVYQapqcHlLVp4Q/BzckqWJyR41y4iURXzHqTFOeemFc0/6px7MpAcxTn3qHOuh3Out3NukHPu69hGWga/3xtqf+ml8M47JbctXuzNIzJjRuinSiIiIiIiIiLi6dgR9tvPSxgWl5YG11wT+pgLLghdnpIS3thCSU4OToQmJ8OoUd7cqaWdeWboRZ19PjjppMjEKCJlqlYJ0hpt+XJv/tELL/QWZTr5ZG/S6I0b4bTToG9fb+6RoUOhRw/4669YRywiIiIiIiJSfU2cCP36eQnOhg29r7fc4n2uDuWhh3auTl+kcWNYtMj7HF7aPfdAly7B5S1bwmWXBZcnJsL33weX+3zeMPoLLvCSog0bel+POAIeeyx0rC1bwqRJ3qJT9et765i0aQOffuolgUUkqqrtEPsaZ8iQkpMrgzd5dL9+sG5dyW7+v/ziPS367LPoxigiIiIiIiJSU7Ro4S1k9MsvsGYN9O7tJRPLEhcH06d7CylNngw9e8LBB3vbFizw5gO97jpvDZE77vAWWP7nP73k5gEHeAssffUVtGrlHfPIIzBsGCxbBs88Awce6JU7B3fdBZ984o0gPeUUr/zhh+Gmm+Dnn70esO3bl399hx/udZ764Qcvlj59QvcqFZGIs+o4lWc4pKenu4yMjOhUlpVV+Sc8iYleQ9i4cWRiEpGIMLPZzrn0WMcRKVFtO0WkzlDbKSJSeWo7RUQqr6ptpx5NhEPpSZUrIi6uaseJiIiIiIiIiIhI2ChBGg5NmoRelQ68nqWlJ5UGr0t/Ubd9ERERERERERERiQklSMPlueeCy8zgrbe8RGjREPzERG/y5eef97aLiIiIiIiIiIhIzGiRpnD529+gWze46ipvAul+/bwJmjt2hIUL4cUX4csvYa+9vNXsdzVZs4iIiIiIiIiIiEScEqTh1KsXfP55cHn9+t7KdpdeGv2YREREREREREREpEwaYl8e5+Dnn2H+fPD7Yx2NiIhInbRq2ypmr5pNZl5mrEMRERGR6mT9esjIgE2bdu88fj9MmgRvvw0FBSW3TZ8Or7wC27fvXh0iUq0pQVqWhQu94fD77QeDB0ObNjBtWqyjEhEBwMzam9kXZrbIzBaa2ZUh9jEze9jMlprZfDPrF4tYRapqW+42jn3lWLo83IXDXjyMFve14J6Z98Q6LBEREYm1/Hw4/3xv8ePDD/c+r19xRdU6Nr36qrdWyPDhMGIEJCXB+PEwd663fsiQIXDmmd7I0EsuCfuliEj1oARpKLm5cMghsHQpZGV5T4rWrIHjjoPVq2MdnYgIQAFwjXNuH2AQcKmZdS+1z1Cga+A1GngiuiGK7J6z3jmLqcunklOQw9bcrWTlZ3HLl7cwcdHEWIcmIiIisXTDDfD6695n961bIScHnn0W7r23cuf56y84/XQoLNxZ5vd764YMHAiZpUavPPEEvPDC7scvItWOEqShfPCB19CWVlCgxlBEqgXn3Grn3A+B77cBi4C2pXYbDrzoPN8CjcysdZRDFamSDVkb+GjpR+QWlvx7nJWfxd0z745RVCIiIhJzzsHjj3udmYrLyoIHHqjcuW66qexteXmhy2++uXJ1iEiNoARpKGvWBM87Al7SdNWq6McjIlIOM+sE9AW+K7WpLfBHsfcrCU6iYmajzSzDzDLWrVsXsThFKmNj9kbi40KvJblm+5ooRyMiIiLVRmFhcHK0yObNlTvXn39Wvv7dne9URKolJUhDOfBAMAsur1cPDj00+vGIiJTBzOoBbwNXOee2lt4c4hAXVODceOdcunMuvXnz5pEIU6TSOjfuTKIvMajcZz4O63xYDCISERGRaiE+HvbZJ/S29PTKnWv48MrXP2hQ5Y8RkWpPCdJQevWCE06AtLSdZSkp0L07HH987OISESnGzBLwkqOvOOdCTcq4Emhf7H07QN3gpUaIj4vnoaEPkZqQuqMsIS6BBkkN+O+Q/8YwMhEREYm5xx6D1FSIC6Q0fD7v8/tDD1XuPBdeCC1aBJc3bAgHHxxc7vPBU09VPl4RqfaUIC3Lyy/DI494EzP36QO33eatYh8ferifiEg0mZkBzwKLnHP/V8Zuk4CzA6vZDwK2OOe00pzUGGf1Oospp09h2J7D6N68O3/f7+/Mv3g+HRt1jHVoIiIiEkuHHAIzZ3qrznfvDqNGwaxZsN9+lTtPXBysWOEt1JSa6nWMOuUUWLkSvvwSxo6FRo28le0HD4bFi6Gj7kNEaiNl+8ri88F553kvEZHqZzBwFvCjmc0NlF0PdABwzj0JTAGGAUuBLEANmtQ4QzoNYUinIbEOQ0RERKqbPn28lex3V3IyvPJK6G133um9RKTWU4JURKQGcs7NIPQco8X3ccCl0YlIREREREREpGbSEHsRERERERERERGps5QgFRERERERERERkTpLCVIRERERERERERGps5QgFREREZE6wcyOMbPFZrbUzP4dYruZ2cOB7fPNrF8s4hQRERGR6FKCVERERERqPTPzAY8BQ4HuwCgz615qt6FA18BrNPBEVIMUERERkZhQglRERERE6oIBwFLn3DLnXB7wGjC81D7DgRed51ugkZm1jnagIiIiIhJdSpCKiIiISF3QFvij2PuVgbLK7iMiIiIitUx8rAOIlNmzZ683sxWVPKwZsD4S8VRzuu66oy5eM4T3ujuG6TzVktrOSqmL110Xrxl03eFQHdpOC1HmqrCPt6PZaLxh+AC5ZrZgN2Kr7mrz/4HafG2g66vp9o51AJFUhfvO2v7vXRZdd92i6959VbrvrLUJUudc88oeY2YZzrn0SMRTnem66466eM1Qd6+7KtR2VlxdvO66eM2g6451HGG0Emhf7H07YFUV9gHAOTceGA+18mdVQm2+vtp8baDrq+nMLCPWMURSZe87a/u/d1l03XWLrjt2NMReREREROqCWUBXM+tsZonASGBSqX0mAWcHVrMfBGxxzq2OdqAiIiIiEl21tgepiIiIiEgR51yBmV0GfAz4gAnOuYVmNiaw/UlgCjAMWApkAefFKl4RERERiR4lSEsaH+sAYkTXXXfUxWuGunvd0VJXf7518brr4jWDrrvWcM5NwUuCFi97stj3Dri0CqeudT+rUmrz9dXmawNdX01X26+vsurqz0PXXbfoumPEvPtAERERERERERERkbpHc5CKiIiIiIiIiIhInaUEKWBmE8xsrZktiHUs0WJm7c3sCzNbZGYLzezKWMcUDWaWbGbfm9m8wHXfEuuYosnMfGY2x8w+iHUs0WJmv5nZj2Y2t7avBBptajvVdtYVajvVdgKY2TFmttjMlprZv0NsNzN7OLB9vpn1i0WcVVWB6zsjcF3zzexrM+sdiziralfXV2y//mZWaGYjohnf7qrI9ZnZIYH/0wvN7Mtox7g7KvD72dDM3i/2d6rGzB+8q/upmt62hENdvOcE3XfWxftO3XPG9p5TCVLP88AxsQ4iygqAa5xz+wCDgEvNrHuMY4qGXOAw51xvoA9wTGCV2rriSmBRrIOIgUOdc32cc+mxDqSWeR61nWo76wa1nXWcmfmAx4ChQHdgVIj/+0OBroHXaOCJqAa5Gyp4fcuBIc65XsBtVIO5wiqqgtdXtN/deAt51RgVuT4zawQ8DpzgnOsBnBrtOKuqgv9+lwI/Bf5OHQLcb2aJUQ206p6n/PupGtu2hNHz1L17TtB9Z12879Q9ZwwpQQo456YDG2MdRzQ551Y7534IfL8N7z9h29hGFXnOsz3wNiHwqhMT8ZpZO+BY4JlYxyK1g9pOtZ0xDClq1HZKwABgqXNumXMuD3gNGF5qn+HAi4H/L98CjcysdbQDraJdXp9z7mvn3KbA22+BdlGOcXdU5N8P4HLgbWBtNIMLg4pc3+nAROfc7wDOuZp0jRW5PgfUNzMD6uHdnxREN8yqqcD9VE1uW8KiLt5zgu47A2/rzH2n7jljTwlSwcw6AX2B72IcSlQEuq3Pxbv5/dQ5VyeuG3gQ+Bfgj3Ec0eaAT8xstpmNjnUwUnuo7VTbWcup7SypLfBHsfcrCf6QWpF9qqvKxn4B8GFEIwqvXV6fmbUFTgKejGJc4VKRf7+9gMZmNi3w//rsqEW3+ypyfY8C+wCrgB+BK51ztaXdrslti4SJ7jvrxH3ng+ieM6b3nEqQ1nFmVg/vSflVzrmtsY4nGpxzhc65Png9HwaYWc8YhxRxZnYcsNY5NzvWscTAYOdcP7zhSZea2cGxDkhqPrWdajvrALWdJVmIstK9WSqyT3VV4djN7FC8BOl1EY0ovCpyfQ8C1znnCiMfTthV5Prigf3weicdDdxoZntFOrAwqcj1HQ3MBdrgDcl91MwaRDasqKnJbYuEge47a/99p+45q8c9pxKkdZiZJeA1tK845ybGOp5oc85tBqZRN+azGQycYGa/4Q1LOszMXo5tSNHhnFsV+LoWeAdvmJZIlantVNsZ25CiQ21nkJVA+2Lv2+H1VKvsPtVVhWI3s154Q/+GO+c2RCm2cKjI9aUDrwX+v48AHjezE6MS3e6r6O/nR865TOfcemA6UFMW2qrI9Z2HN4WAc84txZszt1uU4ou0mty2yG7SfWedue/UPWc1uOdUgrSOCszP8yywyDn3f7GOJ1rMrHlgknrMLAU4Avg5pkFFgXNurHOunXOuEzAS+Nw5d2aMw4o4M0szs/pF3wNHAXVq9UsJL7WdajvVdtZZs4CuZtY5sPDLSGBSqX0mAWcHVpweBGxxzq2OdqBVtMvrM7MOwETgLOfckhjEuDt2eX3Ouc7OuU6B/+9vAZc4596NeqRVU5Hfz/eAg8ws3sxSgYHUnIVAKnJ9vwOHA5hZS2BvYFlUo4ycmty2yG7QfWfdue/UPWf1uOeMj1XF1YmZvYq32mEzM1sJ/Nc592xso4q4wcBZwI+BuT0ArnfOTYldSFHRGnghsBpmHPCGc+6DGMckkdMSeMe7tyAe+J9z7qPYhlR7qO1U2xnjmCRy1HaW4pwrMLPL8FY39wETnHMLzWxMYPuTwBRgGLAUyMLr0VYjVPD6bgKa4vWsBChwMV5ttqIqeH01VkWuzzm3yMw+AubjzW/3jHOuRjz4qOC/323A82b2I96Q9OsCPWWrvVD3U3iL0tT4tiVc6ug9J+i+U/edtV+1uuc05zR9iYiIiIiIiIiIiNRNGmIvIiIiIiIiIiIidZYSpCIiIiIiIiIiIlJnKUEqIiIiIiIiIiIidZYSpCIiIiIiIiIiIlJnKUEqIiIiIiIiIiIidZYSpFIrmNkhZubMbFolj3Nm5iIUVsyZ2bTANR4S61hEpPpR2xma2k4RERGR8NE9Z2i656xelCCVqDKzcwMNwPOxjqWmq+ofGRGpedR2ho/aThEREZHQdM8ZPrrnrHmUIBUREREREREREZE6SwlSERERERERERERqbOUIJUS83qY2Wgzm2NmWWa2wcwmmlnPco5NM7N/mdksM9tqZtlmttDMbjazeqX2/Q14LvD2nKJ6S3fhN7PuZnarmX1tZqvMLM/M1pnZFDM7Jvw/gTKvLcHMxpjZV2a2ycxyzOwXM/s/M2seYv8dwxHMrL6Z3Wtmy80s18z+NLMnzKxJGXVZsZ99duB6J5rZvqGGOQS66X8ReDuk1M9yWhl17GdmkwL/rtlmNs/MLtjtH5RIHaW2s8xrU9spIiIiEia65yzz2nTPKWEVH+sApPowsweAK4CvgPeAfsBJwNFmdrRzbkap/dsBHwPdgXXAN0AO0B/4L3CSmR3inNsUOOQtYBAwGPgVKH6+4t9fDVwALALmAVuBPYChwFAzu8Y593/huu5QzKwBMBk4ENgCzAY24/1M/gGcYmZDnHO/hTi8ITATaAtMBxYEzjMGGGBmg5xz+aWOeQq4CCgAvsT7eaYD3wETQtTxEd7P+mhgTeB9kZ9D7H8M3s91MfAJ0AE4AHjGzBo55+4v40chIrugtnMntZ0iIiIikaF7zp10zykR4ZzTq46/ABd4ZQIHFys3YFxg2+9AcqltXwe2PQKkFtuWArwU2PZ8qbrODVVeap8hQKcQ5QPxGr88oF2pbYcEzjutKtceovy1wLY3gcbFyn3A3aHqKnZtDq+xrldsW5vAz9ABZ5Q67sRA+SagX7HyOODeYucs/bPc5TUD04odf36pbWcGyrcU//fTSy+9KvZS24kLUa62Uy+99NJLL7300iuML91z4kKU655Tr7C/NMReinvCOTe96I3z/lf+B1gGtAdOKbbvMcD+wLfAlc65rGLHZeM9fVkLnGFmjSsThHPuSxfiSY9z7jvgUSABGF6Zc1aGmXUHTgNWAGe7nU/UcM4VAmOB+Xhd5fcNcYrtwAXOue3FjlsViB3g8FL7XxH4er9z7odix/iB64E/du+KAHjbOVfiyZZz7mW8p34N8J5+iUjVqO1EbaeIiIhIhOmeE91zSuQoQSrFvVy6INDAvBp4e0ixTcMCX98ONAylj8sEMvCmcehf2UACc4KMNLO7zGx8YJ6Q54vFsFdlz1kJQwNfPwj88SghcL1FQwz2D3H8bOfcXyHKi7rStykqMLN4vK7zAP8LUVc+8HYF4y7PB2WUB8UkIpWmttOjtlNEREQkcnTP6dE9p0SE5iCV4paXUf5b4Gu7YmV7BL7ea2b37uK8QRMkl8fMhuPN4xFyguSABpU5ZyUVXdulZnbpLvYNdW2/l7Hv1sDX5GJlzYAkwE/ZT55W7CKGiqhMTCJSOWo7PWo7RURERCJH95we3XNKRChBKpXhin3vC3z9kp0Nclkq3GAEJpJ+FW9elLvwntL8BmQ65/xmNhpvgmSr6DmroOjaZuNN2FyehSHKgp7QVZAro7yq5wv3OUSkatR2BlPbKSIiIhJeuucMpntOqTAlSKW4Tnir0IUqB1hVrKzo6cmbzrnHwhjDcXiN7dvOubEhtu8ZxrrKUnRtXzjn/hnhujYAuXhPpdoT+qlgpwjHICK7pxNqO0Ftp4iIiEgkdUL3nKB7TokQzUEqxZ1RusDMfHgTIIO3wlqRDwNfT61kHXmBr2Ul54u66Qd1XzezJEpOPB0pRdd2YmDOkYgJzFnybeDtqNLbzSyBsq95Vz9LEYkOtZ0etZ0iIiIikaN7To/uOSUilCCV4i4xswOL3piZAbfgPQX6k5KTD7+L16V9iJk9aWZB84+Y2R4h5gT5M/B1nzJiKJqE+BQza1nsXInAI+ycbyRiAivTvYt33W8EhhGUYGatzeyqMDXIjwS+XmtmfYrVEQfcDnQo47iin+Wekf7DICLlUtuJ2k4RERGRCNM9J7rnlMjRP5IU9zTwpZlNB1YD/YC9gWzgjOIrxAXmFzkRmAL8HTjdzOYBK/EmMu6At3LdGqB4l/5vgb+AfmaWgTcnSD4w0zn3HDAJmAP0BX4xs2lADjAYaAg8DFwRiYsv5ZxALCcBQwPXtgJvsun2eH8w4oAngYLdqcg597aZTQDOB2YFrnkdkB6o6wngYnY+gSo6boWZFf2s5pvZbLzu/4udc7uaiFtEwkdt505qO0VEREQiQ/ecO+meU8JOPUiluKuBy/G6zZ8ItMB7MjPQOfdl6Z2dcyuBAcBleI1kD7zu5T2BbcB9wMmljskFjgEmA52BM4ELgCGB7QWB7+/Ba/SPAg4CpgP7BeqJOOfcVuBw4OxA3V3wrmU/vAb2SeBo51xOmKq8CK9RXYh3vUcDi4BB7JxLZn2I404G3sD7NxuF97M8NkwxiUjFqO3cGafaThEREZHI0D3nzjh1zylhZ86VtRCX1BVm5gCcc5FcaU6qyMw+w2v8Rzjn3t7V/iISHWo7qze1nSIiIlIb6J6zetM9Z+2hHqQi1YCZ9TCz1FJlCWb2H7zGdh3e8AgREQlQ2ykiIiIikaZ7zrpBc5CKVA9jgZPM7Ae8yZwbAfsCbfDmKTm3+JwyIiICqO0UERERkcjTPWcdoASpSPXwKlAPb6Ltfnj/N1cDLwL3Oed+jGFsIiLVldpOEREREYk03XPWAZqDVEREREREREREROoszUEqIiIiIiIiIiIidZYSpCIiIiIiIiIiIlJnKUEqIiIiIiIiIiIidZYSpCIiIiIiIiIiIlJnKUEqIiIiIiIiIiIidZYSpCIiIiIiIiIiIlJnKUEqIiIiIiIiIiIidZYSpCIiIiIiIiIiIlJnKUEqIiIiIiIiIiIidZYSpCIiIiIiIiISkplNMLO1ZragjO1mZg+b2VIzm29m/aIdo4jI7lKCVERERERERETK8jxwTDnbhwJdA6/RwBNRiElEJKyUIBURqaHMzGdmc8zsgxDbDjGzLWY2N/C6KRYxioiIiEjN5pybDmwsZ5fhwIvO8y3QyMxaRyc6EZHwiI91AJHSrFkz16lTp1iHISK1zOzZs9c755rHOo6AK4FFQIMytn/lnDuuMidU2ykikVDN2s6wU9spIpFQg9rOtsAfxd6vDJStLr2jmY3G62VKWlraft26dYtKgCJSd1S17ay1CdJOnTqRkZER6zBEpJYxsxWxjgHAzNoBxwJ3AFeH67xqO0UkEqpL2xkpajtFJBJqUNtpIcpcqB2dc+OB8QDp6elObaeIhFtV204NsRcRqZkeBP4F+MvZZ38zm2dmH5pZj7J2MrPRZpZhZhnr1q0Ld5wiIiIiUrutBNoXe98OWBWjWEREqkQJUhGRGsbMjgPWOudml7PbD0BH51xv4BHg3bJ2dM6Nd86lO+fSmzevCaO4RERERKQamQScHVjNfhCwxTkXNLxeRKQ6q7VD7EVEarHBwAlmNgxIBhqY2cvOuTOLdnDObS32/RQze9zMmjnn1scgXhERERGpoczsVeAQoJmZrQT+CyQAOOeeBKYAw4ClQBZwXmwiFRGpOiVIRURqGOfcWGAseKvVA9cWT44GylsBa5xzzswG4I0Y2BDlUEVERESkhnPOjdrFdgdcGqVwREQiQglSEZFawszGwI4n+SOAi82sAMgGRgZuXkVERERERESkGCVIRURqMOfcNGBa4Psni5U/Cjwam6hEREREREREag4t0iQiADjnWLF5BeuzNEWl1GKZmbBsGeTmxjoSERERibLNOZtZvmk5hf7CWIciIiLVTMwTpGa2t5nNLfbaamZXldrHzOxhM1tqZvPNrF+MwhWplaYum0qHBzuwz2P70O7/2nHYC4exZvuaWIclEj6FhXDVVdC8OfTqBc2awbhxoFkHREREar1tudsY8cYIWt3Xip5P9KTV/a14fcHrsQ5LRESqkZgnSJ1zi51zfZxzfYD98Fa9e6fUbkOBroHXaOCJqAYpUost3biUE147gZVbV5JdkE1uYS5f/f4VR750JJqyUmqNG2+Ep5+G7GyvF+n27XD77TBhQqwjExERkQgb+fZIPljyAbmFuWTlZ7E+az3nTzqfmb/PjHVoIiJSTcQ8QVrK4cCvzrkVpcqHAy86z7dAIzNrHf3wRGqfx2Y9Rn5hfomyAn8ByzYtY9aqWTGKSiSMCgvhkUcgK6tkeVYW3HlnbGISERGRqPhz6598vuxzcgtLTq+TlZ/FPTPviVFUIiJS3VS3BOlI4NUQ5W2BP4q9XxkoK8HMRptZhpllrFu3LkIhitQuSzcuJd+fH1TuMx9/bPkjxBEiNUx2NuTkhN7211/RjUVERESiatW2VSTGJ4bctmzzsihHIyIi1VW1SZCaWSJwAvBmqM0hyoLG/jrnxjvn0p1z6c2bNw93iCK10mGdDiM1ITWoPM+fR3qb9BhEJBJmaWnQpk3obfvtF91YREREJKq6NesWNFoKICEugUM6HhL9gEREpFqqNglSvHlGf3DOhVoZZiXQvtj7dsCqqEQlUsud3/d8mqQ0ISEuYUdZakIqp+97Oh0bdYxhZCJhYgYPPgipqSXLUlPh3ntjFpaIiIhEXv2k+txw0A0lOgT4zEdaYhr/GvyvCp9na+5WVm9brTn6RURqqeqUIB1F6OH1AJOAswOr2Q8CtjjnVkcvNJHaq2FyQ2aPns3f0/9O+wbt6d68O/cfdT9PH/90rEMTCZ+TToLJk2HIEK836bBh8NVXMHBgrCMTkWrIzH4zsx/NbK6ZZcQ6HhHZPTccfAMTTphAv9b9aFu/Lafvezpz/j6H9g3b7/LYTdmbGP7qcJrf25w9Ht6Dzg91ZuqyqVGIWkREoik+1gEAmFkqcCTw92JlYwCcc08CU4BhwFK8Ve7Pi0GYIrVWi7QWPDL0ER4Z+kisQxGJnEMOgWnTYh2FiNQchzrn1sc6CBEJj9N6nsZpPU+r9HHH/e84MlZlkOfPA2DFlhWc8NoJzB49m27NuoU7TBERiZFq0YPUOZflnGvqnNtSrOzJQHKUwOr1lzrnujjn9nXO6Um+iIiIiIiIRMzCtQuZu2bujuRokdyCXB769qEYRSUiIpFQLRKkIlJzzV41mwOePYD4W+NpfFdjbph6Q8iJ8EVERGoQB3xiZrPNbHSoHcxstJllmFnGunXrohyeiETDii0rSszTX6TQFbJ4w+IYRCQiIpFSLYbYi0jNtHTjUg55/hC2528HYHPuZh749gF+3/I7L538UoyjExERqbLBzrlVZtYC+NTMfnbOTS++g3NuPDAeID09Xau2iNRCfVr1IbcgN6g8OT6ZQzodEv2AREQkYtSDVESq7N6v7yWnIKdEWXZBNm/99Bart2kdNRERqZmcc6sCX9cC7wADYhuRiBTZnredxesXU+AviHhdbeq34ew+Z5OakLqjzGc+6ifW55L+l0S8fhERiR4lSEWkyuasnkOBC745TYpPYsmGJTGISEREZPeYWZqZ1S/6HjgKWBDbqEQkKy+Lfk/2o/64+nR7rBtJtyVx6ZRLI17vE8c+wT1H3sNeTfaiZVpLzu59Nj/8/QeapTaLeN0iIhI9GmIvIlXWt1Vfflj9A4WusER5bkEuXZt2jVFUIiIiu6Ul8I6ZgXev/D/n3EexDUlEBjwzgIXrFu5478fP47Mep139dow9aGzE6o2zOC7tfymX9o98MlZERGJHPUhFpMquPeBakuOTS5SlxKdw8j4n06Z+mxhFJSIiUnXOuWXOud6BVw/n3B2xjkmkrlu7fW2J5Ghx42aMi3I0IiJSGylBKiJV1rVpV7445wsGtB1AnMXRILEBlw+4nOdOfC7WoYmIiIhILfHz+p/L3JaZnxnFSEREpLbSEHsR2S392/bnuwu/wzlHYDiiiIiIiEjY9GvTr8xtzVObRzESERGprdSDVETCQslREREREakIv/OzNXcrzrkK7V8vsR7D9x4ectvDQx8OZ2gRkVeYR1Z+VqzDEBGRcihBKiKsz1rPVR9dRecHO9PriV48Pftp/M4f67BEREREpBZxzjFuxjia3N2Epvc0pdX9rXhuTsWmZnp35Ltc1v8yEn2JGEaTlCa8fPLL/K3H3yIcddVtyNrAKa+fQr0769FgXAP6P92f+WvmxzosEREJQUPsReq4rblb6fdUP9ZsX0OePw+Aqz6+ilmrZjH++PExjk5EREREaou7Zt7F7dNv39Gbcm3mWi778DLqJdbj1B6n7vL4R4Y9wiPDHol0mGHhnOPwFw9n0fpF5PvzAchYlcFBzx3EL5f/Qou0FjGOUEREilMPUpE67rk5z7Eha8OO5ChAVn4WL81/iRWbV8QwMqm18vLgtdfgH/+Axx6DzZtjHZGIiIhEmN/5uXvG3UFDzbPys7jpi5tiFFXkfP3H1/y66VfyCvNKlOcV5vHMD8/EKCoRESmLepCK1HFTl08lqyB4TqTEuEQyVmXQsVHHGEQltdamTTBoEKxaBdu3Q2oq/Oc/MGMG9OgR6+hEREQkQrLys8pccf6PrX9EOZrI+3XTryHLcwpy+GndT1GORkREdkU9SEXquD0a70F8XPCzEj9+2jVoF4OIpFa76Sb47TcvOQqQlQVbtsDZZ8c0LBEREYmstIQ0mqY0Dbltn2b7RDmayOvdsnfIOf1TE1IZ1G5QDCISEZHyKEEqUsdd2v9SEn2JJcriLZ6ODTsyoO2AGEUltdYbb3hD7ItzDhYs8HqXioiISNQ458gtyK3wavK7w8y4+4i7SU1ILVGeGp/K3UfeXeHzFMVcFX7nDxryHim9W/VmcPvBpMSn7CjzmY8GiQ04u7ceDIuIVDdKkIrUcV2bduXd096lbf22pCakkuRLYnCHwXx29meYWazDk9omvpyZXXy+6MUhIiJSx73101t0fqgzqXem0uSeJoybMS7iidKzep1Fj+Ylp9Q5fI/DOazzYbs81u/83DLtFhrd3YjUO1Pp8nAXPljyQYXqzSvM49pPrqXBuAak3JFCj8d6MO23aVW5hEqZNGoSVw26ihZpLWiQ1IDTepxGxugMGiQ1iHjdIiJSOUqQighHdjmSP/7xBz9e/CO//+N3pp07jVb1WsU6LKmNzjsPkpNLlvl8sP/+0EAfFkRERKLho6Ufcc6757Biywr8zs/mnM3cPv12bp1+a0TrHf7acGatmlWi7P0l73Pdp9ft8tjrPruOe76+h625W/E7P8s2LeO0N0/jy9++3OWxF026iMdnPU5mfiZ+5+en9T9x7CvHMu+veVW+lopIjk/mzsPvZM21a9jy7y28csortG3QNqJ1iohI1ShBKiKAN+xpj8Z70CKtRaxDkdrshhsgPR3S0iApCerXhzZt4KWXYh2ZiIhInXHj5zeGXE3+vq/vI78wPyJ1FvgLmPzL5JDbHvzuwXKPzcrP4rHvHwuOuSCL/077b7nHrs1cy+sLXye7ILtEeU5hDuNmjNt14CIiUidoFXsRIbcgl5fnv8xbP71Fo+RGXNz/Yg7ueHCFjs3My+S5uc8xafEkWtdvzWX9L6N/2/4RjhjWZa7jsVmPMeP3GezTbB+uGHgFXZt2jXi9sptSUmD6dJg5E374ATp1gmHDyh96LyIiImFV1grrBf4CNuVsisgD8/VZ63GEHsK/q3lB12xfQ5yF7tvzy4Zfyj12xeYVJMUnkVtYct5Sv/OzcO3Cco8VEZG6Q59IReq4vMI8Dn7+YBasXUBWfhaGMWnJJG4ecjP/HPzPco/dnred/k/35/ctv5OVn0WcxfHWT2/x6NBHOa/veRGL+fctv7Pf+P3YnrudnMIcvlzxJRPmTuDDMz6scGJXYsgMDjzQe4mIiEjUdW/enZl/zAwqT/Yl0ySlSUTqbJHaAsNCJkmLL2QUSpv6bcqcG3/flvuWe2yXJl1CJmB95iO9TXq5x4qISN2hIfYiddxrC15j4dqFO4YsORxZ+VncNO0m1metL/fYJ2Y9wYrNK3Yc63d+svKzuOKjK8jOzy732N0x9rOxbMreRE5hDuD1dsjKz+KCSRdEZRVWERERkZrszsPvDEpKpiakcvOhNxMfV/E+NJW574qLi+PMXmeG3HbTkJvKPTYpPonrBl9HakJqifLUhFRuO/S2co9tktKEC/teGHRsSnwK/z7w3xWIXERE6gIlSEXquHcWvUNmfmZQeaIvka9WfFXusRN/nhg0nxNAnMUxe/XssMVY2se/fkyhKwwq/33L72zM3hixeqWUzZu94fK/lD+0TURERKqXgzsezAenf0C/Vv1I8iXRqVEnHh32KFcOvLJCx788/2U6PtCRuFvjaPt/bZkwZ0KFjnvhxBc4uEPJ0T4je4ysUKJy7IFjOXKPI3cMtU/2JXPrIbdWaGqnh4Y+xM1DbqZ1vdYk+ZIY0nEI08+bzt7N9q5Q3CIiUvtpiL1IHdc0tSlxFoff+UuUO+dolNyo/GNTmoYsL/QX7vLY3VE/qT4bsjeE3JaSUP4QLQmTW26Bu+7yFlrKy4O+fWHSJGga+ndCREREqpfDOh/G7L9X/oH2awte4+8f/H3HCKJV21Zx+YeXA3B+3/PLPfbh7x8mY3VGibJJiyfx/uL3OX7v48s99sYvbuTTZZ/uuGfNKczhxi9uZFC7QQzuMLjcY+Msjn8O/ucup48SEZG6Sz1IReq4MeljSI5PDiqvl1hvl/N5XjHwiqDhSnEWR4eGHejRvEdY4yzu8gGXB9Wb6Evk+L2ODyqXCHj7bbj3XsjJgS1bIDsbZs2C006LdWQiIiISYTd8fkPwavL5Wdz4xY3lHud3fm798taQK9Hf8PkN5R6bnZ/NQ989FHRsdkH2LlexFxERqQglSEXquPQ26dx/1P2kxKfQIKkB9RPr07Z+Wz4961N8cb5yjz2qy1HccNANJMcn0yCpAfUS67FH4z2YfPrkMifSD4crB17J37r/jeT4ZBomNSQ1IZX+bfrzzAnPRKxOKea++yCz1LQM+fkwYwb89VdsYhIREZGo+H3L7yHLV21bFTQiqbis/Cy25m4Nue3XTb+WW+df2//CCH1vuWjdonKPFRERqQgNsRcRxqSP4fR9T2fm7zNpkNSA/dvvv2N+p125/qDrGZM+hm9Xfkuz1Gb0b9M/oslRAF+cj+dOfI5bD72V+Wvm06lRJ3q0iFyPVSllfRmLdyUkwKZN0KpVdOMRERGRqOnUqBNLNy4NKm/XoF25949pCWk0Sm4UchHQrk26lltn6/qty9yme0AREQkH9SAVEQAaJDVgaNehDO4wuMLJ0SJNUpowrOswBrQdEPHkaHHtG7bn2L2O1Y1xtA0d6iVDS0tIgD33jH48IiIiEjXjDh8XcjX5Ow67o9zjzIxbD7k15Gry4w4fV+6xyfHJXLP/NSHrvfXQWysRvYiISGhKkIrUIhmrMrjx8xu5ffrtIZ/sl8U5x4zfZ3D91Ou5e8bdZQ6dEgHg+uuhcWNvgSYAM0hNhccfD504FRERkVpjRPcRjNlvDPFx3mBEn/k4t/e5nN377F0ee3H/i3lk6CO0b9Aen/nYu+nevD7idYZ2HbrLY28YfAMFhQUlyjo37MygdoN2eWyhv5DbvryNZvc0I+G2BAY9M4jv//x+l8eJiEjdYc65WMcQEenp6S4jI2PXO4rUEld+eCXPzHmGnPwcfHE+4uPieeCYB/j7fn8v9zi/83PG22fw/pL3ycrPIsGXgM98vHTSS5zS/ZQoRV9zmNls51x6rOOIlAq3nevWwUMPwWefQceOcPXVMHBg5AMUkRpJbadI7fHWT29xzrvnlFgwKTUhlSePe5Kzep0VsXqTbksiz58XVH5Ml2P48MwPyz12zAdjeGneS2QV7Iw5LSGNWRfNYp/m+4Q91nBR2ykiUnlVbTvVg1SkFvj6j695Zs4zZOVn4cdPvj+f7IJsrvroKtZsX1PusR8s+YD3l7xPZn4mDkdeYR7ZBdmc/e7ZZOZllnus1GHNm8Ptt8O338Lrrys5KiIiUkeMnTo25Cr210+9PmJ1Ll6/OGRyFOCjXz8q99gNWRt4Yd4LJZKjADkFOYybUf7QfhERqTuUIBWpBd786U2y87ODyn3mY8ovU8o99pX5r5CZH5wIjY+L5/Pln4ctRhERERGp+X7b/FvI8pVbV5a7iv3ueHX+q1U+dtmmZST6EoPKC10hc1bP2Z2wRESkFlGCVKQWiLf4kIsjmdmO+aHKPLac7bs6VkRERETqlg4NO4Qsb1O/TaUX+qyoET1GVPnYzo07k1cY3Ps0zuLo1arX7oQlIiK1iBKkIrXAqH1HkRyfHFRe6C/k2L2OLffYc/qcQ1pCWshth3Y+NCzxSWSYmc/M5pjZByG2mZk9bGZLzWy+mfWLRYwhff459O0LycnQqRNMmAC1dD5sERGR2uaOw+4IuZr8bYfeFrE6e7bsSbyFfnB/eOfDyz22WWozTu95OinxKSXKk+OTGXvg2LDFKCIiNZsSpCK1QL/W/bj+wOtJjk8mOT6Z1IRUUuJTePGkF2mS0qTcY4/c40gu7HchKfEpJPmSSEtIIy0hjbf/9nbIpKtUK1cCi8rYNhToGniNBp6IVlDl+uorOO44mDsXcnNhxQq4/HJvwScRERGJmi+Wf8F+T+1H8u3JdH6oM8/Nfa5Cx43sOZIx6SVXsT+n9zmc3/f8Ch1/6eRLSbgtAbvFSLk9hTum31Gh47aM3RKU5Ozbsi+fnf3ZLo996vin+Megf9AgqQGG0adVHz458xN6tuhZobrrOjM7xswWBx68/zvE9oZm9r6ZzTOzhWZ2XiziFBHZHVrFXqQWWb5pOR8s+YCk+CRO6nYSzdOaV/jYResW8fGvH9MgqQEn73MyjZIbRS7QGqy6rCZqZu2AF4A7gKudc8eV2v4UMM0592rg/WLgEOfc6vLOG/G286CDYMaM4PJGjWDdOojXtA4itVF1aTsjRfedUtN8teIrjn75aLILds5hn5qQyp2H38mVA68s99j3fn6PUW+PCjr2mROeYVTPUeUee8475/Di/BeDyscdPo5/HxiUdytTfn4+CQkJFd6/OOdcyKmpqqPq0HaamQ9YAhwJrARmAaOccz8V2+d6oKFz7jozaw4sBlo550KvrBWgtlNEIkGr2IsInRt35vKBlzN6v9GVSo4C7NN8H64adBXn9z1fydGa4UHgX0BZqyG0Bf4o9n5loCy2Fi4MXZ6TAxs2RDcWERGROmrs1LElEpzgrUR/8xc3U+AvKPfY6z67LuSx//6s/ASn3+/npfkvhdx265e3ViDqnaqaHAVqTHK0GhkALHXOLQskPF8DhpfaxwH1zfvh1gM2AuX/IomIVDNKkIrIbvtt82+8OO9FpvwyhfzC/FiHU+uZ2XHAWufc7PJ2C1EWcsiAmY02swwzy1i3bl1YYixTly6hyxMSoEn500GIiIhIePy07qeQ5TmFOWzM3ljusb9u+jVk+e9bfi93Ffu1WWtxoW9FghKuUq1U5KH7o8A+wCrgR+BK50L/MkT1vlNEpBKqRYLUzBqZ2Vtm9rOZLTKz/UttP8TMtpjZ3MDrpljFKiI7Oee48sMr2eexfbh08qWMfGsk7f6vHQvXltFLUMJlMHCCmf2G9xT/MDN7udQ+K4H2xd63w7tpDeKcG++cS3fOpTdvXrmex5V2++2QWnJhB1JT4ZprvCSpiIiIRFyXxqEfWCbEJdA4uXG5x7Zr0C5keat6rcpdxb5ZajMs5PNbSPQlllunxFRFHrofDcwF2gB9gEfNrEGok0X1vlNEpBKqRYIUeAj4yDnXDehN6EVHvnLO9Qm8KjcGQ0Qi4t2f3+XZOc+SU5DD9vztbMvbxtqstRz36nHU1vmNqwPn3FjnXDvnXCdgJPC5c+7MUrtNAs4OrGY/CNiyq/lHo+Loo+HFF73V682gcWO46SbvJSIiIlFx22G3hVyJ/poDriHBV/4Dy1sPvTXksTcfcnO5x8XHxTOs67CQ264aeNUuY5aYqchD9/OAic6zFFgOdItSfCIiYRHzBGngydLBwLMAzrk859zmmAYlIhXyRMYTZOZnBpWvz1rP3L/mRj+gOs7MxpjZmMDbKcAyYCnwNHBJzAIr7ZRTYPnynfOOXnedlywVERGRqDhmz2N44cQX6NCwA3EWR6PkRvzn4P9w08G7fmB5Vq+zOGqPo0qUDekwhNH9Ru/y2EkjJ3HCXifs6EkaZ3FcnH4xdx95d9UuRKJhFtDVzDqbWSLew/lJpfb5HTgcwMxaAnvj3YeKiNQY1WG54D2AdcBzZtYbmI03Z0nprMv+ZjYP72nVtc65oDG8ZjYaGA3QoUOHyEYtIiGTo+Dd7Gouqehwzk0DpgW+f7JYuQMujU1UFZSo4XQiIiKxMqL7CEZ0H0FeYR4JcQkVXrzo5i9u5t3F75Yo+/DXD7n2k2u5/+j7yz02Li6O90a9h9/vZ3veduol1iMuLuZ9dqQczrkCM7sM+BjwAROccwuLHsoH7j9vA543sx/xhuRf55xbH7OgRUSqoDr8NYoH+gFPOOf6AplA6SUQfwA6Oud6A48A74Y6keYzEYmu03ueHjTEqkh6m/QoRyMiIiIilZXoS6zUyu7jZo4LWf7Qdw9V+BxxcXE0SG6g5GgN4Zyb4pzbyznXxTl3R6DsyaKH8865Vc65o5xz+zrnejrnSs+NLyJS7VWHv0grgZXOue8C79/CS5ju4Jzb6pzbHvh+CpBgZs2iG6aIlHZBvwvo2aIn9RLrAd7E/inxKbxw4guabF+qn2++gYEDoUULOPxwWBRqumsREREpT15hXsjyQldIgb8gytGIiIiER8yH2Dvn/jKzP8xsb+fcYry5S34qvo+ZtQLWOOecmQ3AS+xuiEG4IlJMcnwyM86bwcRFE/lw6Ye0TGvJRftdxJ5N9ox1aCIlvfginHPOzveffw49esC0aXDwwTELS0REpKbxmY9CVxhUbhjxcTH/eCkiIlIl1eUv2OXAK4FJn5cB55Wa02QEcLGZFQDZwEinJbJFqoUEXwKn9TyN03qeFutQRMo2ZkxwmXMwahT8+Wf04xEREamhzup9Fs/PfT6o/JR9Tol+MCIiImFSHYbY45ybG5g7tJdz7kTn3KZSc5o86pzr4Zzr7Zwb5Jz7OtYxi1RHBf4CMlZl8OOaH4nmM4Ts/Gy+W/kdSzcujVqdIhW2fj1kl7Fo2KpV0Y1FRESkmvhu5Xcc9sJhNL67Mb2e6MXERRMrdNxzw5+jd8veJcq6Ne3G6yNej0SYJby24DV6PN6Dxnc35qiXjmLO6jkRr1NEROqG6tKDVER20ye/fsLpb59OXmEefuenRVoL3hv5Hvu23Dei9T41+ymu+fgafHE+8gvz6dmiJ5NGTaJVvVYRrVekwlJDLyQGgBaHEBGROqgoOZpVkAXA5pzNnPXOWWzI3sBF/S4q99g7pt/BvDXzSpT9vOFnbvj8BsYdEXoBp3B48NsHueHzG8jK92L+dNmnzHxuJl+f/zW9W/XexdEiIiLl0ydDkVrgjy1/cNLrJ7EhewPb8raRmZ/J8s3LOezFw8gtyI1YvV+t+IqrP76azPxMtuZuJbsgmzmr53Dc/46LWJ0ilZaaCp06hd520EFRDUVERKQ6GDt17I7kaJGs/CzGfjaWQn/w/KLF3Tb9tpDl931zX9jiKy2vMI+bvrhpR3K0SHZ+Njd+cWPE6hURkbpDCVKRWuD5uc+HXDU0tyCXKb9MiVi9D3z7QNCNaoErYNH6Rfy8/ueI1StSaV99BQ0alCxr0wamRO7/h4iISHU196+5Icu3521nY/bGco/NLQz98L3AXxCxVez/3PonfucPKnc4Zq+aHZE6RUSkblGCVKQWWLVtFXmFeUHlhf5C1maujWi9oSTEJUS0XpFKa9cOtmyBN9+Ef/4TPvnEW5ypvOH3IiIitVSHhh1ClvvifDRMbljusT7zhSyP5Cr2LdJaUOhC92zt3LhzROoUEZG6RQlSkVrgqC5HUS+xXlC5wzGk05CI1Xts12NJjk8OKs8rzKNvq74Rq1ekykaMgHvugSOPjHUkIiIiMXPzITeTmlDyIWFqQiqX9b+MRF9iuceO7DkyZPkJe58QtvhKS0tM4/w+55MaHxzzTUNuili9IiJSdyhBKlILHL/38fRs0bPEjW5aQhoje46kW7NuEav3sgGX0Ty1OUm+pB1lqQmp3H7Y7dRPqh+xekVERERqi982/8YZE8+g1X2t2OexfXjmh2dwzkW0zhO7ncijQx+lWWozknxJpCWkccWAK7jz8Dt3eeyLJ77Ivi1KLgK6V5O9mPi3iZEKF4AHj3mQ0fuNJjUhlSRfEi3TWvL08U9zVJejIlqviIjUDVrFXqQWiI+L54tzvmD87PG8Mv8VkuKTGJM+hlE9R0W03sYpjZk7Zi4Pfvsgk5dMpkW9Flw96GqO7KLeebILCxfCnDne4kmDB4NZxY999VWYPh0GDYKzzqr4SvTOwbffwq+/Qq9e3ktERCSGVm1bxX5P7cfm3M34nZ81mWu48qMr+Xn9z9x3VOQWPQI4r+95nNPnHDZmb6RhUkMSfAkVOu7+b+7nx7U/lihbsnEJt355KzcfenMEIvUk+BJ44JgHuPvIu9mau5UmKU2IM/X3ERGR8LCqPJ00s3ZAGyB4bG2Ac276bsS129LT011GRkYsQxCRWsjMZjvn0mMdR6REvO3Mz4dTT/XmAPUF5jDr2BG++AKaNy//2LVrYa+9vLlEi9SrB4sWeXOMlmfTJjjiCFi82EvG+v1w4IHw3nuQXOafMhEJE7WdIqH985N/8vB3D5PnLzmXfJIviT+v/pOmqU1jFFnZUm5PIacwJ6g8IS6BvBuD58SXqlPbKSJSeVVtOyvVg9TMTgbGAXvuYldX2XOLSHhsy92GL84XNK9UJDnn2JK7hZT4FJLik3Z9QKljN+dsJi0xbZdzXkktcO+9XnI0O3tn2eLFcO65MHly+cceeWTJ5CjA9u1w6KHwyy/lH/v3v8OCBZBX7IPbV1/BrbfCnbseTigi1ZeZJQPp7Prh/YtRC0qkgqavmB6UHAVIjk9mwdoFEZ1LvqpCJUcB8v35FPgLIrZQk4iISCRV+K+XmR0PvIE3b+kWYBmwNUJxiUglLVi7gHPfPZf5a+YDcFjnw3hu+HO0rt86ovVOXTaV0R+M5o8tfxBncYzsOZLHhj1GWmLaLo99Z9E7XP7h5azLXIcvzscFfS/g/qPvV6K0NnvqqZLJUYCCAvjsMy/ZWS94sbEd5s8PXb50qdcjtKyh9vn58O673tfisrPhmWeUIBWpwczsH8BNQIMK7K4EqVQ7XZt2JWN1Bn7nL1GeW5hb5krzsRZncUHxQmRXsRcREYm0yvwFux4w4D/Avc65/F3sLyJRsjF7Iwc9dxCbczbvKJu6bCoHPXcQSy5fErH5mX5c8yMnvHYCWflZO8peX/A667PW88HpH5R77FcrvuLMd87ceawfnp3zLFn5WTw7/NmIxCvVQFZW2dtyc8tPkJanvARpQYG3PZSc0L1gRKT6M7PzgfsDbxcBP6OH91LDXHvAtbzz8zsl7qWSfEkc1OEgOjfuHMPIynbKPqfw5k9vBpUP3XNoDKIREREJj8pkTXoBc5xzdyo5KlK9vDjvRXILckuUFbgC1mau5bNln0Ws3vu+uY+cgpIJppzCHKYun8qKzSvKPfb26beX+DAAkF2Qzf8W/K9EoldqmeHDIT7Es7k994Smu5hnrUWL0OUNG4Y+Z5GUFOjbN7jc54Oh+jAnUoNdgTet05nOuR7OuVOcc+eV9arsyc3MZ2ZzzKz8J34iu6FPqz48PuzxHVMjGUbvlr1569S3Il73j2t+5OTXT6bd/7XjoAkH8cmvn1TouNdOeY1ODTuVKGtbry3vj3o/AlGKiIhER2USpPnA4kgFIiJV9/P6n8kuyA4qL/AXsHzT8ojVu2jdopBDrJJ8/8/eXcdJVXYBHP+dme2gu1NSBFwQERG7UGxsMUDsRn1N7O5AVBS7W0qlVZRUWlEapGO75rx/3AE2pnbZ2dk4389nPrDPvc/cs++r15lzn+ecWFbvCpwg/Xu775qR0a5oNqZuLJP4Kgrvl+zzROQNERknIpP9vH6KdKxh9/DD0LAhJHhr5MbGOqtG3347+NyPPy7e7V4EPvgg+Nw334QaNfY1ZEpIcBKyTz8deJ4xpiI7APhFVUO4CZTKDTgrU40Jm//S/uPWSbeSnes86FaUxVsW89jPj4X1ugv+W8Chbx7KV8u+Yn3qemauncnpH5/O+wvfDzr3hgk3sGrXqkJj69PWc9nXl4UpWmOMMSb8SpIgnQu0CVcgxpjSO6TpISRGF6/56RIX3Rt1D9t1+zbvS7Qruth4dn42nep1Cji3d9PePrf+52s+LWu1LLMYI01EagOzgPeAy4ATgAEBXlVbw4ZO1/nHH4dzz4U773SaNPXqFXzugAFOM6aTT4bmzeH442HxYjjppOBzu3WDv/6Cu+6CwYPhoYec6zZrtt+/kjEmYjKANeF4YxFpBpwMvBGO9zdmj2d/fZbdObvJJ3/vWHpuOs/OepbtmdvDdt07fryD9Nx0FN07lpGbwc0Tb/b58Lugl2e/7HN87J9jyzRGY4wxpjyVpAbpY8AEETlWVX8IV0DGmJI7t+u5jJw2kpzUHHI9TgWMuKg4Upqk0Ltp77Bd95ZDb+HtBW+TmpO698N0QnQCl3a/lPqJ9QPOvfeIe/n+r+9Jy03bO5YYncjt/W7fu82singYOBhYC7yE1ciD5GS49lrnVVJt28J3pdzt2rAh3H136eYaYyqiX4CuYXrv54ARQHKY3t8YAKaumkpOfvEu9rHuWBZtXkT/lv3Dct3f1//uc3xX1i62ZWwL+DmuYFK1qJz8HGu2aYwxplLymyAVkaJtE5fjfNH/RkReAL7HeWrv8xGjqoblib4xprj46HhmD53NXZPv4oulXxDjjmFI9yHc0/8epOiW5DLUvGZzfh/6O3f8eAdTVk2hdlxtbuxzI9f2Dp746ly/MzMvm8ntP97OrHWzaJDYgDv73cmQ7kPCFm+EnArsAA5R1f8iHYwxxlQhI4FfROQSVS2zpWsiMhDYrKpzRWRAgPOGAcMAWrSomN3GTcXXpnYbn13sc/JzaJrcNGzXbZLchB1ZO4qNu8RFjdgapX5fS44aY4yprAKtIF0FPh8PCnCr9+WPBnlvY0wZq59Yn9GnjGb0KaPL9boH1D2ALwZ/Uaq5BzU6iAkXTijjiCqcesBES44W4fHA2rXOqs49dUHLQ34+pKY69Uj9db03xlRIIuJrKd0zwBgROYngD++nh3ipw4BTve8ZB9QQkfdU9cIi7zcaGA2QkpLif0mdMQHc2vdWvvnrm0KNK2PcMfRp1oe2ddqG7bp397+by7+5vNB146PiuaLnFcRGxQac27V+VxZtWVRsvG3t8MVrjDHGhFugb4dr/LxWBzi257U2fCEbY0ylsgHIi3QQFcqVV0J0NLRq5XSY79cP8sL8P5GqU3e0dm0nKduwIbz+enivaYwpa1OBKUVeI3Ae3p8FvAX85OOcKcDkUC+iqneqajNVbQWcC0wumhw1pqwc3ORg3jv9PRokNiAhOoFYdyzHtz0+5IfPK3es5LKvL6PN8204fMzhjPt7XEjzzu16Ltf3vp4oVxSC4BIXx7Q5hqePC968cMGVC2hRo/Cq6cZJjVlyzZKQrv3zmp85/r3jaf18a8765CwWbloY0jxjjDEmnPyu8vR+KDTGVBKb0jZx+4+389Wyr4h2RzPkoCGMPHJk2Ot5Ltu6jFsn3cq01dOoFVeLG/vcyE19bvLZgKma+hwYIiLxqpoZ6WAi7p57YHSRVc4//wz9+8Mvv4Tvuo8+6rwyvCtltm6FG290VpIOHhy+6xpjytJ0fO9uMqZSO73T6QzqOIg1u9ZQM7YmteNrhzRv1c5V9HytJ6k5qeRrPit3ruTsT8/mqWOf4qpeVwWcu3DTQl6a/RL5nnwURVX5aeVPfLXsK87ucnbAuW63m9U3rWb9rvVMWzONfi360aJmaGUmvvvrO8759Bwy85yPRGt2rWH8ivFMGzKNlCYpIb2HMcYYEw6iWjU/Z6akpOicOXMiHYYx5SIjN4OOL3VkY9pG8jzOSrw9TZqmD5ketjqka3at4cBXDyQ1O3Vvwf6E6AQu7nYxrw58NSzXjDQRmauqIX+CF5Ek4Gec1fdXqOrmsAVXBsJ+70xIgEw/eeLUVEhKKvtrejxQpw7s2lX8WIcOsGxZ2V/TGFNISe+dlY197jSRcPnXlzP2j7Hka36h8eSYZLbctiXgVvmT3z+Z8SvGF2u41DipMetuXheWB92qSpvn27Bq16pix45oeQRTh0wt82tWdnbvNMaYkivtvTPkOqEiMgaYqapjgpw3BOivqpeVNBhjTOl8tOgjtmdu35scBcjKy2L+xvn8tv43+jTrE5brPvXLU2TmZhb6cJ2Rm8HbC95m5JEjaZDYICzXrci898qi/gFOA/4Wkbn4r5Gnqnp5GMOLvKws/8dWr4YuXcr+mhkZkJ7u+9haqwhjjDGmcpq6emqx5Oge/+z4h871O/udO2v9LJ/d6Ldnbg/axb60MnIzWJe6zuexORssSWaMMSayStJIaYj3z4AJUpzC9pcAliA1ppz8vv530nOLJ4A86uGP//4IW4J01rpZ5Hpyi43HRsWybOuyapkgZd+90pdkYECA4wpU7QRpzZqwc2fxcRFo3z4810xMhPr1YePG4sfCkZA1xpQLEZkMTFDVJ4KcdytwkqoeVT6RGVM+mtVoxr87/i02npOfE/QzWMPEhmzP3F5sXERIjk0usxgLiouKI9YdW+iB/h7hSMgaY4wxJRGOTvPR+OkeaowJj071OhEfFb+3ntMeUa6osHZA7Vy/M/M2ziu2eiE7P5tWtVqF7boV3KWRDqBCe/JJGDq0+Pi550JMTHiuKQKPPw7Dh++rQQpOg6gnAuZVjDEV2wBgVQjndQCOCGskxkTAnf3uZM6GOYU60ce6Yzm5/cnUS6gXdO7w74cX62I/pPsQ4qLiwhKv2+XmqpSreGX2K2Tk7btuQnQCd/S7IyzXNMYYY0IVjgRpF2BnGN7XGOPHxQddzMhpI8nKy9q7XSraFU3j5MYc1Tp8C2Zu63sbny75tNCH67ioOI5re1zIxfqrGlUdG+kYKrQrroD8fBgxAnbvdpKiV18Nzz4b3utedBEkJ8O998KqVc7K0UcfhQEDwntdY0xFEAv43odsTCV2QrsTuLv/3dw35b69qzJ7NenF2NODfxS5sNuFrE9dz0PTH8IlLnLycxjcZTDPnfBcWGN+5OhHSM1JZewfY4l2RZOv+dx66K0M6zksrNc1xhhjggnYpKlILb0hwApgpp/To4BOQE/ge1U9tYxiLBUr+Gyqm6VblnLZN5cxZ8McBOH4dsfz5qlvhn2b+9RVUxn+3XD+2fEPUa4oLjzwQl448QXio+PDet1IKUWTphZAmqoW38dW+LzaQLKqrtnfGPeH3TuNMeEQjkYjIuIB3g5U915EXMBCoLaqNinL6xdk904TCWt2raHHqB7sztm9N0GaEJ3Acyc8x9CePnZr+JCZm8nqXatplNSIWnG1whhtYbuydrEhdQMta7UkITqh3K5b2ViTJmOMKblwNWkaUuDvCrTzvgL5D7irpIEYY/ZPp/qd+PXyX8nIzcAt7oCdS8vSgFYDWHbtMtJy0oh1xxLtji6X61YiK4G3CV5b9Amc7fnhWNlvjDFVgrfuaEEn+BjbIwrnc2tD4JOwBmZMBDw8/WF2Z+8mT/fV9MzIzeC2SbdxyUGXEOMOXromPjqejvU6hjNMn2rG1aRmXM1yv64xxhjjT7Av4ntq6QlOc6aZwJt+zs0B1gOzVDWnbMIzxpRUpJ7CJ8UkReS6lYB4X6Gea4wxxr8BBf6uQCPvK5D5wO3hCsiYSPlx5Y+FkqN75Gs+K7avCNjF3hhjjDGFBUyQFqylJyL34yQ/rb6eMWGSm5/L6/Ne560FbwFwafdLGdpzaEirMjNzM3l59st8sPADYt2xDE8ZzkUHXYRLXEHnrtu9jou/vJhf1/5KTFQMl/e4nKeOfQqXK/hcU2ZqAdmRDiJkGzY4jY8mT4bmzZ2aoqHW85w6Fa66ClauhDp14MEH4fJgC2yNMQaAI71/CjAZmAA87ufcHGB9pEuXGBMuTZKa+Oxin5ufS/0E6wpvjDHGlETIWzlVtVUY4zCm2lNVBn44kJlrZu5terRkyxK+Wf4N4y8Yj4j/xYV5njz6v92fxZsX7+1kv3DzQn5c+SPvnv5uwOtuTttMm+fbkOvJBSArP4tnZz3L9NXTmTPMagKVhrfuaEFJPsb22FO/+Tic7fgV37p10L2702QpNxcWLYJp0+Cll+DSSwPPHT8eTjpp388bNzqNm1atchKlxhgTgKpO2/N3EZkGTC04Zkx1cnu/25n32bxiXeyPb3s89RMtQWqMMcaUhC0PM6aCmL56Oj+v+bnQh9yM3AxmrpnJjDUzAs79etnXLNuybG9yFCA9N53Pl3zOki1LAs69YcINe5OjBc3dOJd5G+eV8LcwXqtwkp17Ep5nFvi56Otv4BsgGXi/vAMtlYcfhl27nOToHhkZcNNNkBOkwoq/laKPPgoeT9nFaIyp8lT1SFV9ItJxGLO/tmVs4+7Jd9N9VHeOe/c4JqyYENK8gQcM5JGjHyEpJokasTWIdcdyTJtjePeMwA/HjTHGGFNcyCtIReTeEE/NAbYCc1V1fqmiMqYamrFmBpm5mcXGM3MzmbF6Bv1b9vc798d/fyQtN63YuIgwY/WMgDWopqya4vfYR4s+omfjnkEiNz6swamNB9ACyMC5L/qyp37zl8BL4Q+tDPzwA+QVr3lGfj6sWAGdA9Q8++8/3+P5+bB0KXTpUjYxGmOMMZXA9sztdH+tO1vSt5Cd71Ta+WXtL4wcMJJb+t4SdP4Nh9zAsJ7D+GvbXzRMakijpGAleY0xxhjjS0m6Jd/Pvi/8gcie80RkITBEVReUODJjqpmGiQ2Jj44nPTe90Hh8dDwNkxoGnNusRjNi3bF7P1jv4RZ30A/K9RLqsSl9k89jrWu1DiFyU1TBkiQi4gE+VdXLIhdRGWvUCP75p/h4bi7Uqxd4blRU4ZWnBTVuvP+xGWOqDREZE+Kpex/eA+NUtfLUezZV3ou/vcjWjK2FPsOl56Zzz5R7GHrwUGrE1gj6HvHR8RzU6KBwhmmMMcZUeSXZYv8AMBYnAZoBfA28ADwHfAXsyeqMxel4/xfQDfgxQO09Y4zXOV3OwS3uYuNucXN257MDzr2k+yVEuQo/7xCEuKg4Tmx/YsC5Dx/1sM/xKFcUVx58ZZCoTQguBd6MdBBlasQISEgoPBYTA0cfDQ0aBJ57/vm+x9u1cxo2GWNM6IZ4X5d4X0OKvPaMDwP+B3wGrBKR48s5TmP8Gr9iPFl5WcXGY9wxLPhvQfkHZIwxxlRTJUmQvgkMBD4EWqrqGap6k6reoqpnAi29x04GHgS6AqOAOsCtZRu2MVVPzbia/HDxDzSv0ZzE6EQSoxNpXqM5P178IzXjagac26xGM74+92saJDYgKSaJhOgE2tVpx9QhU4lxxwScO6jjIG7rexvCviZQcVFxTL1kqnWxLwOqOlZVf450HGXq1FOdhkoJCVCjBsTFwZFHwgcfBJ87ZgwcdljhsaZN4bffwhOrMaYquxR4Gefh/Tqch/Y3ATcAzwJrvcdeAe4BpgANgS9FxOp5mAqhSXITn+O5nlwaJAZ56GiMMcaYMiOqoeyaBxEZCxwJtFVVn/sjRSQa+Aeno+jFIhIPrAa2q2rHMoo5JCkpKTpnjnXgNpWPqrJ4y2IAutTvErB7fVH5nnwWbV5EbFQsHep2KNHcjJwMvl7uJFmPbnN0ieOuLkRkrqqmRDqOcCnRvTM93akb2qgRNGtWsgutWweTJ0P37tCtW4njNMZULuG4d4pIV2AWTpL0LlXNK3I8CngYuBboo6oLReRunF1Rb5dl6RP73GlKa8bqGZzw/gmFmnRGuaI4qOFBzBlm/0xVd/a50xhjSq60986S1CA9Difx6ad4HKhqroj8Ahzr/TlTRP4A+pY0MGOqKxGha4OupZrrdrlLXYMqISaB8w48r1RzzT4ikr8f01VVS3JfjqzEREgp5Wf2Zs3g4ovLNh5jTHUzElivqrf7OqiqeSJyB3Ca99wzgMeA4cCAcorRmIAOb3k4z53wHDdPvBmXuMjNz6Vbw258fe7XkQ4toBXbV/DkL08yd8NcDmp4ECMOG0GHeh0iHZYxxhhTaiX5Il4LSA7hvETvuXtsKcE1jKkwFvy3gDfnvcn2rO2c0fEMBnUcVKzOp4Gc/Bw+WfwJ4/4eR+Okxgw9eCgd64W2YHxrxlZumXgLU1ZNoUlyE5445gn6t+of5ojDLvRlu2U71xhjqpvDgR8CnaCqKiJzcB7070maLsQSpKYCGdpzKBd1u4iFmxZSN6EubWq3iXRIAc3fOJ/+b/cnKzeLPM3jj01/8PHij/np4p84pNkhkQ7PGGOMKZWSFBhcCQwI1HDJe+wo77l7NAa2B3pjEaklIp+JyDIRWSoihxY5LiLygoisEJE/RaRnCeI2psRGzRnFYW8exqtzXuWDhR9wyVeXcOJ7J5LnyQs+uRrJzM3k0DcPZfh3w/lw0Ye88NsLHPzawXy6+NOgc1fvXE2Tp5vwzp/vsHb3Wn5b/xtHjD2CJ35+ohwiDx9VdRV94dTCywCeAXoAtXEeJPUAnsZpcveM99yqLycHhgyB9u3h+OPhv/9Cn5uVBXfd5cwbMQIyMoLPibTdu+Gtt+Cxx+DnnyHE0jbGmKCSgPohnFcf5wH+HjsB+w+6qVDiouLo1bRXhU+OAtww4QbSctLI81a1yPPkkZ6bztXjro5wZMYYY0zpleTL+FggAZgiIueJ7Gu3LSJuETkXp/h9nPfcPbWfDgIWBnnv54EJ3jqlBwFLixw/EWjvfQ0DXi1B3MaUyM6sndw08SYy8jLIV2e3dHpuOr+u+5XPl3we4egqljfmvcHSLUtJz00HIE/zyMjL4IpvriA7Lzvg3PM/P59cT/GKHXf+dGeVSkSLyOXA9cCJqnqrqv6hqrtUdbf377fh3ONuEJGhJXjfOBH5XUT+EJHFIjLSxzkDRGSXiCzwvu4tu9+slP75x2nqNHYsrFgBkyZB48ahNXhavtxpCvXII868J5+EmjXhzz/DH3dpzZkDLVrAddfBPfc4id2BAyGv6vwzbkwELQeOEBG/tWW8xwYAywoMNwW2hTc0Y6quWetm+Ryfv3E+HvWUczTGGGNM2ShJgvRpYCLQGngPyBSR1SKyCsgE3vcem+Q9F6ALsBjw+81XRGoA/YE3AVQ1R1V3FjltEPCOOmYBtUSkcQliNyZkU1f57vyenpvOJ0s+iUBEFddHiz8iMy+z+AGBORsCF1yfvWG2z3GPevjp35/KIryK4mpghqrO8HeCqs4EZgBXleB9s4GjVPUgoDtwgoj08XHeDFXt7n09UIL3D4/DDvO9gvKii4LPPekkyC2SVM/LgxNOKJvYypoqnHEG7NrlNLTKy3P+nDoV3nwz0tEZUxW8CkQDk0XkThFp5X1o7xKRlt76oz8BbmAUgLeBaE9gXsSiNqaSqxFbw+d4QnQCYtWCjDHGVFIhJ0i9nUFPBm7G6UwfBTQHWnj/vga4FRi4p4uod3XU4ar6XoC3boNTp/QtEZkvIm+ISGKRc5oCawv8vM47VoiIDBOROSIyZ8sWK31qSicxuug/fg5BqBHj+wNhdZUc47sssUc9JMb4/t9xj0D1XOvE19mvuCqYDsDGEM7bCBwQ6pt6HxileX+M9r4q/t7tTZt8j3s8sGhR4Ln//ut7fONGZ35Fs3gxbPdRYSYjwxKkxpQBVR0NvIFTtuQh4B8gC+cB0r84HezrAGO854LzMP9L4PVyD9iYKuLqXleTEJ1QaCw+Kp7hKcMRsQSpMcaYyqlE9e5U1aOqz6lqG5zE6KHeV0tVba2qz6hqSTs4R+E8yX9VVXvg1OK7o8g5vv5LWywRoKqjVTVFVVPq1w+lJJUxxQ1oNYBoV3Sx8fjoeIYeHPIO6Grh6l5XF0soC0KDxAYc1NDvjkcAzu58ts/xxOhEejXtVWYxVgDZOLVGg+nhPTdk3pVSC4DNwA+q+puP0w71bsMfLyJd/LxPxXi4VBGTnPtDFfx9Uaxqv6sxEaKqw3C6008DcnBWi7q9f58OnKWqQwucv0RVL1LV8ZGI1xhf1u1ex8nvn0zNx2rS/JnmvDq7YlcTu/eIezmn8znEumOpGVuTOHccgzoM4pGjH4l0aCZMROQEEVnu7QlS9Lv6nnMGeEs6LRaRaeUdozHG7K9SNwRR1XWq+pv3tTb4DL/WAesKfLH/DCdhWvSc5gV+bgZs2I9rGuNXtDua8ReMp3ZcbZJjkkmKSSIuKo57j7iXvs37Rjq8CuWUA07hql5XEeuOJSkmieSYZBomNeS7874LuoLgzUFv0q52u0JjUa4ofrgoYEPiymg60EFEHhQf/6N4m9A9AHT0nhsyVc1X1e4498TeItK1yCnzcB5gHQS8CHzl533K7+GSv/d3uaBbt8BzW/jpEdiwoTO/ounSxamRWlRCAlx2WfnHY0wVpapfqepROE2bGntfyap6pKp+EdnojAls3e51tH6+NeNWjGN39m7Wpa7j6nFXc/7n50c6NL+iXFG8ddpbrLpxFd+c9w3/3PAPH571oc8SVaby8/YeeRmnZn5n4DwR6VzknFrAK8CpqtoF8L0SwhhjKjD/e1zLiar+JyJrRaSDqi4HjgaWFDntG+BaEfkIOATYpaqhbFk1plR6Ne3Ff7f+xw///EBqTipHtjqShkkNIx1WhSMiPHnsk1zf+3pmrplJ3YS6HNX6qIDb5/eIckXx9/V/M3nlZD5f+jnt67Tn2t7XhjS3krkHOA74HzDYex9b6T3WCjgXaIdTy7lUTZRUdaeITAVOABYVGN9d4O/jROQVEamnqltLc50yMW2akzgsWod09Gjf5xc0bhx07164wZHbDd99V6YhlhmXCz77DI47DvLzITPTSY4eeigMtdXoxpQ17y4mP3U8jKmYrvj6Cp/NKT9c9CHPHf8cDZIaRCCq0DRKakSjpEaRDsOEX29ghar+C+D9LDuIwt/Zzwe+UNU1AKq6udyjNMaY/VTiTISIHIqTxGyC07HeF1XVy0vwttcB74tIDE7NqEtFZLj3jUYB44CTgBVABnBpSeM2pqRi3DGcfMDJkQ6jUmhesznnHXheqeYe1foojmp9VBlHVHGo6iIROQmnkV074K4ipwhO/dELVXVhqO8rIvWBXG9yNB44Bni8yDmNgE2qqiLSG2fXQGQ7N3fq5DQquvjifR3e334bWrcOPrdLF9ixA+69F+bNgwMPhIcfdjrbV1R9+sDq1fDxx0791cMPhyOP9L/13hhjTLXy87qf/R77ZMknXNv72nKMxhiffPUDOaTIOQcA0d4H9snA86r6TvmEZ4wxZSPkBKmIxAIfA6fsGQpwugIhJ0hVdQGQUmR4VIHjClwT6vsZY8rX7PWzmbpqKnUT6nJmpzOpGedjW3EFkufJY/zf41myZQkd6nVg4AEDw7pyVVWniUg74CzgCJwt8QDrcermfaaqmSV828bAWO+2Jxfwiap+V+Th0lnAVSKSh7NC9Vzv/TSy4uPh009LNzcpCZ55pmzjCbfatWH48EhHYUyV5L0HnkNoD++PLrfAjAlRYnQiaTlpPo81r9Hc57gx5SyUfiBRwME49+J44FcRmaWqfxV7M5FhwDCAFv7KJxljTASUJCNwP3AqkAa8CywDdgeaYIyp2jzq4dzPzmXc3+PIyc8hxh3DjRNuZOKFEzm0+aGRDs+nbRnb6DumLxtTN5KZm0l8dDz1Eurx6+W/hrWMgqpmAe95X2Xxfn/io/mTNzG65+8vAS+VxfWMMaaiEZHawCSc2vXBlmVH/uGQMT7ccugtjPhxRLHxWHcsgzoOikBExhQTSj+QdcBWVU0H0kVkOnAQUCxBqqqjgdEAKSkpdm82xlQYJUmQDsbpMN/LWyvUGFPNfbjwQ8b9PY703HQAcj25AJz+8elsuGUDLql4jXNumngTK3es3Btrak4qmXmZXDPuGj4757MIR1eNTJ8OTz4JZ54JQ4aUbO7u3bBqlbM9v1atks1duRLmzoV+/aCR1U0zldfOrJ2s2bWGVrVaUSO2ApeZCK+HcVYsrcV5GGQP702lc9thtzFzzUy++eubvWMx7himDbEm4KbCmA20F5HWOLufzsWpOVrQ18BLIhIFxOBswX+2XKM0xpj9VJIEaRNgiiVHjTF7vDH/jb3J0YIycjOYu2EuvZr2ikBUgX2+9PO9ydE98jx5fL38a1QVH43mTVnKyXEaFeXnOz9/9x1ceiksXgydOwee6/HAbbfBK69ATIzzXpddBi+84DRrCiQjw2nw9Pff+8b69oUZM5xmSsZUEnmePG4YfwNj5o8hJiqGnPwcrk65miePe7JCPpQKs1OBHcAhqvpfpIMxprS+Pu9r1uxaw6eLP6V5zeac1eksXPbfJlNBqGqeiFwLTATcwBhVXVywrJOqLhWRCcCfgAd4Q1UX+X9XY4ypeEqSIN2CPZU3xhTg8Xj8H1P/xyIp3CU4ReRfnK2cx6jqSu/PoVJVbRum0CqGevX2JUcL8tXZvqgnn4RRoyAry3mB0+Cpbl144IHAc/v1K5wcBfjlFzj/fPjoo5DDNybSRk4dydt/vE1WfhZZ+c6/B6PmjqJhUkNGHFZ8m24VVw+YaMlRUxW0qNmCW/reEukwjPFJVcfhNE4uODaqyM9PAk+WZ1zGGFOWSvJochzQ17ts3hhjGNJ9CInRicXGY92xpDQp2netYhjUYVCxhkxucXNiuxPLavVoK+8rusjPob6qttRU/8f+/DPw3GeecVaCFpSRAc8/H3heTg7Mn+/72GdWVsFUHqrKC7+/QEZu4X8PMnIzeObXSta8rGxsAPIiHYQxxhhjjKn8SpIgvcf750vejvbGmGruooMu4ohWR5AYnYggxEfFkxidyKfnfIrbFWTLc4Q8d8JzNEtuRlJMEgBJMUk0SmrEqye/WlaXaA20Af4t8HOorzZlFUSlNC1IvbUdO3yP797tbL/3Z+dO/8d8rWY1poJSlN3ZvjfzbM/cXs7RVAifA/1FJD7SgRhjjDHGmMqtJKtBh+PUHRkKnCAik4E1ODVGilJVfbAM4jPGVGBRrii+O+87pq2expSVU6iXUI/zDjyPegn1Ih2aXw2TGrLs2mV8uexLFm9eTKf6nTij0xnERcWVyfur6upAP5sArrwy8PEePeD334uPd+kSuI5ovXoQFQV5PhaalbTJkzER5BIXXep3YfGWxcWO9WjcIwIRRdxI4DjgYxG5QlU3RzogY4wxxhhTOZUkQXo/Tl09AVoAQ3ycs+e4ApYgNaYaEBEGtBrAgFYDIh1KyGKjYjm367mRDqN6Ouss39vaGzZ0Gi8F8txzcMwxTv1RjwdEID4eXnwx8DyXC+65B+67r/ixV14JOXRjKoIXT3yRgR8OJDM3E0VxiYu4qDiePyFIqYmq6QVgBXA68LeIzCXww/vLyzM4Y0K1PWM710+4nh///ZHacbW5f8D9DO46ONJhGWOMMdVKSRKkI8MWRWW2bRv8/LOzCqlfP+uGXIXke/KZsWYGqdmp9GvRj9rxtcvlutsztvPKHCdpc23va6kVV6tcrmvCQ0S+BH4EJqvq0kjHE3GffgpDhsDYsfvGevSAefOCzz30UPj1V3jwQViwALp2hbvvhoMPDj733nuhaVP43/+crfqNGzuJ1VNPLe1vYkxEHNn6SKYPmc5DMx5i0eZFdG/YnXuOuIduDbtFOrRIGILzUB4gGRgQ4FwFLEFqKpzNaZtp8VwLsvOzAdiUvolzPz+Xyasm89rA1yIcnTHGGFN9SLg7OkdKSkqKzpkzJ7wXeeopZ1VSTIzTfblmTZg0CTp1Cu91Tdj9uelPjnv3ODJyMxARcvJzeOrYp7im9zVhve4D0x7gvqmFV7mNHDCSe4+4N6zXNaETkbmqGnIHKhHxsO8L/H/AT3teqrouDCHul3K5dxpjqp2S3jtDfM9LSnK+qo4Nflbp2L3TlNagDwfxzV/f+Dy27bZt1EmoU84RmYokHPfOisTuncaYcCjtvdM60pfW9OnOds2sLOcFkJYGJ5wAK1faStJKLM+Tx7HvHsvm9MKlzEb8OILeTXvTq2mvsFx38ebFxZKjAPdNvY+zO59Np/qWeK+kTgaO9r66ARcCFwCIyN84ydIfgSmqujNCMRpjTKUTzoSnMeVl8qrJfo99tPgjru51dTlGY4wxxlRfpcriiUhNETlGRM4Tkb5lHVSl8MorkJFReEwVtm/33UTEVBrTVk0jMzez2HhWXhavzQ3fVqf7p93v95ivxKmpHFR1vKreqqo9gAbAYOANYCVwAHAV8BmwRUTs5mGMMcZUI4GaRDZIbFCOkRhjjDHVW4kSpN7E6BhgM05H+/eAKwocv1pENohIn7INswLats33uMsFu3aVbyymTO3O3o2IFBv3qIdtmX7+fy8Du7L8/3OzM2tn2K5ryo+qblPVT1X1SlVtB7QCngKyATcQQjHNKmD2bDj2WKhb16k/+tVXkY4ouE8+gW7dnJhPOMGpgWpMhPzwzw/0eaMPdZ+oS78x/Zi2alqkQ4o4EekiIleIyJ0icmqBcZeIBOkAZ0zkXNvrWp/j0a5ozuh4RjlHY4wxxlRfISdIRSQRmIpTEH8HMB6nY31BE4BGwGllEl1FdtZZkJBQfDw3F/pWz0W1VUX/lv3JycspNp4YnchZnc4K23Uv7Hah32OXdC9RmTVTgYlIQxG5QETeAmYCtwBxQD4wO6LBlYfZs2HAAPjxR2fF/YIFcMEF8OabkY7MvxdegEsvhYULnZgnTnSa8v35Z6QjM9XQ18u+5rSPT+O39b+xPXM7P6/9mZPeP4kf/vkh0qFFhIi0EJHJwJ/Aa8BDFP4ceh2QKSJHRyA8Y4K6b8B9HNHyiEJjbnEz/oLxuKxklzHGGFNuSvJf3VuBg3BWjbZR1YFFT1DVf4G/gKPKJrwK7JJLoGNHSEx0fna5nITp009DcnJkYzP7pW5CXR4++mESohMQ7zOAxOhEDmp0EOd0OSds1734oItpV7tdsfF2ddpxwYEXhO26JrxEJFFEThaRZ0VkIbABeBe4BEgHXgXOAOqpatVffX/nncXLk2RkwB13QH5+ZGIKJDfXacbnK+Z77olMTKZau3nSzWTkFv7nMSMvg1sn3RqhiCJHROoB03G61y/EuZ8WfXj/CU6jvEHlGpwxJTB1yFQWDl/IHYfdwYsnvkjW3Vkc3cZy+sYYY0x5KkmTprNxvtgPVdXsAOetAbrsV1SVQVwc/PILfPABfP45NGgAw4dD796RjsyUgZsPvZk+zfrw2tzX2JG5g7M7n83groOJdkeH9brLr13O/dPuZ8z8MQBc1uMy7j/i/rBe04Tddvbda/8D3sdpyvSTqq6PWFSRMm+e7/G0NKd0SYMKVm9t/XrfiVtVsK6rppx51MO/O/71eWzp1qXlHE2FcCfQAngc+J+qqogU6mijqhtFZCnQLxIBGhOqrg278mjDRyMdhjHGGFNtlSRB2gaYGCQ5CrAVqFv6kCqR2Fhn2+Wll0Y6EhMGfZv3pW/z8i2X4HK5eODIB3jgyAfK9bomrKJxVi8tBF4CflTVVRGNKJJatIAdO4qPu1xQq1a5hxNU/fr+V7a2alWuoRjjEhd14+v6rIfdMKlhBCKKuFNwGt79T1U1wHlrgZ7lE5IxxhhjjKmMSrLFPhenTl4wzYC00oVTzeTmwtKlsGVLyedmZcGSJU49PBM2G1I3sHzrcjzqKbdrqiord6xk5Y6VBP6+V1x+fj7vLHiHL5d+GaboTCk8h5McPRCnPt4/IvKPiIwSkbNFpHo8UNrjvvuK129OSICrr4aYCthHJTHReQjmK2bbYm8i4I5+d5AQXfifx4ToBO4+/O4IRRRRzYF5QZKjALuB2uUQjzHGGGOMqaRKkiBdDvQQEb9JUhGpjVOndOH+BlbljR3rbCXt3RuaN4eBA2GX/y7mhTz3nLOqqU8faNIEzjsPMjPDGm51syF1A33f7EvbF9py8OiDafJ0EyasmBD26/7x3x90fLkjXV7pQpdXutDx5Y788d8fIc29deKtRD0UxSVfX8IZn5yBa6SLdxa8E+aITTCqerOqdgcaAucDb+KsKB0GfAxsEpF5IvKkiBwfuUjLyemnw/PPO93g4+L2JUcfeyzSkfn3/PNw+eUQH+/EXL8+jBrldLM3ppzdcugt3NnvTpJjkomLiqNmbE1GDhjJsIOHRTq0SMgEaoVwXktgZ1gjMcYYY4wxlZqEukJNREYAjwEvqOqN3jEP8LaqXub9+VWcL/3XquqrYYk4RCkpKTqnotaHmzYNTjqpcNOPmBins/PEiYHnfvklXHhh4blxcXDWWfDuu2EJt7pRVTq93IkV21eQr/u21iZEJzD/yvkcUPeAsFw3NTuVFs+2YGf2zkLjteJqsfamtSTFJPmdO3nlZI5+x3cx/50jdlIzvmZZhlqtichcVU0pg/dpCRztfZ0OxAKqqiUpfVLmyu3emZ/v1BytWdMpV1IZZGc7D7Lq1XNKAhgTQbn5uWzP3E7dhLpEuSJ62whJWd07i7zndKAr0FpVd3nHin42bQr8DUxT1RPL8voFVejPncaYSisc986KxO6dxphwKO29syTf8F4ClgLXichMEbnZO95KRK4Skck4ydGFOCukjD9PPFG8I3JODkyfDuvWBZ77yCPF52ZlwWefwe7dZRtnNTVr3SzWp64vlBwFyMnP4ZXZr4Ttup8u+ZRcT26x8dz8XD5d/GnAucO/He732LDvquWqogpNRBoDhwP9va9YnM7LRbsvV11ut7OKvrIkR8GJtUEDS46aCiHaHU3DpIaVIjkaRh/grCB9TUSK1egQERfwAs499r3yDc0YY4wxxlQmIX+qVtUMETkO+BToCxzqPXSE9yXAXOA0Vc0p60CrlDVrfI/HxMB//0GzZv7nbtjge9zlcuqR1qix//FVcxtSN+Dy8ewgz5PHyh0rw3rdjNyMYuOZuZmsTw3c7Hxrxla/x1buDF/MJjQiUgMYAByDs2K0455D3j+X4O1sX+7BGWNM5fUGcAFwDtBLRL73jncVkceB04D2wFScZKoxxhhjjDE+lWjZgaquB/qKyAnASTid7d043UHHA1+FUCjfHH00LF/uNGkqKC8POnUKPLdfP2e1qKdI06C4uMCJVROyXk17kZ2fXWw8ITqBY9ocE7brHtrsUBKjE0nLLdzjLCE6gUObHepnlqN3s95M/Md3eYbBXQaXWYym5ETkV+BgnHvlnoToWpxk6I/AT6q6KULhRcbs2U79zh07nHvXq6/CJZdEOipjSiwzN5OPFn3E9NXTaVunLZf3uJzGyY1Dmpuancp7f77H7+t/p3P9zlzW4zLqJlTsnm3rdq/jzXlvsnrXao5qfRRndz6b2KjIrQJX1TwROQl4HSdJeq33UIr3BfAVcIl9PjXGGGOMMYGEXIO0sqnQ9Uw2bIBu3Zxadnl5zlhiItx/P9x6a+C5f/0FvXpBerpTww+cJievvGIJhjJ01XdX8e6f75Kemw5AjDuGJslNWHjVwoC1QPeHRz0MeHsAczbMITPPaboVHxVPr6a9mHrJVET8777enrmdek/UQyn873OcO47Mu62BV1kqaT0Tbz287cAUvElRVV0Rrvj2V9jvne+/79RRLmrwYPjoo/Bd15gytiNzB71f783GtI2k56YTFxVHlCuKHy76gT7N+gScuyF1AymjU9idvZv03HTio+KJjYpl5qUz6dKgSzn9BiUzbdU0Tv7gZPI8eWTnZ5MUnUSLWi2YdfkskmOTg84Pdx09EekEnEiRh/eqOj9c1yyoQn/uNMZUWlaD1BhjSq48apCastKkCSxY4HRFbt0a+vZ1kgbBkqMABxwAc+fC+ec7cwcMgK+/tuRoGXvl5Fd46aSX6NmoJ21rt+XGQ25k7rC5YUuOArjExQ8X/cDIASPpVK8Tnep1YuSAkUy6cFLA5ChAnfg6LLtmGS1rtgRAEHo26smWEVvCFq8JWQpQX1XPVtVRFTk5Wi4uvtj3+Mcfl28cxuynh6Y/xJrda/Y+SMvKyyItJ42LvryIYA+fb5t0G1vSt+ydm5mXya6sXVzx7RVhj7s0POrhgi8uID03fe8Oi7TcNP7d8S9P//p0hKNzqOpSVX1GVa9V1atU9ZHySo4aY4wxxpjKz1aQGmNMCdiT/P0UKNn/5Zdw2mnhu7YxZajFsy1Yu3ttsfG4qDhWXLeCpjWa+p1b49EapOakFht3i5vUO1OJj44v01j311/b/qLnaz33JnQL6lC3A8uuXRb0PezeaUx45OTn8PmSz5m6eiota7ZkSPchNEluEumwTBmxe6cxxpRcae+dfmuQisi/+xGPqmrb/ZhvTKWnqiiKS8p3oXZOfg5u3Ljd7nK9br4nH5e4gq52NcavuhW7/qIxBcVFxfkcV9WgdTlj3MUargPOToKK2JU+LiqOfM33eSw+qmIlc8uCiMQB04FYnM/Kn6nqfZGNypjiUrNT6TumL6t2riItJ424qDgemfEI4y8Yz+EtD490eMYYY0ylEihz02o/X8ZUS9szt3P+5+cT91AcMQ/GcMw7x/DP9n/Cft33/nyP6AeiiX0olqiHokh8JJG5G+aG/bqz1s2i52s9iX4wmuRHk7lp4k1k5xVvcmUMALEBEkeH25c5U3lcefCVJEQnFBpzi5uUJinUS6gXcO6lPS4tlmCNdkVzygGnEO2OLvNY91eLmi3oWLdjsQd+CdEJDE8ZXm5xiEj+frzySnCpbOAoVT0I6A6cICKBC8saEwHP/PoMK7avIC3HafCZlZdFem46F3xxQdBSH8YYY4wpLFCCtPV+vNqEL2RjKi6Pejji7SP4bMln5HhyyNd8pqyaQp83+7Ara1fYrrto8yIu+vIi8nTf97+M3Ax6vd6L/Hzfq37KwvKtyznmnWOY/998FCU9N53X5rzGRV9eFLZrmkpu8WLf46NHl28cxuyn6w+5nmPbHEt8VDyJ0YkkxyTTvGZzPjzzw6BzHxjwAIc0PYTE6EQSoxNJikmiQ70OvHbKa+UQeel8PvhzmiQ1ITkmmcToROKj4hnUYRBX9CzXuqmyH6+Qt3OoI837Y7T3ZdkmU+F8sOgDsvKyio1vy9zG39v/jkBExhhjTOXldx+Xqq4uz0CMqQqmrprKqp2ryPXk7h3zqIeM3Aze+/M9rul9TViue9EXvhOSinLdhOt45eRXwnLdJ395stgH88y8TL7961vW714fsAafqabatgVVOPdcmDIF2rSBn36ChITgc42pQKLd0Xx17lcs3LSQ2Rtm07xGc45qfRRuV/DyJvHR8UwdMpXZ62fz56Y/aVenHf1b9q/QJUra1G7DyhtX8uO/P7IhdQOHNjuUTvU7lWsMqlpuNWtExA3MBdoBL6vqbz7OGQYMA2jRokV5hWbMXnFu/6U+/JUBMcYYY4xvFa/QlTGV2F/b/iLfU3zFZkZuBgs3LwzbdVftXOX32OwNs8N23T83/emzLl2sO5YV2wM3KTHV3EcfRToCY8rEgQ0P5MCGB5Zqbq+mvejVtFcZRxQ+Ua4oTmh3QqTDKBeqmg90F5FawJci0lVVFxU5ZzQwGpxGI+UfpanuhqcM59YfbiUjN2PvmEtcdKjbgRY1LWlvjDHGlET5do8xporrUr+Lz9VDidGJ9GjUI2zXbV+nvd9jh7cIX13HlCYpPhuKZOdn06Feh7Bd1xhjjCkPqroTmApUj8ywqVSGHTyMge0HEh8VT0JUAskxyTRKasRn53wW6dCMMcaYSscSpMaUoX4t+tGxbkdi3fsa0bjFTXJsMhd0uyBs1/3wLN817wThyWOeDNt1b+17a7EtXAnRCQzuMphGSY3Cdl1TQWzaBOPGwYIFzrb5kvjuOzj/fHjppbCEZkx52Zi6ke//+p6Fm0q+S+DnNT9z/5T7Gf/3+DBEZkpLROp7V44iIvHAMcCyiAZljA9ul5uPz/6Y3674jWdPeJYPz/yQ1Teupm2dtpEOzRhjjKl0bIu9MWVIRPjpkp+4/YfbeX/h++R6cjmp3Uk8f+LzJMUkhe26beu05dvzvuXsT84mK9+pCVo7tjazrpiF2x28Hl5ptandhpmXzuSGCTfwy9pfqBFbg+t6X8dd/e8K2zVNBaAKI0Y4yc3YWMjLg3btYOJEaNgw8NzMTOec1FTn5w8/hBtvdJKsXbuGO3JjyoxHPVw37jrGLBhDrDuWXE8uXep3YfwF46mbUDfg3Ky8LDq+1JHVu/aVe68dV5sl1yyxh0sVQ2NgrLcOqQv4RFW/i3BMxvi1P6U+jDHGGOMQLemqn0oiJSVF58yZE+kwjDFVjIjMVdWUAMfH7Mfbq6pevh/z91tI984PPoBhwyA9fd9YVBT06QMzZgSee+CBsGhR8fG4OCd5akwlMXruaG6aeFOh2n/RrmiObn004y8MvCL06LFHM3nV5GLjbWu3ZcX1K8o81oog2L2zsrPPncaYcLB7pzHGlFxp7522gtQYY8rWkP2Yq0BEE6Qhee65wslRcFaRzpkDGzZAkyb+5/pKjgJkZcHKldC6dZmFaUw4Pf/b84WSowC5nlymrJrCjswd1I6v7Xfu1NVTfY7/s+MfsvKyrPu0McYYY4wx5axCJEhFZBWQCuQDeUUzvSIyAPgaWOkd+kJVHyjHEH2bM8epoffPP+B2w5lnwrvvOiupTKWW78nnpd9f4uXZL5OWk8YpHU5h5ICRIW19zMnP4alfnuKNeW+Qk5/D2V3O5t7+9wb8srxHek46j858lHf/eBeAiw66iDv73UliTGLQuSt3rOTMT87kj01/4BIXR7Y6ki8GfxHWrf3Gp0sjHUDY7djhezwqCnbtCpwgDWTTJkuQmkpjV9Yun+MucZGakxrwnu9Rj99jaTlpliA1xhhjjDGmnFWkTN6Rqro1wPEZqjqw3KIJZvly6N17X2MSjwc++gj+/BMWL45sbGa/Xfr1pXy+9PO9q4PGzB/Dd399x5Krl1AzrmbAuYM+HMS01dPIzHO2C78y+xWngcdVC4mNivU7z6MeBowdwKJNi/bWEX36l6eZ9M8kZl0xC5f476m2M2snHV7qQK4nd+97/fDvD7R+rjVbRmwpya9u9pOqjo10DGE3aBC8+CLk5BQej4uDAw4IPDcxsfjq0z169Sqb+IwpBycfcDJj5o8hz5NXaLxOfB2a12gecG7DxIZsSt9UbDzOHUe9hHplGqcxxhhjjDEmOOtiX1rDh/vu2rxkCcybV/7xmDKzcsdKPl3yaaGtk3mePHZk7uDN+W8GnDtnwxxmrJmxNzkKzorSjWkb+XTJpwHnTvpnEsu2LtubHAXIys9i6dal/PDPDwHn3vHDHXuTowVtzdzKe3+8F3CuMSV2xx3QoAHExzs/u92QkABvvOH8PZB33/U9fu21wecaU4Hcf8T91I2vu3e1p1vcJEQn8OapbyIiAee+f8b7CMXPefGkF8MSqzHGGGOMMSYwvytIy7nRiAKTRESB11R1tI9zDhWRP4ANwK2qGtllmgsW+D/27bfQs2e5hWLK1tyNc4lxxZBFVqHxzLxMpqyaws2H3ux37pwNc3xunUzLSWPmmplc2O1Cv3Nnr59Nek7xlXXpOenM3jCb49sd73fu9DXT/R4bt2IcFx7k/7rGlFi9erBwIYweDZMmQatWcMMNTgOmYE4/HWbOhAsugPXrITkZnnwSLq/4pVeNKahxcmMWX72YUXNGMWXVFNrWacuNh9xIp/qdgs49us3RLBi+gKu/v5olW5bQsmZLnj3hWQa0GhD+wI0xxhhjjDHFBNpiP2Q/3rekjUYOU9UNItIA+EFElqlqwYzPPKClqqaJyEnAV0D7om8iIsOAYQAtWrQodfAhadwYdu70fax79/Be24RVy5otydf8YuPRrmg61O0QcG6Lmi2IchX/1yo+Kp72dYr9I1v4urVakhidSFpuWqHxxJhEWtZsGXBu61qtWbp1qc9jHet1DDjXlA8RiQOOBA4AaoCP5WPOw6UHyzWw0qpVC0aMcF4lddhhsGpVWUdkTLmrm1CXu/rfxV397yrx3G4NuzHzsplhiKrqEpHJ+zFdVfXoMgvGGGOMMcZUKYESpOXWaERVN3j/3CwiXwK9gekFju8u8PdxIvKKiNQrWrPUu/J0NEBKSoqP/e9l6Ikn4JRTio8nJTn1+UylldIkhXZ12rF4y+JCteVi3DFc0+uagHOPa3scdeLrkJGbUSjJGuWK4uKDLg4496zOZ3HzxJtJz01Hcf7xFYS4qDjO7HxmwLlPHfcU41aMKzbuFjd3HHZHwLkm/ETkTGAUUCfQaTgPlypHgtQYY8rfgP2YG97PhcYYY4wxplLzmyAtr0YjIpIIuFQ11fv344AHipzTCNikqioivXFqp24rj/j8GjgQHn0U7r4b8r2JsAYN4JdfIhqW2X8iwg8X/cBFX17ElFVTcOGicXJj3hr0Fq1rB+6wHeWKYvql07ngiwv4ff3vCEKb2m149/R3qZ9YP+DchOgEZl42kws+v4BFWxYB0LVBVz444wMSohMCzu1UvxPvnvYul397OTn5TuOcGrE1mHThJGKiYkrw25uyJiKHAB8BHuBDoCtwIPAY0A44FqgJvAmsi1CYpefxgKuU5azz80tfd1QVgtR5NNWPqgat/xmOuR6PB1cp/z3Iz8/HHYH6u/vz+0bQkZEOwBhjjDHGVE2ivhoNlWcAIm2AL70/RgEfqOrDIjIcQFVHici1wFVAHpAJ3KyqATORKSkpOmfOnDBG7uXxOE2ZGjSAcG/rN+VuZ9ZOMnIzaJzUuMRfJLdlbCPXk0ujpEYlvu6WdKfzfLCkqi9/bvrT2dJfN/CWflM6IjJXVVNKcP6nwBnAqar6vYi8BVysqm7v8XrAW0BPoKeqFm9t7ft943BW2sfi3Ds/U9X7ipwjwPPASUAGMERVA3aRC/ne+dxzTrOm7GwnUTlgAEyYADEhJOTPPRc+/njfz127OnWdQ0kSffAB/O9/sGYNNGkCDz4Il5bbhgdTQS34bwHXjLuGWetmER8Vz+U9L+fxYx7f20ApkJ/X/Mx1469jwX8LqBFbg+t6X8d9A+7zWS6lqEdnPMr90+4nJz8HQTi+7fF8e/63Ic0d9OEgvvnrm70/H9z4YH67/LewJktVled/e55HZz7KlvQttK/TnqePf5qBBwwM2zX3KOm9s7Ipt8+dxphqxe6dxhhTcqW9d0Y8QRoudrM1xoRDKRKk64GtqnqQ9+dCCVLvWDKwEifJOTzE9xUg0VubORqYCdygqrMKnHMScB1OgvQQ4HlVPSTQ+4Z073zvPbjoouLjBx0UuIEdwLBh8PrrxcfbtYO//w489+OP4bLLICNj31hCArz4ojNuqqXVO1fT9dWupOXsq98cFxXHcW2O4+vzvg44d9HmRRzyxiFk5O77ZyohOoHzup7HG6e+EXDuK7Nf4ZpxxcuuHNb8sKC1RQd/OphPlnxSbPygBgex4KoFAefuj0dnPMpDMx4q/PtGJfDNed9wdJvwlue0L/nGGFNydu80xpiSK+29s8T7wUQkTkROFJEbROQeEbnXx+uekr5vpbR5s9ORuWZNZyXTM8+Uz3WnT3dWq7rdTgfoJ54on+sa48Pu7N08+fOT9H+rP+d8eg4z11jTkSLqAcsL/JwHICLxewZUNRVnNeiJob6pOvZkhKK9r6JPvAYB73jPnQXUEpHGJf8Virj1Vt/jf/wB64JUCXjzTd/jK1bArl2B5951V+HkKDg/33134HmmSnv+t+fJzssuNJaVl8Wkfyfx745/A859dMajZOVlFRrLyM3g/YXvsy0jcCWfuyb7bsz089qf2Zm1M+DcT5d86nP8j81/7C2TUtZy83N5dOajhZKjABl5Gdw92f4dMsYYY4wx1VvwPWAFWKORArZuhebNIcf7RWb3brjlFpg8Gb77LnzXHTcOTj55389paXD77c42/48+Ct91jfFhd/Zuer7Wkw2pG8jMy0QQvv/7e54+7mmGp4S0ELI62IGzDX6Pnd4/mwEFl0wq0KAkbywibmAuTi3Tl1X1tyKnNAXWFvh5nXdsY5H3GQYMA2gRSqmQbQESR3PnQrNm/o97PP6PzZ/vbNX3Z/Vq3+MbN+5fLVRTqc3/bz65ntxi47HuWJZvXU6b2m38zl2waQEeLf7PZKw7ln93/EvdhLp+5+7O3u332KLNi+jXop/f4xqgX9DaXWtpW6et3+OltT1zu8//nQCWb1vuc7yyEJEmOA+EDgBq4HwWLUpV9fJyDcwYY4wxxlQaIX+bLNBopAZOo5GF3kOPAZ8Be5b+vEmRJktV0lVX7UuOFvT99/6/xJeF887zPf7xx77jMSaMXv79ZdanriczLxNwvvRn5GZwy6RbSM9Jj3B0FcZaoGDWcRHOl/e9Rf+8Der6AetL8saqmq+q3XGSrb1FpGuRU3wmCXy8z2hVTVHVlPr1Q6h7G+icXr0Czw2UxEwJsguitZ8maU2bWnK0GktpnEKMu3jt2+z8bDrW6xhwbs9GPXFJ8X92svOzgyYpa8bW9Husa4Oi/yoWJj7/1XS0qtkq4NzSqptQ1+f/TgCd6nUKyzXLg4jcCPwLvARcDwwp8LrE+9rzszHGGGOMMT6V5Bvlrd7zz1DVC4H5AKp6l6oOxnlqPw6n1t2osg60wvnpJ//H3n03fNfd7X/FCt9+G77rGuPDN8u/KbY9FSDaFc28jQF7AVUnU4EuIrInq/gdTsOkR0XkcRG5zntOPeCH0lxAVXd63+OEIofWAc0L/NwM2FCaaxTy9NO+x3v2dMqNBHLllb7HO3SApKTAcx95xKk5WlBCAjz8cOB5pkq7/pDriXXHFhqLj4rnxHYn0rq2n6S6152H31mskVNCVAKXHHQJdeIDbZaBx455zOd4/xb9qRVXK+Dc87r6ftjZs1HPsDVpinJFcdfhd5EQXfjfofioeB466qGwXDPcROR44BkgC3gU+NV76ErgSZzazuA0q7NCxcYYY4wxxq+SJEj7AotU9XtfB1V1K3A+zlbSkWUQW8VWo4b/Y82b+z+2vwJ1Um9b9lvyjAmkfqLvlYS5ntyAW1OrmU+BaUAPAFXdBtyCUzP0VuA54GCcZGbI9ZtFpL6I1PL+PR44BlhW5LRvgIvF0QfYpaob2V/nnQcvvwzx8XuCgeOPh9+K7vD34ZVX4OKLC4/17AmLFwefe9ZZ8Pbbzr3O7YaWLeG11+CSS0r8K5iqo3nN5vx82c8MaDmAKFcUNWNrck3va/jwzA+Dzu1cvzOTL55M76a9cYubuvF1uaPfHbx80stB5w47eBhPHfsUcW4nwerCxWkdTmPKJVOCzn3/zPc5q/NZhcb6Nu/L3CvnBp27P27rextPHvskTZKb4BY3net15svBX3Jk6yPDet0wuh5nVfyxqno33rIlqvq6qt4OdMbZ2XQ58EvEojTGGGOMMRVeyF3sRSQb+FpVz/H+/DrO0/gkVc0scN4XwMGq2jIM8YYs7B3xRo/2vRIqKgqys8O33bNfP/j55+LjMTHOdY0pRz/9+xOnfnRqoaYfbnHTuX5n/rzqzwhGFj5l1U1URA4GzsKp6bwMeMu7EjTU+d2AsYAb52HXJ6r6gIgMB1DVUd5O9y/hrCzNAC5V1YA3RusmaowJh3B0YhaRzcBKVT3E+/NbwMWq6i5wTjTOStKp3h1QYWH3TmNMOFSULvYicgLOanw38Iaq+txGISK9gFnAYFX9LNj72r3TGBMOpb13lqRJU9gajVRKw4bB1KnwYYEVKtHRMGFCeGvhTZvmbGHdvHnfmNsd2sotY8rY0W2O5sEjH+TuyXcT444hz5NHq1qt+P58nwvNTQGqOhenwVJp5/+Jd1VqkfFRBf6uwDWlvYYxxlRwNXHqj+6RA05dZ1VNB1DVXBH5Gai0y2SNMSaSvE1BXwaOxdnxNFtEvlHVJT7OexyYWP5RGmPM/itJgjRQo5FnofSNRiqtDz6AZ56B9993kpaDB4e/UYjbDZs2OatIx451GqIMHRreaxoTwM2H3szlPS5n7sa51I2vS7eG3ZBApSCMMcaYsrEVp3noHtu9f7YCCtbtiANql1NMxhhT1fQGVqjqvwAi8hEwCFhS5LzrgM+BIB07jTGmYipJgnQqcIOI1FfVLRRuNNII52nSxTiNRr4o60ArrEaN4JZbyv+6hx3mvIypAGrG1eSo1kdFOowKTURigDOBATgr7xWnYdJU4HNVrT41Mv77D154AWbOdJoz3XwzdKq8XbRN5fbDPz8w6KNBZOY51YIGth/It+eH1vRwS/oWXvz9Raaumkq7Ou24qc9NHNjwwJDmrt+9nud+e47f1v1Gl/pduPnQm2lft32pf49qahVQsKTTApyH9+cBdwOISAOc++7q8g3NGGOqjKY4i6X2WAccUvAEEWkKnA4cRZAEqYgMA4YBtGjRItCpxhhTrkqSIP0U6I6zpXOSqm4TkVuAV3AajYDzoXQtJWg0Uql5PPDDDzBuHNSt6zQeadUq0lEZYyoYEekLfIDTUb7o8trLcR40XaCqM8s9uPK2ahUcfDCkpzt1k3/5xVmN/+23cJQl2U35+mrpV5z+yemFxr77+zuaPNWEDbduCDh3/e719HitB7uzd5Odn80va3/h48Uf8+nZn3JS+5MCzv1r21/0fr03mXmZ5OTn8OvaX3n3z3eZdNEk+jbvu9+/VzXyE3CXiLRQ1TXA9zgloe4UkfY4X+LPBJKAryIWpTHGVG6+toYVbWTyHHC7quYH20mmqqOB0eDUIC2LAI0xpiyEnCBV1d9x6o4UHHtNROawH41GKq28PDjlFGcFVFqa0yTpscfgvffgjDMiHZ0xpoIQkS7AJCABp1behzirnsDZBjoYaAdMEJFDVDWEdu6V2P/+Bzt3Og+YAPLzISPDKRWyYgVYeQZTjgZ/Ntjn+Mb0jazZtYYWNf2vbLlv6n3syNxBnuYBkK/5ZORmMPTboay9aS0u8V9y55ZJt7A7ezfq/X6Zp3nk5eZx5XdXsvCqhfvxG1U7HwKNcVaRrlHVNBG5DOeB1NkFzpsPPBSB+IwxpipYh/OQf49mOLugCkoBPvImR+sBJ4lInqp+VS4RGmNMGSjJClKf9rfRSKX18ccwY4azCgogJ8f585JL4MQTIT4+crEZYyqSB3CSo48C96iqp+BBEbnPe87/gJE4D5yqrkmT9iVHC1q/HrZuhfr1yz8mU23leHL8Hrt5ws18Nth/A94JKybsTY4WtCNzB+t2rwuYXJ2ycsre5GhBS7csJSM3g4TohCCRGwBVXQoMLTL2tYgcgFMjf8/D+29UNT8CIRpjTFUwG2gvIq1xeo2cC5xf8ARVbb3n7yLyNvCdJUeNMZVNmDsKVWHvvbcvOVqQy+U0UDLGGMcRwHJVvatochRAVT2qejewHKdOXtVWs6b/Y4mJ5ReHMUF0rd814PFacbV8jnvUQ43YGj6P7eHveJQrihh3TEjxGf9Udb2qvqaqj6rql5YcNcaY0lPVPOBanO70S4FPVHWxiAwXkeGRjc4YY8pOiROkIhIjIueJyGsi8r2IfCcio0XkfBGJDUeQFVJcnP9jMfblxhizVzwwL4Tz5uF0Wq7abrgBEoqsjouNhUGDio8bE2ad6vlvDnb/UfcHnHtTn5uKrfSMccVwTJtj/CZP97i297XF5sZFxXFBtwuIcu335p5qQ0TGeLfUBztviIiMKY+YjDGmKlLVcap6gKq2VdWHvWOjVHWUj3OHqKr/LRjGGFNBlShB6m008hfwHs6WphOBk4ArgHeBv0SkX1kHWSFdcYXv1U4xMdDXGiwYY/ZajlMjL5jGwN9hjiXyrr0WLrrISYrWrOmUI+nbF15/PdKRmWpoyTVLiHUXf7b70gkvBZ17WY/LGNpzKHFRcdSMrUlCdAK9mvbi3dPfDTp3xGEjOKvzWcS6Y6kZW5P4qHgGtBzACye8UKrfoxobAoTyufMw4JLwhmKMMcYYYyqzkJcpWKORIk46CS67zPlSLwJRUc6f33zj/N0YYxyjgFdE5DBV9Vl/Q0QOA/rjbF+q2lwuGDUK7rsPFi2Cli3hgAMiHZWpxrLuzuKrpV/x0IyH6Fq/K2+f/nZI80SE5054jv8d/j/++O8PmtdsTsd6HUOaG+WKYuxpY3nkqEdYvGUxbWu3pW2dtvvxW5ggogEfxY+NMcYYY4xxlCSTZ41GChKBF16A666DH3+E2rWdrvZWQ88YU4CqjhaRjjgPj14B3gdWeg+3Ai4Argae97VNqcpq3Nh5GVMBnNbpNE7rdFqp5jZIbMCxbY8t1dymNZrStEbTUs01JdIF2BnpIIwxxhhjTMVVkgTp3kYjvg56E6Z3i8iZVIdGI3u0b++8jDHGBxEp2BzkVu/LlxtF5MYiY6qqtiTdGGO8fNQS7RegvmgU0AnoCXwf1sCMMcYYY0ylVpIv3iVpNDKodOFEyLp1MGGC0yDklFMgOTn0uZMnw5gx0KAB3H031KkTvjiNCaNVO1fxwz8/UCO2BgMPGEhijK2GLiMSobnGVCv/bP+Hn1b+RO242gw8YCDx0fEhz33p95d45493aF6jOa+f+jp14kP/b/n8jfP5bf1vtKjZguPaHhdykyVVZda6Wfyx6Q/a1WnHUa2PwiUl7p1ZHQ0p8HfFKe/ULsic/wCfD/iNMcYYY4yBkiVIq2ajkUcegQcfBLfbqY03bBh89RUcc0zgeR4PpKTA/Pn7xp59Ft54Ay6/PKwhG1PW7vrpLp6Z9QwuceEWNwDjLhhHvxbVo+daOKmqZTyMCSNV5eZJNzNqzqi99zCXuJh00SR6N+0dcG5Ofg51H69LWm4aALM3zOaLZV/w4okvcm3vwCWBc/NzOf3j05myagqqSpQrilpxtZhx6Qxa1moZcG5GbgbHvXscC/5bgEc9uF1umtVoxvQh06mfWL9k/wNUP5d6/xRgDDATeNPPuTnAemCWquaUQ2zGGGOMMaaSKskX91FAf28zEZ8KNBp5bX8DKxe//w4PPwxZWZCeDqmpzp+nn+78GciDDxZOju4xdKjzfsZUElNXTeW5354jKy+LjNwMUnNSSc1J5ZQPTyEn375PGmMqtvErxvP63NcL3cN2Ze9i4AcDyffkB5x77DvH7k2OFnTd+OvIzw8899lZzzJ55WQycjPIzMskNSeVDakbOO/z84LGfPfku5m7cS7puelk5mWSlpPGiu0ruOLbK4LOre5Udaz39TawBif5OdbP60NVnW7JUWOMMcYYE0zICVJVHQ28gNNo5HER6SYiyd7XgSLyGDCeytRoZOxY38lMEZg4MfDc1/zkgFWdVaTGVBJvzn+TjNyMYuMe9TBl5ZQIRGSMMaF7fe7rpOcWf6iZlZfFL2t/CTh35tqZfo+9MT/wf8tfn/c6mXmZhcbyNZ95G+exJX1LwLnv/PEOWXmFP3/kefIY//d4ezBVAqraSlVHRDoOY4wxxhhT+YW8xb5KNhrJzHS2yhelCtnZgefm5vo/llZ8NYoxFVVmbqbfY9n5Qf49MCETkXbAlcChQH3g6z1f7EWkD9AN+ERVd0YsSGMqoaJJyj1EJOg9TFX9HtuZtTPg3Ow83+8tSNAkp7/jHvXgUR+fS0xQIlIT6IVzf12tqoGz48YYY4wxxhRQki32sh+vilmD75xzINFHI5q8PDjuuMBzzzrL/7Fhw/YvLmPK0XldzyMxuvi/B7n5uRzZ6sgIRFT1iMjlwCLgFqAvTkORegVOqQ+8Cpxe/tEZU7ldcOAFPu9hHvVwWHO/VYEA6Fivo99j1/e+PuDcwV0HE+uOLTbeolYLmiQ3CTh3UMdBxZo5CcIhzQ4hLiou4FxTmIjU9Hax3wxMBN4Drihw/GoR2eB9EGWMMcYYY4xPJdli79qfVzh/iVI7/nina/2eJGlUFMTHw3PPQd26gec++6zvc26+2TrZm0rltI6ncXSbo/cmGKJcUcRHxfPaKa+RHJsc4egqP29t5teALOA24BCKd6efAOwGTi3f6Iyp/M478Dz6Nu9LUkwSANGuaOKj4nlr0FtBO9n/cOEPPjvHD+05lPiYwHPvOvwuWtVqtfe6cVFxJMck897p7yFS9F/xwp489kkaJTXae99NiEqgVlwt3jzVX68h44uIJAJTcTrb78Ap9eTr/toIOK0cQzPGGGOMMZVMxdv2Xp5E4IMPYMoU+OILSE6Giy+GTp2Cz42Lg//+g0cegU8+cZKiDzwAAwaEPWxjypLb5ebLwV/y478/8vWyr6kdX5tLDrqE9nXbRzq0qmIEoMCJqvorUCx5oqq5IrIcCOHmY4wpKMoVxYQLJzBhxQS+++s76iXUY0j3IbSp3Sbo3KY1m7Lrjl1c8PkFTF89nTrxdXjxpBc5qf1JQefWiqvFH8P/4LMlnzFjzQza1G7DkO5DaJDYIOjcRkmNWH7tcj5c+CG/b/idLvW7cFG3i6gdXzuk39nsdStwEM6q0eGqmiEihWoUqOq/IvIXcFQkAjTGGGOMMZWDBKq/VZmlpKTonDlzIh2GMaaKEZG5qppSgvM3A3+r6mEFxjzA26p6WYGxT4HjVLVmmQZcQnbvNMaEQ0nvnSG+5yKgFtBWVbO9Y77ur5OALqratCyvX5DdO40x4RCOe2dFYvdOY0w4lPbeWeKt7yLSTkSeFJGZIrJcRJ4ocKyPiAwTkVolfd9Ka+VKuO46ePppyM8Pfn5Ba9Y42/m/+67k1/37b3jmGfjpp5LP/e8/Z9Xs6tUln2tC4lEPs9fP5uc1P1tHYlMTWBfCeTFU91X9xhhTMm2A2XuSowFsBYLUTjLGGGOMMdVZib6MexuNvIzzRR6cbaO+Go3kAm+VRYAV2iGHwO+/7/v51lvho49g8ODgc084ASZO3PdzQgL8+it06xZ4nscDhx5a+Lo1a8Iff0DLloHn5ufDlVfCe+85JQKys+HYY+Hjj53aq6ZMzNs4j1M/PJXd2bsREQThvTPeY+ABAyMdmomMzUDrEM7rAKwPcyzGlIst6VtwiYu6CeWXk1JVNqZtJCkmiRqxNUo016MeNqZupGZczb01RUOV58njv7T/qBNfh4TohBLN3R/ZedlsydhCg8QGxLhjgk+omnKBULpaNQPSwhyLMcYYY4ypxEJeQWqNRoq4667CSco9zj03+ErSkSMLJ0cBMjLgsMDddgEnwVn0urt2OcnaYJ56Cj780EmM7toFWVnwww9www3B55qQZOVlccw7x7A+dT2pOanszt7NruxdDP50MKt32ordaupnoKeI+F3iLyLHAgfgNBsxptJatHkRB716EM2ebUaTZ5pw6BuH8u+Of8N+3R/++YGWz7Wk7Qttqf9kfU798FR2ZO4Iae7nSz6nydNNaP9ie+o9UY8LPr+A9Jz0kOa+Of9NGj7VkANePIC6T9Rl+HfDw75rwKMe7p1yL3WfqEuHFztQ74l6PDLjEapqyaQglgM9RMRvklREauPUKV1YblEZY4wxxphKpyRb7As2GnlaVWcXPUFVc3E+rFb9RiPPP+//2AMPlG5uWhpMnx547rvv+h7ftCn4lvkXXnASsQVlZTnvmZcXeK4JyXd/fUeep/j/lnmePN5e8Hb5B2QqgmdxHiZ9ISLHiRRumS0i/YExQB7wYgTiM6ZM7MraxeFvHc6fm/8kJz+HnPwcft/wO/3G9Atr0nDplqWc9vFprN29lqy8LHLyc5i4YiIDPwy+av/Xtb9y8VcXsyl9E5l5mWTnZ/PF0i+48IsLg879/q/vuX789WzP3E5mXiZZeVm888c7XD/++rL4tfx68ucnefrXp0nPTScjL4PUnFQemfEIr855NazXraA+AxoAjwU45xEgCfikXCIyxhhjjDGVUkkSpIcCv+/pwhzAWqBx6UOqJLIDlLtatSrw3MxM/8fWrAk8NzfX/7F1Qcoc7t7t/z1zrE5mWdieuZ18T/EVxDmeHDanb45ARCbSVPU3nAdMzYDxwDach02nicgmYArQFBihqrbCyVRaHy76sFgi1KMe0nLS+Hb5t2G77nO/PUd2XuH/Jud4cljw3wKWbFkScO7jPz9OZm7h/yZn5Wcx4Z8JbEzdGHDug9MfJCO38EPHzLxMxv4xlrSc8O3mfuKXJ4pdNz03nUdmPBK2a1ZgLwFLgeu8tfFv9o63EpGrRGQyMAxn9eibkQrSGGOMMcZUfCVJkFqjkYK6dPF/7KabAs/t2dP/sVODVCdo29b3uMsVfJt9//4gRasiAAcc4NRANfttQKsBKMW3OSZFJ3F8u+MjEJGpCFT1aeAkYA5QA2dFaS2cus2LgNNU9blIxWdMWfh3x7/FEncA2fnZrN4VvhIjf2/7m3wt/mAqyhUVtLTJiu0rfN6zY9wxrNsd+CPPml2+H2i6xMXWjK0B55ZWvief7ZnbfR7blL4pLNesyFQ1AzgO+A3oCzzpPXQETvJ0ADAPOFlV7UmwMcYYY4zxqyQJUms0UtCXX/oeP+AA6N498Ny33wa3u/j4pZdCjSCNJd5/33eS8447ICpIXvrppyE5GaKjnZ/dbicx+tprgeeZkB1Q9wAuPuhiEqMT944lRCdwcJODObn9yRGMzESaqk5Q1UNwtoP2xlmV30xVD1LVbyIbnTH7r0+zPj4bHMW4Y+jVpFfYrjug1QDi3MVLUGbnZXNQo4MCzj28xeFEuYr/tzM3P5eO9ToGnHtI00OQYqXYIcYVQ9PkpkGiLh23y027Ou18Hutav2tYrlnRqep6Ve2L8xDqZWAcMAlnxeiZQG9VrfqfS40xxhhjzH4pSYLUGo0U1Lo1rFjhrCR1uSA2FoYOheXLg89t3x7+/huOPBKSkqBpUxg1CsaMCT63Vy/480/o08eZ26oVfPABPPxw8LkdO8KiRXD11c78IUNgzhw4/PDgc03IXj35Vd45/R2Oa3sc/Vv059njn2XSRZNwu3wkxU21o6rbVHWOqv6mqhsiHY8xZeWUA06hZc2WxLpj947FR8XTo1EP+rXoF7brXt3rapJjk4mSfYnOhOgELu1+KU2SmwSce0e/O0iMTsRVoDRwQnQCtx92O8mxyQHnPnjUgyREJxRKkiZEJ/DI0Y8Q7Y4u5W8T3HPHP0d8VHyhsYSoBJ4+/umwXbMy8D6Eul5VB6rqiao6TFW/1GravcoYY4wxxpSMhPq5UUQOAX7BWR16BfAjTlORt1X1Mm+jkfeBhsDBka6ll5KSonPmzIlkCMaYKkhE5qqq3wdFJXyv9kA3YLWqVogblt07zf7Ynb2bh6Y/xIeLPsQtbi7tfim397uduCi/TcbLxLrd67h3yr2M/3s8NeNqcv0h1zM8ZXihxKc/K7av4O7JdzN11VTqJ9bn9sNu54IDL0B87dYoYuGmhdw1+S5+W/8bzWo04+7D7+b0TqeXxa8U0JSVU7h36r38te0vutTvwoNHPshhLQ4L+3X3R1neOysiu3caY8LB7p3GGFNypb13hpwg9V7kFpz6TgrsxqmltwvIBerh1NW7uSLU0iu3m21urrNlvmPHkq/E9Hic7vM1akBiYvDzC8rPd+bWrg3x8cHPLys5ObB1K9Svv2+rfhW2bvc6dmbupHP9zrhcJVlwbaqqkt5sReQMnIdKI70Nm/aM3wPcB3uXn32oqsFbZ4eZfVA1xoRDOL/ki0gMznb6ATgN8RTYgLOj6XNVDdBZs2zYvdMYEw6WIDXGmJIr7b2zRBmfcDUaEZFVIrJQRBaISLE7pDheEJEVIvKniAToclSODjkEYmJg2LB9DZC+/z60uV9+Cc2aQZs2ULcuXHQRZBRvbuHT2LHQsCG0awd16jhb5sPdhV4VRo50rteunRPzY48541XQ8q3Lqf9kfZo/25wDRx1I7MOxvPT7S5EOy1ROFwL9cbooAyAiXYGRgAenfMlO4DxvMtUYY0yIRKQv8BfwHjAUOBHns+oVwLvAXyISvhoPxhhjjDGmSihxt3lVnQBMEJG6OE2b3MDaMqild6Sq+mv7eiLQ3vs6BHjV+2fkXHst/P578fGBA4MnDWfNggsvLJwQ/ewzSE+HL74IPHf8eCchWnDu2287q1FHjQo5/BJ76il44onC133wQahZE666KnzXjQCPx0OP13qQmZe5dyzPk8d146+jW4Nu9G/VP4LRmUqoB/CHt9vyHhfirHC6QlXfEZE2wBKcL/dBbgLGGGMARKQLTkOmBOBf4ENglfdwK2Aw0A7nc+shqro4AmEaY4wxxphKoNR7hsu50cgg4B11zAJqiUjjMF8zsFdf9X9s2LDAcx9/HDIzC49lZcG4cbBxY+C5DzxQfKVpZqazqjQtLfDc/fH448Wvm5ERWnOoSua9he8VSo4WdMukW8o5GlMF1MWp3VzQEUAa8AGAqv4LzAQ6lW9oxpStjNwMHpr+EB1f6kiXV7rw7K/PkpufG/brLt68mB6jehD9YDSJDydy9fdX4/F4wn5dE3EP4CRHHwUOUNV7VPVN7+seoCPwiPeckRGM0xhjjDHGVHBlUlRRRNqLyJmBOtwHocAkEZkrIr6yi02BtQV+XucdKxrHMBGZIyJztmzZUspQQhToi9evvwaeu2KF71WmsbGwbl3guatX+x53uyFcv3N+Pmzb5vvYpk3huWYE/bHpD7/H1uxeU46RmCoiln11RvfUyusO/KqqeQXO+w+nyZ0xlVK+J58j3j6Ch2c8zPJty1myZQl3Tb6LUz48hXA2El+5YyXdRnVjwaYF5HnyyMjL4NU5r9L7jd5hu6apMI4AlqvqXapa7IOZqnpU9W5gOU59UmOMMcYYY3wKOUEqImeIyDhvN/uC4/cAS4FPgN9E5L1SxHGYqvbE2Up/jYgU3cPsq5VssW9bqjpaVVNUNaV+/fqlCKMEYmP9Hxs+PPDcfv0gykd1g5wc6NAh8NzevZ1ap0VFRTk1TcPB7XbqjvrSuXN4rhlBpxxwit9jvZvYF25TYhuBgv+i9MdJmv5c5LwknOZ3xlRK4/4ex7Kty8jKy9o7lpmXycw1M5m1blbYrjv8u+F4iufGmLtxLos3247qKi4emBfCefOAuFDfVESai8gUEVkqIotF5IZSR2iMMcYYYyqFkqwgDVujkT1b9FV1M/AlUDQLtQ5oXuDnZjjdSSNn7Fjf4y4XXHNN4Lm33+50rS/YFT0hAW67zeloH8hDDznnFkySJiQ4W93D2VX+uecgPr7wWHw8PP10+K4ZIQNaDaBVzVbFxl3i4qWTrFGTKbFpQEcRGSEi3YAHcR7wTChyXlece50xldLMNTNJyyle6iXXkxvWBOnvG3zUA/f6ctmXYbuuqRCWA6GUXGoM/F2C980DblHVTkAfnIf3Ve+JsDHGGGOM2askCdJgjUb6A72AXJxGIyERkUQRSd7zd+A4YFGR074BLvZ2s+8D7FLVIMU6w2zwYKdpUcFEZc2akJoafG6rVjB7NpxxBtSv76zCfOUVp0t8MF27wi+/wEknOXO7d4d33gmelN1fJ58M330HfftCvXrQvz9MnAjHHBPe60bI8muXM6jDIKJcUQhChzodmDt0Li1rtYx0aKbyeRin3uijwHycBnM/qersPSeIyAFAG+C3iERoTBloXrM58VHxxcZj3bE0SW4StuvWT/C/Y6RjvY5hu66pEEYB/UXkMH8neI/1B14L9U1VdaOqzvP+PRVnp1Sx0k7GGGOMMabqkFDrgonIbmCCqp5TYOxXnK2jdffU0hORH4F2qtoqxPdtg7NqFCAK+EBVHxaR4QCqOkpEBHgJOAHIAC5V1TmB3jclJUXnzAl4ijHGlJiIzFXVEtVb9q62vxloAPwOPKmqmQWOXwUMA+5S1XFlGW9J2b3TlNb2zO20fq41u3P2VYoQhHoJ9Vhz0xriokLe4Vwiny35jLM/PbvYeHxUPBl3ZfiYYSKhNPfOEN/3GZwH868A7wMrvYdaARcAVwOvq2qpuiyKSCtgOtBVVXcXOTYM595NixYtDl7tr068McaUUrjunRWFfe40xoRDae+dPgph+uWv0cg0H41G/D7JL8rbvfkgH+OjCvxdgTAvkTTGmPBQ1UXAZQGOvwq8Wn4RGVP26sTX4ceLf+Tcz89lY6qzyaNt7bZ8cvYnYUuOApzV+Szu7Hcnj818DPWWJ68VV4uZl84M2zVNxSAi+QV+vNX78uVGEbmxyJiqasDPwSKSBHwO3Fg0Oep9g9HAaHC+5IcatzHGGGOMqXhKssW+ajYa+flnaNLE2SovAj17wtatoc0dN87Zbi7i1BM9/HDIsNUqVUF2XjYjfhhB3SfqkvBwAgM/GMg/2/+JdFjGGFOh9WraixXXrWDR1YtYdu0yFl69kE71O4X9uo8c/QhZd2Ux7vxx/DH8D3bcvoMuDbqE/bom4mQ/XgE/A4tINE5y9H1V/SJM8Zfc8uUwYQJsKMdS/Kowbx5MmgQ7d5bfdY0xxhhjylFJVpBOAy4UkRE4zUUqf6OR1audpGbBMgPz5zsd24N9AJw3z6nLuYcqzJzpdKFfuzYs4Zryc+YnZ/LTyp/2dmMev2I8v77xK8uuWUb9RP/17owxproTEdrUblPu142JiuHE9ieW+3VN5KhqSR70h8xb2ulNYKmqPhOOa5TYrl0waBD8/jvExEB2Nlx4Ibz2WuGmn2Vt9Wo4/nhYtw6iopzrjhwJI0aE75rGGGOMMRFQkk9UVa/RyI03Fk6O7rFrF7z/fuC5V1/te3zdOidRaiqtZVuXMXnl5L3JUQCPesjIzeC1uSH3eDDGGGNM5XQYcBFwlIgs8L5OimhEQ4fCrFmQmel8Ts3Kgg8+gBdfDN81VZ3FAH//Denp+647ciT8+GP4rmuMMcYYEwEhJ0hV9S+cD4xjgfHA/cCgIqcdDfwBfFdG8YXX/Pn+j02bFnju8uX+j/30U+niMRXCos2LiHZHFxvPysvit/WVI/dvjDGV0Vvz36L/W/054b0T+HlN0Qo+4eFRDxNXTOR/P/2PF397ka0ZIZbZ2U85+Tl8vOhj7vzxTt6a/xbpOenlcl0TnKrOVFVR1W6q2t37ilwDvYwM+PprZ/Vm0fEXXgjfdRcvhlWrwOMpft3nnw/fdY0xxhhjIqAkW+yrXqORTp2crUO+9O4deG6rVrBgge9jffvuT1Qmwg6oewB5nrxi47HuWLo37F7+ARnjg4g0B94BGgEeYLSqPl/knAHA1+zr6vyFqj5QjmEaExKPx0PnVzqzfNu+h48T/5nI8IOH8+rA8H2syMnP4bh3j2Puxrmk5aQRHxXP/yb/jwkXTOCwFiH3myyxbRnbOOSNQ9iUvom0nDQSoxO548c7+PWKXyNSnsBUcIHq2+/aFb7r7tjhbKv3JdR6/cYYY4wxlUQYixa9W3PNAAAtw0lEQVRVAs884zRYKiohAS7zmwd2+NvSVLcuHHvs/sdmIqZbw24c3PhgYt2xhcZj3DFc1euqCEVlTDF5wC2q2gnoA1wjIp19nDejwAooS46aCunpX58ulBzdY9TcUaze6edBZhl4bc5rzN4wm7ScNAAy8zJJy0nj7E/PxqOeILNLb8QPI1iza83e66bnprM1cyuXf3N52K5pKrG6daF58+LjLpdTHzRcDj4Y8oo/MCY+Hs44I3zXNcYYY4yJgOqdIO3UCb76CmrU2DfWqhUsWRK84H2/fjB2rJNMLfh+y5aFI1JTzr4//3vOP/B8Yt2xuMRFn2Z9mHHpDJokN4l0aMYAoKobVXWe9++pwFKgaWSjMqZ03pj3ht9jz/wavh45Y/8YS0Zu8dV5qTmpLNq8KGzX/Xzp5+R6cguNedTDjNUzCtW/NgZwHua//rrzmdPtdsZiY6F2bXj44fBdNyEBnn3W+XPPgoL4eGjWDIYPD991jTHGGGMioERb7KukU0/dV3Q+Ksr/ViJfLr7YeWVkOB1FSzLXVGjJscmMGTSGN059A496iHLZ/7em4hKRVkAPfDfIO1RE/gA2ALeq6mIf84cBwwBatGgRxkiN8c0V4KFklDt891+3uH2Oq6rfY2XBJb5/XxFB8LGzxZgjj4R585yE5fLlzoP6a6+Fhg3De92hQ+HAA52dUxs3Op+br7gCkpLCe11jjDHGmHJmWZ894uJKP7fgKlJTpbjE5feLrDEVgYgkAZ8DN6rq7iKH5wEtVTXN24H5K6B90fdQ1dHAaICUlBQNb8TGFHd97+u5etzVPo/d1ve2sF33ip5XsGjLomKrSOsn1KdzfV8VK8rG+Qeezxvz3iA7f1/THbe4ObbNscRGxQaYaaq1Dh1g1Kjyv26fPs7LGGOMMaYKs8zP7t1w333QsaNTa2nMmOLdOv2ZNQsaNXK2HbndcOKJkJ8f2tz16+Gaa6B9e+jfH777rvS/gzGmWhKRaJzk6Puq+kXR46q6W1XTvH8fB0SLSL1yDtOYoK7qdRV9mhZPwNx9+N00SmoUtute2uNSjm1zLInRiUS7okmKSaJWXC2+GPwF4qtGeRl55OhH6FivI0kxSUS7okmOSaZZjWa8car/UgPGGGOMMcaY8KneK0izsuCQQ2DVKufvANdfD9Onw9tvB577559w6KH7fvZ4YMIEp4j+hg2B527cCN27w86dTvH7FStg7lx48EG4+ebS/z7GmGpDnOzNm8BSVfVZpFFEGgGbVFVFpDfOQ7Ft5RimMSH79YpfmbhiIqPmjCIxJpF7+t9Dh3odwnrNKFcUX537Fb+v/50Zq2fQMKkhp3c8ncSYxLBet0ZsDeZdOY8f//2RPzf9Sbs67Ti5/clEu6PDel1jjDHGGGOMb9U7QfrRR7B27b7kKEB6Onz8Mdx1l7O605+zzvI9vnGjk2Dt39//3CeecOqeFuwMmpEB99wDV14JieH9YmaMqRIOAy4CForIAu/Y/4AWAKo6CjgLuEpE8oBM4FxVtS30psI6vt3xHN8ujF25/ejdtDe9m/Yu12u6xMVxbY/juLbHlet1jTHGGGOMMcVV7wTpjz86CdGioqKc7fOBEqQrV/o/9vrrgROkP/0EubnFx6OiYOlSSEnxP9cYYwBVnQmBu7mo6kvAS+UTkTHGmLBbtw7WrHFKQ9WpU7K5K1fCf/9Bly5Qo0bJ5v79N2zbBt26laz2vqrz2TY11dk9FWs1do0xxhhTMVXvGqQtWzrd54sSgcaNA88N9OHwwAMDz23WzPd4Tk74u5EaY4wxYZSTl8P7C9/nk8Wf4Am1prdXbn4us9fPZvHmxdhiZ2MKSE93Osi3bw8nnQRNm8KttzoJyGB27IABA6BzZzjhBKd+/iOPhHbdjRuhVy846CA4/nho0CD0RlH//uskY3v1guOOc+Z+/HFoc40xFYqInCAiy0VkhYjc4eP4BSLyp/f1i4gcFIk4jTFmf1TvBOnQoc6qzYJcLqhdG448MvDcu+7yPS4Ct9wSeO6IEcUTrDExcPjhTg1TY4wxphJ6ftbzxD0cx4VfXMjgzwYT81AM7/3xXkhzv13+LQ2fasjR7xzNIW8cQoeXOrB86/IwR2xMJXHVVfDDD05ZqF27nD9ffRVGjw4+97zz4NdfnTm7d0NmppMg/fLL4HNPPhkWLHDm7N7tJGpvucUpJxWIxwNHHQXLlztlpHbvdl6XXQYLF4b0KxtjKgYRcQMvAycCnYHzRKRzkdNWAkeoajfgQSCEm5MxxlQs1TtB2qoVfPON8yQ9MRHi450n5FOnOl3pAxkxAgYNKjzmdjvb9oPNHTAAXnzR2d6UnOxsNzrqKPjkk/34ZYwxxpjIWbx5MTdOvBFl34q2fM3n4q8uZnPa5oBzV2xfwbmfncuOrB2k5qSSnpvOiu0rOHLskeR58gLONabKy8x0PiMWrJkPTuLx6acDz920yflcm5NTeDw9HZ58MvDcZcucBGdekX8HMzLguecCz/35Z9i+3UmUFpSd7SR2jTGVSW9ghar+q6o5wEdAoS/CqvqLqu7w/jgL8LNl0hhjKq7qnSAFOPpoWL8eZs92aiTNmwetW4c296uvnJpKL7wA33/vfIA86qjQ5l52GWzZAr/8AqtXw/jxUKtWaX8LY4wxJqLunny3z3FFuX/a/QHnvj73dXI9hWtzK0paTho//ftTWYVoTOWUlub/2LZtgedu3w7R0b6PbQ784ILNm/3P3bAh8NwtW5xdVUXl5wefa4ypaJoCawv8vM475s/lwHh/B0VkmIjMEZE5W7ZsKaMQjTFm/1XvJk17uFzQqVPp5iYlwXXXlW5uTAx07Vq6ucYYY0wFsiHVf9Ij0DGA9anriyVIwUmSbk4PksQxpqqrV8+pUb9mTeFxl8vZlRRIu3bFy0mBk/g84YTAc3v0KL7yFCAuztl6H8ihhzqrRYtKTHRqqBpjKhNfTUF9FkAWkSNxEqT9/L2Zqo7GuwU/JSXFCo4bYyoMW0G6P3bvduqYNm7sdBN9L7Q6a8YYY0xVc2qHU/0eO7vL2QHnntjuRBKjE4uN53nyOLzl4fsdmzGVmojTGKlg/XoRp0zTY48Fnhsd7ZR1SkjYt6IzJsbZtfS//wWem5wMDz9c+LoxMU6zpWuvDTy3cWO46SYnIbpHfLzTIPWiiwLP3UPV2eb/55/OylNjTKSsAwo2ymgGFHvyKSLdgDeAQaoaZHm7McZUPJYgLa3du6FJE3jjDfjvP6dG00UXwYUXRjoyY4wxptzd3u92asXWKjbeLLkZFxx4QcC5Z3c5m/Z12xMfFb93LDE6kSt6XEGrWq3KOFJjKqHmzZ3an3uoOj+3ahV87oUXwqRJTu38nj3hxhudRklNmgSfe8EFTukpl8t5gVOHv3bt4HMfeQTefx+OOQZSUmDkSPjtNydRGszSpc7ig4MPhsMOg6ZNYfLk4POMMeEwG2gvIq1FJAY4F/im4Aki0gL4ArhIVf+KQIzGGLPfbIt9aV19tVPgvqj334cnngjtQ6cxxhhTRUS5olh781qGfjOUb//6Fpe4GNxlMC+f9HLQuTHuGH6+7Gdenf0qHy3+iKToJK7udTVndT6rHCI3phLo1q34WG6ukzgMVksUnCTjYYeV/LoDB8Jff+1rtpST4yRIu3cP/n4iTlK2aFPTYHJynNIBW7Y4iWBw6rCeeqqzIKFpoNKHxpiypqp5InItMBFwA2NUdbGIDPceHwXcC9QFXhFntXqeqqZEKmZjjCkNS5CW1ni/dafhrbfgrrvKLxZjjDGmAkiKSeLDsz4s1dyE6ARu6XsLt/S9pYyjMqaSW758X6KwqHA2OFm+HBYvdhKxBWVmwjPPlC7hGopx45xrFP2d8/Lg7bftM7YxEaCq44BxRcZGFfj7FcAV5R2XMcaUJdtiX1oF6zEVVb9++cVhjDHGGGOqrjlzInPdTZt8d7FXhfXrw3tdXzVHs7Nh3brwXdcYY4wx1ZolSEvrFj8rXNxuuOyy8o3FGGOMMcZUTeecE5nrdu/uu4t9bCwcf3z4rtuvn+8Vs0lJcPTR4buuMcYYY6o1S5CW1o03Fv9w6HLBF19AlFUuMMaYsJgwwfnSnpgIXbvCN98EnWLKz9aMrVz29WXUeqwWdR+vy3XjryM1OzXSYQX02ZLPqPtEXWSkEPVAFKd/dDp5nrxIh2WqsjVr4PPPQ18NGR3tNCzy5ZprQr/u5s2waJGzEjMUNWrAvfcW72Jfty7ccEPo1y2pLl3gjDOc+/we8fHO/wannRa+6xpjjDGmWrME6f6YMAGWLXOK1T/7rFMv6dRTIx2VMcZUTd9/D2eeCX/84XRvXrwYzjsPPvkk0pEZIDsvm0NeP4T3/nyPXdm72J61ndFzR3PE20fgUU+kw/Pph39+4OxPz2Z75nYA8jWfr5Z/Re/Xe0c4MlMl5eTAgQdCy5Zw1llOZ/qDD3ZqawbzoZ/avk8/HXxuaqrTKKllS+jb1ykF9XLw5mmA0ywpK2vfzzk50LYt1KkT2vzSeucdeOkl6NPHeSj2wAMwfbotQjDGGGNM2FiCdH916ACPP+6sKI2JiXQ0xhhTdd12m5MYLSgjw3lIZSLui6VfsDljM7mefQ1dcvJz+Hv730xeOTmCkfl39birfY7P/28+q3euLudoTJV37LHOCs6C5s0L7eF6jx6+x2vVCj73ootg4kQn0Zma6rxGjHCaIQVz2GH7OtjvMWMG3Hln8Ln7w+WCIUPg119h/ny49VZnFakxxhhjTJhYgnR/5Oc7HxDbtHE+uE6fHvpcVZgyxXki/tprsHNn2MI0xpgq4e+/fY+vXl38C7wpd/P/m09aTlqx8Zz8HP7c9GcEIgpu7a61fo9NXTW1/AIx1YO/z4kTJwaeN3u2/2MFV3f6snWrs+Op6Lb6jAznAX8gb7/t/976/POB5xpjjDHGVDK2T6W0cnKcp/aZmfvGjjgCLr0UxowJPDc3F04+2Xkqnp7uPBG/7TaYNMnZSmSMMaa4pk2dZGhRDRo4q41MRHWo24HE6ETSc9MLjce6Y2lXp12EogqsXkI91qf67sad0iSlnKMxVVqgbfTBHvBMmVL6627Z4tQw9VV3NFgN1Pnz/R8LtY6pMcYYY0wlYd8oS+vMMwsnR/d46y3Yvj3w3Ndfh59/hrQ0ZyVpRoaz3emss3x37TTGGAP331+4WQg4P997b0TCMYUN7jqYhOgEXLLvo0WURFE3vi4ntT8pgpH59+SxT/ocb1mzJV0adCnnaEyVFhXldH/3pWAzIl/2pyFSmzYg4jueo44KPHfoUP/HmjYtfUzGGGOMMRWQJUhLa9Ik/8f+97/Ac996q3gdPYBdu4rXpjLGGOMYMsRpSFKvnvPlvk4dePhhuNp3HUlTvpJikvj18l85vMXhuMVNlCuK49oex8+X/0yUq2JuWDnvwPN4/JjHiXZF7x3r1qAbfw6vmCUBTCX3yCO+x4M1WoqNdTrK+3LsscHnPvlk4YdLbjckJ8Pddwee27UrtG7t+5g1xzPGGGNMFVMxv7FUdsG2evo7rur7Kb8xxhjH8OFw5ZVOeZKEBNtaX8G0rdOWqUOmkpWXhUtcxLgrfvPCEYeNYMRhI9ictplacbWIiar4MZtK6vrr4cUXYdWqfWMHHBB4peYe06b5btQU6IH9HscdV3iLf34+tGrldLUP5t9/4fTT4ZtvnFIA9erBp59aSShjjDHGVDn2zbK0TjzR/7GHHgo899JLi28TBWc1VBfb0meMMQGJQFKSJUcrsLiouEqRHC2oQVIDS46a8Dr++MLJUYC//nISkMH462IfFxd8bufOTu38gubPd1blh+LLL52kqqpT03TAgNDmGWOMMcZUIvbtsrQ++cR3zaihQ51EZyCXX+40dEpMdLY5JSY6W6c+/9xWkBpjjDHGVEX+mi19913geb//7v9YsGZJv//uv9P9e+8FnmuMMcYYU43YFvvSiolxmiw9+CC8+67T0f7VV+Hgg4PPjY6G7793GjXNmAENGzoNmvzVlzLGGGMqAVVl/IrxfLDwA9wuN0MOGsKAVgMQe/hXzPbM7bwx7w1mrZtF1wZdGZ4ynCbJTSIdlgmXvDz/jTiDdbEPlkANZO5c/8fy80v/vsYYY4wxVYxoFe2anpKSonPmzIl0GMaYKkZE5qpqSqTjCBe7d5rSUlUu+eoSvlj6Bem56QAkRidyZcqVPH1ckCY01czqnavp9Xov0nLSyMzLJNYdS4w7hmlDptGjsZ+t1JWc3TtxGiYV3eoOTtml9HT/83buhNq1/R8P9Fl+yxZo0MD3seRk2L3b/1xjTMTZvdMYY0qutPdO22IfSWvWwMcfw9SpwVcPGGOMMRXYL2t/KZQcBUjPTefV2a+yfOvyCEZW8dw66Va2ZW4jMy8TgOz8bFJzUhn6bQjNekzlNXKk7/HHHw88r1YtZ+eSLz17Bp5bvz507+772KhRgecWtGsXrF1rn1eNMcYYU2VVmASpiLhFZL6IFNtHJCIDRGSXiCzwvu6NRIxlRtXpZNqhg1Oz9NRToU0bp1OoMcYYUwmN+3scGbkZxcY96mHiPxMjEFHFNfGfiXi0eKJpwX8LyMzNjEBEplzccQe8/rrTCd7tdlZ2vvsuXHtt8Lnr1xcfi4oKvIV+j59+Kl43/8gj4fzzg8/dtQvOOMOJtUMHaNrU6WhvjDHGGFPFVJgEKXADsDTA8Rmq2t37eqC8ggqLTz6BMWOcovmpqc5r7VoYNCjSkRljjDGlkhybTLQ7uth4lCuKpJikCERUccVHxfscd7vcRLmsPHyVdsUVzrb3vDzYtAkuvDC0ec2bFx/Lywu+ghSgR4/iW/inTIEnnww+94wzYNw4pzRAZib89x+cdx7Mmxda3MYYY4wxlUSFSJCKSDPgZOCNSMdSLl5+ufgHVY8H/vkH/vorMjEZY4wx++G8rufhFnexcUU5vePpEYio4hp68NBiSdIYdwxndDzDZ5LZVHPr1vnvRD9/fuC5//zjlHTy5dFHA8/991/49VfIzi48npUFT1tdYWOMMcZULRUiQQo8B4wAAhU2OlRE/hCR8SLSxdcJIjJMROaIyJwtW7aEI86ykZrqezwqKnCRfmOMMaaCalmrJWMGjSEhKoEaMTWoEVODpJgkvjjnC2rHB2gwUw3d0/8ejmp9FPFR8STHJJMYnUiPRj14deCrkQ7NVEShbKP3Z/Fi/8f8fR7dY+1a37VPPR5YsaL0MRljjDHGVEAR38clIgOBzao6V0QG+DltHtBSVdNE5CTgK6B90ZNUdTQwGpyOeGEJuCycfTYsW1Z8NUBUFBx4YGRiMsYYY/bTuV3P5eT2J/PTyp9wi5tj2hxDfLTv7eTVWWxULN+d/x1Ltixh4aaFtKvTjp6NeyIikQ7NVETHH+//WLB/Zvr393+sRYvAcw88sPjqUXCSpgMGBJ5rjDHGGFPJVIQVpIcBp4rIKuAj4CgRea/gCaq6W1XTvH8fB0SLSL1yj7SsXHcdtG69r2B+VBTEx8Pbbzt/N8YYYyqp5NhkTut4Gqd0OMWSo0F0rt+ZwV0Hc3CTgy05avyLi4NOnXwfu+aawHNr1XKagfoyenTguXXqOJ9ZCzZ4crshORluvDHwXGOMMcaYSibiCVJVvVNVm6lqK+BcYLKqFqpYLyKNxPvNQUR648S9rdyDLSvJyc52qeefh7POcrqXzp/v/wOsMcYYY4ypvubNg6Qizc46dIAXXww+9+uv4dZbnYfxLhc0awYTJsDRRwef+/jjTu38rl2hSROnqdS8edC4cel+D2OMMcaYCqrCLlcUkeEAqjoKOAu4SkTygEzgXFWtuFvoQxEfD5df7ryMMcYYY4zx5/DDIS2t8Njy5TBiBDzxRPD5Tz4ZWtf6okTgkkuclzHGGGNMFRbxFaQFqepUVR3o/fsob3IUVX1JVbuo6kGq2kdVf4lspAXs2uV8MP3880hHYowxxhhjqpq0NJgzx/exUFaQGmOMMcaYoCrsCtJK4eSTYdy4fT+7XM7PgYrpG2OMMcYYE6rVq/0f89VEyRhjjDHGlFiFWkFaqTz6aOHkKIDHAyeeCPn5kYnJGGOMMcZULR06+O9WX6tWuYZijDHGGFNVWYK0tB591Pe4ami1oIwxxhhjTPWTnw87doT+QD0qCoYM8X3s2WfLLCxjjDHGmOrMEqSllZnp/9iSJeUXhzHGGGOMqfhUnUZJdetCo0ZQvz688IIzHsyYMXDnnU6TTxGoUwfGjrXmScYYY4wxZcQSpKXVrp3/Y9dcU35xGGOMMcaYiu/FF2HkSKfBZ06Os4r0zjvhzTdDm//II5CR4ZR02rYNLr44vPEaY4wxxlQjliAtrU8/9T3etCn06VO+sRhjjDHGmIrtoYcgPb3wWEYGPPBAZOIxxhhjjDF7WYK0tLp2hTlzoFkz52e3G049NXCnUWOMMcYYU/14PLBli+9jGzeWbyzGGGOMMaaYqEgHUKkdfDCsXRvpKIwxxhhjTEXmckHr1rByZfFjHTqUfzzGGGOMMaYQW0G6R1YW5OZGOgpjjDGm0svOyyYnPyfSYRhTsTz1FCQkFB6Lj3caN4XK43G26YfS2MkYY4wxxoTMEqSLFjk1Q5OSIDERzjkHtm+PdFTGGBOQiDQXkSkislREFovIDT7OERF5QURWiMifItIzErGa6uPfHf9y5NgjSXwkkcRHEhn4wUA2ptr2YVP5iMgYEdksIovK7E3POMOpYd+zJ9SoAb16wTffwIknBp/r8Tg1TGvXhlq1oHlz+OijMgvNGGOMMaa6q95b7DdvhsMOg927nZ/z8+Hrr2HFCpg7F0QiG58xxviXB9yiqvNEJBmYKyI/qOqSAuecCLT3vg4BXvX+aUyZS8tJo88bfdiWuQ2PekBh4oqJ9B3Tl7+v+5soV/X+yGEqnbeBl4B3yvRdTzrJeZXU/ffD0087TZ0A1q+Hyy93Eq2leT9jjDHGGFNI9V5B+sYbkFNkC2BODvz9N/z6a2RiMsaYEKjqRlWd5/17KrAUaFrktEHAO+qYBdQSkcblHKqpJj5e9DEZuRlOctQrT/PYlrGNcX+Pi2BkxpScqk4HKsaWotxcePbZfcnRPTIy4J57IhOTMcYYY0wVU70TpIsXO7VHfVmxonxjMcaYUhKRVkAP4Lcih5oCBTvJraN4EhURGSYic0RkzhZ/XZaNCWL5tuWk56YXG8/Oz2bFdvtvqql6yu3euWMH5OX5Puar6ZMxxhhjjCmx6p0gPeSQ4sXywanz1K1b+cdjjDElJCJJwOfAjaq6u+hhH1OKdfZQ1dGqmqKqKfXr1w9HmKYa6NGoB0kxScXGY9wxdGto/001VU+53Tvr1oW4ON/HunYN33WNMcYYY6qR6p0gveQSpzmT271vLC4ODj0UunePWFjGGBMKEYnGSY6+r6pf+DhlHdC8wM/NgA3lEZupfs7odAb1E+oT7YreOxbrjqVdnXYc1fqoCEZmTCXndsODDxZ/qB8fD48+GpmYjDHGGGOqmOqdIK1Z02nGdOaZTqK0Xj24/nr47rtIR2aMMQGJiABvAktV9Rk/p30DXOztZt8H2KWq1lLchEVsVCy/XfEbF3a7kBqxNagdV5uhPYcybcg0XFK9P24Ys9+uvRZeew3atXMSpb17w/jxTrNRY4wxxhiz36ylbLNm8PHHkY7CGGNK6jDgImChiCzwjv0PaAGgqqOAccBJwAogA7i0/MM01Un9xPqMGTSGMYPGRDoUY/aLiHwIDADqicg64D5VfTOiQV14ofMyxhhjjDFlzhKkxhhTCanqTHzXGC14jgLXlE9ExhhTdajqeZGOwRhjjDHGlB/b82aMMcYYY4wxxhhjjKm2LEFqjDHGGGOMMcYYY4yptixBaowxxhhjjDHGGGOMqbYsQWqMMcYYY4wxxhifROQEEVkuIitE5A4fx0VEXvAe/1NEekYiTmOM2R+WIP1/e/cebF1d13H8/RkueQcUMRPxkVIDTAsR8QaPQgmYgUmFUqYxQ+a1sSktSy3/SMPp4iWJHCR0xJnCCxmoiXIRhBQFAS+FiICIiCnERfGBb3+sdWSz2eectc++rfOc92tmzd573fZnrf0737Nmrb1+W5IkSZIk3UOSbYB3AocAewLPT7Ln0GyHAI9qh2OAd801pCRNgSdIJUmSJEnSKPsCl1fVFVV1O/AB4LCheQ4DTqrG+cCOSR4676CSNAlPkEqSJEmSpFEeBlw98Pqadty480hSr2276ACzcuGFF96Q5JuLztHaGbhh0SFG6GMuM3XTx0zQz1zTzvSIKa6rd6ydq+pjJuhnLjN118dc1s4xWDtXZabu+pjLTN1tjbUzI8bVGuZpZkyOobkNH+BHSS6dIFuf9bWNTovbt75t7dv3mLUstNWeIK2qBy86w5Ikn6+qfRadY1gfc5mpmz5mgn7m6mOmPrN2rqyPmaCfuczUXR9z9TFTn1k7V2am7vqYy0zd9TXXhK4BHj7welfg2jXMA0BVHQ8cD1vt/gK27m0Dt2+92wjbt5blvMVekiRJkiSN8jngUUkemWR74Ejg1KF5TgVe2P6a/X7AjVX17XkHlaRJbLXfIJUkSZIkSWtXVVuSvBz4OLANcEJVXZbkJe3044DTgEOBy4FbgRcvKq8krZUnSOfj+EUHWEYfc5mpmz5mgn7m6mMmddPHz66PmaCfuczUXR9z9TGTuunjZ2em7vqYy0zd9TXXRKrqNJqToIPjjht4XsDL1rDqrXJ/tbbmbQO3b71z+0ZIU8skSZIkSZIkaeOxD1JJkiRJkiRJG5YnSCeU5OAkX0tyeZLXjph+VJIvtcN5SR4/MO3KJJckuWitv7K1xkybk9zYvu9FSV7fddkZZvrjgTyXJrkjyQPbabPaTyckuT7JpctMT5K3tZm/lGTvrtszw0xzb08dcy2iTa2Wae5tSt30sW52zGXtxNo5xUxzb08dc1k7e8raOdVM1s5umayd3XNZO5fR4e9x2ba/HkxSm9eDrn9XSZ7Ytvsj5plvUl22r607FyW5LMlZ8864Vh3a5g5J/j3Jxe22rau+gyf5v7qsqnJY40DTSfXXgd2B7YGLgT2H5nkKsFP7/BDggoFpVwI7LyDTZuCja1l2VpmG5n8O8KlZ7qd2vfsDewOXLjP9UOB0IMB+S5/drPZTx0xzbU9j5Jprm+qSaRFtyqHT59a7ujlGLmtnWTunmGnudbNLrkW0KYdOn5u1c4qZhua3dlo7J861iDa1HoaONWJk218Pw6S1ue9D17+rdr5P0fRTe8Sic0/589sR+DKwW/t6l0XnnuK2/Rnwlvb5g4H/BbZfdPYxtnFN/1dXGvwG6WT2BS6vqiuq6nbgA8BhgzNU1XlV9f325fnArovONKNlp7ne5wMnT+F9V1RVZ9MUgeUcBpxUjfOBHZM8lNntp1UzLaA9dcq1goXtqyFzaVPqpI91s1OuGS07zfVaO5efPvc21ce6uYZc1s7+sHbOLpO1c/np1s615bJ23qXL57Jc218P+lqbp6Xr39UrgFOA6+cZbgq6bN8LgA9W1VUAVbVetrHLthVw/yQB7kdT47bMN+baTfB/dVmeIJ3Mw4CrB15f045bztE0Z7CXFPCJJBcmOWbOmZ7cfpX69CR7jbnsrDKR5D7AwTQFdsks9lMXy+We1X4a1zza0zjm2aY661mbUj/r5ji5rJ2rs3Z218u6Cb1rU7J2ziJT39q5tbM7a+f60eVz6cVnt0aT1ua+W3X7kjwMeC5w3BxzTUuXz+/RwE5Jzmz/hl84t3ST6bJt7wD2AK4FLgFeVVV3zifeXIxdW7adaZytX0aMq5EzJs+gKYhPGxj91Kq6NskuwH8m+Wp7FnzWmb4APKKqbk5yKPBh4FEdl51VpiXPAc6tqsErAbPYT10sl3tW+6mzObanrubdpsbRpzalftbNrrmsnd1YO7vpc92EfrUpWTunnWlJn9q5tbMba+f60uVz6ctntxaT1ua+67J9fw+8pqruaL6IuK502b5tgScABwL3Bj6b5Pyq+u9Zh5tQl217FnAR8EzgZ2nq0zlVddOMs83L2LXFb5BO5hrg4QOvd6U5+343SR4HvBs4rKq+tzS+qq5tH68HPkTzNeiZZ6qqm6rq5vb5acB2SXbuuj2zyDTgSIZuSZnRfupiudyz2k+dzLk9dbKANjWOPrUp9bNudspl7ezM2tlBz+sm9KtNydo51UwD+tTOrZ0dWDvXnS6fS18+u7WYqDavA122bx/gA0muBI4A/jHJ4XNJN7mu7fNjVXVLVd0AnA08fk75JtFl215M031AVdXlwDeAn59TvnkYv7ZUDzpXXa8DzdWEK4BHclfHt3sNzbMbcDnwlKHx9wXuP/D8PODgOWX6aSDt832Bq2jOrq+67KwytfPtQNOHxH1nvZ8G1r+J5Tv1fTZ379T3v8bZnhllmmt7GiPXXNtUl0yLalMOq35mvaubY+Sydt61fmvn5JkWUjdXy7WoNuWw6mdm7ZxipnY+a+fqmaydHXMtqk31fehYI0a2/fUwdNy+kX9H62EY9+8KOJH19SNNXT6/PYAz2nnvA1wKPHbR2ae0be8C3tg+fwjwLdbZD8qt5f/qSoO32E+gqrYkeTnwcZpfCTuhqi5L8pJ2+nHA64EH0VxJAdhSVfvQNMAPteO2Bd5fVR+bU6YjgD9IsgW4DTiymhY0ctk5ZYKm75JPVNUtA4vPZD8BJDmZ5pcwd05yDfAGYLuBTKfR/PLZ5cCtNFdYlt2eOWWaa3saI9dc21THTDDnNqXV9bFujpHL2om1c4qZ5l43O+YCa2fvWDunngmsndbO6eYCa+c9dPx7HNn214MJa3PvjVFP16Uu21dVX0nyMeBLwJ3Au6vq0sWl7qbjZ/cm4MQkl9CcRHxNNd+SXRfW+n91xXW2Z1YlSZIkSZIkacOxD1JJkiRJkiRJG5YnSCVJkiRJkiRtWJ4glSRJkiRJkrRheYJUkiRJkiRJ0oblCVJJkiRJkiRJG5YnSLXVSrI5SSU5c0HvX0lqDctd2S67aczlFrq9krYOi64l1k5J682i64h1U5KkyXmCVAuX5EXtQdaJi87SZ0nObPfT5kVnkbR41s5urJ2Sllg3u7FuSpI2om0XHUDaiu2x6ACStA5ZOyVpPNZNSZIm5AlSaUaq6quLziBJ6421U5LGY92UJGly3mKvkQb7MkpyTJIvJrk1yfeSfDDJY1dY9r5J/iTJ55LclOS2JJcleWOS+w3NeyXwnvbl7y697/DtT0n2TPJXSc5Lcm2S25N8N8lpSQ6e0jbvnOTOJNeNmPbKgVx7DE3bsx1/8dD4ZfuDSvKIJCcl+U67f77c7rN7XLRY6ucJOKAd9emh/bR5xDLbJXldkq8m+WGS65O8L8lu3feIpHFZO+8xzdopaUXWzXtMs25KkrQAfoNUK0ryd8ArgXOAjwB7A88FnpXkWVX1maH5dwU+DuwJfBf4LPBD4InAG4DnJtlcVd9vF/k3YD/gqcDXgcH1DT5/NXA08BXgYuAmYHfgEOCQJH9UVX87ybZW1Q3tAecvJvmFqrpkYPKBA88PanMMTzujy/sk2RM4C9gZuJpmv+4EvAl40ohFrgP+BTgYeAjN/r1uaPqg7YDT23Wd1WZ9MnAUsH+Sx1XVD7pklbQ21s6fsHZK6sS6+RPWTUmSFqGqHBzuMQDVDrcA+w+MD/DX7bSrgHsNTTuvnfZ24D4D0+4NvLedduLQe71o1PiheQ4ANo0Y/yTgRuB2YNehaZvb9Z45xna/tV3mDwfGbQP8ALgM+DHwkaFlPtwu8+xR+3DEe1zYTjsJ2H5g/F7A9QP7ftPQcme24zcvk33zwLKfA3YZmLbDwPu+btHty8Fhax2sndZOBweH8QbrpnXTwcHBwcGhD4O32Gs176qqs5deVFUBfw5cATwceN7AvAfTXDU+H3hVVd06sNxtwEtoDsaOSrLTOCGq6qyqunLE+AuAd9BcwT5snHUuY+mK/EED4/ahOdj7CPB5YHOSbQDaxwOALTRXzleU5Ok034i4EXhFVd2+NK2qLqO5oj+pAn6vqq4fWPeNwFvalweOXErSNFk7rZ2SxmPdtG5KkrQwniDVat43PKKq7gBObl9uHph0aPt4SlXdOWK5W2gO9raluf1pLEnun+TIJG9OcnySE9s+o5YyPHrcdY5wNs0V+/0H+mZaOrj7ZDs8ANi3HfcEYEfggqq6ucP6D2gfP9oeQA5771pCD7mq7n6r1pKlDvx/ZgrvIWll1k5rp6TxWDetm5IkLYx9kGo131hm/JXt464D43ZvH49Ncuwq633wOCGSHAacADxwhdkeMM46R6mqW5KcDzyd5laqc2kOVn/YPr+D5tsMB9H0dTVWX1Dctb9G7teq+kGSG2m+PbBWVy0z/qb28V4TrFtSN9ZOa6ek8Vg3rZuSJC2MJ0g1qRp4vk37eBZ3Hcwu55td36DthP9kmj6l3gy8v13/LVV1Z5JjgH+i6Y9qGs6gOVg9MMmFwFOAz1TVj5J8FriV5mD1Tdz9Sn9f3OObFJJ6x9rZsHZK6sq62bBuSpI0A54g1Wo20fyC56jxANcOjLu6ffzXqnrnFDP8Ks2B6ilV9acjpv/cFN8LmgPPN9IckJ5Lc/X7kwBVdXuSc4BnJHkQzS+h3kLTB1YX32ofN42amGQHJruSL6kfNmHttHZKGscmrJvWTUmSFsQ+SLWao4ZHtJ3E/1b78syBSae3j78x5nssdRq/3An7pVucrh6ekOSnuHun/dNwAXAzsB/wa+24wduZzgC2B15LcyB7TlX9uOO6lzrVf06SUbdn/fYKy662nyT1h7WzYe2U1JV1s2HdlCRpATxBqtW8NMnTll4kCfCXNFfQvwWcMjDvh4ELgQOSHJfkHn03Jdk9ycuGRi9d4d5jmQxLHb0/L8lDBta1PfB27uqHaiqqagtNx/nbAccA3we+MDDL0q1NLx963cU5wEU0V+z/Icl2SxOS7AH8xQrLrrafJPWHtdPaKWk81k3rpiRJC+NVQa3mn4GzkpwNfBvYG3gMcBtwVFXdtjRj2zfT4cBpwO8DL0hyMXANsDOwG82vfn4HGLwd6nzgOmDvJJ8HLqP5Vc9zq+o9wKnAF4FfAv4nyZk0Hdg/leag723AK6e83Z+k+YXUewGnDf1C6kXADe02QffO8qmqSvI7NFf1XwQ8s+1jakfgGcB/0OzjR4xY/EPtMscm+WXg+nb8sVX1ta4ZJM2FtdPaKWk81k3rpiRJC+M3SLWaVwOvoLnl6HBgF5qr9k+qqrOGZ66qa4B9aa50fxHYi+Z2pMcC/we8Ffj1oWV+BBxMc6D2SJpbfo4GDminb2mf/w3NAfOv0HRofzbwhPZ9pm3wAPRuV+urqoBPty9vYHR/WcuqqkuBfYD30fRzdThN/1B/CfzmCsudCryU5tsNB9Hso6OBh47z/pLmwtpp7ZQ0HuumdVOSpIVJ839XurskBVBV0/qVTkna6lk7JWk81k1JktQHfoNUkiRJkiRJ0oblCVJJkiRJkiRJG5YnSCVJkiRJkiRtWPZBKkmSJEmSJGnD8hukkiRJkiRJkjYsT5BKkiRJkiRJ2rA8QSpJkiRJkiRpw/IEqSRJkiRJkqQNyxOkkiRJkiRJkjYsT5BKkiRJkiRJ2rD+H3TbxogwWG2yAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt #导入matplotlib.pyplot模块并简写为plt\n", - "\n", - "feature_name = {0: 'sepal length', 1: 'sepal width', 2: 'petal length', 3: 'petal width'} #将不同的特征名称分别标记为0,1,2,3\n", - "axes = plt.figure(figsize=(23, 23)).subplots(4, 4) #画出一个大小为23*23的图,包含4*4=16个子图\n", - "\n", - "colormap = {0: 'r', 1: 'g'} #将标签为0的样本设为红色,标签为1的样本设为绿色\n", - "cvalue = [colormap[i] for i in y] #将100个样本对应的标签设置相应的颜色\n", - "\n", - "for i in range(4):\n", - " for j in range(4):\n", - " if i!= j:\n", - " ax = axes[i][j] #在[i][j]的子图上开始画图\n", - " ax.scatter(X[:, i], X[:, j], c=cvalue) #画出第[i]个特征和第[j]个特征组成的散点图\n", - " ax.set_xlabel(feature_name[i], fontsize=22) #设置X轴的名称为第[i]个特征名称,字体大小为22\n", - " ax.set_ylabel(feature_name[j], fontsize=22) #设置Y轴的名称为第[j]个特征名称,字体大小为22\n", - "plt.show() #渲染图像,即呈现图像" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述呈现的图像可以看到,红色的点表示标签为“0”的样本,绿色的点表示标签为“1”的样本,另外,我们发现,这两类样本的不同特征还是比较容易区分的。\n", - "\n", - "## 5. 数据预处理\n", - "\n", - "接下来,我们需要计算生成搭建Encoder时所要用到的参数,然后将数据集划分为训练集和测试集,执行如下命令。" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(100, 7)\n" - ] - } - ], - "source": [ - "alpha = X[:, :3] * X[:, 1:] #每一个样本中,利用相邻两个特征值计算出一个参数,即每一个样本会多出3个参数(因为有4个特征值),并储存在alpha中\n", - "X = np.append(X, alpha, axis=1) #在axis=1的维度上,将alpha的数据值添加到X的特征值中\n", - "\n", - "print(X.shape) #打印此时X的样本的数据维度" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印可以看到,此时的数据集`X`中仍有100个样本,但此时每个样本却有7个特征,前4个特征值就是原来的特征值,后3个特征值就是通过上述预处理计算得到的特征值,其具体计算公式如下:\n", - "$$\n", - "X_{i+4}^{j} = X_{i}^{j} * X_{i+1}^{j}, i=0,1,2,j=1,2,...,100.\n", - "$$\n", - "最后,我们将此时的数据集分为训练集和测试集,执行如下命令。" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(80, 7)\n", - "(20, 7)\n" - ] - } - ], - "source": [ - "from sklearn.model_selection import train_test_split #导入train_test_split函数,用于对数据集进行划分\n", - "\n", - "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, shuffle=True) #将数据集划分为训练集和测试集\n", - " \n", - "print(X_train.shape) #打印训练集中样本的数据类型\n", - "print(X_test.shape) #打印测试集中样本的数据类型" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印可以看到,此时的训练集有80个样本,测试集有20个样本,每个样本均有7个特征。\n", - "\n", - "说明:\n", - "\n", - "(1)append主要用于为原始数组添加一些值,一般格式如下:np.append(arr, values, axis=None),arr就是需要被添加值的数组,values就是添加到数组arr中的值,axis表示沿着那个方向;\n", - "\n", - "(2)shuffle=True表示将数据集打乱,每次都会以不同的顺序返回, shuffle就是为了避免数据投入的顺序对网络训练造成影响。增加随机性,提高网络的泛化性能,避免因为有规律的数据出现,导致权重更新时的梯度过于极端,避免最终模型过拟合或欠拟合。\n", - "\n", - "(3)train_test_split是交叉验证中常用的函数,主要用于是从样本中随机的按比例选取train data和test data,一般格式如下:\n", - "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size, random_state, shuffle=True),其中test_size表示测试样本的比例,random_state表示产生随机数的种子,shuffle=True表示将数据集打乱;\n", - "\n", - "## 6. 搭建Encoder\n", - "\n", - "根据图示的量子线路图,我们可以在MindQuantum中搭建Encoder,将经典数据编码到量子态上。\n", - "\n", - "![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/encoder_classification_of_iris_by_qnn.png)\n", - "\n", - "在这里,我们采用的编码方式是IQP编码(Instantaneous Quantum Polynomial encoding),一般来说Encoder的编码方式不固定,可根据问题需要选择不同的编码方式,有时也会根据最后的性能对Encoder进行调整。\n", - "\n", - "Encoder中的参数$\\alpha_0,\\alpha_1,...,\\alpha_6$​​的值,就是用上述数据预处理中得到的7个特征值代入。​" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================Circuit Summary==================================\n", - "|Total number of gates : 17. |\n", - "|Parameter gates : 7. |\n", - "|with 7 parameters are : alpha0, alpha1, alpha2, alpha3, alpha4, alpha5, alpha6. |\n", - "|Number qubit of circuit: 4 |\n", - "===================================================================================\n" - ] - }, - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────RZ(alpha0)────●──────────────────●──────────────────────────────────────────────────\n",
-       "                         │                  │                                                  \n",
-       "q1: ──H────RZ(alpha1)────X────RZ(alpha4)────X────●──────────────────●──────────────────────────\n",
-       "                                                 │                  │                          \n",
-       "q2: ──H────RZ(alpha2)────────────────────────────X────RZ(alpha5)────X────●──────────────────●──\n",
-       "                                                                         │                  │  \n",
-       "q3: ──H────RZ(alpha3)────────────────────────────────────────────────────X────RZ(alpha6)────X──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────RZ(alpha0)────●──────────────────●──────────────────────────────────────────────────\n", - " │ │ \n", - "q1: ──H────RZ(alpha1)────X────RZ(alpha4)────X────●──────────────────●──────────────────────────\n", - " │ │ \n", - "q2: ──H────RZ(alpha2)────────────────────────────X────RZ(alpha5)────X────●──────────────────●──\n", - " │ │ \n", - "q3: ──H────RZ(alpha3)────────────────────────────────────────────────────X────RZ(alpha6)────X──" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import mindquantum as mq #导入mindquantum库并简写为mq\n", - "from mindquantum.core import Circuit #导入Circuit模块,用于搭建量子线路\n", - "from mindquantum.core import UN #导入UN模块\n", - "from mindquantum.core import H, X, RZ #导入量子门H, X, RZ\n", - "\n", - "encoder = Circuit() #初始化量子线路\n", - "\n", - "encoder += UN(H, 4) #H门作用在每1位量子比特 \n", - "for i in range(4): #i = 0, 1, 2, 3 \n", - " encoder += RZ(f'alpha{i}').on(i) #RZ(alpha_i)门作用在第i位量子比特\n", - "for j in range(3): #j = 0, 1, 2\n", - " encoder += X.on(j+1, j) #X门作用在第j+1位量子比特,受第j位量子比特控制\n", - " encoder += RZ(f'alpha{j+4}').on(j+1) #RZ(alpha_{j+4})门作用在第0位量子比特 \n", - " encoder += X.on(j+1, j) #X门作用在第j+1位量子比特,受第j位量子比特控制\n", - " \n", - "encoder = encoder.no_grad() #Encoder作为整个量子神经网络的第一层,不用对编码线路中的梯度求导数,因此加入no_grad()\n", - "encoder.summary() #总结Encoder\n", - "encoder" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从对Encoder的Summary中可以看到,该量子线路由17个量子门组成,其中有7个含参量子门且参数为$\\alpha_0,\\alpha_1,...,\\alpha_6$,该量子线路调控的量子比特数为4。\n", - "\n", - "说明:\n", - "\n", - "(1)UN模块用于将量子门映射到不同的目标量子比特和控制量子比特,一般格式如下:mindquantum.circuit.UN(gate, maps_obj, maps_ctrl=None),括号中的gate就是我们需要执行的量子门,maps_obj就是需要执行该量子门的目标量子比特,maps_ctrl就是控制量子比特,若为None即无控制量子位。若每个量子比特位执行同一个非参数量子门,则可以直接写出UN(gate, N),N表示量子比特个数;\n", - "\n", - "## 7. 搭建Ansatz\n", - "\n", - "根据图示的量子线路图,我们可以在MindQuantum中搭建Ansatz。\n", - "\n", - "![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/ansatz_classification_of_iris_by_qnn.png)\n", - "\n", - "与Encoder一样,Ansatz的编码方式也不固定,我们可以尝试不同的编码方式来测试最后的结果。\n", - "\n", - "在这里,我们采用的是HardwareEfficientAnsatz,即上述量子线路图所示的编码方式。" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "====================================================Circuit Summary====================================================\n", - "|Total number of gates : 25. |\n", - "|Parameter gates : 16. |\n", - "|with 16 parameters are : d0_n0_0, d0_n1_0, d0_n2_0, d0_n3_0, d1_n0_0, d1_n1_0, d1_n2_0, d1_n3_0, d2_n0_0, d2_n1_0... |\n", - "|Number qubit of circuit: 4 |\n", - "=======================================================================================================================\n" - ] - }, - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──RY(d0_n0_0)────●────RY(d1_n0_0)────────────────────────●─────────RY(d2_n0_0)────────────────────────●─────────RY(d3_n0_0)────────────────────────────────\n",
-       "                     │                                       │                                            │                                                    \n",
-       "q1: ──RY(d0_n1_0)────X─────────●─────────RY(d1_n1_0)─────────X──────────────●─────────RY(d2_n1_0)─────────X──────────────●─────────RY(d3_n1_0)─────────────────\n",
-       "                               │                                            │                                            │                                     \n",
-       "q2: ──RY(d0_n2_0)──────────────X──────────────●─────────RY(d1_n2_0)─────────X──────────────●─────────RY(d2_n2_0)─────────X──────────────●─────────RY(d3_n2_0)──\n",
-       "                                              │                                            │                                            │                      \n",
-       "q3: ──RY(d0_n3_0)─────────────────────────────X─────────RY(d1_n3_0)────────────────────────X─────────RY(d2_n3_0)────────────────────────X─────────RY(d3_n3_0)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──RY(d0_n0_0)────●────RY(d1_n0_0)────────────────────────●─────────RY(d2_n0_0)────────────────────────●─────────RY(d3_n0_0)────────────────────────────────\n", - " │ │ │ \n", - "q1: ──RY(d0_n1_0)────X─────────●─────────RY(d1_n1_0)─────────X──────────────●─────────RY(d2_n1_0)─────────X──────────────●─────────RY(d3_n1_0)─────────────────\n", - " │ │ │ \n", - "q2: ──RY(d0_n2_0)──────────────X──────────────●─────────RY(d1_n2_0)─────────X──────────────●─────────RY(d2_n2_0)─────────X──────────────●─────────RY(d3_n2_0)──\n", - " │ │ │ \n", - "q3: ──RY(d0_n3_0)─────────────────────────────X─────────RY(d1_n3_0)────────────────────────X─────────RY(d2_n3_0)────────────────────────X─────────RY(d3_n3_0)──" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from mindquantum.algorithm import HardwareEfficientAnsatz # 导入HardwareEfficientAnsatz\n", - "from mindquantum.core import RY # 导入量子门RY\n", - "\n", - "ansatz = HardwareEfficientAnsatz(4, single_rot_gate_seq=[RY], entangle_gate=X, depth=3).circuit # 通过HardwareEfficientAnsatz搭建Ansatz\n", - "ansatz.summary() # 总结Ansatz\n", - "ansatz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从对Ansatz的Summary中可以看到,该量子线路由25个量子门组成,其中有16个含参量子门且参数为d2_n3_0, d1_n1_0, d0_n2_0, d1_n0_0, d3_n2_0, d2_n2_0, d0_n1_0, d3_n1_0, d2_n0_0, d3_n0_0...,该量子线路调控的量子比特数为4。\n", - "\n", - "说明:\n", - "\n", - "(1)HardwareEfficientAnsatz是一种容易在量子芯片上实现的Ansatz,其量子线路图由红色虚线框内的量子门组成,一般格式如下:mindquantum.ansatz.HardwareEfficientAnsatz(n_qubits, single_rot_gate_seq, entangle_gate=X, entangle_mapping=\"linear\", depth=1),括号中的n_qubits表示ansatz需要作用的量子比特总数,single_rot_gate_seq表示一开始每一位量子比特执行的参数门,同时后面需要执行的参数门也固定了,只是参数不同,entangle_gate=X表示执行的纠缠门为X,entangle_mapping=\"linear\"表示纠缠门将作用于每对相邻量子比特,depth表示黑色虚线框内的量子门需要重复的次数;\n", - "\n", - "那么完整的量子线路就是Encoder加上Ansatz。" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================================Circuit Summary================================================\n", - "|Total number of gates : 42. |\n", - "|Parameter gates : 23. |\n", - "|with 23 parameters are : alpha0, alpha1, alpha2, alpha3, alpha4, alpha5, alpha6, d0_n0_0, d0_n1_0, d0_n2_0...|\n", - "|Number qubit of circuit: 4 |\n", - "===============================================================================================================\n" - ] - }, - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────RZ(alpha0)────●──────────────────●────RY(d0_n0_0)──────────────────────────────────────────●─────────RY(d1_n0_0)────────────────────────────────────────────●─────────RY(d2_n0_0)────────────────────────●─────────RY(d3_n0_0)────────────────────────────────\n",
-       "                         │                  │                                                         │                                                                │                                            │                                                    \n",
-       "q1: ──H────RZ(alpha1)────X────RZ(alpha4)────X─────────●───────────────────────●────RY(d0_n1_0)────────X───────────────────────────────────────●────RY(d1_n1_0)─────────X──────────────●─────────RY(d2_n1_0)─────────X──────────────●─────────RY(d3_n1_0)─────────────────\n",
-       "                                                      │                       │                                                               │                                       │                                            │                                     \n",
-       "q2: ──H────RZ(alpha2)─────────────────────────────────X─────────RZ(alpha5)────X─────────●────────────────────────────●─────────RY(d0_n2_0)────X─────────●─────────RY(d1_n2_0)─────────X──────────────●─────────RY(d2_n2_0)─────────X──────────────●─────────RY(d3_n2_0)──\n",
-       "                                                                                        │                            │                                  │                                            │                                            │                      \n",
-       "q3: ──H────RZ(alpha3)───────────────────────────────────────────────────────────────────X─────────RZ(alpha6)─────────X─────────RY(d0_n3_0)──────────────X─────────RY(d1_n3_0)────────────────────────X─────────RY(d2_n3_0)────────────────────────X─────────RY(d3_n3_0)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────RZ(alpha0)────●──────────────────●────RY(d0_n0_0)──────────────────────────────────────────●─────────RY(d1_n0_0)────────────────────────────────────────────●─────────RY(d2_n0_0)────────────────────────●─────────RY(d3_n0_0)────────────────────────────────\n", - " │ │ │ │ │ \n", - "q1: ──H────RZ(alpha1)────X────RZ(alpha4)────X─────────●───────────────────────●────RY(d0_n1_0)────────X───────────────────────────────────────●────RY(d1_n1_0)─────────X──────────────●─────────RY(d2_n1_0)─────────X──────────────●─────────RY(d3_n1_0)─────────────────\n", - " │ │ │ │ │ \n", - "q2: ──H────RZ(alpha2)─────────────────────────────────X─────────RZ(alpha5)────X─────────●────────────────────────────●─────────RY(d0_n2_0)────X─────────●─────────RY(d1_n2_0)─────────X──────────────●─────────RY(d2_n2_0)─────────X──────────────●─────────RY(d3_n2_0)──\n", - " │ │ │ │ │ \n", - "q3: ──H────RZ(alpha3)───────────────────────────────────────────────────────────────────X─────────RZ(alpha6)─────────X─────────RY(d0_n3_0)──────────────X─────────RY(d1_n3_0)────────────────────────X─────────RY(d2_n3_0)────────────────────────X─────────RY(d3_n3_0)──" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "circuit = encoder + ansatz #完整的量子线路由Encoder和Ansatz组成\n", - "circuit.summary()\n", - "circuit" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从对完整的量子线路的Summary中可以看到,该量子线路由42个量子门组成,其中有23个含参量子门且参数为$\\alpha_0,\\alpha_1,...,\\alpha_6$和d2_n3_0, d1_n1_0, d0_n2_0, d1_n0_0, d3_n2_0, d2_n2_0, d0_n1_0, d3_n1_0, d2_n0_0, d3_n0_0...,该量子线路调控的量子比特数为4。\n", - "\n", - "## 8. 构建哈密顿量\n", - "\n", - "我们分别对第2位和第3位量子比特执行泡利`Z`算符测量,构建对应的哈密顿量。" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[1.0 [Z2] , 1.0 [Z3] ]\n" - ] - } - ], - "source": [ - "from mindquantum.core import QubitOperator # 导入QubitOperator模块,用于构造泡利算符\n", - "from mindquantum.core import Hamiltonian # 导入Hamiltonian模块,用于构建哈密顿量\n", - "\n", - "hams = [Hamiltonian(QubitOperator(f'Z{i}')) for i in [2, 3]] # 分别对第2位和第3位量子比特执行泡利Z算符测量,且将系数都设为1,构建对应的哈密顿量\n", - "print(hams)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印可以看到,此时构建的哈密顿量有2个,分别为对第2位和第3位量子比特执行泡利`Z`算符,且将系数都设为1。通过泡利`Z`算符测量,我们可以得到2个哈密顿量测量值,若第1个测量值更大,则会将此样本归类到标签为“0”的类,同理,若第2个测量值更大,则会将此样本归类到标签为“1”的类。通过神经网络的训练,期望训练样本中标签为“0”的样本的第1个测量值更大,而标签为“1”的样本的第2个测量值更大,最后应用此模型来预测新样本的分类。\n", - "\n", - "## 9. 搭建量子神经网络" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "MQLayer<\n", - " (evolution): MQOps<4 qubits projectq VQA Operator>\n", - " >" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import mindspore as ms # 导入mindspore库并简写为ms\n", - "from mindquantum.framework import MQLayer # 导入MQLayer\n", - "from mindquantum.simulator import Simulator\n", - "\n", - "ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target=\"CPU\")\n", - "ms.set_seed(1) # 设置生成随机数的种子\n", - "sim = Simulator('projectq', circuit.n_qubits)\n", - "grad_ops = sim.get_expectation_with_grad(hams,\n", - " circuit,\n", - " None,\n", - " encoder.params_name,\n", - " ansatz.params_name,\n", - " parallel_worker=5)\n", - "QuantumNet = MQLayer(grad_ops) # 搭建量子神经网络\n", - "QuantumNet" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印可以看到,我们已经成功搭建了量子机器学习层,其可以无缝地跟MindSpore中其它的算子构成一张更大的机器学习网络。\n", - "\n", - "说明:\n", - "\n", - "(1)mindspore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景覆盖三大目标,提供支持异构加速的张量可微编程能力,支持云、服务器、边和端多种硬件平台;\n", - "\n", - "\n", - "## 10. 训练\n", - "\n", - "接下来,我们需要定义损失函数,设定需要优化的参数,然后将搭建好的量子机器学习层和MindSpore的算子组合,构成一张更大的机器学习网络,最后对该模型进行训练。" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "epoch: 1 step: 16, loss is 0.6600145\n", - "epoch: 2 step: 16, loss is 0.4009103\n", - "epoch: 3 step: 16, loss is 0.39099234\n", - "epoch: 4 step: 16, loss is 0.3733629\n", - "epoch: 5 step: 16, loss is 0.3705962\n", - "epoch: 6 step: 16, loss is 0.37426245\n", - "epoch: 7 step: 16, loss is 0.37181872\n", - "epoch: 8 step: 16, loss is 0.37131247\n", - "epoch: 9 step: 16, loss is 0.37142643\n", - "epoch: 10 step: 16, loss is 0.37067422\n", - "epoch: 11 step: 16, loss is 0.3701976\n", - "epoch: 12 step: 16, loss is 0.36975253\n", - "epoch: 13 step: 16, loss is 0.36923727\n", - "epoch: 14 step: 16, loss is 0.3688001\n", - "epoch: 15 step: 16, loss is 0.3684062\n", - "epoch: 16 step: 16, loss is 0.36804128\n", - "epoch: 17 step: 16, loss is 0.36773998\n", - "epoch: 18 step: 16, loss is 0.36747772\n", - "epoch: 19 step: 16, loss is 0.36726192\n", - "epoch: 20 step: 16, loss is 0.36708587\n" - ] - } - ], - "source": [ - "from mindspore.nn import SoftmaxCrossEntropyWithLogits #导入SoftmaxCrossEntropyWithLogits模块,用于定义损失函数\n", - "from mindspore.nn import Adam, Accuracy #导入Adam模块和Accuracy模块,分别用于定义优化参数,评估预测准确率\n", - "from mindspore import Model #导入Model模块,用于建立模型\n", - "from mindspore.dataset import NumpySlicesDataset #导入NumpySlicesDataset模块,用于创建模型可以识别的数据集\n", - "from mindspore.train.callback import Callback, LossMonitor #导入Callback模块和LossMonitor模块,分别用于定义回调函数和监控损失\n", - "\n", - "loss = SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean') #通过SoftmaxCrossEntropyWithLogits定义损失函数,sparse=True表示指定标签使用稀疏格式,reduction='mean'表示损失函数的降维方法为求平均值\n", - "opti = Adam(QuantumNet.trainable_params(), learning_rate=0.1) #通过Adam优化器优化Ansatz中的参数,需要优化的是Quantumnet中可训练的参数,学习率设为0.1 \n", - " \n", - "model = Model(QuantumNet, loss, opti, metrics={'Acc': Accuracy()}) #建立模型:将MindQuantum构建的量子机器学习层和MindSpore的算子组合,构成一张更大的机器学习网络\n", - " \n", - "train_loader = NumpySlicesDataset({'features': X_train, 'labels': y_train}, shuffle=False).batch(5) #通过NumpySlicesDataset创建训练样本的数据集,shuffle=False表示不打乱数据,batch(5)表示训练集每批次样本点有5个\n", - "test_loader = NumpySlicesDataset({'features': X_test, 'labels': y_test}).batch(5) #通过NumpySlicesDataset创建测试样本的数据集,batch(5)表示测试集每批次样本点有5个\n", - "\n", - "class StepAcc(Callback): #定义一个关于每一步准确率的回调函数\n", - " def __init__(self, model, test_loader):\n", - " self.model = model\n", - " self.test_loader = test_loader\n", - " self.acc = []\n", - "\n", - " def step_end(self, run_context):\n", - " self.acc.append(self.model.eval(self.test_loader, dataset_sink_mode=False)['Acc']) \n", - "\n", - "monitor = LossMonitor(16) #监控训练中的损失,每16步打印一次损失值\n", - "\n", - "acc = StepAcc(model, test_loader) #使用建立的模型和测试样本计算预测的准确率\n", - "\n", - "model.train(20, train_loader, callbacks=[monitor, acc], dataset_sink_mode=False)#将上述建立好的模型训练20次" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印可以看到,20次迭代后,损失值不断下降并趋于稳定,最后收敛于约0.367。\n", - "\n", - "\n", - "\n", - "说明:\n", - "\n", - "(1)nn.SoftmaxCrossEntropyWithLogits可以计算数据和标签之间的softmax交叉熵。使用交叉熵损失测量输入(使用softmax函数计算)的概率和目标之间的分布误差,其中类是互斥的(只有一个类是正的),一般格式如下:mindspore.nn.SoftmaxCrossEntropyWithLogits(sparse=False, reduction=\"none\"),sparse=False表示指定标签是否使用稀疏格式,默认值:False;reduction=\"none\"表示适用于损失的减少类型。可选值为mean、sum和none。如果为none,则不执行减少,默认值:“没有”。\n", - "\n", - "(2)Adam模块通过自适应矩估计算法更新梯度,可以优化Ansazt中的参数,输入的是神经网络中可训练的参数;一般格式如下:nn.Adam(net.trainable_params(), learning_rate=0.1),学习率可以自己调节;\n", - "\n", - "(3)mindspore.Model是用于训练或测试的高级API,模型将层分组到具有训练和推理特征的对象中,一般格式如下:mindspore.Model(network, loss_fn=None, optimizer=None, metrics=None, eval_network=None, eval_indexes=None, amp_level=\"O0\", acc_level=\"O0\"),其中network就是我们要训练的网络即Quantumnet;loss_fn即目标函数,在这里就是定义的loss函数;optimizer即优化器,用于更新权重,在这里就是定义的opti;metrics就是模型在训练和测试期间需要评估的字典或一组度量,在这里就是评估准确率;\n", - "\n", - "(4)Accuracy用于计算分类和多标签数据的准确率,一般格式如下:mindspore.nn.Accuracy(eval_type=\"classification\"),用于分类(单标签)和多标签(多标签分类))的数据集上计算准确率的度量,默认值:“分类”;\n", - "\n", - "(5)NumpySlicesDataset使用给定的数据切片创建数据集,主要用于将Python数据加载到数据集中,一般格式如下:mindspore.dataset.NumpySlicesDataset(data, column_names=None, num_samples=None, num_parallel_workers=1, shuffle=None, sampler=None, num_shards=None, shard_id=None);\n", - "\n", - "(6)Callback用于构建回调类的抽象基类,回调是上下文管理器,在传递到模型时将输入和输出。你可以使用此机制自动初始化和释放资源。回调函数将执行当前步骤或数据轮回中的一些操作;\n", - "\n", - "(7)LossMonitor主要用于监控训练中的损失,如果损失是NAN或INF,它将终止训练,一般格式如下:mindspore.train.callback.LossMonitor(per_print_times=1),per_print_times=1表示每秒钟打印一次损失,默认值:1;\n", - "\n", - "(8)train模块用于训练模型,其中迭代由python前端控制;当设置pynative模式或CPU时,训练过程将在数据集不接收的情况下执行,一般格式如下:train(epoch, train_dataset, callbacks=None, dataset_sink_mode=True, sink_size=-1),其中epoch表示在数据上的总迭代次数;train_dataset就是我们定义的train_loader;callbacks就是我们需要回调的损失值和准确率;dataset_sink_mode表示确定是否通过数据集通道传递数据,教案中为否;\n", - "\n", - "## 11. 训练过程中的准确率\n", - "\n", - "我们已经看到损失值趋于稳定,那么我们还可以将模型在训练过程中的预测的准确率呈现出来,执行如下代码。" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0, 0.5, 'Accuracy')" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.plot(acc.acc)\n", - "plt.title('Statistics of accuracy', fontsize=20)\n", - "plt.xlabel('Steps', fontsize=20)\n", - "plt.ylabel('Accuracy', fontsize=20)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印的图像可以看到,在大约50步后,预测的准确率收敛于1,也就是说预测的准确率已经可以达到100%。\n", - "\n", - "## 12. 预测\n", - "\n", - "最后,我们测试一下训练好的模型,将其应用在测试集上。" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "预测分类结果: [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]\n", - "实际分类结果: [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]\n", - "{'Acc': 1.0}\n" - ] - } - ], - "source": [ - "from mindspore import ops, Tensor #导入ops模块和Tensor模块\n", - "\n", - "predict = np.argmax(ops.Softmax()(model.predict(Tensor(X_test))), axis=1) #使用建立的模型和测试样本,得到测试样本预测的分类\n", - "correct = model.eval(test_loader, dataset_sink_mode=False) #计算测试样本应用训练好的模型的预测准确率\n", - "\n", - "print(\"预测分类结果:\", predict) #对于测试样本,打印预测分类结果\n", - "print(\"实际分类结果:\", y_test) #对于测试样本,打印实际分类结果\n", - "\n", - "print(correct) #打印模型预测的准确率" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从上述打印的可以看到,预测分类结果和实际分类结果完全一致,模型预测的准确率达到了100%。\n", - "\n", - "至此,我们体验了如何通过搭建量子神经网络来解决经典机器学习中的经典问题——鸢尾花分类问题。相信大家也对使用MindQuantum有了更进一步的了解!期待大家挖掘更多的问题,充分发挥MindQuantum强大的功能!\n", - "\n", - "若想查询更多关于MindQuantum的API,请点击:[https://mindspore.cn/mindquantum/](https://mindspore.cn/mindquantum/)。" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorials/4.quantum_phase_estimation.ipynb b/tutorials/4.quantum_phase_estimation.ipynb deleted file mode 100644 index 61a1a7c0f..000000000 --- a/tutorials/4.quantum_phase_estimation.ipynb +++ /dev/null @@ -1,434 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 量子相位估计算法\n", - "\n", - "\n", - "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", - "\n", - "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/parameterized_quantum_circuit.ipynb) [![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/view_mindquantum_api.png)](https://mindspore.cn/mindquantum/api/zh-CN/master/index.html)\n", - "\n", - "## 概述\n", - "\n", - "量子相位估计算法(Quantum hase Estimation Algorithm,简称QPE),是很多量子算法的关键。假设一个幺正算符 $U$,这个幺正算符作用在其本征态 $|u\\rangle$ 上会出现一个相位 $e^{2\\pi i \\varphi}$,现在我们假设 $U$ 算符的本征值未知,也就是 $\\varphi$ 未知,但是 $U$ 算符和本征态 $|u\\rangle$ 已知,相位估计算法的作用就是对这个相位 $\\varphi$ 进行估计。 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![QPE](./images/quantum_phase_estimation.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 算法解析\n", - "\n", - "相位估计算法的实现需要两个寄存器(register),第一个寄存器包含t个初始在的 $|0\\rangle$ 的量子比特,比特数和最后相位估计的结果的精度和算法的成功概率相关;第二个寄存器初始化在幺正算符 $U$ 的而本征态 $|u\\rangle$ 上。相位估计算法主要分为三步:\n", - "\n", - "1. 对第一个寄存器的所有量子比特进行``Hadamard``门操作,对第二寄存器连续进行``控制U``门操作,其中U门的幂次依次为 $2^0, 2^1,...,2^{t-1}$,控制比特依次为 $q_{t-1}, q_{t-2},..., q_{1}, q_{0}$。这时第一寄存器中的态就会变为\n", - "\n", - "$$\n", - "|\\psi_1\\rangle=\\frac{1}{2^{t/2}}\\left(|0\\rangle+e^{i2\\pi 2^{t-1}\\varphi}|1\\rangle\\right)\\left(|0\\rangle+e^{i2\\pi2^{t-2}\\varphi}|1\\rangle\\right)...\\left(|0\\rangle+e^{i2\\pi 2^{0}\\varphi}|1\\rangle\\right) = \\frac{1}{2^{t/2}}\\sum_{k=0}^{2^t-1}e^{i2\\pi\\varphi k}|k\\rangle\n", - "$$\n", - "\n", - "其中k为直积态的十进制表示,比如 $k=0$ 表示第一寄存器中t个比特全部在基态 $|00...00\\rangle$, $k=2$ 表示 $|00...10\\rangle$,以此类推。\n", - "\n", - "2. 对第一寄存器的进行量子傅里叶变换的逆变换(Inverse Quantum Fourier Transform),在线路中表示成 $QFT^\\dagger$, 对 $|\\psi_1\\rangle$ 进行逆量子傅里叶变换可得 $|\\psi_2\\rangle$\n", - "\n", - "$$\n", - "|\\psi_2\\rangle=QFT^\\dagger|\\psi_1\\rangle =\\frac{1}{2^t}\\sum_{x=0}^{2^t-1}a_x|x\\rangle\n", - "$$\n", - "其中\n", - "$$\n", - "a_x=\\sum_{k=0}^{2^t-1}e^{2\\pi i k(\\varphi-x/2^t)}\n", - "$$\n", - "为本征基矢 $|x\\rangle$ ($x=0.1,...,2^t$) 对应的概率幅 。由上式可得,当 $2^t\\varphi$ 为整数,且满足 $x=2^t\\varphi$ 时,概率幅取最大值1,此时第一寄存器的末态可以精确反映 $\\varphi$;当 $2^t\\varphi$ 不是整数时,$x$ 为 $\\varphi$ 的估计,且t越大,估计精度越高。\n", - "\n", - "3. 对第一寄存器的量子比特进行测量,得到第一寄存器的末态 $f=\\sum_{x}^{2^t-1}a_x|x\\rangle$, $x=0,1,...,2^t$;从中找到最大的概率幅 $a_{max}$,其对应的本征基矢 $|x\\rangle$ 中的 $x$ 在除以 $2^t$ 即为相位的估计值。\n", - "\n", - "\n", - "\n", - "## QPE代码实现\n", - "\n", - "下面用一个实例来演示如何在mindquantum实现相位估计算法,选择 ``T``门作为进行估计的幺正算符,由定义\n", - "\n", - "$$\n", - "T|1\\rangle=e^{i\\pi/4}|1\\rangle\n", - "$$\n", - "可知需要估计的相位角为 $\\varphi=\\frac{1}{8}$。\n", - "\n", - "首先导入相关依赖" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from mindquantum import Circuit\n", - "from mindquantum import Simulator\n", - "from mindquantum import UN, PhaseShift, qft, H, X, BARRIER\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "``Mindquantum.UN`` 可以指定量子门,目标比特和控制比特,从而在线路中搭建门操作。因为我们已知 $\\varphi=1/8$,当 $t=3$ 时可令 $2^t\\varphi$ 为整数,所以第一寄存器只需要3个比特即可准确估计;又已知 ``T`` 门的本征态为 $|1\\rangle$,所以第二寄存器选择一个比特,即:我们需要搭建4比特线路,前 $q_0, q_1, q_2$ 比特用于估计,属于第一寄存器;$q_3$ 属于第二寄存器用于传入 $T$ 算符的本征态。\n", - "\n", - "利用 ``UN`` 对 $q_0, q_1, q_2$ 进行 ``Hadamard`` 门操作, 用 ``X`` 门对 $q_3$ 进行翻转,得到 ``T`` 门的本征态 $|1\\rangle$。" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H──\n",
-       "         \n",
-       "q1: ──H──\n",
-       "         \n",
-       "q2: ──H──\n",
-       "         \n",
-       "q3: ──X──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H──\n", - " \n", - "q1: ──H──\n", - " \n", - "q2: ──H──\n", - " \n", - "q3: ──X──" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "n = 3\n", - "c = Circuit()\n", - "c += UN(H, n)\n", - "c += X.on(n)\n", - "c" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "以 $q_3$ 为目标比特,添加 ``控制PhaseShift``门" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────────────────────────────────●──────\n",
-       "\n",
-       "q1: ──H───────────────────●────────────┼──────\n",
-       "                          │            │      \n",
-       "q2: ──H───────●───────────┼────────────┼──────\n",
-       "              │           │            │      \n",
-       "q3: ──X────PS(phi)────PS(2*phi)────PS(4*phi)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────────────────────────────────●──────\n", - " │ \n", - "q1: ──H───────────────────●────────────┼──────\n", - " │ │ \n", - "q2: ──H───────●───────────┼────────────┼──────\n", - " │ │ │ \n", - "q3: ──X────PS(phi)────PS(2*phi)────PS(4*phi)──" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "for i in range(n):\n", - " c += PhaseShift({'phi': 2**i}).on(n, n-i-1)\n", - "c" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对第一寄存器比特进行逆傅里叶变换" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────────────────────────────────●────────@──────────────────────────PS(-π/4)────PS(-π/2)────H──\n",
-       "                                       │        │                             │           │           \n",
-       "q1: ──H───────────────────●────────────┼────────┼─────────PS(-π/2)────H───────┼───────────●───────────\n",
-       "                          │            │        │            │                │                       \n",
-       "q2: ──H───────●───────────┼────────────┼────────@────H───────●────────────────●───────────────────────\n",
-       "              │           │            │                                                              \n",
-       "q3: ──X────PS(phi)────PS(2*phi)────PS(4*phi)──────────────────────────────────────────────────────────\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────────────────────────────────●────────@──────────────────────────PS(-π/4)────PS(-π/2)────H──\n", - " │ │ │ │ \n", - "q1: ──H───────────────────●────────────┼────────┼─────────PS(-π/2)────H───────┼───────────●───────────\n", - " │ │ │ │ │ \n", - "q2: ──H───────●───────────┼────────────┼────────@────H───────●────────────────●───────────────────────\n", - " │ │ │ \n", - "q3: ──X────PS(phi)────PS(2*phi)────PS(4*phi)──────────────────────────────────────────────────────────" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "c += BARRIER\n", - "c += qft(range(n)).hermitian()\n", - "c" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "选择后端、传入总比特数创建模拟器,将 $\\varphi$ 值传入并进行演化,得到末态" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1¦1100⟩\n" - ] - }, - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
shots: 100\n",
-       "Keys: q3 q2 q1 q0│0.00     0.2         0.4         0.6         0.8         1.0\n",
-       "─────────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n",
-       "             1100│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n",
-       "\n",
-       "{'1100': 100}\n",
-       "
\n" - ], - "text/plain": [ - "shots: 100\n", - "Keys: q3 q2 q1 q0│0.00 0.2 0.4 0.6 0.8 1.0\n", - "─────────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n", - " 1100│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n", - " │ \n", - "{'1100': 100}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from mindquantum import Measure\n", - "sim = Simulator('projectq', c.n_qubits)\n", - "phi = 0.125\n", - "sim.apply_circuit(c,{'phi': 2*np.pi*phi})\n", - "qs = sim.get_qs()\n", - "print(sim.get_qs(ket=True))\n", - "res = sim.sampling(UN(Measure(), c.n_qubits), shots=100)\n", - "res" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "找出概率幅最大值的位置" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "12\n" - ] - } - ], - "source": [ - "index = np.argmax(np.abs(qs))\n", - "print(index)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意此时的 ``index`` 对应的 $x$ 并不是真正的估计值,被 $2^t$ 除之后也不是,因为测量结果中包括第二寄存器中的辅助比特,需要将``index``转成二进制后将辅助位剔除" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "100\n" - ] - } - ], - "source": [ - "bit_string = bin(index)[2:].zfill(c.n_qubits)[1:]\n", - "print(bit_string)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在将二进制转回十进制,得到我们最终的估计值" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.125" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "theta_exp = int(bit_string[::-1], 2) / 2**n\n", - "theta_exp" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可见得到的估计相位和 $\\varphi$ 近似相等。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 参考文献\n", - "\n", - "[1] Michael A. Nielsen and Isaac L. Chuang. [Quantum computation and quantum information](www.cambridge.org/9781107002173)" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "6cd6e2203b621035efd3b4ac9716079b52ce7fc5622f6651a3ae71459e0d54ce" - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/tutorials/5.quantum_approximate_optimization_algorithm.ipynb b/tutorials/5.quantum_approximate_optimization_algorithm.ipynb deleted file mode 100644 index 358af7c16..000000000 --- a/tutorials/5.quantum_approximate_optimization_algorithm.ipynb +++ /dev/null @@ -1,481 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 量子近似优化算法\n", - "\n", - "\n", - "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", - "\n", - "[![](https://gitee.com/mindspore/docs/raw/master/tutorials/training/source_zh_cn/_static/logo_source.png)](https://gitee.com/mindspore/mindquantum/blob/master/tutorials/quantum_approximate_optimization_algorithm.ipynb)\n", - "\n", - "## 概述\n", - "\n", - "量子近似优化算法(Quantum Approximate Optimization Algorithm,QAOA)是利用量子计算机来近似解决组合优化问题的量子算法,最早由Farhi等人于2014年提出。在本教程里,我们将利用QAOA算法来解决最大割问题(Max-Cut),来熟悉MindQuantum中量子线路的搭建和训练。\n", - "\n", - "> 本文档适用于CPU环境。 \n", - "> 你可以在这里找到完整的可运行的样例代码:。\n", - "\n", - "## 环境准备\n", - "\n", - "本教程所需要的额外库:\n", - "\n", - "- networkx\n", - "\n", - "> `networkx`是创建、操作和研究复杂网络的结构、动态和功能库。可通过`pip3 install networkx`来进行安装。\n", - "\n", - "## Max-Cut问题描述\n", - "\n", - "Max-Cut问题是图论中的一个NP-complete问题,它需要将一个图中的顶点分成两部分,并使得两部分被切割的边最多。如下图(a),一个图由五个顶点构成,相互连接的边为```(0, 1), (0, 2), (1, 2), (2, 3), (3, 4), (0, 4)```。为了使得被切割的边最多,我们尝试通过(b)图的分割,将1、2、4分为一组,0、3分成另一组,因此可得到被切割的边有5条。当图中顶点增多时,我们很难找到有效的经典算法来解决Max-Cut问题。下面,我们介绍怎么将Max-Cut问题转化为一个哈密顿量的基态能力求解问题。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![max cut](./images/Max_Cut.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Max-Cut问题量子化\n", - "\n", - "这里我们将图中的每个顶点赋予一个量子比特,当顶点被分到左边时,我们将该顶点上的量子比特设置为$\\left|0\\right>$态,同理,右边为$\\left|1\\right>$态,当两个顶点被分到不同的集合中时,这两个顶点上的比特将处于不同的量子态。例如对于第0个顶点和第1个顶点,当其连线被切割是,两个顶点上的比特对应的量子态可以为$\\left|\\psi\\right>=\\left|0_11_0\\right>$或$\\left|\\psi\\right>=\\left|1_10_0\\right>$,其中下角标表示顶点的序号。此时,我们选择哈密顿量$H=(Z_1Z_0-1)/2$,这里$Z$为泡利$Z$算符。不难发现:\n", - "$$\\left<\\psi\\right|H\\left|\\psi\\right>=-1$$\n", - "而当顶点被分到同一集合中是,不难验证此时:\n", - "$$\\left<\\psi\\right|H\\left|\\psi\\right>=0$$\n", - "因此,我们只用按照上面的规则,写出图对应的哈密顿量$H$,利用量子计算机求得$H$的基态能量与基态,我们就可以得到该图的Max-Cut切割方案与最大切割边数。我们记所有边的集合为$C$,所有边个数为$c$,则哈密顿量可写为:\n", - "$$H=\\sum_{(i,j)\\in C}(Z_iZ_j-1)/2$$\n", - "\n", - "## 导入相关依赖" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from mindquantum.core import Circuit, Hamiltonian, UN, H, ZZ, RX, QubitOperator\n", - "from mindquantum.framework import MQAnsatzOnlyLayer\n", - "from mindquantum.simulator import Simulator\n", - "import networkx as nx\n", - "import mindspore.nn as nn\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 搭建所需求解的图\n", - "\n", - "通过`add_path`可在图中添加边。最后画出图的结构。" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAb4AAAEuCAYAAADx63eqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAA9FElEQVR4nO3deVjU1eIG8HfY3XAXTLSuIbKKCSLuXPW6Vi7gbt6fCyqgEKWkoGYqaC5lKriSCipJSmZa5oIiLoiAYQgoWC64Ai6AMgMD8/vD8Gaios5wZnk/z9Pz5DDznZd7k5dz5nzPkSgUCgWIiIh0hJ7oAERERNWJxUdERDqFxUdERDqFxUdERDqFxUdERDqFxUdERDqFxUdERDqFxUdERDqFxUdERDqFxUdERDrFQHQAUm95RTLsTM5B5q0CFEjlMDUxgLW5KYY6WaBhbWPR8YiIXpmEe3VSZVKv3Ufo0WzEXcwFAMjk5U++ZmKgBwUAt9aN4d3dEo7N64kJSUT0Glh89IytCZcR/HMmpPIyvOi/DokEMDHQR1B/a4xxfafa8hERvQlOddJTHpdeBopLy1/6XIUCKC4tQ/DPGQDA8iMijcARHz2Reu0+RmxIQHFp2TNfe5geh7w9SwEAdZw/RINek576eg1DfeyY5Io2FvWqIyoR0Wvjqk56IvRoNqTyZ0tPXpCHu7+GAXr6z32tVF6GsKPZqoxHRKQULD4C8Hj1ZtzF3Gc+01MoFMjf9xX06zREzdadnvt6hQI4ciEX+UUyFSclInozLD4CAOxMzqn08cIzP0Kak45GH0yHRN/ohdeQANiZUvl1iIjUBYuPAACZtwqeumUBAEpyL+Ne3BbU6zoGRmYtX3oNqbwcmTcLVRWRiEgpuKqTAAAFUvkzjz26cBIok0N69XfIrp1HyZ0/AQDFWadxz8AI9d3+r5LrlKo6KhHRG2HxEQDA1KSS/xQUCgAKSP9Ifuph+YPbkF3PfM51DFWQjohIeVh8BACwNjeFscGtp6Y763UdjXpdRz/5c97er/Ew7XCltzMAj3d0sW5ap1ryEhG9Ln7GRwAADyeLN76GAoBHuze/DhGRKvEGdnpiUmQSDmbcfuE2Zc+jKC+HhSQfv872QO3atZUfjohISTjioyd83CxhYvD8m9RfpIaRAZrknoWtrS1iYmLA36eISF2x+OgJx+b1ENDbEigreaXX1TDUw+wBNvgh/BtERkZizpw5GDBgAC5duqSipEREr4/FR085ueVLvJ2fBBNDPUgkL36uRPJ4j86g/jZPNqju3r07fvvtN/To0QMdOnTAvHnzIJVKVR+ciKiK+BkfPbFx40Z89dVXOH36NP588HjvzSMXciHB45vTK1Scx/fv1o3h7Wb53I2pr127Bn9/f/z2229YtWoV+vXrVy3fBxHRi7D4CABw5swZ9O/fH/Hx8bC2tn7yeH6RDDtTcpB5sxAF0lKYmhjCumkdeLSr+gns+/fvx9SpU+Ho6IgVK1agefPmqvo2iIheisVHyM3NhbOzM77++msMGTJEJe8hlUqxZMkSrFy5EgEBAfj4449hZPTivT+JiFSBxafj5HI5+vbtC2dnZyxevFjl73fp0iVMmzYNV65cQVhYGLp3767y9yQi+jsWn46bOXMmkpKSsH//fhgYVM9GPgqFArt378bHH3+Mbt26YenSpTA3N6+W9yYi4qpOHRYTE4OoqChERUVVW+kBgEQiweDBg5Geno5mzZrBwcEBq1evRlnZs4fgEhEpG0d8OiozMxNdu3bFzz//jPbt2wvNkp6eDh8fHzx48ABr1qxBhw4dhOYhIu3GEZ8OKiwsxODBg7Fo0SLhpQcAtra2iI2NxaefforBgwdj0qRJyM/PFx2LiLQUi0/HKBQKjBs3Dl27dsXEiRNFx3lCIpFg9OjRyMjIgImJCezs7BAeHo7y8vKXv5iI6BVwqlPHLFmyBDt37kR8fDyMjat2H54IZ8+ehZeXF/T09LBmzRo4OjqKjkREWoIjPh1y+PBhfP3119i1a5dalx4AvPfeezh58iTGjRuH3r174+OPP0ZBQYHoWESkBVh8OuLq1asYM2YMtm3bpjE7p+jp6cHT0xPnz5/Hw4cPYWNjg6ioKJ78QERvhFOdOkAqlaJbt27w8PBAQECA6Div7dSpU/Dy8kLDhg0RGhr61NZqRERVxRGfDvD19UWLFi0wY8YM0VHeSMeOHZGUlISBAweia9euCAwMxMOHD0XHIiINw+LTcuHh4YiPj8emTZsgedk5QxrAwMAAvr6+OHfuHK5cuQI7Ozv8+OOPnP4koirjVKcWS0pKQr9+/Z45cUGbxMbGwsfHB5aWlli5ciX+9a9/iY5ERGqOIz4tlZeXB3d3d6xdu1ZrSw8AevTogdTUVHTu3Bnt27fHggULIJPJRMciIjXG4tNCZWVlGDlyJEaMGAF3d3fRcVTOyMgIM2fORHJyMlJSUuDg4IADBw6IjkVEaopTnVpo1qxZSExMxK+//lqtm0+ri3379mHatGlPzhhs1qyZ6EhEpEY44tMyP/zwA7Zv347vvvtOJ0sPAAYMGIDz58/D2toajo6OWL58OUpLS0XHIiI1wRGfFlGnExfURVZWFqZOnYobN24gLCwMXbt2FR2JiARj8WmJwsJCdOjQAf7+/vD09BQdR60oFArs2rUL/v7+6NmzJ5YsWYImTZqIjkVEgnCqUwsoFAqMHz8enTp1YulVQiKRwMPDA+np6WjcuDHs7e2xZs0aHnxLpKM44tMCS5cuRXR0NOLj42FiYiI6jtpLS0uDt7c3iouLERYWxmlhIh3D4tNwsbGxGDVqFBITE9GiRQvRcTSGQqFAZGQkPvvsMwwaNAghISGoX7++6FhEVA041anBrl27htGjR2Pbtm0svVckkUgwduxYpKenQ09PD7a2tti8eTO3PiPSARzxaSiZTIZu3bphyJAh+Oyzz0TH0XhJSUnw9vaGsbExwsLC4ODgIDoSEakIR3waytfXFxYWFhp9zJA6cXZ2xqlTpzB69Gj07NkTn376KQoLC0XHIiIVYPFpoG+//RbHjh3TmhMX1IW+vj6mTJmCtLQ03L17F7a2toiOjub0J5GW4VSnhqk4ceHYsWOwsbERHUerHT9+HN7e3jA3N8fq1athZWUlOhIRKQFHfBokLy8PHh4eWLNmDUuvGnTp0gXJycno168fOnXqhDlz5uDRo0eiYxHRG2LxaYiKExeGDRsGDw8P0XF0hqGhIfz9/ZGamoqsrCzY2dlh7969omMR0RvgVKeGCAwMREJCAg4cOKCzm0+rg0OHDsHHxwc2Njb45ptv8Pbbb4uORESviCM+DbB7925s3bpVp09cUBe9evXCuXPn0L59ezg5OSEkJIQH3xJpGI741NyFCxfQtWtX7N27Fy4uLqLj0N/8+eef8PPzw8WLFxEaGoqePXuKjkREVcDiU2NFRUXo0KED/Pz8MGnSJNFx6Dn27NkDPz8/uLq6Yvny5XjrrbdERyKiF+BUp5qqOHHB1dWVJy6ouQ8//BDnz59Hy5Yt4ejoiBUrVkAul4uORUTPwRGfmlq+fDmioqJw/PhxnrigQTIzMzF16lTk5uZizZo16NSpk+hIRPQPLD41dOTIEYwcORKnT5/mqkENpFAoEB0djU8++QR9+/bF4sWL0bhxY9GxiOgvnOpUM9euXcOoUaOwdetWlp6GkkgkGD58ODIyMmBqago7OzusX78e5eXloqMRETjiUysVJy4MHjwYM2fOFB2HlCQ1NRXe3t6Qy+VYs2YN2rVrJzoSkU5j8amRKVOm4M6dO9i1axc3n9Yy5eXl2LJlC2bNmgUPDw8sXLgQ9erVEx2LSCdxqlNNbNq0CUePHsXmzZtZelpIT08P48aNQ3p6OuRyOWxsbLB161ae/EAkAEd8aiA5ORl9+/ZFXFwcbG1tRcehapCYmAgvLy/UqVMHoaGhsLOzEx2JSGdwxCdYfn4+PDw8EBYWxtLTIS4uLkhMTMTQoUPh5uaGgIAAFBUViY5FpBNYfAJVnLjg4eGBoUOHio5D1UxfXx8+Pj5IS0vDrVu3YGtri127dnH6k0jFONUpUFBQEE6dOsUTFwgAEBcXB29vb7Ro0QKrVq2CpaWl6EhEWokjPkF+/PFHREZG8sQFeqJ79+747bff0LNnT7i6umLevHkoLi4WHYtI67D4BLh48SI8PT3x/fffo0mTJqLjkBoxNDTE9OnTcfbsWaSlpcHe3h6//PKL6FhEWoVTndWs4sQFX19fTJ48WXQcUnP79+/H1KlTn2x+3bx5c9GRiDQeR3zVSKFQYMKECejQoQOPGaIq6du3L9LS0uDo6Ij33nsPX375JUpKSkTHItJoLL5q9PXXXyM7OxuhoaG8SZ2qzMTEBHPnzsXp06cRFxeHtm3b4ujRo6JjEWksTnVWk6NHj2LEiBFISEjAO++8IzoOaSiFQoHdu3fj448/RteuXbFs2TKYm5uLjkWkUTjiqwY5OTkYOXIkIiIiWHr0RiQSCQYPHoz09HRYWFjAwcEBq1at4sG3RK+AIz4Vk8lk6N69OwYOHIhZs2aJjkNaJj09HT4+Pnjw4AHCwsLg6uoqOhKR2mPxqZiXlxdu3bqFmJgYfq5HKqFQKLB9+3bMmDED77//PhYtWoSGDRuKjkWktjjVqUKbN29GbGwstmzZwtIjlZFIJBg9ejQyMjJgYmICW1tbhIeH8+BboufgiE9FUlJS0KdPH564QNXu7Nmz8PLygp6eHsLCwtC2bVvRkYjUCkd8KpCfnw93d3eeuEBCvPfeezh58iTGjRuHPn36wM/PDw8ePBAdi0htsPiUrKysDKNGjYK7uztPXCBh9PT04OnpifPnz+PRo0ewtbVFVFQUT34gAqc6lW727Nk4ceIEDh48yM2nSW2cOnUKXl5eaNCgAUJDQ2FjYyM6EpEwHPEp0Y8//oiIiAjs2LGDpUdqpWPHjkhKSsKgQYPQrVs3zJo1Cw8fPhQdi0gIFp+SVJy4EB0dzRMXSC0ZGBjA19cX586dw9WrV2FnZ4fdu3dz+pN0Dqc6laCoqAiurq6YOnUqpkyZIjoOUZXExsbCx8cH7777LlauXImWLVuKjkRULTjie0MKhQITJ05E+/btecwQaZQePXogNTUVXbp0gYuLCxYsWACpVCo6FpHKsfje0IoVK5CVlYWwsDDepE4ax8jICDNnzkRycjJSUlLg4OCAAwcOiI5FpFKc6nwDcXFxGDZsGE6fPs3Np0kr7Nu3D9OmTYOzszO++uorWFhYiI5EpHQc8b2m69evY+TIkYiMjGTpkdYYMGAAzp8/D2tra7Rt2xbLli1DaWmp6FhESsUR32uQyWRwc3PD+++/j6CgINFxiFQiKysLU6dOxfXr17FmzRp07dpVdCQipWDxvQZvb2/cuHEDMTEx0NPjoJm0l0KhwK5du+Dv748ePXpg6dKlvF2HNB5/ar+iLVu24PDhw9iyZQtLj7SeRCKBh4cH0tPT0aRJE9jb2yMsLAxlZWWioxG9No74XsHZs2fRu3dvHD16FHZ2dqLjEFW7tLQ0eHt749GjR1izZg3at28vOhLRK+OQpYry8/MxZMgQhIaGsvRIZ9nb2yMuLg6+vr748MMP4eXlhXv37omORfRKWHxVUFZWhtGjR2PIkCEYNmyY6DhEQkkkEowdOxbp6enQ09ODra0tNm/ezINvSWNwqrMK5syZg/j4eBw6dIibTxP9Q1JSEry9vWFsbIywsDA4ODiIjkT0QhzxvcSePXuwefNmnrhA9BzOzs44deoURo8ejZ49e+KTTz5BYWGh6FhEz8Xie4GsrCxMnDgR33//PczMzETHIVJb+vr6mDJlCs6fP4/79+/DxsYG0dHRPPmB1BKnOp/j4cOHcHV1hbe3N7y8vETHIdIox48fh7e3N8zMzBAaGgorKyvRkYie4IivEhUnLjg5OfGYIaLX0KVLFyQnJ6N///7o1KkTZs+ejUePHomORQSAxVepb775BhcuXMCaNWt44gLRazI0NIS/vz9SU1ORnZ0NOzs7/PTTT6JjEXGq85+OHTuGoUOHIiEhAf/6179ExyHSGocOHYKPjw+sra3xzTffcHN3EoYjvr+5ceMGRowYgYiICJYekZL16tUL586dg4uLC5ydnRESEgKZTCY6FukgFt9fSkpK4OHhAW9vb/Tp00d0HCKtZGxsjKCgIJw5cwYJCQlo06YNDh06JDoW6RhOdf5l6tSpuHr1Knbv3s3Np4mqyZ49e+Dr64uOHTti+fLleOutt0RHIh3An/AAIiIicODAAURGRrL0iKrRhx9+iPT0dLRs2RJt2rTBihUrIJfLRcciLafzI76KExeOHDkCe3t70XGIdNaFCxfg4+OD3NxchIWFoXPnzqIjkZbS6eHN3bt34e7ujtWrV7P0iARr3bo1Dh48iMDAQAwbNgzjx49Hbm6u6FikhXS2+CpOXBg0aBCGDx8uOg4R4fHJD8OHD0dGRgbq1asHOzs7rFu3jic/kFLp7FTn3LlzERcXh0OHDsHQ0FB0HCKqxLlz5+Dl5QW5XI6wsDA4OTmJjkRaQCdHfD/99BM2bdqE6Oholh6RGmvTpg3i4+MxZcoUDBgwAFOnTsX9+/dFxyINp3PFl52djQkTJiA6OponLhBpAD09PYwbNw7p6emQy+WwtbVFZGQkT36g16ZTU50VJy54eXnB29tbdBwieg2JiYnw8vJC7dq1ERYWBjs7O9GRSMPozIhPoVDA09MT7dq14zFDRBrMxcUFiYmJGDZsGNzc3DBjxgwUFRWJjkUaRGeKb+XKlcjIyMDatWt54gKRhtPX14ePjw/S0tJw+/Zt2NraYteuXZz+pCrRianO+Ph4eHh48MQFIi0VFxcHb29vNG/eHKtXr4alpeVLX5NXJMPO5Bxk3ipAgVQOUxMDWJubYqiTBRrWNq6G1CSK1hffjRs30L59e4SHh6Nv376i4xCRipSWluKbb77B4sWL4ePjg5kzZ6JGjRrPPC/12n2EHs1G3MXHN8fL5P+7R9DEQA8KAG6tG8O7uyUcm9erpvRUnbS6+EpKSvDvf/8bffv2xZw5c0THIaJqcO3aNfj7++Ps2bNYtWoV+vfv/+RrWxMuI/jnTEjlZXjRTz6JBDAx0EdQf2uMcX1H9aGpWml18U2bNg1XrlzhiQtEOujXX3/F1KlT4eDggBUrVuDYjXIE/5yB4tKq7wJTw1APQf1tWH5aRmuLLzIyEvPnz8eZM2dQr1490XGISACpVIolS5Zg9fafYDp4LuT/WM+nkJfgXuy3eJgZD0VJMYzM3kX9nhNh/FbrJ8+pYaiPHZNc0caiXjWnJ1XRyuL77bff8J///AexsbFwcHAQHYeIBBu9Ng4nLhcAkqeLL3//ahT9th+Gjd+GYaO38SgjHhIjEzSbshH6NesCeDzt2cfWDGvHOIuITiqgdfN/FScurFq1iqVHRMgrkiHp+qNnSq/s4X0UnTsESPRgNiIYjQcGoJadGxQlxShM3vvkeQoFcORCLvKLZNUdnVREq4qvvLwcY8aMwYcffogRI0aIjkNEamBnck6lj5fmXQXK5dA3bQz9WvUAAEbmj2+DKLnz51PPlQDYmVL5dUjzaFXxffHFF3j48CGWLFkiOgoRqYnMWwVP3bJQoezhPQCAnpHJk8ckf/17xdcqSOXlyLxZqMKUVJ0MRAdQlr179yI8PBxJSUk8cYGIniiQyit9XL9WfQBAeYn0yWOKv/694mtPX6dUBelIBK0Y8WVnZ2P8+PGIjo6Gubm56DhEpEZMTSr//d6wUXNAzwBlBblPRniymxcBAEZNnt3hydSEv1BrC40f8T18+BBDhgzB559/jk6dOomOQ0RqxtrcFMYGt56Z7tSvVR+1HXqiKPVX3I4KgmHjt/Eo4zgkRjVQx+n9p55rYqAH66Z1qjM2qZBG386gUCgwZswY6OvrY8uWLdx8moiekVckQ+cvYyv9nK+8VIZ7R77Fo4x4lJcUw9j8XdTvMQHGzWyeep6xgR5OftaDe3hqCY0e8a1atQrnz5/HyZMnWXpEVKlGtY3R3aoxDmbcfmabMj1DYzTs7YWGvZ9/VJlEAvy7dWOWnhbR2M/4jh8/juDgYMTExKBmzZqi4xCRGvNxs4SJgf5rvdbEQB/ebi8/7YE0h0YW382bNzF8+HBs3rwZLVu2FB2HiNScY/N6COpvDaNX7L7He3Vac7syLaNxxVdSUoKhQ4di8uTJ6Nevn+g4RKQheresCenJ7TDUU+Cln4woyqGnkHODai2lccU3ffp01K9fH7NnzxYdhYg0hFwux/Dhw/FRx3ewy6sL+tiawdhADyYGT/8INDHQg7GBHnq2bozSX5agWfGfz7kiaTKNWtW5detWzJs3D0lJSTxxgYiqbMaMGTh37hx+/vln6Os/nu/ML5JhZ0oOMm8WokBaClMTQ1g3rQOPdo9PYP/pp5/g7++Pc+fOcR2BltGY4ktNTUWvXr1w+PBhtGnTRnQcItIQ33//PQICApCUlISGDRu+0mtHjhwJCwsLLF26VEXpSASNKL579+7B2dkZCxYswKhRo0THISINkZ6eju7du+PXX39Fu3btXvn1d+7cgYODA/bt2wdnZx5LpC3U/jO+ihMXPvjgA5YeEVVZQUEBBg8ejCVLlrxW6QFAkyZNsHz5ckyYMAGlpdyrU1uo/Yhv3rx5iI2NxeHDh7n5NBFViUKhgLu7O5o0aYK1a9e+8bUGDBiAzp07IygoSEkJSSS1Lr59+/Zh8uTJSEpK4ubTRFRlixcvxu7duxEXFwdj4zffceXq1ato164d4uPjYWNj8/IXkFpT2+K7dOkSOnbsiN27d3PzaSKqskOHDuGjjz7CmTNnYGFhobTrhoaGIioqCseOHYOentp/SkQvoJb/7z169AhDhgzB3LlzWXpEVGVXrlzBmDFjsH37dqWWHgB4eT3ez3PNmjVKvS5VP7Ub8SkUCnz00UeQSCSIiIjg5tNEVCVSqRRdunTBiBEjMH36dJW8R2ZmJrp27Yrk5GS0aNFCJe9Bqqd2xbdq1SqEh4fj5MmTvGmUiKps4sSJKCgowI4dO1T6C3NwcDBOnDiBffv28RdzDaVWU53Hjx/HggULsGvXLpYeEVXZhg0bcPLkSYSHh6u8jAICAnD9+nVs27ZNpe9DqqM2I76bN2/C2dkZGzZsQP/+/UXHISINkZiYiAEDBuD48eNo3bp1tbxnUlISBgwYgN9//x1NmjSplvck5VGL4istLUWPHj3Qq1cvfP7556LjEJGGyM3NhbOzM1asWIHBgwdX63vPmDEDOTk5iIqKqtb3pTenFsXn5+eHS5cuYc+ePVwmTERVIpfL0adPH7i4uGDRokXV/v6PHj1CmzZt8PXXX+ODDz6o9ven16fy4ssrkmFncg4ybxWgQCqHqYkBrM1NMdTp8Q7o27dvx9y5c3HmzBnUr19flVGISIsEBATg7Nmz2L9//5MTF6rbkSNHMHbsWKSlpaFu3bpCMtCrU1nxpV67j9Cj2Yi7mAsAkMnLn3zNxEAPCgDvmRvhyOpZOLhjI09cIKIq27lzJ6ZPn46kpCQ0atRIaJZJkyZBX1+f9/dpEJUU39aEywj+ORNSeRleePXychjqS/D5h/Y85ZiIqiQjIwPdunXD/v374eTkJDoO7t+/D3t7e2zbtg3du3cXHYeqQOkfqD0uvQwUl76k9ABATw+lCgmCf87A1oTLyo5CRFqm4sSFL7/8Ui1KDwDq1auH0NBQeHp6ori4WHQcqgKljvhSr93HiA0JKC4te/JY3t6vIL38G8qKC6BnVBNG5pao3/2/MDJ/96nX1jDUx45JrmhjUU9ZcYhIiygUCnh4eKBRo0ZYt26d6DjPGDZsGN59910hC23o1Sh1xBd6NBtSedlTj8kf3IFxCwfUbvMf6NWoA+mfKbgTs/CZ10rlZQg7mq3MOESkRZYsWYKcnBysXLlSdJRKrVq1Ct9++y1SUlJER6GXMFDWhfKKZIi7mPvM9Kb56MVP/l12Kxu3Nn+MssJ8KMrkkOj/7+0VCuDIhVzkF8nQsPabHyNCRNrj8OHDWLFiBRITE5VyzJAqmJmZYcmSJZgwYQISExN5fqgaU9qIb2dyznO/VpD8E/J/DUPenqUAAFOXQU+VXgUJgJ0pz78OEemeq1evYvTo0di2bRuaN28uOs4LjR079smp7aS+lDbiy7xV8NQtC3/3KPMEZNfSAAD6dRrBuJltpc+TysuRebNQWZGISMNJpVK4u7vj008/RY8ePUTHeSmJRIJ169bB2dkZQ4YMgZWVlehIVAmljfgKpPLnfs189GK0mB6DxkNmo6zoLnJ3L4L8/u3nXKdUWZGISMNNmzYN77zzjsqOGVKFd955B3PmzMHEiRNRXl75YIDEUlrxmZo8O3gsL5VBUf54sYvEwAg1WjpBYmQClJdB/qDy4jM14bw4EQEbN27EiRMn8O2332rc8T9Tp05FaWkp1q9fLzoKVUJpU53W5qYwNrj11HRnyY0LyPtpGYyb20HPpDZk185DIXsEvZp1YWT27jPXMDHQg3XTOsqKREQa6syZM5g1axbi4+NRp47m/UzQ19fHxo0b4ebmhvfff1/pp8HTm1HaiM/D6dn/Y/XrNIRB/bcg/fM3FKUeRLm0CDWtu8BsZDD0TGo983x5WRnc32umrEhEpIFyc3Ph4eGBdevWwdraWnSc12ZnZ4dp06ZhypQpUIOzAOhvlHoD+6TIJBzMuP3yHVsqCwIF9G6mweziHoSEhMDNzU1ZsYhIQ8jlcvTt2xfOzs5YvHjxy1+g5kpKSuDk5ITAwECMHDlSdBz6i1JvYPdxs4SJwevtkm5iaICd8yfBx8cHEyZMQO/evZGUlKTMeESk5oKCggAACxc+u8mFJjIyMkJ4eDj8/f2Rl5cnOg79RanF59i8HoL6W6OG4atdtoahHoL6W+O9Fg0wevRoZGZmYsiQIRg4cCDc3d2Rnp6uzJhEpIZ27dqF7777DlFRUTAwUNryA+FcXFwwatQo+Pv7i45Cf1H6JtVjXN9BUH8b1DDUx8sWYkkkj/foDOpv89TpDIaGhpgyZQqysrLg6uoKNzc3/N///R8uX76s7LhEpAYyMjIwZcoU7Nq1C40bNxYdR+kWLFiAEydO4JdffhEdhaCC4gMel9+OSa7oY2sGYwM9mBg8/TYmBnowNtBDH1sz7Jjk+twjiWrWrIkZM2YgKysLLVq0gJOTE6ZNm4Zbt26pIjYRCVBYWIghQ4Zg8eLFcHZ2Fh1HJWrVqoX169djypQpKCzkJh2iqfwE9vwiGXam5CDzZiEKpKUwNTGEddM68Ghn8cp7ct65cweLFi1CREQEJk+ejBkzZvDUdiINplAoMHToUDRo0EAn7nkbP348atasidWrV4uOotNUXnyqcPXqVcyfPx8//vgjPvnkE/j6+qJWrWdvjyAi9bZkyRLs3LkT8fHxarv5tDLdu3cP9vb22LFjB7p06SI6js5SyVSnqrVo0QIbN27E8ePH8dtvv6FVq1YIDQ1FSUmJ6GhEVEWHDx/G119/jV27dulE6QFA/fr1sXLlSkycOBFSqVR0HJ2lkcVXoXXr1tixYwf27t2Lffv2oXXr1tiyZQvKyspe/mIiEubq1asYM2aMRpy4oGzu7u6ws7PTmls2NJFGTnU+T3x8PGbNmoW7d+9i4cKFGDx4sMbt8Uek7aRSKbp16wYPDw8EBASIjiPEzZs34ejoiIMHD8LR0VF0HJ2jVcUHPP6w/JdffkFgYCAMDQ0REhKCXr16sQCJ1MSkSZNw9+5dfP/99zr99/Lbb79FWFgYEhIStOq+RU2g0VOdlZFIJOjfvz9SUlIwffp0+Pj4oGfPnkhISBAdjUjnhYeHIz4+Hps2bdLp0gOAcePGoV69elixYoXoKDpH60Z8/1RaWootW7bgiy++QLt27bBw4UI4ODiIjkWkc5KSktCvXz/Ex8dr9ObTyvTHH3/AxcUFCQkJsLS0FB1HZ2jdiO+fDA0NMXHiRGRlZcHNzQ29evXCmDFjcOnSJdHRiHRGXl4e3N3dsXbtWpbe37Rs2RKBgYHw9PTkCQ7VSOuLr4KJiQn8/f2RnZ0NKysrdOjQAV5eXrhx44boaERaraysDCNHjsSIESPg7u4uOo7a8fPzw8OHD7Fx40bRUXSGzhRfhTp16mDu3LnIzMxE7dq1YW9vj4CAAOTn54uORqSVZs+ejfLycgQHB4uOopb09fURHh6OwMBAXL9+XXQcnaBzxVehUaNGWLp0KX7//XcUFBSgdevWWLBgAffRI1KimJgYbN++Hd999x1XLr6Ag4MDvLy84OPjwynPaqCzxVehWbNmWLt2LRISEpCZmYlWrVphxYoV3FWB6A1lZmZi8uTJ2Llzp1aeuKBsQUFBuHjxInbu3Ck6itbT+eKrYGlpiW3btuHAgQOIjY2FlZUVwsPDIZfLRUcj0jiFhYUYPHgwFi1ahPbt24uOoxGMjY0RHh4OX19ffvSiYlp/O8PrOnnyJAIDA3Hz5k0sWLAAHh4e0NPj7wlEL1Nx4kL9+vWxYcMG0XE0jp+fHx48eIDNmzeLjqK1WHwvoFAocPDgQQQGBj75cL5v3746f+Mt0YssXboU0dHRiI+Ph4mJieg4GqeoqAj29vZYt24d+vTpIzqOVmLxVYFCoUBMTAxmz56Nxo0bIyQkhEeKEFUiNjYWo0aNQmJiIlq0aCE6jsb69ddfMWXKFPz++++oXbu26Dhah8X3CuRyObZu3Yp58+bBzs4OwcHBaNu2rehYRGrh2rVrcHFxwdatW9GzZ0/RcTTef//7X9SvX59bmqkAi+81yGQyrF+/HiEhIejevTvmz58PKysr0bGIhJHJZOjWrRuGDBmCzz77THQcrZCfnw97e3vExMSgY8eOouNoFa7WeA3GxsaYNm0asrOz0aZNG3Tu3Bmenp64du2a6GhEQvj6+sLCwkJnjxlShYYNG+Kbb77BxIkTIZPJRMfRKiy+N1CrVi0EBgbi4sWLaNSoEdq2bYtPPvkEubm5oqMRVZtvv/0Wx44d44kLKjB06FBYWloiJCREdBStwqlOJbp58yaCg4MRFRWFqVOn4tNPP4WpqanoWEQqU3HiwrFjx2BjYyM6jla6fv062rZti9jYWJ4soyQc8SlR06ZNsXr1aiQlJeHy5cuwtLTEsmXLUFxcLDoakdLl5eXBw8MDa9asYempULNmzRAcHIyJEyeirKxMdBytwOJTgX/961/YsmULjhw5gpMnT6JVq1ZYt24dSktLRUcjUoqKExeGDRsGDw8P0XG03sSJE1GzZk2sXLlSdBStwKnOanDmzBkEBgbizz//xPz58zFixAjuAkMaLTAwEAkJCThw4AA3n64m2dnZcHV1RWJiIlq2bCk6jkZj8VWj2NhYBAYGori4GMHBwRgwYAAXA5DG+eGHH+Dn54ekpCQ0adJEdBydsnTpUvz66684ePAgf3a8ARZfNVMoFNizZw+CgoJgamqKkJAQuLm5iY5FVCWZmZno2rUr9u3bBxcXF9FxdI5cLoerqyu8vb0xfvx40XE0FotPkLKyMkRFRWHu3Llo1aoVgoOD4ezsLDoW0XMVFhaiQ4cO8Pf3h6enp+g4Ois1NRX/+c9/kJqaiqZNm4qOo5FYfIKVlJQgPDwcCxcuRMeOHbFgwQKukCO1o1AoMGzYMNStWxcbN24UHUfnBQUF4cKFCzy77zVxhYVgRkZG8PLyQlZWFlxcXNC9e3eMGzcOV65cER2N6Inly5fj8uXLWL16tegoBGDOnDlIS0tDTEyM6CgaicWnJmrWrImAgABkZWXBwsIC7dq1g6+vL27fvi06Gum4I0eOYNmyZdi5cyePGVITJiYm2LhxI6ZNm4Z79+6JjqNxWHxqpm7duliwYAEyMjKgp6cHW1tbBAUF4f79+6KjkQ66du0aRo0aha1bt+Ltt98WHYf+pkuXLhg0aBCmT58uOorGYfGpqSZNmmDFihU4e/Ysbt26hVatWmHx4sV4+PCh6GikI2QyGTw8PODn54devXqJjkOVWLRoEQ4dOoTDhw+LjqJRWHxqrkWLFggPD0d8fDxSUlLQqlUrhIaGoqSkRHQ00nJ+fn5o1qwZjxlSY6amplizZg08PT35S/ErYPFpCGtra0RHR2Pv3r3Yu3cvWrdujYiICO7dRyqxadMmHD16FJs3b+aN0mquf//+6NSpE+bOnSs6isbg7Qwa6tixYwgMDMS9e/ewcOFCDBo0iD+gSCmSk5PRt29fxMXFwdbWVnQcqoK8vDzY29tjz5493FigClh8GkyhUOCXX35BYGAgjIyMEBISws9i6I3k5+fD2dkZS5YswdChQ0XHoVcQFRWFkJAQJCcnw8jISHQctcbi0wLl5eWIjo7GnDlz0KJFC4SEhKBDhw6iY5GGKSsrQ79+/eDo6IilS5eKjkOvSKFQ4IMPPoCLiwunPV+CxadFSktLsWXLFnzxxRdwcnLCwoULYW9vLzoWaYigoCCcOnWKJy5osGvXrqFdu3acpn4JLm7RIoaGhpg4cSKysrLQvXt39OzZEx999BH++OMP0dFIze3evRuRkZH47rvvWHoarHnz5pg/fz4PrX0JFp8WMjExgb+/P7KystCqVSu4uLjA29sbN27cEB2N1NCFCxfg6emJ77//nscMaYHJkyfDwMAAoaGhoqOoLRafFjM1NcXcuXORmZmJmjVrwsHBAZ999hnu3r0rOhqpiaKiIgwZMgTBwcH8XFhL6OnpYcOGDZg/fz4uX74sOo5aYvHpgEaNGmHZsmVITU3F/fv3YWVlhYULF6KoqEh0NBJIoVBg/PjxcHV15TFDWqZ169b49NNPMXnyZHAZx7NYfDrEwsIC69atQ0JCAjIyMmBpaYlvvvkGMplMdDQS4KuvvsIff/yB0NBQ3gOqhaZPn447d+4gMjJSdBS1w1WdOuzcuXMICgrCuXPn8Pnnn2Ps2LFc2KAjjh49ihEjRuD06dPcfFqLpaSkoF+/fjh37hzMzMxEx1EbLD7CyZMnERgYiFu3bmHBggVwd3eHnh4nA7RVTk4O2rdvj4iICPznP/8RHYdUbObMmfjzzz+xY8cO0VHUBouPADz+vOfgwYMIDAyEQqFAcHAw+vTpwykwLSOTydC9e3cMHDgQs2bNEh2HqkFxcfGTTQkGDhwoOo5aYPHRUxQKBWJiYjB79mw0adIEISEh6Ny5s+hYpCReXl64desWYmJi+EuNDomLi8Po0aNx/vx51K1bV3Qc4Vh8VCm5XI7IyEjMmzcPDg4OCA4OhqOjo+hY9AY2b96MRYsW4cyZMzA1NRUdh6rZlClToFAosG7dOtFRhGPx0QvJZDKsX78eISEhcHNzw/z589GqVSvRsegVpaSkoE+fPtzKSoc9ePAA9vb2iIyMhJubm+g4QnEFA72QsbExpk2bhqysLDg4OKBTp06YNGkScnJyREejKsrPz4e7uzvCwsJYejqsbt26CAsLg6enJx49eiQ6jlAsPqqS2rVrIzAwEBcuXEDDhg3h6OiITz/9FLm5uaKj0QuUlZVh1KhRcHd35zFDhA8++ABOTk6YN2+e6ChCsfjolTRo0ACLFi1CWloapFIprK2tMW/ePBQUFIiORpX4/PPPUVJSgsWLF4uOQmpi5cqV2LJlC5KSkkRHEYbFR6+ladOmCA0NxZkzZ/DHH3+gVatWWL58OYqLi0VHo7/8+OOPiIiIwI4dO7gxAT3RpEkTLFu2DBMmTEBpaanoOEKw+OiNtGzZEhEREYiNjcWJEydgZWWF9evX6+xfKHVx8eJFeHp6Ijo6micu0DPGjBmDt956S2cPHOaqTlKqxMREBAUF4fLly5g/fz6GDx/OXWCqWVFRETp06ABfX19MnjxZdBxSU1euXIGTkxOOHz8Oa2tr0XGqFYuPVCI2NhazZs2CVCpFcHAwBgwYwBumq4FCocCIESNQq1YthIeH839zeqHVq1fju+++w7Fjx3TqF1QWH6mMQqHAnj17EBQUhLp16yIkJATdu3cXHUurffXVV9i2bRuOHz+OGjVqiI5Daq68vBxdu3bFqFGj4OPjIzpOtWHxkcqVlZUhKioKc+fOhZWVFYKDg+Hk5CQ6ltapOHEhISEB77zzjug4pCEyMjLQrVs3JCcno0WLFqLjVAvdGduSMPr6+hgzZgwyMzMxcOBAfPjhhxg6dCgyMzNFR9MaOTk5GDVqFCIiIlh69EpsbGzg5+f3ZEszXcDio2pjZGQELy8vZGVloX379ujWrRvGjx+PK1euiI6m0WQyGYYOHYqpU6eid+/eouOQBgoICMD169exfft20VGqBYuPql3NmjUREBCAixcvolmzZmjXrh38/Pxw+/Zt0dE0kr+/P8zMzDBz5kzRUUhDGRkZITw8XGd2Y2LxkTD16tXDggULkJ6eDolEAltbW8yePRv3798XHU1jbNmyBYcPH8aWLVt0alUeKZ+zszPGjBkDPz8/0VFUjn9TSDgzMzOsWLECKSkpuHnzJqysrPDll1/q/Ea6L3P27FlMnz4dMTExPGONlGL+/PlITEzE3r17RUdRKRYfqY23334b4eHhOHbsGJKTk9GqVSuEhYWhpKREdDS1k5+fjyFDhiA0NBR2dnai45CWqFmzJtavXw9vb2+t3n+XtzOQ2kpOTsbs2bNx4cIFfPHFFxg1ahT09fVFxxKurKwMAwYMgJ2dHZYvXy46DmkhT09PGBoaIiwsTHQUlWDxkdo7duwYZs2ahQcPHmDhwoUYOHCgTu9IMmfOHMTHx+PQoUPcfJpU4v79+7Czs0NUVBS6desmOo7SsfhIIygUCvz8888ICgqCsbExQkJC0LNnT9Gxqt2ePXvg4+ODpKQkmJmZiY5DWmz37t0ICAhAamqq1u0CxOIjjVJeXo7o6GjMmTMHb7/9NoKDg9GhQwfRsapFVlYWOnfujD179sDV1VV0HNIBQ4cOhaWlJRYtWiQ6ilKx+EgjlZaWYvPmzZg/fz6cnZ2xYMEC2Nvbi46lMkVFRXB1dYWPjw+8vLxExyEdcevWLTg6OmL//v147733RMdRGq7qJI1kaGgIT09PXLx4EV27dkXPnj0xduxY/PHHH6KjKZ1CocDEiRPRvn17TJkyRXQc0iHm5ub48ssvMWHCBMjlctFxlIbFRxqtRo0a+OSTT5CVlYV3330XLi4u8PHxwc2bN0VHU5oVK1YgKysLYWFhOr2oh8T473//i0aNGmnVCmJOdZJWycvLw+LFi7Fp0yZ4enoiICAADRo0EB3rtcXFxWHYsGE4ffo0N58mYS5fvgxnZ2ecPHkSVlZWouO8MY74SKs0atQIy5YtQ2pqKu7du4fWrVsjODgYRUVFoqO9suvXr2PkyJGIjIxk6ZFQ77zzDmbPng1PT0+Ul5eLjvPGWHyklSwsLLBu3TqcPHkS58+fR6tWrbBy5UrIZDLR0aqkpKQEHh4e8PHx4YkLpBamTZsGmUyGDRs2iI7yxjjVSTohNTUVs2fPxrlz5zBv3jx89NFHan3zt4+PD3JycvDDDz9w82lSG+fPn4ebmxvOnj0LCwsL0XFeG/9GkU5wdHTETz/9hO3bt2Pz5s1wcHDAzp071fLgzYiICBw8eBAREREsPVIrdnZ2mDp1Kry8vNTy705VccRHOkehUODAgQMIDAyERCJBcHAwevfurRYrJs+ePYvevXvjyJEjWn1fImmukpIStGvXDrNnz8aIESNEx3ktLD7SWQqFArt27cLs2bNhbm6OkJAQdOrUSVieu3fvwtnZGYsWLcLw4cOF5SB6mdOnT2PQoEH4/fff0ahRI9FxXhmLj3SeXC5HZGQk5s2bBwcHBwQHB8PR0bFaM5SVleH999+HjY0Nvvrqq2p9b6LX4e/vj7y8PERGRoqO8sr4AQLpPAMDA4wbNw4XL15E79690bdvX4waNQpZWVnVluGLL77Ao0eP8OWXX1bbexK9iYULF+LEiRPYv3+/6CivjMVH9BdjY2P4+voiKysLdnZ26NixIyZPnoycnByVvu9PP/2ETZs2ITo6GoaGhip9LyJlqVWrFtavX4/JkyejsLBQdJxXwqlOoue4e/culixZgg0bNmDcuHGYOXPmK3+ekVckw87kHGTeKkCBVA5TEwNYm5tiqJMFGtY2RnZ2Njp16oQff/wRHTt2VNF3QqQ648ePR61atbBq1SrRUaqMxUf0Ejdv3sTChQuxY8cOTJs2DZ988gnq1KnzwtekXruP0KPZiLuYCwCQyf+324WJgR4UALq82wCnNs7D1FEfwNvbW5XfApHK3Lt3D3Z2dvj+++/RuXNn0XGqhFOdRC/RtGlThIaGIjExEZcuXYKlpSW++uorFBcXV/r8rQmXMWJDAg5m3IZMXv5U6QGA9K/HDmfegbTzFNR5r391fBtEKlG/fn2sXLkSEydOhFQqFR2nSlh8RFXUsmVLRERE4PDhw4iPj4eVlRU2bNiA0tLSJ8/ZmnAZwT9noLi0DC+dS5HooVzPACG/ZGBrwmWVZidSJXd3d9jY2CA4OFh0lCrhVCfRa0pMTERgYCCuXr2K+fPno3Wn3hi1MRHFpWUAgPxfVkKWkwF5QS4k+oYwessK9f89HkaN337mWjUM9bFjkivaWNSr5u+CSDlu3LgBR0dHHD58GG3atBEd54VYfERv6PDhwwgMDMRdW3fIze1Q8RfqyuL3YfRWaxg1fhvFl1NR9uA29Os0RLPJGyAxMHrqGhIJ0MfWDGvHOFf/N0CkJOHh4Vi7di1OnTql1nvhsviIlCC3UIqOiw5BrvjftmfSnAyYWNgAAOT3b+P62gkAAPP/WwFjc8tnrmFsoIeTn/VAw9rG1ROaSMkUCgV69eqFfv36Yfr06aLjPBc/4yNSgl0p16Gvr//UYxWlBwCKcvnjf5HoQb925QfjSgDsTFHtPYNEqiSRSLB+/XosXrwY2dnZouM8F4uPSAkybxU8s3qzQnlJMfL3fQ0AMHUZBIPnFJ9UXo7Mm5p1IzDRP7377ruYNWsWJk2apLYnOLD4iJSgQCqv9PGyRw9wOyoQsuuZqO3YB/Xcxr3wOjfy7uH+/ftq+wODqCr8/PxQVFSE8PBw0VEqpb6fPhJpEFOTZ/8qyR/cwe0dcyC/ex2mrh6o7/Z/L71O4ok4tJg1ECUlJTAzM3vqH3Nz80r/XLduXbU4UomogoGBATZu3IhevXqhf//+eOutt0RHegoXtxApwdq4S/j60MWnpjtzVo9FWdFd6Js2Rk2r/21HVsu2O4zfav3MNUwM9OD/HytM7vYuiouLcfv2bdy6dQu3b99+8k9lfy4pKUGTJk2eW4x//zNLkqrTnDlzkJaWhpiYGLX6747FR6QEeUUydP4y9qniu7L4/Uqf27D/x6jdptczj7/uqs6KknxZUf69JF80gmRJkrLIZDK0bdsWCxYsgIeHh+g4T7D4iJRkUmQSDmbcfvmOLZWorvv4qlqSt2/fhkwmq1JJmpmZoV69eixJqtTJkyfh4eGBtLQ0NGhQ+cKu6sbiI1KS1Gv3MWJDwpOdW16FOu7c8veSfFlR/r0kX1aULEnd4+vri8LCQmzatEl0FAAsPiKl+t9enZXf2lCZGoZ6COpvgzGu76gumIr9syRfVJRSqbTKC3dYktqhqKgI9vb2WL9+PXr37i06DouPSNkel18mpPIXb1QtkQAmBvoI6m+t0aX3qoqLi3Hnzp1nPn98Xkm+bOEOS1Iz7N+/H15eXvj9999Ru3ZtoVlYfEQqcC7nPsKOZuPIhVxI8Pjm9AoV5/H9u3VjeLtZqtX0prqprCSfV5RVKcmKP7MkxRg7diwaNGiAFStWCM3B4iNSofwiGXam5CDzZiEKpKUwNTGEddM68GhnwT05layiJKuycKe4uLjKC3fq16/PklSS/Px82Nvb44cffoCrq6uwHCw+ItI5Uqm0ygt3/l6SLytKluTL7dixA/Pnz0dKSgqMjY2RVyTDzuQcZN4qQIFUDlMTA1ibm2Kok+p+OWTxERG9wD9L8kVF+c+SfFFR6mpJKhQKDBo0CM0du6Ck1b8RdzEXAJ66B7bi4wC31o3h3d0Sjs3rKTUDi4+ISEmkUmmVF+48evSoygt3tK0kV+1PxbJDl6BnaIwXFZCqFoCx+IiIBKisJJ9XlFUpyYo/q3tJqsMtPyw+IiI1V1GSVVm48/Dhwyov3GnQoEG1lmRlmzwUnPkRRecOojTvKqAoR93OI1Gv6+hnXqvMTR54OgMRkZozMTFBixYt0KJFi5c+9+8l+fdi/OOPP3Dq1KmnivLvJfmyolRGSYYezYZU/vTORiW3sqFnUhv6dRqhrODO878veRnCjmYrZVs/Fh8RkRZR15LMK5Ih7mLuM5s6NPrgUwDAnV0LUfyC4lMogCMXcpFfJHvj1Z4sPiIiHfUqJSmTySpd3VpRkn8vzn+WpJmZGe6aO0GOt/Em559LAOxMycHkbu++9jUAFh8REVWBsbHxK5XkPxfuRF02Qpn89UsPeLwDUubNwje6BsDiIyIiJTM2Nkbz5s3RvHnzJ4+d2HIG2ZnPn8qsqgJp6Rtf483ql4iIqApMTZQzzjI1MXzja3DER0REKmdtbgpjg1tP7dACAIWpv0J2LR0lty8BAB5lJUD+4A5qWrmiplXHp55rYqAH66Z13jgLR3xERKRyHk4WlT4uu5aOh2mHUVbweOuy0jt/4mHaYZTc/uOZ5yoAeLSr/DqvgjewExFRtZgUmYSDGbdfeE7l80gkQB9bM6Xcx8cRHxERVQsfN0uYGOi/1mtNDPTh7WaplBwsPiIiqhaOzeshqL81ahi+WvU83qvTWmmHNnNxCxERVZuKjaaDf86EVF72wmlPns5ARERa41zOfYQdzcaRC7mQ4PHN6RUqzuP7d+vG8HazVNpIrwKLj4iIhMkvkmFnSg4ybxaiQFoKUxNDWDetA492PIGdiIhIKbi4hYiIdAqLj4iIdAqLj4iIdAqLj4iIdAqLj4iIdAqLj4iIdAqLj4iIdAqLj4iIdAqLj4iIdMr/A2ICSgmCkVFGAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "g = nx.Graph()\n", - "nx.add_path(g, [0,1])\n", - "nx.add_path(g, [1,2])\n", - "nx.add_path(g, [2,3])\n", - "nx.add_path(g, [3,4])\n", - "nx.add_path(g, [0,4])\n", - "nx.add_path(g, [0,2])\n", - "nx.draw(g,with_labels=True, font_weight='bold')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如上如,我们得到一个由5个节点和6条边构成的图结构。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 搭建QAOA量子线路\n", - "\n", - "### 线路搭建\n", - "\n", - "这里我们采用量子绝热近似算法,经过演化将量子态从$X^{\\otimes n}$的本征态演化到图多应哈密的量的基态。\n", - "\n", - "搭建图对应哈密顿量的含时演化线路:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def build_hc(g,para):\n", - " hc = Circuit()\n", - " for i in g.edges:\n", - " hc += ZZ(para).on(i)\n", - " return hc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "搭建$X^{\\otimes n}$含时演化的量子线路:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "def build_hb(g, para):\n", - " hc = Circuit()\n", - " for i in g.nodes:\n", - " hc += RX(para).on(i)\n", - " return hc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了使得最后优化的结果足够准确,我们需要将量子线路重复多次,因此我们通过如下函数搭建多层的训练网络:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "def build_ansatz(g, p):\n", - " c = Circuit()\n", - " for i in range(p):\n", - " c += build_hc(g,f'g{i}')\n", - " c += build_hb(g,f'b{i}')\n", - " return c" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "构建图对应的哈密顿量:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "def build_ham(g):\n", - " hc = QubitOperator()\n", - " for i in g.edges:\n", - " hc += QubitOperator(f'Z{i[0]} Z{i[1]}')\n", - " return hc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 生成完整的量子线路和图所对应的哈密顿量\n", - "\n", - "这里我们选择`p = 4`,表示选用4曾的QAOA量子线路,`ansatz`是求解该问题的量子线路,`init_state_circ`是将量子态制备到均匀叠加态上的量子线路。" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "p = 4\n", - "ham = Hamiltonian(build_ham(g))\n", - "ansatz = build_ansatz(g, p)\n", - "init_state_circ = UN(H, g.nodes)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 搭建待训练量子神经网络\n", - "\n", - "由于该问题不需要编码层量子线路,我们这里使用`MQAnsatzOnlyLayer`作为待训练的量子神经网络,并采用`Adam`优化器。" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "import mindspore as ms\n", - "ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target=\"CPU\")\n", - "\n", - "circ = init_state_circ + ansatz\n", - "sim = Simulator('projectq', circ.n_qubits)\n", - "grad_ops = sim.get_expectation_with_grad(ham, circ)\n", - "net = MQAnsatzOnlyLayer(grad_ops)\n", - "opti = nn.Adam(net.trainable_params(), learning_rate=0.05)\n", - "train_net = nn.TrainOneStepCell(net, opti)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 训练并展示结果" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "train step: 0 , cut: [3.0000384]\n", - "train step: 10 , cut: [3.0740116]\n", - "train step: 20 , cut: [3.291378]\n", - "train step: 30 , cut: [3.5130358]\n", - "train step: 40 , cut: [3.7116692]\n", - "train step: 50 , cut: [3.9258695]\n", - "train step: 60 , cut: [4.06885]\n", - "train step: 70 , cut: [4.1482286]\n", - "train step: 80 , cut: [4.2075667]\n", - "train step: 90 , cut: [4.2216697]\n", - "train step: 100 , cut: [4.219716]\n", - "train step: 110 , cut: [4.2484426]\n", - "train step: 120 , cut: [4.310321]\n", - "train step: 130 , cut: [4.3869066]\n", - "train step: 140 , cut: [4.468603]\n", - "train step: 150 , cut: [4.5552807]\n", - "train step: 160 , cut: [4.6389103]\n", - "train step: 170 , cut: [4.7030687]\n", - "train step: 180 , cut: [4.7325706]\n", - "train step: 190 , cut: [4.720634]\n", - "train step: 200 , cut: [4.6801863]\n", - "train step: 210 , cut: [4.641413]\n", - "train step: 220 , cut: [4.630719]\n", - "train step: 230 , cut: [4.6555276]\n", - "train step: 240 , cut: [4.7030406]\n", - "train step: 250 , cut: [4.7517853]\n", - "train step: 260 , cut: [4.7854385]\n", - "train step: 270 , cut: [4.8003826]\n", - "train step: 280 , cut: [4.8025503]\n", - "train step: 290 , cut: [4.800086]\n", - "train step: 300 , cut: [4.7993703]\n", - "train step: 310 , cut: [4.8025393]\n", - "train step: 320 , cut: [4.8066764]\n", - "train step: 330 , cut: [4.8070974]\n", - "train step: 340 , cut: [4.8019547]\n", - "train step: 350 , cut: [4.7941465]\n", - "train step: 360 , cut: [4.7895412]\n", - "train step: 370 , cut: [4.7927914]\n", - "train step: 380 , cut: [4.8037663]\n", - "train step: 390 , cut: [4.817476]\n", - "train step: 400 , cut: [4.8274126]\n", - "train step: 410 , cut: [4.830078]\n", - "train step: 420 , cut: [4.8275557]\n", - "train step: 430 , cut: [4.82526]\n", - "train step: 440 , cut: [4.826516]\n", - "train step: 450 , cut: [4.829952]\n", - "train step: 460 , cut: [4.8320074]\n", - "train step: 470 , cut: [4.831057]\n", - "train step: 480 , cut: [4.8289337]\n", - "train step: 490 , cut: [4.828836]\n", - "train step: 500 , cut: [4.832123]\n", - "train step: 510 , cut: [4.8371778]\n", - "train step: 520 , cut: [4.8411226]\n", - "train step: 530 , cut: [4.842478]\n", - "train step: 540 , cut: [4.8421907]\n", - "train step: 550 , cut: [4.8421745]\n", - "train step: 560 , cut: [4.8431606]\n", - "train step: 570 , cut: [4.844285]\n", - "train step: 580 , cut: [4.8445735]\n", - "train step: 590 , cut: [4.844259]\n" - ] - } - ], - "source": [ - "for i in range(600):\n", - " if i%10 == 0:\n", - " print(\"train step:\", i, \", cut:\", (len(g.edges)-train_net())/2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "根据上面的训练结果我们发现,该问题哈密顿量的基态能量对应的边切割数趋近与5。\n", - "\n", - "### 量子态展示\n", - "\n", - "前面我们通过训练得到了量子线路中参数的最优值,下面,我们通过`StateEvolution`类的`final_state`来输出量子线路在最优参数时的量子态,其中`ket`参数表示是否将最终量子态表示为右矢形式。" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(0.02090837256298316-0.013896610875073252j)¦00000⟩\n", - "(0.012865120061967678+0.0036417563002864367j)¦00001⟩\n", - "(0.024461651769855132+0.016082588541572487j)¦00010⟩\n", - "(0.02665900288209095+0.028667026525713252j)¦00011⟩\n", - "(0.012865120061967661+0.003641756300286507j)¦00100⟩\n", - "(-0.13261220694607562+0.12131816696926725j)¦00101⟩\n", - "(0.02665900288209106+0.028667026525713284j)¦00110⟩\n", - "(0.0077605605960298545+0.013307014150508825j)¦00111⟩\n", - "(-0.005108830641820924-0.016855682940484437j)¦01000⟩\n", - "(0.2893552119928307+0.36375187206353543j)¦01001⟩\n", - "(-0.05369567695690487+0.10854655574235807j)¦01010⟩\n", - "(0.2893552119928306+0.36375187206353565j)¦01011⟩\n", - "(0.012083509070472419+0.010521810302265303j)¦01100⟩\n", - "(-0.05369567695690495+0.10854655574235811j)¦01101⟩\n", - "(0.012083509070472474+0.010521810302265332j)¦01110⟩\n", - "(-0.005108830641820898-0.016855682940484465j)¦01111⟩\n", - "(-0.005108830641820921-0.016855682940484402j)¦10000⟩\n", - "(0.012083509070472466+0.010521810302265334j)¦10001⟩\n", - "(-0.05369567695690492+0.1085465557423581j)¦10010⟩\n", - "(0.012083509070472504+0.01052181030226525j)¦10011⟩\n", - "(0.2893552119928306+0.3637518720635356j)¦10100⟩\n", - "(-0.053695676956904935+0.10854655574235816j)¦10101⟩\n", - "(0.2893552119928306+0.3637518720635357j)¦10110⟩\n", - "(-0.005108830641820899-0.016855682940484448j)¦10111⟩\n", - "(0.007760560596029854+0.01330701415050879j)¦11000⟩\n", - "(0.02665900288209102+0.028667026525713263j)¦11001⟩\n", - "(-0.13261220694607564+0.1213181669692672j)¦11010⟩\n", - "(0.012865120061967626+0.0036417563002865416j)¦11011⟩\n", - "(0.02665900288209097+0.028667026525713308j)¦11100⟩\n", - "(0.02446165176985506+0.01608258854157249j)¦11101⟩\n", - "(0.012865120061967642+0.003641756300286466j)¦11110⟩\n", - "(0.020908372562983193-0.013896610875073252j)¦11111⟩\n" - ] - } - ], - "source": [ - "pr = dict(zip(ansatz.params_name, net.weight.asnumpy()))\n", - "print(circ.get_qs(pr=pr, ket=True))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 概率图\n", - "\n", - "我们画出最终量子态在计算基矢下的概率分布" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "def show_amp(state):\n", - " amp = np.abs(state)**2\n", - " n_qubits = int(np.log2(len(amp)))\n", - " labels = [bin(i)[2:].zfill(n_qubits) for i in range(len(amp))]\n", - " plt.bar(labels, amp)\n", - " plt.xticks(rotation=45)\n", - " plt.show()\n", - "state = circ.get_qs(pr=pr)\n", - "show_amp(state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "根据概率分布图我们发现,该Max-Cut问题具有四个简并解,每个解对应的概率大概为25%。\n", - "\n", - "## 总结\n", - "\n", - "这里我们通过量子近似优化算法来解决了Max-Cut问题,并得到了案例中的图对应的最大切割方案。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 参考文献\n", - "\n", - "[1] Edward Farhi, Jeffrey Goldstone, and Sam Gutmann. [A Quantum Approximate Optimization Algorithm](https://arxiv.org/pdf/1411.4028.pdf)" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "5545a57ef4a1ac7dca167cae0bf17fda051fcd0639773c034bd7ce77ffd97d30" - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/tutorials/6.qnn_for_nlp.ipynb b/tutorials/6.qnn_for_nlp.ipynb deleted file mode 100644 index 39ae980ca..000000000 --- a/tutorials/6.qnn_for_nlp.ipynb +++ /dev/null @@ -1,903 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 量子神经网络在自然语言处理中的应用\n", - "\n", - "\n", - "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", - "\n", - "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/qnn_for_nlp.ipynb)\n", - "\n", - "## 概述\n", - "\n", - "在自然语言处理过程中,词嵌入(Word embedding)是其中的重要步骤,它是一个将高维度空间的词向量嵌入到一个维数更低的连续向量空间的过程。当给予神经网络的语料信息不断增加时,网络的训练过程将越来越困难。利用量子力学的态叠加和纠缠等特性,我们可以利用量子神经网络来处理这些经典语料信息,加入其训练过程,并提高收敛精度。下面,我们将简单地搭建一个量子经典混合神经网络来完成一个词嵌入任务。\n", - "\n", - "\n", - "## 环境准备\n", - "\n", - "导入本教程所依赖模块\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import time\n", - "from mindquantum.core import QubitOperator\n", - "import mindspore.ops as ops\n", - "import mindspore.dataset as ds\n", - "from mindspore import nn\n", - "from mindspore.train.callback import LossMonitor\n", - "from mindspore import Model\n", - "from mindquantum.framework import MQLayer\n", - "from mindquantum import Hamiltonian, Circuit, RX, RY, X, H, UN" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "本教程实现的是一个[CBOW模型](https://blog.csdn.net/u010665216/article/details/78724856),即利用某个词所处的环境来预测该词。例如对于“I love natural language processing”这句话,我们可以将其切分为5个词,\\[\"I\", \"love\", \"natural\", \"language\", \"processing”\\],在所选窗口为2时,我们要处理的问题是利用\\[\"I\", \"love\", \"language\", \"processing\"\\]来预测出目标词汇\"natural\"。这里我们以窗口为2为例,搭建如下的量子神经网络,来完成词嵌入任务。\n", - "\n", - "![quantum word embedding](https://gitee.com/mindspore/docs/raw/master/docs/mindquantum/docs/source_zh_cn/images/qcbow.png)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里,编码线路会将\"I\"、\"love\"、\"language\"和\"processing\"的编码信息编码到量子线路中,待训练的量子线路由四个Ansatz线路构成,最后我们在量子线路末端对量子比特做$\\text{Z}$基矢上的测量,具体所需测量的比特的个数由所需嵌入空间的维数确定。\n", - "\n", - "## 数据预处理\n", - "\n", - "我们对所需要处理的语句进行处理,生成关于该句子的词典,并根据窗口大小来生成样本点。\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "def GenerateWordDictAndSample(corpus, window=2):\n", - " all_words = corpus.split()\n", - " word_set = list(set(all_words))\n", - " word_set.sort()\n", - " word_dict = {w: i for i,w in enumerate(word_set)}\n", - " sampling = []\n", - " for index, word in enumerate(all_words[window:-window]):\n", - " around = []\n", - " for i in range(index, index + 2*window + 1):\n", - " if i != index + window:\n", - " around.append(all_words[i])\n", - " sampling.append([around,all_words[index + window]])\n", - " return word_dict, sampling" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'I': 0, 'language': 1, 'love': 2, 'natural': 3, 'processing': 4}\n", - "word dict size: 5\n", - "samples: [[['I', 'love', 'language', 'processing'], 'natural']]\n", - "number of samples: 1\n" - ] - } - ], - "source": [ - "word_dict, sample = GenerateWordDictAndSample(\"I love natural language processing\")\n", - "print(word_dict)\n", - "print('word dict size: ', len(word_dict))\n", - "print('samples: ', sample)\n", - "print('number of samples: ', len(sample))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "根据如上信息,我们得到该句子的词典大小为5,能够产生一个样本点。\n", - "\n", - "## 编码线路\n", - "\n", - "为了简单起见,我们使用的编码线路由$\\text{RX}$旋转门构成,结构如下。\n", - "\n", - "![encoder circuit](https://gitee.com/mindspore/docs/raw/master/docs/mindquantum/docs/source_zh_cn/images/encoder.png)\n", - "\n", - "我们对每个量子门都作用一个$\\text{RX}$旋转门。" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "def GenerateEncoderCircuit(n_qubits, prefix=''):\n", - " if len(prefix) != 0 and prefix[-1] != '_':\n", - " prefix += '_'\n", - " circ = Circuit()\n", - " for i in range(n_qubits):\n", - " circ += RX(prefix + str(i)).on(i)\n", - " return circ" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──RX(e_0)──\n",
-       "               \n",
-       "q1: ──RX(e_1)──\n",
-       "               \n",
-       "q2: ──RX(e_2)──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──RX(e_0)──\n", - " \n", - "q1: ──RX(e_1)──\n", - " \n", - "q2: ──RX(e_2)──" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "GenerateEncoderCircuit(3,prefix='e')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们通常用$\\left|0\\right>$和$\\left|1\\right>$来标记二能级量子比特的两个状态,由态叠加原理,量子比特还可以处于这两个状态的叠加态:\n", - "\n", - "$$\\left|\\psi\\right>=\\alpha\\left|0\\right>+\\beta\\left|1\\right>$$\n", - "\n", - "对于$n$比特的量子态,其将处于$2^n$维的希尔伯特空间中。对于上面由5个词构成的词典,我们只需要$\\lceil \\log_2 5 \\rceil=3$个量子比特即可完成编码,这也体现出量子计算的优越性。\n", - "\n", - "例如对于上面词典中的\"love\",其对应的标签为2,2的二进制表示为`010`,我们只需将编码线路中的`e_0`、`e_1`和`e_2`分别设为$0$、$\\pi$和$0$即可。下面来验证一下。" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Label is: 2\n", - "Binary label is: 010\n", - "Parameters of encoder is: \n", - " [0. 3.14159 0. ]\n", - "Encoder circuit is: \n", - " q0: ──RX(e_0)──\n", - " \n", - "q1: ──RX(e_1)──\n", - " \n", - "q2: ──RX(e_2)──\n", - "Encoder parameter names are: \n", - " ['e_0', 'e_1', 'e_2']\n", - "Amplitude of quantum state is: \n", - " [0. 0. 1. 0. 0. 0. 0. 0.]\n", - "Label in quantum state is: 2\n" - ] - } - ], - "source": [ - "from mindquantum.simulator import Simulator\n", - "from mindspore import context\n", - "from mindspore import Tensor\n", - "\n", - "n_qubits = 3 # number of qubits of this quantum circuit\n", - "label = 2 # label need to encode\n", - "label_bin = bin(label)[-1:1:-1].ljust(n_qubits,'0') # binary form of label\n", - "label_array = np.array([int(i)*np.pi for i in label_bin]).astype(np.float32) # parameter value of encoder\n", - "encoder = GenerateEncoderCircuit(n_qubits, prefix='e') # encoder circuit\n", - "encoder_params_names = encoder.params_name # parameter names of encoder\n", - "\n", - "print(\"Label is: \", label)\n", - "print(\"Binary label is: \", label_bin)\n", - "print(\"Parameters of encoder is: \\n\", np.round(label_array, 5))\n", - "print(\"Encoder circuit is: \\n\", encoder)\n", - "print(\"Encoder parameter names are: \\n\", encoder_params_names)\n", - "\n", - "# quantum state evolution operator\n", - "state = encoder.get_qs(pr=dict(zip(encoder_params_names, label_array)))\n", - "amp = np.round(np.abs(state)**2, 3)\n", - "\n", - "print(\"Amplitude of quantum state is: \\n\", amp)\n", - "print(\"Label in quantum state is: \", np.argmax(amp))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过上面的验证,我们发现,对于标签为2的数据,最后得到量子态的振幅最大的位置也是2,因此得到的量子态正是对输入标签的编码。我们将对数据编码生成参数数值的过程总结成如下函数。" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "def GenerateTrainData(sample, word_dict):\n", - " n_qubits = np.int(np.ceil(np.log2(1 + max(word_dict.values()))))\n", - " data_x = []\n", - " data_y = []\n", - " for around, center in sample:\n", - " data_x.append([])\n", - " for word in around:\n", - " label = word_dict[word]\n", - " label_bin = bin(label)[-1:1:-1].ljust(n_qubits,'0')\n", - " label_array = [int(i)*np.pi for i in label_bin]\n", - " data_x[-1].extend(label_array)\n", - " data_y.append(word_dict[center])\n", - " return np.array(data_x).astype(np.float32), np.array(data_y).astype(np.int32)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([[0. , 0. , 0. , 0. , 3.1415927, 0. ,\n", - " 3.1415927, 0. , 0. , 0. , 0. , 3.1415927]],\n", - " dtype=float32),\n", - " array([3], dtype=int32))" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "GenerateTrainData(sample, word_dict)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "根据上面的结果,我们将4个输入的词编码的信息合并为一个更长向量,便于后续神经网络调用。\n", - "\n", - "## Ansatz线路\n", - "\n", - "Ansatz线路的选择多种多样,我们选择如下的量子线路作为Ansatz线路,它的一个单元由一层$\\text{RY}$门和一层$\\text{CNOT}$门构成,对此单元重复$p$次构成整个Ansatz线路。\n", - "\n", - "![ansatz circuit](https://gitee.com/mindspore/docs/raw/master/docs/mindquantum/docs/source_zh_cn/images/ansatz.png)\n", - "\n", - "定义如下函数生成Ansatz线路。" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "def GenerateAnsatzCircuit(n_qubits, layers, prefix=''):\n", - " if len(prefix) != 0 and prefix[-1] != '_':\n", - " prefix += '_'\n", - " circ = Circuit()\n", - " for l in range(layers):\n", - " for i in range(n_qubits):\n", - " circ += RY(prefix + str(l) + '_' + str(i)).on(i)\n", - " for i in range(l % 2, n_qubits, 2):\n", - " if i < n_qubits and i + 1 < n_qubits:\n", - " circ += X.on(i + 1, i)\n", - " return circ" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──RY(a_0_0)────────●────────RY(a_1_0)───────\n",
-       "\n",
-       "q1: ──RY(a_0_1)────────X────────RY(a_1_1)────●──\n",
-       "\n",
-       "q2: ──RY(a_0_2)────────●────────RY(a_1_2)────X──\n",
-       "\n",
-       "q3: ──RY(a_0_3)────────X────────RY(a_1_3)────●──\n",
-       "\n",
-       "q4: ──RY(a_0_4)────RY(a_1_4)─────────────────X──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──RY(a_0_0)────────●────────RY(a_1_0)───────\n", - " │ \n", - "q1: ──RY(a_0_1)────────X────────RY(a_1_1)────●──\n", - " │ \n", - "q2: ──RY(a_0_2)────────●────────RY(a_1_2)────X──\n", - " │ \n", - "q3: ──RY(a_0_3)────────X────────RY(a_1_3)────●──\n", - " │ \n", - "q4: ──RY(a_0_4)────RY(a_1_4)─────────────────X──" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "GenerateAnsatzCircuit(5, 2, 'a')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 测量\n", - "\n", - "我们把对不同比特位上的测量结果作为降维后的数据。具体过程与比特编码类似,例如当我们想将词向量降维为5维向量时,对于第3维的数据可以如下产生:\n", - "\n", - "- 3对应的二进制为`00011`。\n", - "- 测量量子线路末态对$Z_0Z_1$哈密顿量的期望值。\n", - "\n", - "下面函数将给出产生各个维度上数据所需的哈密顿量(hams),其中`n_qubits`表示线路的比特数,`dims`表示词嵌入的维度:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "def GenerateEmbeddingHamiltonian(dims, n_qubits):\n", - " hams = []\n", - " for i in range(dims):\n", - " s = ''\n", - " for j, k in enumerate(bin(i + 1)[-1:1:-1]):\n", - " if k == '1':\n", - " s = s + 'Z' + str(j) + ' '\n", - " hams.append(Hamiltonian(QubitOperator(s)))\n", - " return hams" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[1.0 [Z0] , 1.0 [Z1] , 1.0 [Z0 Z1] , 1.0 [Z2] , 1.0 [Z0 Z2] ]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "GenerateEmbeddingHamiltonian(5, 5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 量子版词向量嵌入层\n", - "\n", - "量子版词向量嵌入层结合前面的编码量子线路和待训练量子线路,以及测量哈密顿量,将`num_embedding`个词嵌入为`embedding_dim`维的词向量。这里我们还在量子线路的最开始加上了Hadamard门,将初态制备为均匀叠加态,用以提高量子神经网络的表达能力。\n", - "\n", - "下面,我们定义量子嵌入层,它将返回一个量子线路模拟算子。" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "def QEmbedding(num_embedding, embedding_dim, window, layers, n_threads):\n", - " n_qubits = int(np.ceil(np.log2(num_embedding)))\n", - " hams = GenerateEmbeddingHamiltonian(embedding_dim, n_qubits)\n", - " circ = Circuit()\n", - " circ = UN(H, n_qubits)\n", - " encoder_param_name = []\n", - " ansatz_param_name = []\n", - " for w in range(2 * window):\n", - " encoder = GenerateEncoderCircuit(n_qubits, 'Encoder_' + str(w))\n", - " ansatz = GenerateAnsatzCircuit(n_qubits, layers, 'Ansatz_' + str(w))\n", - " encoder.no_grad()\n", - " circ += encoder\n", - " circ += ansatz\n", - " encoder_param_name.extend(encoder.params_name)\n", - " ansatz_param_name.extend(ansatz.params_name)\n", - " grad_ops = Simulator('projectq', circ.n_qubits).get_expectation_with_grad(hams,\n", - " circ,\n", - " None,\n", - " encoder_param_name,\n", - " ansatz_param_name,\n", - " n_threads)\n", - " return MQLayer(grad_ops)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "整个训练模型跟经典网络类似,由一个嵌入层和两个全连通层构成,然而此处的嵌入层是由量子神经网络构成。下面定义量子神经网络CBOW。" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "class CBOW(nn.Cell):\n", - " def __init__(self, num_embedding, embedding_dim, window, layers, n_threads,\n", - " hidden_dim):\n", - " super(CBOW, self).__init__()\n", - " self.embedding = QEmbedding(num_embedding, embedding_dim, window,\n", - " layers, n_threads)\n", - " self.dense1 = nn.Dense(embedding_dim, hidden_dim)\n", - " self.dense2 = nn.Dense(hidden_dim, num_embedding)\n", - " self.relu = ops.ReLU()\n", - "\n", - " def construct(self, x):\n", - " embed = self.embedding(x)\n", - " out = self.dense1(embed)\n", - " out = self.relu(out)\n", - " out = self.dense2(out)\n", - " return out" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "下面我们对一个稍长的句子来进行训练。首先定义`LossMonitorWithCollection`用于监督收敛过程,并搜集收敛过程的损失。" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "class LossMonitorWithCollection(LossMonitor):\n", - " def __init__(self, per_print_times=1):\n", - " super(LossMonitorWithCollection, self).__init__(per_print_times)\n", - " self.loss = []\n", - " \n", - " def begin(self, run_context):\n", - " self.begin_time = time.time()\n", - " \n", - " def end(self, run_context):\n", - " self.end_time = time.time()\n", - " print('Total time used: {}'.format(self.end_time - self.begin_time))\n", - " \n", - " def epoch_begin(self, run_context):\n", - " self.epoch_begin_time = time.time()\n", - " \n", - " def epoch_end(self, run_context):\n", - " cb_params = run_context.original_args()\n", - " self.epoch_end_time = time.time()\n", - " if self._per_print_times != 0 and cb_params.cur_step_num % self._per_print_times == 0:\n", - " print('')\n", - " \n", - " def step_end(self, run_context):\n", - " cb_params = run_context.original_args()\n", - " loss = cb_params.net_outputs\n", - "\n", - " if isinstance(loss, (tuple, list)):\n", - " if isinstance(loss[0], Tensor) and isinstance(loss[0].asnumpy(), np.ndarray):\n", - " loss = loss[0]\n", - "\n", - " if isinstance(loss, Tensor) and isinstance(loss.asnumpy(), np.ndarray):\n", - " loss = np.mean(loss.asnumpy())\n", - "\n", - " cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num + 1\n", - "\n", - " if isinstance(loss, float) and (np.isnan(loss) or np.isinf(loss)):\n", - " raise ValueError(\"epoch: {} step: {}. Invalid loss, terminating training.\".format(\n", - " cb_params.cur_epoch_num, cur_step_in_epoch))\n", - " self.loss.append(loss)\n", - " if self._per_print_times != 0 and cb_params.cur_step_num % self._per_print_times == 0:\n", - " print(\"\\repoch: %+3s step: %+3s time: %5.5s, loss is %5.5s\" % (cb_params.cur_epoch_num, cur_step_in_epoch, time.time() - self.epoch_begin_time, loss), flush=True, end='')\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "接下来,利用量子版本的`CBOW`来对一个长句进行词嵌入。运行之前请在终端运行`export OMP_NUM_THREADS=4`,将量子模拟器的线程数设置为4个,当所需模拟的量子系统比特数较多时,可设置更多的线程数来提高模拟效率。" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "epoch: 25 step: 20 time: 0.336, loss is 3.154\n", - "epoch: 50 step: 20 time: 0.449, loss is 2.945\n", - "epoch: 75 step: 20 time: 0.325, loss is 0.226\n", - "epoch: 100 step: 20 time: 0.370, loss is 0.016\n", - "epoch: 125 step: 20 time: 0.377, loss is 0.002\n", - "epoch: 150 step: 20 time: 0.399, loss is 0.006\n", - "epoch: 175 step: 20 time: 0.370, loss is 0.166\n", - "epoch: 200 step: 20 time: 0.345, loss is 0.139\n", - "epoch: 225 step: 20 time: 0.350, loss is 3.355\n", - "epoch: 250 step: 20 time: 0.334, loss is 1.059\n", - "epoch: 275 step: 20 time: 0.339, loss is 0.035\n", - "epoch: 300 step: 20 time: 0.334, loss is 0.024\n", - "epoch: 325 step: 20 time: 0.344, loss is 0.010\n", - "epoch: 350 step: 20 time: 0.344, loss is 0.009\n", - "Total time used: 126.26282787322998\n" - ] - } - ], - "source": [ - "import mindspore as ms\n", - "from mindspore import context\n", - "from mindspore import Tensor\n", - "context.set_context(mode=context.PYNATIVE_MODE, device_target=\"CPU\")\n", - "corpus = \"\"\"We are about to study the idea of a computational process.\n", - "Computational processes are abstract beings that inhabit computers.\n", - "As they evolve, processes manipulate other abstract things called data.\n", - "The evolution of a process is directed by a pattern of rules\n", - "called a program. People create programs to direct processes. In effect,\n", - "we conjure the spirits of the computer with our spells.\"\"\"\n", - "\n", - "ms.set_seed(42)\n", - "window_size = 2\n", - "embedding_dim = 10\n", - "hidden_dim = 128\n", - "word_dict, sample = GenerateWordDictAndSample(corpus, window=window_size)\n", - "train_x,train_y = GenerateTrainData(sample, word_dict)\n", - "\n", - "train_loader = ds.NumpySlicesDataset({\n", - " \"around\": train_x,\n", - " \"center\": train_y\n", - "},shuffle=False).batch(3)\n", - "net = CBOW(len(word_dict), embedding_dim, window_size, 3, 4, hidden_dim)\n", - "net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", - "net_opt = nn.Momentum(net.trainable_params(), 0.01, 0.9)\n", - "loss_monitor = LossMonitorWithCollection(500)\n", - "model = Model(net, net_loss, net_opt)\n", - "model.train(350, train_loader, callbacks=[loss_monitor], dataset_sink_mode=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "打印收敛过程中的损失函数值:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "plt.plot(loss_monitor.loss,'.')\n", - "plt.xlabel('Steps')\n", - "plt.ylabel('Loss')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过如下方法打印量子嵌入层的量子线路中的参数:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 1.5175925e+00, -5.5282825e-01, 1.9824509e-01, -2.3327057e+00,\n", - " 8.4526891e-01, -1.3019586e+00, 9.3813318e-01, -1.0318477e-01,\n", - " 4.4351882e-01, 1.8607093e+00, 6.0036021e-01, -3.0638957e-01,\n", - " 9.3188483e-01, 6.0410827e-01, -1.8905094e-01, 6.5970606e-01,\n", - " -1.2129487e+00, -3.1650740e-01, -2.5501034e+00, 3.6324959e-02,\n", - " 4.0066850e-01, 7.5752664e-01, -5.6982380e-01, -5.6846058e-01,\n", - " -9.0591955e-01, 3.3477244e-01, -6.1832809e-01, 2.1618415e-01,\n", - " 1.0225463e-01, 4.0966314e-01, -9.0604734e-01, 1.3528558e+00,\n", - " -5.3387892e-01, -3.2625124e-02, 6.8196923e-02, 4.1799426e-01,\n", - " 2.6094767e-01, -3.3765252e+00, -1.9021339e+00, -1.1502613e+00,\n", - " -2.0344164e+00, 8.0160522e-01, -2.8717926e-01, 3.3720109e-01,\n", - " -2.1616800e+00, 1.1822585e+00, -7.0481867e-01, 4.0014455e-01,\n", - " -2.8856799e-01, 8.4199363e-01, -5.8137196e-01, -1.9842222e+00,\n", - " 1.7555025e-01, 4.1823694e-01, -3.1270559e+00, 2.6714945e+00,\n", - " 2.3251233e+00, 3.0707479e-01, -5.3547442e-01, 3.0258337e-01,\n", - " -1.5764916e+00, 3.0099937e-01, -2.9257689e+00, -1.1786047e+00,\n", - " -5.7270378e-01, 2.0587114e-03, -1.5863895e+00, -2.1442556e+00,\n", - " -1.7923084e-01, -1.2772868e+00, 4.1606693e-04, -9.2881303e-03],\n", - " dtype=float32)" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "net.embedding.weight.asnumpy()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 经典版词向量嵌入层\n", - "\n", - "这里我们利用经典的词向量嵌入层来搭建一个经典的CBOW神经网络,并与量子版本进行对比。\n", - "\n", - "首先,搭建经典的CBOW神经网络,其中的参数跟量子版本的类似。" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "class CBOWClassical(nn.Cell):\n", - " def __init__(self, num_embedding, embedding_dim, window, hidden_dim):\n", - " super(CBOWClassical, self).__init__()\n", - " self.dim = 2 * window * embedding_dim\n", - " self.embedding = nn.Embedding(num_embedding, embedding_dim, True)\n", - " self.dense1 = nn.Dense(self.dim, hidden_dim)\n", - " self.dense2 = nn.Dense(hidden_dim, num_embedding)\n", - " self.relu = ops.ReLU()\n", - " self.reshape = ops.Reshape()\n", - "\n", - " def construct(self, x):\n", - " embed = self.embedding(x)\n", - " embed = self.reshape(embed, (-1, self.dim))\n", - " out = self.dense1(embed)\n", - " out = self.relu(out)\n", - " out = self.dense2(out)\n", - " return out" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "生成适用于经典CBOW神经网络的数据集。" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "train_x shape: (58, 4)\n", - "train_y shape: (58,)\n" - ] - } - ], - "source": [ - "train_x = []\n", - "train_y = []\n", - "for i in sample:\n", - " around, center = i\n", - " train_y.append(word_dict[center])\n", - " train_x.append([])\n", - " for j in around:\n", - " train_x[-1].append(word_dict[j])\n", - "train_x = np.array(train_x).astype(np.int32)\n", - "train_y = np.array(train_y).astype(np.int32)\n", - "print(\"train_x shape: \", train_x.shape)\n", - "print(\"train_y shape: \", train_y.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们对经典CBOW网络进行训练。" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "epoch: 25 step: 20 time: 0.031, loss is 3.155\n", - "epoch: 50 step: 20 time: 0.033, loss is 3.027\n", - "epoch: 75 step: 20 time: 0.033, loss is 3.010\n", - "epoch: 100 step: 20 time: 0.033, loss is 2.955\n", - "epoch: 125 step: 20 time: 0.032, loss is 0.630\n", - "epoch: 150 step: 20 time: 0.034, loss is 0.059\n", - "epoch: 175 step: 20 time: 0.033, loss is 0.008\n", - "epoch: 200 step: 20 time: 0.031, loss is 0.003\n", - "epoch: 225 step: 20 time: 0.032, loss is 0.001\n", - "epoch: 250 step: 20 time: 0.030, loss is 0.001\n", - "epoch: 275 step: 20 time: 0.032, loss is 0.000\n", - "epoch: 300 step: 20 time: 0.030, loss is 0.000\n", - "epoch: 325 step: 20 time: 0.030, loss is 0.000\n", - "epoch: 350 step: 20 time: 0.029, loss is 0.000\n", - "Total time used: 11.819875240325928\n" - ] - } - ], - "source": [ - "context.set_context(mode=context.GRAPH_MODE, device_target=\"CPU\")\n", - "\n", - "train_loader = ds.NumpySlicesDataset({\n", - " \"around\": train_x,\n", - " \"center\": train_y\n", - "},shuffle=False).batch(3)\n", - "net = CBOWClassical(len(word_dict), embedding_dim, window_size, hidden_dim)\n", - "net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", - "net_opt = nn.Momentum(net.trainable_params(), 0.01, 0.9)\n", - "loss_monitor = LossMonitorWithCollection(500)\n", - "model = Model(net, net_loss, net_opt)\n", - "model.train(350, train_loader, callbacks=[loss_monitor], dataset_sink_mode=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "打印收敛过程中的损失函数值:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXgAAAEGCAYAAABvtY4XAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAA580lEQVR4nO3dfXyU5b3g/881M0l4MMAYHgLGJEZtCoQeTZAEbVF7xCMetyrWVtM9bbdVpNt9/Y6vnv5W69O62HY9u9u+ume3L/Ghbk/PAYqKYOvq1mfxiQBBMUCMQiQhPCSAAwQCeZj72j/u+57cM3PPZBJmknn4vl8vXiQzc89cCeE7V77X9/peSmuNEEKI7OMZ6wEIIYRIDQnwQgiRpSTACyFElpIAL4QQWUoCvBBCZCnfWA/AaerUqbq8vHyshyGEEBmjsbHxiNZ6mtt9aRXgy8vL2bp161gPQwghMoZSqi3WfZKiEUKILCUBXgghspQEeCGEyFIS4IUQIktJgBdCiCwlAV4IIbJUWpVJZovVDe08/W4rx870A3C6L8iZviBKgQa0Bo/1sWGY13g9ybnP0OYfrwJU8p8PYEKel4J8LwAFPi/nTR4HwBen+jh3Yj5TJuSHfT+mFRawtLqEmjJ/0r7HQoihSYAfhkdfauYPH+zlzIARCoyRQTJu82XHnUbEAweM5N4X1IOvl+zn6+4N0t0bDN23P3B68IGHT+FmdUM7dy2q4N7rZ7veL4RIPgnwLuwZ+OFTvfT0BgkaGq3Dg7cRJ1iLaBpYubGVQyfO8JvbLh3r4QiREyTAW1Y3tPPbNz/j0IkzBI2hHy9GZsNHB/ho3zGuuGiqpG2ESLGcDvCPvtTMqoY2TvUFR20W7rXSOZmYg9ck57eVvUd72Hu0nTWb27lm9gzuuvJCCfRCpEDKA7xSygtsBfZrrW9I9eslYnVDOyv+vJMzAyOfqscLoAU+DzMmjSPPq0KLjtmy0NjYFmDdtg52d3az/9hpel1+3TndF+SUI0cfi6HhlV2dvNbcyc9vmkd9bWkqhixEzhqNGfzfA83ApFF4rZjOJgWjMGe2+T4vVbMmcc+S2RkfqEeqpsyf0Nfe2BZg5dt7eL25c8hZv6HhvvVNbPiwg4tnFGbFG6EQ6UCl8tBtpVQJ8M/AL4CfDDWDnz9/vk52N8nVDe38+tUWjpzsS/gar4KCPAnmyWAH+teaO0n0R83rUTxyY5XM6IVIgFKqUWs93/W+FAf454D/AhQCP3UL8EqpZcAygNLS0pq2tpidL4elsS3Aj/+1kUPdvQk9Pt+rmF5YwL+/+mIJLCmwuqGd+9Y3Jfx4BSyeI/l5IYYSL8CnLEWjlLoB6NJaNyqlror1OK31E8ATYM7gz/Z1hztjLy+awK++dYkEkRSrry3lrZYuXtnVmdDjNWZ+/o2WLtYuWyj/PkKMQCpz8FcA31BKXQ+MAyYppf5Va/1vk/1CjW0BHn25mY/3HaM3mNh7xJyZhTxy0zzXwGEvJB6xZv/ZskA61u668kJe/6SLoEtSXgFfLi7kk87usFTOQFCzbluHfO+FGIGUpmhCL2LO4F1TNE4jycE3tgW45bH3Rz64DObzgH9CPpeW+sc8leF8UzzW0xdqWxC5aLq6oZ0HNjS5LrzW15ZSNWsy969vCttUdu2cGTzxXdffQIXIeWOWg3cM4CpSFOAvuPf/xG8PINJCYYGXn10/h/ra0phB3l5cBcLul0VXIWKLF+BHpZuk1vqtVNXAS3DPDN29Qe5b38R3f9dAfW0pP79pnrmXwCFoaB56YQeVxYXcvqA06vbGtsAoj1qIzJbx7YIz/gvIMRs/OxIK8rctiJ6RBw3NptajLK0uwedRYbev29YxmkMVIuNlfHxsffRvx3oIYpg2fnaErzz8F+bOmhwWxMFcbK2rKKKmzM+KG6tCs3wNPNfYIbN4IYYhK3rRXDtnRsLld7bZxYUUjvPxxak+KqadM+aLlLGsbmhn7ZZ2Cnweus8M0BHoQWvoDRr0J1gxlI5OnBngvvVNnDshj0BPfyjVZgAth7qpKfNTX1vKzgPHWd3Qjgb6BwypqBFiGEZlkTVRI93JmoxKGgVcVu4PHVbhrARxHmBh357Obwrp4tGXmlm5sXXY1100bSKv/cNVgPlve/sTH9BnvZnl+zysubNOvu9CWMZko9NoqinzU1xYkPCuVTca2LzX5df/GAdY7D58ild3dSb0phDvvpFcM1qvdbb1//deP5vFc4v5/9ZsY/+xMwlft/vwKVY3tFNfW0pNmZ9b558vs3ghRiArZvAA969vYlVDe9zH+DyD7W9FYjwKKmcU0h80ot4YEn0DaGwL8O3HP2BgGN/4r108lX/5YW3oepnFC+FuzMskR8PS6pIhHzNgWL3NPWaLggXlfs6bMg415JW5y9DQfKib3YdPsXlvgFd2dYb+rGpo55bH3udbK9+Pu/hpL5h6hvGN3r7vWOg57Vm8fXkwaLCp9ehZfFVC5IasSNGAGQQWlPvd0ywRgoZ56ETb0R7uWlTB4rnFYa0JIH6aY/+x0xw4dkZq8C2b9wb45mPvx20OZm9SenBDE4msDZ84M8CtK9/n2eWXU1PmZ2l1Ceu2ddDXb6CUwh+RYhJCRMuaFA0kr22BV0FlcexeNfZrJfqmEO++dM3BH+vpY2tbYNjpLI8i7ilNiaTSnJxtClY3tPPQCzswtCbf52HVHZKmESLrF1ltw5nFxxPUsOtg98jfLGIszMa9byTXjPZrJcA+pen1T7pc2wssrS5h7ZZ9CefjX2vupLEtQE2Zn0BPH4bWGNpcbN3UelQCvBBxZE0O3nbPktljPQSBufP0gQ1NrI6YrdeU+bnjqxck/DyGJrSDta6iiHyfBw9ImkaIBGRdgK8p87N8UcVYD0NgBucHNzRFLcAWjs8LLbja+w/i2d3ZDZj/tg/dMBePR2FozYoXd8rOViHiyLoAD2b9dcmUcTHvV5g7WSfme0dvUDkqqOEnaz8Ku62uoijUokAD2zuOs+jiqTGfY2tbIBTI3dI0Qgh3WRngAf791RfHvE9jlv6dGTD45c3z2Pvo37LuR5ezeM4Mpp6TT15km0NxVtq+6GHxr94KfR5Z9tg/YFBy7gTKzp3ger2h4fG39wCDaRqvgjyfh7qKohSPXojMlVVVNJG++7sGNn52JO5jFPCLm+dJr/E47IOzdx04bvXAMTh5ZoCgMbznWXTxVP4QZ/PSw/9mbsxzWz2KUMnk6oZ2Xt5xkCVVM+XfTeS8MT/wI1HJDvC/fXM3/+0vLQk9dkG5P+r0IRGffVTilmFULS1fVMG915sL4fevbwq1IPAq+Mm1lWzfdyxm47hrrTr77zy1ib4BQ0olhSBHdrK6qasocv0C811SMJv3BljV0M43H3ufa379dlT1h4hWU+bn2eWXs+5HlzO7uDCha1ZubA3l05dWl1CQF14Vc9eVF8a89vVPunh+Wwd9A4bk4IVIQFYH+JoyP8tcKmr6gjpmewIN7O46yX3rm1jym40s+8NW7l8fXQkiBtWU+Xn57kXMmZlYkH/05ebQdZFVMQDFkwpcrzMMjQYplRQiQVm10clN4fg819s15jmhJ3uDMVsONB/qpvmQWaK3ZnN7VNOts+22mG0euWket658f8jdr1v2BuJuXrrpkvNc2wxrYFKBj4dumBva0brixZ1UFhfKv4EQLrI+wNdVFJHvVaHFPKfu3iBg5t/BDDyxYpPddAsI2+25ZnM7863gMlR7gGzP8deU+bl9QWlCrQjslr92VYyzx8yPr76I37+/lzMD0au4T777Obdddr7saBUiAVkf4O2SvHhBZ/PeAL+8eR73LJk97EVDQ0f0kY/THmDz3kDoDcHu9zKW/d5H8nxD/dZitiJoxyU2h7F7+NhpmsgZeeE4H2dO9kVdF3SkafoHDCmVFCKOrA/wkFj/k/vWN/HLm+fx7PLLwxqJjbTpVixRbwiQPr1mEnw+t3SVbVphAV//8tBHKB7rGQzebmmaS0v9MZ9jUoGPVXfUsW5bh7R6FiKOnAjwdj/yoVrV3re+iQ0fdnDPktn88uZ5odvtgL+7szs0owWSGvgzSax0lU1Zf+J9a5x5eDtN45yR+yfkxwzwT737OaVFE0MVNeu2dUi5pBAuciLAA6EDnIfKD2/ea7YcnnZOPpeW+kNtb92CR+RMP16aI8/roaWzOyfeEBL5EjWw8u09PPnd+dSU+aNm5IGe6PSMLWhoXt5xMKpcUgK8EOFyJsDD8FrVHj7ZFzq5qLxoAldcNDUq9xwr8McS2UM+03LwyU5XfX74ZNjnzhn5QzfMxaPcj1fUQNHEfMnDCzGEnArwiaZqIu092sPeo+2samhnyngfWoFhmA3L7lkyO+EgP9w3hHTklq5yHjoerxIpUuuRU6E0zabWo2Ez8kBPHz+/aV7M1gUvfnyQFTdWsePAccnDCxFDTgV4cE/VKKD03Am0fdEz5PXHTg+EPrbTOVPG+/D5PPQHDc70meUj4/M8+HyD+8j6gwaGASVTxnN+RFOtyJlyOtfXD/UmtbqhnQc2NCU0yzf0YJrGLQ8PxJzFG1qz48BxycMLEUfOBXiITtVooCPQwy9vnsebLV1s/vwoxx2BfCjHXB7bG6NO0Ll5KopjwXJVQzvn5Hvpt8bo9oYR781kuPf1Bw36BzQT871cMHXiiGv262tLqSwuZOXbe3i9uXPIQG+nadzy8Jtaj8a83uf1oEDy8ELEkZMB3k7V3L++KZROCGp4s6WLJx3nf/761RaOuNRij5aTfcHQx7HeMJJ9X09fkMMn+0K9eeyUlB38nQvPsdSU+Xnyu/NpbAtwz3Pb2R2nNLPti55QmgZc8vCA21cwEDQoLPBJHl6IOLK6m+RQlv1ha1gpnlLwi5vCWwc7c877j52m12qVe6Z/mL1ys8zs4kKqy/xDzvIb2wJ887H3Y+blFfDTv6nkx1dfxG/f3M2vXmnB0IPdJf0T8mPm4X0exYobqwj09FFXUSSzd5GTcubQ7eG668oLef2TLoJ2qkbDAxvMYGIH+Vg559UN7Tz9bivHzvSHbouXGsm2NwU71bS6oZ3FVhtft+9TTZmfxXNib3zSQPdp83sYKw/v9eDae97QOhTc7a6SEuSFGJTTM3hwXxRUEDdonc1rrd3SToHPE7dUEWD/sdOc7BtIap493n2n+4Kc6h1MCQ2X1wPP3HV5zP0C8WbxX7t4Kv/iOAhkU+vR0Iw8Xk9/+5CQFS/ulP7wImfJDD4Oe6buDPIaeGVXJ69/0sUjN1Yl7dSg+trStD6BKHLjlp2SSiT4Bw14YH0TL9+9KOq+mjI/XymZzPaO467XFk3MD3usM0DHawf8VyWTCfT0yUKrEDHkfIAH9yAP5o7J+9eHp2yyWbwSSDv4f9gWiFkF1Hyom0dfag6d2OS0sKIoZoB/8eOD/N3CcmrK/FEz+EBPX8xSyW3tx7j50hJZaBUiBgnwFjuAR26C0pg9at5q6Up6yiaTOIO/fUbrqy559cffaWXx3OKo71OsvvxgvpHaOfTI4/jsvLzb+oVhmDn4VXfUhb0pCCFMWX2i03DV15byzPLLuWj6OVH3vbKrk1tXvs+3Vr6f8yc82WWQ6350OdPOCU+haG32eo9UV1GEL8ZPm8ZMxUTuZrXTLd9fWO5+oSIU1O2F1lz+dxEikgT4CDVlfv7xlq/gczm31W71u6qhnVseM4N9LgeUmjI/K/9uPp6Ib9XaLe1R35eaMj/fvix2mmvHgeOh2bpXEZZu2XnwhOs1WsOrOw/R2BbgO09t4levtPCdpzbl9L+JEE4S4F3UlPlZu2whi+fMiApeTpv3mtUh31r5fs6e3Wqf4uQUNMwWBJGWVpfE/IF7dus+AFbdUcdPrq0Mq4aZO3NSzNd/4p1WOYhbiBgkBx+DczdmvG33mvADPFY3tDPLP57zJo+LKoVM5x4zZ2NpdQl/3NwetnbxenNn2A5VML+nFdPPYXfXyajnGAiaefgfX32Ra/4+Vn95rZETnoSIIWUBXik1DtgIFFiv85zW+j+l6vVSxRnoh6oiATPY7A+cZn/gtOv9qxvauazcn5I2v2P1BlJT5ueRiM6Phh48d9XpB1dc4Loz1edVocAcWUlTV1FEQZ77QqvPq7iluoRbqktkoVWICKmcwfcCX9dan1RK5QHvKqVe1lpvSuFrpoyzisTexbrn8KmEW+PaImf8YZJw1F684/Qg9hvG2b451NeW8lZLV9iOVbvvfeTjnn7v86hZfNDQtFhvnJGVNPZC68qNrVHPNxA0r7OroGRHqxCDUhbgtblF1v5fnGf9SZ9ts2fB3rAUuTFoOL3QU2Wo4/RCXO5zppdG0k3yrisv5I2WLgasXM0bLV1RaRpwn8UbGh56YQffvux810qamAutwIMv7ACQHa1CREhpDl4p5QUagYuA32qtG1weswxYBlBamlmbiSI3Btn5+s8PnzyrgzDGijO9tHlvgDWb25lf5k84/VNT5ufrldNDs/iBoHZN09TXlvJ/dxxk42dHwm4PGjpmPn3uzEm8E/F4myFH+AnhKqUBXmsdBC5RSk0B1iulqrTWOyIe8wTwBJi9aFI5nlSz8/VuIo/rc0pGDh6Sfwi4XRZqW7O5nZ9HdNuMNK2wIOxzt6+3sS3Ae7ujg7VSxMynx1tozfN5WFI1ky17v5CFViEcRqWKRmt9TCn1FnAdsGOIh2el0TiuL95xeja3N4xEf8MwNNy/IX7rhqXVJazdui9ummZT61HXIxPtvndu36tYC63nTsznp9dWhg4akYVWIQalsopmGtBvBffxwDXAP6bq9cTZvYk400t5Xg8tnd3uZaEuLZUjxzBUmqauooh8r6IvIsprzAM/YrUdfuiGuWGHtAB8caqPB63xVBYXDvOrFiK7pXIGPxP4ZysP7wGe0Vq/mMLXE2chMr3k/G0gcnZvDBHkI9M0kXvFasr8rFm2kEdfbmZLREXRp53dodePnI2/1dLl+ltG0BqPz+thICiLrELYUllF8zFwaaqeX6SWW3Ox15o7Q2mUeEHemabxeRVLq0tcn/+qyulRAb6xLcDqhnbXipjOE2dijtdeXNXIIqsQNmlVIIZkz+5/cdO8sNYNdpBf3dAedY1HmQ/UmlB9e6S6iqKo2b2hzV42bq0H4vWy8XkVeS59bITIZdKqQCTMrW++Xb9eWVwYmjFvaj1Kv3Wgd9DQUfc7zS/3R83idx48gc/rIRgMr4ipry2l/eipqA1PheN8LKwo4qrK6XI+qxAOEuDFsLgF+aARvpBaV1GE16MYsB5gaB2VMrE7QLq1H9CG5psLzue8KeOjgrVbuWT3mQFe2dXJW58eZs2dknsXwiYpGjFs9bWl/PymedgdlTXwXGNHqJNmTZmfFTdW4VXmAqvPo6JSJnbvdzcaqJo12bXxmH9Cfsxyzv4Bg3XbOvjtm7tzrqunEG4kwIsRqa8t5TZHm+CBiDa9lcWFKI9CA25h3O797sbQZtsBtyAd6OmLytvblDLfaKQvvBAmCfBixObOmhz62CD8gOx12zpCm53sWninmjI/q+6oo7621DVg9/YbridDxZrBK+Ca2TMYCEpfeCFskoMXI+Y8ENujzM9tkUHbLYjbpZhVsyZHNR/TwDNb96EgrAeO8zWcvlxcyNTCAtfFWSFylczgxYjZaRYPZlmkcwa/tLqEfJ8Hhdk8zK0W3vZmS5fr7QNBzaqG9rB0S11FES6nKdJ8qJs1De2gNbctKJWNTkIgAV6cBbt9gMejMLQOy5vXlPlZc2cd18yZweziwpi18ACfH4nT1hjoc6Rbasr8/PXsGa6P08CAoZk1ZbwEdyGQAC/OUqCnD0Nr17x3y6FuXt3VyfaO49y33n1DVGNbYLDLWAweFV6Fc9eVF5LvNo13eawQuUwCvDgrzjSNikjTvLzjYNhjIz+3a+F3Rxw+sujiqYzLM9M7HgV3fPWCqLNd1yxbyEXTJoZdpxSsuLEKQEolhUACvDhL8dI0S6pmhj028vNYtfDv7D7CdXOL8Vp9EX7/wd6oYN1yqJs9EW8Ml5X52XHgOLc/uUlKJYVAArxIglhpmvraUn558zwumjaRi6afE3Wdc/bvpDW8sP1AzNRPY1uABzc0RZVLbm0LsKbBvY+NELlIyiTFWbMDdazTlOwUjF0Kabc7sGvhN7Ue5cXtBwbPksUM8l6PAq2jUj+xDgxx9q9XSNMxIWQGL86aHah/cm1lVHniUHn4mjI/P776IkrOnRB2u517V1ZPm4f/vDOsVDLWIiuAV5lvIlIqKXKdBHiRFDVlfuoqitjUejQs7z1UHt42PeKQkGtmz6C7d4CgNS3vGzB43trZai+yLp4zI2oDlUfBIzfN4xc3z5PgLnKepGhEUtgVMZGHdNjpmKffbTXLXGJYWl3Cs40doUXXYy47Vp1ZmZoyP5ecP4VXraMBnbcHevpY3dAurYNFzpMAL5LCrohxLm46A2usPDwMHs93fVUxGz46AMDmvQG8HvB6IGiAzwO3ROyG9UccKG4/11briEGPQo7vEzlNArxIingLrW55eDvAO2f+kfudgoa50KrQeDzR2US3vjTOhdZYbzZC5AoJ8CIpnBUxkWmRJVUzeeezI2Gf25wz/0gK0FqHzll1HirS2Bbgo33H4o7JI8f3iRwnAV4kjfOgbqd4eXjnzB8IK3+cX+5n+75j9AV16FARO03znac20etyGpRt8ZwZXHL+FMnBi5wmAV6MGrc8vHPm75+Qz8N/2kGfFeXPmzKeL80oZJXVw8Z5qEjfgBHzZCefR7H8ygslsIucJ2WSIqka2wKufWDi1cPbtfD1taVcP28wfbPhowPs+6In9Ll9qEisHbBg/kB//cvTk/GlCJHxJMCLpLEXTN36wCRaDx+ZV/94/3GsljShQ0XsWf8VF08N3WczgFd3dUofGiGQAC+SyK1U0mb3pfnaxVP55c3zwsoknUojdrSWnzuBfJ8Hr1XyaC+Y1pT5ufuaL+GLjPCY9fJ9cgC3EJKDF8kzVE8aO6jb6Rm3Wvjjp/vDrikcn8eqO+pY+fYeuk6coeVQdyi3XlPm59b554dy9JGea+xgIGhILbzIWRLgRdLEK5UEWN3QHlpgtcsm62tLw2rhIyfkc2dOCh0cArC9I3yjVOQOWNusyeM4cPyM1MKLnCYpGpFU9oKpWzCNtdDqTO1oDZeVD1771Hufs3ZLu+t1jW0B1m3rwHA5EWpcnhef10ztSC28yFUygxejJtaGp8jUjnMSPxDUUbPzJVUzQ7P+3n73csnWI6fweRS3LShlaXWJzN5FTpIAL0aNMwe/pGqma1/4uooiVvx5Z9h1+T4Pv7x5XthGKXvWH6sW3tAQlAO4RY6TAC+Szl4wdcvDx1pode6CXVhRxPaO46FrFlrpFedGqeWLKkKzfqXMvjWRwd7rldSMyG0JBXil1ETgtNbaUEp9Cfgy8LLWun+IS0WOidU22BZrodXpRO9A2Od7jpxi58ETYbftPHgibAfsgxuaok55uvJL0wDzAG5pWSByUaKLrBuBcUqp84DXgX8H/D5VgxKZK14tPMTf0Wrvgj3S3Rv2mDc+6WLuzElhty2pmhla0A309Lke4ff54ZNyALfIaYmmaJTWukcp9UPgf2qt/6tS6sNUDkxkpqFq4WMttDpn/j6PwqMGW/8GDc2J3gHyvIr+oCbPq6gsLgx7TefjbXsOnwqlbaRUUuSihAO8Umoh8B3gh8O8VuSQoWrhYy20Omf+QUNTOaMw7BDu3Z3doeP7BoI6rHVwTZmfZV+rYOXG1tDjFYM5eTmAW+SqRIP03cDPgPVa651KqQrgzZSNSmS0WG2DbZXFhew4cJydB47T2BYInefqnPn3R0zHvzjVh8+jQq2Dn9m6L9Q6eFPrURbPLQZgw0f7QUPnyV6Uhjyv4tb550uppMhJSrtsEol7gVIe4Byt9YkhHzxM8+fP11u3bk3204o00tgW4PYnPgi1BM73eVhzp7kQ66y+Wfn2nrDzVu0Dtl+JuO2dzw6H0joG5uzeafmiCu69fvZofGlCjAmlVKPWer7bfQktsiqlViulJlnVNLuAFqXU/5/MQYrcsKn1KP2OIOxciHXugl1+5YX4rJ9Oj4KrK6czrbAg7Lm6TpwZXNAN6qjgDkRV3wiRSxKtopljzdhvAl4CSoG/S9WgRPaqqygizzu4VzVWbrymzM8dX63Ao8z2BSte3MncWZPJt6K+UnDB1ImhTpN5XhXVxwZgfJ5XqmdEzko0B5+nlMrDDPD/S2vdr5SKm9tRSp0P/AEoxmzT/YTW+n+czWBF5qsp87Nm2ULWbetAQczceGNbgKfe/TxUGdPbbxDo6eMHl5ezcmMrWpsHgixfVEHh+DzqKop4deehsIVWj4LXmjt5q6VL8vAiJyUa4B8H9gLbgY1KqTJgqN99B4B/0FpvU0oVAo1KqVe11rtGPFqRFWItwjpz8JtajzLgWGjVmKc5RdbV7zx4gn/5YS1A1H325X1BzeqGdtZt65C2wSKnJBTgtdb/BPyT46Y2pdTVQ1xzEDhofdytlGoGzsPM4YssF69dAZg7Wp2lkpE7YL+/sDzqmh0HjkfV0Ts3QPkn5Mccj0Zq4UXuSbRVwWTgPwGLrJveBlYAx2NeFH59OXAp0OBy3zJgGUBpqfspPyKzjKRdQaCnL2wHrNvi6JHuXuprS2k/eorH3zHTNE+/v5fFc4upKfMT6OlzHY/XSuRLLbzINYkusj4NdAPfsv6cAP53Ihcqpc4B1gF3u5VWaq2f0FrP11rPnzZtWoLDEelsJO0K7Dp4u3/7kqqZ5HvDV03f+vQwjW0BunsHsKt7+wYMHn97D799czf+CfmMy4v+ka6aNYmfXFsp6RmRcxLNwV+otb7F8fl/Vkp9NNRF1sLsOmCV1vr5EYxPZKCRtCtw2wFbWVzIij/vDHWWDAbNN4vI1f3Xmjt5rbkzlNr588cH2H/sTOj+GZPGSbMxkZMSDfCnlVJf1Vq/C6CUugI4He8CpZQCfgc0a61/fXbDFJlkpO0KIhdfa8r8LKwoomn/cbQOL6l8Zks79jkgocXUfoMn32mNajz2WnMnGz87LDN4kXMSDfDLgT9YuXiAAPC9Ia65ArNWvskx279Pa/3SsEcpMs5Q7Qrqa0uj2gRD+OJsy6HusLLH7y8sDz3nty8rZXVDe1i/GY9HhVXe2ORcVpGrEq2i2Q78lVJqkvX5CaXU3cDHca55F3DZeiKEKbLSJnJxtsQ/IezxHzhy+c7Dtj0Krpk9g6sqp7v2hVeYC60Hjp0O9b4RIhcM69BtrfUJx0LpT1IwHpEj7J40//0vLdz+xAehYO9cnCWiT1Lk2ayGYX5uaHMBtrK4kEdumkfE2iwaCGpYs7ld+sKLnDKsAB9BZudixNZt6wh1huyz2v9GVtL84KsVYT+gzYe6Wd3QDpiVOkFHvLdTMPW1pTyz/HLOnZgX9npBQ8es6hEiW51NgB9eG0ohHCJnB4rBxVm7pLG+tpR5JZPDHmeXWEb2tPF4VNhGp8Cp8NMkFWbrAqmFF7kkbg5eKdWNeyBXwPiUjEjkBDuHbpdSLrV6u0cuzkYewG3vXK0p8/PwN6p4+t1WWo+cwjA0D72wAzB3vLr90HqU4qEb5koOXuSMuAFea10Y734hRqqmzM+aO91LKZ2Lr5EHcNufN7YFWPHiTnr7jVAwH7CCfHXplKjX04DWOuZuVyGykRy7J8aMHdSd/eAjK2kWXRy+u9k+kNtekI2cqQ8Y2nURVY7tE7lIArxImaEajtmVNPZB2muWLYyqpJlaWIDPq0KHedjtCpy7ZYGw0kiXUnhQSHpG5JyzWWQVIiZ7Jv6rV1piliYmUklTNWtyWMfIgeDghiV7Qfa2BaWhRVt7MTWS1uYCrZRIilwiM3iREm4NxyJnz/EqaTa1HsU/IZ8VL+7kTP9gPaShB9sC28+3blsHXq8iaP0mcFXl9LCzW23vfHaEhs+/CJ0BK0S2kxm8SInImbhb7ntpdQn5Pg8K8/BtZyXNj6++KNRCONKbLV3A4G8JaxraGbB+E0CZAT6yE6Wtb8Dg+W0dyfoyhUhrMoMXKTFUwzH7MWvurAsd3+fU2BZg/7HT+LyeqCD/+eGTgPtCa/+AwY4Dx7l1/vmssjZFRZINHCJXSIAXKTNUwzHb89vMnjL2kXpAqJLG51EsKPezee9g7rzti56whda+fgP7LUADz2zdx4pvVOHzQOQvAPlexS3WbwpCZDtJ0Ygx5Zard94WNDRXVk7n2jkzQtcYhg5baP2Hv6lkQfngG8lAULP+w46oahqvR/HwN6ok/y5yhszgxZiqqyjC51H0BzVejwrl6iMPDKmrKOKtTw/TP2Dg9Q7m9O3fEg4cOx02y9/aFojsVUbQkI1OIrdIgBdjTylAW3+75+8b2wIEDTPfHjTC8y6NbQE0kOc13yiUimpEGdJ9ut/9DiGykKRoxJja1HqUgaAZuPutPDwMVtLYwf2edR+HukcGDXj87T3AYCXNHze3o7W2Dtg2H6eILsV84p3WUEdKIbKdBHgxpuwUDZhx+bnGjtBmpMa2APetb+L2Jzexu+tk2HU7D5gNyMLz9WZ+XmP+YH+lZDKeiJ9wQ8NDL+yQDU8iJ0iAF2OqpszPrfPPD8207YO1nTXubrXw+4+dYXVDe3i9vVeR5/PgwWwfXODzhPWMtxlaS094kRMkwIsxt7S6hIK88E1RkTXubumWl3ccDGtZsGbZQn5weTkoc0E1VtOxfGk6JnKEBHgx5mrK/Dx0w1yqzpsc6h7pnJnnexX1taXctagi7Dpnb/gfX30RAE+9+zmGNtM9hnY5dkyajokcIlU0ImWG6ibpfNzDf9pBX1ADx3nr08OsubPOdSfsoRNn2PDRAQCefu9zFs8tDi3E/ua1TwlGFL9PPSefwycdpZEaKZUUOUMCvEiJyL7uq+6I3eBrU+tR+h39fu0NT/as3Jkv/5MV3MHsQLny7T0sv/JCvvPUprDDP8CcxYcFd6KP9hMim0mAFymRSDdJm32+ap8V5O08vPNNwudRlJ47gcg10zc+6WJ6YUEoX+8BSosm0Ha0x7XnTNDQrHhxJ5XFhZKmEVlPcvAiJRLpJmmrKfOzZtlC6mtLuXbODG6tMXvFON8k+oKa3YdPRV1rl0WG8vV5HpYtujDsQO5IZ/oH6+2FyGYygxcpkUg3ycjHw2CTsXXbOnjohrnk+zxRqRcnDUwq8Lm+1oMbmsJOenJ6dus+bqkukVm8yGoygxcp49yNmojnt3XQ2z+Y1gn09LHqjjpury0NzdB9HiieVBB23VPvfg4Q9lqBnr64bYEHglILL7KfzOBFWmhsC/Ds1n2Dde9W4zHnm8OR7l7e+vQwh070hl07YOhQysXuLT931uSYZ7aCHMAtcoMEeJEWIitp7GZhzoVWj1IYMbqI/XFzO2u3tId2rub7PDz8b+ay48BxNrceDcvfnzsxn59eWynpGZH1JMCLtFBXUYTXoxiw6tgNa1Z+3pTxoYVWHatFJOamJmdOxj7Z6bmt+0LVObYvTvXx8J+lkkZkP8nBi7RQU+ZnxY1V2MUvduMx/4R88q3+MhExPK48n4cj3b1Rwd0mZ7OKXCABXqSN+tpSbltQGmovYM/CV91Rx7ySyQk/z0XTz2HNnXVMKyyI+7iu7t649wuR6STAi7Qyd9ZkPBGz+JZD3TQfPBHzmshGZJ8fOUXLoW7mzor/pvD2p4elbbDIahLgRdpobAuw4sWdYRUv/QMGa7e0hxZgFXBZuR/nPqY8n4fFjjNbg4bm/vVNbPiwI/Rm4cZuTSxEtpIAL9KGvXPVSQM7DhwP5d41EOjpDztQ+69KJnPXlReGDg6xH7d5bwCvR4Xq550UUiopsp8EeJFSjW0Bfvvm7oRSIc72Bs4ZeuShHbu7ToYttm7ZG6DlUDd3fPWCqOccl+fhr2fP4OtfnsHs4sJQKkcD11mdKIXIVlImKVJmOB0lYbC9wbptHRzp7uWNli4GYvUaiLB2SzvXzi2Our37TJBXdnW6XrPhowMsuKCI+trSxL4gITKMzOBFyrh1lEzE89s6eK25E20kWhRppnH8E/LJj9NkzM3aLXIAt8heEuBFygyno6TN+aagNTEXSRUwZfzgL6BBA95s6WLNsoUsnjMj7uKq08cdx1ndIEFeZCcJ8CJlnOelDpWesYUd1ZfnYdnXKqKP3cPMoR87PRB22xufdAGw/MoLuW1BKQvK/a7XRj7P/RuaJMiLrJSyHLxS6mngBqBLa12VqtcR6a2mzD+shUz7fNaXdxxkSdVM6mtLaT1yKmYe3SlomCc8vfPZYXr7DbwexV2LKmhsD7B1byB2y2END72wQ1oXiKyTyhn874HrUvj8IgvZtfDv7T7Cihd30tgWMEsgE8ytv97cGeofP2Bonnr3c740oxA1xOVBR0dKIbJFygK81noj8EWqnl9kp1hH/a21cutDhXlDExbMDW2e+OQbIilv75qVna0im4x5Dl4ptUwptVUptfXw4cNjPRwxxuwcvAdQavCA7JoyP09+dz7P/ehyiofoMfPVi6bi8yg8ymwbfEt1CVdVTh/ytfsH5Cg/kV3GPMBrrZ/QWs/XWs+fNm3aWA9HjDE7B+/xmL3f7TSN8/6/u7w87nO8t+coK26s4h+sxV2ANz6JncP3Wv8LZBYvss2YB3iR/YazmxXM4/YMrTE09LockF1XURS33j1oaNZuacc/IZ9NrUd5fltH1G7YMI7VV+lPI7KJ7GQVKTXc3axgBnCfR9EX1KFZtfOA7JoyPw9/o4r71jfFfI7tHcfZ3tGER5n5d69XhXbFehRhvWwMbbZGMDR4raMChcgGKZvBK6XWAB8AlUqpDqXUD1P1WiJ9jWQ3a02Zn1vnnx/WFz5yFr/zwPGEXt/Q5oz+W/PPp762lGvnzODcc/LDHqMBj70IO1S5jRAZJJVVNLdrrWdqrfO01iVa69+l6rVE+hrJblaApdUl5FlpGLfceOJNDMzXvaW6hFuqS3ijpYsj3X1Rjwka5m8LkqIR2URy8CKlRrKb1b4u3iz+luqSqBbAbpSC7y8sp6bMz+Nv74nZvMyj1LDfhIRId5KDFyk33N2stqXVJTxrHZqtgbVb9lE1a3Ko+6PH4wEj3uqpuUv18Y2tHDpxhtebY1fS3PCVmUwo8A1ZZy9EJpEAL9KWPYtfZfWJCRo61FJgU+tR+gfiB3ebxmwNHM+fth/A5/UwEDR/UxjObxtCpCtJ0YhRMdxSSdvS6pKwXah2S4G6iiK8ibaMTIC9CDzc1sZCpDMJ8CLl7FLJX73Swnee2jSsIF9T5mfFjVWhE5408MzWfQCuJzidDWWdJCV5eJEtJMCLlBvpwR+2+tpS/nr24KHaA0Gza2Th+Ly4OXOPYsi2Bk6GNt9QbqkuGdb4hEhXEuBFysXqLzMc0yIC9WvNnXSf7g+VUgJRwd7Q0Nndi1dBYYE3oQXUzXsDrNncPuzfNIRIRxLgRcoN1V8mEUurS8Jy7nZ1zOTxeYO3uVyngaCG7t5g6H4FlBdNYHZxoetrSR5eZAsJ8GJUDNVfZig1ZX4eubEq7Cg+DRw+Gb1pKR4FFOR5uG5uMS2HumM+zuuVPLzIfFImKUZFZH+ZyJr2RNiPfWBDE8M4jxuFuYDq8yhunX8+c2dN5sENTcQrsrzyS9OkTFJkPJnBi1Fh17Tbgobm/vXDPwu1vraUn980j3gHPDkP4wZzpq+AqyqnM3fWZNZuaSfGhtaQNz7pknNaRcaTAC9GzdLqkrDAPNIDr+trS3lm+eUsnjMDt1L4XpcNUEENr+zq5L71TWzvGLpRmb2pShZaRSaTAC9GTU2ZP6zcEczF0gdGEOTtE56eXX45504Mr8o53W8kpeVA0NCy0CoymgR4MarcDtA2NCOeLdeU+fnptZVRt0dmYJwzfQUJNSrTMKKSTiHShQR4MarCDtB2BF27BcFI1NeWsujiqXEf41yU9SiYMXn8kM/rwaz+ESJTSYAXo85Or/zCsVh6tueh/v01X0r4sUEN+wOnh3ycxzOyTVlCpAsJ8GLM1NeWctuC0rgnNyUq2bly81i/kW3KEiJdSIAXY2qok5sSZdfZJ2qoRxra/NMnO1pFBpMAL8ZUIuevJvo8w+kuOWNSYk3IDC0LrSJzSYAXYy5Zs/hCR1+aeBRw+GRvQo+VhVaRySTAizEXOYsf6cHXdRVF5Mfb4mrRQDCBw6A8CvLzpCeNyFwS4EVaWFpdQkHe2bUUrinzc1Xl9KSNad55k+XoPpHRJMCLtJCMlsIAU4dxwMdQ2o6e4vltIy/dFGKsSYAXaSPQ00fQMFsK9/WPLE1zS3VJUtoUABw7PcCqhnZuf1IO/xCZSQK8SBv+CfmhFgMGI69eUcmK8BY5/ENkKgnwIm0EevpCPWMUsOPA0F0fI21qPYoeRq/4RHg8ShZaRUaSAC/ShnOz0kjLJesqisLOaU0G/4Q8WWgVGUkCvEgbySiXjDxYJBmOnOzj7j9+mNTnFGI0SIAXaSUZ5ZJLq0sSqocfjg0fHZATnkTGkQAv0oqzXNI+VWkkh4EkexYP8PS7rUl/TiFSSQK8SDt2uaQGBgw9ohOfIo8HTIbdh0/JLF5kFAnwIu3UVRThdXSGNEZwrF9NmZ+q8yYnfWz3rW+SmniRMSTAi7RTU+ZnxY1VYcfsGRruX9/Esj9sTTjAfvuy0pSM7/YnPpAgLzKCBHiRluprS/n5TfPCgrwGXtnVya0r309oNl9fW8pNl8xK+tj6gppbHntfKmtE2pMAL9KWW5AHazafYMrmN7ddyuziwpSMb8NHB7j4/pckLy/SltLJ3vZ3FubPn6+3bt061sMQaWZ1Qzv3r28i8idVAYvnzOCuKy+MuxGpsS3ALY+9n9IxApQXTeBX37pENkWJUaWUatRaz3e9TwK8yASrG9p5YEMThsuPqwIunH4OP7jiAupr3fPuj77UzMqNo1fm6PVA5YxCHrlpngR8kVIS4EVWaGwLsPLtPbzW3Bmz38zs4kKqy/wsrS6JCqx3//FDNnx0YBRGGpsEfpFsEuBFVlnd0M6DG5oIxvnR9SiYbwXQL071UTHtHO668kL+5YO9Yx7khyPfq8j3elAexeziQu5ZMlveGESYMQvwSqnrgP8BeIGntNaPxnu8BHiRqNBsfldnVG4+FgVcVu5nd9dJvujpT+XwRBwK8w0YBRPyvHi8iv4BTb5X4fN5KPB5OW/yOKZMyGdaYYHrb2Ni0JgEeKWUF/gUWAx0AFuA27XWu2JdIwFeDFdjW4BHX25my16pSxeZzQO0Pvq3w74uXoBPZZnkAmC31rpVa90H/BG4MYWvJ3JQTZmfZ5dfzrofXU59bSkLyv1RZZVCZAIDqLj3/yT1OX1JfbZw5wH7HJ93ALWRD1JKLQOWAZSWpmbnoch+NWX+0K/xjW0B1m3r4Eh3L8d6+th/7DQHjp1JOJUjxFgxkvx8qQzwbvOoqP9jWusngCfATNGkcDwiRziDvc0Z9G3Hevr44lQf507MZ4qjLfGxnj5aj5zi+Ol++uOt5AqRZMlOqaQywHcAzp6tJUDmlC+IrOIW9BMR+cYQ600h1n3HevrYeeAEp/qCKMwySeU4NNY+ZNy+TwOG4TITEllvpDn4eFIZ4LcAFyulLgD2A7cB9Sl8PSGSbqRvDEKkg5QFeK31gFLqPwB/wSyTfFprvTNVryeEECJcKmfwaK1fAl5K5WsIIYRwJ90khRAiS0mAF0KILCUBXgghspQEeCGEyFJp1U1SKXUYaBvh5VOBI0kcTipl0lghs8abSWOFzBpvJo0VMmu8ZzPWMq31NLc70irAnw2l1NZYDXfSTSaNFTJrvJk0Vsis8WbSWCGzxpuqsUqKRgghspQEeCGEyFLZFOCfGOsBDEMmjRUya7yZNFbIrPFm0lghs8abkrFmTQ5eCCFEuGyawQshhHCQAC+EEFkq4wO8Uuo6pVSLUmq3UureMRzH00qpLqXUDsdt5yqlXlVKfWb97Xfc9zNrzC1Kqb9x3F6jlGqy7vsn5Wwenryxnq+UelMp1ayU2qmU+vt0Ha9SapxSarNSars11v+crmN1vI5XKfWhUurFDBjrXut1PlJKbc2A8U5RSj2nlPrE+vldmI7jVUpVWt9T+88JpdTdoz5WrXXG/sFsQ7wHqADyge3AnDEayyKgGtjhuO2/AvdaH98L/KP18RxrrAXABdbX4LXu2wwsxDwD4mVgSQrGOhOotj4uxDwcfU46jtd63nOsj/OABqAuHcfqGPNPgNXAi+n8c2C9zl5gasRt6TzefwbusD7OB6ak83it1/ICh4Cy0R5rSr6g0fpjfdF/cXz+M+BnYziecsIDfAsw0/p4JtDiNk7MnvkLrcd84rj9duDxURj3C8DidB8vMAHYhnm2b1qOFfPksteBrzMY4NNyrNZz7yU6wKfleIFJwOdYxSHpPl7H818LvDcWY830FI3bwd7njdFY3MzQWh8EsP6ebt0ea9znWR9H3p4ySqly4FLMmXFajtdKeXwEdAGvaq3TdqzAb4D/SPj5yek6VjBPB3xFKdWolFqW5uOtAA4D/9tKgT2llJqYxuO13QassT4e1bFmeoBP6GDvNBRr3KP69SilzgHWAXdrrU/Ee6jLbaM2Xq11UGt9CebseIFSqirOw8dsrEqpG4AurXVjope43DbaPwdXaK2rgSXAj5VSi+I8dqzH68NMgz6mtb4UOIWZ5ohlrMeLUiof+Abw7FAPdbntrMea6QE+3Q/27lRKzQSw/u6ybo817g7r48jbk04plYcZ3FdprZ9P9/ECaK2PAW8B16XpWK8AvqGU2gv8Efi6Uupf03SsAGitD1h/dwHrgQVpPN4OoMP6DQ7gOcyAn67jBfONc5vWutP6fFTHmukBPnSwt/VOeRvwpzEek9OfgO9ZH38PM9dt336bUqpAmYeSXwxstn5l61ZK1Vkr5d91XJM01nP/DmjWWv86ncerlJqmlJpifTweuAb4JB3HqrX+mda6RGtdjvmz+IbW+t+m41gBlFITlVKF9seYueId6TperfUhYJ9SqtK66a+BXek6XsvtDKZn7DGN3lhTtbAwWn+A6zGrQPYA94/hONYAB4F+zHfdHwJFmAtun1l/n+t4/P3WmFtwrIoD8zH/k+0B/hcRC0pJGutXMX/N+xj4yPpzfTqOF/gK8KE11h3AQ9btaTfWiHFfxeAia1qOFTOnvd36s9P+/5Ou47Ve5xJgq/XzsAHwp+t4MYsCjgKTHbeN6lilVYEQQmSpTE/RCCGEiEECvBBCZCkJ8EIIkaUkwAshRJaSAC+EEFlKArzISUqp+5XZnfJjq9tfrdXtb8JYj02IZJEySZFzlFILgV8DV2mte5VSUzE7E74PzNdaHxnTAQqRJDKDF7loJnBEa90LYAX0bwKzgDeVUm8CKKWuVUp9oJTappR61urdY/dQ/0dl9qnfrJS6yLr9VqXUDmX2rt84Nl+aEINkBi9yjhWo38XcafgasFZr/bbVQ2a+1vqINat/HnNH4Sml1D1AgdZ6hfW4J7XWv1BKfRf4ltb6BqVUE3Cd1nq/UmqKNnvnCDFmZAYvco7W+iRQAyzDbD+7Vin1/YiH1WEewvCe1ar4e5gHNtjWOP5eaH38HvB7pdSdmIc8CDGmfGM9ACHGgtY6iNmZ8i1r5v29iIcozN7zt8d6isiPtdbLlVK1wN8CHymlLtFaH03uyIVInMzgRc5R5nmZFztuugRoA7oxjzAE2ARc4civT1BKfclxzbcdf39gPeZCrXWD1voh4Ajh7V+FGHUygxe56Bzgf1ptiAeA3ZjpmtuBl5VSB7XWV1tpmzVKqQLrugcwO5cCFCilGjAnSfYs/79ZbxwKs1Pg9tH4YoSIRRZZhRgm52LsWI9FiHgkRSOEEFlKZvBCCJGlZAYvhBBZSgK8EEJkKQnwQgiRpSTACyFElpIAL4QQWer/AX8/wv6QJp6jAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "plt.plot(loss_monitor.loss,'.')\n", - "plt.xlabel('Steps')\n", - "plt.ylabel('Loss')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由上可知,通过量子模拟得到的量子版词嵌入模型也能很好的完成嵌入任务。当数据集大到经典计算机算力难以承受时,量子计算机将能够轻松处理这类问题。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 参考文献\n", - "\n", - "[1] Tomas Mikolov, Kai Chen, Greg Corrado, Jeffrey Dean. [Efficient Estimation of Word Representations in\n", - "Vector Space](https://arxiv.org/pdf/1301.3781.pdf)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorials/8.grover_search_algorithm_based_on_mindquantum.ipynb b/tutorials/8.grover_search_algorithm_based_on_mindquantum.ipynb deleted file mode 100644 index 795d2774e..000000000 --- a/tutorials/8.grover_search_algorithm_based_on_mindquantum.ipynb +++ /dev/null @@ -1,1069 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 基于MindQuantum的Grover搜索算法\n", - "\n", - "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", - "\n", - "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/parameterized_quantum_circuit.ipynb) [![](https://gitee.com/mindspore/mindquantum/raw/master/tutorials/images/view_mindquantum_api.png)](https://mindspore.cn/mindquantum/api/zh-CN/master/index.html)\n", - "\n", - "## 1. 概述\n", - "\n", - "如果你听过量子计算,那么你一定听说过Grover搜索算法。1996年,Lov Grover \\[1\\] 提出了Grover搜索算法,它是一种利用量子状态的叠加性进行并行计算并实现加速的算法。Grover搜索算法被公认为是继Shor算法后的第二大量子算法,也是第一个被完整的实验实现的量子算法,它解决的是无序数据库搜索问题。1997年,Bennett \\[2\\] 等人证明,对于非结构化的量子搜索问题,至少需要$\\Omega(\\sqrt{N})$​次量子查询,因此Grover搜索算法对于该问题是渐进意义下的最优算法。\n", - "\n", - "无序数据库搜索问题(Unordered Database Search problem)就是从一个海量元素的无序数据库中,找到某些满足要求的元素。由于数据库中元素的数量是巨大的且这些元素是无序排列的,所以,要验证给定的元素是否满足要求很容易,但反过来,要找到这些元素却不是一件容易的事。\n", - "\n", - "求解无序数据库搜索问题(不妨假设只有一个目标搜索数据),经典算法所需的时间复杂度为$\\mathcal{O}(N)$,而Grover搜索算法所需的时间复杂度仅为$\\mathcal{O}(\\sqrt{N})$,相比经典算法具有平方加速,展示了量子计算的强大性能。此外,Grover搜索算法中用到的振幅扩大技巧,对许多启发式的经典搜索算法可以实现加速,因而具有广泛的应用。\n", - "\n", - "本教案将会介绍Grover搜索算法的基本原理,以及通过两个具体的小例子来展示如何利用MindQuantum实现该算法。\n", - "\n", - "## 2. 问题描述\n", - "\n", - "我们需要在一组无序的$N$元素集合(数据库)中进行搜索。将数据库中的元素与索引(从$0$到$N-1$之间的整数)建立一一对应,我们关注于搜索这些元素的索引。考虑将该搜索问题表示为一个关于输入$x$的函数$f(x)$,其中$x$为$0$到$N-1$之间的整数。那么,函数$f$定义为:\n", - "$$\n", - "\\begin{equation}\n", - "f(x)=\\begin{cases}0,x\\neq x_{target}\\\\\\\\\n", - "1,x=x_{target}\n", - "\\end{cases}\n", - "\\end{equation}.\n", - "$$\n", - "不是一般性,假设$N=2^n$​,那么在量子系统中,索引以量子态$|0\\rangle,|1\\rangle,...,|N-1\\rangle$​(或$|00...0\\rangle,|00...1\\rangle,...,|11...1\\rangle$​)表示,也即我们可以使用$n$​个量子比特存储这些索引。\n", - "\n", - "同时假设搜索问题只有一个目标态$|\\omega\\rangle$。Grover搜索算法的目标就是以极大的概率将$|\\omega\\rangle$搜索出来。\n", - "\n", - "## 3. Grover搜索算法的基本原理\n", - "\n", - "Grover搜索算法的基本原理:首先通过`Hadamard`门产生均匀叠加态,然后反复调用Grover迭代(或称为$G$算子),以放大目标项的概率振幅同时抑制非目标项的概率振幅(该方法称之为振幅放大),最后对末态进行测量,那么就能以极大的概率得到目标态$|\\omega\\rangle$​​。\n", - "\n", - "Grover搜索算法主要包括以下步骤:\n", - "\n", - "- Step 1:数据库初始化\n", - "\n", - " 对$|0\\rangle^{\\otimes n}$​​​​执行$H^{\\otimes n}$​​​​​操作,使得数据库被初始为一个均匀叠加态,即\n", - " $$\n", - " |\\psi_0\\rangle=H^{\\otimes n}|0\\rangle^{\\otimes n}=\\frac{1}{\\sqrt{N}}\\sum_{i=0}^{N-1}|i\\rangle.\n", - " $$\n", - "\n", - "- Step 2:Grover迭代\n", - "\n", - " Grover迭代又可以分解为四步:\n", - "\n", - " - Substep 1:执行Oracle算子$U_{\\omega}$​,翻转目标态$|\\omega \\rangle$​​​​​的相位\n", - "\n", - " 为了将需要寻找的数据和其它的数据区别开,最简单的方法就是翻转目标态的相位(增加一个负号),此时我们需要构造一个Oracle算子$U_{\\omega}$,其作用如下:\n", - " $$\n", - " \\begin{equation}\n", - " U_{\\omega}|x\\rangle=\\begin{cases}\n", - " &|x\\rangle,x\\neq \\omega&\\\\\\\\\n", - " -&|x\\rangle,x=\\omega&\n", - " \\end{cases}\n", - " \\end{equation}.\n", - " $$\n", - " 由于当$x=\\omega$​时,$f(\\omega)=1$​,那么$U_{\\omega}$​​的作用还可以表示成:\n", - " $$\n", - " U_{\\omega}|x\\rangle=(-1)^{f(x)}|x\\rangle,\n", - " $$\n", - " 其矩阵表达式为\n", - " $$\n", - " \\begin{equation}\n", - " U_{\\omega}=\n", - " \\left[\n", - " \\begin{array}{ccc}\n", - " (-1)^{f(0)} & 0 & \\dots & 0 \\\\\\\\\n", - " 0 & (-1)^{f(1)} & \\dots & 0 \\\\\\\\\n", - " \\vdots & \\vdots & \\ddots & \\vdots \\\\\\\\\n", - " 0 & 0 & \\dots & (-1)^{f(N-1)}\n", - " \\end{array}\n", - " \\right] \n", - " \\end{equation}.\n", - " $$\n", - "\n", - " - Substep 2:执行$H^{\\otimes n}$操作\n", - "\n", - " 对$n$位量子比特执行$H^{\\otimes n}$操作。\n", - "\n", - " - Substep 3:执行条件相移算子$P$\n", - "\n", - " 条件相移算子$P$能使$|0\\rangle$​态以外的每个态的相位都翻转,其作用如下:\n", - " $$\n", - " \\begin{equation}\n", - " P|x\\rangle=\\begin{cases}&|0\\rangle,x= 0&\\\\\\\\\n", - " -&|x\\rangle,x\\neq0&\n", - " \\end{cases}\n", - " \\end{equation}.\n", - " $$\n", - " 其矩阵表达式为\n", - " $$\n", - " \\begin{equation}\n", - " P = 2(|0\\rangle\\langle0|)^{\\otimes n} - I_n =\n", - " \\left[\n", - " \\begin{array}{ccc}\n", - " 1 & 0 & \\dots & 0 \\\\\\\\\n", - " 0 & -1 & \\dots & 0 \\\\\\\\\n", - " \\vdots & \\vdots & \\ddots & \\vdots \\\\\\\\\n", - " 0 & 0 & \\dots & -1\n", - " \\end{array}\n", - " \\right]\n", - " \\end{equation}.\n", - " $$\n", - "\n", - " - Substep 4:再次执行$H^{\\otimes n}$操作\n", - "\n", - " 至此,完整的$G$算子可以表示为\n", - " $$\n", - " G = H^{\\otimes n} [2(|0\\rangle\\langle0|)^{\\otimes n} - I_n] H^{\\otimes n} U_{\\omega}.\n", - " $$\n", - "\n", - " 注意:$G$算子需要迭代的次数为\n", - " $$\n", - " r = \\left[ \\frac{\\pi}{4} \\sqrt{\\frac{N}{M}} \\right] \\sim O(\\sqrt{N}),\n", - " $$\n", - "\n", - " 其中,M表示目标态的个数。\n", - "\n", - "- Step 3:测量\n", - "\n", - " 对末态进行$\\{|0\\rangle,|1\\rangle\\}$基测量,就能以极大的概率得到目标态$|\\omega \\rangle$。\n", - "\n", - "Grover搜索算法的完整量子线路模型如下所示:\n", - "\n", - "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/grover_algorithm_circuit.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/grover_algorithm_circuit.ipynb) \n", - "\n", - "## 4. 构造翻转量子比特相位的酉算子\n", - "\n", - "通过上述介绍,我们发现,Grover搜索算法中最关键的部分就是存在可以翻转量子比特相位的酉算子,Oracle算子$U_{\\omega}$可以翻转目标态的相位,条件相移算子$P$可以翻转$|0\\rangle$态以外的每个态的相位。\n", - "\n", - "接下来,我们将构造可以翻转某一位量子比特相位的酉算子,定义如下:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "def bitphaseflip_operator(phase_inversion_qubit, n_qubits): #定义可以翻转某一位量子比特相位的函数\n", - " s = [1 for i in range(1 << n_qubits)]\n", - " for i in phase_inversion_qubit:\n", - " s[i] = -1\n", - " if s[0] == -1:\n", - " for i in range(len(s)):\n", - " s[i] = -1 * s[i]\n", - " circuit = Circuit()\n", - " length = len(s)\n", - " cz = []\n", - " for i in range(length):\n", - " if s[i] == -1:\n", - " cz.append([])\n", - " current = i\n", - " t = 0\n", - " while current != 0:\n", - " if (current & 1) == 1:\n", - " cz[-1].append(t)\n", - " t += 1\n", - " current = current >> 1\n", - " for j in range(i + 1, length):\n", - " if i & j == i:\n", - " s[j] = -1 * s[j]\n", - " for i in cz:\n", - " if i:\n", - " if len(i) > 1:\n", - " circuit += Z.on(i[-1], i[:-1])\n", - " else:\n", - " circuit += Z.on(i[0])\n", - "\n", - " return circuit" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,`bitphaseflip_operator()`函数就可以实现翻转某一位量子比特的相位,只需要输入需要翻转相位的目标量子态和量子比特总数即可。\n", - "\n", - "举个例子,我们现在生成3​​量子比特的均匀叠加态,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H──\n",
-       "         \n",
-       "q1: ──H──\n",
-       "         \n",
-       "q2: ──H──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H──\n", - " \n", - "q1: ──H──\n", - " \n", - "q2: ──H──" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import mindquantum as mq\n", - "from mindquantum import Circuit, UN, H, Z\n", - "from mindquantum.simulator import Simulator\n", - "\n", - "n_qubits = 3 #设定量子比特数为3\n", - "sim = Simulator('projectq', n_qubits) #使用projectq模拟器,命名为sim\n", - "\n", - "circuit = Circuit() #初始化量子线路,命名为circuit\n", - "circuit += UN(H, n_qubits) #每位量子比特上执行H门操作\n", - "\n", - "sim.apply_circuit(circuit) #通过模拟器sim运行搭建好的量子线路circuit\n", - "\n", - "circuit #打印此时的量子线路circuit" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "√2/4¦000⟩\n", - "√2/4¦001⟩\n", - "√2/4¦010⟩\n", - "√2/4¦011⟩\n", - "√2/4¦100⟩\n", - "√2/4¦101⟩\n", - "√2/4¦110⟩\n", - "√2/4¦111⟩\n" - ] - } - ], - "source": [ - "print(sim.get_qs(True)) #打印模拟器sim中运行量子线路circuit后的末态" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到此时的量子线路,以及我们成功生成了3量子比特的均匀叠加态。\n", - "\n", - "假设我们需要翻转$|4\\rangle$态的相位,只需调用我们定义好的`bitphaseflip_operator()`函数即可,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H─────────●─────────●──\n",
-       "                │         │  \n",
-       "q1: ──H─────────┼────●────●──\n",
-       "                │    │    │  \n",
-       "q2: ──H────Z────Z────Z────Z──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H─────────●─────────●──\n", - " │ │ \n", - "q1: ──H─────────┼────●────●──\n", - " │ │ │ \n", - "q2: ──H────Z────Z────Z────Z──" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sim.reset() #重置模拟器sim维护好的量子态,使得初始化的量子态为|000>\n", - "\n", - "phase_inversion_qubit = [4] #翻转|4>态的相位\n", - "operator = bitphaseflip_operator(phase_inversion_qubit, n_qubits)#调用我们定义好的bitphaseflip_operator()函数\n", - "\n", - "circuit += operator #在量子线路circuit中添加翻转|4>态的相位所需的量子门\n", - "\n", - "sim.apply_circuit(circuit) #通过模拟器sim再次运行搭建好的量子线路circuit\n", - "\n", - "circuit #打印此时的量子线路circuit" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "√2/4¦000⟩\n", - "√2/4¦001⟩\n", - "√2/4¦010⟩\n", - "√2/4¦011⟩\n", - "-√2/4¦100⟩\n", - "√2/4¦101⟩\n", - "√2/4¦110⟩\n", - "√2/4¦111⟩\n" - ] - } - ], - "source": [ - "print(sim.get_qs(True)) #打印模拟器sim中运行量子线路circuit后的末态" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到此时的量子线路,以及$|100\\rangle$​​的相位翻转为-1,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "4\n" - ] - } - ], - "source": [ - "print(int('100', 2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到,发生相位翻转的$|100\\rangle$态即为我们希望相位翻转的$|4\\rangle$态。\n", - "\n", - "假设我们需要翻转除$|0\\rangle$态以外的每个态的相位,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────Z────●────●─────────●──\n",
-       "                │    │         │  \n",
-       "q1: ──H────Z────Z────┼────●────●──\n",
-       "                     │    │    │  \n",
-       "q2: ──H────Z─────────Z────Z────Z──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────Z────●────●─────────●──\n", - " │ │ │ \n", - "q1: ──H────Z────Z────┼────●────●──\n", - " │ │ │ \n", - "q2: ──H────Z─────────Z────Z────Z──" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "n_qubits = 3 #设定量子比特数为3\n", - "sim1 = Simulator('projectq', n_qubits) #使用projectq模拟器,命名为sim1\n", - "\n", - "operator1 = bitphaseflip_operator([i for i in range(1, pow(2, n_qubits))], n_qubits) #调用我们定义好的bitphaseflip_operator()函数,翻转除|0>态以外的每个态的相位,命名为operator1\n", - " \n", - "circuit1 = Circuit() #初始化量子线路,命名为circuit1\n", - "circuit1 += UN(H, n_qubits) #每位量子比特上执行H门操作\n", - "circuit1 += operator1 #在量子线路circuit1中添加翻转除|0>态以外的每个态的相位所需的量子门\n", - "\n", - "sim1.apply_circuit(circuit1) #通过模拟器sim1运行搭建好的量子线路circuit1\n", - "\n", - "circuit1 #打印此时的量子线路circuit1" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "√2/4¦000⟩\n", - "-√2/4¦001⟩\n", - "-√2/4¦010⟩\n", - "-√2/4¦011⟩\n", - "-√2/4¦100⟩\n", - "-√2/4¦101⟩\n", - "-√2/4¦110⟩\n", - "-√2/4¦111⟩\n" - ] - } - ], - "source": [ - "print(sim1.get_qs(True)) #打印模拟器sim1中运行量子线路circuit1后的末态" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到此时的量子线路,以及我们成功翻转除$|0\\rangle$态以外的每个态的相位。\n", - "\n", - "也就是说,我们定义的函数`bitphaseflip_operator()`可以实现Grover搜素算法中的Oracle算子$U_{\\omega}$和条件相移算子$P$。\n", - "\n", - "## 5. 利用MindQuantum实现Grover搜素算法实例\n", - "\n", - "### 5.1 实例1:$n=3$​,$|\\omega\\rangle=|2\\rangle$(单目标)​\n", - "\n", - "首先,我们需要定义$G$算子,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "def G(phase_inversion_qubit, n_qubits): #定义Grover搜索算法中的G算子 \n", - " operator = bitphaseflip_operator(phase_inversion_qubit, n_qubits) \n", - " operator += UN(H, n_qubits) \n", - " operator += bitphaseflip_operator([i for i in range(1, pow(2, n_qubits))], n_qubits) \n", - " operator += UN(H, n_qubits) \n", - " return operator" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后,我们根据Grover搜索算法的量子线路模型在MindQuantum中搭建对应的量子线路:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H─────────●─────────●────H────Z────●────●─────────●────H─────────●─────────●────H────Z────●────●─────────●────H──\n",
-       "                │         │              │    │         │              │         │              │    │         │       \n",
-       "q1: ──H────Z────Z────●────●────H────Z────Z────┼────●────●────H────Z────Z────●────●────H────Z────Z────┼────●────●────H──\n",
-       "                     │    │                   │    │    │                   │    │                   │    │    │       \n",
-       "q2: ──H──────────────Z────Z────H────Z─────────Z────Z────Z────H──────────────Z────Z────H────Z─────────Z────Z────Z────H──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H─────────●─────────●────H────Z────●────●─────────●────H─────────●─────────●────H────Z────●────●─────────●────H──\n", - " │ │ │ │ │ │ │ │ │ │ \n", - "q1: ──H────Z────Z────●────●────H────Z────Z────┼────●────●────H────Z────Z────●────●────H────Z────Z────┼────●────●────H──\n", - " │ │ │ │ │ │ │ │ │ │ \n", - "q2: ──H──────────────Z────Z────H────Z─────────Z────Z────Z────H──────────────Z────Z────H────Z─────────Z────Z────Z────H──" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import numpy as np\n", - "from numpy import pi, sqrt\n", - "\n", - "n_qubits = 3 #设定量子比特数为3\n", - "phase_inversion_qubit = [2] #设定需要翻转相位的目标态,在这里翻转|2>态的相位\n", - "\n", - "N = 2 ** (n_qubits) #计算出数据库中元素的总个数\n", - "M = len(phase_inversion_qubit) #计算出目标态的总个数\n", - "\n", - "r = int(pi / 4 * sqrt(N / M)) #设定G算子迭代次数为r\n", - "\n", - "sim2 = Simulator('projectq', n_qubits) #使用projectq模拟器,命名为sim2\n", - "\n", - "circuit2 = Circuit() #初始化量子线路,命名为circuit2\n", - "circuit2 += UN(H, n_qubits) #每位量子比特上执行H门操作\n", - "\n", - "for i in range(r): #循环执行G算子r次\n", - " circuit2 += G(phase_inversion_qubit, n_qubits)\n", - "\n", - "sim2.apply_circuit(circuit2) #通过模拟器sim2运行搭建好的量子线路circuit2\n", - "\n", - "circuit2 #打印此时的量子线路circuit2" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-√2/16¦000⟩\n", - "-√2/16¦001⟩\n", - "0.9722718241315036¦010⟩\n", - "-√2/16¦011⟩\n", - "-√2/16¦100⟩\n", - "-√2/16¦101⟩\n", - "-√2/16¦110⟩\n", - "-√2/16¦111⟩\n" - ] - } - ], - "source": [ - "print(sim2.get_qs(True)) #打印模拟器sim2中运行量子线路circuit2后的末态" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到,$|010\\rangle$态的振幅为0.9722718241315036,相比于其它的量子态,这是极大的振幅,也就是说,若我们测量此时的状态,将会以极大的概率得到目标态$|010\\rangle$​,运行如下代码进行测量:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
shots: 1000\n",
-       "Keys: q2 q1 q0│0.00     0.2         0.4         0.6         0.8         1.0\n",
-       "──────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n",
-       "           000│▒\n",
-       "\n",
-       "           001│▒\n",
-       "\n",
-       "           010│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n",
-       "\n",
-       "           011│▒\n",
-       "\n",
-       "           100│▒\n",
-       "\n",
-       "           101│▒\n",
-       "\n",
-       "           110│▒\n",
-       "\n",
-       "           111│▒\n",
-       "\n",
-       "{'000': 9, '001': 10, '010': 947, '011': 4, '100': 8, '101': 8, '110': 8, '111': 6}\n",
-       "
\n" - ], - "text/plain": [ - "shots: 1000\n", - "Keys: q2 q1 q0│0.00 0.2 0.4 0.6 0.8 1.0\n", - "──────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n", - " 000│▒\n", - " │ \n", - " 001│▒\n", - " │ \n", - " 010│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n", - " │ \n", - " 011│▒\n", - " │ \n", - " 100│▒\n", - " │ \n", - " 101│▒\n", - " │ \n", - " 110│▒\n", - " │ \n", - " 111│▒\n", - " │ \n", - "{'000': 9, '001': 10, '010': 947, '011': 4, '100': 8, '101': 8, '110': 8, '111': 6}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from mindquantum import Measure\n", - "\n", - "sim2.reset() #重置模拟器sim2维护好的量子态,使得初始化的量子态为|000>\n", - "\n", - "circuit2 += UN(Measure(), circuit2.n_qubits) #对量子线路circuit2中的每一位量子比特添加测量门\n", - "\n", - "result = sim2.sampling(circuit2, shots=1000) #通过模拟器sim2对量子线路circuit2进行1000次的采样\n", - "result #打印采样结果" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到,1000次采样中有947次的采样结果为`010`,将其转化为10进制数,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2\n" - ] - } - ], - "source": [ - "print(int('010', 2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到,我们成功地搜索出$|2\\rangle$态。\n", - "\n", - "### 5.2 实例2:$n=5$,$|\\omega\\rangle=|5\\rangle$和$|11\\rangle$(多目标)\n", - "\n", - "实例1中实现的是单目标搜索,现在我们尝试实现多目标搜索。首先,$G$算子已经定义好了,我们只需设定量子比特数和需要翻转相位的目标态,然后搭建对应的量子线路即可,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
q0: ──H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H──\n",
-       "           │    │    │    │    │    │    │    │              │    │         │    │         │         │         │    │         │         │         │         │         │         │         │         │    │    │    │    │    │    │    │              │    │         │    │         │         │         │    │         │         │         │         │         │         │         │         │    │    │    │    │    │    │    │              │    │         │    │         │         │         │    │         │         │         │         │         │         │         │       \n",
-       "q1: ──H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H──\n",
-       "           │    │    │    │    │    │    │    │                   │    │    │    │    │    │         │    │    │    │    │    │         │    │    │         │    │    │         │    │    │         │    │    │    │    │    │    │    │                   │    │    │    │    │    │         │    │    │    │    │    │         │    │    │         │    │    │         │    │    │         │    │    │    │    │    │    │    │                   │    │    │    │    │    │         │    │    │    │    │    │         │    │    │         │    │    │         │    │    │       \n",
-       "q2: ──H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H──\n",
-       "                     │    │    │    │    │    │                                  │    │    │    │    │    │    │    │    │    │    │    │    │    │         │    │    │    │    │    │    │                   │    │    │    │    │    │                                  │    │    │    │    │    │    │    │    │    │    │    │    │    │         │    │    │    │    │    │    │                   │    │    │    │    │    │                                  │    │    │    │    │    │    │    │    │    │    │    │    │    │         │    │    │    │    │    │    │       \n",
-       "q3: ──H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──\n",
-       "                               │    │    │    │                                                                     │    │    │    │    │    │    │    │    │    │    │    │    │    │    │                             │    │    │    │                                                                     │    │    │    │    │    │    │    │    │    │    │    │    │    │    │                             │    │    │    │                                                                     │    │    │    │    │    │    │    │    │    │    │    │    │    │    │       \n",
-       "q4: ──H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H──\n",
-       "
\n" - ], - "text/plain": [ - "q0: ──H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H──\n", - " │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ \n", - "q1: ──H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H──\n", - " │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ \n", - "q2: ──H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H──\n", - " │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ \n", - "q3: ──H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──\n", - " │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ \n", - "q4: ──H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H──" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "n_qubits = 5 #设定量子比特数为5\n", - "phase_inversion_qubit = [5, 11] #设定需要翻转相位的目标态,在这里翻转|5>态和|11>态的相位\n", - "\n", - "N = 2 ** (n_qubits) #计算出数据库中元素的总个数\n", - "M = len(phase_inversion_qubit) #计算出目标态的总个数\n", - "\n", - "r = int(pi / 4 * sqrt(N / M)) #设定G算子迭代次数为r\n", - "\n", - "sim3 = Simulator('projectq', n_qubits) #使用projectq模拟器,命名为sim3\n", - "\n", - "circuit3 = Circuit() #初始化量子线路,命名为circuit3\n", - "circuit3 += UN(H, n_qubits) #每位量子比特上执行H门操作\n", - "\n", - "for i in range(r): #循环执行G算子r次\n", - " circuit3 += G(phase_inversion_qubit, n_qubits)\n", - "\n", - "sim3.apply_circuit(circuit3) #通过模拟器sim3运行搭建好的量子线路circuit3\n", - "\n", - "circuit3 #打印此时的量子线路circuit3" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-0.03590776623212942¦00000⟩\n", - "-0.03590776623212939¦00001⟩\n", - "-0.03590776623212932¦00010⟩\n", - "-0.03590776623212946¦00011⟩\n", - "-0.03590776623212935¦00100⟩\n", - "0.6932961018664991¦00101⟩\n", - "-0.035907766232129434¦00110⟩\n", - "-0.03590776623212945¦00111⟩\n", - "-0.035907766232129434¦01000⟩\n", - "-0.03590776623212945¦01001⟩\n", - "-0.03590776623212935¦01010⟩\n", - "0.6932961018664991¦01011⟩\n", - "-0.03590776623212932¦01100⟩\n", - "-0.03590776623212946¦01101⟩\n", - "-0.03590776623212939¦01110⟩\n", - "-0.03590776623212939¦01111⟩\n", - "-0.0359077662321294¦10000⟩\n", - "-0.03590776623212941¦10001⟩\n", - "-0.035907766232129414¦10010⟩\n", - "-0.035907766232129434¦10011⟩\n", - "-0.03590776623212944¦10100⟩\n", - "-0.035907766232129434¦10101⟩\n", - "-0.03590776623212944¦10110⟩\n", - "-0.035907766232129434¦10111⟩\n", - "-0.03590776623212943¦11000⟩\n", - "-0.035907766232129434¦11001⟩\n", - "-0.03590776623212943¦11010⟩\n", - "-0.035907766232129434¦11011⟩\n", - "-0.0359077662321294¦11100⟩\n", - "-0.035907766232129434¦11101⟩\n", - "-0.035907766232129414¦11110⟩\n", - "-0.03590776623212941¦11111⟩\n" - ] - } - ], - "source": [ - "print(sim3.get_qs(True)) #打印模拟器sim3中运行量子线路circuit3后的末态" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到,$|00101\\rangle$​​和$|01011\\rangle$​​态的振幅均为0.6932961018664989,相比于其它的量子态,这是极大的振幅,也就是说,若我们测量此时的状态,将会以极大的概率得到目标态$|00101\\rangle$​​和$|01011\\rangle$​​态,运行如下代码进行测量:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
shots: 1000\n",
-       "Keys: q4 q3 q2 q1 q0│0.00   0.126       0.251       0.377       0.503       0.629\n",
-       "────────────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n",
-       "               00000│▒\n",
-       "\n",
-       "               00001│▒\n",
-       "\n",
-       "               00100│▒\n",
-       "\n",
-       "               00101│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\n",
-       "\n",
-       "               00110│▒\n",
-       "\n",
-       "               00111│▒\n",
-       "\n",
-       "               01000│▒\n",
-       "\n",
-       "               01001│▒\n",
-       "\n",
-       "               01010│▒\n",
-       "\n",
-       "               01011│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n",
-       "\n",
-       "               01100│▒\n",
-       "\n",
-       "               01101│▒\n",
-       "\n",
-       "               01111│▒\n",
-       "\n",
-       "               10000│▒\n",
-       "\n",
-       "               10001│▒\n",
-       "\n",
-       "               10011│▒\n",
-       "\n",
-       "               10100│▒\n",
-       "\n",
-       "               10101│▒\n",
-       "\n",
-       "               10111│▒\n",
-       "\n",
-       "               11000│▒\n",
-       "\n",
-       "               11011│▒\n",
-       "\n",
-       "               11100│▒\n",
-       "\n",
-       "               11101│▒\n",
-       "\n",
-       "               11111│▒\n",
-       "\n",
-       "{'00000': 2, '00001': 2, '00100': 1, '00101': 463, '00110': 2, '00111': 3, '01000': 1, \n",
-       "'01001': 1, '01010': 1, '01011': 503, '01100': 1, '01101': 1, '01111': 1, '10000': 1, \n",
-       "'10001': 1, '10011': 2, '10100': 2, '10101': 2, '10111': 1, '11000': 3, '11011': 1, '11100': \n",
-       "2, '11101': 2, '11111': 1}\n",
-       "
\n" - ], - "text/plain": [ - "shots: 1000\n", - "Keys: q4 q3 q2 q1 q0│0.00 0.126 0.251 0.377 0.503 0.629\n", - "────────────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴\n", - " 00000│▒\n", - " │ \n", - " 00001│▒\n", - " │ \n", - " 00100│▒\n", - " │ \n", - " 00101│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\n", - " │ \n", - " 00110│▒\n", - " │ \n", - " 00111│▒\n", - " │ \n", - " 01000│▒\n", - " │ \n", - " 01001│▒\n", - " │ \n", - " 01010│▒\n", - " │ \n", - " 01011│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n", - " │ \n", - " 01100│▒\n", - " │ \n", - " 01101│▒\n", - " │ \n", - " 01111│▒\n", - " │ \n", - " 10000│▒\n", - " │ \n", - " 10001│▒\n", - " │ \n", - " 10011│▒\n", - " │ \n", - " 10100│▒\n", - " │ \n", - " 10101│▒\n", - " │ \n", - " 10111│▒\n", - " │ \n", - " 11000│▒\n", - " │ \n", - " 11011│▒\n", - " │ \n", - " 11100│▒\n", - " │ \n", - " 11101│▒\n", - " │ \n", - " 11111│▒\n", - " │ \n", - "{'00000': 2, '00001': 2, '00100': 1, '00101': 463, '00110': 2, '00111': 3, '01000': 1, '01001': 1, '01010': 1, '01011': 503, '01100': 1, '01101': 1, '01111': 1, '10000': 1, '10001': 1, '10011': 2, '10100': 2, '10101': 2, '10111': 1, '11000': 3, '11011': 1, '11100': 2, '11101': 2, '11111': 1}" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sim3.reset() #重置模拟器sim3维护好的量子态,使得初始化的量子态为|00000>\n", - "\n", - "circuit3 += UN(Measure(), circuit3.n_qubits) #对量子线路circuit3中的每一位量子比特添加测量门\n", - "\n", - "result1 = sim3.sampling(circuit3, shots=1000) #通过模拟器sim3对量子线路circuit3进行1000次的采样\n", - "result1 #打印采样结果" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到,1000次采样中有463次的采样结果为`00101`和503次的采样结果为`01011`,将其转化为10进制数,运行如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5\n", - "11\n" - ] - } - ], - "source": [ - "print(int('00101', 2))\n", - "print(int('01011', 2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从运行的结果看到,我们成功地搜索出$|5\\rangle$​​和$|11\\rangle$​​​​​态。\n", - "\n", - "至此,我们介绍了Grover搜索算法的基本原理,以及通过两个具体的小例子来展示如何利用MindQuantum实现该算法!赶紧动手体验一下量子编程的乐趣吧!\n", - "\n", - "若想查询更多关于MindQuantum的API,请点击:https://mindspore.cn/mindquantum/。\n", - "\n", - "\n", - "### **参考文献:**\n", - "\n", - "\\[1\\] L. K. Grover, A fast quantum mechanical algorithm for database search\\[C\\]// Proceedings of the twenty-eighth annual ACM symposium on Theory of computing. ACM, 1996: 212-219.\n", - "\n", - "\\[2\\] G. Brassard, P. Hoyer, M. Mosca, et al. Quantum amplitude amplification and estimation\\[J\\]. Contemporary Mathematics, 2002, 305: 53-74." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorials/README.md b/tutorials/README.md deleted file mode 100644 index 365c37148..000000000 --- a/tutorials/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# MindQuantum Tutorials - -TODO: 🔥🔥🔥🔥🔥《文档编写》↪️编写该README文档,简单介绍一些这些tutorial,并列举目录 - -TODO: 🔥🔥🔥🔥🔥《新tutorial1》↪️利用MindQuantum的模拟器来运行Grover算法,可参考https://hiq.huaweicloud.com/doc/algorithms/GroverAlgorithm.html - -TODO: 🔥🔥🔥🔥🔥《新tutorial2》↪️利用MindQuantum的模拟器来运行相位估计算法,可参考https://hiq.huaweicloud.com/doc/algorithms/introduction_to_phase_estimation.html - -TODO: 🔥🔥🔥🔥🔥《新tutorial3》↪️利用MindQuantum的模拟器来运行Shor算法,可参考https://hiq.huaweicloud.com/doc/algorithms/ShorAlgorithm.html# diff --git a/tutorials/benchmarks/README.md b/tutorials/benchmarks/README.md index 3ffe8ec20..d6e05a9bb 100644 --- a/tutorials/benchmarks/README.md +++ b/tutorials/benchmarks/README.md @@ -8,4 +8,4 @@ ## Content Description -These scripts are going to test the performance between MindQuantum and other frameworks. Please read the material in each folder for detail description. +These scripts are going to test the performance between MindQuantum and other frameworks. Please read the material in each folder for detail description. \ No newline at end of file diff --git a/tutorials/benchmarks/grad/_parse_args.py b/tutorials/benchmarks/grad/_parse_args.py index 722c4e43d..b0b5f5368 100644 --- a/tutorials/benchmarks/grad/_parse_args.py +++ b/tutorials/benchmarks/grad/_parse_args.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tutorials/benchmarks/grad/mindquantum_grad.py b/tutorials/benchmarks/grad/mindquantum_grad.py index 8609983d3..9946d8a7a 100644 --- a/tutorials/benchmarks/grad/mindquantum_grad.py +++ b/tutorials/benchmarks/grad/mindquantum_grad.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,17 +16,17 @@ import time import os from _parse_args import parser - args = parser.parse_args() os.environ['OMP_NUM_THREADS'] = str(args.omp_num_threads) import numpy as np -from mindquantum.core import QubitOperator -from mindquantum.core import Circuit, X, H, XX, ZZ, Hamiltonian -from mindquantum.simulator import Simulator +from mindquantum.ops import QubitOperator +from mindquantum import Circuit, X, H, XX, ZZ, RX, Hamiltonian +from mindquantum.nn import generate_pqc_operator import mindspore.context as context +from mindspore import Tensor import tqdm -context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") +context.set_context(mode=context.GRAPH_MODE, device_target="CPU") class CircuitLayerBuilder(): @@ -43,12 +42,7 @@ def add_layer(self, circuit, gate, prefix): def convert_to_circuit(image, data_qubits=None): - """ - convert_to_circuit - - Returns: - Circuit - """ + """convert_to_circuit""" values = np.ndarray.flatten(image) if data_qubits is None: data_qubits = range(len(values)) @@ -61,12 +55,7 @@ def convert_to_circuit(image, data_qubits=None): def create_quantum_model(n_qubits): - """ - Create QNN. - - Returns: - tuple - """ + """Create QNN.""" data_qubits = range(1, n_qubits) readout = 0 c = Circuit() @@ -86,15 +75,18 @@ def create_quantum_model(n_qubits): x_train_circ = [convert_to_circuit(x, range(1, n_qubits)) for x in x_train_bin] ansatz, ham = create_quantum_model(n_qubits) -model_params_names = ansatz.params_name -ops = Simulator('projectq', ansatz.n_qubits).get_expectation_with_grad( - ham, ansatz, parallel_worker=args.parallel_worker) +model_para_names = ansatz.para_name +ops = generate_pqc_operator(model_para_names, ['null'], + RX('null').on(0) + ansatz, + ham, + n_threads=args.parallel_worker) t0 = time.time() eval_time = [] for x in tqdm.tqdm(x_train_circ[:args.num_sampling]): eval_time.append(time.time()) - ops(np.random.normal(size=32)) + ops(Tensor(np.random.normal(size=(1, 32)).astype(np.float32)), + Tensor(np.array([0]).astype(np.float32))) eval_time[-1] = time.time() - eval_time[-1] eval_time = np.sort(eval_time[1:]) t1 = time.time() diff --git a/tutorials/benchmarks/grad/tensorflow_quantum_grad.py b/tutorials/benchmarks/grad/tensorflow_quantum_grad.py index fe8a12df4..d5c4617d7 100644 --- a/tutorials/benchmarks/grad/tensorflow_quantum_grad.py +++ b/tutorials/benchmarks/grad/tensorflow_quantum_grad.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tutorials/benchmarks/mnist/README.md b/tutorials/benchmarks/mnist/README.md index 17568a930..424ba01fc 100644 --- a/tutorials/benchmarks/mnist/README.md +++ b/tutorials/benchmarks/mnist/README.md @@ -2,7 +2,7 @@ These scripts are going to test the performance between MindQuantum and TensorFlow Quantum on mnist dataset. -Please install TensorFlow Quantum before running these scripts. +Please install TensorFlow Quantum before runing these scripts. ## MindQuantum diff --git a/tutorials/benchmarks/mnist/_parse_args.py b/tutorials/benchmarks/mnist/_parse_args.py index 722c4e43d..b0b5f5368 100644 --- a/tutorials/benchmarks/mnist/_parse_args.py +++ b/tutorials/benchmarks/mnist/_parse_args.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tutorials/benchmarks/mnist/mnist.py b/tutorials/benchmarks/mnist/mnist.py index 22521b74b..23e7312ea 100644 --- a/tutorials/benchmarks/mnist/mnist.py +++ b/tutorials/benchmarks/mnist/mnist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,11 +18,9 @@ import os import numpy as np from _parse_args import parser - args = parser.parse_args() os.environ['OMP_NUM_THREADS'] = str(args.omp_num_threads) -import mindspore as ms import mindspore.context as context import mindspore.dataset as ds from mindspore.ops import operations as P @@ -31,10 +28,9 @@ from mindspore import nn from mindspore import Model from mindspore.train.callback import Callback -from mindquantum.core import Circuit, X, Z, H, XX, ZZ, Hamiltonian, RX, QubitOperator -from mindquantum.framework import MQLayer -from mindquantum.simulator import Simulator -ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target="CPU") +from mindquantum import Circuit, X, Z, H, XX, ZZ, Hamiltonian, RX +from mindquantum.nn import MindQuantumLayer +from mindquantum.ops import QubitOperator class FPSMonitor(Callback): @@ -85,12 +81,7 @@ def construct(self, x): def encoder_circuit_builder(n_qubits_range, prefix='encoder'): - """ - RX encoder circuit. - - Returns: - Circuit - """ + """RX encoder circuit.""" c = Circuit() for i in n_qubits_range: c += RX('{}_{}'.format(prefix, i)).on(i) @@ -111,12 +102,7 @@ def add_layer(self, circuit, gate, prefix): def create_quantum_model(n_qubits): - """ - Create QNN. - - Returns: - tuple - """ + """Create QNN.""" data_qubits = range(1, n_qubits) readout = 0 c = Circuit() @@ -130,12 +116,7 @@ def create_quantum_model(n_qubits): def binary_encoder(image, n_qubits=None): - """ - Input a binary image into data supported by RX encoder. - - Returns: - numbers.Number - """ + """Input a binary image into data supported by RX encoder.""" values = np.ndarray.flatten(image) if n_qubits is None: n_qubits = len(values) @@ -144,12 +125,7 @@ def binary_encoder(image, n_qubits=None): def generate_dataset(data_file_path, n_qubits, sampling_num, batch_num, eval_size_num): - """ - Generate train and test dataset. - - Returns: - Dataset - """ + """Generate train and test dataset.""" data = np.load(data_file_path) x_train_bin, y_train_nocon, x_test_bin, y_test_nocon = data['arr_0'], data[ 'arr_1'], data['arr_2'], data['arr_3'] @@ -180,6 +156,7 @@ def generate_dataset(data_file_path, n_qubits, sampling_num, batch_num, if __name__ == '__main__': + context.set_context(mode=context.GRAPH_MODE, device_target="CPU") n = 17 num_sampling = args.num_sampling eval_size = 100 @@ -192,16 +169,16 @@ def generate_dataset(data_file_path, n_qubits, sampling_num, batch_num, ansatz, read_out = create_quantum_model(n) encoder_circuit = encoder_circuit_builder(range(1, n)) encoder_circuit.no_grad() - encoder_names = encoder_circuit.params_name - ansatz_names = ansatz.params_name + encoder_names = encoder_circuit.para_name + ansatz_names = ansatz.para_name ham = Hamiltonian(QubitOperator('Z0')) - circ = encoder_circuit + ansatz - sim = Simulator('projectq', circ.n_qubits) - grad_ops = sim.get_expectation_with_grad(ham, circ, None, encoder_names, - ansatz_names, parallel_worker) - mql = MQLayer(grad_ops, 'normal') - + mql = MindQuantumLayer(encoder_names, + ansatz_names, + encoder_circuit + ansatz, + ham, + weight_init='normal', + n_threads=parallel_worker) mnist_net = MnistNet(mql) net_loss = Hinge() net_opt = nn.Adam(mnist_net.trainable_params()) diff --git a/tutorials/benchmarks/mnist/mnist_tf.py b/tutorials/benchmarks/mnist/mnist_tf.py index c3cf0931f..5ad53981c 100644 --- a/tutorials/benchmarks/mnist/mnist_tf.py +++ b/tutorials/benchmarks/mnist/mnist_tf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tutorials/benchmarks/qaoa/_parse_args.py b/tutorials/benchmarks/qaoa/_parse_args.py index 722c4e43d..b0b5f5368 100644 --- a/tutorials/benchmarks/qaoa/_parse_args.py +++ b/tutorials/benchmarks/qaoa/_parse_args.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tutorials/benchmarks/qaoa/qaoa_mindquantum.py b/tutorials/benchmarks/qaoa/qaoa_mindquantum.py index 648c035b4..8f521f4b5 100644 --- a/tutorials/benchmarks/qaoa/qaoa_mindquantum.py +++ b/tutorials/benchmarks/qaoa/qaoa_mindquantum.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,21 +16,20 @@ import time import os from _parse_args import parser - args = parser.parse_args() os.environ['OMP_NUM_THREADS'] = str(args.omp_num_threads) import numpy as np +from mindquantum.ops import QubitOperator import mindspore.context as context import mindspore.dataset as ds import mindspore.nn as nn -from mindquantum.core import QubitOperator -from mindquantum.core import Hamiltonian -from mindquantum.core import Circuit -from mindquantum.core import RX, X, RZ, H -from mindquantum.core import UN -from mindquantum.simulator import Simulator -from mindquantum.framework import MQAnsatzOnlyLayer -context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") +from mindspore import Model +from mindspore.train.callback import LossMonitor +from mindquantum import Hamiltonian +from mindquantum import Circuit +from mindquantum import RX, X, RZ, H +from mindquantum.circuit import UN +from mindquantum.nn import MindQuantumLayer def circuit_qaoa(p): @@ -61,18 +59,25 @@ def circuit_qaoa(p): ham = Hamiltonian(ham) circ = circuit_qaoa(p) -ansatz_name = circ.params_name -net = MQAnsatzOnlyLayer( - Simulator('projectq', circ.n_qubits).get_expectation_with_grad(ham, circ)) +ansatz_name = circ.para_name +net = MindQuantumLayer(['null'], ansatz_name, RX('null').on(0) + circ, ham) train_loader = ds.NumpySlicesDataset({ 'x': np.array([[0]]).astype(np.float32), 'y': np.array([0]).astype(np.float32) }).batch(1) + +class Loss(nn.MSELoss): + """Loss""" + def construct(self, base, target): + return self.get_loss(-base) + + +context.set_context(mode=context.GRAPH_MODE, device_target="CPU") +net_loss = Loss() net_opt = nn.Adam(net.trainable_params(), learning_rate=LR) -train_net = nn.TrainOneStepCell(net, net_opt) +model = Model(net, net_loss, net_opt) t0 = time.time() -for i in range(ITR): - train_net() +model.train(ITR, train_loader, callbacks=[LossMonitor()]) t1 = time.time() print('Total time for mindquantum :{}'.format(t1 - t0)) diff --git a/tutorials/benchmarks/qaoa/qaoa_paddle.py b/tutorials/benchmarks/qaoa/qaoa_paddle.py index 4f7292b4c..bc20378ca 100644 --- a/tutorials/benchmarks/qaoa/qaoa_paddle.py +++ b/tutorials/benchmarks/qaoa/qaoa_paddle.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tutorials/images/error_circuit.png b/tutorials/images/error_circuit.png deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/images/faq_circuit1.jpg b/tutorials/images/faq_circuit1.jpg deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/images/faq_circuit2.jpg b/tutorials/images/faq_circuit2.jpg deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/images/faq_circuit3.jpg b/tutorials/images/faq_circuit3.jpg deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/images/faq_circuit4.jpg b/tutorials/images/faq_circuit4.jpg deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/images/faq_circuit5.jpg b/tutorials/images/faq_circuit5.jpg deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/images/grover_algorithm_circuit.png b/tutorials/images/grover_algorithm_circuit.png deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/images/quantum_phase_estimation.png b/tutorials/images/quantum_phase_estimation.png deleted file mode 100644 index e69de29bb..000000000 diff --git a/tutorials/qnn_for_nlp.ipynb b/tutorials/qnn_for_nlp.ipynb new file mode 100644 index 000000000..d1d45f6b9 --- /dev/null +++ b/tutorials/qnn_for_nlp.ipynb @@ -0,0 +1,845 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 量子神经网络在自然语言处理中的应用\n", + "\n", + "\n", + "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", + "\n", + "[![](https://gitee.com/mindspore/docs/raw/master/resource/_static/logo_source.png)](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/qnn_for_nlp.ipynb)\n", + "\n", + "## 概述\n", + "\n", + "在自然语言处理过程中,词嵌入(Word embedding)是其中的重要步骤,它是一个将高维度空间的词向量嵌入到一个维数更低的连续向量空间的过程。当给予神经网络的语料信息不断增加时,网络的训练过程将越来越困难。利用量子力学的态叠加和纠缠等特性,我们可以利用量子神经网络来处理这些经典语料信息,加入其训练过程,并提高收敛精度。下面,我们将简单地搭建一个量子经典混合神经网络来完成一个词嵌入任务。\n", + "\n", + "\n", + "## 环境准备\n", + "\n", + "导入本教程所依赖模块\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import time\n", + "from mindquantum.ops import QubitOperator\n", + "import mindspore.ops as ops\n", + "import mindspore.dataset as ds\n", + "from mindspore import nn\n", + "from mindspore.train.callback import LossMonitor\n", + "from mindspore import Model\n", + "from mindquantum.nn import MindQuantumLayer\n", + "from mindquantum import Hamiltonian, Circuit, RX, RY, X, H, UN" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "本教程实现的是一个[CBOW模型](https://blog.csdn.net/u010665216/article/details/78724856),即利用某个词所处的环境来预测该词。例如对于“I love natural language processing”这句话,我们可以将其切分为5个词,\\[\"I\", \"love\", \"natural\", \"language\", \"processing”\\],在所选窗口为2时,我们要处理的问题是利用\\[\"I\", \"love\", \"language\", \"processing\"\\]来预测出目标词汇\"natural\"。这里我们以窗口为2为例,搭建如下的量子神经网络,来完成词嵌入任务。\n", + "\n", + "![quantum word embedding](https://gitee.com/mindspore/docs/raw/master/docs/mindquantum/docs/source_zh_cn/images/qcbow.png)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,编码线路会将\"I\"、\"love\"、\"language\"和\"processing\"的编码信息编码到量子线路中,待训练的量子线路由四个Ansatz线路构成,最后我们在量子线路末端对量子比特做$\\text{Z}$基矢上的测量,具体所需测量的比特的个数由所需嵌入空间的维数确定。\n", + "\n", + "## 数据预处理\n", + "\n", + "我们对所需要处理的语句进行处理,生成关于该句子的词典,并根据窗口大小来生成样本点。\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def GenerateWordDictAndSample(corpus, window=2):\n", + " all_words = corpus.split()\n", + " word_set = list(set(all_words))\n", + " word_set.sort()\n", + " word_dict = {w: i for i,w in enumerate(word_set)}\n", + " sampling = []\n", + " for index, word in enumerate(all_words[window:-window]):\n", + " around = []\n", + " for i in range(index, index + 2*window + 1):\n", + " if i != index + window:\n", + " around.append(all_words[i])\n", + " sampling.append([around,all_words[index + window]])\n", + " return word_dict, sampling" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'I': 0, 'language': 1, 'love': 2, 'natural': 3, 'processing': 4}\n", + "word dict size: 5\n", + "samples: [[['I', 'love', 'language', 'processing'], 'natural']]\n", + "number of samples: 1\n" + ] + } + ], + "source": [ + "word_dict, sample = GenerateWordDictAndSample(\"I love natural language processing\")\n", + "print(word_dict)\n", + "print('word dict size: ', len(word_dict))\n", + "print('samples: ', sample)\n", + "print('number of samples: ', len(sample))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "根据如上信息,我们得到该句子的词典大小为5,能够产生一个样本点。\n", + "\n", + "## 编码线路\n", + "\n", + "为了简单起见,我们使用的编码线路由$\\text{RX}$旋转门构成,结构如下。\n", + "\n", + "![encoder circuit](https://gitee.com/mindspore/docs/raw/master/docs/mindquantum/docs/source_zh_cn/images/encoder.png)\n", + "\n", + "我们对每个量子门都作用一个$\\text{RX}$旋转门。" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def GenerateEncoderCircuit(n_qubits, prefix=''):\n", + " if len(prefix) != 0 and prefix[-1] != '_':\n", + " prefix += '_'\n", + " circ = Circuit()\n", + " for i in range(n_qubits):\n", + " circ += RX(prefix + str(i)).on(i)\n", + " return circ" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RX(e_0|0)\n", + "RX(e_1|1)\n", + "RX(e_2|2)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "GenerateEncoderCircuit(3,prefix='e')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们通常用$\\left|0\\right>$和$\\left|1\\right>$来标记二能级量子比特的两个状态,由态叠加原理,量子比特还可以处于这两个状态的叠加态:\n", + "\n", + "$$\\left|\\psi\\right>=\\alpha\\left|0\\right>+\\beta\\left|1\\right>$$\n", + "\n", + "对于$n$比特的量子态,其将处于$2^n$维的希尔伯特空间中。对于上面由5个词构成的词典,我们只需要$\\lceil \\log_2 5 \\rceil=3$个量子比特即可完成编码,这也体现出量子计算的优越性。\n", + "\n", + "例如对于上面词典中的\"love\",其对应的标签为2,2的二进制表示为`010`,我们只需将编码线路中的`e_0`、`e_1`和`e_2`分别设为$0$、$\\pi$和$0$即可。下面我们通过`Evolution`算子来验证以下。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Label is: 2\n", + "Binary label is: 010\n", + "Parameters of encoder is: \n", + " [0. 3.14159 0. ]\n", + "Encoder circuit is: \n", + " RX(e_0|0)\n", + "RX(e_1|1)\n", + "RX(e_2|2)\n", + "Encoder parameter names are: \n", + " ['e_0', 'e_1', 'e_2']\n", + "Amplitude of quantum state is: \n", + " [0. 0. 1. 0. 0. 0. 0. 0.]\n", + "Label in quantum state is: 2\n" + ] + } + ], + "source": [ + "from mindquantum.nn import generate_evolution_operator\n", + "from mindspore import context\n", + "from mindspore import Tensor\n", + "\n", + "n_qubits = 3 # number of qubits of this quantum circuit\n", + "label = 2 # label need to encode\n", + "label_bin = bin(label)[-1:1:-1].ljust(n_qubits,'0') # binary form of label\n", + "label_array = np.array([int(i)*np.pi for i in label_bin]).astype(np.float32) # parameter value of encoder\n", + "encoder = GenerateEncoderCircuit(n_qubits, prefix='e') # encoder circuit\n", + "encoder_para_names = encoder.para_name # parameter names of encoder\n", + "\n", + "print(\"Label is: \", label)\n", + "print(\"Binary label is: \", label_bin)\n", + "print(\"Parameters of encoder is: \\n\", np.round(label_array, 5))\n", + "print(\"Encoder circuit is: \\n\", encoder)\n", + "print(\"Encoder parameter names are: \\n\", encoder_para_names)\n", + "\n", + "context.set_context(mode=context.GRAPH_MODE, device_target=\"CPU\")\n", + "# quantum state evolution operator\n", + "evol = generate_evolution_operator(param_names=encoder_para_names, circuit=encoder)\n", + "state = evol(Tensor(label_array))\n", + "amp = np.round(np.abs(state)**2, 3)\n", + "\n", + "print(\"Amplitude of quantum state is: \\n\", amp)\n", + "print(\"Label in quantum state is: \", np.argmax(amp))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过上面的验证,我们发现,对于标签为2的数据,最后得到量子态的振幅最大的位置也是2,因此得到的量子态正是对输入标签的编码。我们将对数据编码生成参数数值的过程总结成如下函数。" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def GenerateTrainData(sample, word_dict):\n", + " n_qubits = np.int(np.ceil(np.log2(1 + max(word_dict.values()))))\n", + " data_x = []\n", + " data_y = []\n", + " for around, center in sample:\n", + " data_x.append([])\n", + " for word in around:\n", + " label = word_dict[word]\n", + " label_bin = bin(label)[-1:1:-1].ljust(n_qubits,'0')\n", + " label_array = [int(i)*np.pi for i in label_bin]\n", + " data_x[-1].extend(label_array)\n", + " data_y.append(word_dict[center])\n", + " return np.array(data_x).astype(np.float32), np.array(data_y).astype(np.int32)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([[0. , 0. , 0. , 0. , 3.1415927, 0. ,\n", + " 3.1415927, 0. , 0. , 0. , 0. , 3.1415927]],\n", + " dtype=float32),\n", + " array([3], dtype=int32))" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "GenerateTrainData(sample, word_dict)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "根据上面的结果,我们将4个输入的词编码的信息合并为一个更长向量,便于后续神经网络调用。\n", + "\n", + "## Ansatz线路\n", + "\n", + "Ansatz线路的选择多种多样,我们选择如下的量子线路作为Ansatz线路,它的一个单元由一层$\\text{RY}$门和一层$\\text{CNOT}$门构成,对此单元重复$p$次构成整个Ansatz线路。\n", + "\n", + "![ansatz circuit](https://gitee.com/mindspore/docs/raw/master/docs/mindquantum/docs/source_zh_cn/images/ansatz.png)\n", + "\n", + "定义如下函数生成Ansatz线路。" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def GenerateAnsatzCircuit(n_qubits, layers, prefix=''):\n", + " if len(prefix) != 0 and prefix[-1] != '_':\n", + " prefix += '_'\n", + " circ = Circuit()\n", + " for l in range(layers):\n", + " for i in range(n_qubits):\n", + " circ += RY(prefix + str(l) + '_' + str(i)).on(i)\n", + " for i in range(l % 2, n_qubits, 2):\n", + " if i < n_qubits and i + 1 < n_qubits:\n", + " circ += X.on(i + 1, i)\n", + " return circ" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RY(a_0_0|0)\n", + "RY(a_0_1|1)\n", + "RY(a_0_2|2)\n", + "RY(a_0_3|3)\n", + "RY(a_0_4|4)\n", + "X(1 <-: 0)\n", + "X(3 <-: 2)\n", + "RY(a_1_0|0)\n", + "RY(a_1_1|1)\n", + "RY(a_1_2|2)\n", + "RY(a_1_3|3)\n", + "RY(a_1_4|4)\n", + "X(2 <-: 1)\n", + "X(4 <-: 3)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "GenerateAnsatzCircuit(5, 2, 'a')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 测量\n", + "\n", + "我们把对不同比特位上的测量结果作为降维后的数据。具体过程与比特编码类似,例如当我们想将词向量降维为5维向量时,对于第3维的数据可以如下产生:\n", + "\n", + "- 3对应的二进制为`00011`。\n", + "- 测量量子线路末态对$Z_0Z_1$哈密顿量的期望值。\n", + "\n", + "下面函数将给出产生各个维度上数据所需的哈密顿量(hams),其中`n_qubits`表示线路的比特数,`dims`表示词嵌入的维度:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def GenerateEmbeddingHamiltonian(dims, n_qubits):\n", + " hams = []\n", + " for i in range(dims):\n", + " s = ''\n", + " for j, k in enumerate(bin(i + 1)[-1:1:-1]):\n", + " if k == '1':\n", + " s = s + 'Z' + str(j) + ' '\n", + " hams.append(Hamiltonian(QubitOperator(s)))\n", + " return hams" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1.0 Z0, 1.0 Z1, 1.0 Z0 Z1, 1.0 Z2, 1.0 Z0 Z2]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "GenerateEmbeddingHamiltonian(5, 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 量子版词向量嵌入层\n", + "\n", + "量子版词向量嵌入层结合前面的编码量子线路和待训练量子线路,以及测量哈密顿量,将`num_embedding`个词嵌入为`embedding_dim`维的词向量。这里我们还在量子线路的最开始加上了Hadamard门,将初态制备为均匀叠加态,用以提高量子神经网络的表达能力。\n", + "\n", + "下面,我们定义量子嵌入层,它将返回一个量子线路模拟算子。" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "def QEmbedding(num_embedding, embedding_dim, window, layers, n_threads):\n", + " n_qubits = int(np.ceil(np.log2(num_embedding)))\n", + " hams = GenerateEmbeddingHamiltonian(embedding_dim, n_qubits)\n", + " circ = Circuit()\n", + " circ = UN(H, n_qubits)\n", + " encoder_param_name = []\n", + " ansatz_param_name = []\n", + " for w in range(2 * window):\n", + " encoder = GenerateEncoderCircuit(n_qubits, 'Encoder_' + str(w))\n", + " ansatz = GenerateAnsatzCircuit(n_qubits, layers, 'Ansatz_' + str(w))\n", + " encoder.no_grad()\n", + " circ += encoder\n", + " circ += ansatz\n", + " encoder_param_name.extend(encoder.para_name)\n", + " ansatz_param_name.extend(ansatz.para_name)\n", + " net = MindQuantumLayer(encoder_param_name,\n", + " ansatz_param_name,\n", + " circ,\n", + " hams,\n", + " n_threads=n_threads)\n", + " return net" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "整个训练模型跟经典网络类似,由一个嵌入层和两个全连通层构成,然而此处的嵌入层是由量子神经网络构成。下面定义量子神经网络CBOW。" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "class CBOW(nn.Cell):\n", + " def __init__(self, num_embedding, embedding_dim, window, layers, n_threads,\n", + " hidden_dim):\n", + " super(CBOW, self).__init__()\n", + " self.embedding = QEmbedding(num_embedding, embedding_dim, window,\n", + " layers, n_threads)\n", + " self.dense1 = nn.Dense(embedding_dim, hidden_dim)\n", + " self.dense2 = nn.Dense(hidden_dim, num_embedding)\n", + " self.relu = ops.ReLU()\n", + "\n", + " def construct(self, x):\n", + " embed = self.embedding(x)\n", + " out = self.dense1(embed)\n", + " out = self.relu(out)\n", + " out = self.dense2(out)\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下面我们对一个稍长的句子来进行训练。首先定义`LossMonitorWithCollection`用于监督收敛过程,并搜集收敛过程的损失。" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "class LossMonitorWithCollection(LossMonitor):\n", + " def __init__(self, per_print_times=1):\n", + " super(LossMonitorWithCollection, self).__init__(per_print_times)\n", + " self.loss = []\n", + " \n", + " def begin(self, run_context):\n", + " self.begin_time = time.time()\n", + " \n", + " def end(self, run_context):\n", + " self.end_time = time.time()\n", + " print('Total time used: {}'.format(self.end_time - self.begin_time))\n", + " \n", + " def epoch_begin(self, run_context):\n", + " self.epoch_begin_time = time.time()\n", + " \n", + " def epoch_end(self, run_context):\n", + " cb_params = run_context.original_args()\n", + " self.epoch_end_time = time.time()\n", + " if self._per_print_times != 0 and cb_params.cur_step_num % self._per_print_times == 0:\n", + " print('')\n", + " \n", + " def step_end(self, run_context):\n", + " cb_params = run_context.original_args()\n", + " loss = cb_params.net_outputs\n", + "\n", + " if isinstance(loss, (tuple, list)):\n", + " if isinstance(loss[0], Tensor) and isinstance(loss[0].asnumpy(), np.ndarray):\n", + " loss = loss[0]\n", + "\n", + " if isinstance(loss, Tensor) and isinstance(loss.asnumpy(), np.ndarray):\n", + " loss = np.mean(loss.asnumpy())\n", + "\n", + " cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num + 1\n", + "\n", + " if isinstance(loss, float) and (np.isnan(loss) or np.isinf(loss)):\n", + " raise ValueError(\"epoch: {} step: {}. Invalid loss, terminating training.\".format(\n", + " cb_params.cur_epoch_num, cur_step_in_epoch))\n", + " self.loss.append(loss)\n", + " if self._per_print_times != 0 and cb_params.cur_step_num % self._per_print_times == 0:\n", + " print(\"\\repoch: %+3s step: %+3s time: %5.5s, loss is %5.5s\" % (cb_params.cur_epoch_num, cur_step_in_epoch, time.time() - self.epoch_begin_time, loss), flush=True, end='')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来,利用量子版本的`CBOW`来对一个长句进行词嵌入。运行之前请在终端运行`export OMP_NUM_THREADS=4`,将量子模拟器的线程数设置为4个,当所需模拟的量子系统比特数较多时,可设置更多的线程数来提高模拟效率。" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "epoch: 25 step: 20 time: 0.592, loss is 3.154\n", + "epoch: 50 step: 20 time: 0.614, loss is 2.944\n", + "epoch: 75 step: 20 time: 0.572, loss is 0.224\n", + "epoch: 100 step: 20 time: 0.562, loss is 0.015\n", + "epoch: 125 step: 20 time: 0.545, loss is 0.009\n", + "epoch: 150 step: 20 time: 0.599, loss is 0.003\n", + "epoch: 175 step: 20 time: 0.586, loss is 0.002\n", + "epoch: 200 step: 20 time: 0.552, loss is 0.045\n", + "epoch: 225 step: 20 time: 0.590, loss is 0.001\n", + "epoch: 250 step: 20 time: 0.643, loss is 0.001\n", + "epoch: 275 step: 20 time: 0.562, loss is 0.001\n", + "epoch: 300 step: 20 time: 0.584, loss is 0.001\n", + "epoch: 325 step: 20 time: 0.566, loss is 0.000\n", + "epoch: 350 step: 20 time: 0.578, loss is 0.000\n", + "Total time used: 206.29734826087952\n" + ] + } + ], + "source": [ + "import mindspore as ms\n", + "from mindspore import context\n", + "from mindspore import Tensor\n", + "context.set_context(mode=context.GRAPH_MODE, device_target=\"CPU\")\n", + "corpus = \"\"\"We are about to study the idea of a computational process.\n", + "Computational processes are abstract beings that inhabit computers.\n", + "As they evolve, processes manipulate other abstract things called data.\n", + "The evolution of a process is directed by a pattern of rules\n", + "called a program. People create programs to direct processes. In effect,\n", + "we conjure the spirits of the computer with our spells.\"\"\"\n", + "\n", + "ms.set_seed(42)\n", + "window_size = 2\n", + "embedding_dim = 10\n", + "hidden_dim = 128\n", + "word_dict, sample = GenerateWordDictAndSample(corpus, window=window_size)\n", + "train_x,train_y = GenerateTrainData(sample, word_dict)\n", + "\n", + "train_loader = ds.NumpySlicesDataset({\n", + " \"around\": train_x,\n", + " \"center\": train_y\n", + "},shuffle=False).batch(3)\n", + "net = CBOW(len(word_dict), embedding_dim, window_size, 3, 4, hidden_dim)\n", + "net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", + "net_opt = nn.Momentum(net.trainable_params(), 0.01, 0.9)\n", + "loss_monitor = LossMonitorWithCollection(500)\n", + "model = Model(net, net_loss, net_opt)\n", + "model.train(350, train_loader, callbacks=[loss_monitor], dataset_sink_mode=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "打印收敛过程中的损失函数值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "plt.plot(loss_monitor.loss,'.')\n", + "plt.xlabel('Steps')\n", + "plt.ylabel('Loss')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "得到收敛图为\n", + "\n", + "![nlp loss](images/nlp_loss.png)\n", + "\n", + "通过如下方法打印量子嵌入层的量子线路中的参数:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1.52044818e-01, 1.71521559e-01, 2.35021308e-01, -3.95286232e-01,\n", + " -3.71680595e-03, 7.96886325e-01, -4.04954888e-02, 1.55393332e-01,\n", + " 4.11805660e-02, 7.79824018e-01, 2.96543002e-01, -2.21819162e-01,\n", + " -4.67430688e-02, 4.66759771e-01, 2.75283188e-01, 1.35858059e-01,\n", + " -3.23841363e-01, -2.31937021e-01, -4.68942285e-01, -1.96520030e-01,\n", + " 2.16065589e-02, 1.23866223e-01, -9.68078300e-02, 1.69127151e-01,\n", + " -8.90062153e-01, 2.56734312e-01, 8.37369189e-02, -1.15734830e-01,\n", + " -1.34410933e-01, -3.12207133e-01, -8.90189946e-01, 1.97006428e+00,\n", + " -2.49193460e-02, 2.25960299e-01, -3.90179232e-02, -3.03875893e-01,\n", + " 2.02030335e-02, -7.07065910e-02, -4.81521547e-01, 5.04257262e-01,\n", + " -1.32081115e+00, 2.83502758e-01, 2.80248702e-01, 1.63375765e-01,\n", + " -6.91465080e-01, 6.82975233e-01, -2.67829001e-01, 2.29658693e-01,\n", + " 2.78859794e-01, -1.04206935e-01, -5.57148576e-01, 4.41706657e-01,\n", + " -6.76973104e-01, 2.47751385e-01, -2.96468334e-03, -1.66827604e-01,\n", + " -3.47717047e-01, -9.04396921e-03, -7.69433856e-01, 4.33617719e-02,\n", + " -2.09145937e-02, -1.55236557e-01, -2.16777384e-01, -2.26556376e-01,\n", + " -6.16374731e-01, 2.05871137e-03, -3.08128931e-02, -1.63372140e-02,\n", + " 1.46710426e-01, 2.31793106e-01, 4.16066934e-04, -9.28813033e-03],\n", + " dtype=float32)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "net.embedding.weight.asnumpy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 经典版词向量嵌入层\n", + "\n", + "这里我们利用经典的词向量嵌入层来搭建一个经典的CBOW神经网络,并与量子版本进行对比。\n", + "\n", + "首先,搭建经典的CBOW神经网络,其中的参数跟量子版本的类似。" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "class CBOWClassical(nn.Cell):\n", + " def __init__(self, num_embedding, embedding_dim, window, hidden_dim):\n", + " super(CBOWClassical, self).__init__()\n", + " self.dim = 2 * window * embedding_dim\n", + " self.embedding = nn.Embedding(num_embedding, embedding_dim, True)\n", + " self.dense1 = nn.Dense(self.dim, hidden_dim)\n", + " self.dense2 = nn.Dense(hidden_dim, num_embedding)\n", + " self.relu = ops.ReLU()\n", + " self.reshape = ops.Reshape()\n", + "\n", + " def construct(self, x):\n", + " embed = self.embedding(x)\n", + " embed = self.reshape(embed, (-1, self.dim))\n", + " out = self.dense1(embed)\n", + " out = self.relu(out)\n", + " out = self.dense2(out)\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "生成适用于经典CBOW神经网络的数据集。" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "train_x shape: (58, 4)\n", + "train_y shape: (58,)\n" + ] + } + ], + "source": [ + "train_x = []\n", + "train_y = []\n", + "for i in sample:\n", + " around, center = i\n", + " train_y.append(word_dict[center])\n", + " train_x.append([])\n", + " for j in around:\n", + " train_x[-1].append(word_dict[j])\n", + "train_x = np.array(train_x).astype(np.int32)\n", + "train_y = np.array(train_y).astype(np.int32)\n", + "print(\"train_x shape: \", train_x.shape)\n", + "print(\"train_y shape: \", train_y.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们对经典CBOW网络进行训练。" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "epoch: 25 step: 20 time: 0.008, loss is 3.155\n", + "epoch: 50 step: 20 time: 0.026, loss is 3.027\n", + "epoch: 75 step: 20 time: 0.010, loss is 3.010\n", + "epoch: 100 step: 20 time: 0.009, loss is 2.955\n", + "epoch: 125 step: 20 time: 0.008, loss is 0.630\n", + "epoch: 150 step: 20 time: 0.008, loss is 0.059\n", + "epoch: 175 step: 20 time: 0.009, loss is 0.008\n", + "epoch: 200 step: 20 time: 0.008, loss is 0.003\n", + "epoch: 225 step: 20 time: 0.017, loss is 0.001\n", + "epoch: 250 step: 20 time: 0.008, loss is 0.001\n", + "epoch: 275 step: 20 time: 0.016, loss is 0.000\n", + "epoch: 300 step: 20 time: 0.008, loss is 0.000\n", + "epoch: 325 step: 20 time: 0.016, loss is 0.000\n", + "epoch: 350 step: 20 time: 0.008, loss is 0.000\n", + "Total time used: 5.06074857711792\n" + ] + } + ], + "source": [ + "train_loader = ds.NumpySlicesDataset({\n", + " \"around\": train_x,\n", + " \"center\": train_y\n", + "},shuffle=False).batch(3)\n", + "net = CBOWClassical(len(word_dict), embedding_dim, window_size, hidden_dim)\n", + "net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", + "net_opt = nn.Momentum(net.trainable_params(), 0.01, 0.9)\n", + "loss_monitor = LossMonitorWithCollection(500)\n", + "model = Model(net, net_loss, net_opt)\n", + "model.train(350, train_loader, callbacks=[loss_monitor], dataset_sink_mode=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "打印收敛过程中的损失函数值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "plt.plot(loss_monitor.loss,'.')\n", + "plt.xlabel('Steps')\n", + "plt.ylabel('Loss')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "得到收敛图为\n", + "\n", + "![classical nlp loss](images/classical_nlp_loss.png)\n", + "\n", + "由上可知,通过量子模拟得到的量子版词嵌入模型也能很好的完成嵌入任务。当数据集大到经典计算机算力难以承受时,量子计算机将能够轻松处理这类问题。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考文献\n", + "\n", + "[1] Tomas Mikolov, Kai Chen, Greg Corrado, Jeffrey Dean. [Efficient Estimation of Word Representations in\n", + "Vector Space](https://arxiv.org/pdf/1301.3781.pdf)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/quantum_approximate_optimization_algorithm.ipynb b/tutorials/quantum_approximate_optimization_algorithm.ipynb new file mode 100644 index 000000000..b56542ebb --- /dev/null +++ b/tutorials/quantum_approximate_optimization_algorithm.ipynb @@ -0,0 +1,383 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 量子近似优化算法\n", + "\n", + "\n", + "`Linux` `CPU` `全流程` `初级` `中级` `高级`\n", + "\n", + "[![](https://gitee.com/mindspore/docs/raw/master/tutorials/training/source_zh_cn/_static/logo_source.png)](https://gitee.com/mindspore/mindquantum/blob/master/tutorials/quantum_approximate_optimization_algorithm.ipynb)\n", + "\n", + "## 概述\n", + "\n", + "量子近似优化算法(Quantum Approximate Optimization Algorithm,QAOA)是利用量子计算机来近似解决组合优化问题的量子算法,最早由Farhi等人于2014年提出。在本教程里,我们将利用QAOA算法来解决最大割问题(Max-Cut),来熟悉MindQuantum中量子线路的搭建和训练。\n", + "\n", + "> 本文档适用于CPU环境。 \n", + "> 你可以在这里找到完整的可运行的样例代码:。\n", + "\n", + "## 环境准备\n", + "\n", + "本教程所需要的额外库:\n", + "\n", + "- networkx\n", + "\n", + "> `networkx`是创建、操作和研究复杂网络的结构、动态和功能库。可通过`pip3 install networkx`来进行安装。\n", + "\n", + "## Max-Cut问题描述\n", + "\n", + "Max-Cut问题是图论中的一个NP-complete问题,它需要将一个图中的顶点分成两部分,并使得两部分被切割的边最多。如下图(a),一个图由五个顶点构成,相互连接的边为```(0, 1), (0, 2), (1, 2), (2, 3), (3, 4), (0, 4)```。为了使得被切割的边最多,我们尝试通过(b)图的分割,将1、2、4分为一组,0、3分成另一组,因此可得到被切割的边有5条。当图中顶点增多时,我们很难找到有效的经典算法来解决Max-Cut问题。下面,我们介绍怎么将Max-Cut问题转化为一个哈密顿量的基态能力求解问题。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![max cut](./images/Max_Cut.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Max-Cut问题量子化\n", + "\n", + "这里我们将图中的每个顶点赋予一个量子比特,当顶点被分到左边时,我们将该顶点上的量子比特设置为$\\left|0\\right>$态,同理,右边为$\\left|1\\right>$态,当两个顶点被分到不同的集合中时,这两个顶点上的比特将处于不同的量子态。例如对于第0个顶点和第1个顶点,当其连线被切割是,两个顶点上的比特对应的量子态可以为$\\left|\\psi\\right>=\\left|0_11_0\\right>$或$\\left|\\psi\\right>=\\left|1_10_0\\right>$,其中下角标表示顶点的序号。此时,我们选择哈密顿量$H=(Z_1Z_0-1)/2$,这里$Z$为泡利$Z$算符。不难发现:\n", + "$$\\left<\\psi\\right|H\\left|\\psi\\right>=-1$$\n", + "而当顶点被分到同一集合中是,不难验证此时:\n", + "$$\\left<\\psi\\right|H\\left|\\psi\\right>=0$$\n", + "因此,我们只用按照上面的规则,写出图对应的哈密顿量$H$,利用量子计算机求得$H$的基态能量与基态,我们就可以得到该图的Max-Cut切割方案与最大切割边数。我们记所有边的集合为$C$,所有边个数为$c$,则哈密顿量可写为:\n", + "$$H=\\sum_{(i,j)\\in C}(Z_iZ_j-1)/2$$\n", + "\n", + "## 导入相关依赖" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from mindquantum import Circuit, Hamiltonian, UN, H, ZZ, RX, StateEvolution\n", + "from mindquantum.nn import MindQuantumAnsatzOnlyLayer\n", + "from mindquantum.ops import QubitOperator\n", + "import networkx as nx\n", + "import mindspore.nn as nn\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 搭建所需求解的图\n", + "\n", + "通过`add_path`可在图中添加边。最后画出图的结构。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n \n \n \n \n 2021-08-30T08:18:14.080359\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "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\n" + }, + "metadata": {} + } + ], + "source": [ + "g = nx.Graph()\n", + "nx.add_path(g, [0,1])\n", + "nx.add_path(g, [1,2])\n", + "nx.add_path(g, [2,3])\n", + "nx.add_path(g, [3,4])\n", + "nx.add_path(g, [0,4])\n", + "nx.add_path(g, [0,2])\n", + "nx.draw(g,with_labels=True, font_weight='bold')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如上如,我们得到一个由5个节点和6条边构成的图结构。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 搭建QAOA量子线路\n", + "\n", + "### 线路搭建\n", + "\n", + "这里我们采用量子绝热近似算法,经过演化将量子态从$X^{\\otimes n}$的本征态演化到图多应哈密的量的基态。\n", + "\n", + "搭建图对应哈密顿量的含时演化线路:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def build_hc(g,para):\n", + " hc = Circuit()\n", + " for i in g.edges:\n", + " hc += ZZ(para).on(i)\n", + " return hc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "搭建$X^{\\otimes n}$含时演化的量子线路:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def build_hb(g, para):\n", + " hc = Circuit()\n", + " for i in g.nodes:\n", + " hc += RX(para).on(i)\n", + " return hc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了使得最后优化的结果足够准确,我们需要将量子线路重复多次,因此我们通过如下函数搭建多层的训练网络:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def build_ansatz(g, p):\n", + " c = Circuit()\n", + " for i in range(p):\n", + " c += build_hc(g,f'g{i}')\n", + " c += build_hb(g,f'b{i}')\n", + " return c" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "构建图对应的哈密顿量:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def build_ham(g):\n", + " hc = QubitOperator()\n", + " for i in g.edges:\n", + " hc += QubitOperator(f'Z{i[0]} Z{i[1]}')\n", + " return hc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 生成完整的量子线路和图所对应的哈密顿量\n", + "\n", + "这里我们选择`p = 4`,表示选用4曾的QAOA量子线路,`ansatz`是求解该问题的量子线路,`init_state_circ`是将量子态制备到均匀叠加态上的量子线路。" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "p = 4\n", + "ham = Hamiltonian(build_ham(g))\n", + "ansatz = build_ansatz(g, p)\n", + "init_state_circ = UN(H, g.nodes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 搭建待训练量子神经网络\n", + "\n", + "由于该问题不需要编码层量子线路,我们这里使用`MindQuantumAnsatzOnlyLayer`作为待训练的量子神经网络,并采用`Adam`优化器。" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "net = MindQuantumAnsatzOnlyLayer(ansatz.para_name, init_state_circ+ansatz, ham)\n", + "opti = nn.Adam(net.trainable_params(), learning_rate=0.05)\n", + "train_net = nn.TrainOneStepCell(net, opti)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 训练并展示结果" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "train step: 0 , cut: [[2.9974403]]\ntrain step: 10 , cut: [[4.118001]]\ntrain step: 20 , cut: [[4.7207108]]\ntrain step: 30 , cut: [[4.8198557]]\ntrain step: 40 , cut: [[4.8452663]]\ntrain step: 50 , cut: [[4.894426]]\ntrain step: 60 , cut: [[4.9361014]]\ntrain step: 70 , cut: [[4.93437]]\ntrain step: 80 , cut: [[4.937895]]\ntrain step: 90 , cut: [[4.93891]]\ntrain step: 100 , cut: [[4.939043]]\ntrain step: 110 , cut: [[4.939199]]\ntrain step: 120 , cut: [[4.9392414]]\ntrain step: 130 , cut: [[4.9392495]]\ntrain step: 140 , cut: [[4.9392548]]\ntrain step: 150 , cut: [[4.939256]]\ntrain step: 160 , cut: [[4.9392567]]\ntrain step: 170 , cut: [[4.939257]]\ntrain step: 180 , cut: [[4.939257]]\ntrain step: 190 , cut: [[4.939257]]\n" + } + ], + "source": [ + "for i in range(200):\n", + " res = train_net()\n", + " if i % 10 == 0:\n", + " print(\"train step:\", i, \", cut:\", (len(g.edges)-res)/2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "根据上面的训练结果我们发现,该问题哈密顿量的基态能量对应的边切割数趋近与5。\n", + "\n", + "### 量子态展示\n", + "\n", + "前面我们通过训练得到了量子线路中参数的最优值,下面,我们通过`StateEvolution`类的`final_state`来输出量子线路在最优参数时的量子态,其中`ket`参数表示是否将最终量子态表示为右矢形式。" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "(0.01627434976398945+0.03802764043211937j)¦00000⟩\n(-0.021525444462895393+0.010120502673089504j)¦00001⟩\n(0.018090182915329933-0.004060747567564249j)¦00010⟩\n(0.0007923207012936473+0.020563315600156784j)¦00011⟩\n(-0.021525444462895393+0.010120502673089504j)¦00100⟩\n(-0.009984630160033703-0.09121354669332504j)¦00101⟩\n(0.0007923207012936473+0.020563315600156784j)¦00110⟩\n(0.004760198760777712+0.01567949540913105j)¦00111⟩\n(0.012238028459250927-0.001225721905939281j)¦01000⟩\n(-0.3078080117702484+0.3823811709880829j)¦01001⟩\n(-0.04003702476620674-0.010155842639505863j)¦01010⟩\n(-0.3078080117702484+0.3823811709880829j)¦01011⟩\n(-0.028323723003268242-0.004314310383051634j)¦01100⟩\n(-0.04003702476620674-0.010155842639505863j)¦01101⟩\n(-0.028323723003268242-0.004314310383051634j)¦01110⟩\n(0.012238028459250927-0.001225721905939281j)¦01111⟩\n(0.012238028459250927-0.001225721905939281j)¦10000⟩\n(-0.028323723003268242-0.004314310383051634j)¦10001⟩\n(-0.04003702476620674-0.010155842639505863j)¦10010⟩\n(-0.028323723003268242-0.004314310383051634j)¦10011⟩\n(-0.3078080117702484+0.3823811709880829j)¦10100⟩\n(-0.04003702476620674-0.010155842639505863j)¦10101⟩\n(-0.3078080117702484+0.3823811709880829j)¦10110⟩\n(0.012238028459250927-0.001225721905939281j)¦10111⟩\n(0.004760198760777712+0.01567949540913105j)¦11000⟩\n(0.0007923207012936473+0.020563315600156784j)¦11001⟩\n(-0.009984630160033703-0.09121354669332504j)¦11010⟩\n(-0.021525444462895393+0.010120502673089504j)¦11011⟩\n(0.0007923207012936473+0.020563315600156784j)¦11100⟩\n(0.018090182915329933-0.004060747567564249j)¦11101⟩\n(-0.021525444462895393+0.010120502673089504j)¦11110⟩\n(0.01627434976398945+0.03802764043211937j)¦11111⟩\n" + } + ], + "source": [ + "pr = dict(zip(ansatz.para_name, net.weight.asnumpy()))\n", + "print(StateEvolution(init_state_circ+ansatz).final_state(pr, ket=True))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 概率图\n", + "\n", + "我们画出最终量子态在计算基矢下的概率分布" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n \n \n \n \n 2021-08-30T08:18:15.952192\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAENCAYAAAABh67pAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAlp0lEQVR4nO2dd7gdVbmH34+ThGIggglF4BJKREKNHKKA9JYQJSAoAYFQpElRKQpSIsEC0lUQgkSkGQgo5moAkWahmIMFBS8auJREr4SmIhBF1v3jW5u9zmSXOTn7nH2y+L3PM8/ZM/PNt76ZWfNbdeZYCAEhhBD5slS7AxBCCNG3SOiFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITKnlNCb2Tgze9zM5prZKTX2n2Bmj5nZI2Z2l5mtlez7j5n9Ji6zWhm8EEKI5lizefRm1gH8EdgFmAfMAfYLITyW2OwAPBRCeNXMjga2DyHsG/e9EkIYWjag4cOHh5EjR/b4RIQQ4u3Mww8//HwIYUStfYNKHD8WmBtCeBLAzGYAE4G3hD6EcE9i/yBwwOIGO3LkSLq6uhb3cCGEeFtiZk/X21em62Z14NlkfV7cVo/DgNuS9WXMrMvMHjSzPesEeES06VqwYEGJkIQQQpSlTI2+NGZ2ANAJbJdsXiuEMN/M1gHuNrPfhRCeSI8LIUwDpgF0dnbqmwxCCNFCytTo5wNrJutrxG3dMLOdgdOAPUIICyvbQwjz498ngXuBMb2IVwghRA8pI/RzgFFmtraZDQEmAd1mz5jZGOAKXOSfS7avaGZLx9/Dga1J+vaFEEL0PU27bkIIb5jZscAdQAcwPYTwqJlNBbpCCLOA84ChwEwzA3gmhLAHsAFwhZm9iRcq56SzdYQQQvQ9TadX9jednZ1Bs26EEKJnmNnDIYTOWvv0ZqwQQmSOhF4IITKnpdMrRb6MPOVHdfc9dc6ElvlaHH/CaeV11T3KC9XohRAicyT0QgiRORJ6IYTIHAm9EEJkjoReCCEyR0IvhBCZI6EXQojMkdALIUTmSOiFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROZI6IUQInMk9EIIkTkSeiGEyBwJvRBCZI6EXgghMkdCL4QQmSOhF0KIzJHQCyFE5kjohRAicyT0QgiRORJ6IYTIHAm9EEJkjoReCCEyR0IvhBCZI6EXQojMKSX0ZjbOzB43s7lmdkqN/SeY2WNm9oiZ3WVmayX7JpvZn+IyuZXBCyGEaE5ToTezDuBSYDwwGtjPzEYXzH4NdIYQNgFuBr4aj10JmAK8HxgLTDGzFVsXvhBCiGaUqdGPBeaGEJ4MIfwLmAFMTA1CCPeEEF6Nqw8Ca8TfuwF3hhBeDCG8BNwJjGtN6EIIIcpQRuhXB55N1ufFbfU4DLitJ8ea2RFm1mVmXQsWLCgRkhBCiLK0dDDWzA4AOoHzenJcCGFaCKEzhNA5YsSIVoYkhBBve8oI/XxgzWR9jbitG2a2M3AasEcIYWFPjhVCCNF3lBH6OcAoM1vbzIYAk4BZqYGZjQGuwEX+uWTXHcCuZrZiHITdNW4TQgjRTwxqZhBCeMPMjsUFugOYHkJ41MymAl0hhFl4V81QYKaZATwTQtgjhPCimZ2NFxYAU0MIL/bJmQghhKhJU6EHCCHMBmYXtp2Z/N65wbHTgemLG6AQQojeoTdjhRAicyT0QgiRORJ6IYTIHAm9EEJkjoReCCEyR0IvhBCZI6EXQojMkdALIUTmSOiFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROZI6IUQInMk9EIIkTkSeiGEyBwJvRBCZI6EXgghMkdCL4QQmSOhF0KIzJHQCyFE5kjohRAicyT0QgiRORJ6IYTIHAm9EEJkjoReCCEyR0IvhBCZI6EXQojMkdALIUTmSOiFECJzJPRCCJE5pYTezMaZ2eNmNtfMTqmxf1sz+5WZvWFm+xT2/cfMfhOXWa0KXAghRDkGNTMwsw7gUmAXYB4wx8xmhRAeS8yeAQ4GTqrh4rUQwma9D1UIIcTi0FTogbHA3BDCkwBmNgOYCLwl9CGEp+K+N/sgRiGEEL2gTNfN6sCzyfq8uK0sy5hZl5k9aGZ71jIwsyOiTdeCBQt64FoIIUQz+mMwdq0QQiewP3Cxma1bNAghTAshdIYQOkeMGNEPIQkhxNuHMkI/H1gzWV8jbitFCGF+/PskcC8wpgfxCSGE6CVlhH4OMMrM1jazIcAkoNTsGTNb0cyWjr+HA1uT9O0LIYToe5oKfQjhDeBY4A7gD8BNIYRHzWyqme0BYGZbmNk84KPAFWb2aDx8A6DLzH4L3AOcU5itI4QQoo8pM+uGEMJsYHZh25nJ7zl4l07xuPuBjXsZoxBCiF6gN2OFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROZI6IUQInMk9EIIkTkSeiGEyBwJvRBCZI6EXgghMkdCL4QQmSOhF0KIzJHQCyFE5kjohRAicyT0QgiRORJ6IYTIHAm9EEJkjoReCCEyR0IvhBCZI6EXQojMkdALIUTmSOiFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROZI6IUQInMk9EIIkTmlhN7MxpnZ42Y218xOqbF/WzP7lZm9YWb7FPZNNrM/xWVyqwIXQghRjqZCb2YdwKXAeGA0sJ+ZjS6YPQMcDNxQOHYlYArwfmAsMMXMVux92EIIIcpSpkY/FpgbQngyhPAvYAYwMTUIITwVQngEeLNw7G7AnSGEF0MILwF3AuNaELcQQoiSlBH61YFnk/V5cVsZSh1rZkeYWZeZdS1YsKCkayGEEGUYEIOxIYRpIYTOEELniBEj2h2OEEJkRRmhnw+smayvEbeVoTfHCiGEaAFlhH4OMMrM1jazIcAkYFZJ/3cAu5rZinEQdte4TQghRD/RVOhDCG8Ax+IC/QfgphDCo2Y21cz2ADCzLcxsHvBR4AozezQe+yJwNl5YzAGmxm1CCCH6iUFljEIIs4HZhW1nJr/n4N0ytY6dDkzvRYxCCCF6wYAYjBVCCNF3SOiFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROZI6IUQInMk9EIIkTkSeiGEyBwJvRBCZI6EXgghMkdCL4QQmSOhF0KIzJHQCyFE5kjohRAicyT0QgiRORJ6IYTIHAm9EEJkjoReCCEyR0IvhBCZI6EXQojMkdALIUTmSOiFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROZI6IUQInNKCb2ZjTOzx81srpmdUmP/0mZ2Y9z/kJmNjNtHmtlrZvabuFze4viFEEI0YVAzAzPrAC4FdgHmAXPMbFYI4bHE7DDgpRDCemY2CTgX2DfueyKEsFlrwxZCCFGWMjX6scDcEMKTIYR/ATOAiQWbicB34u+bgZ3MzFoXphBCiMWljNCvDjybrM+L22rahBDeAP4GvCvuW9vMfm1m95nZNrUSMLMjzKzLzLoWLFjQoxMQQgjRmL4ejP0L8F8hhDHACcANZrZC0SiEMC2E0BlC6BwxYkQfhySEEG8vygj9fGDNZH2NuK2mjZkNAoYBL4QQFoYQXgAIITwMPAG8p7dBCyGEKE8ZoZ8DjDKztc1sCDAJmFWwmQVMjr/3Ae4OIQQzGxEHczGzdYBRwJOtCV0IIUQZms66CSG8YWbHAncAHcD0EMKjZjYV6AohzAKuAq41s7nAi3hhALAtMNXM/g28CRwVQnixL05ECCFEbZoKPUAIYTYwu7DtzOT368BHaxx3C3BLL2MUQgjRC/RmrBBCZI6EXgghMkdCL4QQmSOhF0KIzJHQCyFE5kjohRAicyT0QgiRORJ6IYTIHAm9EEJkjoReCCEyR0IvhBCZI6EXQojMkdALIUTmSOiFECJzJPRCCJE5EnohhMgcCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROZI6IUQInMk9EIIkTkSeiGEyBwJvRBCZI6EXgghMkdCL4QQmSOhF0KIzJHQCyFE5kjohRAicwa1OwDRnZGn/KjuvqfOmdCPkQgxMNEz0nNUoxdCiMyR0AshROZI6IUQInNKCb2ZjTOzx81srpmdUmP/0mZ2Y9z/kJmNTPadGrc/bma7tTB2IYQQJWgq9GbWAVwKjAdGA/uZ2eiC2WHASyGE9YCLgHPjsaOBScCGwDjgsuhPCCFEP1Fm1s1YYG4I4UkAM5sBTAQeS2wmAl+Iv28GvmFmFrfPCCEsBP7XzOZGfw+0JvxF0Yh8+2h07aHn17/MvSybZn/ni1bEVdauL66r6Bta/YyUxUIIjQ3M9gHGhRA+EdcPBN4fQjg2sfl9tJkX158A3o+L/4MhhOvi9quA20IINxfSOAI4Iq6uDzze+1MDYDjwfD/bvR18tSPNgeqrHWkOVF/tSHOg+mp1mmVYK4QwouaeEELDBdgH+FayfiDwjYLN74E1kvUn4gl8Azgg2X4VsE+zNFu1AF39bfd28LWkx69roWuxpF2L3i5lBmPnA2sm62vEbTVtzGwQMAx4oeSxQggh+pAyQj8HGGVma5vZEHxwdVbBZhYwOf7eB7g7eHE1C5gUZ+WsDYwCftma0IUQQpSh6WBsCOENMzsWuAPoAKaHEB41s6l4s2MW3iVzbRxsfREvDIh2N+EDt28Ax4QQ/tNH51KLaW2wezv4akeaA9VXO9IcqL7akeZA9dXqNHtF08FYIYQQSzZ6M1YIITJHQi+EEJkjoRdCiMyR0NchvtnbkmPa7as3x5X1NVDjapevgRSLfA0sf33ttxZvO6E3s/XMbHOLxG21LvjScV/da2Rmm5jZTma2qpkNDiGEOr6WjfZ1v/PTSl9x/wfMbB8ze5+ZLRP9LVWweWf82zDDlYytHXG10tf2Zna4mX3YzIbW8hXtVovvijQklJzlUPLbT91i72UloJSvkv66XYdeCtdAjaul/sxsucrvenmsT+iPt7IGygLsDTwK3Ad8CzgaWDbus8Tuw/j3eDaM60vV8LUn8Af8XYGr8A+5Da3hay/gL8Dmcb2jL33F7RPwz0hMB64EbgKGpecCfAT4B7Bzk2vWNLY2xdVKX7sAf4zndgX+rsdKxXsfr8Ur+NvhyzTwtz3wlZj+ZnVsRie/a16vJLbp+CdCxifb03yxGbB0ifzf1FdZf/hHDm8FTgMOa+BrLLD+khhXWX898LUHcDfwNeDUOjZbA7s1yl+Ls7TM0UBf8Br694Et4/ok4EJgClHs4/ZNgT8B18cHfhGxx1tC1+Lf9wHYErgAuJpEBPHv9jwA3AAsADrjvo6+8JX4vBQ4OP5eLfq7l6oQrgXcDlyHF3w71rlmZWLr97j6wNeXgM8m6xfEPFARewNWwQuTK4EfxvyziOgAO+KF3kl4oXENiUBEmwnAm/h3nyrbal2vDwLP4AL4uXg+ny/YjKv4AoZU4l0cX2X9AVvE6zMJf0HyV8CFyf5KBWDn6GsWdYRwoMZV1l8PfG0U8+H46Pcx4OLCs7ZD9PW9eL4tE/u2C3B/LXjXwk+I39oBBgM7xYc6LanXAg6Jv0+KN7eb2Mebcg1waFzvANaNvr4IDIrbVwE+Gn8fDbxEQQjxZuFbYtDA12olfFUy8hTghOScOqK/6/ACb0Vgt7jvIPzbRDsWH54ysbUpLou+Tmzg613NfEU/BhwCnFXIL+cDvwDeEdffQbWS8GG8UJlEUkmI+z5eOUdgVfyBnUU1r6wUr+neuGjNTq93wdc44Jz4exlcLB4k1gaB5YGvAvvG876V+iI4Dji3nq+4fYUy/vAPFqbfv1oJbxFdUHjeTorX4yt4ReC9NZ7LARlXGX/ActHXASV8vRcX8MHJOd0HXJI8awcBBwOfxHscxlPIX4utf61wMpAXXAAqYvkR4AfA2Lg+BP90w1XRrnITlk6OPwEX+43i+trRdkfgd8BOyY3aDm+Crpgcv0zy+yhcCLfAhWNzvMDZAXiE2MVQ8LU61e6lIbV8xfWNk/i3wGvXe1YeBrwAu4pY6yicY0UIK+eyHbBC/F0vtmuA1fo5rl2o1rI3A54DPlLwdR3wvsq2Br7eX0kL/38Jc4H9CnnnSmD7ZH1w8rsi9vvF9e3xB/9QvCVYyXPL4Q/st4EN4raxiZ+fkoh9If1dgfsL13cj4EfJeWxAteD6Pi6Ci4gD3h3wYCNfcdtoqhWat/zRvXDcDPhvYJXkuJXwz6WkHzFci2ph/zW8lTy6cF8GZFxx3ybA7Eb+8G95Da7lK4ltKfw7X98mVoLi9uXxbtET4/rwJN+chOe/CbXuZ491sLcOBvKC9x1/B69RbYWL9DHA5finlit2v48Z5wd498QKyT4DTgR+A9wIzACWj/sOihkhzZAPACcl60sVbnql1vtH/MNvB8RMsHfM3Lskx/4aF5O7gQ8B6xbO72jg/4Dv4oXOu5J9E/Cm4l5xfVugC5iQxpXYHxSvw4/wz1j8Av9/AovEhheYf43++jOul3HhOzrG9cG4b+9CXL/DuwI+UIir4utWvLtg1WTf9vG+7Jdsux3/fwpvVRoK/j4c45oJPA38V/LAX0u1IHk3Xps7uk4+fUvs8YI1FYNvxTywTFxfDu9qSoUrvV63ArfG30cAn6EqHhcDPyv4Ogs4GS+8h1AYj6r4A4YCuyd54CL8mUgLv8kkXWA1Yvs6XuvtBM4GJg2wuIYBp+OthzXjvil4fkoLojPxysl/sWiLLvV1OPCxZN/ngHuAkcm23fHWgNWI62Rc7DcHjgOOWGwt7I2QDuQFL1X/FDPKp3ARPxjvrvkk3mzeL178hfhAyafwQuHwyo2Ovsbh3+pZCHwu2f5OfGDuMeBI/LPM/8FrFalgpEK/Nj5A+DLwebyv+YwY50dwETwqZqaFMSMcEO2mUBjcA36O9+t1kXRlJJnoOVwsXgP+CXwp2V8U1R/G89wvSbMY22m4mD4RM19/xPWDGNe+eC33NPwbIVvhg1d/xcXv+RjXgfHaXk5ScEZfd8e4/hc4v7BvO7yG9WW8UH8j+ju/QWx3R7s5eB/+h/Fa9rl47W4IXlC+CDyJF357Esc4Ej+34a2dV4DdC/suxwuDZeO1ezz6SfPVoMT+Gryg/RcuoDfiFZxhMa5fAMtF2xvxAu4OXKBOILaaCue4EP/y7Khk+3S8MrJmsv5yvI5pbOmY1I3R12vxnAdKXGfhhf+/8QL+Wrz7rgMX/kfw2vse8T49jVciv05s2dbw9SZwZGHfOcBdeL79GPA63gIcTNI1nNgfgj9D86kzsF9KD9styH214DWjdLBrPF46Tsb7lScCt+D99j9P7HbDS+tP4DULi8ctwAuCK4H9awjExcBDuPDtFe26iX38eyDwd2DjuL4FXrs5HRiB13Avwf9T1y+T4yu1oDOp1hy3Ap6KaY+JxxRFdXNcgH6KDyh1s0ky18bAw8BDyb5asV0fM92mLYhrZsm4HgIeSPatgxfWV+DjIJvghc1TVLvY1sFr/t+kOhtoE7xQ/gLeh38XSf9ttNkAF+ab8EJuEbsktg3wQmb/uP5pfJzgaHyQ+uv4Q/qnaPcePA9dhFcMhiY+z6Q6EPc9YEwhrivxLsTX8fxzZ8WGgnDhFZqFwIFxfU/gPHxcZWhM/2fR5z+odkPsiQvROVS7yDYE/hzTfQzYthDXeXhXym0xzZtinNsWY8O7/e6PaW44UOKKfydHu2Pi+vvwmvY1MfbT8QrHXOBZvHvpfcBUvFBKW4cn44X/DXirvVhwn4j3DLyA68rLFRsKYh/jeplkltZi6WG7BbmvFrwmNRPYI9k2Hu9qqfTRD8YHgIp2u8UMUhlUXA8fTFkJf4imkTSdk+MG44VDavfxgs0qeI3/2GTb2Ji5Kze7kglnFuy2iHaVQcbV8G6BofhDtH085rM1YivanJzsGxT3D8dF5rhGsdWwqRfXsHhNGsVVtKkX1410r1mvi9ec0oH0WjZfACbH9ZWirxXj+ki8oL8oOSYdU2lktzxew/45sG+y/eO42Feuxf54d9GnE5v9cFGbkMR5SzyfVfDullnEAio5bndcwMYmNt3GI/AC+SJ8oO+TybEfxGumh8f1reM1v7tgtzUuqMfiAjca757aBK9dPgJsV4hrXbyyc3xc72aXxDYMbzF9sQVxbYg/y83i2h74VJO43gF8Fs9/aYt9Tbyl+JW4PirafKVgc1a858vGfHF65X7jlb6/V+514bij8Oekmw1VkTe8u3GTXuthbx0MpCVmhp2pDhweHzPHlonNSbiI79rE7hvAj1m06T8iZphpMa0z8BJ8pwZ2e+EPdyW9vfGpnWn/3aExrrSWV8/uduI4QSHNpfGWzEy8W2W/mFE66tgcgteEL6V77e98Yv9p3HYuXjM9uIHNoXjTe38KhSBemFbSPCee0+WFuFKbQ/Aa71HJ/s2Ay+g+/nEQXttepoHNBLxfdNXi9Yr718FF/MyY5hSS/tg6dvsAp+IF0T7x+r0luLho3EK1tljL5mS8YDK8oFue6qDeynjr4L/xlo/hhWdHPZu4bZXkWm6DFwQ7JWnui7ec0q6nenYzkvjfkdgfjPdZb5dcl8pkgY5adnila3SMf3u8ZrxzjbiGJPdy2zp2N1GdCbV8g7jWp1pQD25gt16MqwMvNH5DdRaZAR/Aa+Yrx231bK5Nrv/gQpp74i2UDyX5dIUmNmOI3Vgt0cZWOWr3ggv3M3jt4KH495148+uc5MZcEC9oXbvo6wW8n+9B4JuFtIbjg5CP483tqxrYfRHvg36NagthGN71cCHVEfcLYpr7JBm5md0iGQEX8uPwJu0/SQb2CjYbUO1HPB/vtrkwbj8mpnMiLr4v4c3eX9WyiT7Pjuf4ObxQuAxYvZDmZLxJ+xpey61lswEwDx/r+CowIhGv8XhXzCXxwZiPjxusVcsmbjsv3u9TgOGVh7NwPYbEa1pJs5ndQnwgcGu8ZjYFbzlUBHci3q1wcz2baPdbvED4NrBNIa3VcCG/L16rJyi8+JXY3IALzZ1URfedeI3xyvQ4PM8fnqwvYod3SXRRbfkWB0IPiX5uxFs072xg90e8u+M5XKhHAofhFaB04sEjeKH9Y3ycYxE7vOLzV/xZm8CiffaVuG6L1//uJnY34/l+WLJvIt6C+Vhc3zLa7FLPJm77OfDlZL3YBbMX3gVzFz64vnIdm+fjvbiPmPdboo/9Lch9seCl8Xeozvd+Bz775Ut4TekovLvhTuBvwBea2P0Zf+llM7wr5hcs+n9yT8eF9PS4vogdPjPkaVzYrsULh8qLRsNihr45ZpJXYwa4pAd2EyiIfUxzbrS7to6N4bXXv9N9nvZ38QJvfapvBD6ND+yNaWBzV8yg5ye+rsdnn6xKdb76HXihMbpok8R2Il5IfQ8vPI6ju9hvGNN8Hh/gnFbH5ka8Bv4KXjM8G2/2D6+Rfw6N9+j7TexOxAfrjsL74X+HFzg74DX9K/FB4Pnxup1Yx+ZAqpMAPhb9/R5vDaW11E3xQuolvMKwiE20ux8I8Zqlb5auHe/zbfjYwIXR7voGdpfjBd5LJGNRLFro3VsnzbS18B78eXspnu+seN12wsV2dozrlHgtJuCt0Fp2X8Sfy6fi9ZoVj12vEFdlQsGBia9advfE+O8kmc2CdxXuEe/b1TGuv9O9Sye1OR2vWLyJi//hxWuBD+DviuedhUT9KdrEv9PxvN3r7ppu59tuke5V8N0z1TF4c7jS9BuKj2afi4tMB963eUo9u7heGWWfWrB5AG9+V27MF/EWQNHXA8ClcX14vHHvw2teV+EiPqxwkz9AtdayOHZDk3McjhcCGxdtKueXiNaJMZNWmpzL4U3ji5Lrenw8h0Y2o3CxLPqaAVyW2F2NC3AjmxNwYazMVrkEF/K0MBiMN+2H17OJdqvj8+WLvirN8MrDdShemDazOw/4SeJ/d1yEJuJ963vgNcV5VAdKa9n8EBeaexJf44gzwZJtZ+AF0EYNbCbiYvp1qq2s3ZP9y+P55voY22UN7LbGW21deGF+ATVEHO+y/D+8e/OYBnZH45WhysSDrfCC+Si8NbJ1jOtWkn+SXcfudlzoK9d1S7zQPJ7qOx874i2fOYmvenbz433eHR/U7zZ1Ea8s3I4PDh/TwOYcvLD4WuKrlth/BS/8P9nAZixeaLdU5ENY8oU+fZFhW7zGuH6ybQW8lr17CbtfUn3zsZ7Nz0r6ejBmsGJT9tO4aFb64bYPiZD0xg4XkY1L+No4yVib4y2hXag2+ZfDm6p7lbFp4mvZaDexrE3cls5Z3js+RMfF9c6yNiXsxvTELl7ja/DCoyL+u+PdR5W8syb+0lYjm6XxllfR13h8BknFbhO8ldHI5t14a6sD7w44Fe+K+1AhHwzG3zxuZjccH3tIbYoDiSvjs06Kvop2q+KVgo9Rncu/NV5YjY/rldk4325iN7iGzVbRZte4PgLv8iljt070ORR/w/lykjGhJM2GNg3sitMq3xXveSObYdQZS+q1VvaF0/5YcHF7AC+tp+ICexRea3kvVSG5HW/y1rWLvv6KN/Na5esHeJfQyELcn8ZnRdyGz3O+pgV2t8Y0X8WnNNbzNRtvRq9CVaCPisfvgteexsfrMCE5vptN3DaT2O1Tz66XvtLW2t54zW8O3pXx7jo2U/Ea0WsVmxJ2qzexuy9e14rgTMW7QEZSbR1dgAtaZwOb4/G+9O2a2J0ffY1tYHNc9LUjhTEYXFw/H2Mag48LfLKJ3YH4uwMHl/B1TBNfH4z+tsK7LI6N57Q91YHkyXT/HEA9uyPjtRhcwtfSTXx1syvEvwLezXM53jV2IPHt7To2e+Hv2uxZ0tdHmvhaxKbletkfotzyoH3a1JPxRm6D933+HK+xHIkL2pT4gPw7XvB6dhfjfb1/xgeLWunrNLwQGFWI/168H/GQ6Gux7eK1eBUX+skNfFX6cV9l0Vf9j8Br/bfh/bNvAp+pY3NNXPrDV6Uw2ir6eZPGbzje3sxXD+3ujvd8Jl5gXRi3Xx7zwzZ4YfYcXmOvaRPXL8MH46a12NetwFWFc1gVz5OPxHsws4Hd1+J1+Fc83974uhwfE/kn1fcshuKF1XlUZ3Zdgj8nW1ItvLrZ4S/oPYM/V4N746uG3VLF+42L75R4LV4h6T4p2OyEvxfxH1wLeuvrl3jX28Z9qpn9IcwtD9rF9crKhYvL5/GXb1aIN/MQ/M3LmU3sjsEfrA36yNfn8K6cteKx6+KzdW4p+FpcuzF4LX3TJr7m4bXprfC3Qovz+zeJD8Y1eNOyns1EvJZ1WF/6Sh6IQbjQPI0XZPVsVovneHQTX2XtVscfwNOSB/NBqvnuDLyV8Zfob9M6NtfhBcvfiF9l7ANfvwBuLlzfM4mzoOrZ4d0mjxBfyOmlr5HxHi3ExbmTatfJULxwvzqe0z/j3xl17G6O5/iPeL698VXPrji4PBLPC68n8df6CuiX4zne3wJfJ+OVwl69DFVKM9st2j0K1md6bIn3sXXR/YWKpfAXF04taXdBP/o6Ex+dfy/e3B7VAruLcaEaiU8DbeRrK/xDTpWukl3wFtGByXUdi/cR1rSJ2zbCC5a6dn3g6wPRZr0GvjaJvlZvkmZZu0pX3Rnp9rjtfuC8+HtFfAD6+CY22+Ddbn3p62fAFcn6WXgttpndT4gzx3rjC//uy9X4xIMz8RZJJ0mXSvw7juqYTU276OswvP+/V76a2FUKBYtpfrNG/G/ZxL9fAj7RIl8nE9+t6HPt7I9EWhKoN+X+B5+tcB4+4j8fOCix2Q2vATSzm4rXggear75K8278IfwU1TnPu+ECN5XqdMDv1LHZLabzwhLqqyd2x+EzKTrw7/38nvhph2g7HJ/rPTqu17P5Ht1novSHr5vpPlmgnt3tVD+93Upfw5J9Z+Avc1W+Ypq+eV7TDp+OuWkrfLUjzR742rTf9bO/E1ysIH0w5kZg67g+CZ+G9118mldldsRheF/1LvXsoq+H8Jry8v3tCy/xF9uuF2nujYvdl+n+qd838e9tbFTHZiO8P/I1qt8eWZJ89dTuTbp/I+ls/Nsmle/4fAgfN/lxPZu47ad4031Gf/hqR5p1fKWzl87AB41nxuv6vQZ29+LjIS9Tnda6uL7akWZZX+fg3WUr96uG9mdiix2ki9ttVF/B78AHMj6DD1L9FJ8v/ge8OdnIbjo+QHL6APPV12kuhc/4+CqxmwefD/0vqv29tWx2wAX3p0uor7J242OaU/Aa/3cLAvdbvKD9I9W3XmvZHInPTnkF7/rrD1/tSLPo67rEJv2fApUC46zoaxE7/MXFF3GRvLU3vtqRZg983RuvV58OvNbU0HYKeI8C9ZrpLKozDzrwwbkL8T7cZfGmZBm7vQeor/5Ic3984G9pfK7xISVs1l/CfZW124bqR9SKQrgXPoB7A95Mb2TzLbyA6U9f7Uiz6OstgYt278HHj3Yu+Kpl9zt8PKYVvtqRZllfm7ZFP9st4D0Q+mXw+bHTSD5Jis9zHtMTu4Hqqx/TvIf4L8/K2Czpvnpil2x/F/4dmu/G9Q2Js5h6YtMOX22O/7q4vhnx7eWe2rXSVzvSLOurP5dBLCGEEF43s+vx+c+nmtl78WlOw/HmUGm7geqrH9NcGR/MLWWzpPvqiV1i/4KZHQmcZ2aPU/3yYo9s2uFrAMT/P9FmuxDC8z21a6WvdqRZ1le/0q4SZnEX/HskO+BzWK+m8A8aemI3UH0t6fEPVF89sUvsP4MPctftVy1j0w5fS3r8b5dr0R9LWxPvVeBeSi7VCruB6mtJj3+g+upBmiviH6yq+5GpMjbt8LWkx/92uRb9tVQm7gshamBmy4QQXu+tTTt8tSPNgeqrHWmW9dUfSOiFECJzlmp3AEIIIfoWCb0QQmSOhF4IITJHQi+EEJkjoRdCiMyR0AshROb8P3ANPHi1xfCQAAAAAElFTkSuQmCC\n" + }, + "metadata": { + "needs_background": "light" + } + } + ], + "source": [ + "def show_amp(state):\n", + " amp = np.abs(state)**2\n", + " n_qubits = int(np.log2(len(amp)))\n", + " labels = [bin(i)[2:].zfill(n_qubits) for i in range(len(amp))]\n", + " plt.bar(labels, amp)\n", + " plt.xticks(rotation=45)\n", + " plt.show()\n", + "state = StateEvolution(init_state_circ+ansatz).final_state(pr)\n", + "show_amp(state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "根据概率分布图我们发现,该Max-Cut问题具有四个简并解,每个解对应的概率大概为25%。\n", + "\n", + "## 总结\n", + "\n", + "这里我们通过量子近似优化算法来解决了Max-Cut问题,并得到了案例中的图对应的最大切割方案。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 参考文献\n", + "\n", + "[1] Edward Farhi, Jeffrey Goldstone, and Sam Gutmann. [A Quantum Approximate Optimization Algorithm](https://arxiv.org/pdf/1411.4028.pdf)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "5545a57ef4a1ac7dca167cae0bf17fda051fcd0639773c034bd7ce77ffd97d30" + }, + "kernelspec": { + "display_name": "Python 3.7.5 64-bit", + "language": "python", + "name": "python37564bit6afae4a42a5941c0967cdcfc2650559a" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5-final" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/tutorials/source/1.parameterized_quantum_circuit.py b/tutorials/source/1.parameterized_quantum_circuit.py deleted file mode 100644 index ea94ca3ab..000000000 --- a/tutorials/source/1.parameterized_quantum_circuit.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -import mindquantum as mq -from mindquantum.core import X, Y, Z, H, RX, RY, RZ - -print('Gate name:', X) -X.matrix() - -print('Gate name:', Y) -Y.matrix() - -print('Gate name:', Z) -Z.matrix() - -print('Gate name:', H) -H.matrix() - -cnot = X.on(0, 1) -print(cnot) - -rx = RX('theta') -print('Gate name:', rx) -rx.matrix({'theta': 0}) - -ry = RY('theta') -print('Gate name:', ry) -ry.matrix({'theta': np.pi / 2}) - -rz = RZ('theta') -print('Gate name:', rz) -np.round(rz.matrix({'theta': np.pi})) - -from mindquantum.core import Circuit - -encoder = Circuit() -encoder += H.on(0) -encoder += X.on(1, 0) -encoder += RY('theta').on(2) - -print(encoder) -encoder.summary() diff --git a/tutorials/source/2.initial_experience_of_quantum_neural_network.py b/tutorials/source/2.initial_experience_of_quantum_neural_network.py deleted file mode 100644 index eff188a5c..000000000 --- a/tutorials/source/2.initial_experience_of_quantum_neural_network.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -import mindquantum as mq -from mindquantum.core import Circuit -from mindquantum.core import H, RX, RY, RZ - -encoder = Circuit() -encoder += H.on(0) -encoder += RX(f'alpha{0}').on(0) -encoder += RY(f'alpha{1}').on(0) -encoder += RZ(f'alpha{2}').on(0) -encoder = encoder.no_grad() -encoder.summary() -encoder - -alpha0, alpha1, alpha2 = 0.2, 0.3, 0.4 -state = encoder.get_qs(pr={ - 'alpha0': alpha0, - 'alpha1': alpha1, - 'alpha2': alpha2 -}, - ket=True) -print(state) - -ansatz = Circuit() -ansatz += RX(f'theta{0}').on(0) -ansatz += RY(f'theta{1}').on(0) -ansatz - -theta0, theta1 = 0, 0 -state = ansatz.get_qs(pr=dict(zip(ansatz.params_name, [theta0, theta1])), - ket=True) -print(state) - -circuit = encoder + ansatz -circuit - -from mindquantum.core import QubitOperator -from mindquantum.core import Hamiltonian - -ham = Hamiltonian(QubitOperator('Z0', -1)) -print(ham) - -encoder_names = encoder.params_name -ansatz_names = ansatz.params_name - -print('encoder_names = ', encoder.params_name, '\nansatz_names =', - ansatz.params_name) - -# 导入Simulator模块 -from mindquantum.simulator import Simulator - -sim = Simulator('projectq', circuit.n_qubits) - -grad_ops = sim.get_expectation_with_grad(ham, - circuit, - encoder_params_name=encoder_names, - ansatz_params_name=ansatz_names) - -encoder_data = np.array([[alpha0, alpha1, alpha2]]).astype(np.float32) - -ansatz_data = np.array([theta0, theta1]).astype(np.float32) - -measure_result, encoder_grad, ansatz_grad = grad_ops(encoder_data, ansatz_data) - -print('Measurement result: ', measure_result) -print('Gradient of encoder parameters: ', encoder_grad) -print('Gradient of ansatz parameters: ', ansatz_grad) - -from mindquantum.framework import MQLayer -import mindspore as ms - -ms.set_seed(1) -ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target="CPU") - -QuantumNet = MQLayer(grad_ops) -QuantumNet - -from mindspore import nn -from mindspore.nn import Adam, TrainOneStepCell - -opti = Adam(QuantumNet.trainable_params(), learning_rate=0.5) -net = TrainOneStepCell(QuantumNet, opti) - -for i in range(200): - res = net(ms.Tensor(encoder_data)) - if i % 10 == 0: - print(i, ': ', res) - -theta0, theta1 = QuantumNet.weight.asnumpy() - -print(QuantumNet.weight.asnumpy()) - -pr = { - 'alpha0': alpha0, - 'alpha1': alpha1, - 'alpha2': alpha2, - 'theta0': theta0, - 'theta1': theta1 -} -state = circuit.get_qs(pr=pr, ket=True) - -print(state) - -state = circuit.get_qs(pr=pr) -fid = np.abs(np.vdot(state, [1, 0]))**2 - -print(fid) diff --git a/tutorials/source/3.classification_of_iris_by_qnn.py b/tutorials/source/3.classification_of_iris_by_qnn.py deleted file mode 100644 index b0b60ac52..000000000 --- a/tutorials/source/3.classification_of_iris_by_qnn.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -from sklearn import datasets - -iris_dataset = datasets.load_iris() - -print(iris_dataset.data.shape) -print(iris_dataset.feature_names) -print(iris_dataset.target_names) -print(iris_dataset.target) -print(iris_dataset.target.shape) -X = iris_dataset.data[:100, :].astype(np.float32) -X_feature_names = iris_dataset.feature_names -y = iris_dataset.target[:100].astype(int) -y_target_names = iris_dataset.target_names[:2] - -print(X.shape) -print(X_feature_names) -print(y_target_names) -print(y) -print(y.shape) - -import matplotlib.pyplot as plt - -feature_name = { - 0: 'sepal length', - 1: 'sepal width', - 2: 'petal length', - 3: 'petal width' -} -axes = plt.figure(figsize=(23, 23)).subplots(4, 4) - -colormap = {0: 'r', 1: 'g'} -cvalue = [colormap[i] for i in y] - -for i in range(4): - for j in range(4): - if i != j: - ax = axes[i][j] - ax.scatter(X[:, i], X[:, j], c=cvalue) - ax.set_xlabel(feature_name[i], fontsize=22) - ax.set_ylabel(feature_name[j], fontsize=22) -plt.show() - -alpha = X[:, :3] * X[:, 1:] -X = np.append(X, alpha, axis=1) -print(X.shape) - -from sklearn.model_selection import train_test_split - -X_train, X_test, y_train, y_test = train_test_split(X, - y, - test_size=0.2, - random_state=0, - shuffle=True) - -print(X_train.shape) -print(X_test.shape) - -import mindquantum as mq -from mindquantum.core import Circuit -from mindquantum.core import UN -from mindquantum.core import H, X, RZ - -encoder = Circuit() - -encoder += UN(H, 4) -for i in range(4): - encoder += RZ(f'alpha{i}').on(i) -for j in range(3): #j = 0, 1, 2 - encoder += X.on(j + 1, j) - encoder += RZ(f'alpha{j+4}').on(j + 1) - encoder += X.on(j + 1, j) - -encoder = encoder.no_grad() -encoder.summary() -encoder - -from mindquantum.algorithm import HardwareEfficientAnsatz -from mindquantum.core import RY - -ansatz = HardwareEfficientAnsatz(4, - single_rot_gate_seq=[RY], - entangle_gate=X, - depth=3).circuit -ansatz.summary() -ansatz - -circuit = encoder + ansatz -circuit.summary() -circuit - -from mindquantum.core import QubitOperator -from mindquantum.core import Hamiltonian - -hams = [Hamiltonian(QubitOperator(f'Z{i}')) for i in [2, 3]] -print(hams) - -import mindspore as ms -from mindquantum.framework import MQLayer -from mindquantum.simulator import Simulator - -ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target="CPU") -ms.set_seed(1) -sim = Simulator('projectq', circuit.n_qubits) -grad_ops = sim.get_expectation_with_grad(hams, - circuit, - None, - encoder.params_name, - ansatz.params_name, - parallel_worker=5) -QuantumNet = MQLayer(grad_ops) -QuantumNet - -from mindspore.nn import SoftmaxCrossEntropyWithLogits -from mindspore.nn import Adam, Accuracy -from mindspore import Model -from mindspore.dataset import NumpySlicesDataset -from mindspore.train.callback import Callback, LossMonitor - -loss = SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean') -opti = Adam(QuantumNet.trainable_params(), learning_rate=0.1) - -model = Model(QuantumNet, loss, opti, metrics={'Acc': Accuracy()}) - -train_loader = NumpySlicesDataset({ - 'features': X_train, - 'labels': y_train -}, - shuffle=False).batch(5) -test_loader = NumpySlicesDataset({ - 'features': X_test, - 'labels': y_test -}).batch(5) - - -class StepAcc(Callback): - def __init__(self, model, test_loader): - self.model = model - self.test_loader = test_loader - self.acc = [] - - def step_end(self, run_context): - self.acc.append( - self.model.eval(self.test_loader, dataset_sink_mode=False)['Acc']) - - -monitor = LossMonitor(16) - -acc = StepAcc(model, test_loader) - -model.train(20, - train_loader, - callbacks=[monitor, acc], - dataset_sink_mode=False) - -plt.plot(acc.acc) -plt.title('Statistics of accuracy', fontsize=20) -plt.xlabel('Steps', fontsize=20) -plt.ylabel('Accuracy', fontsize=20) -plt.show() - -from mindspore import ops, Tensor - -predict = np.argmax(ops.Softmax()(model.predict(Tensor(X_test))), axis=1) -correct = model.eval(test_loader, dataset_sink_mode=False) - -print("预测分类结果:", predict) -print("实际分类结果:", y_test) - -print(correct) diff --git a/tutorials/source/4.quantum_phase_estimation.py b/tutorials/source/4.quantum_phase_estimation.py deleted file mode 100644 index 6834d4997..000000000 --- a/tutorials/source/4.quantum_phase_estimation.py +++ /dev/null @@ -1,36 +0,0 @@ -from mindquantum import Circuit -from mindquantum import Simulator -from mindquantum import UN, PhaseShift, qft, H, X, BARRIER -import numpy as np - -n = 3 -c = Circuit() -c += UN(H, n) -c += X.on(n) -print(c) - -for i in range(n): - c += PhaseShift({'phi': 2**i}).on(n, n - i - 1) -print(c) - -c += BARRIER -c += qft(range(n)).hermitian() -print(c) - -from mindquantum import Measure -sim = Simulator('projectq', c.n_qubits) -phi = 0.125 -sim.apply_circuit(c, {'phi': 2 * np.pi * phi}) -qs = sim.get_qs() -print(sim.get_qs(ket=True)) -res = sim.sampling(UN(Measure(), c.n_qubits), shots=100) -print(res) - -index = np.argmax(np.abs(qs)) -print(index) - -bit_string = bin(index)[2:].zfill(c.n_qubits)[1:] -print(bit_string) - -theta_exp = int(bit_string[::-1], 2) / 2**n -print(theta_exp) diff --git a/tutorials/source/8.grover_search_algorithm_based_on_mindquantum.py b/tutorials/source/8.grover_search_algorithm_based_on_mindquantum.py deleted file mode 100644 index 419b0e337..000000000 --- a/tutorials/source/8.grover_search_algorithm_based_on_mindquantum.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- - - -def bitphaseflip_operator(phase_inversion_qubit, n_qubits): - s = [1 for i in range(1 << n_qubits)] - for i in phase_inversion_qubit: - s[i] = -1 - if s[0] == -1: - for i in range(len(s)): - s[i] = -1 * s[i] - circuit = Circuit() - length = len(s) - cz = [] - for i in range(length): - if s[i] == -1: - cz.append([]) - current = i - t = 0 - while current != 0: - if (current & 1) == 1: - cz[-1].append(t) - t += 1 - current = current >> 1 - for j in range(i + 1, length): - if i & j == i: - s[j] = -1 * s[j] - for i in cz: - if i: - if len(i) > 1: - circuit += Z.on(i[-1], i[:-1]) - else: - circuit += Z.on(i[0]) - - return circuit - - -import mindquantum as mq -from mindquantum import Circuit, UN, H, Z -from mindquantum.simulator import Simulator - -n_qubits = 3 -sim = Simulator('projectq', n_qubits) - -circuit = Circuit() -circuit += UN(H, n_qubits) - -sim.apply_circuit(circuit) - -circuit - -print(sim.get_qs(True)) - -sim.reset() - -phase_inversion_qubit = [4] -operator = bitphaseflip_operator(phase_inversion_qubit, n_qubits) - -circuit += operator - -sim.apply_circuit(circuit) - -circuit - -print(sim.get_qs(True)) -print(int('100', 2)) - -n_qubits = 3 -sim1 = Simulator('projectq', n_qubits) - -operator1 = bitphaseflip_operator([i for i in range(1, pow(2, n_qubits))], - n_qubits) - -circuit1 = Circuit() -circuit1 += UN(H, n_qubits) -circuit1 += operator1 - -sim1.apply_circuit(circuit1) - -circuit1 - -print(sim1.get_qs(True)) - - -def G(phase_inversion_qubit, n_qubits): - operator = bitphaseflip_operator(phase_inversion_qubit, n_qubits) - operator += UN(H, n_qubits) - operator += bitphaseflip_operator([i for i in range(1, pow(2, n_qubits))], - n_qubits) - operator += UN(H, n_qubits) - return operator - - -import numpy as np -from numpy import pi, sqrt - -n_qubits = 3 -phase_inversion_qubit = [2] - -N = 2**(n_qubits) -M = len(phase_inversion_qubit) - -r = int(pi / 4 * sqrt(N / M)) - -sim2 = Simulator('projectq', n_qubits) - -circuit2 = Circuit() -circuit2 += UN(H, n_qubits) - -for i in range(r): - circuit2 += G(phase_inversion_qubit, n_qubits) - -sim2.apply_circuit(circuit2) - -circuit2 - -print(sim2.get_qs(True)) -from mindquantum import Measure - -sim2.reset() - -circuit2 += UN(Measure(), circuit2.n_qubits) - -result = sim2.sampling(circuit2, shots=1000) -result - -print(int('010', 2)) - -n_qubits = 5 -phase_inversion_qubit = [5, 11] - -N = 2**(n_qubits) -M = len(phase_inversion_qubit) - -r = int(pi / 4 * sqrt(N / M)) - -sim3 = Simulator('projectq', n_qubits) - -circuit3 = Circuit() -circuit3 += UN(H, n_qubits) - -for i in range(r): - circuit3 += G(phase_inversion_qubit, n_qubits) - -sim3.apply_circuit(circuit3) - -circuit3 - -print(sim3.get_qs(True)) - -sim3.reset() - -circuit3 += UN(Measure(), circuit3.n_qubits) - -result1 = sim3.sampling(circuit3, shots=1000) -print(result1) - -print(int('00101', 2)) -print(int('01011', 2)) diff --git a/tutorials/source/iris_classification.py b/tutorials/source/iris_classification.py new file mode 100644 index 000000000..4e5dc7432 --- /dev/null +++ b/tutorials/source/iris_classification.py @@ -0,0 +1,151 @@ +import os + +os.environ['OMP_NUM_THREADS'] = '2' +from sklearn import datasets +from sklearn import preprocessing +from sklearn.model_selection import train_test_split +import numpy as np + + +def generate_train_and_test(split=0.8, shuffle=True): + iris = datasets.load_iris() + data = iris.data[:100, :].astype(np.float32) + data = preprocessing.minmax_scale(data) * 2 - 1 + label = np.zeros(100).astype(int) + label[50:] = 1 + return train_test_split(data, label, train_size=split, shuffle=True) + + +train_x, test_x, train_y, test_y = generate_train_and_test() +print('train sample and feature shape: ', train_x.shape) + +import matplotlib.pyplot as plt + +feature_name = { + 0: 'speal length', + 1: 'speal width', + 2: 'petal length', + 3: 'petal width' +} +axs = plt.figure(figsize=(18, 18)).subplots(4, 4) +for i in range(4): + for j in range(4): + if i != j: + ax = axs[i][j] + ax.scatter(train_x[:, i], train_x[:, j], c=train_y) + ax.set_xlabel(feature_name[i]) + ax.set_ylabel(feature_name[j]) +plt.show() + +from mindquantum import H, RZ, RX, X, Circuit + + +def encoder(n): + c = Circuit([H.on(i) for i in range(n)]) + for i in range(n): + c += RZ(f'x{i}').on(i) + for i in range(n - 1): + c += X.on(i + 1, i) + c += RZ(f'x{i},{i+1}').on(i + 1) + c += X.on(i + 1, i) + return c + + +enc = encoder(4).no_grad() +enc + +from mindquantum.ansatz import HardwareEfficientAnsatz +from mindquantum import X + +ans = HardwareEfficientAnsatz(4, + single_rot_gate_seq=[RX], + entangle_gate=X, + depth=3).circuit +ans.summary() + +from mindquantum.ops import QubitOperator +from mindquantum import Hamiltonian + +hams = [Hamiltonian(QubitOperator(f'Z{i}')) for i in [0, 1]] +hams + +from mindquantum.nn import MindQuantumLayer + +pqc = MindQuantumLayer(enc.para_name, + ans.para_name, + enc + ans, + hams, + n_threads=5) +pqc + +import mindspore.dataset as ds +import mindspore.nn as nn +import mindspore as ms +from mindspore.train.callback import Callback + + +class StepAcc(Callback): + def __init__(self, model, test_loader): + self.model = model + self.test_loader = test_loader + self.acc = [] + + def step_end(self, run_context): + self.acc.append( + self.model.eval(self.test_loader, dataset_sink_mode=False)['Acc']) + + +class DataPrep(ms.nn.Cell): + def __init__(self): + super(DataPrep, self).__init__() + self.concat = ms.ops.Concat(axis=1) + self.pi = np.pi + + def construct(self, x): + y = (self.pi - x[:, :-1]) * (self.pi - x[:, 1:]) + y = self.concat((x, y)) + return y + + +class QuantumNet(ms.nn.Cell): + def __init__(self, pqc): + super(QuantumNet, self).__init__() + self.dp = DataPrep() + self.pqc = pqc + + def construct(self, x): + x = self.dp(x) + x = self.pqc(x) + return x + + +batch = 5 +train_loader = ds.NumpySlicesDataset({ + 'feats': train_x, + 'labs': train_y +}, + shuffle=False).batch(batch) +test_loader = ds.NumpySlicesDataset({ + 'feats': test_x, + 'labs': test_y +}).batch(batch) +net = QuantumNet(pqc) +loss = ms.nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean') +opti = ms.nn.Adam(net.trainable_params(), learning_rate=1e-1) +monitor = ms.train.callback.LossMonitor(16) +model = ms.Model(net, loss, opti, metrics={'Acc': ms.nn.Accuracy()}) +acc = StepAcc(model, test_loader) +model.train(10, + train_loader, + callbacks=[monitor, acc], + dataset_sink_mode=False) + +plt.plot(acc.acc) +plt.title('acc') +plt.xlabel('step') +plt.ylabel('acc') +plt.show() + +predict = np.argmax(ms.ops.Softmax()(model.predict(ms.Tensor(test_x))), axis=1) +corr = model.eval(test_loader, dataset_sink_mode=False) +print(corr) diff --git a/tutorials/source/parameterized_quantum_circuit.py b/tutorials/source/parameterized_quantum_circuit.py new file mode 100644 index 000000000..841f70b6e --- /dev/null +++ b/tutorials/source/parameterized_quantum_circuit.py @@ -0,0 +1,91 @@ +import numpy as np +import mindquantum as mq +from mindquantum.gate import H, X, Y, RY, RX + +print('Gate name: ', Y) +print('Gate matrix: \n', Y.matrix()) + +ry = RY('a') +ry.matrix({'a': 0.5}) + +eng = mq.engine.CircuitEngine() +qubits = eng.allocate_qureg(3) +H | qubits[0] +X | (qubits[0], qubits[1]) +RY('p1') | qubits[2] +encoder = eng.circuit +print(encoder) +encoder.summary() + +from mindquantum.engine import circuit_generator + + +@circuit_generator(3) +def encoder(qubits): + H | qubits[0] + X | (qubits[0], qubits[1]) + RY('p1') | qubits[2] + + +print(encoder) +encoder.summary() + +@circuit_generator(3, prefix='encoder') +def encoder(qubits, prefix): + H | qubits[0] + X | (qubits[0], qubits[1]) + RY(prefix + '_1') | qubits[2] + + +print(encoder) +encoder.summary() + +from mindquantum import Circuit + +encoder = Circuit() +encoder += H.on(0) +encoder += X.on(1, 0) +encoder += RY('p1').on(2) +print(encoder) +encoder.summary() + +from mindquantum.ops import QubitOperator + + +@circuit_generator(2) +def encoder(qubits): + RY('a') | qubits[0] + RY('b') | qubits[1] + + +@circuit_generator(2) +def ansatz(qubits): + X | (qubits[0], qubits[1]) + RX('p1') | qubits[0] + RX('p2') | qubits[1] + + +ham = mq.Hamiltonian(QubitOperator('Z1')) +encoder_names = ['a', 'b'] +ansatz_names = ['p1', 'p2'] + +from mindquantum.nn import generate_pqc_operator +from mindspore import Tensor +from mindspore import context + +context.set_context(mode=context.GRAPH_MODE, device_target="CPU") + +pqc = generate_pqc_operator(encoder_names, ansatz_names, encoder + ansatz, ham) +encoder_data = Tensor(np.array([[0.1, 0.2]]).astype(np.float32)) +ansatz_data = Tensor(np.array([0.3, 0.4]).astype(np.float32)) +measure_result, encoder_grad, ansatz_grad = pqc(encoder_data, ansatz_data) +print('Measurement result: ', measure_result.asnumpy()) +print('Gradient of encoder parameters: ', encoder_grad.asnumpy()) +print('Gradient of ansatz parameters: ', ansatz_grad.asnumpy()) + +encoder.no_grad() +pqc = generate_pqc_operator(encoder_names, ansatz_names, encoder + ansatz, ham) +measure_result, encoder_grad, ansatz_grad = pqc(encoder_data, ansatz_data) +print('Measurement result: ', measure_result.asnumpy()) +print('Gradient of encoder parameters: ', encoder_grad.asnumpy()) +print('Gradient of ansatz parameters: ', ansatz_grad.asnumpy()) diff --git a/tutorials/source/6.qnn_for_nlp.py b/tutorials/source/qnn_for_nlp.py similarity index 88% rename from tutorials/source/6.qnn_for_nlp.py rename to tutorials/source/qnn_for_nlp.py index f2193cd57..fdc694e4c 100644 --- a/tutorials/source/6.qnn_for_nlp.py +++ b/tutorials/source/qnn_for_nlp.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- import time import numpy as np -from mindquantum.core import QubitOperator +from mindquantum.ops import QubitOperator import mindspore.ops as ops import mindspore.dataset as ds from mindspore import nn from mindspore.train.callback import LossMonitor from mindspore import Model -from mindquantum.framework import MQLayer +from mindquantum.nn import MindQuantumLayer from mindquantum import Hamiltonian, Circuit, RX, RY, X, H, UN @@ -45,30 +44,34 @@ def GenerateEncoderCircuit(n_qubits, prefix=''): GenerateEncoderCircuit(3, prefix='e') -from mindquantum.simulator import Simulator +from mindquantum.nn import generate_evolution_operator from mindspore import context from mindspore import Tensor -n_qubits = 3 -label = 2 -label_bin = bin(label)[-1:1:-1].ljust(n_qubits, '0') -label_array = np.array([int(i) * np.pi for i in label_bin]).astype(np.float32) -encoder = GenerateEncoderCircuit(n_qubits, prefix='e') -encoder_params_names = encoder.params_name +n_qubits = 3 # number of qubits of this quantum circuit +label = 2 # label need to encode +label_bin = bin(label)[-1:1:-1].ljust(n_qubits, '0') # binary form of label +label_array = np.array([int(i) * np.pi for i in label_bin + ]).astype(np.float32) # parameter value of encoder +encoder = GenerateEncoderCircuit(n_qubits, prefix='e') # encoder circuit +encoder_para_names = encoder.para_name # parameter names of encoder print("Label is: ", label) print("Binary label is: ", label_bin) print("Parameters of encoder is: \n", np.round(label_array, 5)) print("Encoder circuit is: \n", encoder) -print("Encoder parameter names are: \n", encoder_params_names) +print("Encoder parameter names are: \n", encoder_para_names) -state = encoder.get_qs(pr=dict(zip(encoder_params_names, label_array))) +context.set_context(mode=context.GRAPH_MODE, device_target="CPU") +# quantum state evolution operator +evol = generate_evolution_operator(param_names=encoder_para_names, + circuit=encoder) +state = evol(Tensor(label_array)) amp = np.round(np.abs(state)**2, 3) print("Amplitude of quantum state is: \n", amp) print("Label in quantum state is: ", np.argmax(amp)) - def GenerateTrainData(sample, word_dict): n_qubits = np.int(np.ceil(np.log2(1 + max(word_dict.values())))) data_x = [] @@ -87,7 +90,6 @@ def GenerateTrainData(sample, word_dict): GenerateTrainData(sample, word_dict) - def GenerateAnsatzCircuit(n_qubits, layers, prefix=''): if len(prefix) != 0 and prefix[-1] != '_': prefix += '_' @@ -103,7 +105,6 @@ def GenerateAnsatzCircuit(n_qubits, layers, prefix=''): GenerateAnsatzCircuit(5, 2, 'a') - def GenerateEmbeddingHamiltonian(dims, n_qubits): hams = [] for i in range(dims): @@ -117,7 +118,6 @@ def GenerateEmbeddingHamiltonian(dims, n_qubits): GenerateEmbeddingHamiltonian(5, 5) - def QEmbedding(num_embedding, embedding_dim, window, layers, n_threads): n_qubits = int(np.ceil(np.log2(num_embedding))) hams = GenerateEmbeddingHamiltonian(embedding_dim, n_qubits) @@ -131,11 +131,14 @@ def QEmbedding(num_embedding, embedding_dim, window, layers, n_threads): encoder.no_grad() circ += encoder circ += ansatz - encoder_param_name.extend(encoder.params_name) - ansatz_param_name.extend(ansatz.params_name) - grad_ops = Simulator('projectq', circ.n_qubits).get_expectation_with_grad( - hams, circ, None, encoder_param_name, ansatz_param_name, n_threads) - return MQLayer(grad_ops) + encoder_param_name.extend(encoder.para_name) + ansatz_param_name.extend(ansatz.para_name) + net = MindQuantumLayer(encoder_param_name, + ansatz_param_name, + circ, + hams, + n_threads=n_threads) + return net class CBOW(nn.Cell): @@ -208,7 +211,8 @@ def step_end(self, run_context): import mindspore as ms from mindspore import context from mindspore import Tensor -context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") + +context.set_context(mode=context.GRAPH_MODE, device_target="CPU") corpus = """We are about to study the idea of a computational process. Computational processes are abstract beings that inhabit computers. As they evolve, processes manipulate other abstract things called data. @@ -245,9 +249,6 @@ def step_end(self, run_context): plt.ylabel('Loss') plt.show() -net.embedding.weight.asnumpy() - - class CBOWClassical(nn.Cell): def __init__(self, num_embedding, embedding_dim, window, hidden_dim): super(CBOWClassical, self).__init__() @@ -280,8 +281,6 @@ def construct(self, x): print("train_x shape: ", train_x.shape) print("train_y shape: ", train_y.shape) -context.set_context(mode=context.GRAPH_MODE, device_target="CPU") - train_loader = ds.NumpySlicesDataset({ "around": train_x, "center": train_y diff --git a/tutorials/source/5.quantum_approximate_optimization_algorithm.py b/tutorials/source/quantum_approximate_optimization_algorithm.py similarity index 70% rename from tutorials/source/5.quantum_approximate_optimization_algorithm.py rename to tutorials/source/quantum_approximate_optimization_algorithm.py index 67d984db2..9117d48fc 100644 --- a/tutorials/source/5.quantum_approximate_optimization_algorithm.py +++ b/tutorials/source/quantum_approximate_optimization_algorithm.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -from mindquantum.core import Circuit, Hamiltonian, UN, H, ZZ, RX, QubitOperator -from mindquantum.framework import MQAnsatzOnlyLayer -from mindquantum.simulator import Simulator +from mindquantum.gate import Hamiltonian, H, ZZ, RX +from mindquantum.circuit import Circuit, StateEvolution, UN +from mindquantum.nn import MindQuantumAnsatzOnlyLayer +from mindquantum.ops import QubitOperator import networkx as nx import mindspore.nn as nn import numpy as np @@ -15,6 +15,7 @@ nx.add_path(g, [0, 4]) nx.add_path(g, [0, 2]) nx.draw(g, with_labels=True, font_weight='bold') +plt.show() def build_hc(g, para): @@ -31,6 +32,13 @@ def build_hb(g, para): return hc +def build_ham(g): + hc = QubitOperator() + for i in g.edges: + hc += QubitOperator(f'Z{i[0]} Z{i[1]}') + return hc + + def build_ansatz(g, p): c = Circuit() for i in range(p): @@ -39,11 +47,13 @@ def build_ansatz(g, p): return c -def build_ham(g): - hc = QubitOperator() - for i in g.edges: - hc += QubitOperator(f'Z{i[0]} Z{i[1]}') - return hc +def show_amp(state): + amp = np.abs(state)**2 + n_qubits = int(np.log2(len(amp))) + labels = [bin(i)[2:].zfill(n_qubits) for i in range(len(amp))] + plt.bar(labels, amp) + plt.xticks(rotation=45) + plt.show() p = 4 @@ -51,13 +61,8 @@ def build_ham(g): ansatz = build_ansatz(g, p) init_state_circ = UN(H, g.nodes) -import mindspore as ms -ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target="CPU") - -circ = init_state_circ + ansatz -sim = Simulator('projectq', circ.n_qubits) -grad_ops = sim.get_expectation_with_grad(ham, circ) -net = MQAnsatzOnlyLayer(grad_ops) +net = MindQuantumAnsatzOnlyLayer(ansatz.para_name, init_state_circ + ansatz, + ham) opti = nn.Adam(net.trainable_params(), learning_rate=0.05) train_net = nn.TrainOneStepCell(net, opti) @@ -65,18 +70,8 @@ def build_ham(g): if i % 10 == 0: print("train step:", i, ", cut:", (len(g.edges) - train_net()) / 2) -pr = dict(zip(ansatz.params_name, net.weight.asnumpy())) -print(circ.get_qs(pr=pr, ket=True)) - - -def show_amp(state): - amp = np.abs(state)**2 - n_qubits = int(np.log2(len(amp))) - labels = [bin(i)[2:].zfill(n_qubits) for i in range(len(amp))] - plt.bar(labels, amp) - plt.xticks(rotation=45) - plt.show() - +pr = dict(zip(ansatz.para_name, net.weight.asnumpy())) -state = circ.get_qs(pr=pr) +print(StateEvolution(init_state_circ + ansatz).final_state(pr, ket=True)) +state = StateEvolution(init_state_circ + ansatz).final_state(pr) show_amp(state) diff --git a/tutorials/source/7.vqe_for_quantum_chemistry.py b/tutorials/source/vqe_for_quantum_chemistry.py similarity index 71% rename from tutorials/source/7.vqe_for_quantum_chemistry.py rename to tutorials/source/vqe_for_quantum_chemistry.py index c64ccc20d..0a1e38aef 100644 --- a/tutorials/source/7.vqe_for_quantum_chemistry.py +++ b/tutorials/source/vqe_for_quantum_chemistry.py @@ -1,17 +1,16 @@ -# -*- coding: utf-8 -*- - import numpy as np from openfermion.chem import MolecularData from openfermionpyscf import run_pyscf import mindquantum as mq -from mindquantum import Circuit, X, RX, Hamiltonian, Simulator -from mindquantum.algorithm import generate_uccsd +from mindquantum.circuit import generate_uccsd, Circuit +from mindquantum.gate import X, RX, Hamiltonian +from mindquantum.nn import generate_pqc_operator import mindspore as ms import mindspore.context as context -from mindspore.common.parameter import Parameter +from mindspore import Parameter from mindspore.common.initializer import initializer -context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") +context.set_context(mode=context.GRAPH_MODE, device_target="CPU") dist = 1.5 geometry = [ @@ -44,13 +43,25 @@ total_circuit.summary() print("Number of parameters: %d" % (len(ansatz_parameter_names))) -sim = Simulator('projectq', total_circuit.n_qubits) -molecule_pqc = sim.get_expectation_with_grad(Hamiltonian(hamiltonian_QubitOp), - total_circuit) +molecule_pqc = generate_pqc_operator(["null"], ansatz_parameter_names, + RX("null").on(0) + total_circuit, + Hamiltonian(hamiltonian_QubitOp)) + +class PQCNet(ms.nn.Cell): + def __init__(self, pqc): + super(PQCNet, self).__init__() + self.pqc = pqc + self.weight = Parameter(initializer("Zeros", + len(self.pqc.ansatz_params_names)), + name="weight") + self.encoder_data_dummy = ms.Tensor([[0]], self.weight.dtype) -from mindquantum.framework import MQAnsatzOnlyLayer + def construct(self): + energy, _, grads = self.pqc(self.encoder_data_dummy, self.weight) + return energy -molecule_pqcnet = MQAnsatzOnlyLayer(molecule_pqc, 'Zeros') + +molecule_pqcnet = PQCNet(molecule_pqc) initial_energy = molecule_pqcnet() print("Initial energy: %20.16f" % (initial_energy.asnumpy())) @@ -75,11 +86,11 @@ print("Optimized energy: %20.16f" % (energy_i)) print("Optimized amplitudes: \n", molecule_pqcnet.weight.asnumpy()) -from mindquantum.algorithm.nisq.chem import Transform -from mindquantum.algorithm.nisq.chem import get_qubit_hamiltonian -from mindquantum.algorithm.nisq.chem import uccsd_singlet_generator, uccsd_singlet_get_packed_amplitudes -from mindquantum.core.operators import TimeEvolution -from mindquantum.framework import MQAnsatzOnlyLayer +from mindquantum.hiqfermion.transforms import Transform +from mindquantum.hiqfermion.ucc import get_qubit_hamiltonian +from mindquantum.hiqfermion.ucc import uccsd_singlet_generator, uccsd_singlet_get_packed_amplitudes +from mindquantum.circuit import TimeEvolution +from mindquantum.nn import MindQuantumAnsatzOnlyLayer hamiltonian_QubitOp = get_qubit_hamiltonian(molecule_of) @@ -90,7 +101,7 @@ ucc_qubit_ops = Transform(ucc_fermion_ops).jordan_wigner() ansatz_circuit = TimeEvolution(ucc_qubit_ops.imag, 1.0).circuit -ansatz_parameter_names = ansatz_circuit.params_name +ansatz_parameter_names = ansatz_circuit.para_name total_circuit = hartreefock_wfn_circuit + ansatz_circuit total_circuit.summary() @@ -102,11 +113,9 @@ init_amplitudes_ccsd[param_i] for param_i in ansatz_parameter_names ] -grad_ops = Simulator('projectq', - total_circuit.n_qubits).get_expectation_with_grad( - Hamiltonian(hamiltonian_QubitOp.real), total_circuit) - -molecule_pqcnet = MQAnsatzOnlyLayer(grad_ops) +molecule_pqcnet = MindQuantumAnsatzOnlyLayer( + ansatz_parameter_names, total_circuit, + Hamiltonian(hamiltonian_QubitOp.real)) molecule_pqcnet.weight = Parameter( ms.Tensor(init_amplitudes_ccsd, molecule_pqcnet.weight.dtype)) diff --git a/tutorials/7.vqe_for_quantum_chemistry.ipynb b/tutorials/vqe_for_quantum_chemistry.ipynb similarity index 78% rename from tutorials/7.vqe_for_quantum_chemistry.ipynb rename to tutorials/vqe_for_quantum_chemistry.ipynb index 6f4acb771..1c7db5c95 100644 --- a/tutorials/7.vqe_for_quantum_chemistry.ipynb +++ b/tutorials/vqe_for_quantum_chemistry.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "grand-contest", "metadata": {}, "source": [ "# 在量子化学计算中应用量子变分求解器\n", @@ -48,27 +49,30 @@ { "cell_type": "code", "execution_count": 1, + "id": "comic-pointer", "metadata": { "scrolled": true }, - "outputs": [], "source": [ "import numpy as np\n", "from openfermion.chem import MolecularData\n", "from openfermionpyscf import run_pyscf\n", "import mindquantum as mq\n", - "from mindquantum import Circuit, X, RX, Hamiltonian, Simulator\n", - "from mindquantum.algorithm import generate_uccsd\n", + "from mindquantum import Circuit, X, RX, Hamiltonian\n", + "from mindquantum.circuit import generate_uccsd\n", + "from mindquantum.nn import generate_pqc_operator\n", "import mindspore as ms\n", "import mindspore.context as context\n", "from mindspore.common.parameter import Parameter\n", "from mindspore.common.initializer import initializer\n", "\n", - "context.set_context(mode=context.PYNATIVE_MODE, device_target=\"CPU\")" - ] + "context.set_context(mode=context.GRAPH_MODE, device_target=\"CPU\")" + ], + "outputs":[] }, { "cell_type": "markdown", + "id": "seasonal-newman", "metadata": {}, "source": [ "## 量子化学计算方法\n", @@ -93,6 +97,7 @@ { "cell_type": "code", "execution_count": 2, + "id": "frequent-framing", "metadata": {}, "outputs": [ { @@ -117,6 +122,7 @@ }, { "cell_type": "markdown", + "id": "alpha-shoot", "metadata": {}, "source": [ "上面的代码定义了一个Li-H键长为1.5Å分子。使用STO-3G基组进行计算。接下来使用openfermionpyscf,调用PySCF进行HF、CCSD和FCI计算。这三种方法属于波函数方法,开始计算之前,先对这些方法作一个简单的介绍。\n", @@ -202,15 +208,16 @@ { "cell_type": "code", "execution_count": 3, + "id": "horizontal-johns", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Hartree-Fock energy: -7.8633576215351164 Ha\n", - "CCSD energy: -7.8823529091527007 Ha\n", - "FCI energy: -7.8823622867987213 Ha\n" + "Hartree-Fock energy: -7.8633576215351200 Ha\n", + "CCSD energy: -7.8823529091527051 Ha\n", + "FCI energy: -7.8823622867987249 Ha\n" ] } ], @@ -234,6 +241,7 @@ }, { "cell_type": "markdown", + "id": "advisory-milton", "metadata": {}, "source": [ "在上面的例子中,我们运行了Hartree-Fock(HF)、CCSD、FCI进行总能量的计算。若对运行时间进行统计,会发现$T_{HF}E_{CCSD}>E_{FCI}$。计算完成后,我们将结果保存到`molecule_file`文件(即`molecule_of.filename`)中:" @@ -242,13 +250,14 @@ { "cell_type": "code", "execution_count": 4, + "id": "overall-puzzle", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "/home/xuxs/anaconda3/lib/python3.8/site-packages/openfermion/testing/data/H1-Li1_sto3g_singlet\n" + "/home/xuxs/anaconda3/envs/p37/lib/python3.7/site-packages/openfermion/testing/data/H1-Li1_sto3g_singlet\n" ] } ], @@ -260,6 +269,7 @@ }, { "cell_type": "markdown", + "id": "cross-wrong", "metadata": {}, "source": [ "量子化学计算的一大阻碍是计算量。随着体系大小(电子数、原子数)的增加,求解FCI波函数和基态能量的时间消耗大约以$2^{N}$增长,即使是较小的分子如乙烯分子等,进行FCI计算也并不容易。量子计算机的出现为此提供了一条可能的解决途径,已有的研究表明,量子计算机可以多项式的时间复杂度模拟哈密顿量的含时演化,在量子处理器上进行化学模拟相较于经典计算机有指数级的加速。本教程将介绍其中一类量子算法:量子变分求解器。\n", @@ -294,19 +304,17 @@ { "cell_type": "code", "execution_count": 5, + "id": "handmade-headline", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "q0: ──X──\n", - " \n", - "q1: ──X──\n", - " \n", - "q2: ──X──\n", - " \n", - "q3: ──X──\n" + "X(0)\n", + "X(1)\n", + "X(2)\n", + "X(3)\n" ] } ], @@ -317,6 +325,7 @@ }, { "cell_type": "markdown", + "id": "promotional-excitement", "metadata": {}, "source": [ "基于此,我们可以构造如下形式的试探波函数:\n", @@ -339,6 +348,7 @@ }, { "cell_type": "markdown", + "id": "brave-discharge", "metadata": {}, "source": [ "使用mindquantum的circuit模块中的`generate_uccsd`函数可读取先前保存在`molecule_file`的计算结果,“一键”构造UCCSD波函数拟设,以及其对应的量子线路:" @@ -347,14 +357,15 @@ { "cell_type": "code", "execution_count": 6, + "id": "hundred-omaha", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ccsd:-7.882352909152701.\n", - "fci:-7.882362286798721.\n" + "ccsd:-7.882352909152705.\n", + "fci:-7.882362286798725.\n" ] } ], @@ -368,6 +379,7 @@ }, { "cell_type": "markdown", + "id": "specified-hello", "metadata": {}, "source": [ "`generate_uccsd`将幺正耦合簇相关的函数打包了起来,包括导出分子哈密度量、构造幺正耦合簇拟设算符、提取CCSD计算的耦合簇系数等多个步骤。该函数通过输入分子的文件路径来读取该分子,参数`th`是表示量子线路中哪些参数需要更新梯度的阈值。在[分步构造幺正耦合簇拟设](#step-by-step)章节,我们会演示如何使用mindquantum的相关接口分步完成其中包含的步骤。完整的量子线路包含HF初态+UCCSD拟设,如下代码所示:" @@ -376,18 +388,19 @@ { "cell_type": "code", "execution_count": 7, + "id": "celtic-citation", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "============================Circuit Summary============================\n", - "|Total number of gates : 15172. |\n", - "|Parameter gates : 640. |\n", - "|with 44 parameters are : p0, p8, p1, p9, p2, p10, p3, p11, p4, p12...|\n", - "|Number qubit of circuit: 12 |\n", - "=======================================================================\n", + "==============================Circuit Summary==============================\n", + "|Total number of gates : 12612. |\n", + "|Parameter gates : 640. |\n", + "|with 44 parameters are : p40, p9, p8, p3, p32, p28, p15, p4, p18, p22... |\n", + "|Number qubit of circuit: 12 |\n", + "===========================================================================\n", "Number of parameters: 44\n" ] } @@ -400,6 +413,7 @@ }, { "cell_type": "markdown", + "id": "municipal-bolivia", "metadata": {}, "source": [ "对于LiH分子而言,其UCCSD波函数拟设中包含44个变分参数。该线路总共的量子比特门数量为12612,总共需要12个量子比特进行模拟。" @@ -407,6 +421,7 @@ }, { "cell_type": "markdown", + "id": "accurate-incentive", "metadata": {}, "source": [ "### VQE的一般流程\n", @@ -429,55 +444,76 @@ { "cell_type": "code", "execution_count": 8, + "id": "featured-chocolate", "metadata": {}, "outputs": [], "source": [ - "sim = Simulator('projectq', total_circuit.n_qubits)\n", - "molecule_pqc = sim.get_expectation_with_grad(Hamiltonian(hamiltonian_QubitOp), total_circuit)" + "molecule_pqc = generate_pqc_operator(\n", + " [\"null\"], ansatz_parameter_names, \n", + " RX(\"null\").on(0) + total_circuit, \n", + " Hamiltonian(hamiltonian_QubitOp))" ] }, { "cell_type": "markdown", + "id": "aerial-department", "metadata": {}, "source": [ - "通过将参数的具体数值传入`molecule_pqc`,即可得到对应于此变分参数的能量$E(\\theta)=\\langle \\Psi_{UCC}(\\theta) | \\hat{H} | \\Psi_{UCC}(\\theta) \\rangle$以及关于每个变分参数的导数。" + "由于mindquantum需要提供两套线路(以及参数)分别作为Encoding circuit和Ansatz circuit,此处我们使用`RX(\"null\")`作为一个Encoding circuit,在之后令参数`null`等于0将其无效化。通过将参数的具体数值传入`molecule_pqc`,即可得到对应于此变分参数的能量$E(\\theta)=\\langle \\Psi_{UCC}(\\theta) | \\hat{H} | \\Psi_{UCC}(\\theta) \\rangle$以及关于每个变分参数的导数。" ] }, { "cell_type": "markdown", + "id": "waiting-committee", "metadata": {}, "source": [ - "接下来需要进行VQE优化的(5)~(7)步,即对参数化量子线路进行优化。我们可以借助MindQuantum框架,使用参数化量子线路算子`molecule_pqc`构造一个神经网络模型,然后通过类似于训练神经网络的方法来优化变分参数:" + "接下来需要进行VQE优化的(5)~(7)步,即对参数化量子线路进行优化。我们可以借助MindSpore框架,使用参数化量子线路算子`molecule_pqc`构造一个神经网络模型,然后通过类似于训练神经网络的方法来优化变分参数:" ] }, { "cell_type": "code", "execution_count": 9, + "id": "biological-mouse", "metadata": {}, "outputs": [], "source": [ - "from mindquantum.framework import MQAnsatzOnlyLayer\n", + "class PQCNet(ms.nn.Cell):\n", + " def __init__(self, pqc):\n", + " super(PQCNet, self).__init__()\n", + " self.pqc = pqc\n", + " self.weight = Parameter(initializer(\"Zeros\",\n", + " len(self.pqc.ansatz_params_names)),\n", + " name=\"weight\")\n", + " self.encoder_data_dummy = ms.Tensor([[0]], \n", + " self.weight.dtype)\n", "\n", - "molecule_pqcnet = MQAnsatzOnlyLayer(molecule_pqc, 'Zeros')" + " def construct(self):\n", + " energy, _, grads = self.pqc(self.encoder_data_dummy, self.weight)\n", + " return energy\n", + "\n", + "molecule_pqcnet = PQCNet(molecule_pqc)" ] }, { "cell_type": "markdown", + "id": "preceding-outline", "metadata": {}, "source": [ - "此处我们构造了一个基本的`MQAnsatzOnlyLayer`作为模型示例,该模型可以和常规的机器学习模型类似使用,比如优化权重、计算导数等" + "此处我们手动构造了一个基本的`PQCNet`作为模型示例,该模型可以和常规的机器学习模型类似使用,比如优化权重、计算导数等。更好的选择是使用mindquantum中封装的`MindQuantumAnsatzOnlyLayer`,将会在后文中进行演示。" ] }, { "cell_type": "markdown", + "id": "proved-stress", "metadata": {}, "source": [ - "构造的`MQAnsatzOnlyLayer`使用`\"Zeros\"`关键字,将所有的变分参数初始化为0。使用CCSD(耦合簇理论)或者MP2(二阶多体微扰论)的计算结果也可以作为幺正耦合簇变分参数的初始值。此时有$E(\\vec{0})=\\langle \\Psi_{UCC}(\\vec{0}) | \\hat{H} | \\Psi_{UCC}(\\vec{0}) \\rangle = E_{HF}$:" + "构造的`PQCNet`使用`\"Zeros\"`关键字,将所有的变分参数初始化为0。使用CCSD(耦合簇理论)或者MP2(二阶多体微扰论)的计算结果也可以作为幺正耦合簇变分参数的初始值。此时有$E(\\vec{0})=\\langle \\Psi_{UCC}(\\vec{0}) | \\hat{H} | \\Psi_{UCC}(\\vec{0}) \\rangle = E_{HF}$:" ] }, { "cell_type": "code", "execution_count": 10, + "id": "secret-dubai", "metadata": {}, "outputs": [ { @@ -495,6 +531,7 @@ }, { "cell_type": "markdown", + "id": "charming-argument", "metadata": {}, "source": [ "最后使用mindspore的Adam优化器进行优化,学习率设置为$1\\times 10^{-2}$,优化终止标准设置为$\\left.|\\epsilon|\\right. = \\left.|E^{k+1} - E^{k}|\\right. \\le 1\\times 10^{-8}$" @@ -503,6 +540,7 @@ { "cell_type": "code", "execution_count": 11, + "id": "pediatric-budapest", "metadata": {}, "outputs": [ { @@ -515,27 +553,28 @@ "Step 15 energy -7.8822836875915527\n", "Step 20 energy -7.8823199272155762\n", "Step 25 energy -7.8823370933532715\n", - "Step 30 energy -7.8815641403198242\n", - "Step 35 energy -7.8786268234252930\n", - "Step 40 energy -7.8778734207153320\n", - "Step 45 energy -7.8808088302612305\n", - "Step 50 energy -7.8819031715393066\n", - "Step 55 energy -7.8821558952331543\n", - "Step 60 energy -7.8823504447937012\n", - "Optimization completed at step 63\n", - "Optimized energy: -7.8823523521423340\n", + "Step 30 energy -7.8823437690734863\n", + "Step 35 energy -7.8618836402893066\n", + "Step 40 energy -7.8671770095825195\n", + "Step 45 energy -7.8751692771911621\n", + "Step 50 energy -7.8822755813598633\n", + "Step 55 energy -7.8812966346740723\n", + "Step 60 energy -7.8823189735412598\n", + "Step 65 energy -7.8823523521423340\n", + "Optimization completed at step 67\n", + "Optimized energy: -7.8823528289794922\n", "Optimized amplitudes: \n", - " [ 2.40339446e-04 1.89154677e-03 3.49554531e-02 1.59917790e-02\n", - " 2.33248898e-07 9.09393420e-04 -1.79268172e-05 1.41595434e-02\n", - " 6.28582342e-08 9.08669957e-04 -1.49387897e-05 1.41652254e-02\n", - " -5.46666037e-04 4.26779327e-04 2.86067789e-03 5.38198128e-02\n", - " 2.32545775e-04 -2.78862785e-07 -7.10907813e-08 -7.98562283e-08\n", - " 7.00364581e-07 -9.21200325e-08 -6.73263187e-08 1.26236855e-05\n", - " -1.04519488e-04 7.97090179e-04 -4.01437364e-06 -3.34858555e-06\n", - " -5.49289174e-02 3.09006264e-03 7.01365061e-05 -1.36400865e-06\n", - " -1.35536197e-06 4.63907739e-08 5.32547162e-08 -2.34681625e-08\n", - " 3.92657455e-07 5.11744884e-06 3.09006032e-03 -2.05122589e-07\n", - " 5.91138871e-08 -2.44064164e-08 4.26194856e-06 3.72134935e-04]\n" + " [ 2.3980068e-04 1.8912849e-03 3.5044324e-02 1.6005965e-02\n", + " -1.9985158e-07 9.0940151e-04 1.6222824e-05 1.4160988e-02\n", + " -1.1072063e-07 9.0867787e-04 1.3825165e-05 1.4166672e-02\n", + " -5.4699212e-04 4.2679289e-04 2.8641545e-03 5.3817011e-02\n", + " 2.3320253e-04 1.7034533e-07 6.6684343e-08 -2.7686235e-07\n", + " 7.2332718e-08 1.2834757e-05 -1.0439425e-04 7.1826143e-08\n", + " 3.6483241e-06 6.1677817e-08 3.1003920e-06 7.9770159e-04\n", + " -5.4951470e-02 3.0904056e-03 -4.4321241e-05 8.5840838e-07\n", + " -1.9589644e-08 -4.9430941e-08 8.6163556e-07 -2.5008637e-07\n", + " 2.1493735e-08 -4.6331229e-06 3.0904033e-03 9.5311613e-08\n", + " -4.8755901e-08 2.0483398e-08 -3.9453280e-06 3.7235476e-04]\n" ] } ], @@ -562,6 +601,7 @@ }, { "cell_type": "markdown", + "id": "advised-infrared", "metadata": {}, "source": [ "可以看到,幺正耦合簇给出的计算结果和FCI非常接近,具有良好的精度。" @@ -569,6 +609,7 @@ }, { "cell_type": "markdown", + "id": "honey-budapest", "metadata": {}, "source": [ "## 分步构造幺正耦合簇拟设\n", @@ -578,6 +619,7 @@ }, { "cell_type": "markdown", + "id": "color-indian", "metadata": {}, "source": [ "在上文中,我们使用了`generate_uccsd`一步构造出了幺正耦合簇拟设所需要的所有内容,此处我们将步骤拆分,分别得到我们需要的耦合簇算符、对应的量子线路以及取自于经典CCSD计算结果的变分参数初猜值。\n", @@ -587,18 +629,20 @@ { "cell_type": "code", "execution_count": 12, + "id": "addressed-cathedral", "metadata": {}, "outputs": [], "source": [ - "from mindquantum.algorithm.nisq.chem import Transform\n", - "from mindquantum.algorithm.nisq.chem import get_qubit_hamiltonian\n", - "from mindquantum.algorithm.nisq.chem import uccsd_singlet_generator, uccsd_singlet_get_packed_amplitudes\n", - "from mindquantum.core.operators import TimeEvolution\n", - "from mindquantum.framework import MQAnsatzOnlyLayer" + "from mindquantum.hiqfermion.transforms import Transform\n", + "from mindquantum.hiqfermion.ucc import get_qubit_hamiltonian\n", + "from mindquantum.hiqfermion.ucc import uccsd_singlet_generator, uccsd_singlet_get_packed_amplitudes\n", + "from mindquantum.circuit import TimeEvolution\n", + "from mindquantum.nn.mindquantum_ansatz_only_layer import MindQuantumAnsatzOnlyLayer" ] }, { "cell_type": "markdown", + "id": "strategic-nature", "metadata": {}, "source": [ "分子哈密顿量使用`get_qubit_hamiltonian`,读取之前的计算结果得到:" @@ -607,6 +651,7 @@ { "cell_type": "code", "execution_count": 13, + "id": "vocational-literature", "metadata": {}, "outputs": [], "source": [ @@ -615,6 +660,7 @@ }, { "cell_type": "markdown", + "id": "coordinate-vampire", "metadata": {}, "source": [ "对于幺正耦合簇算符$ \\hat{T} - \\hat{T}^{\\dagger} $,可以使用`uccsd_singlet_generator`进行构造。提供总量子比特数(总自旋轨道数)和总电子数,并设置参数`anti_hermitian=True`:" @@ -623,6 +669,7 @@ { "cell_type": "code", "execution_count": 14, + "id": "sound-course", "metadata": {}, "outputs": [], "source": [ @@ -632,6 +679,7 @@ }, { "cell_type": "markdown", + "id": "serial-possibility", "metadata": {}, "source": [ "上一步构造的`ucc_fermion_ops`是参数化的。使用Jordan-Wigner变换将费米子激发算符映射为Pauli算符:" @@ -640,6 +688,7 @@ { "cell_type": "code", "execution_count": 15, + "id": "silver-following", "metadata": {}, "outputs": [], "source": [ @@ -648,6 +697,7 @@ }, { "cell_type": "markdown", + "id": "miniature-airplane", "metadata": {}, "source": [ "接下来,我们需要得到幺正算符 $ \\exp{(\\hat{T} - \\hat{T}^{\\dagger})} $ 所对应的量子线路。`TimeEvolution`可生成$ \\exp{(-i\\hat{H}t)} $所对应的线路,其中$ \\hat{H} $是一个厄米算符,$t$是实数。需要注意的是,使用`TimeEvolution`时,`ucc_qubit_ops`中已经包含了复数因子$i$,所以我们需要将`ucc_qubit_ops`除以$i$,或者提取其虚部:" @@ -656,15 +706,17 @@ { "cell_type": "code", "execution_count": 16, + "id": "olive-blade", "metadata": {}, "outputs": [], "source": [ "ansatz_circuit = TimeEvolution(ucc_qubit_ops.imag, 1.0).circuit\n", - "ansatz_parameter_names = ansatz_circuit.params_name" + "ansatz_parameter_names = ansatz_circuit.para_name" ] }, { "cell_type": "markdown", + "id": "improved-canvas", "metadata": {}, "source": [ "我们使用`ansatz_parameter_names`记录该线路中的参数名。到目前为止,我们已经得到了VQE量子线路所需要内容,包括哈密顿量`hamiltonian_QubitOp`、参数化的波函数拟设线路`ansatz_circuit`,故可仿照前文,得到完整的态制备线路。其中Hartree-Fock参考态复用之前的`hartreefock_wfn_circuit`:" @@ -673,18 +725,19 @@ { "cell_type": "code", "execution_count": 17, + "id": "former-support", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "==================================Circuit Summary==================================\n", - "|Total number of gates : 15172. |\n", - "|Parameter gates : 640. |\n", - "|with 44 parameters are : s_0, d1_0, s_1, d1_1, s_2, d1_2, s_3, d1_3, s_4, d1_4...|\n", - "|Number qubit of circuit: 12 |\n", - "===================================================================================\n" + "======================================Circuit Summary======================================\n", + "|Total number of gates : 12612. |\n", + "|Parameter gates : 640. |\n", + "|with 44 parameters are : d1_3, d2_26, d2_6, d2_1, d2_2, d2_14, d1_1, s_1, d2_16, d2_11...|\n", + "|Number qubit of circuit: 12 |\n", + "===========================================================================================\n" ] } ], @@ -695,6 +748,7 @@ }, { "cell_type": "markdown", + "id": "sought-intellectual", "metadata": {}, "source": [ "下一步,需要为变分参数提供一个合理的初始值。前文构造的`PQCNet`默认使用0作为初猜,在大多数情况下是可行的。不过,使用CCSD的计算数据作为UCC的出发点,可能会有更好的结果。使用`uccsd_singlet_get_packed_amplitudes`函数从`molecule_of`提取CCSD的参数:" @@ -703,6 +757,7 @@ { "cell_type": "code", "execution_count": 18, + "id": "indie-politics", "metadata": {}, "outputs": [], "source": [ @@ -713,26 +768,26 @@ }, { "cell_type": "markdown", + "id": "active-space", "metadata": {}, "source": [ - "使用`MQAnsatzOnlyLayer`可以方便地由参数、量子线路获得以参数化量子线路为基础的机器学习模型:" + "使用`MindQuantumAnsatzOnlyLayer`可以方便地由参数、量子线路获得以参数化量子线路为基础的机器学习模型:" ] }, { "cell_type": "code", "execution_count": 19, + "id": "rental-pierce", "metadata": {}, "outputs": [], "source": [ - "grad_ops = Simulator('projectq', total_circuit.n_qubits).get_expectation_with_grad(\n", - " Hamiltonian(hamiltonian_QubitOp.real),\n", - " total_circuit)\n", - "\n", - "molecule_pqcnet = MQAnsatzOnlyLayer(grad_ops)" + "molecule_pqcnet = MindQuantumAnsatzOnlyLayer(\n", + " ansatz_parameter_names, total_circuit, Hamiltonian(hamiltonian_QubitOp.real))" ] }, { "cell_type": "markdown", + "id": "ready-trick", "metadata": {}, "source": [ "使用`init_amplitudes_ccsd`(即CCSD计算的耦合簇系数)作为初始变分参数:" @@ -741,6 +796,7 @@ { "cell_type": "code", "execution_count": 20, + "id": "passive-border", "metadata": {}, "outputs": [ { @@ -759,6 +815,7 @@ }, { "cell_type": "markdown", + "id": "behavioral-microphone", "metadata": {}, "source": [ "在这个例子中,CCSD初猜并没有提供一个更好的起点。读者可以对更多的分子、更多种类的初始值(如随机数初猜)等进行测试和探究。最后进行VQE的优化步骤,优化器依然使用Adam,收敛标准不变。优化所用的代码与前文基本一致,注意更新相应的变量即可:" @@ -767,6 +824,7 @@ { "cell_type": "code", "execution_count": 21, + "id": "million-twelve", "metadata": {}, "outputs": [ { @@ -775,32 +833,32 @@ "text": [ "eps: 1e-08\n", "Step 0 energy -7.8173098564147949\n", - "Step 5 energy -7.8740758895874023\n", + "Step 5 energy -7.8740763664245605\n", "Step 10 energy -7.8818783760070801\n", "Step 15 energy -7.8821649551391602\n", "Step 20 energy -7.8822622299194336\n", - "Step 25 energy -7.8823080062866211\n", - "Step 30 energy -7.8822288513183594\n", - "Step 35 energy -7.8758554458618164\n", - "Step 40 energy -7.8761253356933594\n", - "Step 45 energy -7.8807921409606934\n", - "Step 50 energy -7.8818383216857910\n", - "Step 55 energy -7.8821811676025391\n", - "Step 60 energy -7.8823504447937012\n", - "Optimization completed at step 63\n", + "Step 25 energy -7.8823084831237793\n", + "Step 30 energy -7.8823180198669434\n", + "Step 35 energy -7.8737111091613770\n", + "Step 40 energy -7.8724455833435059\n", + "Step 45 energy -7.8801403045654297\n", + "Step 50 energy -7.8821926116943359\n", + "Step 55 energy -7.8818311691284180\n", + "Step 60 energy -7.8823456764221191\n", + "Optimization completed at step 64\n", "Optimized energy: -7.8823523521423340\n", "Optimized amplitudes: \n", - " [-2.42472161e-04 1.89258391e-03 -3.46013680e-02 1.59353409e-02\n", - " -8.40432079e-08 9.09362687e-04 2.01798011e-05 1.41534032e-02\n", - " -2.81526667e-07 9.08639806e-04 2.00776722e-05 1.41590768e-02\n", - " 5.45396935e-04 4.26715094e-04 -2.84755090e-03 5.38354851e-02\n", - " 2.29954778e-04 9.55212727e-07 -1.24844689e-07 -9.20767249e-08\n", - " -4.53033465e-07 -7.44455733e-08 -8.83169875e-08 1.17984437e-05\n", - " -1.04996754e-04 7.94677646e-04 -4.50417019e-06 -4.49753043e-06\n", - " -5.48430867e-02 3.08870710e-03 -6.50319926e-05 1.26427835e-06\n", - " 1.25660222e-06 -2.82077963e-07 7.96948143e-08 -3.28978906e-08\n", - " -3.63568660e-07 5.76087541e-06 3.08870478e-03 8.82309266e-08\n", - " 5.73797401e-08 -2.53652850e-08 5.72846511e-06 3.71275470e-04]\n" + " [-2.4216002e-04 1.8924323e-03 -3.4653045e-02 1.5943546e-02\n", + " 3.6362690e-07 9.0936717e-04 -1.7181528e-05 1.4154296e-02\n", + " -4.4650793e-08 9.0864423e-04 -2.6399141e-06 1.4159971e-02\n", + " 5.4558384e-04 4.2672374e-04 -2.8494308e-03 5.3833455e-02\n", + " 2.3033506e-04 1.2578158e-06 3.3855862e-08 7.3955505e-08\n", + " -5.2005623e-07 2.9746575e-08 1.2325607e-08 1.1919828e-05\n", + " -1.0492613e-04 7.9503102e-04 3.8478893e-06 5.9738107e-07\n", + " -5.4855812e-02 3.0889052e-03 7.9252044e-05 -1.5384763e-06\n", + " -1.5373821e-06 -3.0784176e-07 -3.5303248e-08 1.7360321e-08\n", + " 4.4359115e-07 -4.9067144e-06 3.0889027e-03 1.3888703e-07\n", + " -1.6715177e-08 6.3234533e-09 -7.5149819e-07 3.7140178e-04]\n" ] } ], @@ -827,6 +885,7 @@ }, { "cell_type": "markdown", + "id": "changing-coach", "metadata": {}, "source": [ "## 总结\n", @@ -851,7 +910,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.7.5" } }, "nbformat": 4,