diff --git a/docs/guides/serverless-template-hamiltonian-simulation.ipynb b/docs/guides/serverless-template-hamiltonian-simulation.ipynb index 2a122c4bc9..08de2a770e 100644 --- a/docs/guides/serverless-template-hamiltonian-simulation.ipynb +++ b/docs/guides/serverless-template-hamiltonian-simulation.ipynb @@ -1342,6 +1342,268 @@ "" ] }, + { + "cell_type": "markdown", + "id": "20502fe4-7940-40fa-a978-64cc3ff6c1b1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "version-info" + ] + }, + "source": [ + "
\n", + "Full program source code\n", + "\n", + "Here is the entire source of `./source_files/template_hamiltonian_simulation.py` as one code block.\n", + "\n", + "```python\n", + "\n", + "from qiskit import QuantumCircuit\n", + "from qiskit_serverless import get_arguments, save_result\n", + "\n", + "\n", + "# Extract parameters from arguments\n", + "#\n", + "# Do this at the top of the program so it fails early if any required arguments are missing or invalid.\n", + "\n", + "arguments = get_arguments()\n", + "\n", + "dry_run = arguments.get(\"dry_run\", False)\n", + "backend_name = arguments[\"backend_name\"]\n", + "\n", + "aqc_evolution_time = arguments[\"aqc_evolution_time\"]\n", + "aqc_ansatz_num_trotter_steps = arguments[\"aqc_ansatz_num_trotter_steps\"]\n", + "aqc_target_num_trotter_steps = arguments[\"aqc_target_num_trotter_steps\"]\n", + "\n", + "remainder_evolution_time = arguments[\"remainder_evolution_time\"]\n", + "remainder_num_trotter_steps = arguments[\"remainder_num_trotter_steps\"]\n", + "\n", + "# Stop if this fidelity is achieved\n", + "aqc_stopping_fidelity = arguments.get(\"aqc_stopping_fidelity\", 1.0)\n", + "# Stop after this number of iterations, even if stopping fidelity is not achieved\n", + "aqc_max_iterations = arguments.get(\"aqc_max_iterations\", 500)\n", + "\n", + "hamiltonian = arguments[\"hamiltonian\"]\n", + "observable = arguments[\"observable\"]\n", + "initial_state = arguments.get(\"initial_state\", QuantumCircuit(hamiltonian.num_qubits))\n", + "\n", + "import numpy as np\n", + "import json\n", + "from mergedeep import merge\n", + "\n", + "\n", + "# Configure `EstimatorOptions`, to control the parameters of the hardware experiment\n", + "#\n", + "# Set default options\n", + "estimator_default_options = {\n", + " \"resilience\": {\n", + " \"measure_mitigation\": True,\n", + " \"zne_mitigation\": True,\n", + " \"zne\": {\n", + " \"amplifier\": \"gate_folding\",\n", + " \"noise_factors\": [1, 2, 3],\n", + " \"extrapolated_noise_factors\": list(np.linspace(0, 3, 31)),\n", + " \"extrapolator\": [\"exponential\", \"linear\", \"fallback\"],\n", + " },\n", + " \"measure_noise_learning\": {\n", + " \"num_randomizations\": 512,\n", + " \"shots_per_randomization\": 512,\n", + " },\n", + " },\n", + " \"twirling\": {\n", + " \"enable_gates\": True,\n", + " \"enable_measure\": True,\n", + " \"num_randomizations\": 300,\n", + " \"shots_per_randomization\": 100,\n", + " \"strategy\": \"active\",\n", + " },\n", + "}\n", + "# Merge with user-provided options\n", + "estimator_options = merge(\n", + " arguments.get(\"estimator_options\", {}), estimator_default_options\n", + ")\n", + "\n", + "print(\"estimator_options =\", json.dumps(estimator_options, indent=4))\n", + "\n", + "# Perform parameter validation\n", + "\n", + "if not 0.0 < aqc_stopping_fidelity <= 1.0:\n", + " raise ValueError(\n", + " f\"Invalid stopping fidelity: {aqc_stopping_fidelity}. It must be a positive float no greater than 1.\"\n", + " )\n", + "\n", + "output = {\"metadata\": arguments}\n", + "\n", + "import os\n", + "os.environ[\"NUMBA_CACHE_DIR\"] = \"/data\"\n", + "\n", + "import datetime\n", + "import quimb.tensor\n", + "from scipy.optimize import OptimizeResult, minimize\n", + "from qiskit.synthesis import SuzukiTrotter\n", + "from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit\n", + "from qiskit_addon_aqc_tensor.ansatz_generation import (\n", + " generate_ansatz_from_circuit,\n", + " AnsatzBlock,\n", + ")\n", + "from qiskit_addon_aqc_tensor.simulation import (\n", + " tensornetwork_from_circuit,\n", + " compute_overlap,\n", + ")\n", + "from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator\n", + "from qiskit_addon_aqc_tensor.objective import OneMinusFidelity\n", + "\n", + "print(\"Hamiltonian:\", hamiltonian)\n", + "print(\"Observable:\", observable)\n", + "simulator_settings = QuimbSimulator(quimb.tensor.CircuitMPS, autodiff_backend=\"jax\")\n", + "\n", + "# Construct the AQC target circuit\n", + "aqc_target_circuit = initial_state.copy()\n", + "if aqc_evolution_time:\n", + " aqc_target_circuit.compose(\n", + " generate_time_evolution_circuit(\n", + " hamiltonian,\n", + " synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),\n", + " time=aqc_evolution_time,\n", + " ),\n", + " inplace=True,\n", + " )\n", + "\n", + "# Construct matrix-product state representation of the AQC target state\n", + "aqc_target_mps = tensornetwork_from_circuit(aqc_target_circuit, simulator_settings)\n", + "print(\"Target MPS maximum bond dimension:\", aqc_target_mps.psi.max_bond())\n", + "output[\"target_bond_dimension\"] = aqc_target_mps.psi.max_bond()\n", + "\n", + "# Generate an ansatz and initial parameters from a Trotter circuit with fewer steps\n", + "aqc_good_circuit = initial_state.copy()\n", + "if aqc_evolution_time:\n", + " aqc_good_circuit.compose(\n", + " generate_time_evolution_circuit(\n", + " hamiltonian,\n", + " synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),\n", + " time=aqc_evolution_time,\n", + " ),\n", + " inplace=True,\n", + " )\n", + "aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(aqc_good_circuit)\n", + "print(\"Number of AQC parameters:\", len(aqc_initial_parameters))\n", + "output[\"num_aqc_parameters\"] = len(aqc_initial_parameters)\n", + "\n", + "# Calculate the fidelity of ansatz circuit vs. the target state, before optimization\n", + "good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)\n", + "starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2\n", + "print(\"Starting fidelity of AQC portion:\", starting_fidelity)\n", + "output[\"aqc_starting_fidelity\"] = starting_fidelity\n", + "\n", + "# Optimize the ansatz parameters by using MPS calculations\n", + "def callback(intermediate_result: OptimizeResult):\n", + " fidelity = 1 - intermediate_result.fun\n", + " print(f\"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}\")\n", + " if intermediate_result.fun < stopping_point:\n", + " raise StopIteration\n", + "\n", + "\n", + "objective = OneMinusFidelity(aqc_target_mps, aqc_ansatz, simulator_settings)\n", + "stopping_point = 1.0 - aqc_stopping_fidelity\n", + "\n", + "result = minimize(\n", + " objective,\n", + " aqc_initial_parameters,\n", + " method=\"L-BFGS-B\",\n", + " jac=True,\n", + " options={\"maxiter\": aqc_max_iterations},\n", + " callback=callback,\n", + ")\n", + "if result.status not in (\n", + " 0,\n", + " 1,\n", + " 99,\n", + "): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration\n", + " raise RuntimeError(\n", + " f\"Optimization failed: {result.message} (status={result.status})\"\n", + " )\n", + "print(f\"Done after {result.nit} iterations.\")\n", + "output[\"num_iterations\"] = result.nit\n", + "aqc_final_parameters = result.x\n", + "output[\"aqc_final_parameters\"] = list(aqc_final_parameters)\n", + "\n", + "# Construct an optimized circuit for initial portion of time evolution\n", + "aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)\n", + "\n", + "# Calculate fidelity after optimization\n", + "aqc_final_mps = tensornetwork_from_circuit(aqc_final_circuit, simulator_settings)\n", + "aqc_fidelity = abs(compute_overlap(aqc_final_mps, aqc_target_mps)) ** 2\n", + "print(\"Fidelity of AQC portion:\", aqc_fidelity)\n", + "output[\"aqc_fidelity\"] = aqc_fidelity\n", + "\n", + "# Construct final circuit, with remainder of time evolution\n", + "final_circuit = aqc_final_circuit.copy()\n", + "if remainder_evolution_time:\n", + " remainder_circuit = generate_time_evolution_circuit(\n", + " hamiltonian,\n", + " synthesis=SuzukiTrotter(reps=remainder_num_trotter_steps),\n", + " time=remainder_evolution_time,\n", + " )\n", + " final_circuit.compose(remainder_circuit, inplace=True)\n", + "\n", + "from qiskit_ibm_runtime import QiskitRuntimeService\n", + "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", + "\n", + "service = QiskitRuntimeService()\n", + "backend = service.backend(backend_name)\n", + "\n", + "# Transpile PUBs (circuits and observables) to match ISA\n", + "pass_manager = generate_preset_pass_manager(backend=backend, optimization_level=3)\n", + "isa_circuit = pass_manager.run(final_circuit)\n", + "isa_observable = observable.apply_layout(isa_circuit.layout)\n", + "\n", + "isa_2qubit_depth = isa_circuit.depth(lambda x: x.operation.num_qubits == 2)\n", + "print(\"ISA circuit two-qubit depth:\", isa_2qubit_depth)\n", + "output[\"twoqubit_depth\"] = isa_2qubit_depth\n", + "\n", + "# Exit now if dry run; don't execute on hardware\n", + "if dry_run:\n", + " import sys\n", + "\n", + " print(\"Exiting before hardware execution since `dry_run` is True.\")\n", + " save_result(output)\n", + " sys.exit(0)\n", + "\n", + "# ## Step 3: Execute quantum experiments on backend\n", + "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n", + "\n", + "\n", + "estimator = Estimator(backend, options=estimator_options)\n", + "\n", + "# Submit the underlying Estimator job. Note that this is not the\n", + "# actual function job.\n", + "job = estimator.run([(isa_circuit, isa_observable)])\n", + "print(\"Job ID:\", job.job_id())\n", + "output[\"job_id\"] = job.job_id()\n", + "\n", + "# Wait until job is complete\n", + "hw_results = job.result()\n", + "hw_results_dicts = [pub_result.data.__dict__ for pub_result in hw_results]\n", + "\n", + "# Save hardware results to serverless output dictionary\n", + "output[\"hw_results\"] = hw_results_dicts\n", + "\n", + "# Reorganize expectation values\n", + "hw_expvals = [pub_result_data[\"evs\"].tolist() for pub_result_data in hw_results_dicts]\n", + "\n", + "# Save expectation values to Qiskit Serverless\n", + "print(\"Hardware expectation values\", hw_expvals)\n", + "output[\"hw_expvals\"] = hw_expvals[0]\n", + "\n", + "save_result(output)\n", + "```\n", + "
" + ] + }, { "cell_type": "code", "execution_count": 24,