eqc-models 0.10.1__py3-none-any.whl → 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/__init__.py +2 -1
- eqc_models-0.11.0.data/platlib/eqc_models/assignment/setpartition.py +4 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/__init__.py +3 -1
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/base.py +44 -4
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/polyeval.c +163 -163
- eqc_models-0.11.0.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/polynomial.py +7 -3
- eqc_models-0.11.0.data/platlib/eqc_models/base/results.py +94 -0
- eqc_models-0.11.0.data/platlib/eqc_models/combinatorics/__init__.py +6 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/combinatorics/setcover.py +34 -17
- {eqc_models-0.10.1.data/platlib/eqc_models/assignment → eqc_models-0.11.0.data/platlib/eqc_models/combinatorics}/setpartition.py +2 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/maxkcut.py +26 -68
- eqc_models-0.11.0.data/platlib/eqc_models/sequence/scheduling.py +29 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/solvers/qciclient.py +11 -2
- {eqc_models-0.10.1.dist-info → eqc_models-0.11.0.dist-info}/METADATA +3 -2
- eqc_models-0.11.0.dist-info/RECORD +61 -0
- {eqc_models-0.10.1.dist-info → eqc_models-0.11.0.dist-info}/WHEEL +1 -1
- eqc_models-0.10.1.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- eqc_models-0.10.1.data/platlib/eqc_models/base.py +0 -115
- eqc_models-0.10.1.data/platlib/eqc_models/combinatorics/__init__.py +0 -5
- eqc_models-0.10.1.data/platlib/eqc_models/communitydetection.py +0 -25
- eqc_models-0.10.1.data/platlib/eqc_models/eqcdirectsolver.py +0 -61
- eqc_models-0.10.1.data/platlib/eqc_models/graphs.py +0 -28
- eqc_models-0.10.1.data/platlib/eqc_models/maxcut.py +0 -113
- eqc_models-0.10.1.data/platlib/eqc_models/maxkcut.py +0 -185
- eqc_models-0.10.1.data/platlib/eqc_models/quadraticmodel.py +0 -131
- eqc_models-0.10.1.data/platlib/eqc_models/solvers/eqcdirect.py +0 -160
- eqc_models-0.10.1.dist-info/RECORD +0 -66
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/compile_extensions.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/algorithms/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/algorithms/base.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/algorithms/penaltymultiplier.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/allocation.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/portbase.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/portmomentum.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/assignment/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/assignment/qap.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/constraints.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/operators.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/polyeval.pyx +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/quadratic.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/decoding.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/base.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/hypergraph.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/maxcut.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/partition.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/classifierbase.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/classifierqboost.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/classifierqsvm.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/clustering.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/clusteringbase.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian.pyx +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.c +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.h +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/decomposition.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/forecast.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/forecastbase.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/regressor.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/regressorbase.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/reservoir.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/sequence/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/sequence/tsp.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/solvers/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/__init__.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/fileio.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/polynomial.py +0 -0
- {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/qplib.py +0 -0
- {eqc_models-0.10.1.dist-info → eqc_models-0.11.0.dist-info/licenses}/LICENSE.txt +0 -0
- {eqc_models-0.10.1.dist-info → eqc_models-0.11.0.dist-info}/top_level.txt +0 -0
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import logging
|
|
3
|
-
from typing import (Dict, List, Tuple)
|
|
4
|
-
import numpy as np
|
|
5
|
-
from eqc_direct.client import EqcClient
|
|
6
|
-
|
|
7
|
-
log = logging.getLogger(name=__name__)
|
|
8
|
-
|
|
9
|
-
# base class
|
|
10
|
-
class EqcModel:
|
|
11
|
-
""" EqcModel subclasses must provide these properties/methods.
|
|
12
|
-
|
|
13
|
-
:decode: takes a raw solution and translates it into the original problem
|
|
14
|
-
formulation
|
|
15
|
-
:H: property which returns a Hamiltonian operator
|
|
16
|
-
:levels: property to set the number of levels in each qudit
|
|
17
|
-
:qudit_limits: maximjm value permitted for each qudit """
|
|
18
|
-
|
|
19
|
-
_levels = 100
|
|
20
|
-
_domains = None
|
|
21
|
-
_H = None
|
|
22
|
-
_machine_slacks = 0
|
|
23
|
-
|
|
24
|
-
def decode(self, solution : np.ndarray) -> np.ndarray:
|
|
25
|
-
""" Interpret the solution given the norm value and domains """
|
|
26
|
-
|
|
27
|
-
# ignore any slacks that may have been added during encoding
|
|
28
|
-
solution = solution[:self.n]
|
|
29
|
-
if self._domains is not None:
|
|
30
|
-
multipliers = self.domains / self.sum_constraint
|
|
31
|
-
else:
|
|
32
|
-
multipliers = self.sum_constraint / np.sum(solution)
|
|
33
|
-
|
|
34
|
-
return multipliers * solution
|
|
35
|
-
|
|
36
|
-
def encode(self, norm_value:float=1000) -> np.ndarray:
|
|
37
|
-
""" Encode Hamiltonian into the domain of the device """
|
|
38
|
-
|
|
39
|
-
raise NotImplementedError()
|
|
40
|
-
|
|
41
|
-
def encode_sum_constraint(self, levels):
|
|
42
|
-
new_sc = self.n * (levels-1) * self.sum_constraint / (np.sum(self.domains))
|
|
43
|
-
return new_sc
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def domains(self) -> np.array:
|
|
47
|
-
return self._domains
|
|
48
|
-
|
|
49
|
-
@domains.setter
|
|
50
|
-
def domains(self, value):
|
|
51
|
-
self._domains = value
|
|
52
|
-
|
|
53
|
-
@property
|
|
54
|
-
def n(self) -> int:
|
|
55
|
-
return int(max(self.domains.shape))
|
|
56
|
-
|
|
57
|
-
def processH(self, H : np.ndarray) -> np.ndarray:
|
|
58
|
-
""" By default, do nothing to H """
|
|
59
|
-
|
|
60
|
-
return H
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def H(self) -> Dict[str, np.ndarray]:
|
|
64
|
-
""" Matrix of a quadratic operator with the first column containing
|
|
65
|
-
the linear terms and the remaining columns containing a symmetric
|
|
66
|
-
quadratic matrix"""
|
|
67
|
-
return self._H
|
|
68
|
-
|
|
69
|
-
@H.setter
|
|
70
|
-
def H(self, value : Dict[str, np.ndarray]):
|
|
71
|
-
""" The H setter ensures that matrices order 2 and above are symmetric """
|
|
72
|
-
|
|
73
|
-
H = self.processH(value)
|
|
74
|
-
self._H = H
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
def sparse(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
78
|
-
H = self.H
|
|
79
|
-
coeff = []
|
|
80
|
-
idx = []
|
|
81
|
-
poly_orders = {"C": 1, "J": 2, "T": 3, "Q": 4, "P": 5}
|
|
82
|
-
key_len = max([poly_orders[k] for k, v in H.items() if v is not None])
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def machine_slacks(self):
|
|
87
|
-
""" Number of slack qudits to add to the model """
|
|
88
|
-
return self._machine_slacks
|
|
89
|
-
|
|
90
|
-
@machine_slacks.setter
|
|
91
|
-
def machine_slacks(self, value:int):
|
|
92
|
-
assert int(value) == value, "value not integer"
|
|
93
|
-
self._machine_slacks = value
|
|
94
|
-
|
|
95
|
-
class ModelSolver:
|
|
96
|
-
""" Provide a common interface for solver implementations.
|
|
97
|
-
Store a model, implement a solve method."""
|
|
98
|
-
|
|
99
|
-
def __init__(self, model : EqcModel, levels : int = 200):
|
|
100
|
-
self.model = model
|
|
101
|
-
self._levels = levels
|
|
102
|
-
|
|
103
|
-
def solve(self, *args, **kwargs) -> Dict:
|
|
104
|
-
raise NotImplementedError()
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def levels(self) -> int:
|
|
108
|
-
""" This integer value indicates the number of distinct
|
|
109
|
-
states each qudit can represent. These levels are separated
|
|
110
|
-
by some constant value with the first level taking the value 0. """
|
|
111
|
-
return self._levels
|
|
112
|
-
|
|
113
|
-
@levels.setter
|
|
114
|
-
def levels(self, value : int):
|
|
115
|
-
self._levels = value
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# (C) Quantum Computing Inc., 2024.
|
|
2
|
-
import numpy as np
|
|
3
|
-
import networkx as nx
|
|
4
|
-
from .graphs import GraphModel
|
|
5
|
-
|
|
6
|
-
class CommunityDetectionModel(GraphModel):
|
|
7
|
-
"""
|
|
8
|
-
This model is the generic n-community model, which requires enforcing
|
|
9
|
-
membership to a single community
|
|
10
|
-
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(self, G : nx.Graph, num_communities : int):
|
|
14
|
-
super(CommunityDetectionModel, self).__init__(G)
|
|
15
|
-
self.cnum_communities = num_communities
|
|
16
|
-
|
|
17
|
-
def build(self):
|
|
18
|
-
# num_nodes = len(self.G.nodes)
|
|
19
|
-
# num_variables = num_nodes * self.num_communities
|
|
20
|
-
# lhs = np.zeros((num_nodes, num_variables), dtype=np.int32)
|
|
21
|
-
# rhs = np.ones((num_nodes, 1), dtype=np.int32)
|
|
22
|
-
# for i in range(num_nodes):
|
|
23
|
-
# lhs[i, 3*i:3*(i+1)] = 1
|
|
24
|
-
# self.constraints = lhs, rhs
|
|
25
|
-
raise NotImplementedError("Community Detection is not implemented yet")
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
from typing import Dict
|
|
2
|
-
import logging
|
|
3
|
-
import numpy as np
|
|
4
|
-
from eqc_direct.client import EqcClient
|
|
5
|
-
from .base import ModelSolver
|
|
6
|
-
|
|
7
|
-
log = logging.getLogger(name=__name__)
|
|
8
|
-
|
|
9
|
-
class EqcDirectMixin:
|
|
10
|
-
|
|
11
|
-
ip_addr = None
|
|
12
|
-
port = None
|
|
13
|
-
|
|
14
|
-
def connect(self, ip_addr : str, port : str):
|
|
15
|
-
""" Explicitly set device address, if environment is configured with the connection, this call is not required """
|
|
16
|
-
self.ip_addr = ip_addr
|
|
17
|
-
self.port = port
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def client(self):
|
|
21
|
-
|
|
22
|
-
params = {}
|
|
23
|
-
if self.ip_addr is not None:
|
|
24
|
-
params["ip_address"] = self.ip_addr
|
|
25
|
-
if self.port is not None:
|
|
26
|
-
params["port"] = self.port
|
|
27
|
-
return EqcClient(**params)
|
|
28
|
-
|
|
29
|
-
class EqcDirectSolver(ModelSolver, EqcDirectMixin):
|
|
30
|
-
|
|
31
|
-
def solve(self, relaxation_schedule:int=2, precision : float = 1.0) -> Dict:
|
|
32
|
-
model = self.model
|
|
33
|
-
poly_coefficients, poly_indices = model.sparse
|
|
34
|
-
scval = model.encode_sum_constraint(self.levels)
|
|
35
|
-
|
|
36
|
-
client = self.client
|
|
37
|
-
lock_id, start_ts, end_ts = client.wait_for_lock()
|
|
38
|
-
log.debug("Got device lock id %s. Wait time %f", lock_id, end_ts - start_ts)
|
|
39
|
-
resp = None
|
|
40
|
-
try:
|
|
41
|
-
log.debug("Calling device with parameters relaxation_schedule %d sum_constraint %s lock_id %s solution_precision %f",
|
|
42
|
-
relaxation_schedule, scval, lock_id, precision)
|
|
43
|
-
resp = client.process_job(poly_coefficients=poly_coefficients,
|
|
44
|
-
poly_indices=poly_indices,
|
|
45
|
-
relaxation_schedule=relaxation_schedule,
|
|
46
|
-
sum_constraint = scval,
|
|
47
|
-
lock_id = lock_id,
|
|
48
|
-
solution_precision=precision)
|
|
49
|
-
log.debug("Received response with status %s", resp["err_desc"])
|
|
50
|
-
log.debug("Runtime %f resulting in energy %f", resp["runtime"], resp["energy"])
|
|
51
|
-
log.debug("Distillation runtime %s resulting in energy %f", resp["distilled_runtime"], resp["distilled_energy"])
|
|
52
|
-
finally:
|
|
53
|
-
client.release_lock(lock_id=lock_id)
|
|
54
|
-
if resp is not None:
|
|
55
|
-
solution = resp["solution"]
|
|
56
|
-
energy = resp["energy"]
|
|
57
|
-
runtime = resp["runtime"]
|
|
58
|
-
dirac3_sol = np.array(solution)
|
|
59
|
-
else:
|
|
60
|
-
raise RuntimeError("FAILED TO GET RESPONSE")
|
|
61
|
-
return resp
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
import networkx as nx
|
|
3
|
-
from .quadraticmodel import QuadraticModel
|
|
4
|
-
|
|
5
|
-
class GraphModel(QuadraticModel):
|
|
6
|
-
|
|
7
|
-
def __init__(self, G : nx.Graph):
|
|
8
|
-
self.G = G
|
|
9
|
-
|
|
10
|
-
class TwoPartitionModel(GraphModel):
|
|
11
|
-
""" Create a model where the variables are node-based """
|
|
12
|
-
|
|
13
|
-
@property
|
|
14
|
-
def variables(self) -> List[str]:
|
|
15
|
-
""" Provide a variable name to index lookup, order enforced by sorting the list before returning """
|
|
16
|
-
names = [node.name for node in self.G.nodes]
|
|
17
|
-
names.sort()
|
|
18
|
-
return names
|
|
19
|
-
|
|
20
|
-
class EdgeModel(GraphModel):
|
|
21
|
-
""" Create a model where the variables are edge-based """
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def variables(self) -> List[str]:
|
|
25
|
-
""" Provide a variable name to index lookup, order enforced by sorting the list before returning """
|
|
26
|
-
names = [f"({u},{v})" for u, v in self.G.edges]
|
|
27
|
-
names.sort()
|
|
28
|
-
return names
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import networkx as nx
|
|
2
|
-
import numpy as np
|
|
3
|
-
from .graphs import TwoPartitionModel
|
|
4
|
-
|
|
5
|
-
class MaxCutModel(TwoPartitionModel):
|
|
6
|
-
|
|
7
|
-
def build(self):
|
|
8
|
-
variables = self.variables
|
|
9
|
-
n = len(variables)
|
|
10
|
-
self.domains = np.ones((n,))
|
|
11
|
-
|
|
12
|
-
J = np.zeros((n+1, n+1), dtype=np.float32)
|
|
13
|
-
h = np.zeros((n+1,1), dtype=np.float32)
|
|
14
|
-
for u, v in G.edges:
|
|
15
|
-
J[u, v] += 1
|
|
16
|
-
J[v, u] += 1
|
|
17
|
-
J[u, u] = 1
|
|
18
|
-
J[v, v] = 1
|
|
19
|
-
h[u] -= 1
|
|
20
|
-
h[v] -= 1
|
|
21
|
-
J *= 1/t**2
|
|
22
|
-
h *= 1/t
|
|
23
|
-
H = np.hstack([h, J])
|
|
24
|
-
return H
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def J(self) -> np.ndarray:
|
|
28
|
-
if getattr(self, "_J", None) is None:
|
|
29
|
-
self.build()
|
|
30
|
-
return self._J
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def C(self) -> np.ndarray:
|
|
34
|
-
if getattr(self, "C", None) is None:
|
|
35
|
-
self.build()
|
|
36
|
-
return self._C
|
|
37
|
-
|
|
38
|
-
def get_graph(n, d):
|
|
39
|
-
""" Produce a repeatable graph with parameters n and d """
|
|
40
|
-
|
|
41
|
-
seed = n * d
|
|
42
|
-
return nx.random_graphs.random_regular_graph(d, n, seed)
|
|
43
|
-
|
|
44
|
-
def get_partition_graph(G, solution):
|
|
45
|
-
"""
|
|
46
|
-
Build the partitioned graph, counting cut size
|
|
47
|
-
|
|
48
|
-
:parameters: G : nx.DiGraph, solution : np.ndarray
|
|
49
|
-
:returns: nx.DiGraph, int
|
|
50
|
-
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
cut_size = 0
|
|
54
|
-
Gprime = nx.DiGraph()
|
|
55
|
-
Gprime.add_nodes_from(G.nodes)
|
|
56
|
-
for i, j in G.edges:
|
|
57
|
-
if solution[i] != solution[j]:
|
|
58
|
-
cut_size+=1
|
|
59
|
-
else:
|
|
60
|
-
Gprime.add_edge(i, j)
|
|
61
|
-
return Gprime, cut_size
|
|
62
|
-
|
|
63
|
-
def determine_solution(G, solution):
|
|
64
|
-
"""
|
|
65
|
-
Use a simple bisection method to determine the binary solution. Uses
|
|
66
|
-
the cut size as the metric.
|
|
67
|
-
|
|
68
|
-
Returns the partitioned graph and solution.
|
|
69
|
-
|
|
70
|
-
:parameters: G : nx.DiGraph, solution : np.ndarray
|
|
71
|
-
:returns: nx.DiGraph, np.ndarray
|
|
72
|
-
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
solution = np.array(solution)
|
|
76
|
-
lower = np.min(solution)
|
|
77
|
-
upper = np.max(solution)
|
|
78
|
-
best_cut_size = 0
|
|
79
|
-
best_graph = G
|
|
80
|
-
best_solution = None
|
|
81
|
-
while upper > lower + 0.0001:
|
|
82
|
-
middle = (lower + upper) / 2
|
|
83
|
-
test_solution = (solution>=middle).astype(np.int32)
|
|
84
|
-
Gprime, cut_size = get_partition_graph(G, test_solution)
|
|
85
|
-
if cut_size > best_cut_size:
|
|
86
|
-
best_cut_size = cut_size
|
|
87
|
-
lower = middle
|
|
88
|
-
best_solution = test_solution
|
|
89
|
-
best_graph = Gprime
|
|
90
|
-
else:
|
|
91
|
-
upper = middle
|
|
92
|
-
return best_graph, best_solution
|
|
93
|
-
|
|
94
|
-
def get_maxcut_H(G, t):
|
|
95
|
-
"""
|
|
96
|
-
Return a Hamiltonian representing the Maximum Cut Problem. Scale the problem using `t`.
|
|
97
|
-
Automatically adds a slack qudit.
|
|
98
|
-
|
|
99
|
-
"""
|
|
100
|
-
n = len(G.nodes)
|
|
101
|
-
J = np.zeros((n+1, n+1), dtype=np.float32)
|
|
102
|
-
h = np.zeros((n+1,1), dtype=np.float32)
|
|
103
|
-
for u, v in G.edges:
|
|
104
|
-
J[u, v] += 1
|
|
105
|
-
J[v, u] += 1
|
|
106
|
-
J[u, u] = 1
|
|
107
|
-
J[v, v] = 1
|
|
108
|
-
h[u] -= 1
|
|
109
|
-
h[v] -= 1
|
|
110
|
-
J *= 1/t**2
|
|
111
|
-
h *= 1/t
|
|
112
|
-
H = np.hstack([h, J])
|
|
113
|
-
return H
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import networkx as nx
|
|
3
|
-
from .quadraticmodel import QuadraticModel
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class MaxKCut(QuadraticModel):
|
|
7
|
-
|
|
8
|
-
def __init__(self, G : nx.Graph, k : int):
|
|
9
|
-
self.G = G
|
|
10
|
-
self.node_map = list(G.nodes)
|
|
11
|
-
self.k = k
|
|
12
|
-
self.partitions = []
|
|
13
|
-
self._lhs = None
|
|
14
|
-
self._rhs = None
|
|
15
|
-
self._objective = None
|
|
16
|
-
self._J = None
|
|
17
|
-
self._C = None
|
|
18
|
-
|
|
19
|
-
def decode(self, solution: np.ndarray) -> np.ndarray:
|
|
20
|
-
""" Override the default decoding to use a the max cut metric to determine a solution """
|
|
21
|
-
|
|
22
|
-
# only one partition per node can be selected
|
|
23
|
-
# rather than the same cutoff per node, use the max value per partition
|
|
24
|
-
decoded_solution = np.zeros_like(solution, dtype=np.int32)
|
|
25
|
-
k = self.k
|
|
26
|
-
for i, u in enumerate(self.node_map):
|
|
27
|
-
idx = slice(k*i, k*(i+1))
|
|
28
|
-
spins = solution[idx]
|
|
29
|
-
mx = np.max(spins)
|
|
30
|
-
for j in range(k):
|
|
31
|
-
if spins[j] == mx:
|
|
32
|
-
decoded_solution[k*i+j] = 1
|
|
33
|
-
break
|
|
34
|
-
return decoded_solution
|
|
35
|
-
|
|
36
|
-
def partition(self, solution):
|
|
37
|
-
""" Return a dictionary with the partition number of each node """
|
|
38
|
-
k = self.k
|
|
39
|
-
n = len(self.node_map)
|
|
40
|
-
partition_num = {}
|
|
41
|
-
for i, u in enumerate(self.node_map):
|
|
42
|
-
for j in range(k):
|
|
43
|
-
if solution[i*k+j] == 1:
|
|
44
|
-
partition_num[u] = j+1
|
|
45
|
-
return partition_num
|
|
46
|
-
|
|
47
|
-
def getCutSize(self, partition):
|
|
48
|
-
cut_size = 0
|
|
49
|
-
for u, v in self.G.edges:
|
|
50
|
-
if partition[u]!=partition[v]:
|
|
51
|
-
cut_size += 1
|
|
52
|
-
return cut_size
|
|
53
|
-
|
|
54
|
-
def _build_objective(self):
|
|
55
|
-
|
|
56
|
-
node_map = self.node_map
|
|
57
|
-
G = self.G
|
|
58
|
-
m = len(G.nodes)
|
|
59
|
-
n = self.k * m
|
|
60
|
-
# construct the quadratic portion of the objective
|
|
61
|
-
# the linear portion is 0
|
|
62
|
-
objective = np.zeros((n, n), dtype=np.float32)
|
|
63
|
-
# increment the joint variable terms indicating the nodes are in different sets
|
|
64
|
-
pairs = [(i, j) for i in range(self.k) for j in range(self.k) if i!=j]
|
|
65
|
-
for u, v in G.edges:
|
|
66
|
-
i = node_map.index(u)
|
|
67
|
-
j = node_map.index(v)
|
|
68
|
-
ibase = i * self.k
|
|
69
|
-
jbase = j * self.k
|
|
70
|
-
for incr1, incr2 in pairs:
|
|
71
|
-
idx1 = ibase + incr1
|
|
72
|
-
idx2 = jbase + incr2
|
|
73
|
-
objective[idx1, idx2] += -1
|
|
74
|
-
self._objective = (np.zeros((n, 1)), objective)
|
|
75
|
-
|
|
76
|
-
def _build_constraints(self):
|
|
77
|
-
|
|
78
|
-
node_map = self.node_map
|
|
79
|
-
G = self.G
|
|
80
|
-
m = len(G.nodes)
|
|
81
|
-
n = self.k * m
|
|
82
|
-
|
|
83
|
-
# build the constraints
|
|
84
|
-
A = np.zeros((m, n))
|
|
85
|
-
b = np.ones((m,))
|
|
86
|
-
for u in G.nodes:
|
|
87
|
-
i = node_map.index(u)
|
|
88
|
-
ibase = i * self.k
|
|
89
|
-
A[i, ibase:ibase+self.k] = 1
|
|
90
|
-
self._lhs = A
|
|
91
|
-
self._rhs = b
|
|
92
|
-
|
|
93
|
-
def build(self, multiplier=None):
|
|
94
|
-
""" Create the constraints and objective and Hamiltonian """
|
|
95
|
-
|
|
96
|
-
# there are k * m variables in this problem where m is the number of nodes in the graph
|
|
97
|
-
node_map = self.node_map
|
|
98
|
-
G = self.G
|
|
99
|
-
m = len(G.nodes)
|
|
100
|
-
n = self.k * m
|
|
101
|
-
self.domains = np.ones((n,))
|
|
102
|
-
|
|
103
|
-
self._build_objective()
|
|
104
|
-
if multiplier is None:
|
|
105
|
-
multiplier = np.max(np.abs(self._objective[1]))
|
|
106
|
-
self._build_constraints()
|
|
107
|
-
|
|
108
|
-
self._C, self._J = self.buildH(multiplier)
|
|
109
|
-
self.sum_constraint = m
|
|
110
|
-
|
|
111
|
-
def buildH(self, multiplier):
|
|
112
|
-
""" Combine the objective and penalties using the multiplier """
|
|
113
|
-
|
|
114
|
-
objC, objJ = self.objective
|
|
115
|
-
lhs, rhs = self.constraints
|
|
116
|
-
Pq = lhs.T@lhs
|
|
117
|
-
Pl = -2 * rhs.T@lhs
|
|
118
|
-
offset = rhs.T@rhs
|
|
119
|
-
n = self.n
|
|
120
|
-
J = np.zeros((n, n), np.float32)
|
|
121
|
-
C = np.zeros([n, 1], np.float32)
|
|
122
|
-
C += objC
|
|
123
|
-
J[:,:] += objJ
|
|
124
|
-
C += multiplier * Pl.reshape((n, 1))
|
|
125
|
-
J[:,:] += multiplier * Pq
|
|
126
|
-
return C, J
|
|
127
|
-
|
|
128
|
-
@property
|
|
129
|
-
def constraints(self):
|
|
130
|
-
""" Return LHS, RHS in numpy matrix format """
|
|
131
|
-
if self._rhs is None:
|
|
132
|
-
self.build()
|
|
133
|
-
return self._lhs, self._rhs
|
|
134
|
-
|
|
135
|
-
@property
|
|
136
|
-
def objective(self):
|
|
137
|
-
""" Return the quadratic objective as NxN+1 matrix """
|
|
138
|
-
|
|
139
|
-
if self._objective is None:
|
|
140
|
-
self.build()
|
|
141
|
-
return self._objective
|
|
142
|
-
|
|
143
|
-
@property
|
|
144
|
-
def H(self):
|
|
145
|
-
""" Return the Hamiltonian as parts C, J """
|
|
146
|
-
|
|
147
|
-
if self._C is None:
|
|
148
|
-
self.build()
|
|
149
|
-
return self._C, self._J
|
|
150
|
-
|
|
151
|
-
class WeightedMaxKCut(MaxKCut):
|
|
152
|
-
|
|
153
|
-
def __init__(self, G: nx.Graph, k: int, weight_label : str = "weight"):
|
|
154
|
-
super().__init__(G, k)
|
|
155
|
-
|
|
156
|
-
self.weight_label = weight_label
|
|
157
|
-
|
|
158
|
-
def _build_objective(self):
|
|
159
|
-
|
|
160
|
-
node_map = self.node_map
|
|
161
|
-
G = self.G
|
|
162
|
-
m = len(G.nodes)
|
|
163
|
-
n = self.k * m
|
|
164
|
-
# construct the quadratic portion of the objective
|
|
165
|
-
# the linear portion is 0
|
|
166
|
-
objective = np.zeros((n, n), dtype=np.float32)
|
|
167
|
-
# increment the joint variable terms indicating the nodes are in different sets
|
|
168
|
-
pairs = [(i, j) for i in range(self.k) for j in range(self.k) if i!=j]
|
|
169
|
-
for u, v in G.edges:
|
|
170
|
-
i = node_map.index(u)
|
|
171
|
-
j = node_map.index(v)
|
|
172
|
-
ibase = i * self.k
|
|
173
|
-
jbase = j * self.k
|
|
174
|
-
for incr1, incr2 in pairs:
|
|
175
|
-
idx1 = ibase + incr1
|
|
176
|
-
idx2 = jbase + incr2
|
|
177
|
-
objective[idx1, idx2] += G[u][v][self.weight_label]
|
|
178
|
-
self._objective = (np.zeros((n, 1)), objective)
|
|
179
|
-
|
|
180
|
-
def getCutSize(self, partition):
|
|
181
|
-
cut_size = 0
|
|
182
|
-
for u, v in self.G.edges:
|
|
183
|
-
if partition[u]!=partition[v]:
|
|
184
|
-
cut_size += self.G[u][v][self.weight_label]
|
|
185
|
-
return cut_size
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
from typing import Tuple
|
|
2
|
-
import numpy as np
|
|
3
|
-
from .base import EqcModel
|
|
4
|
-
|
|
5
|
-
class QuadraticMixIn:
|
|
6
|
-
C = None
|
|
7
|
-
J = None
|
|
8
|
-
|
|
9
|
-
def encode(self, levels:int=200, norm_value:float=None, dtype=np.float32) -> np.ndarray:
|
|
10
|
-
"""
|
|
11
|
-
Encode Hamiltonian into the domain of the device
|
|
12
|
-
|
|
13
|
-
The encoding method can be tuned using levels or norm_value. The parameter
|
|
14
|
-
levels and the member self.domains are used to generate a vector t such that
|
|
15
|
-
$$
|
|
16
|
-
x = ts
|
|
17
|
-
$$
|
|
18
|
-
thus,
|
|
19
|
-
$$
|
|
20
|
-
h^Tx + x^TJx = h^T(ts) + (ts)^TJ(ts) = (th)^Ts + s^T(t\\cross tJ)s
|
|
21
|
-
$$
|
|
22
|
-
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
n = max(self.domains.shape)
|
|
26
|
-
J = self.J
|
|
27
|
-
C = self.C
|
|
28
|
-
if norm_value is None:
|
|
29
|
-
max_level = levels - 1
|
|
30
|
-
multipliers = (self.domains) / max_level
|
|
31
|
-
J = np.array(np.outer(multipliers, multipliers) * J)
|
|
32
|
-
# h = np.multiply(h[:,0], multipliers)
|
|
33
|
-
C *= multipliers
|
|
34
|
-
C = C.reshape((n, 1))
|
|
35
|
-
else:
|
|
36
|
-
# normalize the operator
|
|
37
|
-
max_val_J = np.max(np.abs(J))
|
|
38
|
-
max_val_C = np.max(np.abs(C))
|
|
39
|
-
if max_val_J > max_val_C:
|
|
40
|
-
max_val = max_val_J
|
|
41
|
-
else:
|
|
42
|
-
max_val = max_val_C
|
|
43
|
-
C /= max_val
|
|
44
|
-
C *= norm_value
|
|
45
|
-
J /= max_val
|
|
46
|
-
J *= norm_value
|
|
47
|
-
# make J symmetric
|
|
48
|
-
J += J.T
|
|
49
|
-
J /= 2
|
|
50
|
-
# return operator in hJ format
|
|
51
|
-
H = np.hstack([C, J]).astype(dtype)
|
|
52
|
-
|
|
53
|
-
if self.machine_slacks > 0:
|
|
54
|
-
machine_slacks = self.machine_slacks
|
|
55
|
-
n = H.shape[0]
|
|
56
|
-
Hslack = np.zeros((n+machine_slacks, n+machine_slacks+1), dtype=dtype)
|
|
57
|
-
Hslack[:n, :n+1] = H
|
|
58
|
-
H = Hslack
|
|
59
|
-
|
|
60
|
-
return np.array(H)
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def sparse(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
64
|
-
""" Put the linear and quadratic terms in a sparse format
|
|
65
|
-
|
|
66
|
-
:returns: coefficients : List, indices : List
|
|
67
|
-
"""
|
|
68
|
-
C, J = self.H
|
|
69
|
-
n = self.n
|
|
70
|
-
indices = []
|
|
71
|
-
coefficients = []
|
|
72
|
-
# build a key (ordered tuple of indices) of length 2 for each element
|
|
73
|
-
for i in range(n):
|
|
74
|
-
if C[i,0] != 0:
|
|
75
|
-
key = (0, i+1)
|
|
76
|
-
indices.append(key)
|
|
77
|
-
coefficients.append(C[i,0])
|
|
78
|
-
# make J upper triangular
|
|
79
|
-
J = np.triu(J) + np.tril(J, -1).T
|
|
80
|
-
for i in range(n):
|
|
81
|
-
for j in range(i, n):
|
|
82
|
-
val = J[i, j]
|
|
83
|
-
if val != 0:
|
|
84
|
-
key = (i+1, j+1)
|
|
85
|
-
indices.append(key)
|
|
86
|
-
coefficients.append(val)
|
|
87
|
-
return np.array(coefficients, dtype=np.float32), np.array(indices, dtype=np.int32)
|
|
88
|
-
|
|
89
|
-
def evaluate(self, solution: np.ndarray, decode:bool=False, levels:int=200) -> float:
|
|
90
|
-
"""
|
|
91
|
-
Evaluate the solution using the original operator. The decode
|
|
92
|
-
and levels parameters control the decoding of the solution. Without
|
|
93
|
-
specifying decode, the evaluation of the operator is done with the
|
|
94
|
-
solution provided.
|
|
95
|
-
|
|
96
|
-
"""
|
|
97
|
-
H = self.H
|
|
98
|
-
h, J = H[:, 0], H[:, 1:]
|
|
99
|
-
if decode:
|
|
100
|
-
sol = self.decode(solution)
|
|
101
|
-
else:
|
|
102
|
-
sol = solution
|
|
103
|
-
return np.squeeze(sol.T@J@sol + h.T@sol)
|
|
104
|
-
|
|
105
|
-
class QuadraticModel(QuadraticMixIn, EqcModel):
|
|
106
|
-
""" Provides a quadratic operator and device sum constraint support """
|
|
107
|
-
|
|
108
|
-
def __init__(self, C : np.ndarray, J : np.ndarray, sum_constraint : float):
|
|
109
|
-
self._C = C
|
|
110
|
-
self._J = J
|
|
111
|
-
self._sum_constraint = sum_constraint
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
def H(self):
|
|
115
|
-
return self._C, self._J
|
|
116
|
-
|
|
117
|
-
def check_constraint(self, solution: np.array) -> bool:
|
|
118
|
-
""" Evaluate the solution against the original sum constraint """
|
|
119
|
-
return np.sum(solution) == self.sum_constraint
|
|
120
|
-
|
|
121
|
-
@property
|
|
122
|
-
def sum_constraint(self) -> int:
|
|
123
|
-
""" Integer value which all qudits must sum to.
|
|
124
|
-
The value must be less than or equal to n * base for
|
|
125
|
-
the model to make sense. """
|
|
126
|
-
|
|
127
|
-
return self._sum_constraint
|
|
128
|
-
|
|
129
|
-
@sum_constraint.setter
|
|
130
|
-
def sum_constraint(self, value : int):
|
|
131
|
-
self._sum_constraint = value
|