luna-quantum 0.0.29__py3-none-any.whl → 0.0.37__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.

Potentially problematic release.


This version of luna-quantum might be problematic. Click here for more details.

Files changed (36) hide show
  1. {luna_quantum-0.0.29.dist-info → luna_quantum-0.0.37.dist-info}/METADATA +1 -1
  2. {luna_quantum-0.0.29.dist-info → luna_quantum-0.0.37.dist-info}/RECORD +35 -33
  3. luna_sdk/controllers/luna_http_client.py +27 -0
  4. luna_sdk/controllers/luna_platform_client.py +34 -16
  5. luna_sdk/controllers/luna_q.py +5 -7
  6. luna_sdk/controllers/luna_solve.py +9 -7
  7. luna_sdk/controllers/luna_transform.py +13 -7
  8. luna_sdk/error/http_error_utils.py +10 -3
  9. luna_sdk/exceptions/encryption_exception.py +0 -6
  10. luna_sdk/interfaces/circuit_repo_i.py +0 -6
  11. luna_sdk/interfaces/optimization_repo_i.py +5 -4
  12. luna_sdk/interfaces/qpu_token_repo_i.py +18 -18
  13. luna_sdk/interfaces/solutions_repo_i.py +0 -6
  14. luna_sdk/repositories/circuit_repo.py +0 -11
  15. luna_sdk/repositories/optimization_repo.py +11 -10
  16. luna_sdk/repositories/qpu_token_repo.py +36 -27
  17. luna_sdk/repositories/solutions_repo.py +0 -8
  18. luna_sdk/schemas/create/circuit.py +0 -3
  19. luna_sdk/schemas/create/qpu_token.py +0 -3
  20. luna_sdk/schemas/create/solution.py +0 -1
  21. luna_sdk/schemas/enums/optimization.py +8 -7
  22. luna_sdk/schemas/enums/qpu_token_type.py +1 -1
  23. luna_sdk/schemas/optimization.py +53 -15
  24. luna_sdk/schemas/optimization_formats/qubo.py +8 -0
  25. luna_sdk/schemas/qpu_token.py +1 -5
  26. luna_sdk/schemas/rest/qpu_token/qpu_token_source.py +18 -0
  27. luna_sdk/schemas/rest/qpu_token/token_provider.py +47 -15
  28. luna_sdk/schemas/solution.py +89 -35
  29. luna_sdk/schemas/solver_parameters/dwave/base.py +4 -1
  30. luna_sdk/schemas/solver_parameters/dwave/quantum_annealing.py +13 -0
  31. luna_sdk/schemas/solver_parameters/dwave/repeated_reverse_quantum_annealing.py +13 -1
  32. luna_sdk/schemas/solver_parameters/dwave/tabu_search.py +2 -4
  33. luna_sdk/utils/qpu_tokens.py +14 -13
  34. luna_sdk/controllers/custom_login_client.py +0 -66
  35. {luna_quantum-0.0.29.dist-info → luna_quantum-0.0.37.dist-info}/LICENSE +0 -0
  36. {luna_quantum-0.0.29.dist-info → luna_quantum-0.0.37.dist-info}/WHEEL +0 -0
@@ -1,7 +1,9 @@
1
+ from datetime import datetime
1
2
  from typing import Any, Dict, List, Optional, Union
2
3
 
3
4
  from pydantic import BaseModel
4
5
 
6
+ from luna_sdk.schemas.enums.optimization import OptFormat
5
7
  from luna_sdk.schemas.enums.solution import SenseEnum
6
8
  from luna_sdk.schemas.enums.status import StatusEnum
7
9
  from luna_sdk.schemas.optimization import Optimization
@@ -105,45 +107,17 @@ class Solution(PrettyBase):
105
107
  solver: str
106
108
  provider: str
107
109
  status: StatusEnum
110
+ status_timeline: Dict[StatusEnum, datetime] = {}
111
+ used_format: Optional[OptFormat] = None
108
112
  optimization: Union[Optimization, str]
109
113
  representation: Optional[Any] = None
110
114
 
111
- def __str__(self):
112
- """Overwrite the default object string representation to use the custom pretty print console representation"""
113
- data = self.model_dump()
114
- results = data.pop("results") # Extract and remove results from data
115
- metadata = data.pop("metadata") # Extract and remove metadata from data
116
- provider = data["provider"]
115
+ verbose: bool = False
117
116
 
