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.
Files changed (72) hide show
  1. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/__init__.py +2 -1
  2. eqc_models-0.11.0.data/platlib/eqc_models/assignment/setpartition.py +4 -0
  3. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/__init__.py +3 -1
  4. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/base.py +44 -4
  5. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/polyeval.c +163 -163
  6. eqc_models-0.11.0.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
  7. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/polynomial.py +7 -3
  8. eqc_models-0.11.0.data/platlib/eqc_models/base/results.py +94 -0
  9. eqc_models-0.11.0.data/platlib/eqc_models/combinatorics/__init__.py +6 -0
  10. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/combinatorics/setcover.py +34 -17
  11. {eqc_models-0.10.1.data/platlib/eqc_models/assignment → eqc_models-0.11.0.data/platlib/eqc_models/combinatorics}/setpartition.py +2 -0
  12. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/maxkcut.py +26 -68
  13. eqc_models-0.11.0.data/platlib/eqc_models/sequence/scheduling.py +29 -0
  14. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/solvers/qciclient.py +11 -2
  15. {eqc_models-0.10.1.dist-info → eqc_models-0.11.0.dist-info}/METADATA +3 -2
  16. eqc_models-0.11.0.dist-info/RECORD +61 -0
  17. {eqc_models-0.10.1.dist-info → eqc_models-0.11.0.dist-info}/WHEEL +1 -1
  18. eqc_models-0.10.1.data/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
  19. eqc_models-0.10.1.data/platlib/eqc_models/base.py +0 -115
  20. eqc_models-0.10.1.data/platlib/eqc_models/combinatorics/__init__.py +0 -5
  21. eqc_models-0.10.1.data/platlib/eqc_models/communitydetection.py +0 -25
  22. eqc_models-0.10.1.data/platlib/eqc_models/eqcdirectsolver.py +0 -61
  23. eqc_models-0.10.1.data/platlib/eqc_models/graphs.py +0 -28
  24. eqc_models-0.10.1.data/platlib/eqc_models/maxcut.py +0 -113
  25. eqc_models-0.10.1.data/platlib/eqc_models/maxkcut.py +0 -185
  26. eqc_models-0.10.1.data/platlib/eqc_models/quadraticmodel.py +0 -131
  27. eqc_models-0.10.1.data/platlib/eqc_models/solvers/eqcdirect.py +0 -160
  28. eqc_models-0.10.1.dist-info/RECORD +0 -66
  29. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/compile_extensions.py +0 -0
  30. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/algorithms/__init__.py +0 -0
  31. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/algorithms/base.py +0 -0
  32. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/algorithms/penaltymultiplier.py +0 -0
  33. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/__init__.py +0 -0
  34. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/allocation.py +0 -0
  35. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/portbase.py +0 -0
  36. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/allocation/portmomentum.py +0 -0
  37. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/assignment/__init__.py +0 -0
  38. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/assignment/qap.py +0 -0
  39. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/constraints.py +0 -0
  40. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/operators.py +0 -0
  41. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/polyeval.pyx +0 -0
  42. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/base/quadratic.py +0 -0
  43. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/decoding.py +0 -0
  44. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/__init__.py +0 -0
  45. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/base.py +0 -0
  46. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/hypergraph.py +0 -0
  47. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/maxcut.py +0 -0
  48. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/graph/partition.py +0 -0
  49. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/__init__.py +0 -0
  50. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/classifierbase.py +0 -0
  51. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/classifierqboost.py +0 -0
  52. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/classifierqsvm.py +0 -0
  53. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/clustering.py +0 -0
  54. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/clusteringbase.py +0 -0
  55. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian.pyx +0 -0
  56. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.c +0 -0
  57. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.h +0 -0
  58. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/decomposition.py +0 -0
  59. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/forecast.py +0 -0
  60. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/forecastbase.py +0 -0
  61. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/regressor.py +0 -0
  62. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/regressorbase.py +0 -0
  63. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/ml/reservoir.py +0 -0
  64. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/sequence/__init__.py +0 -0
  65. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/sequence/tsp.py +0 -0
  66. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/solvers/__init__.py +0 -0
  67. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/__init__.py +0 -0
  68. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/fileio.py +0 -0
  69. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/polynomial.py +0 -0
  70. {eqc_models-0.10.1.data → eqc_models-0.11.0.data}/platlib/eqc_models/utilities/qplib.py +0 -0
  71. {eqc_models-0.10.1.dist-info → eqc_models-0.11.0.dist-info/licenses}/LICENSE.txt +0 -0
  72. {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,5 +0,0 @@
1
- # (C) Quantum Computing Inc., 2024.
2
-
3
- from .setcover import SetCoverModel
4
-
5
- __all__ = ["SetCoverModel"]
@@ -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