eqc-models 0.9.8__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.9.8.data/platlib/compile_extensions.py +23 -0
- eqc_models-0.9.8.data/platlib/eqc_models/__init__.py +15 -0
- eqc_models-0.9.8.data/platlib/eqc_models/algorithms/__init__.py +4 -0
- eqc_models-0.9.8.data/platlib/eqc_models/algorithms/base.py +10 -0
- eqc_models-0.9.8.data/platlib/eqc_models/algorithms/penaltymultiplier.py +169 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/__init__.py +6 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/allocation.py +367 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/portbase.py +128 -0
- eqc_models-0.9.8.data/platlib/eqc_models/allocation/portmomentum.py +137 -0
- eqc_models-0.9.8.data/platlib/eqc_models/assignment/__init__.py +5 -0
- eqc_models-0.9.8.data/platlib/eqc_models/assignment/qap.py +82 -0
- eqc_models-0.9.8.data/platlib/eqc_models/assignment/setpartition.py +170 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/__init__.py +72 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/base.py +150 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/constraints.py +276 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/operators.py +201 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.c +11363 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polyeval.pyx +72 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/polynomial.py +274 -0
- eqc_models-0.9.8.data/platlib/eqc_models/base/quadratic.py +250 -0
- eqc_models-0.9.8.data/platlib/eqc_models/decoding.py +20 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/__init__.py +5 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/base.py +63 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/hypergraph.py +307 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/maxcut.py +155 -0
- eqc_models-0.9.8.data/platlib/eqc_models/graph/maxkcut.py +184 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/__init__.py +15 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierbase.py +99 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqboost.py +423 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqsvm.py +237 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/clustering.py +323 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/clusteringbase.py +112 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/decomposition.py +363 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/forecast.py +255 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/forecastbase.py +139 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/regressor.py +220 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/regressorbase.py +97 -0
- eqc_models-0.9.8.data/platlib/eqc_models/ml/reservoir.py +106 -0
- eqc_models-0.9.8.data/platlib/eqc_models/sequence/__init__.py +5 -0
- eqc_models-0.9.8.data/platlib/eqc_models/sequence/tsp.py +217 -0
- eqc_models-0.9.8.data/platlib/eqc_models/solvers/__init__.py +12 -0
- eqc_models-0.9.8.data/platlib/eqc_models/solvers/qciclient.py +707 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/__init__.py +6 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/fileio.py +38 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/polynomial.py +137 -0
- eqc_models-0.9.8.data/platlib/eqc_models/utilities/qplib.py +375 -0
- eqc_models-0.9.8.dist-info/LICENSE.txt +202 -0
- eqc_models-0.9.8.dist-info/METADATA +139 -0
- eqc_models-0.9.8.dist-info/RECORD +52 -0
- eqc_models-0.9.8.dist-info/WHEEL +5 -0
- eqc_models-0.9.8.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
# (C) Quantum Computing Inc., 2024.
|
|
2
|
+
from typing import Dict, List, Tuple
|
|
3
|
+
import logging
|
|
4
|
+
import datetime
|
|
5
|
+
import numpy as np
|
|
6
|
+
from qci_client import QciClient
|
|
7
|
+
from eqc_models.base.base import ModelSolver, EqcModel
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(name=__name__)
|
|
10
|
+
|
|
11
|
+
class QciClientMixin:
|
|
12
|
+
"""
|
|
13
|
+
This class provides an instance method and property that manage the connection to
|
|
14
|
+
the REST API.
|
|
15
|
+
|
|
16
|
+
Methods
|
|
17
|
+
-------
|
|
18
|
+
|
|
19
|
+
connect
|
|
20
|
+
|
|
21
|
+
Properties
|
|
22
|
+
----------
|
|
23
|
+
|
|
24
|
+
client : QciClient
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
url = None
|
|
29
|
+
api_token = None
|
|
30
|
+
|
|
31
|
+
def connect(self, url: str = None, api_token: str = None):
|
|
32
|
+
"""
|
|
33
|
+
Use this method to define connection parameters manually. Returns the string "SUCCESS"
|
|
34
|
+
on a successful connection, raises an a RuntimeError on failure.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
--------------
|
|
38
|
+
|
|
39
|
+
url: The URL used to connect to the Dirac machine.
|
|
40
|
+
|
|
41
|
+
api_token: Authentication token.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
self.url = url
|
|
45
|
+
self.api_token = api_token
|
|
46
|
+
client = self.client
|
|
47
|
+
if client is None:
|
|
48
|
+
raise RuntimeError("Failed to connect to Qatalyst service")
|
|
49
|
+
return "SUCCESS"
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def client(self) -> QciClient:
|
|
53
|
+
"""
|
|
54
|
+
Returns a new client object every time. If the connection was not
|
|
55
|
+
configured in code, then QciClient attempts to use environment variables
|
|
56
|
+
to connect to the service
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
# pull from environment variables if the class variables are not set
|
|
60
|
+
if self.url is None and self.api_token is None:
|
|
61
|
+
log.debug(
|
|
62
|
+
"Getting QciClient connection using environment variables"
|
|
63
|
+
)
|
|
64
|
+
client = QciClient()
|
|
65
|
+
else:
|
|
66
|
+
log.debug("Getting QciClient connection using class variables")
|
|
67
|
+
client = QciClient(url=self.url, api_token=self.api_token)
|
|
68
|
+
return client
|
|
69
|
+
|
|
70
|
+
class Dirac1Mixin:
|
|
71
|
+
sampler_type = "dirac-1"
|
|
72
|
+
job_params_names = ["num_samples", "alpha", "atol"]
|
|
73
|
+
|
|
74
|
+
class QuboSolverMixin:
|
|
75
|
+
job_type = "qubo"
|
|
76
|
+
|
|
77
|
+
def uploadJobFiles(self, client:QciClient, model:EqcModel):
|
|
78
|
+
"""
|
|
79
|
+
This method retrieves a QUBO representation from the model's
|
|
80
|
+
:code:`qubo` property and uploads it, returning :code:`qubo_file_id`
|
|
81
|
+
for submission with a job request.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
--------------
|
|
85
|
+
|
|
86
|
+
client: The QciClient instance.
|
|
87
|
+
|
|
88
|
+
model: The EqcModel instance.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# C, J = model.H
|
|
93
|
+
# Q = J + np.diag(C)
|
|
94
|
+
Q = model.qubo.Q
|
|
95
|
+
qubo_file = {
|
|
96
|
+
"file_name": f"{model.__class__.__name__}-qubo",
|
|
97
|
+
"file_config": {
|
|
98
|
+
"qubo": {"data": Q, "num_variables": Q.shape[0]}
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
qubo_file_id = client.upload_file(file=qubo_file)["file_id"]
|
|
102
|
+
return {"qubo_file_id": qubo_file_id}
|
|
103
|
+
|
|
104
|
+
class Dirac3Mixin:
|
|
105
|
+
"""
|
|
106
|
+
Defines the specifics required for using Dirac-3 as the sampler
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
sampler_type = "dirac-3"
|
|
111
|
+
job_params_names = [
|
|
112
|
+
"num_samples",
|
|
113
|
+
"solution_precision",
|
|
114
|
+
"relaxation_schedule",
|
|
115
|
+
"mean_photon_number",
|
|
116
|
+
"normalized_loss_rate",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
def uploadJobFiles(self, client: QciClient, model: EqcModel):
|
|
120
|
+
"""
|
|
121
|
+
Upload a Hamiltonian in polynomial format.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
--------------
|
|
125
|
+
|
|
126
|
+
client: The QciClient instance.
|
|
127
|
+
|
|
128
|
+
model: The EqcModel instance.
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
# poly_coeffs, poly_indices = model.sparse
|
|
133
|
+
polynomial = model.polynomial
|
|
134
|
+
poly_coeffs = polynomial.coefficients
|
|
135
|
+
poly_indices = polynomial.indices
|
|
136
|
+
data = []
|
|
137
|
+
# must find these attributes of the polynomial before uploading
|
|
138
|
+
max_degree = 0
|
|
139
|
+
min_degree = len(poly_indices[-1])
|
|
140
|
+
num_variables = 0
|
|
141
|
+
for i in range(len(poly_coeffs)):
|
|
142
|
+
idx = 0
|
|
143
|
+
if num_variables < max(poly_indices[i]):
|
|
144
|
+
num_variables = max(poly_indices[i])
|
|
145
|
+
while max_degree < len(poly_indices[i]) and idx < len(
|
|
146
|
+
poly_indices[i]
|
|
147
|
+
):
|
|
148
|
+
if (
|
|
149
|
+
poly_indices[i][idx] > 0
|
|
150
|
+
and max_degree < len(poly_indices[i]) - idx
|
|
151
|
+
):
|
|
152
|
+
max_degree = len(poly_indices[i]) - idx
|
|
153
|
+
idx += len(poly_indices[i])
|
|
154
|
+
else:
|
|
155
|
+
idx += 1
|
|
156
|
+
idx = len(poly_indices[i]) - 1
|
|
157
|
+
while min_degree > 1 and idx > 0:
|
|
158
|
+
if (
|
|
159
|
+
poly_indices[i][idx] > 0
|
|
160
|
+
and min_degree > len(poly_indices[i]) - idx
|
|
161
|
+
):
|
|
162
|
+
min_degree = len(poly_indices[i]) - idx
|
|
163
|
+
idx = 0
|
|
164
|
+
else:
|
|
165
|
+
idx -= 1
|
|
166
|
+
data.append(
|
|
167
|
+
{
|
|
168
|
+
"idx": poly_indices[i],
|
|
169
|
+
"val": float(poly_coeffs[i]),
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
log.debug("Min degree of polynomial %d", min_degree)
|
|
173
|
+
log.debug("Max degree of polynomial %d", max_degree)
|
|
174
|
+
log.debug("Number of polynomial elements %d", len(poly_coeffs))
|
|
175
|
+
polynomial = {
|
|
176
|
+
"file_name": f"{model.__class__.__name__}",
|
|
177
|
+
"file_config": {
|
|
178
|
+
"polynomial": {
|
|
179
|
+
"num_variables": int(num_variables)
|
|
180
|
+
+ model.machine_slacks,
|
|
181
|
+
"max_degree": max_degree,
|
|
182
|
+
"min_degree": min_degree,
|
|
183
|
+
"data": data,
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
log.debug(polynomial)
|
|
188
|
+
file_id = client.upload_file(file=polynomial)["file_id"]
|
|
189
|
+
log.debug("Upload polynomial file produced file id %s", file_id)
|
|
190
|
+
return {"polynomial_file_id": file_id}
|
|
191
|
+
|
|
192
|
+
class QciClientSolver(QciClientMixin, ModelSolver):
|
|
193
|
+
"""
|
|
194
|
+
Parameters
|
|
195
|
+
-----------
|
|
196
|
+
|
|
197
|
+
url : string
|
|
198
|
+
optional value specifying the QCi API URL
|
|
199
|
+
api_token : string
|
|
200
|
+
optional value specifying the authentication token for the QCi API
|
|
201
|
+
|
|
202
|
+
QCi API client wrapper for solving an EQC model. This class provides the
|
|
203
|
+
common method for uploading a file to the API for solving. Since the file
|
|
204
|
+
types change for the job types, the specific files required for the job are
|
|
205
|
+
specified in subclasses within the `uploadFiles` method.
|
|
206
|
+
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, url=None, api_token=None):
|
|
210
|
+
self.url = url
|
|
211
|
+
self.api_token = api_token
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def uploadFile(
|
|
215
|
+
file_data: np.ndarray,
|
|
216
|
+
file_name: str = None,
|
|
217
|
+
file_type: str = None,
|
|
218
|
+
client: QciClient = None,
|
|
219
|
+
) -> str:
|
|
220
|
+
"""
|
|
221
|
+
Upload the operator file, return the file ID.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
--------------
|
|
225
|
+
|
|
226
|
+
file_data: numpy array, dictionary or list
|
|
227
|
+
contains file data to be uploaded
|
|
228
|
+
|
|
229
|
+
file_name: str
|
|
230
|
+
Name of the file to be uploaded.
|
|
231
|
+
|
|
232
|
+
file_type: str
|
|
233
|
+
Type of the file to be uploaded.
|
|
234
|
+
|
|
235
|
+
client: QciClient
|
|
236
|
+
QciClient instance
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
n = file_data.shape[0]
|
|
241
|
+
log.debug("Uploading %s file of %d variables", file_type, n)
|
|
242
|
+
if client is None:
|
|
243
|
+
log.debug("Retrieving instance client")
|
|
244
|
+
client = self.client
|
|
245
|
+
file_obj = {"file_config": {file_type: {"data": file_data}}}
|
|
246
|
+
if file_type in (
|
|
247
|
+
"constraints",
|
|
248
|
+
"hamiltonian",
|
|
249
|
+
"qubo",
|
|
250
|
+
"objective",
|
|
251
|
+
):
|
|
252
|
+
file_obj["file_config"][file_type]["num_variables"] = n
|
|
253
|
+
# print(ham_file)
|
|
254
|
+
if file_name is None:
|
|
255
|
+
ts = datetime.datetime.now().timestamp()
|
|
256
|
+
file_name = f"{file_type}{n}-{ts}"
|
|
257
|
+
log.debug("Using file name %s", file_name)
|
|
258
|
+
file_obj["file_name"] = file_name
|
|
259
|
+
file_id = client.upload_file(file=file_obj)["file_id"]
|
|
260
|
+
return file_id
|
|
261
|
+
|
|
262
|
+
def uploadJobFiles(self, client: QciClient, model: EqcModel):
|
|
263
|
+
raise NotImplementedError("Subclass must override uploadJobFiles")
|
|
264
|
+
|
|
265
|
+
def solve(
|
|
266
|
+
self,
|
|
267
|
+
model: EqcModel,
|
|
268
|
+
name: str = None,
|
|
269
|
+
tags: List = None,
|
|
270
|
+
num_samples: int = 1,
|
|
271
|
+
wait=True,
|
|
272
|
+
job_type=None,
|
|
273
|
+
**job_kwargs,
|
|
274
|
+
) -> Dict:
|
|
275
|
+
"""
|
|
276
|
+
Parameters
|
|
277
|
+
--------------
|
|
278
|
+
|
|
279
|
+
model: EqcModel
|
|
280
|
+
Instance of a model for solving.
|
|
281
|
+
|
|
282
|
+
name: str
|
|
283
|
+
Name of the job; default is None.
|
|
284
|
+
|
|
285
|
+
tags: list
|
|
286
|
+
A list of job tags; default is None.
|
|
287
|
+
|
|
288
|
+
num_samples: int
|
|
289
|
+
Number of samples used; default is 1.
|
|
290
|
+
|
|
291
|
+
wait: bool
|
|
292
|
+
The wait flag indicating whether to wait for the job to complete
|
|
293
|
+
before returning the complete job data otherwise return a job ID
|
|
294
|
+
as soon as a job is submitted; default is True.
|
|
295
|
+
|
|
296
|
+
job_type: str
|
|
297
|
+
Type of the job; default is None. When None, it is constructed
|
|
298
|
+
from the instance `job_type` property.
|
|
299
|
+
|
|
300
|
+
Returns
|
|
301
|
+
----------
|
|
302
|
+
job response dictionary
|
|
303
|
+
|
|
304
|
+
This method takes the particulars of the instance model and handles
|
|
305
|
+
the QciClient.solve call.
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
job_config = {}
|
|
310
|
+
job_config.update(
|
|
311
|
+
{"num_samples": num_samples, "device_type": self.sampler_type}
|
|
312
|
+
)
|
|
313
|
+
# set the job parameters
|
|
314
|
+
for name in self.job_params_names:
|
|
315
|
+
if name in job_kwargs:
|
|
316
|
+
job_config[name] = job_kwargs.pop(name)
|
|
317
|
+
elif hasattr(model, name):
|
|
318
|
+
job_config[name] = getattr(model, name)
|
|
319
|
+
leftovers = ",".join(job_kwargs.keys())
|
|
320
|
+
if leftovers:
|
|
321
|
+
raise ValueError(
|
|
322
|
+
f"Unused job parameters given to solve method: {leftovers}"
|
|
323
|
+
)
|
|
324
|
+
# prep files in the API
|
|
325
|
+
client = self.client
|
|
326
|
+
job_files = self.uploadJobFiles(client, model)
|
|
327
|
+
log.debug(f"Building job body: {job_config}")
|
|
328
|
+
if job_type is None:
|
|
329
|
+
job_type = f"sample-{self.job_type}"
|
|
330
|
+
job_body = client.build_job_body(
|
|
331
|
+
job_type=job_type,
|
|
332
|
+
job_params=job_config,
|
|
333
|
+
job_tags=tags,
|
|
334
|
+
**job_files,
|
|
335
|
+
)
|
|
336
|
+
response = client.process_job(job_body=job_body, wait=wait)
|
|
337
|
+
return response
|
|
338
|
+
|
|
339
|
+
def getResults(self, response: Dict) -> Dict[str, List]:
|
|
340
|
+
"""
|
|
341
|
+
Extract the results from response.
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
--------------
|
|
345
|
+
response: The responce from QciClient.
|
|
346
|
+
|
|
347
|
+
Returns
|
|
348
|
+
--------------
|
|
349
|
+
The results json object.
|
|
350
|
+
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
results = response["results"]
|
|
354
|
+
log.debug("Got results object: %s", results)
|
|
355
|
+
return results
|
|
356
|
+
|
|
357
|
+
# Some example usage
|
|
358
|
+
class Dirac1CloudSolver(Dirac1Mixin, QuboSolverMixin, QciClientSolver):
|
|
359
|
+
"""
|
|
360
|
+
Overview
|
|
361
|
+
---------
|
|
362
|
+
Dirac1CloudSolver is a class that encapsulates the different calls to Qatalyst
|
|
363
|
+
for Dirac-1 jobs, which are quadratic binary optimization problems.
|
|
364
|
+
|
|
365
|
+
Examples
|
|
366
|
+
-------------------
|
|
367
|
+
|
|
368
|
+
>>> C = np.array([[-1], [-1]])
|
|
369
|
+
>>> J = np.array([[0, 1.0], [1.0, 0]])
|
|
370
|
+
>>> from eqc_models.base.quadratic import QuadraticModel
|
|
371
|
+
>>> model = QuadraticModel(C, J)
|
|
372
|
+
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
373
|
+
>>> solver = Dirac1CloudSolver()
|
|
374
|
+
>>> response = solver.solve(model, num_samples=5) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
375
|
+
2... submitted... COMPLETED...
|
|
376
|
+
>>> response["results"]["energies"][0] <= 1.0
|
|
377
|
+
True
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
Dirac3CloudSolver is a class that encapsulates the different calls to Qatalyst
|
|
384
|
+
for Dirac-3 jobs. Currently, there are two different jobs, one for integer and
|
|
385
|
+
another for continuous solutions. Calling the solve method with different arguments
|
|
386
|
+
controls which job is submitted. The continuous job requires :code:`sum_constraint`
|
|
387
|
+
and optionally takes the :code:`solution_precision` argument. The integer job
|
|
388
|
+
does not accept either of these parameters, so specifying a sum constraint forces
|
|
389
|
+
the job type to be continuous and not specifying it results in the integer job being
|
|
390
|
+
called.
|
|
391
|
+
|
|
392
|
+
Continuous Solver
|
|
393
|
+
-------------------
|
|
394
|
+
|
|
395
|
+
Utilizing Dirac-3 as a continuous solver involves encoding the variables in single time bins
|
|
396
|
+
with the values of each determined by a normalized photon count value.
|
|
397
|
+
|
|
398
|
+
Integer Solver
|
|
399
|
+
-------------------
|
|
400
|
+
|
|
401
|
+
Utilizing Dirac-3 as an integer solver involves encoding the variables in multiple time bins,
|
|
402
|
+
each representing a certain value for that variable, or "qudit".
|
|
403
|
+
|
|
404
|
+
Examples
|
|
405
|
+
-------------------
|
|
406
|
+
|
|
407
|
+
>>> C = np.array([[1], [1]])
|
|
408
|
+
>>> J = np.array([[-1.0, 0], [0, -1.0]])
|
|
409
|
+
>>> from eqc_models.base.quadratic import QuadraticModel
|
|
410
|
+
>>> model = QuadraticModel(C, J)
|
|
411
|
+
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
412
|
+
>>> solver = Dirac3CloudSolver()
|
|
413
|
+
>>> response = solver.solve(model, sum_constraint=1, relaxation_schedule=1,
|
|
414
|
+
... solution_precision=None) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
415
|
+
2... submitted... COMPLETED...
|
|
416
|
+
>>> response["results"]["energies"][0] <= 1.0
|
|
417
|
+
True
|
|
418
|
+
>>> C = np.array([-1, -1], dtype=np.float32)
|
|
419
|
+
>>> J = np.array([[0, 1], [1, 0]], dtype=np.float32)
|
|
420
|
+
>>> model = QuadraticModel(C, J)
|
|
421
|
+
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
422
|
+
>>> response = solver.solve(model, relaxation_schedule=1) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
423
|
+
2... submitted... COMPLETED...
|
|
424
|
+
>>> response["results"]["energies"][0] == -1.0
|
|
425
|
+
True
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
job_type = "hamiltonian"
|
|
429
|
+
job_params_names = Dirac3Mixin.job_params_names + ["num_levels", "sum_constraint"]
|
|
430
|
+
|
|
431
|
+
def solve(
|
|
432
|
+
self,
|
|
433
|
+
model: EqcModel,
|
|
434
|
+
name: str = None,
|
|
435
|
+
tags: List = None,
|
|
436
|
+
sum_constraint: float = None,
|
|
437
|
+
relaxation_schedule: int = None,
|
|
438
|
+
solution_precision: float = None,
|
|
439
|
+
num_samples: int = 1,
|
|
440
|
+
wait: bool = True,
|
|
441
|
+
mean_photon_number: float = None,
|
|
442
|
+
normalized_loss_rate: int = None,
|
|
443
|
+
**job_kwargs,
|
|
444
|
+
):
|
|
445
|
+
"""
|
|
446
|
+
Parameters
|
|
447
|
+
--------------
|
|
448
|
+
|
|
449
|
+
model: EqcModel
|
|
450
|
+
a model object which supplies a hamiltonian operator for the
|
|
451
|
+
device to sample. Must support the polynomial operator property.
|
|
452
|
+
tags: List
|
|
453
|
+
a list of strings to save with the job
|
|
454
|
+
sum_constraint : float
|
|
455
|
+
a value which applies a constraint to the solution, forcing
|
|
456
|
+
all variables to sum to this value, changes method to continuous
|
|
457
|
+
solver
|
|
458
|
+
relaxation_schedule : int
|
|
459
|
+
a predefined schedule indicator which sets parameters
|
|
460
|
+
on the device to control the sampling through photon
|
|
461
|
+
measurement
|
|
462
|
+
solution_precision : float
|
|
463
|
+
a value which, when not None, indicates the numerical
|
|
464
|
+
precision desired in the solution: 1 for integer, 0.1
|
|
465
|
+
for tenths place, 0.01 for hundreths and None for raw,
|
|
466
|
+
only used with continuous solver
|
|
467
|
+
num_samples : int
|
|
468
|
+
the number of samples to take, defaults to 1
|
|
469
|
+
wait : bool
|
|
470
|
+
a flag for waiting for the response or letting it run asynchronously.
|
|
471
|
+
Asynchronous runs must retrieve results directly using qci-client and
|
|
472
|
+
the job_id.
|
|
473
|
+
mean_photon_number : float
|
|
474
|
+
an optional decimal value which sets the average number
|
|
475
|
+
of photons that are present in a given quantum state.
|
|
476
|
+
Modify this value to control the relaxation schedule more
|
|
477
|
+
precisely than the four presets given in schedules 1
|
|
478
|
+
through 4. Allowed values are decimals between 0.1 and 2.
|
|
479
|
+
normalized_loss_rate : int
|
|
480
|
+
an integer value which Sets the amount of loss introduced
|
|
481
|
+
into the system for each loop during the measurement process.
|
|
482
|
+
Modify this value to control the relaxation schedule more
|
|
483
|
+
precisely than the four presets given in schedules 1
|
|
484
|
+
through 4. Allowed values range from 1 to 50.
|
|
485
|
+
|
|
486
|
+
"""
|
|
487
|
+
# choose integer or continuous solver
|
|
488
|
+
continuous = sum_constraint is not None
|
|
489
|
+
if continuous:
|
|
490
|
+
job_kwargs["sum_constraint"] = sum_constraint
|
|
491
|
+
if relaxation_schedule not in (1, 2, 3, 4):
|
|
492
|
+
raise ValueError(
|
|
493
|
+
"relaxation_schedule must be one of 1, 2, 3 or 4"
|
|
494
|
+
)
|
|
495
|
+
job_kwargs["relaxation_schedule"] = relaxation_schedule
|
|
496
|
+
job_kwargs["mean_photon_number"] = mean_photon_number
|
|
497
|
+
job_kwargs["normalized_loss_rate"] = normalized_loss_rate
|
|
498
|
+
job_kwargs["solution_precision"] = solution_precision
|
|
499
|
+
job_type = "sample-" + self.job_type
|
|
500
|
+
return super().solve(
|
|
501
|
+
model,
|
|
502
|
+
name,
|
|
503
|
+
tags=tags,
|
|
504
|
+
num_samples=num_samples,
|
|
505
|
+
wait=wait,
|
|
506
|
+
job_type=job_type,
|
|
507
|
+
**job_kwargs,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
else:
|
|
511
|
+
job_kwargs["mean_photon_number"] = mean_photon_number
|
|
512
|
+
job_kwargs["normalized_loss_rate"] = normalized_loss_rate
|
|
513
|
+
job_kwargs["relaxation_schedule"] = relaxation_schedule
|
|
514
|
+
job_kwargs["num_levels"] = ub = [val + 1 for val in model.upper_bound.tolist()]
|
|
515
|
+
job_type = "sample-" + self.job_type + "-integer"
|
|
516
|
+
return super().solve(
|
|
517
|
+
model,
|
|
518
|
+
name,
|
|
519
|
+
tags=tags,
|
|
520
|
+
num_samples=num_samples,
|
|
521
|
+
wait=wait,
|
|
522
|
+
job_type=job_type,
|
|
523
|
+
**job_kwargs,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
class Dirac3IntegerCloudSolver(Dirac3Mixin, QciClientSolver):
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
>>> C = np.array([-1, -1], dtype=np.float32)
|
|
530
|
+
>>> J = np.array([[0, 1], [1, 0]], dtype=np.float32)
|
|
531
|
+
>>> from eqc_models.base.quadratic import QuadraticModel
|
|
532
|
+
>>> model = QuadraticModel(C, J)
|
|
533
|
+
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
534
|
+
>>> solver = Dirac3IntegerCloudSolver()
|
|
535
|
+
>>> model = QuadraticModel(C, J)
|
|
536
|
+
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
537
|
+
>>> response = solver.solve(model, relaxation_schedule=1) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
538
|
+
2... submitted... COMPLETED...
|
|
539
|
+
>>> response["results"]["energies"][0] == -1.0
|
|
540
|
+
True
|
|
541
|
+
|
|
542
|
+
"""
|
|
543
|
+
job_type = "hamiltonian-integer"
|
|
544
|
+
job_params_names = Dirac3Mixin.job_params_names + ["num_levels"]
|
|
545
|
+
|
|
546
|
+
def solve(self,
|
|
547
|
+
model : EqcModel,
|
|
548
|
+
name: str = None,
|
|
549
|
+
tags: List = None,
|
|
550
|
+
relaxation_schedule: int = None,
|
|
551
|
+
num_samples: int = 1,
|
|
552
|
+
wait: bool = True,
|
|
553
|
+
mean_photon_number: float = None,
|
|
554
|
+
normalized_loss_rate: int = None,
|
|
555
|
+
**job_kwargs,
|
|
556
|
+
):
|
|
557
|
+
"""
|
|
558
|
+
Parameters
|
|
559
|
+
--------------
|
|
560
|
+
|
|
561
|
+
model: EqcModel
|
|
562
|
+
a model object which supplies a hamiltonian operator for the
|
|
563
|
+
device to sample. Must support the polynomial operator property.
|
|
564
|
+
tags: List
|
|
565
|
+
a list of strings to save with the job
|
|
566
|
+
relaxation_schedule : int
|
|
567
|
+
a predefined schedule indicator which sets parameters
|
|
568
|
+
on the device to control the sampling through photon
|
|
569
|
+
measurement
|
|
570
|
+
num_samples : int
|
|
571
|
+
the number of samples to take, defaults to 1
|
|
572
|
+
wait : bool
|
|
573
|
+
a flag for waiting for the response or letting it run asynchronously.
|
|
574
|
+
Asynchronous runs must retrieve results directly using qci-client and
|
|
575
|
+
the job_id.
|
|
576
|
+
mean_photon_number : float
|
|
577
|
+
an optional decimal value which sets the average number
|
|
578
|
+
of photons that are present in a given quantum state.
|
|
579
|
+
Modify this value to control the relaxation schedule more
|
|
580
|
+
precisely than the four presets given in schedules 1
|
|
581
|
+
through 4. Allowed values are decimals between 0.1 and 2.
|
|
582
|
+
normalized_loss_rate : int
|
|
583
|
+
an integer value which Sets the amount of loss introduced
|
|
584
|
+
into the system for each loop during the measurement process.
|
|
585
|
+
Modify this value to control the relaxation schedule more
|
|
586
|
+
precisely than the four presets given in schedules 1
|
|
587
|
+
through 4. Allowed values range from 1 to 50.
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
Dirac3IntegerCloudSolver is a class that encapsulates the different calls to
|
|
591
|
+
Qatalyst for Dirac-3 jobs. Utilizing Dirac-3 as an integer solver involves
|
|
592
|
+
encoding the variables in multiple time bins, each representing a certain
|
|
593
|
+
value for that variable, or "qudit".
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
"""
|
|
597
|
+
return super().solve(
|
|
598
|
+
model,
|
|
599
|
+
name,
|
|
600
|
+
tags=tags,
|
|
601
|
+
num_samples=num_samples,
|
|
602
|
+
wait=wait,
|
|
603
|
+
mean_photon_number=mean_photon_number,
|
|
604
|
+
normalized_loss_rate=normalized_loss_rate,
|
|
605
|
+
relaxation_schedule=relaxation_schedule,
|
|
606
|
+
num_levels=[val + 1 for val in model.upper_bound.tolist()],
|
|
607
|
+
**job_kwargs,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
>>> C = np.array([[1], [1]])
|
|
615
|
+
>>> J = np.array([[-1.0, 0], [0, -1.0]])
|
|
616
|
+
>>> from eqc_models.base.quadratic import QuadraticModel
|
|
617
|
+
>>> model = QuadraticModel(C, J)
|
|
618
|
+
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
619
|
+
>>> solver = Dirac3ContinuousCloudSolver()
|
|
620
|
+
>>> response = solver.solve(model, sum_constraint=1, relaxation_schedule=1,
|
|
621
|
+
... solution_precision=None) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
622
|
+
2... submitted... COMPLETED...
|
|
623
|
+
>>> response["results"]["energies"][0] <= 1.0
|
|
624
|
+
True
|
|
625
|
+
|
|
626
|
+
"""
|
|
627
|
+
job_type = "hamiltonian"
|
|
628
|
+
job_params_names = Dirac3Mixin.job_params_names + ["sum_constraint"]
|
|
629
|
+
|
|
630
|
+
def solve(
|
|
631
|
+
self,
|
|
632
|
+
model: EqcModel,
|
|
633
|
+
name: str = None,
|
|
634
|
+
tags: List = None,
|
|
635
|
+
sum_constraint: float = None,
|
|
636
|
+
relaxation_schedule: int = None,
|
|
637
|
+
solution_precision: float = None,
|
|
638
|
+
num_samples: int = 1,
|
|
639
|
+
wait: bool = True,
|
|
640
|
+
mean_photon_number: float = None,
|
|
641
|
+
normalized_loss_rate: int = None,
|
|
642
|
+
**job_kwargs,
|
|
643
|
+
):
|
|
644
|
+
"""
|
|
645
|
+
Parameters
|
|
646
|
+
--------------
|
|
647
|
+
|
|
648
|
+
model: EqcModel
|
|
649
|
+
a model object which supplies a hamiltonian operator for the
|
|
650
|
+
device to sample. Must support the polynomial operator property.
|
|
651
|
+
tags: List
|
|
652
|
+
a list of strings to save with the job
|
|
653
|
+
sum_constraint : float
|
|
654
|
+
a value which applies a constraint to the solution, forcing
|
|
655
|
+
all variables to sum to this value
|
|
656
|
+
relaxation_schedule : int
|
|
657
|
+
a predefined schedule indicator which sets parameters
|
|
658
|
+
on the device to control the sampling through photon
|
|
659
|
+
measurement
|
|
660
|
+
solution_precision : float
|
|
661
|
+
a value which, when not None, indicates the numerical
|
|
662
|
+
precision desired in the solution: 1 for integer, 0.1
|
|
663
|
+
for tenths place, 0.01 for hundreths and None for raw
|
|
664
|
+
num_samples : int
|
|
665
|
+
the number of samples to take, defaults to 1
|
|
666
|
+
wait : bool
|
|
667
|
+
a flag for waiting for the response or letting it run asynchronously.
|
|
668
|
+
Asynchronous runs must retrieve results directly using qci-client and
|
|
669
|
+
the job_id.
|
|
670
|
+
mean_photon_number : float
|
|
671
|
+
an optional decimal value which sets the average number
|
|
672
|
+
of photons that are present in a given quantum state.
|
|
673
|
+
Modify this value to control the relaxation schedule more
|
|
674
|
+
precisely than the four presets given in schedules 1
|
|
675
|
+
through 4. Allowed values are decimals between 0.1 and 2.
|
|
676
|
+
normalized_loss_rate : int
|
|
677
|
+
an integer value which Sets the amount of loss introduced
|
|
678
|
+
into the system for each loop during the measurement process.
|
|
679
|
+
Modify this value to control the relaxation schedule more
|
|
680
|
+
precisely than the four presets given in schedules 1
|
|
681
|
+
through 4. Allowed values range from 1 to 50.
|
|
682
|
+
|
|
683
|
+
"""
|
|
684
|
+
if sum_constraint is None:
|
|
685
|
+
raise ValueError(
|
|
686
|
+
"sum_constraint must be specified as a positive number"
|
|
687
|
+
)
|
|
688
|
+
if relaxation_schedule not in (1, 2, 3, 4):
|
|
689
|
+
raise ValueError(
|
|
690
|
+
"relaxation_schedule must be one of 1, 2, 3 or 4"
|
|
691
|
+
)
|
|
692
|
+
job_type = "sample-" + self.job_type
|
|
693
|
+
return super().solve(
|
|
694
|
+
model,
|
|
695
|
+
name,
|
|
696
|
+
tags=tags,
|
|
697
|
+
num_samples=num_samples,
|
|
698
|
+
wait=wait,
|
|
699
|
+
job_type=job_type,
|
|
700
|
+
solution_precision=solution_precision,
|
|
701
|
+
sum_constraint=sum_constraint,
|
|
702
|
+
relaxation_schedule=relaxation_schedule,
|
|
703
|
+
mean_photon_number=mean_photon_number,
|
|
704
|
+
normalized_loss_rate=normalized_loss_rate,
|
|
705
|
+
**job_kwargs,
|
|
706
|
+
)
|
|
707
|
+
|