118
- divider = "--------------------------------------------------------------------------------\n"
119
-
120
- # Build Meta Data section
121
- output = f"{divider}META DATA:\n{divider}"
122
- output += self._pretty_print(data)
123
-
124
- # Build Results section
125
- if results:
126
- output += f"\n\n{divider}RESULTS:\n{divider}"
127
- for i, result in enumerate(results, start=1):
128
- r = f"Result {i}:\n"
129
- r += f" {result}\n"
130
- output += r
131
- else:
132
- output += f"\n\n{divider}RESULTS:\n{divider}"
133
- output += " No results..\n"
134
- output += " Solution has status: " + str(self.status.value) + "\n"
135
- if self.error_message:
136
- output += " Error message: " + str(self.error_message) + "\n"
137
-
138
- # Build Provider Meta Data section
139
- output += f"\n\n{divider}{provider.upper()} META DATA:\n{divider}"
140
- output += (
141
- self._pretty_print(metadata)
142
- if metadata
143
- else " No metadata from provider..\n"
144
- )
145
-
146
- return output
117
+ def __str__(self):
118
+ if self.verbose:
119
+ return self.details()
120
+ return self.subset()
147
121
 
148
122
  @property
149
123
  def head(self):
@@ -216,6 +190,86 @@ class Solution(PrettyBase):
216
190
 
217
191
  return output
218
192
 
193
+ def details(self):
194
+ """Overwrite the default object string representation to use the custom pretty print console representation"""
195
+ data = self.model_dump()
196
+ results = data.pop("results") # Extract and remove results from data
197
+ metadata = data.pop("metadata") # Extract and remove metadata from data
198
+ provider = data["provider"]
199
+
200
+ divider = "--------------------------------------------------------------------------------\n"
201
+
202
+ # Build Meta Data section
203
+ output = f"{divider}META DATA:\n{divider}"
204
+ output += self._pretty_print(data)
205
+
206
+ # Build Results section
207
+ if results:
208
+ output += f"\n\n{divider}RESULTS:\n{divider}"
209
+ for i, result in enumerate(results, start=1):
210
+ r = f"Result {i}:\n"
211
+ r += f" {result}\n"
212
+ output += r
213
+ else:
214
+ output += f"\n\n{divider}RESULTS:\n{divider}"
215
+ output += " No results..\n"
216
+ output += " Solution has status: " + str(self.status.value) + "\n"
217
+ if self.error_message:
218
+ output += " Error message: " + str(self.error_message) + "\n"
219
+
220
+ # Build Provider Meta Data section
221
+ output += f"\n\n{divider}{provider.upper()} META DATA:\n{divider}"
222
+ output += (
223
+ self._pretty_print(metadata)
224
+ if metadata
225
+ else " No metadata from provider..\n"
226
+ )
227
+ return output
228
+
229
+ def subset(self):
230
+ limit = 5
231
+ subset_keys = [
232
+ "id",
233
+ "name",
234
+ "status",
235
+ "solver",
236
+ "provider",
237
+ "runtime",
238
+ "optimization_name",
239
+ "created_date",
240
+ ]
241
+ data = self.model_dump()
242
+ data["optimization_name"] = data["optimization"]["original_format"]
243
+ output = ""
244
+ data_subset = {key: data.get(key) for key in subset_keys if key in data}
245
+ output += self._pretty_print(data_subset)
246
+
247
+ # Build Results section
248
+ results = data.pop("results")
249
+ if results:
250
+ output += "results:\n"
251
+ output += (
252
+ f"{len(results)} results found. Displaying first {limit} results.\n"
253
+ )
254
+ for i, result in enumerate(results, start=1):
255
+ if i > limit:
256
+ output += "....\n"
257
+ break
258
+ r = f"Result {i}:\n"
259
+ r += (
260
+ f" {str(result)[:150]} ....\n"
261
+ if len(str(result)) > 150
262
+ else f" {result}\n"
263
+ )
264
+ output += r
265
+ else:
266
+ output += "results:\n"
267
+ output += " No results..\n"
268
+ output += " Solution has status: " + str(self.status.value) + "\n"
269
+ if self.error_message:
270
+ output += " Error message: " + str(self.error_message) + "\n"
271
+ return output
272
+
219
273
 
220
274
  class UseCaseResult(BaseModel):
221
275
  representation: Any
@@ -376,6 +376,8 @@ class Loop(BaseParameter):
376
376
  Energy level that the algorithm tries to reach.
377
377
  rtol: float
378
378
  Relative tolerance for convergence.
379
+ rtol: float
380
+ Relative tolerance for convergence.
379
381
  atol: float
