luna-quantum 0.0.16__py3-none-any.whl → 0.0.29__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.
- {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/METADATA +2 -1
- {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/RECORD +57 -53
- luna_sdk/controllers/custom_login_client.py +8 -3
- luna_sdk/controllers/luna_platform_client.py +9 -9
- luna_sdk/controllers/luna_q.py +7 -10
- luna_sdk/controllers/luna_solve.py +8 -15
- luna_sdk/controllers/luna_transform.py +9 -16
- luna_sdk/interfaces/circuit_repo_i.py +18 -12
- luna_sdk/interfaces/cplex_repo_i.py +25 -10
- luna_sdk/interfaces/info_repo_i.py +10 -3
- luna_sdk/interfaces/lp_repo_i.py +20 -8
- luna_sdk/interfaces/optimization_repo_i.py +31 -57
- luna_sdk/interfaces/qpu_token_repo_i.py +24 -28
- luna_sdk/interfaces/solutions_repo_i.py +44 -24
- luna_sdk/repositories/circuit_repo.py +11 -44
- luna_sdk/repositories/cplex_repo.py +32 -20
- luna_sdk/repositories/info_repo.py +4 -7
- luna_sdk/repositories/lp_repo.py +21 -15
- luna_sdk/repositories/optimization_repo.py +25 -200
- luna_sdk/repositories/qpu_token_repo.py +28 -120
- luna_sdk/repositories/solutions_repo.py +109 -181
- luna_sdk/schemas/create/solution.py +2 -2
- luna_sdk/schemas/optimization.py +9 -11
- luna_sdk/schemas/pretty_base.py +10 -3
- luna_sdk/schemas/qpu_token.py +3 -0
- luna_sdk/schemas/solution.py +7 -6
- luna_sdk/schemas/solver_info.py +31 -1
- luna_sdk/schemas/solver_parameters/aws/optimizer_params.py +40 -0
- luna_sdk/schemas/solver_parameters/aws/qaoa.py +36 -4
- luna_sdk/schemas/solver_parameters/base_parameter.py +5 -0
- luna_sdk/schemas/solver_parameters/dwave/base.py +15 -14
- luna_sdk/schemas/solver_parameters/dwave/dialectic_search.py +3 -2
- luna_sdk/schemas/solver_parameters/dwave/kerberos.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/leap_hybrid_bqm.py +2 -2
- luna_sdk/schemas/solver_parameters/dwave/leap_hybrid_cqm.py +2 -2
- luna_sdk/schemas/solver_parameters/dwave/parallel_tempering.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/parallel_tempering_qpu.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/population_annealing.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/population_annealing_qpu.py +2 -1
- luna_sdk/schemas/solver_parameters/dwave/qaga.py +4 -2
- luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_qpu.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_simulated_annealing.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_tabu_search.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/quantum_annealing.py +2 -3
- luna_sdk/schemas/solver_parameters/dwave/repeated_reverse_quantum_annealing.py +4 -2
- luna_sdk/schemas/solver_parameters/dwave/repeated_reverse_simulated_annealing.py +3 -2
- luna_sdk/schemas/solver_parameters/dwave/saga.py +1 -1
- luna_sdk/schemas/solver_parameters/dwave/tabu_search.py +3 -1
- luna_sdk/schemas/solver_parameters/fujitsu/base.py +5 -4
- luna_sdk/schemas/solver_parameters/fujitsu/partial_config.py +7 -5
- luna_sdk/schemas/solver_parameters/ibm/standard_parameters.py +121 -7
- luna_sdk/schemas/solver_parameters/qctrl/qaoa.py +2 -2
- luna_sdk/schemas/wrappers/__init__.py +1 -0
- luna_sdk/schemas/wrappers/datetime_wrapper.py +31 -0
- luna_sdk/utils/parameter_finder.py +90 -0
- luna_sdk/constants.py +0 -1
- {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/LICENSE +0 -0
- {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.29.dist-info}/WHEEL +0 -0
|
@@ -26,13 +26,16 @@ class QpuTokenRepo(IQpuTokenRepo):
|
|
|
26
26
|
else:
|
|
27
27
|
return f"{self._endpoint}/organization"
|
|
28
28
|
|
|
29
|
-
def _get_by_name(
|
|
29
|
+
def _get_by_name(
|
|
30
|
+
self, name: str, token_type: QpuTokenTypeEnum, **kwargs
|
|
31
|
+
) -> QpuTokenOut:
|
|
30
32
|
response: Response = self._client.get(
|
|
31
|
-
f"{self._get_endpoint_by_type(token_type)}/by_name/{name}"
|
|
33
|
+
f"{self._get_endpoint_by_type(token_type)}/by_name/{name}", **kwargs
|
|
32
34
|
)
|
|
33
35
|
response.raise_for_status()
|
|
34
36
|
|
|
35
37
|
qpu_token_data = response.json()
|
|
38
|
+
qpu_token_data["token_type"] = token_type
|
|
36
39
|
return QpuTokenOut.model_validate(qpu_token_data)
|
|
37
40
|
|
|
38
41
|
def create(
|
|
@@ -40,32 +43,10 @@ class QpuTokenRepo(IQpuTokenRepo):
|
|
|
40
43
|
name: str,
|
|
41
44
|
provider: str,
|
|
42
45
|
token: str,
|
|
43
|
-
token_type: QpuTokenTypeEnum
|
|
46
|
+
token_type: QpuTokenTypeEnum,
|
|
44
47
|
encryption_key: Optional[str] = None,
|
|
48
|
+
**kwargs,
|
|
45
49
|
) -> QpuTokenOut:
|
|
46
|
-
"""
|
|
47
|
-
Create user QPU token
|
|
48
|
-
|
|
49
|
-
Parameters
|
|
50
|
-
----------
|
|
51
|
-
name: str
|
|
52
|
-
Name of the QPU token
|
|
53
|
-
provider: str
|
|
54
|
-
Name of provider
|
|
55
|
-
token: str
|
|
56
|
-
Token
|
|
57
|
-
token_type: QpuTokenTypeEnum
|
|
58
|
-
There are two types of QPU tokens: PERSONAL and ORGANIZATION.
|
|
59
|
-
The default value is PERSONAL.
|
|
60
|
-
All users of an organization can use organization QPU tokens.
|
|
61
|
-
User QPU tokens can only be used by the user who created them.
|
|
62
|
-
encryption_key: Optional[str]
|
|
63
|
-
Encryption key to be used for encryption of QPU tokens.
|
|
64
|
-
Returns
|
|
65
|
-
-------
|
|
66
|
-
QpuTokenOut
|
|
67
|
-
QpuTokenOut instances.
|
|
68
|
-
"""
|
|
69
50
|
encryption_key = encryption_key or os.environ.get("LUNA_ENCRYPTION_KEY")
|
|
70
51
|
if encryption_key is None:
|
|
71
52
|
raise EncryptionNotSetException
|
|
@@ -77,10 +58,13 @@ class QpuTokenRepo(IQpuTokenRepo):
|
|
|
77
58
|
)
|
|
78
59
|
|
|
79
60
|
response: Response = self._client.post(
|
|
80
|
-
self._get_endpoint_by_type(token_type),
|
|
61
|
+
self._get_endpoint_by_type(token_type),
|
|
62
|
+
content=qpu_token.model_dump_json(),
|
|
63
|
+
**kwargs,
|
|
81
64
|
)
|
|
82
65
|
response.raise_for_status()
|
|
83
66
|
qpu_token_data = response.json()
|
|
67
|
+
qpu_token_data["token_type"] = token_type
|
|
84
68
|
return QpuTokenOut.model_validate(qpu_token_data)
|
|
85
69
|
|
|
86
70
|
def get_all(
|
|
@@ -88,30 +72,8 @@ class QpuTokenRepo(IQpuTokenRepo):
|
|
|
88
72
|
filter_provider: Optional[str] = None,
|
|
89
73
|
name: Optional[str] = None,
|
|
90
74
|
token_type: Optional[QpuTokenTypeEnum] = None,
|
|
75
|
+
**kwargs,
|
|
91
76
|
) -> Dict[QpuTokenTypeEnum, List[QpuTokenOut]]:
|
|
92
|
-
"""
|
|
93
|
-
Retrieve list of user QPU tokens.
|
|
94
|
-
|
|
95
|
-
Parameters
|
|
96
|
-
----------
|
|
97
|
-
filter_provider: Optional[str]
|
|
98
|
-
The provider for which qpu tokens should be retrieved
|
|
99
|
-
name: Optional[str]
|
|
100
|
-
Name of the QPU token that should be retrieved
|
|
101
|
-
token_type: Optional[QpuTokenTypeEnum]
|
|
102
|
-
If you want to retrieve only user or organization QPU tokens
|
|
103
|
-
otherwise all QPU tokens will be retrieved
|
|
104
|
-
token_type: QpuTokenTypeEnum
|
|
105
|
-
There are two types of QPU tokens: PERSONAL and ORGANIZATION.
|
|
106
|
-
The default value is PERSONAL.
|
|
107
|
-
All users of an organization can use organization QPU tokens.
|
|
108
|
-
User QPU tokens can only be used by the user who created them.
|
|
109
|
-
|
|
110
|
-
Returns
|
|
111
|
-
-------
|
|
112
|
-
Dict[QpuTokenTypeEnum, List[QpuTokenOut]]
|
|
113
|
-
List of QpuTokenOut instances.
|
|
114
|
-
"""
|
|
115
77
|
params = {}
|
|
116
78
|
if filter_provider:
|
|
117
79
|
params["filter_provider"] = filter_provider
|
|
@@ -119,14 +81,19 @@ class QpuTokenRepo(IQpuTokenRepo):
|
|
|
119
81
|
if name:
|
|
120
82
|
params["name"] = name
|
|
121
83
|
|
|
122
|
-
response = self._client.get(
|
|
84
|
+
response = self._client.get(
|
|
85
|
+
self._get_endpoint_by_type(), params=params, **kwargs
|
|
86
|
+
)
|
|
123
87
|
response.raise_for_status()
|
|
124
88
|
|
|
125
89
|
to_return: Dict[QpuTokenTypeEnum, List[QpuTokenOut]] = {}
|
|
126
90
|
for key, value in response.json().items():
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
91
|
+
tokens = []
|
|
92
|
+
for item in value:
|
|
93
|
+
if token_type is None or token_type.value == key:
|
|
94
|
+
item["token_type"] = QpuTokenTypeEnum(key)
|
|
95
|
+
tokens.append(QpuTokenOut.model_validate(item))
|
|
96
|
+
to_return[QpuTokenTypeEnum(key)] = tokens
|
|
130
97
|
|
|
131
98
|
return to_return
|
|
132
99
|
|
|
@@ -134,56 +101,15 @@ class QpuTokenRepo(IQpuTokenRepo):
|
|
|
134
101
|
self,
|
|
135
102
|
name: str,
|
|
136
103
|
token_type: QpuTokenTypeEnum = QpuTokenTypeEnum.PERSONAL,
|
|
104
|
+
**kwargs,
|
|
137
105
|
) -> QpuTokenOut:
|
|
138
|
-
|
|
139
|
-
Retrieve user QPU token by id
|
|
140
|
-
|
|
141
|
-
Parameters
|
|
142
|
-
----------
|
|
143
|
-
name: str
|
|
144
|
-
Name of the QPU token that should be retrieved
|
|
145
|
-
token_type: QpuTokenTypeEnum
|
|
146
|
-
There are two types of QPU tokens: PERSONAL and ORGANIZATION.
|
|
147
|
-
The default value is PERSONAL.
|
|
148
|
-
All users of an organization can use organization QPU tokens.
|
|
149
|
-
User QPU tokens can only be used by the user who created them.
|
|
150
|
-
|
|
151
|
-
Returns
|
|
152
|
-
-------
|
|
153
|
-
QpuTokenOut
|
|
154
|
-
QpuTokenOut instance.
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
qpu_token: QpuTokenOut = self._get_by_name(name, token_type)
|
|
106
|
+
qpu_token: QpuTokenOut = self._get_by_name(name, token_type, **kwargs)
|
|
158
107
|
|
|
159
108
|
return qpu_token
|
|
160
109
|
|
|
161
110
|
def rename(
|
|
162
|
-
self,
|
|
163
|
-
name: str,
|
|
164
|
-
new_name: str,
|
|
165
|
-
token_type: QpuTokenTypeEnum = QpuTokenTypeEnum.PERSONAL,
|
|
111
|
+
self, name: str, new_name: str, token_type: QpuTokenTypeEnum, **kwargs
|
|
166
112
|
) -> QpuTokenOut:
|
|
167
|
-
"""
|
|
168
|
-
Update user QPU token by id
|
|
169
|
-
|
|
170
|
-
Parameters
|
|
171
|
-
----------
|
|
172
|
-
name: str
|
|
173
|
-
Name of the QPU token that should be updated
|
|
174
|
-
new_name: str
|
|
175
|
-
The new name
|
|
176
|
-
token_type: QpuTokenTypeEnum
|
|
177
|
-
There are two types of QPU tokens: PERSONAL and ORGANIZATION.
|
|
178
|
-
The default value is PERSONAL.
|
|
179
|
-
All users of an organization can use organization QPU tokens.
|
|
180
|
-
User QPU tokens can only be used by the user who created them.
|
|
181
|
-
|
|
182
|
-
Returns
|
|
183
|
-
-------
|
|
184
|
-
QpuTokenOut
|
|
185
|
-
QpuTokenOut instance.
|
|
186
|
-
"""
|
|
187
113
|
qpu_token_update_data = {"name": new_name}
|
|
188
114
|
|
|
189
115
|
token: QpuTokenOut = self.get(name, token_type)
|
|
@@ -191,36 +117,18 @@ class QpuTokenRepo(IQpuTokenRepo):
|
|
|
191
117
|
response = self._client.put(
|
|
192
118
|
f"{self._get_endpoint_by_type(token_type)}/{token.id}",
|
|
193
119
|
content=json.dumps(qpu_token_update_data),
|
|
120
|
+
**kwargs,
|
|
194
121
|
)
|
|
195
122
|
response.raise_for_status()
|
|
196
123
|
|
|
197
124
|
qpu_token_data = response.json()
|
|
125
|
+
qpu_token_data["token_type"] = token_type
|
|
198
126
|
return QpuTokenOut.model_validate(qpu_token_data)
|
|
199
127
|
|
|
200
|
-
def delete(
|
|
201
|
-
self,
|
|
202
|
-
name: str,
|
|
203
|
-
token_type: QpuTokenTypeEnum = QpuTokenTypeEnum.PERSONAL,
|
|
204
|
-
) -> None:
|
|
205
|
-
"""
|
|
206
|
-
Delete organization QPU token by name
|
|
207
|
-
|
|
208
|
-
Parameters
|
|
209
|
-
----------
|
|
210
|
-
name: str
|
|
211
|
-
Name of the QPU token that should be deleted
|
|
212
|
-
token_type: QpuTokenTypeEnum
|
|
213
|
-
There are two types of QPU tokens: PERSONAL and ORGANIZATION.
|
|
214
|
-
The default value is PERSONAL.
|
|
215
|
-
All users of an organization can use organization QPU tokens.
|
|
216
|
-
User QPU tokens can only be used by the user who created them.
|
|
217
|
-
|
|
218
|
-
Returns
|
|
219
|
-
-------
|
|
220
|
-
"""
|
|
128
|
+
def delete(self, name: str, token_type: QpuTokenTypeEnum, **kwargs) -> None:
|
|
221
129
|
token: QpuTokenOut = self.get(name, token_type)
|
|
222
130
|
|
|
223
131
|
response = self._client.delete(
|
|
224
|
-
f"{self._get_endpoint_by_type(token_type)}/{token.id}"
|
|
132
|
+
f"{self._get_endpoint_by_type(token_type)}/{token.id}", **kwargs
|
|
225
133
|
)
|
|
226
134
|
response.raise_for_status()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
from time import sleep
|
|
4
|
-
from typing import Dict, List, Optional, Union
|
|
4
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel
|
|
6
|
+
from pydantic import BaseModel, ValidationError
|
|
7
7
|
|
|
8
8
|
from luna_sdk.exceptions.encryption_exception import EncryptionNotSetException
|
|
9
9
|
from luna_sdk.interfaces.solutions_repo_i import ISolutionsRepo
|
|
@@ -19,32 +19,15 @@ from luna_sdk.schemas.solution import (
|
|
|
19
19
|
UseCaseRepresentation,
|
|
20
20
|
UseCaseResult,
|
|
21
21
|
)
|
|
22
|
+
from luna_sdk.utils.parameter_finder import get_parameter_by_solver
|
|
22
23
|
from luna_sdk.utils.qpu_tokens import extract_qpu_tokens_from_env
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class SolutionsRepo(ISolutionsRepo):
|
|
26
27
|
_endpoint = "/solutions"
|
|
27
28
|
|
|
28
|
-
def get(
|
|
29
|
-
self,
|
|
30
|
-
solution_id: str,
|
|
31
|
-
) -> Solution:
|
|
32
|
-
"""
|
|
33
|
-
Retrieve one solution by id.
|
|
34
|
-
|
|
35
|
-
Parameters
|
|
36
|
-
----------
|
|
37
|
-
solution_id: str
|
|
38
|
-
Id of the solution that should be retrieved
|
|
39
|
-
|
|
40
|
-
Returns
|
|
41
|
-
-------
|
|
42
|
-
Solution
|
|
43
|
-
Solution instance
|
|
44
|
-
"""
|
|
45
|
-
response = self._client.get(
|
|
46
|
-
f"{self._endpoint}/{solution_id}",
|
|
47
|
-
)
|
|
29
|
+
def get(self, solution_id: str, **kwargs) -> Solution:
|
|
30
|
+
response = self._client.get(f"{self._endpoint}/{solution_id}", **kwargs)
|
|
48
31
|
|
|
49
32
|
response.raise_for_status()
|
|
50
33
|
|
|
@@ -56,26 +39,8 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
56
39
|
limit: int = 50,
|
|
57
40
|
offset: int = 0,
|
|
58
41
|
optimization_id: Optional[str] = None,
|
|
42
|
+
**kwargs,
|
|
59
43
|
) -> List[Solution]:
|
|
60
|
-
"""
|
|
61
|
-
Get list of available optimizations.
|
|
62
|
-
|
|
63
|
-
Parameters
|
|
64
|
-
----------
|
|
65
|
-
timeframe: Optional[TimeframeEnum]
|
|
66
|
-
Only return Solutions created within a specified timeframe. Default None.
|
|
67
|
-
limit:
|
|
68
|
-
Limit the number of Optimizations to be returned. Default value 10.
|
|
69
|
-
offset:
|
|
70
|
-
Offset the list of solutions by this amount. Default value 0.
|
|
71
|
-
optimization_id: Optional[str]
|
|
72
|
-
Show solutions for only this optimization id. Default None.
|
|
73
|
-
|
|
74
|
-
Returns
|
|
75
|
-
-------
|
|
76
|
-
List[SolutionOut]
|
|
77
|
-
List of SolutionOut instances.
|
|
78
|
-
"""
|
|
79
44
|
params = {}
|
|
80
45
|
if timeframe and timeframe != TimeframeEnum.all_time: # no value == all_time
|
|
81
46
|
params["timeframe"] = timeframe.value
|
|
@@ -89,30 +54,14 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
89
54
|
|
|
90
55
|
params["limit"] = str(limit)
|
|
91
56
|
params["offset"] = str(offset)
|
|
92
|
-
response = self._client.get(
|
|
93
|
-
self._endpoint,
|
|
94
|
-
params=params,
|
|
95
|
-
)
|
|
57
|
+
response = self._client.get(self._endpoint, params=params, **kwargs)
|
|
96
58
|
|
|
97
59
|
response.raise_for_status()
|
|
98
60
|
|
|
99
61
|
return [Solution.model_validate(i) for i in response.json()]
|
|
100
62
|
|
|
101
|
-
def delete(self, solution_id: str) -> None:
|
|
102
|
-
""
|
|
103
|
-
Delete one optimization by id.
|
|
104
|
-
|
|
105
|
-
Parameters
|
|
106
|
-
----------
|
|
107
|
-
solution_id: str
|
|
108
|
-
Id of the optimization that should be deleted
|
|
109
|
-
|
|
110
|
-
Returns
|
|
111
|
-
-------
|
|
112
|
-
"""
|
|
113
|
-
self._client.delete(
|
|
114
|
-
f"{self._endpoint}/{solution_id}",
|
|
115
|
-
)
|
|
63
|
+
def delete(self, solution_id: str, **kwargs) -> None:
|
|
64
|
+
self._client.delete(f"{self._endpoint}/{solution_id}", **kwargs)
|
|
116
65
|
|
|
117
66
|
def create(
|
|
118
67
|
self,
|
|
@@ -120,39 +69,16 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
120
69
|
solver_name: str,
|
|
121
70
|
provider: str,
|
|
122
71
|
qpu_tokens: Optional[TokenProvider] = None,
|
|
123
|
-
solver_parameters: Optional[Union[Dict, BaseModel]] = None,
|
|
72
|
+
solver_parameters: Optional[Union[Dict[str, Any], BaseModel]] = None,
|
|
124
73
|
encryption_key: Optional[str] = None,
|
|
125
74
|
name: Optional[str] = None,
|
|
75
|
+
fail_on_invalid_params: bool = True,
|
|
76
|
+
**kwargs,
|
|
126
77
|
) -> Solution:
|
|
127
|
-
"""
|
|
128
|
-
Create a solution for an optimization
|
|
129
|
-
|
|
130
|
-
Parameters
|
|
131
|
-
----------
|
|
132
|
-
optimization_id: str
|
|
133
|
-
The id of the optimization for which solution should be created
|
|
134
|
-
solver_name: str
|
|
135
|
-
The name of the solver to use.
|
|
136
|
-
provider: str
|
|
137
|
-
The name of the QPU provider to use.
|
|
138
|
-
qpu_tokens: Optional[TokenProvider]
|
|
139
|
-
The tokens to be used for the QPU.
|
|
140
|
-
solver_parameters: Optional[Dict]
|
|
141
|
-
Parameters to be passed to the solver.
|
|
142
|
-
encryption_key: Optional[str]
|
|
143
|
-
Encryption key to be used for encryption of QPU tokens.
|
|
144
|
-
name: Optional[str]
|
|
145
|
-
Default: None, The name of the solution to create.
|
|
146
|
-
|
|
147
|
-
Returns
|
|
148
|
-
-------
|
|
149
|
-
SolutionOut
|
|
150
|
-
Returns the location where the solution can be found once solving is complete.
|
|
151
|
-
"""
|
|
152
|
-
if solver_parameters is None:
|
|
153
|
-
solver_parameters = {}
|
|
154
78
|
if qpu_tokens is not None:
|
|
155
|
-
rest_qpu_tokens = RestAPITokenProvider.from_sdk_token_provider(
|
|
79
|
+
rest_qpu_tokens = RestAPITokenProvider.from_sdk_token_provider(
|
|
80
|
+
TokenProvider.model_validate(qpu_tokens)
|
|
81
|
+
)
|
|
156
82
|
else:
|
|
157
83
|
rest_qpu_tokens = None
|
|
158
84
|
# try to retrieve qpu tokens from env variables
|
|
@@ -162,21 +88,21 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
162
88
|
encryption_key = encryption_key or os.environ.get("LUNA_ENCRYPTION_KEY")
|
|
163
89
|
if encryption_key is None:
|
|
164
90
|
raise EncryptionNotSetException
|
|
91
|
+
params = SolutionsRepo.validate_solver_params(
|
|
92
|
+
solver_name, provider, solver_parameters, fail_on_invalid_params
|
|
93
|
+
)
|
|
94
|
+
|
|
165
95
|
solution_in = SolutionIn(
|
|
166
96
|
optimization=optimization_id,
|
|
167
97
|
solver_name=solver_name,
|
|
168
98
|
provider=provider,
|
|
169
|
-
parameters=
|
|
170
|
-
solver_parameters.model_dump()
|
|
171
|
-
if isinstance(solver_parameters, BaseModel)
|
|
172
|
-
else solver_parameters
|
|
173
|
-
),
|
|
99
|
+
parameters=params,
|
|
174
100
|
qpu_tokens=rest_qpu_tokens,
|
|
175
101
|
encryption_key=encryption_key,
|
|
176
102
|
name=name,
|
|
177
103
|
)
|
|
178
104
|
response = self._client.post(
|
|
179
|
-
self._endpoint, content=solution_in.model_dump_json()
|
|
105
|
+
self._endpoint, content=solution_in.model_dump_json(), **kwargs
|
|
180
106
|
)
|
|
181
107
|
response.raise_for_status()
|
|
182
108
|
|
|
@@ -188,53 +114,19 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
188
114
|
solver_name: str,
|
|
189
115
|
provider: str,
|
|
190
116
|
qpu_tokens: Optional[TokenProvider] = None,
|
|
191
|
-
solver_parameters: Optional[Union[Dict, BaseModel]] = None,
|
|
117
|
+
solver_parameters: Optional[Union[Dict[str, Any], BaseModel]] = None,
|
|
192
118
|
sleep_time_max: float = 60.0,
|
|
193
119
|
sleep_time_increment: float = 5.0,
|
|
194
120
|
sleep_time_initial: float = 5.0,
|
|
195
121
|
encryption_key: Optional[str] = None,
|
|
196
122
|
name: Optional[str] = None,
|
|
123
|
+
fail_on_invalid_params: bool = True,
|
|
124
|
+
**kwargs,
|
|
197
125
|
) -> Solution:
|
|
198
|
-
"""
|
|
199
|
-
Create a solution for optimization. This method will block your code until the solution is ready.
|
|
200
|
-
Depending on the problem size, this can take a long time.
|
|
201
|
-
|
|
202
|
-
Parameters
|
|
203
|
-
----------
|
|
204
|
-
optimization_id: str
|
|
205
|
-
The id of the optimization for which solution should be created
|
|
206
|
-
solver_name: str
|
|
207
|
-
The name of the solver to use.
|
|
208
|
-
provider: str
|
|
209
|
-
The name of the provider to use.
|
|
210
|
-
|
|
211
|
-
qpu_tokens: Optional[TokenProvider] = None
|
|
212
|
-
The tokens to be used for the QPU.
|
|
213
|
-
solver_parameters: Optional[Dict]
|
|
214
|
-
Parameters to be passed to the solver.
|
|
215
|
-
|
|
216
|
-
sleep_time_max: float
|
|
217
|
-
Maximum time to sleep between requests.
|
|
218
|
-
sleep_time_increment: float
|
|
219
|
-
Increment of sleep time between requests. Initial sleep time will be
|
|
220
|
-
sleep_time_initial: float
|
|
221
|
-
Initial sleep time.
|
|
222
|
-
encryption_key: Optional[str]
|
|
223
|
-
Encryption key to be used for encryption of QPU tokens.
|
|
224
|
-
name: Optional[str]
|
|
225
|
-
Default: None, The name of the solution to create.
|
|
226
|
-
Returns
|
|
227
|
-
-------
|
|
228
|
-
SolutionOut
|
|
229
|
-
Returns the location where the solution can be found once solving is complete.
|
|
230
|
-
"""
|
|
231
126
|
# First create the solution
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
params = solver_parameters
|
|
236
|
-
if isinstance(solver_parameters, BaseModel):
|
|
237
|
-
params = solver_parameters.dict()
|
|
127
|
+
params = SolutionsRepo.validate_solver_params(
|
|
128
|
+
solver_name, provider, solver_parameters, fail_on_invalid_params
|
|
129
|
+
)
|
|
238
130
|
|
|
239
131
|
solution: Solution = self.create(
|
|
240
132
|
optimization_id=optimization_id,
|
|
@@ -244,6 +136,7 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
244
136
|
qpu_tokens=qpu_tokens,
|
|
245
137
|
encryption_key=encryption_key,
|
|
246
138
|
name=name,
|
|
139
|
+
**kwargs,
|
|
247
140
|
)
|
|
248
141
|
# times are in sec
|
|
249
142
|
|
|
@@ -271,44 +164,20 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
271
164
|
if cur_sleep_time > sleep_time_max:
|
|
272
165
|
cur_sleep_time = sleep_time_max
|
|
273
166
|
|
|
274
|
-
solution = self.get(solution_id=solution.id)
|
|
167
|
+
solution = self.get(solution_id=solution.id, **kwargs)
|
|
275
168
|
|
|
276
169
|
return solution
|
|
277
170
|
|
|
278
|
-
def get_use_case_representation(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
solution_id: str
|
|
285
|
-
Id of the solution that should be retrieved
|
|
286
|
-
|
|
287
|
-
Returns
|
|
288
|
-
-------
|
|
289
|
-
UseCaseRepresentation
|
|
290
|
-
The use-case-specific representation
|
|
291
|
-
"""
|
|
292
|
-
response = self._client.get(f"{self._endpoint}/{solution_id}/representation")
|
|
171
|
+
def get_use_case_representation(
|
|
172
|
+
self, solution_id: str, **kwargs
|
|
173
|
+
) -> UseCaseRepresentation:
|
|
174
|
+
response = self._client.get(
|
|
175
|
+
f"{self._endpoint}/{solution_id}/representation", **kwargs
|
|
176
|
+
)
|
|
293
177
|
response.raise_for_status()
|
|
294
178
|
return UseCaseRepresentation.model_validate_json(response.text)
|
|
295
179
|
|
|
296
180
|
def get_best_result(self, solution: Solution) -> Optional[Result]:
|
|
297
|
-
"""
|
|
298
|
-
Retrieves the best result from a solution.
|
|
299
|
-
|
|
300
|
-
Parameters
|
|
301
|
-
----------
|
|
302
|
-
solution : Solution
|
|
303
|
-
The solution received via `solutions.get` or `solutions.get_all`.
|
|
304
|
-
|
|
305
|
-
Returns
|
|
306
|
-
-------
|
|
307
|
-
Result | None
|
|
308
|
-
The best result of the solution. If there are several best solutions with
|
|
309
|
-
the same objective value, return only the first. If the solution results are
|
|
310
|
-
not (yet) available or no the solution sense is `None`, return `None`.
|
|
311
|
-
"""
|
|
312
181
|
if solution.results is None or solution.sense is None:
|
|
313
182
|
return None
|
|
314
183
|
|
|
@@ -320,21 +189,6 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
320
189
|
def get_best_use_case_result(
|
|
321
190
|
self, use_case_representation: UseCaseRepresentation
|
|
322
191
|
) -> Optional[UseCaseResult]:
|
|
323
|
-
"""
|
|
324
|
-
Retrieves the best result from a solution's use case representation.
|
|
325
|
-
|
|
326
|
-
Parameters
|
|
327
|
-
----------
|
|
328
|
-
use_case_representation : UseCaseRepresentation
|
|
329
|
-
A solution's use case representation.
|
|
330
|
-
|
|
331
|
-
Returns
|
|
332
|
-
-------
|
|
333
|
-
UseCaseResult | None
|
|
334
|
-
The best result of the solution. If there are several best solutions with
|
|
335
|
-
the same objective value, return only the first. If the solution results are
|
|
336
|
-
not (yet) available or no the solution sense is `None`, return `None`.
|
|
337
|
-
"""
|
|
338
192
|
if (
|
|
339
193
|
use_case_representation.results is None
|
|
340
194
|
or use_case_representation.sense is None
|
|
@@ -345,3 +199,77 @@ class SolutionsRepo(ISolutionsRepo):
|
|
|
345
199
|
best_result = agg(use_case_representation.results, key=lambda x: x.obj_value)
|
|
346
200
|
|
|
347
201
|
return best_result
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def validate_solver_params(
|
|
205
|
+
solver: str,
|
|
206
|
+
provider: str,
|
|
207
|
+
solver_parameter: Optional[Union[Dict[str, Any], BaseModel]],
|
|
208
|
+
fail_on_invalid_params: bool = True,
|
|
209
|
+
) -> Dict[str, Any]:
|
|
210
|
+
"""
|
|
211
|
+
This function checks if the params provided are valid for the provided solver
|
|
212
|
+
and provider.
|
|
213
|
+
If no parameter class was found, there will be no check.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
solver: str
|
|
218
|
+
The solver
|
|
219
|
+
provider: str
|
|
220
|
+
The provider
|
|
221
|
+
solver_parameter: Optional[Union[Dict[str, Any], BaseModel]]
|
|
222
|
+
The solver parameter
|
|
223
|
+
fail_on_invalid_params: bool
|
|
224
|
+
Default true.
|
|
225
|
+
If True, a ValueError will be raised, if the solver_parameter are invalid.
|
|
226
|
+
Otherwise, there will only a warning.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
Dict[str, Any]
|
|
231
|
+
Validated solver params
|
|
232
|
+
|
|
233
|
+
Raises
|
|
234
|
+
-------
|
|
235
|
+
ValidationError
|
|
236
|
+
If the object could not be validated.
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
if solver_parameter is None:
|
|
240
|
+
logging.info(
|
|
241
|
+
"You didn't provide any specific solver parameters, so we chose the "
|
|
242
|
+
"default ones for this solver."
|
|
243
|
+
)
|
|
244
|
+
return {}
|
|
245
|
+
|
|
246
|
+
params: Dict[str, Any]
|
|
247
|
+
if isinstance(solver_parameter, BaseModel):
|
|
248
|
+
params = solver_parameter.dict()
|
|
249
|
+
else:
|
|
250
|
+
params = solver_parameter
|
|
251
|
+
|
|
252
|
+
parameter_class: Optional[Type[BaseModel]] = get_parameter_by_solver(
|
|
253
|
+
solver, provider
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if parameter_class is None:
|
|
257
|
+
logging.info(
|
|
258
|
+
f"SDK was not able to find a parameter for solver {solver} "
|
|
259
|
+
f"and provider {provider}."
|
|
260
|
+
)
|
|
261
|
+
return params
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
parameter_class.model_validate(params)
|
|
265
|
+
|
|
266
|
+
except ValidationError as e:
|
|
267
|
+
if fail_on_invalid_params:
|
|
268
|
+
raise e
|
|
269
|
+
logging.warning(
|
|
270
|
+
"The validation for the provided solver parameter failed.\n"
|
|
271
|
+
f"Detected error:\n{e}\n"
|
|
272
|
+
"Since continue on error is enabled, no error was raised.\n"
|
|
273
|
+
"Solving with Luna can still fail due to the parameters."
|
|
274
|
+
)
|
|
275
|
+
return params
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict, Optional
|
|
1
|
+
from typing import Dict, Optional, Any
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
@@ -9,7 +9,7 @@ class SolutionIn(BaseModel):
|
|
|
9
9
|
optimization: str # id of the optimization
|
|
10
10
|
solver_name: str
|
|
11
11
|
provider: str
|
|
12
|
-
parameters: Dict
|
|
12
|
+
parameters: Dict[str, Any]
|
|
13
13
|
qpu_tokens: Optional[RestAPITokenProvider] = None
|
|
14
14
|
encryption_key: str
|
|
15
15
|
name: Optional[str] = None
|
luna_sdk/schemas/optimization.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
|
3
2
|
|
|
4
|
-
from pydantic import BaseModel,
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
4
|
|
|
6
5
|
from luna_sdk.schemas.enums.optimization import InputType
|
|
7
6
|
from luna_sdk.schemas.optimization_formats.bqm import BQMSchema
|
|
8
7
|
from luna_sdk.schemas.optimization_formats.cqm import CQMSchema
|
|
9
8
|
from luna_sdk.schemas.optimization_formats.lp import LPSchema
|
|
10
9
|
from luna_sdk.schemas.pretty_base import PrettyBase
|
|
10
|
+
from luna_sdk.schemas.wrappers import PydanticDatetimeWrapper
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Optimization(PrettyBase):
|
|
@@ -17,11 +17,11 @@ class Optimization(PrettyBase):
|
|
|
17
17
|
----------
|
|
18
18
|
id: str
|
|
19
19
|
Id of the optimization
|
|
20
|
-
created_date: Optional[
|
|
20
|
+
created_date: Optional[DatetimeWrapper]
|
|
21
21
|
Date when optimization was created
|
|
22
22
|
created_by: Optional[str]
|
|
23
23
|
Id of the user who created optimization
|
|
24
|
-
modified_date: Optional[
|
|
24
|
+
modified_date: Optional[DatetimeWrapper]
|
|
25
25
|
Date when optimization was modified
|
|
26
26
|
modified_by: Optional[str]
|
|
27
27
|
Id of the user who modified optimization
|
|
@@ -29,25 +29,23 @@ class Optimization(PrettyBase):
|
|
|
29
29
|
|
|
30
30
|
id: str
|
|
31
31
|
name: Optional[str] = None
|
|
32
|
-
created_date:
|
|
32
|
+
created_date: PydanticDatetimeWrapper
|
|
33
33
|
created_by: str
|
|
34
|
-
modified_date: Optional[
|
|
34
|
+
modified_date: Optional[PydanticDatetimeWrapper] = None
|
|
35
35
|
modified_by: Optional[str] = None
|
|
36
36
|
input_type: Optional[InputType] = None
|
|
37
37
|
use_case_name: Optional[str] = None
|
|
38
38
|
params: Optional[Dict[str, Any]] = None
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
extra = Extra.ignore
|
|
42
|
-
from_attributes = False
|
|
40
|
+
model_config = ConfigDict(extra="ignore", from_attributes=False)
|
|
43
41
|
|
|
44
42
|
|
|
45
43
|
class OptimizationQubo(BaseModel):
|
|
46
44
|
id: str
|
|
47
45
|
name: Optional[str] = None
|
|
48
|
-
created_date:
|
|
46
|
+
created_date: PydanticDatetimeWrapper
|
|
49
47
|
created_by: str
|
|
50
|
-
modified_date: Optional[
|
|
48
|
+
modified_date: Optional[PydanticDatetimeWrapper] = None
|
|
51
49
|
modified_by: Optional[str] = None
|
|
52
50
|
|
|
53
51
|
matrix: List[List[float]]
|