diff --git a/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_I.ipynb b/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_I.ipynb index ad0640a5b..8c893b9e6 100644 --- a/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_I.ipynb +++ b/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_I.ipynb @@ -83,7 +83,7 @@ "\n", "show(qprog) # Visualize the quantum program for analysis\n", "\n", - "# Execute and print the results:\n", + "# Execute and print the results\n", "with ExecutionSession(qprog) as es:\n", " res = es.sample()\n", " display(res.dataframe)" @@ -168,12 +168,19 @@ "\n", "@qfunc\n", "def main(x: Output[QNum[3, SIGNED, 0]]):\n", + " allocate(x)\n", " # TODO: Create GHZ state with QNum (gives 0 and -1)\n", " # Hint: Is it any different from the previous main implementation?\n", " pass\n", "\n", "\n", - "# TODO: Synthesize the model, show, execute and print results" + "qprog = synthesize(main)\n", + "show(qprog) # Visualize the quantum program for analysis\n", + "\n", + "# Execute and print the results\n", + "with ExecutionSession(qprog) as es:\n", + " res = es.sample()\n", + " display(res.dataframe)" ], "outputs": [], "execution_count": null @@ -236,13 +243,20 @@ " allocate(b)\n", " # TODO: Put a and b in equal superposition\n", "\n", - " # TODO:Assign the value of 3*a + b to c\n", + " # TODO: Assign the value of 3*a + b to c\n", + " allocate(1, c) # Placeholder - replace with actual assignment\n", "\n", " # Print out c's inferred size in qubits\n", " print(f\"The size of c is {c.size}\")\n", "\n", "\n", - "# TODO: Synthesize the model, show, execute and print results" + "qprog = synthesize(main)\n", + "show(qprog) # Visualize the quantum program for analysis\n", + "\n", + "# Execute and print the results\n", + "with ExecutionSession(qprog) as es:\n", + " res = es.sample()\n", + " display(res.dataframe)" ], "outputs": [], "execution_count": null @@ -288,7 +302,7 @@ " # TODO: Put a and b in equal superposition\n", "\n", " # TODO: Assign the value of 3*a + b to c\n", - " # Hint: Is it any different from the previous main implementation?\n", + " allocate(1, c) # Placeholder - replace with actual assignment\n", "\n", " # Print out the numeric attributes of the inferred type\n", " print(\"Numeric attributes of c:\")\n", @@ -297,7 +311,13 @@ " )\n", "\n", "\n", - "# TODO: Synthesize the model, show, execute and print results" + "qprog = synthesize(main)\n", + "show(qprog) # Visualize the quantum program for analysis\n", + "\n", + "# Execute and print the results\n", + "with ExecutionSession(qprog) as es:\n", + " res = es.sample()\n", + " display(res.dataframe)" ], "outputs": [], "execution_count": null @@ -345,7 +365,13 @@ " # TODO : Flip the state of flag if x < 0.5\n", "\n", "\n", - "# TODO: Synthesize the model, show, execute and print results" + "qprog = synthesize(main)\n", + "show(qprog) # Visualize the quantum program for analysis\n", + "\n", + "# Execute and print the results\n", + "with ExecutionSession(qprog) as es:\n", + " res = es.sample()\n", + " display(res.dataframe)" ], "outputs": [], "execution_count": null @@ -419,10 +445,10 @@ " grover_operator(v)\n", "\n", "\n", - "# Synthesize the model, show, execute and print results\n", "qprog = synthesize(main)\n", - "show(qprog)\n", + "show(qprog) # Visualize the quantum program for analysis\n", "\n", + "# Execute and print the results\n", "with ExecutionSession(qprog) as es:\n", " res = es.sample()\n", " display(res.dataframe)" @@ -445,19 +471,17 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "### Exercise 6: Computing x² Using Phase Operations" - ] + "source": "### Exercise 6: Phase Arithmetic" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Part A: Phase Encoding x²\n", + "#### Part A\n", + "\n", + "Compute the expression $x * y$ in the phase of their respective states. Use a coefficient to distribute all possible states over the $2\\pi$ phase rotation.\n", "\n", - "Compute x² in the phase of its state. To actually view the phases of the states simulate the program using state-vector simulation.\n", - "Expand the quantum program visualization down to the gate-level implementation. How is the 'phase' statement synthesized?\n", - "Inspect the resulting phases of the different states. Do they match the phase expression?\n", + "To actually view the phases of the states, simulate the program using state-vector simulation. Expand the quantum program visualization down to the gate-level implementation. How is the *phase* statement synthesized? Inspect the resulting phases of the different states in the printout. Do they match the phase expression over `x` and `y`?\n", "\n", "Further reading: [Phase Statement](https://docs.classiq.io/latest/qmod-reference/language-reference/statements/phase/)" ] @@ -473,11 +497,12 @@ "\n", "\n", "@qfunc\n", - "def main(x: Output[QNum[3]]):\n", + "def main(x: Output[QNum[2]], y: Output[QNum[2]]):\n", " allocate(x)\n", - " # TODO: put x in uniform superposition\n", + " allocate(y)\n", + " # TODO: put x and y in uniform superposition\n", "\n", - " # TODO: Encode x² into the phase\n", + " # TODO: Encode x * y into the phase with a normalization coefficient to 2pi\n", "\n", "\n", "# Synthesize the model and show\n", @@ -517,7 +542,7 @@ "source": [ "#### Bonus: Fourier Arithmetic\n", "\n", - "Create a quantum program that computes res = x² by encoding the result in the phase of a Fourier basis. Then transform the result back to the computational basis. Inspect the execution results to validate the correctness of your algorithm." + "Create a quantum program that computes $y = x^2$ by computing $x^2$ in the Fourier basis. Then transform the result back to the computational basis. Inspect the execution results to validate the correctness of your algorithm." ] }, { @@ -530,12 +555,12 @@ "\n", "\n", "@qfunc\n", - "def main(x: Output[QNum[3]], res: Output[QNum[4]]):\n", + "def main(x: Output[QNum[3]], y: Output[QNum[4]]):\n", " allocate(x)\n", " hadamard_transform(x)\n", - " allocate(res)\n", + " allocate(y)\n", "\n", - " # TODO: Use within_apply with QFT and phase operations\n", + " # TODO: Use within_apply and function qft() to transform into and out of the Fourier basis\n", "\n", "\n", "# Synthesize the model and show\n", @@ -677,7 +702,7 @@ "\n", "with ExecutionSession(qprog) as es:\n", " res = es.sample()\n", - " print(\"Ex.4A Results:\")\n", + " print(\"Ex.3A Results:\")\n", " display(res.dataframe)" ], "outputs": [], @@ -694,7 +719,6 @@ "cell_type": "code", "metadata": {}, "source": [ - "# Part B: Fractioned quantum numbers (explicit syntax)\n", "from classiq import *\n", "\n", "\n", @@ -724,7 +748,7 @@ "\n", "with ExecutionSession(qprog) as es:\n", " res = es.sample()\n", - " print(\"Ex.4B Results:\")\n", + " print(\"Ex.3B Results:\")\n", " display(res.dataframe)" ], "outputs": [], @@ -760,7 +784,7 @@ "\n", "with ExecutionSession(qprog) as es:\n", " res = es.sample()\n", - " print(\"Ex.3 Results:\")\n", + " print(\"Ex.4 Results:\")\n", " display(res.dataframe)" ], "outputs": [], @@ -790,7 +814,7 @@ "\n", "@qfunc\n", "def phase_oracle(v: MyProblemVars):\n", - " # Apply a phase flip if the state of v satisfies the equation (use field access in the form `v.a` etc.)\n", + " # Apply a phase flip if the state of v satisfies the equation (use field access in the form 'v.a' etc.)\n", " control(3 * v.a + v.b + 2 * v.c == 9, lambda: phase(pi))\n", "\n", "\n", @@ -831,16 +855,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "### Solution 6: Computing x² Using Phase Operations" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Part A: Phase Encoding x²" - ] + "source": "### Solution 6: Phase Arithmetic" }, { "cell_type": "code", @@ -853,18 +868,20 @@ "\n", "\n", "@qfunc\n", - "def main(x: Output[QNum[3]]):\n", + "def main(x: Output[QNum[2]], y: Output[QNum[2]]):\n", " allocate(x)\n", - " # Put x in uniform superposition\n", + " allocate(y)\n", + " # Put x and y in uniform superposition\n", " hadamard_transform(x)\n", + " hadamard_transform(y)\n", "\n", - " # Encode x² into the phase\n", - " phase(x**2, 2 * np.pi / 8)\n", + " # Encode x * y into the phase with a normalization coefficient to 2pi\n", + " phase(x * y, np.pi / 8)\n", "\n", "\n", "# Synthesize the model and show\n", "qprog = synthesize(main)\n", - "# show(qprog)\n", + "show(qprog)\n", "\n", "# Specify execution preferences for state vector simulation\n", "preferences = ExecutionPreferences(\n", @@ -872,7 +889,7 @@ " backend_preferences=ClassiqBackendPreferences(backend_name=\"simulator_statevector\"),\n", ")\n", "\n", - "# Execute and print results:\n", + "# Execute and print results\n", "with ExecutionSession(qprog, preferences) as es:\n", " res = es.sample()\n", " print(\"Ex.6A Results:\")\n", @@ -884,9 +901,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "#### Bonus: Fourier Arithmetic\n" - ] + "source": "#### Bonus\n" }, { "cell_type": "code", @@ -898,17 +913,16 @@ "\n", "\n", "@qfunc\n", - "def main(x: Output[QNum[3]], res: Output[QNum[4]]):\n", + "def main(x: Output[QNum[3]], y: Output[QNum[4]]):\n", " allocate(x)\n", " hadamard_transform(x)\n", - " allocate(res)\n", + " allocate(y)\n", "\n", - " # Use within_apply with QFT and phase operations\n", + " # Use within_apply and function qft() to transform into and out of the Fourier basis\n", " within_apply(\n", - " lambda: qft(res),\n", - " lambda: phase(\n", - " res * (x**2), 2 * np.pi / (2**res.size)\n", - " ), # evaluates res += x**2 in the Fourier basis\n", + " lambda: qft(y),\n", + " # Evaluates y += x**2 in the Fourier basis\n", + " lambda: phase(y * (x**2), 2 * np.pi / (2**y.size)),\n", " )\n", "\n", "\n", @@ -919,7 +933,6 @@ "with ExecutionSession(qprog) as es:\n", " res = es.sample()\n", " print(\"Ex.6B Results:\")\n", - " res.dataframe.sort_values(by=\"x\", inplace=True) # Sort by x for better readability\n", " display(res.dataframe)" ], "outputs": [], diff --git a/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_II.ipynb b/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_II.ipynb index 2007ecddc..43336c83a 100644 --- a/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_II.ipynb +++ b/tutorials/workshops/algo_design_QCE_tutorial/algo_design_QCE_tutorial_part_II.ipynb @@ -37,13 +37,10 @@ "NUM_LAYERS = 3\n", "\n", "\n", - "def maxcut_cost(v: QArray[QBit, 3]): # graph of 3 nodes\n", - " return -(\n", - " v[0] * (1 - v[1])\n", - " + v[1] * (1 - v[0]) # cut edge (0, 1)\n", - " + v[0] * (1 - v[2])\n", - " + v[2] * (1 - v[0]) # cut edge (0, 2)\n", - " )\n", + "# Python function encapsulating the cost expression (reused in ansatz and classical optimizer loop)\n", + "def maxcut_cost(v: QArray[QBit, 3]): # Toy graph with 3 nodes and 2 edges\n", + " graph_edges = [(0, 1), (0, 2)]\n", + " return -sum(v[n1] ^ v[n2] for (n1, n2) in graph_edges)\n", "\n", "\n", "@qfunc\n", @@ -52,7 +49,7 @@ "\n", "\n", "@qfunc\n", - "def init(v: QArray[QBit]):\n", + "def init_layer(v: QArray[QBit]):\n", " apply_to_all(lambda q: H(q), v)\n", "\n", "\n", @@ -66,11 +63,13 @@ " params: CArray[CReal, NUM_LAYERS * 2],\n", " v: Output[QArray[QBit, 3]],\n", "):\n", + " gammas = params[0:NUM_LAYERS]\n", + " betas = params[NUM_LAYERS : 2 * NUM_LAYERS]\n", " allocate(v)\n", - " init(v)\n", + " init_layer(v)\n", " for i in range(NUM_LAYERS):\n", - " cost_layer(v, params[i])\n", - " mixer_layer(v, params[NUM_LAYERS + i])\n", + " cost_layer(v, gammas[i])\n", + " mixer_layer(v, betas[i])\n", "\n", "\n", "qprog = synthesize(main)\n", @@ -102,7 +101,7 @@ " max_iteration=40,\n", " )\n", " res = es.sample(parameters=trace[-1][1])\n", - " print(\"Result histogram = \", res.dataframe)" + " display(res.dataframe)" ], "outputs": [], "execution_count": null @@ -267,17 +266,11 @@ "\n", "def sample_anzatz(anzatz, params, num_shots):\n", " with ExecutionSession(anzatz, ExecutionPreferences(num_shots=num_shots)) as es:\n", - " # Sample the ansatz with the optimized parameters\n", " res = es.sample(parameters=params)\n", " res.dataframe[\"feasible value\"] = res.dataframe.apply(\n", " lambda row: feasible_value(dotdict(a=row[\"v.a\"], b=row[\"v.b\"])), axis=1\n", " )\n", - " return res\n", - "\n", - "\n", - "@qfunc\n", - "def dummy_cost_layer(qba: QArray, gamma: CReal):\n", - " phase(qba[0], gamma)" + " return res" ], "outputs": [], "execution_count": null @@ -288,11 +281,11 @@ "source": [ "## Representing Hard Constraints as Cost\n", "\n", - "The cost operator in QAOA is typically defined in terms of QUBO expressions, or more generally low-degree polynomials over problem variables. In Qmod this is directly expressible with the *phase* statement. But logical operators in phase arithmetic cannot be efficiently synthesised into gate-level descriptions. So we cannot express the knapsack cost directly in terms of the feasible value defined by the problem - the value sum *if* the constraint is satisfied *and otherwise zero*.\n", + "The cost operator in QAOA is typically defined in terms of QUBO expressions, or more generally low-degree polynomials over problem variables. In Qmod this is directly expressible using the *phase* statement. But logical operators are not allowed in the *phase* expression, because they cannot be reduced to low-degree polynomials. The feasible value defined by the problem is the value sum *if* the constraint is satisfied *and otherwise zero*. An expression of this form cannot be used directly in *phase* statement. A common approach is to add to the overall cost a penalty term for violating the constraint.\n", "\n", - "A common approach is to add a penalty value to the overall cost for violation of the constraint. For equality constraints we can square the difference between the expression over the variables and its target value to obtain a non-negative penalty. However, in the knapsack case we need to represent an *inequality* constraint. An inequality expression can be reduced to an equality by introducing another *non-negative* slack (\"don't-care\") variable that represents the difference between the left and right sides of the inequality. When considering the assignment the slack variable is simply disregarded. As an example, the constraint $x + y \\leq 10$ can be expressed as $x + y + slack = 10$. In this case the penalty will be $(x + y + slack - 10)^2$. The cost layer multiplies the penalty term can be given more significance by multiplying it by a constant \"penalty factor\", e.g. $2*(x + y + slack - 10)^2$.\n", + "For equality constraints we can square the difference between the variable expression and its target to obtain a non-negative penalty. However, in the knapsack case we need to represent an *inequality* constraint. An inequality expression can be rewritten as an equality by introducing a *non-negative* slack (\"don't-care\") variable that represents the difference between the left and right sides of the inequality. When evaluating an assignment the slack variable is disregarded. As an example, the constraint $x + y \\leq 10$ can be expressed as $x + y + slack = 10$. In this case the penalty will be $(x + y + slack - 10)^2$. The cost layer multiplies the penalty term can be given more significance by multiplying it by a constant \"penalty factor\", e.g. $2*(x + y + slack - 10)^2$.\n", "\n", - "The penalty-term approach has the downside of not representing the true semantics of the problem. It incentivises assignments that have high value with only a small violation of the constraint. But our problem is defined in terms of a hard constraint, where even a small violation means zero actual value.\n", + "The penalty approach has the downside of not representing the true semantics of the problem. It incentivises assignments that have high value with only a small violation of the constraint. But our problem is defined in terms of a hard constraint, where even a small violation means zero actual value.\n", "\n", "In *Exercise 2* we will define the cost operator in terms of the constraint penalty term, and in *Exercise 3* we will define it to accurately capture the Boolean condition.\n" ] @@ -329,7 +322,7 @@ "@qfunc\n", "def cost_layer(v: KnapsackVarsPenalty, gamma: CReal):\n", " # TODO: Define the cost phase shift in terms of value_sum() and constraint_slack_penalty()\n", - " dummy_cost_layer(v, gamma) # Remove this line (placeholder to avoid syntax error)\n", + " pass\n", "\n", "\n", "@qfunc\n", @@ -338,7 +331,7 @@ " v: Output[KnapsackVarsPenalty],\n", "):\n", " allocate(v)\n", - " init(v)\n", + " init_layer(v)\n", " for i in range(NUM_LAYERS):\n", " cost_layer(v, params[i])\n", " mixer_layer(v, params[NUM_LAYERS + i])\n", @@ -384,7 +377,7 @@ "@qfunc\n", "def cost_layer(v: KnapsackVars, gamma: CReal):\n", " # TODO: Define the cost as phase shift by value_sum(), under the condition that the constraint is satisfied\n", - " dummy_cost_layer(v, gamma) # Remove this line (placeholder to avoid syntax error)\n", + " pass\n", "\n", "\n", "@qfunc\n", @@ -393,14 +386,14 @@ " v: Output[KnapsackVars],\n", "):\n", " allocate(v)\n", - " init(v)\n", + " init_layer(v)\n", " for i in range(NUM_LAYERS):\n", " cost_layer(v, params[i])\n", " mixer_layer(v, params[NUM_LAYERS + i])\n", "\n", "\n", "qprog = synthesize(main)\n", - "# show(qprog)\n", + "show(qprog)\n", "final_params = optimize_qaoa_params(qprog, max_iteration=50)\n", "res = sample_anzatz(qprog, params=final_params, num_shots=10)\n", "display(res.dataframe)" @@ -501,14 +494,14 @@ " v: Output[KnapsackVarsPenalty],\n", "):\n", " allocate(v)\n", - " init(v)\n", + " init_layer(v)\n", " for i in range(NUM_LAYERS):\n", " cost_layer(v, params[i])\n", " mixer_layer(v, params[NUM_LAYERS + i])\n", "\n", "\n", "qprog = synthesize(main)\n", - "show(qprog)\n", + "# show(qprog)\n", "final_params = optimize_qaoa_params(qprog, max_iteration=50)\n", "res = sample_anzatz(qprog, params=final_params, num_shots=10)\n", "display(res.dataframe)" @@ -537,7 +530,7 @@ " v: Output[KnapsackVars],\n", "):\n", " allocate(v)\n", - " init(v)\n", + " init_layer(v)\n", " for i in range(NUM_LAYERS):\n", " cost_layer(v, params[i])\n", " mixer_layer(v, params[NUM_LAYERS + i])\n",