380
382
  Absolute tolerance for convergence.
381
383
  """
@@ -383,7 +385,8 @@ class Loop(BaseParameter):
383
385
  max_iter: Optional[int] = 100
384
386
  max_time: int = 5
385
387
  convergence: int = 3
386
- target: Optional[float] = DEFAULT_RTOL
388
+ target: Optional[float] = None
389
+ rtol: float = DEFAULT_RTOL
387
390
  atol: float = DEFAULT_ATOL
388
391
 
389
392
 
@@ -1,5 +1,13 @@
1
1
  from luna_sdk.schemas.solver_parameters.base_parameter import BaseParameter
2
2
  from luna_sdk.schemas.solver_parameters.dwave import Embedding, SamplingParams
3
+ from pydantic import StringConstraints
4
+
5
+ import sys
6
+
7
+ if sys.version_info >= (3, 9):
8
+ from typing import Annotated
9
+ else:
10
+ from typing_extensions import Annotated
3
11
 
4
12
 
5
13
  class QuantumAnnealingParameters(BaseParameter):
@@ -13,7 +21,12 @@ class QuantumAnnealingParameters(BaseParameter):
13
21
  sampling_params: SamplingParams
14
22
  Parameters for the sampling. See https://docs.dwavesys.com/docs/latest/c_solver_parameters.html
15
23
  for more details.
24
+ qpu_backend: str
25
+ Specific D-Wave quantum processing unit (QPU) for your optimization
16
26
  """
17
27
 
18
28
  embedding: Embedding = Embedding()
19
29
  sampling_params: SamplingParams = SamplingParams()
30
+ qpu_backend: Annotated[
31
+ str, StringConstraints(strip_whitespace=True, min_length=1)
32
+ ] = "default"
@@ -1,9 +1,16 @@
1
1
  from typing import Any, Dict, List, Optional
2
2
 
3
- from pydantic import BaseModel, Field
3
+ from pydantic import Field, StringConstraints
4
4
 
5
5
  from luna_sdk.schemas.solver_parameters.base_parameter import BaseParameter
6
6
 
7
+ import sys
8
+
9
+ if sys.version_info >= (3, 9):
10
+ from typing import Annotated
11
+ else:
12
+ from typing_extensions import Annotated
13
+
7
14
 
8
15
  class RRQuantumAnnealingSamplingParams(BaseParameter):
9
16
  """
@@ -69,6 +76,8 @@ class RepeatedReverseQuantumAnnealingParameters(BaseParameter):
69
76
  The target energy for the solving process.
70
77
  check_trivial: bool
71
78
  Whether to check for trivial variables. Checking for trivial variables means an overhead. On the other hand, when set to `False`, trivial variables, i.e., variables without interactions, will lead to a runtime error.
79
+ qpu_backend: str
80
+ Specific D-Wave quantum processing unit (QPU) for your optimization
72
81
  """
73
82
 
