Skip to content

Commit 49c0471

Browse files
authored
Sanne initial branch (#31)
* documentation for the files dicke_initialstate, dicke1_2_initialstate, and lessthank_initialstate * added link to where the algorithm for different cases of k is found, and added start of documentation for maxkcut_feasible_initialstate * futher description of Dicke1_2 class, and first part of documentation for the maxkcut_feasible_initialstate file * updates on maxkcut_feasible_initialstates * finished documenting maxkcut_feasible_initialstate * documentation for the plus_initialstate file * documentaition for statevector_initialstate * documentation for tensor_initialstate * changing all files so that it is in the same format as Dina's (adding line with name, parent class, raises, and defaults * updated formatting for most files in the folder problems so that it fits the format for documentation. * formatting done for problems folder, still need to write documentation for it * added full documentation for exactcover_problem, and started on graph_problem. * done documentation for graph_problem inside problems folder * updated documentation for the maxkcut_binary_fullH file in the problems folder. * documentation for the maxkcut_binary_powertwo file * documentation for the maxkcut_one_hot_problem file * documentation for the portfolio_problem file * documentation for the qubo_problem file * documentation on the README, added new cases for initial state, mixers and problems * documentation on the README, added comments for correction of intro + formatting equations, created subparagraphs for create_graphs_and_draw_circuits and tensorize_mixers * finished documentation on maxkcut_binary_poweroftow (noticed some were missing) * deleted create_graphs_and_draw_circuits as it is already a part of qiskit * valid combinations of initial states, mixers, and problems * added combinations for Max k-CUT binary power of two * took away the valid combinations section * added documentation for maxkcut_binary_fullH that i had missed previously * indication for standard MaxCut * removed the tensor function from initial states list due to it only being a helper function * added that k means hamming weight in dicke1_2_initialstate * deleted documentation on private methods for the lessthank_initialstate file * deleted comment #subN_qubits in tensor class * added a little bit to the example * fixed the example so that it doesn't use the outdated function MaxCut but rather MaxKCutBinaryPowerOfTwo * restore WithFlip.ipynb to upstream version * Remove VSCode settings.json from repo to replace with YAML config for Black
1 parent ba9892b commit 49c0471

15 files changed

+615
-11
lines changed

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# QAOA
22

3-
This package is a flexible python implementation of the [Quantum Approximate Optimization Algorithm](https://arxiv.org/pdf/1411.4028.pdf) /[Quantum Alternating Operator ansatz](https://arxiv.org/pdf/1709.03489.pdf) (QAOA) **aimed at researchers** to readily test the performance of a new ansatz, a new classical optimizers, etc. By default it uses qiskit as a backend.
3+
This package is a flexible python implementation of the [Quantum Approximate Optimization Algorithm](https://arxiv.org/pdf/1411.4028.pdf) /[Quantum Alternating Operator ansatz](https://arxiv.org/pdf/1709.03489.pdf) (QAOA) **aimed at researchers** to readily test the performance of a new ansatz, a new classical optimizer, etc. By default it uses qiskit as a backend.
44

55
Install with `pip install qaoa` or `pip install -e .`.
66

@@ -13,7 +13,7 @@ one defines a **problem Hamiltonian** $H_P$ through the action on computational
1313
$$ H_P |x\rangle = c(x) |x\rangle,$$
1414

1515
which means that ground states minimize the cost function $c$.
16-
Given a parametrized ansatz $ | \gamma, \beta \rangle$, a classical optimizer is used to minimize the energy
16+
Given a parametrized ansatz $| \gamma, \beta \rangle$, a classical optimizer is used to minimize the energy
1717

1818
$$ \langle \gamma, \beta | H_P | \gamma, \beta \rangle.$$
1919

@@ -38,29 +38,38 @@ In order to create a custom QAOA ansatz, one needs to specify a [problem](qaoa/p
3838
This library already contains several standard implementations.
3939

4040
- The following [problem](qaoa/problems/base_problem.py) cases are already available:
41-
- [MaxCut](qaoa/problems/maxcut_problem.py)
41+
- [Max k-CUT binary power of two](qaoa/problems/maxkcut_binary_powertwo.py) *
42+
- [Max k-CUT binary full H](qaoa/problems/maxkcut_binary_fullH.py)
43+
- [Max k-CUT binary one hot](qaoa/problems/maxkcut_binary_one_hot.py)
4244
- [QUBO](qaoa/problems/qubo_problem.py)
4345
- [Exact cover](qaoa/problems/exactcover_problem.py)
4446
- [Portfolio](qaoa/problems/portfolio_problem.py)
47+
- [Graph](qaoa/problems/graph_problem.py)
4548
- The following [mixer](qaoa/mixers/base_mixer.py) cases are already available:
4649
- [X-mixer](qaoa/mixers/x_mixer.py)
4750
- [XY-mixer](qaoa/mixers/xy_mixer.py)
4851
- [Grover-mixer](qaoa/mixers/grover_mixer.py)
52+
- [Max k-CUT grover](qaoa/mixers/maxkcut_grover_mixer.py)
53+
- [Max k-CUT LX](qaoa/mixers/maxkcut_lx_mixer.py)
4954
- The following [initial state](qaoa/initialstates/base_initialstate.py) cases are already available:
5055
- [Plus](qaoa/initialstates/plus_initialstate.py)
5156
- [Statevector](qaoa/initialstates/statevector_initialstate.py)
5257
- [Dicke](qaoa/initialstates/dicke_initialstate.py)
58+
- [Dicke 1- and 2-states superposition](qaoa/initialstates/dicke1_2_initialstate.py)
59+
- [Less than k](qaoa/initialstates/lessthank_initialstate.py)
60+
- [Max k-CUT feasible](qaoa/initialstates/maxkcut_feasible_initialstate.py)
5361

5462
It is **very easy to extend this list** by providing an implementation of a circuit/cost of the base classes mentioned above. Feel free to fork the repo and create a pull request :-)
5563

5664
To make an ansatz for the MaxCut problem, the X-mixer and the initial state $|+\rangle^{\otimes n}$ one can create an instance like this:
5765

5866
qaoa = QAOA(
5967
initialstate=initialstates.Plus(),
60-
problem=problems.MaxCut(G="some networkx instance"),
68+
problem=problems.MaxKCutBinaryPowerOfTwo(G="some networkx instance", k_cuts=2),
6169
mixer=mixers.X()
6270
)
6371

72+
*(can be used for the standard MaxCut with argument k_cuts=2)
6473
***
6574
### Run optimization at depth $p$
6675

@@ -117,6 +126,16 @@ Additionally, for each depth every time the loss function is called, the **angle
117126
qaoa.optimization_results[i]
118127

119128

129+
***
130+
### Tensorize mixers
131+
To tensorize a mixer, i.e. decomposing the mixer into a tensor product of unitaries that is
132+
performed on each qubit, one can call the tensor class with the arguments of mixer and number of qubits in subpart.
133+
134+
For example, for the standard MaxCut problem above where the X mixer was used, one could find the tensor by writing:
135+
136+
tensorized_mixer = Tensor(mixer.X(), number_of_qubits_of_subpart)
137+
<!--find out the number of qubits we want here -->
138+
120139
***
121140
### Example usage
122141

qaoa/initialstates/dicke1_2_initialstate.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,22 @@
99

1010
class Dicke1_2(InitialState):
1111
"""
12-
Returs equal superposition Dicke 1 and Dicke 2 states. Hard Coded
12+
Dicke1_2 initial state.
13+
14+
Subclass of the `InitialState` class, and it returns equal superposition Dicke 1 and Dicke 2 states. It is Hard Coded for the case of Hamming weight k = 6
15+
16+
Methods:
17+
create_circuit(): Creates a circuit that is a superposition of Dicke 1 and Dicke 2 states
1318
"""
1419

1520
def __init__(self) -> None:
1621
self.k = 6
1722
self.N_qubits = 3
1823

1924
def create_circuit(self) -> None:
25+
"""
26+
Circuit to prepare a superposition of Dicke 1 and Dicke 2 states
27+
"""
2028
q = QuantumRegister(self.N_qubits)
2129
circuit = QuantumCircuit(q)
2230
X = SparsePauliOp("X")

qaoa/initialstates/dicke_initialstate.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88

99

1010
class Dicke(InitialState):
11+
"""
12+
Dicke initial state.
13+
14+
Subclass of the `InitialState` class, and it creates a circuit that creates a Dicke state with Hamming weight k
15+
16+
Attributes:
17+
k (int): The Hamming weight of the Dicke states.
18+
19+
Methods:
20+
create_circuit(): Creates the circuit to prepare the Dicke states
21+
"""
1122
def __init__(self, k) -> None:
1223
"""
1324
Args:

qaoa/initialstates/lessthank_initialstate.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,38 @@
66

77

88
class LessThanK(InitialState):
9+
"""
10+
LessThanK initial state.
11+
12+
Subclass for the `InitialState` class, and it creates a quantum circuit that creates the initial state for the for the MAX k-CUT problem for
13+
different cases of k subsets.
14+
15+
Attributes:
16+
k (int): subsets (or "colors") of the vertices in the MAX k-CUT problem is seperated into
17+
18+
Methods:
19+
create_circuit(): creates a circuit that can create the wanted initial state for the special k
20+
"""
921
def __init__(self, k: int) -> None:
22+
"""
23+
Checks that the k-value is valid and initizalizes N qubits and k cuts
24+
25+
Args:
26+
k (int): the number of subsets of the vertices in the MAX k-CUT problem
27+
28+
Raises:
29+
ValueError: if k is neither a power of 2 or between 2 and 8
30+
"""
1031
if not LessThanK.is_power_of_two_or_between_2_and_8(k):
1132
raise ValueError("k must be a power of two or between 2 and 8")
1233
self.k = k
1334
self.N_qubits = int(np.ceil(np.log2(self.k)))
1435

1536
def create_circuit(self) -> None:
37+
"""
38+
Creates a circuit by calling on the methods for different k, following the algorithm from https://arxiv.org/abs/2411.08594.
39+
k is between 2 and 8 or a power of 2.
40+
"""
1641
if self.k == 3:
1742
self.circuit = self.k3()
1843
elif self.k == 5:
@@ -25,6 +50,12 @@ def create_circuit(self) -> None:
2550
self.circuit = self.power_of_two()
2651

2752
def is_power_of_two_or_between_2_and_8(k):
53+
"""
54+
Checks the validity of the argument k, so that k is either between 2 and 8 or a power of 2
55+
56+
Returns:
57+
True if k is a power of 2 or between 2 and 8, and False otherwise
58+
"""
2859
# Check if k is between 2 and 8
2960
if 2 <= k <= 8:
3061
return True
@@ -37,12 +68,24 @@ def is_power_of_two_or_between_2_and_8(k):
3768
return False
3869

3970
def power_of_two(self) -> QuantumCircuit:
71+
"""
72+
Creates a circuit for the case k = a power of 2
73+
74+
Returns:
75+
QuantumCircuit: circuit that creates the initial state for k = a power of 2
76+
"""
4077
q = QuantumRegister(self.N_qubits)
4178
circuit = QuantumCircuit(q)
4279
circuit.h(q)
4380
return circuit
4481

4582
def k3(self) -> QuantumCircuit:
83+
"""
84+
Creates a circuit for the case k = 3
85+
86+
Returns:
87+
QuantumCircuit: circuit that creates the initial state for k = 3
88+
"""
4689
q = QuantumRegister(self.N_qubits)
4790
circuit = QuantumCircuit(q)
4891
theta = np.arccos(1 / np.sqrt(3)) * 2
@@ -54,6 +97,12 @@ def k3(self) -> QuantumCircuit:
5497
return circuit
5598

5699
def k5(self) -> QuantumCircuit:
100+
"""
101+
Creates a circuit for the case k = 5
102+
103+
Returns:
104+
QuantumCircuit: circuit that creates the initial state for k = 5
105+
"""
57106
q = QuantumRegister(self.N_qubits)
58107
circuit = QuantumCircuit(q)
59108
theta = np.arcsin(1 / np.sqrt(5)) * 2
@@ -62,6 +111,12 @@ def k5(self) -> QuantumCircuit:
62111
return circuit
63112

64113
def k6(self) -> QuantumCircuit:
114+
"""
115+
Creates a circuit for the case k = 6
116+
117+
Returns:
118+
QuantumCircuit: circuit that creates the initial state for k = 6
119+
"""
65120
q = QuantumRegister(self.N_qubits)
66121
circuit = QuantumCircuit(q)
67122
theta = np.pi / 2
@@ -75,6 +130,12 @@ def k6(self) -> QuantumCircuit:
75130
return circuit
76131

77132
def k7(self) -> QuantumCircuit:
133+
"""
134+
Creates a circuit for the case k = 7
135+
136+
Returns:
137+
QuantumCircuit: circuit that creates the initial state for k = 7
138+
"""
78139
q = QuantumRegister(self.N_qubits)
79140
circuit = QuantumCircuit(q)
80141
delta = np.arcsin(1 / np.sqrt(7)) * 2

qaoa/initialstates/maxkcut_feasible_initialstate.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,36 @@
99

1010

1111
class MaxKCutFeasible(InitialState):
12+
"""
13+
MaxKCutFeasible initial state.
14+
15+
Subclass of the `InitialState` class, and it determines the feasible states for the type of MAX k-CUT problem that is specified by the arguments. This specifies the number of cuts, number of qubits per vertex,
16+
and method for solving the special case of k = 6.
17+
18+
Attributes:
19+
k_cuts (int): The number of cuts (or "colors") of the vertices in the MAX k-CUT problem is separated into.
20+
problem_encoding (str): description of the type of problem, either "onehot" (which corresponds to ...) or "binary" (which corresponds to ...)
21+
color_encoding (str): determines the approach to solving the MAX k-cut problem by following one of three methods,
22+
either "Dicke1_2" (which corresponds to creating an initial state that is a superposition of the valid states that represents a color(only 6/8 possible states)),
23+
"LessThanK" (which corresponds to grouping states together and make the group represent one color),
24+
or "max_balanced" (which corresponds to the onehot case where a color corresponds to a state)
25+
26+
Methods:
27+
create_circuit(): creates a circuit that creates an initial state for only feasible initial states of the MAX k-CUT problem given constraints
28+
"""
1229
def __init__(
1330
self, k_cuts: int, problem_encoding: str, color_encoding: str = "LessThanK"
1431
) -> None:
32+
"""
33+
Args:
34+
k_cuts (int):
35+
problem_encoding (str): description of the type of problem, either "onehot" (which corresponds to ...) or "binary" (which corresponds to ...)
36+
color_encoding (str): determines the approach to solving the MAX k-cut problem by following one of three methods,
37+
either "Dicke1_2" (which corresponds to creating an initial state that is a superposition of the valid states that represents a color(only 6/8 possible states)),
38+
"LessThanK" (which corresponds to grouping states together and make the group represent one color),
39+
or "max_balanced" (which corresponds to the onehot case where a color corresponds to a state). Defaults to "LessThanK".
40+
41+
"""
1542
self.k_cuts = k_cuts
1643
self.problem_encoding = problem_encoding
1744
self.color_encoding = color_encoding
@@ -39,6 +66,10 @@ def __init__(
3966
self.infeasible = ["111"]
4067

4168
def create_circuit(self) -> None:
69+
"""
70+
Creates a circuit that creates the initial state (for only feasible states) for the MAX k-CUT problem given
71+
the methods and cuts given as arguments
72+
"""
4273
if self.problem_encoding == "binary":
4374
self.k_bits = int(np.ceil(np.log2(self.k_cuts)))
4475
self.num_V = self.N_qubits / self.k_bits

qaoa/initialstates/plus_initialstate.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,21 @@
55

66

77
class Plus(InitialState):
8+
"""
9+
Plus initial state.
10+
11+
Subclass of `InitialState` class, and it creates an initial plus state
12+
13+
Methods:
14+
create_circuit(): Creates a circuit that sets up plus states for the initial states
15+
"""
816
def __init__(self) -> None:
917
super().__init__()
1018

1119
def create_circuit(self):
20+
"""
21+
Creates a circuit of Hadamard-gates, which creates an initial state that is a plus state
22+
"""
1223
q = QuantumRegister(self.N_qubits)
1324
self.circuit = QuantumCircuit(q)
1425
self.circuit.h(q)

qaoa/initialstates/statevector_initialstate.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,29 @@
55

66

77
class StateVector(InitialState):
8+
"""
9+
State vector initial state.
10+
11+
Subclass of the `InitialState` class, and it creates the initial statevector.
12+
13+
Attributes:
14+
statevector (list): The statevector to initialize the circuit with.
15+
16+
Methods:
17+
create_circuit(): Creates a circuit that creates the initial statevector.
18+
"""
819
def __init__(self, statevector) -> None:
20+
"""
21+
Args:
22+
statevector (list): The statevector to initialize the circuit with.
23+
"""
924
super().__init__()
1025
self.statevector = statevector
1126

1227
def create_circuit(self):
28+
"""
29+
Creates a circuit that makes the initial statevector
30+
"""
1331
q = QuantumRegister(self.N_qubits)
1432
self.circuit = QuantumCircuit(q)
1533
self.circuit.initialize(self.statevector, q)

qaoa/initialstates/tensor_initialstate.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,32 @@
77

88

99
class Tensor(InitialState):
10+
"""
11+
Tensor initial state.
12+
13+
Subclass of the `IntialState` class that creates a tensor out of a circuit
14+
15+
Attributions:
16+
subcircuit (InitialState): the circuit that is to be tensorised
17+
num (int): number of qubits of the subpart
18+
19+
Methods:
20+
create_circuit():
21+
"""
1022
def __init__(self, subcircuit: InitialState, num: int) -> None:
1123
"""
1224
Args:
1325
subcircuit (InitialState): the circuit that is to be tensorised
14-
#subN_qubits (int): number of qubits of the subpart
26+
num (int): number of qubits of the subpart #subN_qubits
1527
"""
1628
self.num = num
1729
self.subcircuit = subcircuit
1830
self.N_qubits = self.num * self.subcircuit.N_qubits
1931

2032
def create_circuit(self) -> None:
33+
"""
34+
Creates a circuit that tensorises a given subcircuit
35+
"""
2136
self.subcircuit.create_circuit()
2237
self.circuit = self.subcircuit.circuit
2338
for v in range(self.num - 1):

0 commit comments

Comments
 (0)