Skip to content

Commit de0e22f

Browse files
Merge branch '8-add-json-backend' into 'main'
Resolve "add json backend" Closes #8 See merge request devel/qubo!9
2 parents 1d72b4f + b9920aa commit de0e22f

6 files changed

Lines changed: 53 additions & 7 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ uv sync --group dev
172172
```
173173
The tests can then be run via `pytest tests\test_qubo.py`
174174

175+
## Utility
176+
For integrating external solvers not natively supported by GAMSPy, you can export the formulated Q matrix and pass the resulting solution vector back as a JSON file with the json "backend". The package will then automatically map this external binary solution to your original model variables.
177+
175178
## GAMS Version
176179

177180
The original tool was developed for the GAMS modeling language. While this repository now focuses on the GAMSPy (Python) implementation, the classic GAMS version remains available in the GAMS subfolder.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from gamspy_qubo.backend.base import baseBackend
22
from gamspy_qubo.backend.dwave import DwaveBackend
33
from gamspy_qubo.backend.kipu import KipuBackend
4+
from gamspy_qubo.backend.load_json import JsonBackend
45

5-
__all__ = ["baseBackend", "DwaveBackend", "KipuBackend"]
6+
__all__ = ["baseBackend", "DwaveBackend", "KipuBackend", "JsonBackend"]

gamspy_qubo/src/gamspy_qubo/backend/dwave.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def solve(self, *args, **kwargs) -> pd.DataFrame:
3434
ut_mat *= multiplier
3535
matrix_dict = {}
3636
rows, cols = ut_mat.nonzero()
37-
for i, j in zip(rows, cols):
37+
for i, j in zip(rows, cols, strict=True):
3838
matrix_dict[(self.q_variables[i], self.q_variables[j])] = ut_mat[i, j]
3939

4040
dimod = importlib.import_module("dimod")

gamspy_qubo/src/gamspy_qubo/backend/kipu.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def solve(self, *args, **kwargs) -> pd.DataFrame:
6161
ut_mat *= multiplier
6262
problem = {}
6363
rows, cols = ut_mat.nonzero()
64-
for i, j in zip(rows, cols):
64+
for i, j in zip(rows, cols, strict=True):
6565
if i == j:
6666
problem[f"({i},)"] = ut_mat[i, j] # diagonal terms
6767
else:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import json
2+
3+
import pandas as pd
4+
5+
from gamspy_qubo.backend import baseBackend
6+
7+
8+
class JsonBackend(baseBackend):
9+
"""
10+
A custom backend that reads a pre-solved QUBO solution from a JSON file.
11+
"""
12+
13+
def __init__(self, q_matrix, q_variables, q_constant, sense):
14+
super().__init__(q_matrix, q_variables, q_constant, sense)
15+
16+
def solve(self, json_path="solution.json", *args, **kwargs) -> pd.DataFrame:
17+
with open(json_path, "r") as f:
18+
sol_dict = json.load(f)
19+
20+
results = []
21+
# q_variables holds the QUBO binary indices (e.g., 'b1', 'b2')
22+
for var in self.q_variables:
23+
# Match the variable name to the JSON keys, defaulting to 0.0 if not found
24+
val = sol_dict.get(var, 0.0)
25+
results.append({"i": var, "level": float(val)})
26+
27+
df = pd.DataFrame(results)
28+
29+
# Provide safe default bounds for the binary variables
30+
df["marginal"] = 0.0
31+
df["lower"] = 0.0
32+
df["upper"] = 1.0
33+
df["scale"] = 1.0
34+
35+
return df

gamspy_qubo/src/gamspy_qubo/qubo.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
from gamspy.exceptions import GamspyException, ValidationError
77

88
from gamspy_qubo import _utils
9-
from gamspy_qubo.backend import DwaveBackend, KipuBackend
9+
from gamspy_qubo.backend import DwaveBackend, JsonBackend, KipuBackend
1010

1111
LOG_LEVEL_DICT = {0: log.WARN, 1: log.INFO, 2: log.DEBUG}
1212
SUPPORTED_QUANTUM_BACKENDS = [
1313
"dwave",
1414
"kipu",
15+
"json",
1516
] # append this list when adding new quantum backend
1617

1718

@@ -595,7 +596,7 @@ def write_qubowl(self) -> None:
595596
non_zero_values = self.Q[non_zero_indices]
596597
with open(f"QMat_{self._modelName}.qs", "w") as fp:
597598
fp.write(f"{self.Q.shape[0]} {len(non_zero_values)} {self.Qconst}\n")
598-
for i, j, value in zip(*non_zero_indices, non_zero_values):
599+
for i, j, value in zip(*non_zero_indices, non_zero_values, strict=True):
599600
if value != 0:
600601
fp.write(f"{i + 1} {j + 1} {value}\n")
601602

@@ -655,8 +656,14 @@ def solve(self, *args, **kwargs) -> pd.DataFrame | None:
655656
optimized_variable_values = self._q_container["x"].records
656657
elif self._backend in SUPPORTED_QUANTUM_BACKENDS:
657658
q_vars: pd.Series = self._q_container["qi"].records["uni"]
658-
_initialize = {"dwave": DwaveBackend, "kipu": KipuBackend}
659-
_backend: DwaveBackend | KipuBackend = _initialize[self._backend](
659+
_initialize = {
660+
"dwave": DwaveBackend,
661+
"kipu": KipuBackend,
662+
"json": JsonBackend,
663+
}
664+
_backend: DwaveBackend | KipuBackend | JsonBackend = _initialize[
665+
self._backend
666+
](
660667
q_matrix=self.Q,
661668
q_variables=q_vars,
662669
q_constant=self.Qconst,

0 commit comments

Comments
 (0)