74
83
  sampling_params: RRQuantumAnnealingSamplingParams = (
@@ -82,3 +91,6 @@ class RepeatedReverseQuantumAnnealingParameters(BaseParameter):
82
91
  max_iter: int = 10
83
92
  target: Optional[Any] = None
84
93
  check_trivial: bool = True
94
+ qpu_backend: Annotated[
95
+ str, StringConstraints(strip_whitespace=True, min_length=1)
96
+ ] = "default"
@@ -1,11 +1,9 @@
1
1
  from typing import Any, Optional
2
2
 
3
- from pydantic import BaseModel
3
+ from .base import Tabu
4
4
 
5
- from luna_sdk.schemas.solver_parameters.base_parameter import BaseParameter
6
5
 
7
-
8
- class TabuSearchParameters(BaseParameter):
6
+ class TabuSearchParameters(Tabu):
9
7
  """
10
8
  Tabu Search is a heuristic optimization method that works with the help of a tabu list.
11
9
  Initially, random states are chosen in the solution landscape.
@@ -1,9 +1,10 @@
1
1
  import os
2
2
 
3
- from luna_sdk.schemas import QpuToken, QpuTokenSource
3
+ from luna_sdk.schemas.rest.qpu_token.qpu_token_source import _RESTQpuTokenSource
4
4
  from luna_sdk.schemas.rest.qpu_token.token_provider import (
5
5
  RestAPITokenProvider,
6
6
  AWSQpuTokens,
7
+ _RestQpuToken,
7
8
  )
8
9
 
9
10
 
@@ -15,37 +16,37 @@ def extract_qpu_tokens_from_env() -> RestAPITokenProvider:
15
16
  aws_access_key = os.environ.get("LUNA_AWS_ACCESS_KEY")
16
17
  aws_access_secret_key = os.environ.get("LUNA_AWS_SECRET_ACCESS_KEY")
17
18
  return RestAPITokenProvider(
18
- ibm=QpuToken(
19
- source=QpuTokenSource.INLINE,
19
+ ibm=_RestQpuToken(
20
+ source=_RESTQpuTokenSource.INLINE,
20
21
  token=ibm_token,
21
22
  )
22
23
  if ibm_token
23
24
  else None,
24
- dwave=QpuToken(
25
- source=QpuTokenSource.INLINE,
25
+ dwave=_RestQpuToken(
26
+ source=_RESTQpuTokenSource.INLINE,
26
27
  token=dwave_token,
27
28
  )
28
29
  if dwave_token
29
30
  else None,
30
- qctrl=QpuToken(
31
- source=QpuTokenSource.INLINE,
31
+ qctrl=_RestQpuToken(
32
+ source=_RESTQpuTokenSource.INLINE,
32
33
  token=qctrl_token,
33
34
  )
34
35
  if qctrl_token
35
36
  else None,
36
- fujitsu=QpuToken(
37
- source=QpuTokenSource.INLINE,
37
+ fujitsu=_RestQpuToken(
38
+ source=_RESTQpuTokenSource.INLINE,
38
39
  token=fujitsu_token,
39
40
  )
40
41
  if fujitsu_token
41
42
  else None,
42
43
  aws=AWSQpuTokens(
43
- aws_access_key=QpuToken(
44
- source=QpuTokenSource.INLINE,
44
+ aws_access_key=_RestQpuToken(
45
+ source=_RESTQpuTokenSource.INLINE,
45
46
  token=aws_access_key,
46
47
  ),
47
- aws_secret_access_key=QpuToken(
48
- source=QpuTokenSource.INLINE,
48
+ aws_secret_access_key=_RestQpuToken(
49
+ source=_RESTQpuTokenSource.INLINE,
49
50
  token=aws_access_secret_key,
50
51
  ),
51
52
  ),
@@ -1,66 +0,0 @@
1
- import logging
2
- from http.client import UNAUTHORIZED
3
- from importlib.metadata import version
4
-
5
- from httpx import Client, ReadTimeout, Response
6
-
7
- from luna_sdk.error.http_error_utils import HttpErrorUtils
8
- from luna_sdk.exceptions.timeout_exception import TimeoutException
9
-
10
-
11
- class CustomLoginClient(Client):
12
- _login_url: str
13
- _email: str
14
- _password: str
15
- _bearer_token: str
16
-
17
- _version: str = version("luna-quantum")
18
-
19
- _user_agent: str = f"LunaSDK/{_version}"
20
-
21
- def __init__(self, email: str, password: str, login_url: str, *args, **kwargs):
22
- super().__init__(*args, **kwargs)
23
- self._email = email
24
- self._password = password
25
- self._login_url = login_url
26
-
27
- self.headers["User-Agent"] = self._user_agent
28
-
29
- def login(self, email: str, password: str):
30
- with Client() as client:
31
- headers = {
32
- "accept": "application/json",
33
- "Content-Type": "application/x-www-form-urlencoded",
34
- "User-Agent": self._user_agent,
35
- }
36
-
37
- response: Response = client.post(
38
- self._login_url,
39
- data={"username": email, "password": password},
40
- headers=headers,
41
- # Ensure timeout is high enough. CustomLoginClient will be deleted in
42
- # near future anyway.
43
- timeout=None,
44
- )
45
- HttpErrorUtils.check_for_error(response)
46
-
47
- self._bearer_token = response.json()["access_token"]
48
-
49
- def request(self, *args, **kwargs) -> Response:
50
- try:
51
- response: Response = super().request(*args, **kwargs)
52
- if response.status_code == UNAUTHORIZED:
53
- logging.info("Unauthorized - trying to login")
54
-
55
- self.login(self._email, self._password)
56
-
57
- logging.info("Re-login successful")
58
- self.headers.update(
59
- headers={"authorization": f"Bearer {self._bearer_token}"}
60
- )
61
- logging.info("Trying request again")
62
- response = super().request(*args, **kwargs)
63
- except ReadTimeout:
64
- raise TimeoutException()
65
- HttpErrorUtils.check_for_error(response)
66
- return response