Skip to content

Commit 4d21272

Browse files
fgfuchsCopilotCopilot
authored
Copilot/copilotimplement objective direction migration (#61)
core objective sense and energy semantics Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: fgfuchs <2428162+fgfuchs@users.noreply.github.com>
1 parent 8f9dfb4 commit 4d21272

37 files changed

Lines changed: 874 additions & 522 deletions

README.md

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A flexible, modular Python library for the [Quantum Approximate Optimization Alg
1313
- [Installation](#installation)
1414
- [Requirements](#requirements)
1515
- [Quick Example](#quick-example)
16+
- [Objective direction and sign convention](#objective-direction-and-sign-convention)
1617
- [Background](#background)
1718
- [Custom Ansatz](#custom-ansatz)
1819
- [Running Optimization](#running-optimization-at-depth-p)
@@ -81,7 +82,8 @@ qaoa.sample_cost_landscape()
8182
qaoa.optimize(depth=3)
8283

8384
# Extract results
84-
print("Optimal expectation value:", qaoa.get_Exp(depth=3))
85+
print("Optimal energy:", qaoa.get_energy(depth=3))
86+
print("Optimal objective:", qaoa.get_objective(depth=3))
8587
print("Optimal parameters (gamma):", qaoa.get_gamma(depth=3))
8688
print("Optimal parameters (beta):", qaoa.get_beta(depth=3))
8789
```
@@ -90,14 +92,38 @@ See [examples/](examples/) for more complete worked examples.
9092

9193
---
9294

95+
## Objective direction and sign convention
96+
97+
Each problem declares an explicit objective direction via `problem.objective_sense` (`"minimize"` or `"maximize"`).
98+
99+
- `problem.objective_value(x)` is the natural mathematical objective.
100+
- `problem.energy(x)` is the canonical quantity minimized by QAOA.
101+
102+
Conversion is centralized:
103+
104+
- `MINIMIZE`: `energy(x) = objective_value(x)`
105+
- `MAXIMIZE`: `energy(x) = -objective_value(x)`
106+
107+
Phase separators follow:
108+
109+
$$U_P(\gamma)|x\rangle = e^{-i\gamma\,\mathrm{energy}(x)}|x\rangle.$$
110+
111+
Examples:
112+
113+
- **MaxCut**: natural objective is positive cut value; energy is its negative.
114+
- **QUBO**: natural objective is the un-negated polynomial
115+
$x^\top Q x + c^\top x + b$.
116+
117+
---
118+
93119
## Background
94-
Given a **cost function**
95-
$$c: \lbrace 0, 1\rbrace^n \rightarrow \mathbb{R}$$
120+
Given an **energy function**
121+
$$E: \lbrace 0, 1\rbrace^n \rightarrow \mathbb{R}$$
96122
one defines a **problem Hamiltonian** $H_P$ through the action on computational basis states via
97123

98-
$$ H_P |x\rangle = c(x) |x\rangle,$$
124+
$$ H_P |x\rangle = E(x) |x\rangle,$$
99125

100-
which means that ground states minimize the cost function $c$.
126+
which means that ground states minimize the canonical energy.
101127
Given a parametrized ansatz $| \gamma, \beta \rangle$, a classical optimizer is used to minimize the energy
102128

103129
$$ \langle \gamma, \beta | H_P | \gamma, \beta \rangle.$$
@@ -119,7 +145,7 @@ $U_M(\beta_l)=e^{-i\beta_l X^{\otimes n}}$, $U_P(\gamma_l)=e^{-i\gamma_l H_P}$,
119145

120146
## Custom Ansatz
121147

122-
To create a custom QAOA ansatz, specify a [problem](qaoa/problems/base_problem.py), a [mixer](qaoa/mixers/base_mixer.py), and an [initial state](qaoa/initialstates/base_initialstate.py). These base classes each have an abstract method `def create_circuit:` that must be implemented. The problem base class additionally requires `def cost:`.
148+
To create a custom QAOA ansatz, specify a [problem](qaoa/problems/base_problem.py), a [mixer](qaoa/mixers/base_mixer.py), and an [initial state](qaoa/initialstates/base_initialstate.py). These base classes each have an abstract method `def create_circuit:` that must be implemented. A custom problem should define `objective_sense` and `objective_value()`. The phase separator must encode `energy(x)` as above.
123149

124150
This library already contains several standard implementations.
125151

@@ -177,12 +203,12 @@ qaoa.sample_cost_landscape()
177203

178204
Sampling high-dimensional target functions quickly becomes intractable for depth $p>1$. The library therefore **iteratively increases the depth**. At each depth a **local optimization** algorithm (e.g. COBYLA) finds a local minimum, using the following **initial guess**:
179205

180-
- At depth $p=1$: parameters $(\gamma, \beta)$ are taken from the minimum of the sampled cost landscape.
206+
- At depth $p=1$: parameters $(\gamma, \beta)$ are taken from the minimum of the sampled energy landscape.
181207
- At depth $p>1$: two strategies are available, controlled by the `interpolate` parameter:
182208

183209
* **Interpolation** (`interpolate=True`, default): uses the [INTERP heuristic](https://arxiv.org/pdf/1812.01041.pdf) to produce a smooth initial guess by interpolating the optimal angles from depth $p-1$. Works well for vanilla QAOA.
184210

185-
* **Layer-by-layer grid scan** (`interpolate=False`): the best angles from depth $p-1$ are *locked* and a 2-D grid search is performed over the new layer's parameters. Because the grid includes $(γ=0, β=0)$ — which adds an identity layer reproducing the depth-$(p-1)$ result — the initial cost at depth $p$ is guaranteed to be ≤ cost at depth $p-1$, ensuring a monotonically increasing approximation ratio. Recommended for multi-angle and orbit ansätze.
211+
* **Layer-by-layer grid scan** (`interpolate=False`): the best angles from depth $p-1$ are *locked* and a 2-D grid search is performed over the new layer's parameters. Because the grid includes $(γ=0, β=0)$ — which adds an identity layer reproducing the depth-$(p-1)$ result — the initial energy at depth $p$ is guaranteed to be ≤ energy at depth $p-1$, ensuring a monotonically increasing approximation ratio. Recommended for multi-angle and orbit ansätze.
186212

187213
```python
188214
# Interpolation (default)
@@ -224,16 +250,24 @@ qaoa = QAOA(
224250

225251
## Extract Results
226252

227-
Once `qaoa.optimize(depth=p)` is run, extract the expectation value, variance, and parameters for each depth $1\leq i \leq p$:
253+
Once `qaoa.optimize(depth=p)` is run, extract the best energy, variance,
254+
objective value, and parameters for each depth $1\leq i \leq p$:
228255

229256
```python
230-
qaoa.get_Exp(depth=i)
257+
qaoa.get_energy(depth=i)
258+
qaoa.get_objective(depth=i)
231259
qaoa.get_Var(depth=i)
232260
qaoa.get_gamma(depth=i)
233261
qaoa.get_beta(depth=i)
234262
```
235263

236-
Additionally, for every optimizer call at each depth, the **angles, expectation value, variance, maximum cost, minimum cost, and number of shots** are stored in:
264+
The legacy compatibility aliases `problem.cost()`, `problem.computeMinMaxCosts()`,
265+
and `qaoa.get_Exp()` are not part of the API anymore. Use
266+
`objective_value()`, `objective_bounds()`, `get_energy()`, and
267+
`get_objective()` directly.
268+
269+
Additionally, for every optimizer call at each depth, the optimizer history,
270+
variance, best solutions, and shot counts are stored in:
237271

238272
```python
239273
qaoa.optimization_results[i]

examples/ExactCover/CompareGroverXY.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"\n",
9797
"opt_sol = ec_problem.brute_force_solve()\n",
9898
"print(f\"Optimal solution (brute force): {opt_sol}\")\n",
99-
"print(f\"Cost: {ec_problem.cost(opt_sol):.6f}\")"
99+
"print(f\"Cost: {ec_problem.objective_value(opt_sol):.6f}\")"
100100
]
101101
},
102102
{

examples/ExactCover/ExactCover 6 3.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@
101101
"print(\"solutions:\")\n",
102102
"costs=[]\n",
103103
"for s in [''.join(i) for i in itertools.product('01', repeat =nL)]:\n",
104-
" costs.append(-qaoa.problem.cost(s))\n",
104+
" costs.append(qaoa.problem.energy(s))\n",
105105
" if qaoa.problem.isFeasible(s):\n",
106-
" print(s, -qaoa.problem.cost(s))"
106+
" print(s, qaoa.problem.energy(s))"
107107
]
108108
},
109109
{
@@ -216,7 +216,7 @@
216216
}
217217
],
218218
"source": [
219-
"min_cost, max_cost = qaoa.problem.computeMinMaxCosts()\n",
219+
"min_cost, max_cost = qaoa.problem.objective_bounds()\n",
220220
"min_cost, max_cost"
221221
]
222222
},

examples/ExactCover/data/exact_cover_path_problem_example.json

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
0.0,
7474
0.0
7575
],
76-
"hamming_weight": 2
76+
"hamming_weight": 2,
77+
"objective_sense": "minimize"
7778
},
7879
"qaoa_params": {
7980
"cvar": 0.7,
@@ -118,7 +119,9 @@
118119
"00000011": 19,
119120
"01000010": 503
120121
},
121-
"opt_time": 0.7518182500207331
122+
"opt_time": 0.7518182500207331,
123+
"best_energy": null,
124+
"best_objective": null
122125
},
123126
"2": {
124127
"optimal_angles": [
@@ -157,7 +160,9 @@
157160
"10000100": 224,
158161
"00000011": 150
159162
},
160-
"opt_time": 0.31045849999645725
163+
"opt_time": 0.31045849999645725,
164+
"best_energy": null,
165+
"best_objective": null
161166
},
162167
"3": {
163168
"optimal_angles": [
@@ -195,7 +200,9 @@
195200
"01010000": 102,
196201
"00000110": 439
197202
},
198-
"opt_time": 0.438680374994874
203+
"opt_time": 0.438680374994874,
204+
"best_energy": null,
205+
"best_objective": null
199206
},
200207
"4": {
201208
"optimal_angles": [
@@ -238,7 +245,9 @@
238245
"00010010": 33,
239246
"01100000": 3335
240247
},
241-
"opt_time": 0.6514912919956259
248+
"opt_time": 0.6514912919956259,
249+
"best_energy": null,
250+
"best_objective": null
242251
},
243252
"5": {
244253
"optimal_angles": [
@@ -283,7 +292,9 @@
283292
"00011000": 103,
284293
"00000110": 146
285294
},
286-
"opt_time": 0.9302357080159709
295+
"opt_time": 0.9302357080159709,
296+
"best_energy": null,
297+
"best_objective": null
287298
},
288299
"6": {
289300
"optimal_angles": [
@@ -330,7 +341,9 @@
330341
"00010010": 14,
331342
"01100000": 2922
332343
},
333-
"opt_time": 1.1260944169771392
344+
"opt_time": 1.1260944169771392,
345+
"best_energy": null,
346+
"best_objective": null
334347
},
335348
"7": {
336349
"optimal_angles": [
@@ -379,7 +392,9 @@
379392
"00001001": 143,
380393
"00000110": 132
381394
},
382-
"opt_time": 1.5074970000132453
395+
"opt_time": 1.5074970000132453,
396+
"best_energy": null,
397+
"best_objective": null
383398
},
384399
"8": {
385400
"optimal_angles": [
@@ -430,7 +445,9 @@
430445
"10000010": 56,
431446
"10000001": 17
432447
},
433-
"opt_time": 1.9193396670161746
448+
"opt_time": 1.9193396670161746,
449+
"best_energy": null,
450+
"best_objective": null
434451
},
435452
"9": {
436453
"optimal_angles": [
@@ -482,7 +499,9 @@
482499
"00000011": 29,
483500
"00000110": 180
484501
},
485-
"opt_time": 2.4477102090022527
502+
"opt_time": 2.4477102090022527,
503+
"best_energy": null,
504+
"best_objective": null
486505
},
487506
"10": {
488507
"optimal_angles": [
@@ -537,7 +556,9 @@
537556
"00100001": 5,
538557
"10010000": 4087
539558
},
540-
"opt_time": 2.9181083750154357
559+
"opt_time": 2.9181083750154357,
560+
"best_energy": null,
561+
"best_objective": null
541562
}
542563
}
543564
},
@@ -551,5 +572,6 @@
551572
"conda_env": "neqst-kongsberg-2025",
552573
"qaoa_repo_dir": "/Users/havahol/playground/quantum/QAOA/qaoa/utils",
553574
"qaoa_git_commit": "ff2d04cf2aa5069dcb58ba57ab7d2fc79c465513"
554-
}
575+
},
576+
"schema_version": 3
555577
}

examples/MaxCut/CVaR.ipynb

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"id": "3d17ca29",
3636
"metadata": {},
3737
"source": [
38-
"Create Barabási–Albert graph instance, more examples can be found here https://github.com/OpenQuantumComputing/data."
38+
"Create Barab\u00e1si\u2013Albert graph instance, more examples can be found here https://github.com/OpenQuantumComputing/data."
3939
]
4040
},
4141
{
@@ -55,11 +55,7 @@
5555
"output_type": "display_data"
5656
}
5757
],
58-
"source": [
59-
"G = nx.read_gml(\"data/w_ba_n21_k4_0.gml\") # Load graph data for the 21-node, 4-regular graph (GML format) in examples/MaxCUt/data\n",
60-
"nx.draw_networkx(G)\n",
61-
"mincost = -25.23404480588015 # Precalculated minimum cost (for comparison)"
62-
]
58+
"source": "G = nx.read_gml(\"data/w_ba_n21_k4_0.gml\") # Load graph data for the 21-node, 4-regular graph (GML format) in examples/MaxCUt/data\nnx.draw_networkx(G)\nmincost = 0 # worst feasible objective (no edges cut)\nmaxcost = 25.23404480588015 # precalculated optimal MaxCut value"
6359
},
6460
{
6561
"cell_type": "markdown",
@@ -222,29 +218,7 @@
222218
"output_type": "display_data"
223219
}
224220
],
225-
"source": [
226-
"import matplotlib.pyplot as plt\n",
227-
"\n",
228-
"fig = plt.figure()\n",
229-
"utils.plot_ApproximationRatio(\n",
230-
" qaoa,\n",
231-
" maxdepth,\n",
232-
" mincost=mincost,\n",
233-
" maxcost=0,\n",
234-
" label=\"QAOA vanilla\",\n",
235-
" style=\"o--b\",\n",
236-
" fig=fig,\n",
237-
")\n",
238-
"utils.plot_ApproximationRatio(\n",
239-
" qaoa_CVaR,\n",
240-
" maxdepth,\n",
241-
" mincost=mincost,\n",
242-
" maxcost=0,\n",
243-
" label=\"QAOA CVaR\",\n",
244-
" style=\"x--k\",\n",
245-
" fig=fig,\n",
246-
")"
247-
]
221+
"source": "import matplotlib.pyplot as plt\n\nfig = plt.figure()\nutils.plot_ApproximationRatio(\n qaoa,\n maxdepth,\n mincost=mincost,\n maxcost=maxcost,\n label=\"QAOA vanilla\",\n style=\"o--b\",\n fig=fig,\n)\nutils.plot_ApproximationRatio(\n qaoa_CVaR,\n maxdepth,\n mincost=mincost,\n maxcost=maxcost,\n label=\"QAOA CVaR\",\n style=\"x--k\",\n fig=fig,\n)"
248222
},
249223
{
250224
"cell_type": "markdown",
@@ -301,4 +275,4 @@
301275
},
302276
"nbformat": 4,
303277
"nbformat_minor": 5
304-
}
278+
}

examples/MaxCut/ComparisonOptimizers.ipynb

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"id": "0e9efef7",
3838
"metadata": {},
3939
"source": [
40-
"Create Barabási–Albert graph instance, more examples can be found here https://github.com/OpenQuantumComputing/data"
40+
"Create Barab\u00e1si\u2013Albert graph instance, more examples can be found here https://github.com/OpenQuantumComputing/data"
4141
]
4242
},
4343
{
@@ -57,11 +57,7 @@
5757
"output_type": "display_data"
5858
}
5959
],
60-
"source": [
61-
"G = nx.read_gml(\"data/w_ba_n10_k4_0.gml\") # Load graph data for the 10-node, 4-regular graph (GML format) in examples/MaxCut/data\n",
62-
"nx.draw_networkx(G)\n",
63-
"mincost = -8.657714089848158 # Precalculated"
64-
]
60+
"source": "G = nx.read_gml(\"data/w_ba_n10_k4_0.gml\") # Load graph data for the 10-node, 4-regular graph (GML format) in examples/MaxCut/data\nnx.draw_networkx(G)\nmincost = 0 # worst feasible objective (no edges cut)\nmaxcost = 8.657714089848158 # precalculated optimal MaxCut value"
6561
},
6662
{
6763
"cell_type": "markdown",
@@ -257,21 +253,7 @@
257253
"output_type": "display_data"
258254
}
259255
],
260-
"source": [
261-
"import matplotlib.pyplot as plt\n",
262-
"\n",
263-
"fig = plt.figure()\n",
264-
"for key in optimizers:\n",
265-
" utils.plot_ApproximationRatio(\n",
266-
" qaoa[key],\n",
267-
" maxdepth,\n",
268-
" mincost=mincost,\n",
269-
" maxcost=0,\n",
270-
" label=key,\n",
271-
" style=plotstyle[key] + \"-\",\n",
272-
" fig=fig,\n",
273-
" )"
274-
]
256+
"source": "import matplotlib.pyplot as plt\n\nfig = plt.figure()\nfor key in optimizers:\n utils.plot_ApproximationRatio(\n qaoa[key],\n maxdepth,\n mincost=mincost,\n maxcost=maxcost,\n label=key,\n style=plotstyle[key] + \"-\",\n fig=fig,\n )"
275257
},
276258
{
277259
"cell_type": "markdown",
@@ -441,4 +423,4 @@
441423
},
442424
"nbformat": 4,
443425
"nbformat_minor": 5
444-
}
426+
}

0 commit comments

Comments
 (0)