eqc-models 0.12.0__py3-none-any.whl → 0.14.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.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/base.py +11 -0
- eqc_models-0.14.0.data/platlib/eqc_models/base/binaries.py +33 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/constraints.py +39 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/polyeval.c +443 -267
- eqc_models-0.14.0.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/polynomial.py +21 -2
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/results.py +81 -1
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/__init__.py +2 -0
- eqc_models-0.14.0.data/platlib/eqc_models/graph/rcshortestpath.py +81 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/shortestpath.py +52 -19
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/classifierbase.py +0 -5
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/classifierqboost.py +115 -72
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/clustering.py +29 -1
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/clusteringbase.py +29 -7
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/decomposition.py +50 -11
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/regressor.py +14 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/regressorbase.py +24 -6
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/reservoir.py +17 -2
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/solvers/__init__.py +9 -1
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/solvers/eqcdirect.py +27 -6
- eqc_models-0.14.0.data/platlib/eqc_models/solvers/mip.py +115 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/solvers/qciclient.py +11 -22
- eqc_models-0.14.0.data/platlib/eqc_models/solvers/responselog.py +47 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/__init__.py +2 -1
- eqc_models-0.14.0.data/platlib/eqc_models/utilities/general.py +83 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/METADATA +10 -9
- eqc_models-0.14.0.dist-info/RECORD +70 -0
- eqc_models-0.12.0.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- eqc_models-0.12.0.dist-info/RECORD +0 -65
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/compile_extensions.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/algorithms/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/algorithms/base.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/algorithms/penaltymultiplier.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/allocation.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/portbase.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/allocation/portmomentum.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/qap.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/resource.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/assignment/setpartition.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/operators.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/polyeval.pyx +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/base/quadratic.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/combinatorics/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/combinatorics/setcover.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/combinatorics/setpartition.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/decoding.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/base.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/hypergraph.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/maxcut.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/maxkcut.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/graph/partition.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/classifierqsvm.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian.pyx +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.c +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.h +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/forecast.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/ml/forecastbase.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/process/base.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/process/mpc.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/sequence/__init__.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/sequence/tsp.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/fileio.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/polynomial.py +0 -0
- {eqc_models-0.12.0.data → eqc_models-0.14.0.data}/platlib/eqc_models/utilities/qplib.py +0 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/WHEEL +0 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/licenses/LICENSE.txt +0 -0
- {eqc_models-0.12.0.dist-info → eqc_models-0.14.0.dist-info}/top_level.txt +0 -0
|
@@ -45,6 +45,12 @@ class LinearRegression(RegressorBase):
|
|
|
45
45
|
|
|
46
46
|
num_samples: Number of samples used by Dirac-3; default: 1.
|
|
47
47
|
|
|
48
|
+
solver_access: Solver access type: cloud or direct; default: cloud.
|
|
49
|
+
|
|
50
|
+
ip_addr: IP address of the device when direct access is used; default: None.
|
|
51
|
+
|
|
52
|
+
port: Port number of the device when direct access is used; default: None.
|
|
53
|
+
|
|
48
54
|
l2_reg_coef: L2 regularization penalty multiplier; default: 0.
|
|
49
55
|
|
|
50
56
|
alpha: A penalty multiplier to ensure the correct sign of a
|
|
@@ -72,13 +78,21 @@ class LinearRegression(RegressorBase):
|
|
|
72
78
|
self,
|
|
73
79
|
relaxation_schedule=2,
|
|
74
80
|
num_samples=1,
|
|
81
|
+
solver_access="cloud",
|
|
82
|
+
ip_addr=None,
|
|
83
|
+
port=None,
|
|
75
84
|
l2_reg_coef=0,
|
|
76
85
|
alpha=0,
|
|
77
86
|
):
|
|
78
87
|
super(LinearRegression).__init__()
|
|
79
88
|
|
|
89
|
+
assert solver_access in ["cloud", "direct"]
|
|
90
|
+
|
|
80
91
|
self.relaxation_schedule = relaxation_schedule
|
|
81
92
|
self.num_samples = num_samples
|
|
93
|
+
self.solver_access = solver_access
|
|
94
|
+
self.ip_addr = ip_addr
|
|
95
|
+
self.port = port
|
|
82
96
|
self.l2_reg_coef = l2_reg_coef
|
|
83
97
|
self.alpha = alpha
|
|
84
98
|
self.params = None
|
|
@@ -11,19 +11,27 @@ import numpy as np
|
|
|
11
11
|
|
|
12
12
|
from eqc_models import QuadraticModel
|
|
13
13
|
from eqc_models.solvers.qciclient import Dirac3CloudSolver
|
|
14
|
-
|
|
14
|
+
from eqc_models.solvers.eqcdirect import Dirac3DirectSolver
|
|
15
15
|
|
|
16
16
|
class RegressorBase(QuadraticModel):
|
|
17
17
|
def __init__(
|
|
18
18
|
self,
|
|
19
19
|
relaxation_schedule=2,
|
|
20
20
|
num_samples=1,
|
|
21
|
+
solver_access="cloud",
|
|
22
|
+
ip_addr=None,
|
|
23
|
+
port=None,
|
|
21
24
|
):
|
|
22
25
|
|
|
23
26
|
super(self).__init__(None, None, None)
|
|
27
|
+
|
|
28
|
+
assert solver_access in ["cloud", "direct"]
|
|
24
29
|
|
|
25
30
|
self.relaxation_schedule = relaxation_schedule
|
|
26
31
|
self.num_samples = num_samples
|
|
32
|
+
self.solver_access = solver_access
|
|
33
|
+
self.ip_addr = ip_addr
|
|
34
|
+
self.port = port
|
|
27
35
|
self.params = None
|
|
28
36
|
|
|
29
37
|
def predict(self, X: np.array):
|
|
@@ -51,18 +59,28 @@ class RegressorBase(QuadraticModel):
|
|
|
51
59
|
return
|
|
52
60
|
|
|
53
61
|
def solve(self):
|
|
54
|
-
|
|
62
|
+
if self.solver_access == "direct":
|
|
63
|
+
solver = Dirac3DirectSolver()
|
|
64
|
+
solver.connect(self.ip_addr, self.port)
|
|
65
|
+
else:
|
|
66
|
+
solver = Dirac3CloudSolver()
|
|
67
|
+
|
|
55
68
|
response = solver.solve(
|
|
56
69
|
self,
|
|
57
70
|
sum_constraint=self._sum_constraint,
|
|
58
71
|
relaxation_schedule=self.relaxation_schedule,
|
|
59
|
-
solution_precision=1,
|
|
60
72
|
num_samples=self.num_samples,
|
|
61
73
|
)
|
|
62
74
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
if self.solver_access == "cloud":
|
|
76
|
+
energies = response["results"]["energies"]
|
|
77
|
+
solutions = response["results"]["solutions"]
|
|
78
|
+
elif self.solver_access == "direct":
|
|
79
|
+
energies = response["energy"]
|
|
80
|
+
solutions = response["solution"]
|
|
81
|
+
|
|
82
|
+
min_id = np.argmin(energies)
|
|
83
|
+
sol = solutions[min_id]
|
|
66
84
|
|
|
67
85
|
print(response)
|
|
68
86
|
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# (C) Quantum Computing Inc., 2025.
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from emucore_direct.client import EmuCoreClient
|
|
6
|
+
except ModuleNotFoundError:
|
|
7
|
+
# Only warn here to try to disrupt package behavior as least as possible.
|
|
8
|
+
logging.warning("emucore-direct package not available")
|
|
9
|
+
|
|
10
|
+
class EmuCoreClient:
|
|
11
|
+
"""This is a stub for unsupported EmuCoreClient."""
|
|
12
|
+
|
|
13
|
+
def __init__(*_, **__) -> None:
|
|
14
|
+
"""Raise exception when client cannot be created."""
|
|
15
|
+
raise ModuleNotFoundError(
|
|
16
|
+
"emucore-direct package not available, likely because Python version is 3.11+"
|
|
17
|
+
)
|
|
3
18
|
|
|
4
19
|
# Parameters
|
|
5
20
|
VBIAS = 0.31
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
from .eqcdirect import Dirac3DirectSolver
|
|
3
3
|
from .qciclient import (Dirac1CloudSolver, Dirac3CloudSolver, QciClientSolver,
|
|
4
4
|
Dirac3IntegerCloudSolver, Dirac3ContinuousCloudSolver)
|
|
5
|
+
from .mip import MIPMixin
|
|
6
|
+
|
|
7
|
+
class Dirac3MIPCloudSolver(MIPMixin, Dirac3ContinuousCloudSolver):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
class Dirac3MIPDirectSolver(MIPMixin, Dirac3DirectSolver):
|
|
11
|
+
pass
|
|
5
12
|
|
|
6
13
|
__all__ = ["Dirac3DirectSolver", "Dirac1CloudSolver", "Dirac3CloudSolver",
|
|
7
14
|
"EqcDirectSolver", "QciClientSolver", "Dirac3IntegerCloudSolver",
|
|
8
|
-
"Dirac3ContinuousCloudSolver"
|
|
15
|
+
"Dirac3ContinuousCloudSolver", "MILPMixin",
|
|
16
|
+
"Dirac3MIPCloudSolver", "Dirac3MIPDirectSolver"]
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
# (C) Quantum Computing Inc., 2025.
|
|
2
2
|
import logging
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from eqc_direct.client import EqcClient
|
|
7
|
+
except ModuleNotFoundError:
|
|
8
|
+
# Only warn here to try to disrupt package behavior as least as possible.
|
|
9
|
+
logging.warning("eqc-direct package not available")
|
|
10
|
+
|
|
11
|
+
class EqcClient:
|
|
12
|
+
"""This is a stub for unsupported EqcClient."""
|
|
13
|
+
|
|
14
|
+
def __init__(*_, **__) -> None:
|
|
15
|
+
"""Raise exception when client cannot be created."""
|
|
16
|
+
raise ModuleNotFoundError(
|
|
17
|
+
"eqc-direct package not available, likely because Python version is 3.11+"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from eqc_models.base.base import EqcModel, ModelSolver
|
|
6
21
|
from eqc_models.base.results import SolutionResults
|
|
7
22
|
|
|
8
23
|
log = logging.getLogger(name=__name__)
|
|
@@ -19,7 +34,6 @@ class Dirac3DirectSolver(ModelSolver):
|
|
|
19
34
|
sum_constraint,
|
|
20
35
|
relaxation_schedule=1,
|
|
21
36
|
num_samples=1,
|
|
22
|
-
solution_precision=None,
|
|
23
37
|
mean_photon_number=None,
|
|
24
38
|
quantum_fluctuation_coefficient=None,
|
|
25
39
|
):
|
|
@@ -30,7 +44,8 @@ class Dirac3DirectSolver(ModelSolver):
|
|
|
30
44
|
for i in range(len(indices)):
|
|
31
45
|
if max(indices[i]) > num_variables:
|
|
32
46
|
num_variables = max(indices[i])
|
|
33
|
-
|
|
47
|
+
# add machine slacks
|
|
48
|
+
num_variables += model.machine_slacks
|
|
34
49
|
print("Num variables:", num_variables)
|
|
35
50
|
|
|
36
51
|
client = self.client
|
|
@@ -49,7 +64,6 @@ class Dirac3DirectSolver(ModelSolver):
|
|
|
49
64
|
lock_id=lock_id,
|
|
50
65
|
relaxation_schedule=relaxation_schedule,
|
|
51
66
|
sum_constraint=sum_constraint,
|
|
52
|
-
solution_precision=solution_precision,
|
|
53
67
|
mean_photon_number=mean_photon_number,
|
|
54
68
|
quantum_fluctuation_coefficient=quantum_fluctuation_coefficient,
|
|
55
69
|
)
|
|
@@ -68,4 +82,11 @@ class Dirac3DirectSolver(ModelSolver):
|
|
|
68
82
|
|
|
69
83
|
@property
|
|
70
84
|
def client(self) -> EqcClient:
|
|
85
|
+
"""Return a new client from eqc-direct based on class config."""
|
|
86
|
+
|
|
71
87
|
return EqcClient(self.ip_addr, self.port, cert_file=self.cert_file)
|
|
88
|
+
|
|
89
|
+
def makeResults(self, model : EqcModel, response : Dict):
|
|
90
|
+
""" Builds the results object """
|
|
91
|
+
|
|
92
|
+
return SolutionResults.from_eqcdirect_response(model, response, self)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import numpy as np
|
|
3
|
+
from eqc_models.base.base import EqcModel
|
|
4
|
+
from eqc_models.base.binaries import make_binary_penalty
|
|
5
|
+
from eqc_models.base.polynomial import PolynomialModel
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(name=__name__)
|
|
8
|
+
|
|
9
|
+
class MIPMixin:
|
|
10
|
+
"""
|
|
11
|
+
Implements a solve method which intercepts the operator and builds a new
|
|
12
|
+
model with added terms for restriction of discrete variables with an
|
|
13
|
+
upper bound of 1 to take on only values 0 or 1 at minima.
|
|
14
|
+
|
|
15
|
+
Following the submission of a new model with the added penalties,
|
|
16
|
+
the solutions are updated to exclude added slack variables.
|
|
17
|
+
|
|
18
|
+
This is only supported with continuous-capable devices.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def solve(self, model : EqcModel, *args, **kwargs):
|
|
23
|
+
if model.is_discrete is None:
|
|
24
|
+
raise ValueError("Model solved with an MIP solver must have certain variables labeled discrete")
|
|
25
|
+
elif len([b for b in model.is_discrete if b])==0:
|
|
26
|
+
raise ValueError("Model solved with an MIP solver must have certain variables labeled discrete")
|
|
27
|
+
if kwargs.get("sum_constraint", None) is None:
|
|
28
|
+
raise ValueError("sum_constraint must be specified for MIP model sampling")
|
|
29
|
+
# get a polynomial
|
|
30
|
+
poly = model.polynomial
|
|
31
|
+
if hasattr(poly.coefficients, "tolist"):
|
|
32
|
+
coefficients = poly.coefficients.tolist()
|
|
33
|
+
else:
|
|
34
|
+
coefficients = list(poly.coefficients)
|
|
35
|
+
indices = poly.indices
|
|
36
|
+
old_n = model.n
|
|
37
|
+
log.debug("Model coefficients %d", len(coefficients))
|
|
38
|
+
log.debug("Model indices %d", len(indices))
|
|
39
|
+
log.debug("Model size %d", old_n)
|
|
40
|
+
if "penalty_multiplier" in kwargs:
|
|
41
|
+
penalty_multiplier = kwargs["penalty_multiplier"]
|
|
42
|
+
del kwargs["penalty_multiplier"]
|
|
43
|
+
else:
|
|
44
|
+
penalty_multiplier = getattr(model, "penalty_multiplier", 1)
|
|
45
|
+
log.debug("Binary enforcement penalty multiplier %f", penalty_multiplier)
|
|
46
|
+
bin_slacks = []
|
|
47
|
+
offset = 0
|
|
48
|
+
variables = model.variables
|
|
49
|
+
addtl_upper_bound = []
|
|
50
|
+
for i, v in enumerate(model.variables):
|
|
51
|
+
if model.is_discrete[i]:
|
|
52
|
+
bin_slacks.append(f"bin_slacks_{v}")
|
|
53
|
+
penalty_coeff, penalty_indices, penalty_offset = make_binary_penalty(i+1, len(variables)+len(bin_slacks), penalty_multiplier=penalty_multiplier)
|
|
54
|
+
log.debug("Adding penalties coeff: %s indices %s offset %s", penalty_coeff, penalty_indices, penalty_offset)
|
|
55
|
+
coefficients += penalty_coeff
|
|
56
|
+
indices += penalty_indices
|
|
57
|
+
offset += penalty_offset
|
|
58
|
+
addtl_upper_bound.append(1)
|
|
59
|
+
assert len(coefficients) == len(indices)
|
|
60
|
+
variables = variables + bin_slacks
|
|
61
|
+
log.debug("New model binary penalty slacks: %s", bin_slacks)
|
|
62
|
+
new_model = PolynomialModel(coefficients, indices)
|
|
63
|
+
new_model.variables = variables
|
|
64
|
+
log.debug("New model variables: %s", variables)
|
|
65
|
+
new_model.upper_bound = np.array(model.upper_bound.tolist() + addtl_upper_bound)
|
|
66
|
+
new_model.machine_slacks = model.machine_slacks
|
|
67
|
+
coefficients, indices = new_model.H
|
|
68
|
+
log.debug("New model coefficients %d", len(coefficients))
|
|
69
|
+
log.debug("New model indices %d", len(indices))
|
|
70
|
+
log.debug("New model size: %d", new_model.n)
|
|
71
|
+
response = super().solve(new_model, *args, **kwargs)
|
|
72
|
+
# translate the response into results
|
|
73
|
+
results = self.makeResults(new_model, response)
|
|
74
|
+
log.debug(results)
|
|
75
|
+
# update the results to relect the original model
|
|
76
|
+
solutions = results.solutions
|
|
77
|
+
for i in range(len(solutions)):
|
|
78
|
+
log.debug("SolutionResults solution: %s", solutions[i])
|
|
79
|
+
if hasattr(model, "evaluateObjective"):
|
|
80
|
+
new_objectives = []
|
|
81
|
+
else:
|
|
82
|
+
new_objectives = None
|
|
83
|
+
if hasattr(model, "evaluatePenalties"):
|
|
84
|
+
new_penalties = []
|
|
85
|
+
else:
|
|
86
|
+
new_penalties = None
|
|
87
|
+
new_solutions = []
|
|
88
|
+
new_energies = []
|
|
89
|
+
num_vars = len(solutions[0]) - len(bin_slacks)
|
|
90
|
+
log.debug("Num_vars %f", num_vars)
|
|
91
|
+
machine_slacks = model.machine_slacks
|
|
92
|
+
for i, solution in enumerate(solutions):
|
|
93
|
+
log.debug("%d - Raw solution %s", i, solution)
|
|
94
|
+
new_sol = [v for v in solution[:num_vars]]
|
|
95
|
+
new_sol = np.array(new_sol)
|
|
96
|
+
if machine_slacks > 0:
|
|
97
|
+
new_sol[-machine_slacks:] = solution[-machine_slacks:]
|
|
98
|
+
log.debug("%d - New solution %s", i, new_sol)
|
|
99
|
+
if new_objectives is not None:
|
|
100
|
+
try:
|
|
101
|
+
new_objectives.append(model.evaluateObjective(new_sol))
|
|
102
|
+
except NotImplementedError as err:
|
|
103
|
+
pass
|
|
104
|
+
if new_penalties is not None:
|
|
105
|
+
try:
|
|
106
|
+
new_penalties.append(model.evaluatePenalties(new_sol))
|
|
107
|
+
except NotImplementedError as err:
|
|
108
|
+
pass
|
|
109
|
+
new_solutions.append(new_sol)
|
|
110
|
+
new_energies.append(model.evaluate(new_sol))
|
|
111
|
+
results.solutions = new_solutions
|
|
112
|
+
results.penalties = new_penalties
|
|
113
|
+
results.objectives = new_objectives
|
|
114
|
+
results.energies = new_energies
|
|
115
|
+
return results
|
|
@@ -6,6 +6,7 @@ import numpy as np
|
|
|
6
6
|
from qci_client import QciClient
|
|
7
7
|
from eqc_models.base.base import ModelSolver, EqcModel
|
|
8
8
|
from eqc_models.base.operators import OperatorNotAvailableError
|
|
9
|
+
from eqc_models.base.results import SolutionResults
|
|
9
10
|
|
|
10
11
|
log = logging.getLogger(name=__name__)
|
|
11
12
|
|
|
@@ -77,6 +78,11 @@ class QciClientMixin:
|
|
|
77
78
|
metrics = client.get_job_metrics(job_id=job_id)
|
|
78
79
|
return metrics
|
|
79
80
|
|
|
81
|
+
def makeResults(self, model : EqcModel, response : Dict) -> SolutionResults:
|
|
82
|
+
""" Build the results object """
|
|
83
|
+
|
|
84
|
+
return SolutionResults.from_cloud_response(model, response, self)
|
|
85
|
+
|
|
80
86
|
class Dirac1Mixin:
|
|
81
87
|
sampler_type = "dirac-1"
|
|
82
88
|
requires_operator = "qubo"
|
|
@@ -125,7 +131,6 @@ class Dirac3Mixin:
|
|
|
125
131
|
max_upper_bound = 10000
|
|
126
132
|
job_params_names = [
|
|
127
133
|
"num_samples",
|
|
128
|
-
"solution_precision",
|
|
129
134
|
"relaxation_schedule",
|
|
130
135
|
"mean_photon_number",
|
|
131
136
|
"quantum_fluctuation_coefficient",
|
|
@@ -427,10 +432,9 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
427
432
|
Dirac3CloudSolver is a class that encapsulates the different calls to Qatalyst
|
|
428
433
|
for Dirac-3 jobs. Currently, there are two different jobs, one for integer and
|
|
429
434
|
another for continuous solutions. Calling the solve method with different arguments
|
|
430
|
-
controls which job is submitted. The continuous job requires :code:`sum_constraint
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
the job type to be continuous and not specifying it results in the integer job being
|
|
435
|
+
controls which job is submitted. The continuous job requires :code:`sum_constraint`.
|
|
436
|
+
The integer job does not accept this parameter, so specifying a sum constraint forces
|
|
437
|
+
the job type to be continuous, and not specifying it results in the integer job being
|
|
434
438
|
called.
|
|
435
439
|
|
|
436
440
|
Continuous Solver
|
|
@@ -454,8 +458,7 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
454
458
|
>>> model = QuadraticModel(C, J)
|
|
455
459
|
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
456
460
|
>>> solver = Dirac3CloudSolver()
|
|
457
|
-
>>> response = solver.solve(model, sum_constraint=1, relaxation_schedule=1,
|
|
458
|
-
... solution_precision=None) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
461
|
+
>>> response = solver.solve(model, sum_constraint=1, relaxation_schedule=1) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
459
462
|
2... submitted... COMPLETED...
|
|
460
463
|
>>> response["results"]["energies"][0] <= 1.0
|
|
461
464
|
True
|
|
@@ -479,7 +482,6 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
479
482
|
tags: List = None,
|
|
480
483
|
sum_constraint: float = None,
|
|
481
484
|
relaxation_schedule: int = None,
|
|
482
|
-
solution_precision: float = None,
|
|
483
485
|
num_samples: int = 1,
|
|
484
486
|
wait: bool = True,
|
|
485
487
|
mean_photon_number: float = None,
|
|
@@ -503,11 +505,6 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
503
505
|
a predefined schedule indicator which sets parameters
|
|
504
506
|
on the device to control the sampling through photon
|
|
505
507
|
measurement
|
|
506
|
-
solution_precision : float
|
|
507
|
-
a value which, when not None, indicates the numerical
|
|
508
|
-
precision desired in the solution: 1 for integer, 0.1
|
|
509
|
-
for tenths place, 0.01 for hundreths and None for raw,
|
|
510
|
-
only used with continuous solver
|
|
511
508
|
num_samples : int
|
|
512
509
|
the number of samples to take, defaults to 1
|
|
513
510
|
wait : bool
|
|
@@ -540,7 +537,6 @@ class Dirac3CloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
540
537
|
job_kwargs["relaxation_schedule"] = relaxation_schedule
|
|
541
538
|
job_kwargs["mean_photon_number"] = mean_photon_number
|
|
542
539
|
job_kwargs["quantum_fluctuation_coefficient"] = quantum_fluctuation_coefficient
|
|
543
|
-
job_kwargs["solution_precision"] = solution_precision
|
|
544
540
|
job_type = "sample-" + self.job_type
|
|
545
541
|
return super().solve(
|
|
546
542
|
model,
|
|
@@ -663,8 +659,7 @@ class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
663
659
|
>>> model = QuadraticModel(C, J)
|
|
664
660
|
>>> model.upper_bound = np.array([1, 1]) # set the domain maximum per variable
|
|
665
661
|
>>> solver = Dirac3ContinuousCloudSolver()
|
|
666
|
-
>>> response = solver.solve(model, sum_constraint=1, relaxation_schedule=1,
|
|
667
|
-
... solution_precision=None) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
662
|
+
>>> response = solver.solve(model, sum_constraint=1, relaxation_schedule=1) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
668
663
|
2... submitted... COMPLETED...
|
|
669
664
|
>>> response["results"]["energies"][0] <= 1.0
|
|
670
665
|
True
|
|
@@ -680,7 +675,6 @@ class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
680
675
|
tags: List = None,
|
|
681
676
|
sum_constraint: float = None,
|
|
682
677
|
relaxation_schedule: int = None,
|
|
683
|
-
solution_precision: float = None,
|
|
684
678
|
num_samples: int = 1,
|
|
685
679
|
wait: bool = True,
|
|
686
680
|
mean_photon_number: float = None,
|
|
@@ -703,10 +697,6 @@ class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
703
697
|
a predefined schedule indicator which sets parameters
|
|
704
698
|
on the device to control the sampling through photon
|
|
705
699
|
measurement
|
|
706
|
-
solution_precision : float
|
|
707
|
-
a value which, when not None, indicates the numerical
|
|
708
|
-
precision desired in the solution: 1 for integer, 0.1
|
|
709
|
-
for tenths place, 0.01 for hundreths and None for raw
|
|
710
700
|
num_samples : int
|
|
711
701
|
the number of samples to take, defaults to 1
|
|
712
702
|
wait : bool
|
|
@@ -744,7 +734,6 @@ class Dirac3ContinuousCloudSolver(Dirac3Mixin, QciClientSolver):
|
|
|
744
734
|
num_samples=num_samples,
|
|
745
735
|
wait=wait,
|
|
746
736
|
job_type=job_type,
|
|
747
|
-
solution_precision=solution_precision,
|
|
748
737
|
sum_constraint=sum_constraint,
|
|
749
738
|
relaxation_schedule=relaxation_schedule,
|
|
750
739
|
mean_photon_number=mean_photon_number,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
class ResponseLogMixin:
|
|
6
|
+
|
|
7
|
+
def logResponse(self, response, model, metrics=None):
|
|
8
|
+
record = {
|
|
9
|
+
"response": response,
|
|
10
|
+
"metrics": metrics,
|
|
11
|
+
"size": model.n,
|
|
12
|
+
"name": model.__class__.__name__,
|
|
13
|
+
"upper_bound": [u for u in model.upper_bound],
|
|
14
|
+
"machine_slacks": model.machine_slacks,
|
|
15
|
+
"penalty_multiplier": getattr(model, "penalty_multiplier", None)
|
|
16
|
+
}
|
|
17
|
+
fname = f"response-{time.time()}.json"
|
|
18
|
+
dirname = self.getLogDir()
|
|
19
|
+
fullname = os.path.join(dirname, fname)
|
|
20
|
+
if not os.access(fullname, os.W_OK):
|
|
21
|
+
log.warn(f"Response will not be logged because {fullname} is not writable")
|
|
22
|
+
return
|
|
23
|
+
elif os.path.exists(fullname):
|
|
24
|
+
log.warn(f"Response will not be logged because {fullname} exists")
|
|
25
|
+
return
|
|
26
|
+
with open(fullname, "w") as fp:
|
|
27
|
+
log.debug(f"Wrote response to {fullname}")
|
|
28
|
+
json.dump(record, fp)
|
|
29
|
+
|
|
30
|
+
def getLogDir(self):
|
|
31
|
+
""" Ensure the logging directory exists and return the path """
|
|
32
|
+
|
|
33
|
+
dirname = os.path.expanduser("~/.eqc-models")
|
|
34
|
+
if not os.path.exists(dirname):
|
|
35
|
+
try:
|
|
36
|
+
os.mkdir(dirname)
|
|
37
|
+
except OSError:
|
|
38
|
+
log.warn(f"Responses will not be logged because {dirname} is not writable")
|
|
39
|
+
return None
|
|
40
|
+
dirname = os.path.join(dirname, "responses")
|
|
41
|
+
if not os.path.exists(dirname):
|
|
42
|
+
try>
|
|
43
|
+
os.mkdir(dirname)
|
|
44
|
+
except OSError:
|
|
45
|
+
log.warn(f"Responses will not be logged because {dirname} is not writable")
|
|
46
|
+
return None
|
|
47
|
+
return dirname
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# (C) Quantum Computing Inc., 2024.
|
|
2
2
|
from .polynomial import evaluate_polynomial, convert_hamiltonian_to_polynomial
|
|
3
3
|
from .fileio import read_coefficient_file, read_config_file, read_index_file
|
|
4
|
+
from .general import create_json_problem
|
|
4
5
|
|
|
5
6
|
__all__ = ["evaluate_polynomial",
|
|
6
|
-
"read_coefficient_file", "read_index_file", "read_config_file", "convert_hamiltonian_to_polynomial"]
|
|
7
|
+
"read_coefficient_file", "read_index_file", "read_config_file", "convert_hamiltonian_to_polynomial", "create_json_problem"]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# (C) Quantum Computing Inc., 2024.
|
|
2
|
+
import numpy as np
|
|
3
|
+
from eqc_models.utilities.polynomial import convert_hamiltonian_to_polynomial
|
|
4
|
+
|
|
5
|
+
def create_json_problem(
|
|
6
|
+
A: np.array,
|
|
7
|
+
B: np.array,
|
|
8
|
+
C: np.array,
|
|
9
|
+
D: np.array,
|
|
10
|
+
num_vars: int,
|
|
11
|
+
sum_constraint: float = None,
|
|
12
|
+
num_levels: int = None,
|
|
13
|
+
):
|
|
14
|
+
|
|
15
|
+
"""Converts a hamiltonian of up to fourth order to a polynomial.
|
|
16
|
+
|
|
17
|
+
D_{ijkl} x_i x_j x_k x_l + C_{ijk} x_i x_j x_k + B_{ij} x_i x_j
|
|
18
|
+
+ A_i x_i
|
|
19
|
+
|
|
20
|
+
Input:
|
|
21
|
+
|
|
22
|
+
A: First order hamiltonian.
|
|
23
|
+
B: Second order hamiltonian.
|
|
24
|
+
C: Third order hamiltonian.
|
|
25
|
+
D: Fourth order hamiltonian.
|
|
26
|
+
num_vars: Number of variables.
|
|
27
|
+
|
|
28
|
+
Output:
|
|
29
|
+
|
|
30
|
+
Problem in json format.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
if D is not None:
|
|
35
|
+
assert len(D.shape) == 4, "Incorrect shape!"
|
|
36
|
+
assert D.shape[0] == num_vars, "Inconsistent dimensions!"
|
|
37
|
+
assert D.shape[1] == num_vars, "Inconsistent dimensions!"
|
|
38
|
+
assert D.shape[2] == num_vars, "Inconsistent dimensions!"
|
|
39
|
+
assert D.shape[3] == num_vars, "Inconsistent dimensions!"
|
|
40
|
+
degree = 4
|
|
41
|
+
elif C is not None:
|
|
42
|
+
assert len(C.shape) == 3, "Incorrect shape!"
|
|
43
|
+
assert C.shape[0] == num_vars, "Inconsistent dimensions!"
|
|
44
|
+
assert C.shape[1] == num_vars, "Inconsistent dimensions!"
|
|
45
|
+
assert C.shape[2] == num_vars, "Inconsistent dimensions!"
|
|
46
|
+
degree = 3
|
|
47
|
+
elif B is not None:
|
|
48
|
+
assert len(B.shape) == 2, "Incorrect shape!"
|
|
49
|
+
assert B.shape[0] == num_vars, "Inconsistent dimensions!"
|
|
50
|
+
assert B.shape[1] == num_vars, "Inconsistent dimensions!"
|
|
51
|
+
degree = 2
|
|
52
|
+
elif A is not None:
|
|
53
|
+
assert len(A.shape) in [1, 2], "Incorrect shape!"
|
|
54
|
+
if len(A.shape) == 2:
|
|
55
|
+
if A.shape[1] == 1:
|
|
56
|
+
A = A.reshape((A.shape[0]))
|
|
57
|
+
else:
|
|
58
|
+
assert False, "Incorrect shape!"
|
|
59
|
+
|
|
60
|
+
assert A.shape[0] == num_vars, "Inconsistent dimensions!"
|
|
61
|
+
|
|
62
|
+
degree = 1
|
|
63
|
+
else:
|
|
64
|
+
assert False, "No hamiltonian provided!"
|
|
65
|
+
|
|
66
|
+
poly_indices, poly_coefs = convert_hamiltonian_to_polynomial(
|
|
67
|
+
A, B, C, D, num_vars,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
json_object = {
|
|
71
|
+
"degree": degree,
|
|
72
|
+
"num_variables": num_vars,
|
|
73
|
+
"poly_indices": poly_indices,
|
|
74
|
+
"poly_coefficients": poly_coefs,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if num_levels is not None:
|
|
78
|
+
json_object["levels"] = [num_levels] * num_vars
|
|
79
|
+
|
|
80
|
+
if sum_constraint is not None:
|
|
81
|
+
json_object["sum_constraint"] = sum_constraint
|
|
82
|
+
|
|
83
|
+
return json_object
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eqc-models
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: Optimization and ML modeling package targeting EQC devices
|
|
5
|
-
Author: Quantum Computing Inc.
|
|
6
|
-
Author-email: support@quantumcomputinginc.com
|
|
5
|
+
Author-email: "Quantum Computing Inc." <support@quantumcomputinginc.com>
|
|
7
6
|
Project-URL: Homepage, https://quantumcomputinginc.com
|
|
8
7
|
Project-URL: Documentation, https://quantumcomputinginc.com/learn/support/software-packages/
|
|
9
8
|
Project-URL: Issues, https://support.quantumcomputinginc.com/
|
|
10
|
-
Requires-Python: <3.
|
|
9
|
+
Requires-Python: <3.14,>=3.9
|
|
11
10
|
Description-Content-Type: text/markdown
|
|
12
11
|
License-File: LICENSE.txt
|
|
12
|
+
Requires-Dist: emucore-direct==1.0.7; python_version < "3.11"
|
|
13
|
+
Requires-Dist: eqc-direct==2.0.2; python_version < "3.11"
|
|
13
14
|
Requires-Dist: numpy<2,>=1.22.1
|
|
14
15
|
Requires-Dist: networkx<3,>=2.6.3
|
|
15
|
-
Requires-Dist: pandas
|
|
16
|
-
Requires-Dist: scikit-learn
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist:
|
|
16
|
+
Requires-Dist: pandas<3,>=2.1.0
|
|
17
|
+
Requires-Dist: scikit-learn<2,>=1.2.1
|
|
18
|
+
Requires-Dist: lightgbm<5,>=4.6.0
|
|
19
|
+
Requires-Dist: xgboost<2,>=1.7.4
|
|
20
|
+
Requires-Dist: qci-client<6,>=5
|
|
20
21
|
Provides-Extra: dev
|
|
21
22
|
Requires-Dist: pytest<8,>=7.1.0; extra == "dev"
|
|
22
23
|
Requires-Dist: pytest-cov; extra == "dev"
|